代码改变世界

深入了解Dynamic & DLR(二)

2011-09-23 11:33  refined code  阅读(2989)  评论(12)    收藏  举报

在上一篇随笔深入了解Dynamic & DLR(一)主要谈到在面向服务设计中,动态编程的必要性,并深入了解下dynamic这个关键字,以及它是如何在利用C#这门静态语言实现动态功能的。紧接上篇,一起来探讨下DLR ScripRuntime和两个常用类DynamicObject 和 ExpandoObject。

2.DLR ScriptRuntime

DLR(Dynamic Language Runtime),是微软的一个开源项目。为.NET应用程序提供了动态脚本注入技持。DLR构建的功能包含两个方面,一个是共享的动态类型系统,一个是标准的承载模型。但是VS2010并没有集成相关dll,大家可以从Codeplex获得源码目前最新版本为1.0。这里不得不提到CLR,它是整个运行环境的基础,DLR也是运行在其之上的,这样是有显而易见的好处的,CLR的垃圾回收、JIT编译、安全模型,DLR也能享用这些底层架构功能,如果我们对垃圾回收进行优化,或者是提供某种功能,那么DLR相应的也能享用这种便利。DLR内部为了提高执行效率,会从自己先compile script,然后cache。有些类似JIT机制。避免重复加载,解析外部脚本。

在实际业务逻辑中,我们希望能够实时动态执行存储在文件中的代码或者完整一个业务逻辑功能,甚至我们可以动态选择脚本语言引擎,在应用程序域中动态生成脚本,并注入脚本,来达到控制业务逻辑的目的。在随笔一,我列举了一个场景,需要随意调整销售策略在所有POS机上,假设我们根据客户等级来进行折扣,VIP客户8折,荣誉客户9折,一般客户9.8折,并且金额达到5000时,再能进行9.8的折扣。UI我们假设使用winform。界面如下。

在程序中我们首先声明和初始化ScriptRuntime环境,设置ScriptEngine,创建ScriptSource和ScriptScope。然后通过外部Python脚本来执行我们折扣策略。

 1         private void button1_Click(object sender, EventArgs e)
2 {
3 int level = 0;
4 if (this.rbtnVIP.Checked)
5 {
6 level = 0;
7 }
8 else if (this.rbtnCredit.Checked)
9 {
10 level = 1;
11 }
12 else
13 {
14 level = 2;
15 }
16
17 ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
18 ScriptEngine pythEng = scriptRuntime.GetEngine("Python");
19
20 //create the ScriptSource from the local file.
21 ScriptSource source = pythEng.CreateScriptSourceFromFile("Disc.py");
22
23 //create a new ScriptScope, and set parameters for script.
24 ScriptScope scope = pythEng.CreateScope();
25 scope.SetVariable("level", level);
26 scope.SetVariable("amt", Convert.ToDecimal(this.txtAmount.Text));
27
28 //execute the script
29 source.Execute(scope);
30
31 this.textBox1.Text = scope.GetVariable("amtPay").ToString();
32 this.textBox2.Text = scope.GetVariable("discAmt").ToString();
33 }

Python脚本如下:

 1 amtPay=amt
2 if level==0:
3 amtPay=amt*0.8
4 elif level==1:
5 amtPay=amt*0.9
6 else:
7 amtPay=amt*0.98
8 if amt>5000:
9 amtPay=amtPay*0.98
10 discAmt=amt-amtPay

在上述代码中,我们设置ScriptRuntime从configuration文件初始化,由此可以动态的选择创建Script Engine。configution文件配置如下

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="microsoft.scripting" type="Microsoft.Scripting.Hosting.Configuration.Section, Microsoft.Scripting, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" requirePermission="false" />
</configSections>
<microsoft.scripting>
<languages>
<language names="IronPython;Python;py" extensions=".py" displayName="IronPython 2.6.1" type="IronPython.Runtime.PythonContext, IronPython, Version=2.6.10920.0, Culture=neutral, PublicKeyToken=null" />
</languages>
</microsoft.scripting>
</configuration>

在本例配置文件中,设置了IronPython语言引擎的几个属性。ScriptRuntime会根据Language节点来获取一个对ScriptEngine的引用。可以把ScriptEngine当成整个Runtime的大脑,正是由它来执行脚本的。脚本是如何给加载读取的呢,这就是ScriptSource要做的工作了,它访问并操作脚本文件的源代码,加载到Runtime之后,然后解析脚本,并编译脚本到CompiledCode中,生成表达式树加以缓存。如果多次执行相同的脚本,只需要一次加载编译即可。Runtime自己会根据实际需要优化脚本便于执行,从而提高了执行效率。这是实际上和dynamic对象执行机制是一样的。执行完操作后,需要把结果返回给当前程序,这时需要通过ScirptScope来完成。通过ScriptScope的方法SetVariable(string name, object value);来绑定输入变量到ScriptScope中,执行后,通过Execute(ScriptScope scope)执行操作,通过dynamic GetVariable(string name)获取结果。当然返回值是一个dynamic对象。

打个比方吧,可以把一个人体当成是ScriptRuntime,他是一个有机整体环境,ScriptEngine是人的大脑,用思考处理事务的地方。ScriptSource类似眼睛,耳朵的功能,用于收集信息,并初步解析。然后通过大脑判断,来控制手脚活动,手脚也就类似ScriptScope的功能。下面来做一个简单的执行图:

3.DynamicObject & ExpandoObject

DynamicObject & ExpandoObject能够使我们根据实际需要动态创建自己所需的对象。区别在于ExpandoObject是一个seal class,可以直接使用。DynamicObject则需要override几个方法。

构建一个对象,必须包含该对象的一些描述(属性,字段等)和该对象的行为(方法),但是在这样一个场合中,我们事先都不知道需要创建的对象有什么属性或支持方法。DynamicObject正是适合这种情形。下面我们尝试代码演示下,先定义动态类。

View Code
 1     public class DynamicUser : DynamicObject
2 {
3 private Dictionary<string, object> _dyData = new Dictionary<string, object>();
4
5 public override bool TryGetMember(GetMemberBinder binder, out object result)
6 {
7 if (_dyData.ContainsKey(binder.Name))
8 {
9 result = _dyData[binder.Name];
10 return true;
11 }
12 else
13 {
14 result = "property couldn't be found";
15 return false;
16 }
17 }
18
19 public override bool TrySetMember(SetMemberBinder binder, object value)
20 {
21 _dyData[binder.Name] = value;
22 return true;
23 }
24
25 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
26 {
27 dynamic method = _dyData[binder.Name];
28 result = method(args[0].ToString());
29 return result != null;
30 }
31 }

我们把该对象的member信息都放在一个dictionary里。Get和Set的时候都去轮检这个dictionary。NET内部调用这些重载方法,在IL中,我们可以看到DynamicObject是如何处理这些绑定的。内部使用了System.Runtime.CompilerServices.CallSite和System.Runtime.CompilerServices.CallSiteBinder.CallSite,这和dynamic是一样的,详细请看 深入了解Dynamic & DLR

执行代码如下:添加一个GetMsg的方法,该方法接受一个参数为string,返回一个welcome string,我们使用lambda表达式来初始化该方法。

View Code
 1         static void Main(string[] args)
2 {
3 dynamic user = new DynamicUser();
4 user.NickName = "Loong";
5 Console.WriteLine(user.GetType());
6 Console.WriteLine(user.NickName);
7
8 Func<string, string> GetWelcomeMsg = name => string.Format("welcome {0}", name);
9 user.GetMsg = GetWelcomeMsg;
10 Console.WriteLine(user.GetMsg("Loong"));
11
12 Console.ReadLine();
13 }

执行结果如下:

ExpandoObject的工作方式类似,不必重写方法。

1             dynamic expUser = new ExpandoObject();
2 expUser.NickName = "loong2";
3 Console.WriteLine(expUser.GetType());
4 Console.WriteLine(expUser.NickName);
5
6 Func<string, string> GetWelcomeMsg = name => string.Format("welcome {0}", name);
7 expUser.GetMsg = GetWelcomeMsg;
8 Console.WriteLine(expUser.GetMsg("Loong"));

ExpandoObject使用上和dynamic没什么区别, 有一点区别是不能仅仅是创建dynamic对象,而不去初始化它。创建dynamic的同时必须初始化它。另外ExpandoObject的GetType方法总是显示System.Dynamic.ExpandoObject.而DynamicObject则显示继承类的type。一般来说dynamic和ExpandoObject就可以满足我们的需要, 如果需要准确控制对象的binding,可以尝试使用DynamicObject,重写GET、SET方法来满足自己的需要。

以上是个人对Dynamic和DLR的一些认识和学习。欢迎各位童鞋互相探讨。

文章首发站点:www.agilesharp.com IT创业产品互推平台