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)
AmountDisc.py 25以上 75折
复制代码
复制代码
discCount = 5
discAmt = .1
retAmt = amt
if prodCount > discCount:
    retAmt = amt-(amt * discAmt)
CountDisc.py 5个以上 9 折
复制代码

然后启动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     }
WorxDynamicObject
复制代码

重写了三个方法,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。

 

posted @   xunzf  阅读(380)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示