C# 动态语言扩展
1、DLR
Dynamic Language Runtime(动态语言进行时),C# 4 的动态功能是其中的一部分。它是添加一系列服务到CLR中,允许添加动态语言,如Ruby 和 Python,使得C# 具备和这些动态语言相同的动态功能。
位于System.Dynamic 和 System.Runtime.Complier-Services名称空间里的几个类中。
IronRuby 和 IronPython 是Ruby 和Python 的开源版本,它们使用DLR。Silverlight 也是用 DLR。通过包含DLR,可以给应用程序添加脚本编辑功能。脚本运行库允许给脚本传入变量和从脚本传出变量。
2、dynamic 类型
dynamic 类型允许写 忽略编译期间 的类型检查的代码。编译器直接就认为dynamic 对象的操作都是有效的。如果无效,运行时才会出错。下面来看一段代码示例:
可以得出正常是会报错的,但是dynamic不会,如果尝试过的话,就会发现,用 dynamic 都没有方法提示出来,所以说它是直接跳过编译的。 注释staticEmployee调用方法这一段,执行会发生一个运行错误。抛出的异常是 RuntimeBinderException 异常。RuntimetBinder 对象会在运行时判断该调用,确定Employee 类是否支持被调用的方法。
与var不同,定义的 dynamic 对象可以在运行期间改变其类型。var 对象类型是延迟确定,但是确定之后,就不能更改。动态对象的类型可以更改,而且可以改变多次,但这并不是强制转换。它能实现int转string。
对于dynamic 类型有两个限制。不支持扩展方法,匿名函数(Lambda表达式)也不能用动态方法调用的参数,所以LINQ不能用于动态对象。大多数的LINQ调用的都是扩展方法,而Lambda表达式用作这些扩展方法的参数。
从后台的角度观察
这里就不从IL代码角度分析了(主要是还看不懂)。直接上结论。
在生成的代码中,能看到 System.Runtime.CompilerServices.CallSite 类和 System.Runtime.CompilerServices.CallSiteBinder 类的引用。
CallSite 是在运行期间处理查找操作的类型。在运行期间调用动态对象时,必须要找到该对象,查看其成员是否存在。CallSite 会缓存这个信息,这样查找就不需要重复执行。没有这个过程,循环结构的性能就会出现问题。
CallSite 完成了成员查找操作后,就调用 CallSiteBinder() 方法。它从CallSite中提取信息,并生成表达式树,来表示绑定器绑定的操作。
做的工作有很多,优化非常复杂的操作时就要格外小心。显然,使用 dynamic 类型是有用的,也是有代价的。
3、包含DLR ScriptRuntime
ScriptRuntime可以执行存储在文件中的代码段或完整的脚本。可以选择合适的语言引擎,也可以让DLR自己确定。脚本可以在自己的应用程序域或者在当前的应用程序域中创建。不仅可以给脚本传入数值并从脚本中传出数值,还可以在脚本中调用在动态对象上创建的方法。
这种灵活性为包含 ScriptRuntime 提供了无数种用法。
以购买物品的一个Windows客户端应用程序举例,当然也可以是其他的。
有折扣计算。
先要确定应用折扣的脚本 AmountDisc.py 或 CountDisc.py

disAmt = .25 retAmt = amt if amt > 25.00: retAmt = amt-(amt * discAmt)

discCount = 5 discAmt = .1 retAmt = amt if prodCount > discCount: retAmt = amt-(amt * discAmt)
然后启动ScriptRuntime 环境步骤:创建 ScriptRuntime 对象、设置合适的 ScriptEngine、创建 ScriptSource 以及创建 ScripeScope。
ScriprRuntime 对象是起点,也是包含ScriptRuntime 的基础。它拥有包含环境的全局状态。ScriptRuntime 对象使用 CreateFromConfiguration() 静态方法创建。app.config如下所示:
ScriptRuntime获取ScriptEngine 的引用。因为由 .py的扩展名,所以ScriptRuntimr 可以自己确定。
ScriptEngine执行脚本代码。执行文件或者代码段的脚本中有几种方法。ScriptEngine 还提供了 ScriptSource 和 ScriptScope。
ScriptSource 对象允许访问脚本,它表示脚本的源代码。其可以操作脚本的源代码。从磁盘加载,逐行解析,甚至把脚本编译到 CompiledCode 对象中。执行多个时很方便。
ScriptScope 对象实际上是一个名称空间。要给脚本传入值或从脚本传出值,把一个变量绑定到 ScriptScope上。
理解很浅,以后待加深。
4、DynamicObject 和 ExpandObject
要创建自己的动态对象有两种方法:
从DynamicObject中派生,需重写几个方法
使用ExpandoObject,可立即使用的密封类
DynamicObject
考虑一个人的对象。一般定义名字,年龄等属性。现在假定要在运行期间构建这个对象,且系统事先不知道对象有什么属性或该对象支持什么方法。此时就可以使用基于 DynamicObject对象。(需要这样的功能的场合几乎没有)
先看 DynamicObject:

1 class WroxDynamicObject : DynamicObject 2 { 3 Dictionary<string, object> _dynamicData = new Dictionary<string, object>(); 4 5 public override bool TryGetMember(GetMemberBinder binder, out object result) 6 { 7 bool success = false; 8 result = null; 9 if (_dynamicData.ContainsKey(binder.Name)) 10 { 11 result = _dynamicData[binder.Name]; 12 success = true; 13 } 14 else 15 result = "Property Not Found!"; 16 17 return success; 18 } 19 20 public override bool TrySetMember(SetMemberBinder binder, object value) 21 { 22 _dynamicData[binder.Name] = value; 23 return true; 24 } 25 26 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) 27 { 28 dynamic method = _dynamicData[binder.Name]; 29 result = method((DateTime)args[0]); 30 return result != null; 31 } 32 33 }
重写了三个方法,TrySetMember() 、TryGetMember() 和 TryInvokeMember()
TrySetMember() 添加新方法、属性或字段。这里存储在一个 Dictionary 对象中。传递给 TrySetMember() 方法的 SetMemberBinder 对象包含Name属性,它用于标识 Dictionary 中的元素。
TryGetMember() 方法根据 GetMemberBinder对象的 Name属性检索存储在 Dictionary 中的对象。那么这些方法如何使用呢?看下面的代码:
dyn = new WroxDynamicObject(); dyn.FirstName = "Bugs"; dyn.LastName = "Bunny"; Console.WriteLine(dyn.GetType()); Console.WriteLine("{0} {1}", dyn.FirstName, dyn.LastName);
没错,当它一直存在直接用就好了。调用的重写方法是 .NET Framework帮我们完成的。DynamicObject 处理了绑定。
那么如何添加方法呢?看下面这个例子:
Func<DateTime, string> GetTomorrow = today => today.AddDays(1).ToShortDateString(); dyn.GetTomorrowDate = GetTomorrow; Console.WriteLine("Tomorrow is {0}", dyn.GetTomorrowDate(DateTime.Now));
创建委托GetTomorrow。这里的委托表示的方法调用了 AddDays,给传入的Date 加上一天,返回得到的日期字符串。接着把委托设置为wroxDyn 对象上的GetTomorrowDate() 方法。最后一行调用新方法,并传递今天的日期。
动态功能发挥作用,对象上有了一个有效的方法。
ExpandoObject
ExpandoObject 的工作方式与前面的类似,却别是不需要重写方法
static void DoExpando() { dynamic expObj = new ExpandoObject(); expObj.FirstName = "Daffy"; expObj.LastName = "Duck"; Console.WriteLine(expObj.FirstName + " " + expObj.LastName); Func<DateTime, string> GetTomorrow = today => today.AddDays(1).ToShortDateString(); expObj.GetTomorrowDate = GetTomorrow; Console.WriteLine("Tomorrow is {0}", expObj.GetTomorrowDate(DateTime.Now)); expObj.Friends = new List<Person>(); expObj.Friends.Add(new Person() { FirstName = "Bob", LastName = "Jones" }); expObj.Friends.Add(new Person() { FirstName = "Robert", LastName = "Jones" }); expObj.Friends.Add(new Person() { FirstName = "Bobby", LastName = "Jones" }); foreach (Person friend in expObj.Friends) { Console.WriteLine(friend.FirstName + " " + friend.LastName); } }
与 dynamic 似乎一样,但是多做了一件事,它把一个Person对象集合添加为对象的一个属性。看起来似乎和 dynamic 没什么区别,但是这里面有两点很重要。第一不能仅创建 dynamic 类型的空对象。必须把dynamic 类型赋予某个对象,像下面的代码就是无效的:
dynamic dynObj; dynObj.FristName = "Tom";
第二,因为 dynamic 类型必须赋予某个对象,所以如果执行 GetType 调用,它就会报告赋予了 dynamic 类型的对象类型。赋int 就报 int ,这就不适用于 ExpandoObject 或派生自 DynamicObject 的对象。
如果需要控制动态对象中属性的添加和访问,则使该对象派生字 DynamicObject 是最佳选择。使用 DynamicObject,可以重写几个方法,准确的控制对象与运行库的交互方式。而对于其他情况,就应使用dynamic 类型 或 ExpandoObject。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构