《Effective C#》快速笔记(五)- - C# 中的动态编程

静态类型和动态类型各有所长,静态类型能够让编译器帮你找出更多的错误,因为编译器能够在编译时进行大部分的检查工作。C# 是一种静态类型的语言,不过它加入了动态类型的语言特性,可以更高效地解决问题。
一、目录
- 三十八、理解动态类型的优劣
- 三十九、使用动态类型表达泛型类型参数的运行时类型
- 四十、将接受匿名类型的参数声明为 dynamic
- 四十一、用 DynamicObject 或 IDynamicMetaObjectProvider 实现数据驱动的动态类型
- 四十二、如何使用表达式 API
- 四十三、使用表达式将延迟绑定转换为预先绑定
- 四十四、尽量减少在公有 API 中使用动态类型
三十八、理解动态类型的优劣
C# 动态类型是为了让静态代码能够更加平滑地与其他使用动态类型的环境进行交互,而不是鼓励在一般场景中使用 dynamic 进行动态编程。
只要对象在运行时包含成员,那么即可正常使用。
若是一个操作数(包括 this)为动态类型,那么返回结果也会是动态类型。不过,最后依然要转换成静态类型,以便被其它 C# 代码所使用,以及被编译器感知。
当你需要不知道具体类型的运行时解析方法的时候,动态类型是最佳的工具。如果你能在编译期间明确类型,那么可以使用 lambda 表达式和函数式编程来解决问题。
表达式树,一种在运行时创建代码的方法(下面提供示例)。
大多数情况,可以使用 Lambda 表达式创建泛型 API,让调用者自己动态定义所需要执行的代码即可。
优先使用静态类型,静态类型比动态类型更高效,动态类型和在运行时创建表达式树都会带来性能上的影响,即便这点影响微不足道。
若你能控制程序中所有涉及的类型时,可以引入一个接口,而不是动态类型,即基于接口编程,并让所有需要支持该接口行为的类型都实现该接口。通过 C# 类型系统可以减少代码在运行时所产生的错误,编译器也能够生成更加高效的代码。
动态类型做法的效率比纯粹的静态类型下降挺大,但实现的难度却比解析表达式树要简单地挺多。
这里,我使用 3 种数字相加的方法,dynamic 动态、Func 委托以及使用表达式树进行相加的区别:

[TestMethod]
public void Test()
{
var result1 = AddDynamic(2, 3);
Console.WriteLine((int)result1); var result2 = AddFunc(3, 4, (x, y) => x + y);
Console.WriteLine(result2); var result3 = AddExpressionTree(4, 5);
Console.WriteLine(result3);
} /// <summary>
/// Add,动态
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
private dynamic AddDynamic(dynamic a, dynamic b)
{
return a + b;
} /// <summary>
/// Add,使用委托
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="TR"></typeparam>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="func"></param>
/// <returns></returns>
private TR AddFunc<T1, T2, TR>(T1 a, T2 b, Func<T1, T2, TR> func)
{
return func(a, b);
} /// <summary>
/// Add,使用表达式树
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
private T AddExpressionTree<T>(T a, T b)
{
ParameterExpression leftOperand = Expression.Parameter(typeof(T), "left");
ParameterExpression rightOperand = Expression.Parameter(typeof(T), "right");
BinaryExpression body = Expression.Add(leftOperand, rightOperand);
Expression<Func<T, T, T>> adder = Expression.Lambda<Func<T, T, T>>(body, leftOperand, rightOperand); Func<T, T, T> theDelegate = adder.Compile();
return theDelegate(a, b);
}
}


三十九、使用动态类型表达泛型类型参数的运行时类型
System.Linq.Enumerable.Cast<T> 将序列中的对象转换成 T,从而使得 LINQ 可以配合 IEnumerable 进行工作。
Convert<T> 要比 Cast<T> 适用性更广,但同时也会执行更多的工作。
四十、将接受匿名类型的参数声明为 dynamic
不要过度使用动态类型,因为动态调用会增加系统的额外开销,即便不大。
长远来看,具体类型更易于维护,编译器和类型系统也会为其提供更好的支持。
扩展方法不能基于动态对象定义。
四十一、用 DynamicObject 或 IDynamicMetaObjectProvider 实现数据驱动的动态类型
创建带有动态功能的类型的最简单的方法就是继承 System.Dynamic.DynamicObject。若能直接继承 DynamicObject,那么创建动态类就会比较简单。
实现 IDynamicMetaObjectProvider 就意味着需要实现方法 GetmetaObject()。
创建动态类型时首选继承,如果必须使用其他基类,可以手工实现 IDynamicMetaObjectProvider 接口,虽然所有的动态类型都会带来性能上的损失,但这种手工实现接口的方式所带来的损失往往更大一些。
这是一个实现动态类型模型的一个示例。除了需要继承 DynamicObject,还需要重写 TryGetMemebr() 和 TrySetMemebr()。

[TestMethod]
public void Test1()
{
dynamic propDynamic = new DynamicPropertyBag();
propDynamic.Now = DateTime.Now; Console.WriteLine(propDynamic.Now);
} /// <summary>
/// 动态属性绑定模型
/// </summary>
internal class DynamicPropertyBag : DynamicObject
{
private readonly Dictionary<string, object> _storage = new Dictionary<string, object>(); /// <summary>
/// 获取属性值
/// </summary>
/// <param name="binder"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var key = binder.Name; if (_storage.ContainsKey(key))
{
result = _storage[key];
return true;
} result = null;
return false;
} /// <summary>
/// 设置属性值
/// </summary>
/// <param name="binder"></param>
/// <param name="value"></param>
/// <returns></returns>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
var key = binder.Name; try
{
if (_storage.ContainsKey(key))
{
_storage[key] = value;
}
else
{
_storage.Add(key, value);
} return true;
}
catch (Exception e)
{
Console.WriteLine(e);
return false;
}
}


四十二、如何使用表达式 API
传统的反射 API 可以用表达式和表达式树进行更好的替代,表达式可以直接编译为委托。
接口使我们可以得到一个更为清晰、也更具可维护性的系统,反射是一个很强大的晚期绑定机制(虽然效率有所降低),.NET 框架使用它来实现 Windows 控件和 Web 控件的数据绑定。
这里提供了一个示例:

[TestMethod]
public void Test2()
{
var result = Call<int, string>((x) => (x * 2).ToString("D")); Console.WriteLine(result);
} /// <summary>
/// 调用
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="op"></param>
/// <returns></returns>
private TResult Call<T, TResult>(Expression<Func<T, TResult>> op)
{
var exp = op.Body as MethodCallExpression;
var result = default(TResult); if (exp == null)
{
return result;
} var methodName = exp.Method.Name;
var parameters = exp.Arguments.Select(ProcessArgument); Console.WriteLine($"方法名 {methodName}"); foreach (var parameter in parameters)
{
Console.WriteLine("参数:");
Console.WriteLine($"\t{parameter.Item1}:{parameter.Item2}");
} return result;
} /// <summary>
/// 处理参数
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
private Tuple<Type, object> ProcessArgument(Expression expression)
{
object arg = default(object);
LambdaExpression l = Expression.Lambda(Expression.Convert(expression, expression.Type)); Type parmType = l.ReturnType;
arg = l.Compile().DynamicInvoke(); return Tuple.Create(parmType, arg);
}


四十三、使用表达式将延迟绑定转换为预先绑定
延迟绑定API要使用符号(symbol)信息来实现,而预先编译好的 API 则无需这些信息,表达式 API 正是二者之间的桥梁。
延迟绑定常见于 Silverlight 和 WPF 中使用的属性通知接口,通过实现 INotifyPropertyChanged 和 INotifyPropertyChanging 接口来实现属性变更的预绑定。
四十四、尽量减少在公有 API 中使用动态类型
优先使用 C# 的静态类型,并尽可能地降低动态类型的作用范围。若是想一直使用动态特性,你应该直接选用一种动态语言,而非 C#。
若要在程序中使用动态特性,请尽量不要在公有接口中使用,这样会将动态类型限制在一个单独的对象(或类型)中。
本系列
《Effective C#》快速笔记(一)- C# 语言习惯
《Effective C#》快速笔记(二)- .NET 资源托管
《Effective C#》快速笔记(三)- 使用 C# 表达设计
《Effective C#》快速笔记(五) - C# 中的动态编程
《Effective C#》快速笔记(六) - C# 高效编程要点补充
《Effective C#》快速笔记(五)- - C# 中的动态编程的更多相关文章
- [.NET] 《Effective C#》快速笔记 - C# 中的动态编程
<Effective C#>快速笔记 - C# 中的动态编程 静态类型和动态类型各有所长,静态类型能够让编译器帮你找出更多的错误,因为编译器能够在编译时进行大部分的检查工作.C# 是一种静 ...
- 《Effective C#》快速笔记(六)- - C# 高效编程要点补充
目录 四十五.尽量减少装箱拆箱 四十六.为应用程序创建专门的异常类 四十七.使用强异常安全保证 四十八.尽量使用安全的代码 四十九.实现与 CLS 兼容的程序集 五十.实现小尺寸.高内聚的程序集 这是 ...
- Java学习笔记五:Java中常用的运算符
Java中常用的运算符 运算符是一种“功能”符号,用以通知 Java 进行相关的运算.譬如,我们需要将变量 score 的值设置为 20 ,这时候就需要一个“=”,告诉程序需要进行赋值操作. Java ...
- [.NET] 《Effective C#》快速笔记(四)- 使用框架
<Effective C#>快速笔记(四)- 使用框架 .NET 是一个类库,你了解的越多,自己需要编写的代码就越少. 目录 三十.使用重写而不是事件处理函数 三十一.使用 ICompar ...
- [.NET] 《Effective C#》快速笔记 - C# 高效编程要点补充
<Effective C#>快速笔记 - C# 高效编程要点补充 目录 四十五.尽量减少装箱拆箱 四十六.为应用程序创建专门的异常类 四十七.使用强异常安全保证 四十八.尽量使用安全的代码 ...
- [.NET] 《Effective C#》快速笔记(二)- .NET 资源托管
<Effective C#>快速笔记(二)- .NET 资源托管 简介 续 <Effective C#>读书笔记(一)- C# 语言习惯. .NET 中,GC 会帮助我们管理内 ...
- [.NET] 《Effective C#》快速笔记(一)- C# 语言习惯
<Effective C#>快速笔记(一)- C# 语言习惯 目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 ...
- 《Effective C#》快速笔记(一)- C# 语言习惯
目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 is 或 as 操作符而不是强制类型转换 四.使用 Conditional ...
- 《Effective C#》快速笔记(四)- 使用框架
.NET 是一个类库,你了解的越多,自己需要编写的代码就越少. 目录 三十.使用重写而不是事件处理函数 三十一.使用 IComparable<T> 和 IComparer<T> ...
随机推荐
- 前端页面加载速度优化---Ngnix之GZIP压缩
gzip on; #开启Gzip gzip_static on;#是否开启gzip静态资源 #nginx对于静态文件的处理模块,该模块可以读取预先压缩的gz文件,这样可以减少每次请求进行gzip压缩的 ...
- OI生涯回忆录(二)
(二)NOIP2016之后到HLOI2017 之后变得有点颓废,因为有的地方难度上来了,碰见不会的题我就会放挺.又或者有时候题水,改完了就不思进取了.到了过年前那几天连着考了几天试,好像是长春那边冬令 ...
- 青岛Uber优步司机奖励政策(8/10-8/16)
亲爱的Uber青岛优步的大司机朋友们,又到了每周发布奖励细则的时刻啦!下一周的奖励与上周有所不同,请一定要仔细按照自己的情况阅读!另外,之前参与过投票并表示想加入新小时保底政策的老司机朋友们从本周起, ...
- Android AOSP 编译sdk
首先你要有AOSP 工程. 然后执行下面的命令编译sdk. //运行下面的命令得到编译环境 source build/envsetup.sh lunch sdk make sdk 最后文件输出在哪个位 ...
- springBoot -webSocket 基于STOMP协议交互
浅谈WebSocket WebSocket是在HTML5基础上单个TCP连接上进行全双工通讯的协议,只要浏览器和服务器进行一次握手,就可以建立一条快速通道,两者就可以实现数据互传了.说白了,就是打破了 ...
- springBoot cache操作2
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/zxd1435513775/article/details/85091793一.基本项目搭建测试项目是 ...
- linux_fdisk命令详解,关于分区的详解
这篇文章写的十分详细,特别的好 fdisk -l 可以列出所有的分区,包括没有挂上的分区和usb设备.我一般用这个来查找需要挂载的分区的位置,比如挂上u盘. 实例解说Linux中fdisk分区使用方法 ...
- linux-centos6②
- django1.11+xadmin的搭建
1.git clone https://github.com/sshwsfc/xadmin.git或者直接下载zip包 2..在项目根目录下建一个extra_apps的包,将xadmin源码包存放在里 ...
- JDK11安装后,环境变量的坑
安装了最新的JDK11,安装完后设置环境变量,打开CMD,没生效 检查了3遍,都没发现问题,在PATH中将JAVA设置移到第一也不行 最后偶然发现,在点击如图右下的‘编辑文本’,用文本方式编辑时,发现 ...