C# · 12月 20, 2021

一、目录

<h1 style=”text-align: center;”>《Effective C#》快速笔记 – C# 中的动态编程
<p style=”text-align: center;”>

<img style=”max-width: 100%;” src=”https://www.jb51.cc/res/2019/01-27/18/447a0be01a1699650c015e43e769b8c7.png” alt=””>

  静态类型和动态类型各有所长,静态类型能够让编译器帮你找出更多的错误,因为编译器能够在编译时进行大部分的检查工作。C# 是一种静态类型的语言,不过它加入了动态类型的语言特性,可以更高效地解决问题。  

一、目录三十八、理解动态类型的优劣三十九、使用动态类型表达泛型类型参数的运行时类型四十、将接受匿名类型的参数声明为 dynamic四十一、用 DynamicObject 或 IDynamicMetaObjectProvider 实现数据驱动的动态类型四十二、如何使用表达式 API四十三、使用表达式将延迟绑定转换为预先绑定四十四、尽量减少在公有 API 中使用动态类型三十八、理解动态类型的优劣

C# 动态类型是为了让静态代码能够更加平滑地与其他使用动态类型的环境进行交互,而不是鼓励在一般场景中使用 dynamic 进行动态编程。

只要对象在运行时包含成员,那么即可正常使用。

若是一个操作数(包括 this)为动态类型,那么返回结果也会是动态类型。不过,最后依然要转换成静态类型,以便被其它 C# 代码所使用,以及被编译器感知。

当你需要不知道具体类型的运行时解析方法的时候,动态类型是最佳的工具。如果你能在编译期间明确类型,那么可以使用 lambda 表达式和函数式编程来解决问题。

表达式树,一种在运行时创建代码的方法(下面提供示例)。

大多数情况,可以使用 Lambda 表达式创建泛型 API,让调用者自己动态定义所需要执行的代码即可。

优先使用静态类型,静态类型比动态类型更高效,动态类型和在运行时创建表达式树都会带来性能上的影响,即便这点影响微不足道。

若你能控制程序中所有涉及的类型时,可以引入一个接口,而不是动态类型,即基于接口编程,并让所有需要支持该接口行为的类型都实现该接口。通过 C# 类型系统可以减少代码在运行时所产生的错误,编译器也能够生成更加高效的代码。

动态类型做法的效率比纯粹的静态类型下降挺大,但实现的难度却比解析表达式树要简单地挺多。

  这里,我使用 3 种数字相加的方法,dynamic 动态、Func 委托以及使用表达式树进行相加的区别:

result1 = AddDynamic(, </span><span style=”color: #0000ff;”&gt;var</span> result2 = AddFunc(<span style=”color: #800080;”&gt;3</span>,<span style=”color: #800080;”&gt;4</span>,(x,y) => x +<span style=”color: #000000;”&gt; y); Console.WriteLine(result2); </span><span style=”color: #0000ff;”&gt;var</span> result3 = AddExpressionTree(<span style=”color: #800080;”&gt;4</span>,<span style=”color: #800080;”&gt;5</span><span style=”color: #000000;”&gt;); Console.WriteLine(result3); } </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<summary></span> <span style=”color: #808080;”&gt;///</span><span style=”color: #008000;”&gt; Add,动态 </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;</summary></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”a”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”b”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<returns></returns></span> <span style=”color: #0000ff;”&gt;private</span> <span style=”color: #0000ff;”&gt;dynamic</span> AddDynamic(<span style=”color: #0000ff;”&gt;dynamic</span> a,<span style=”color: #0000ff;”&gt;dynamic</span><span style=”color: #000000;”&gt; b) { </span><span style=”color: #0000ff;”&gt;return</span> a +<span style=”color: #000000;”&gt; b; } </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<summary></span> <span style=”color: #808080;”&gt;///</span><span style=”color: #008000;”&gt; Add,使用委托 </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;</summary></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<typeparam name=”T1″&gt;</typeparam></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<typeparam name=”T2″&gt;</typeparam></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<typeparam name=”TR”&gt;</typeparam></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”a”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”b”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”func”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<returns></returns></span> <span style=”color: #0000ff;”&gt;private</span> TR AddFunc<T1,T2,TR>(T1 a,T2 b,Func<T1,TR><span style=”color: #000000;”&gt; func) { </span><span style=”color: #0000ff;”&gt;return</span><span style=”color: #000000;”&gt; func(a,b); } </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<summary></span> <span style=”color: #808080;”&gt;///</span><span style=”color: #008000;”&gt; Add,使用表达式树 </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;</summary></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<typeparam name=”T”&gt;</typeparam></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”a”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”b”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<returns></returns></span> <span style=”color: #0000ff;”&gt;private</span> T AddExpressionTree<T><span style=”color: #000000;”&gt;(T a,T b) { ParameterExpression leftOperand </span>= Expression.Parameter(<span style=”color: #0000ff;”&gt;typeof</span>(T),<span style=”color: #800000;”&gt;”</span><span style=”color: #800000;”&gt;left</span><span style=”color: #800000;”&gt;”</span><span style=”color: #000000;”&gt;); ParameterExpression rightOperand </span>= Expression.Parameter(<span style=”color: #0000ff;”&gt;typeof</span>(T),<span style=”color: #800000;”&gt;”</span><span style=”color: #800000;”&gt;right</span><span style=”color: #800000;”&gt;”</span><span style=”color: #000000;”&gt;); BinaryExpression body </span>=<span style=”color: #000000;”&gt; Expression.Add(leftOperand,rightOperand); Expression</span><Func<T,T,T>> adder = Expression.Lambda<Func<T,T>><span style=”color: #000000;”&gt;(body,leftOperand,rightOperand); Func</span><T,T> theDelegate =<span style=”color: #000000;”&gt; adder.Compile(); </span><span style=”color: #0000ff;”&gt;return</span><span style=”color: #000000;”&gt; theDelegate(a,b); }}</span></pre>

<p style="text-align: center;"> 

三十九、使用动态类型表达泛型类型参数的运行时类型

System.Linq.Enumerable.Cast 将序列中的对象转换成 T,从而使得 LINQ 可以配合 IEnumerable 进行工作。

Convert 要比 Cast 适用性更广,但同时也会执行更多的工作。

四十、将接受匿名类型的参数声明为 dynamic

不要过度使用动态类型,因为动态调用会增加系统的额外开销,即便不大。

长远来看,具体类型更易于维护,编译器和类型系统也会为其提供更好的支持。

扩展方法不能基于动态对象定义。

四十一、用 DynamicObject 或 IDynamicMetaObjectProvider 实现数据驱动的动态类型

创建带有动态功能的类型的最简单的方法就是继承 System.Dynamic.DynamicObject。若能直接继承 DynamicObject,那么创建动态类就会比较简单。

实现 IDynamicMetaObjectProvider 就意味着需要实现方法 GetMetaObject()。

创建动态类型时首选继承,如果必须使用其他基类,可以手工实现 IDynamicMetaObjectProvider 接口,虽然所有的动态类型都会带来性能上的损失,但这种手工实现接口的方式所带来的损失往往更大一些。

  这是一个实现动态类型模型的一个示例。除了需要继承 DynamicObject,还需要重写 TryGetMemebr() 和 TrySetMemebr()。

propDynamic = = Console.WriteLine(propDynamic.<a href=”https://www.jb51.cc/tag/Now/” target=”_blank” class=”keywords”>Now</a>); } </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<summary></span> <span style=”color: #808080;”&gt;///</span><span style=”color: #008000;”&gt; 动态<a href=”https://www.jb51.cc/tag/shuxing/” target=”_blank” class=”keywords”>属性</a>绑定模型 </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;</summary></span> <span style=”color: #0000ff;”&gt;internal</span> <span style=”color: #0000ff;”&gt;class</span><span style=”color: #000000;”&gt; DynamicPropertyBag : DynamicObject { </span><span style=”color: #0000ff;”&gt;private</span> <span style=”color: #0000ff;”&gt;readonly</span> Dictionary<<span style=”color: #0000ff;”&gt;string</span>,<span style=”color: #0000ff;”&gt;object</span>> _storage = <span style=”color: #0000ff;”&gt;new</span> Dictionary<<span style=”color: #0000ff;”&gt;string</span>,<span style=”color: #0000ff;”&gt;object</span>><span style=”color: #000000;”&gt;(); </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<summary></span> <span style=”color: #808080;”&gt;///</span><span style=”color: #008000;”&gt; <a href=”https://www.jb51.cc/tag/huoqu/” target=”_blank” class=”keywords”>获取</a><a href=”https://www.jb51.cc/tag/shuxing/” target=”_blank” class=”keywords”>属性</a>值 </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;</summary></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”binder”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”result”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<returns></returns></span> <span style=”color: #0000ff;”&gt;public</span> <span style=”color: #0000ff;”&gt;override</span> <span style=”color: #0000ff;”&gt;bool</span> TryGetMember(GetMemberBinder binder,<span style=”color: #0000ff;”&gt;out</span> <span style=”color: #0000ff;”&gt;object</span><span style=”color: #000000;”&gt; result) { </span><span style=”color: #0000ff;”&gt;var</span> key =<span style=”color: #000000;”&gt; binder.Name; </span><span style=”color: #0000ff;”&gt;if</span><span style=”color: #000000;”&gt; (_storage.ContainsKey(key)) { result </span>=<span style=”color: #000000;”&gt; _storage[key]; </span><span style=”color: #0000ff;”&gt;return</span> <span style=”color: #0000ff;”&gt;true</span><span style=”color: #000000;”&gt;; } result </span>= <span style=”color: #0000ff;”&gt;null</span><span style=”color: #000000;”&gt;; </span><span style=”color: #0000ff;”&gt;return</span> <span style=”color: #0000ff;”&gt;false</span><span style=”color: #000000;”&gt;; } </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<summary></span> <span style=”color: #808080;”&gt;///</span><span style=”color: #008000;”&gt; 设置<a href=”https://www.jb51.cc/tag/shuxing/” target=”_blank” class=”keywords”>属性</a>值 </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;</summary></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”binder”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”value”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<returns></returns></span> <span style=”color: #0000ff;”&gt;public</span> <span style=”color: #0000ff;”&gt;override</span> <span style=”color: #0000ff;”&gt;bool</span> TrySetMember(SetMemberBinder binder,<span style=”color: #0000ff;”&gt;object</span><span style=”color: #000000;”&gt; value) { </span><span style=”color: #0000ff;”&gt;var</span> key =<span style=”color: #000000;”&gt; binder.Name; </span><span style=”color: #0000ff;”&gt;try</span><span style=”color: #000000;”&gt; { </span><span style=”color: #0000ff;”&gt;if</span><span style=”color: #000000;”&gt; (_storage.ContainsKey(key)) { _storage[key] </span>=<span style=”color: #000000;”&gt; value; } </span><span style=”color: #0000ff;”&gt;else</span><span style=”color: #000000;”&gt; { _storage.Add(key,value); } </span><span style=”color: #0000ff;”&gt;return</span> <span style=”color: #0000ff;”&gt;true</span><span style=”color: #000000;”&gt;; } </span><span style=”color: #0000ff;”&gt;catch</span><span style=”color: #000000;”&gt; (Exception e) { Console.WriteLine(e); </span><span style=”color: #0000ff;”&gt;return</span> <span style=”color: #0000ff;”&gt;false</span><span style=”color: #000000;”&gt;; } }</span></pre>

 

四十二、如何使用表达式 API

传统的反射 API 可以用表达式和表达式树进行更好的替代,表达式可以直接编译为委托。

接口使我们可以得到一个更为清晰、也更具可维护性的系统,反射是一个很强大的晚期绑定机制(虽然效率有所降低),.NET 框架使用它来实现 Windows 控件和 Web 控件的数据绑定。

  这里提供了一个示例:

result = Call((x) => (x * ).ToString( Console.WriteLine(result); } </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<summary></span> <span style=”color: #808080;”&gt;///</span><span style=”color: #008000;”&gt; <a href=”https://www.jb51.cc/tag/diaoyong/” target=”_blank” class=”keywords”>调用</a> </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;</summary></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<typeparam name=”T”&gt;</typeparam></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<typeparam name=”TResult”&gt;</typeparam></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”op”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<returns></returns></span> <span style=”color: #0000ff;”&gt;private</span> TResult Call@H_91_<a href=”https://www.jb51.cc/tag/404/” target=”_blank” class=”keywords”>404</a>@(Expression<Func@H_91_<a href=”https://www.jb51.cc/tag/404/” target=”_blank” class=”keywords”>404</a>@><span style=”color: #000000;”&gt; op) { </span><span style=”color: #0000ff;”&gt;var</span> exp = op.Body <span style=”color: #0000ff;”&gt;as</span><span style=”color: #000000;”&gt; MethodCallExpression; </span><span style=”color: #0000ff;”&gt;var</span> result = <span style=”color: #0000ff;”&gt;default</span><span style=”color: #000000;”&gt;(TResult); </span><span style=”color: #0000ff;”&gt;if</span> (exp == <span style=”color: #0000ff;”&gt;null</span><span style=”color: #000000;”&gt;) { </span><span style=”color: #0000ff;”&gt;return</span><span style=”color: #000000;”&gt; result; } </span><span style=”color: #0000ff;”&gt;var</span> methodName =<span style=”color: #000000;”&gt; exp.Method.Name; </span><span style=”color: #0000ff;”&gt;var</span> parameters =<span style=”color: #000000;”&gt; exp.Arguments.Select(ProcessArgument); Console.WriteLine($</span><span style=”color: #800000;”&gt;”</span><span style=”color: #800000;”&gt;<a href=”https://www.jb51.cc/tag/fangfa/” target=”_blank” class=”keywords”>方法</a>名 {methodName}</span><span style=”color: #800000;”&gt;”</span><span style=”color: #000000;”&gt;); </span><span style=”color: #0000ff;”&gt;foreach</span> (<span style=”color: #0000ff;”&gt;var</span> parameter <span style=”color: #0000ff;”&gt;in</span><span style=”color: #000000;”&gt; parameters) { Console.WriteLine(</span><span style=”color: #800000;”&gt;”</span><span style=”color: #800000;”&gt;参数:</span><span style=”color: #800000;”&gt;”</span><span style=”color: #000000;”&gt;); Console.WriteLine($</span><span style=”color: #800000;”&gt;”</span><span style=”color: #800000;”&gt;\t{parameter.Item1}:{parameter.Item2}</span><span style=”color: #800000;”&gt;”</span><span style=”color: #000000;”&gt;); } </span><span style=”color: #0000ff;”&gt;return</span><span style=”color: #000000;”&gt; result; } </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<summary></span> <span style=”color: #808080;”&gt;///</span><span style=”color: #008000;”&gt; 处理参数 </span><span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;</summary></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<param name=”expression”&gt;</param></span> <span style=”color: #808080;”&gt;///</span> <span style=”color: #808080;”&gt;<returns></returns></span> <span style=”color: #0000ff;”&gt;private</span> Tuple<Type,<span style=”color: #0000ff;”&gt;object</span>><span style=”color: #000000;”&gt; ProcessArgument(Expression expression) { </span><span style=”color: #0000ff;”&gt;object</span> arg = <span style=”color: #0000ff;”&gt;default</span>(<span style=”color: #0000ff;”&gt;object</span><span style=”color: #000000;”&gt;); LambdaExpression l </span>=<span style=”color: #000000;”&gt; Expression.Lambda(Expression.Convert(expression,expression.Type)); Type parmType </span>=<span style=”color: #000000;”&gt; l.ReturnType; arg </span>=<span style=”color: #000000;”&gt; l.Compile().DynamicInvoke(); </span><span style=”color: #0000ff;”&gt;return</span><span style=”color: #000000;”&gt; Tuple.Create(parmType,arg); }</span></pre>

四十三、使用表达式将延迟绑定转换为预先绑定

延迟绑定API要使用符号(symbol)信息来实现,而预先编译好的 API 则无需这些信息,表达式 API 正是二者之间的桥梁。

延迟绑定常见于 Silverlight 和 WPF 中使用的属性通知接口,通过实现 INotifyPropertyChanged 和 INotifyPropertyChanging 接口来实现属性变更的预绑定。

四十四、尽量减少在公有 API 中使用动态类型

优先使用 C# 的静态类型,并尽可能地降低动态类型的作用范围。若是想一直使用动态特性,你应该直接选用一种动态语言,而非 C#。

若要在程序中使用动态特性,请尽量不要在公有接口中使用,这样会将动态类型限制在一个单独的对象(或类型)中。

本系列

  

  

  

  

  

  

【博主】反骨仔

【原文】

【GitHub】 可以下载 XMind

<p style="text-align: right;">

<p style="text-align: right;">