深入了解Dynamic & DLR(一)

别来无恙 提交于 2019-12-20 00:48:47

本文主要谈谈个人对DLR的认识和学习,希望能和大家一起探讨下。

主要内容:

  • DLR在面向服务设计的必要性
  • dynamic类型
  • DLR ScriptRuntime
  • DynamicObject & ExpandoObject

1.DLR在面向服务设计中的必要性

题外前因话:商业业务环境的变更总是伴随着技术的变革。近来面向服务编程SOA的兴起是一种必然,涉及以下几个因素:

其一商业经营需要及时快速的调整;

其二在快速调整的同时,无需额外的开销,无需重构代码,重新发布平台;

其三商业平台的扩展性和稳定性;

其四商业平台维护费用;

         SOA在业务平台整体上进行布局整合,发布服务契约,数据契约等等,那么涉及细颗粒度的操作,比如说商场需要实时对销货策略进行变更。1.客户类别不同打折销售,2.节假日不同打折,3.不同的商品类别折扣不同,4.促销商品折扣,5.销售额满多少,折扣多少等等,我们需要以后对销售策略进行实时维护,能新增,删除,修改。在当前我们可能通过App.config文件设置不同参数值来满足我们的业务需求,或者通过dll反射的方式来选择不同销售策略。但这些能灵活配置的有限,无法满足日益增长复杂业务逻辑的变化。对此我们需要这些业务需求能够“动态加载,动态注入”,最好是脚本语言,不需要进行第二次编译,现有的Framework能够自动解析执行。

      C#仍是一种静态的类型化语言,无法完美支持动态编程。而Ruby,Python,JavaScript等天生在动态编程方面有绝对的优势。如果能加载第三方动态语言,并且自身具备和这些动态语言相同的某些动态功能。DLR(Dynamic Language Runtime)是添加到CLR的一系列服务,具有动态运行功能,并且允许加载第三方动态语言脚本,如Ruby和Python。DLR体系结构图如下:

2.新的关键字dynamic

      dynamic是支持动态编程新的关键字,用此关键字声明object,编译器不会检查其类型安全,认定其任何操作都是安全有效的,只有再运行时才会判断,如果有错误,则会抛出RuntimeBinderException异常。这里要提到的是var关键字,var的对象是延迟确定的,当编译成IL时,其类型已经确定,且类型不可在运行时更改。dynamic类型可以在运行时根据需要转换为另外的类型。下表中描述他们的区别:

 

Dynamic

Var

编译器检查

不会检查

会检查

类型安全

不安全抛出RuntimeBinderException

类型安全

类型确定

类型不确定,可多次转换(转换对象之间不必存在关系)

类型延迟确定,可按照继承关系进行转换

下面用代码来展示下dynamic类型转换过程。可惜的是MS未对dynamic对象的方法提供智能感知(实际上对象在运行才能确定,也无从提供其方法,数据信息),感觉回归到了C时代。

    class Program
    {
        static void Main(string[] args)
        {
            dynamic dyn;

            dyn = 10;
            Console.WriteLine(dyn.GetType());
            Console.WriteLine(dyn);

            dyn = "a string object";
            Console.WriteLine(dyn.GetType());
            Console.WriteLine(dyn);

            dyn = new User { Nickname = "loong" };
            Console.WriteLine(dyn.GetType());
            Console.WriteLine(dyn.GetWelcomeMsg());
        }
    }

    class User
    {
        public string Nickname { get; set; }

        public string GetWelcomeMsg()
        {
            return string.Format("Welcome {0}!",this.Nickname);
        }
    }

dynamic对象的类型是在运行时才根据当前对象来进行判断的,并且可以多次变更。这段代码执行结果如下:

good!那这样我们是不是可以说dynamic是万能的呢,实际上由于其对象类型不确定,也决定了不支持扩展方法,匿名方法(包含lambda表达式)也不能用做动态方法调用的参数。其次延伸开来,LINQ也与动态code无缘了。因为大多LINQ都是扩展方法。

C#是一种静态、类型化的语言,那么又是如何实现动态对象和方法的呢。我们先来看静态类型和动态类型在IL中有和区别。如下代码

View Code
 1     class Program 2     { 3         static void Main(string[] args) 4         { 5             StaticUser staticUser = new StaticUser(); 6             //DynamicUser dynamicUser = new DynamicUser(); 7  8             Console.WriteLine(staticUser.Nickname); 9             //Console.WriteLine(dynamicUser.Nickname);10 11             Console.ReadLine();12         }13     }14 15     class StaticUser16     {17         public string Nickname = "Loong";18     }19 20     class DynamicUser21     {22         public dynamic Nickname = "Loong";23     }

我们来看下IL:

动态类型属性声明是

.field public object Nickname.custom instance void [System.Core]System.Runtime.CompilerServices.DynamicAttribute::.ctor() = ( 01 00 00 00 ) 

而静态类型属性声明

.field public string Nickname

动态类型属性是有一个object来代替目前声明的对象,之后用了System.Runtime.CompilerServices.DynamicAttribute。进而动态构造对象。这就是两者之间的差别。

我们再来比较下静态类型和动态类型在方法中执行情况:

首先是静态类型, IL_0001行调用newobj构造函数,IL_0008行获取该属性的value, 然后下一行输出。

View Code
 1 .method private hidebysig static void  Main(string[] args) cil managed 2 { 3   .entrypoint 4   // Code size       26 (0x1a) 5   .maxstack  1 6   .locals init ([0] class Loong.Dynamic.StaticUser staticUser) 7   IL_0000:  nop 8   IL_0001:  newobj     instance void Loong.Dynamic.StaticUser::.ctor() 9   IL_0006:  stloc.010   IL_0007:  ldloc.011   IL_0008:  ldfld      string Loong.Dynamic.StaticUser::Nickname12   IL_000d:  call       void [mscorlib]System.Console::WriteLine(string)13   IL_0012:  nop14   IL_0013:  call       string [mscorlib]System.Console::ReadLine()15   IL_0018:  pop16   IL_0019:  ret17 } // end of method Program::Main

动态类型,通过Microsoft.CSharp.RuntimeBinder初始化Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo C#动态操作执行信息,RuntimeBinderException处理 C# 运行时联编程序中的动态绑定时发生的错误等等。

其中有两个重要的类System.Runtime.CompilerServices.CallSite和System.Runtime.CompilerServices.CallSiteBinder.CallSite主要在运行期间查找动态对象归属类型,个人认为会和系统默认类型匹配, 继而再和自定义类型匹配。也就是说会扫描该应用程序域manged heap,查找该type。以确保该type有需要的执行的属性、方法等等。这个过程是非常耗时耗能的,MS也给出解决方案,CallSite这个类会缓存查找到的type信息,从而避免了重复查找。当CallSite执行完查找任务后,找到对应的type就调用CallSiteBinder()方法,产生表达树(这点和linq类似),表示将要执行的操作。如果没有找到该Type,则会抛出RuntimeBinderException。

View Code
 1 .method private hidebysig static void  Main(string[] args) cil managed 2 { 3   .entrypoint 4   // Code size       125 (0x7d) 5   .maxstack  8 6   .locals init ([0] class Loong.Dynamic.DynamicUser dynamicUser, 7            [1] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000) 8   IL_0000:  nop 9   IL_0001:  newobj     instance void Loong.Dynamic.DynamicUser::.ctor()10   IL_0006:  stloc.011   IL_0007:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> Loong.Dynamic.Program/'<Main>o__SiteContainer0'::'<>p__Site1'12   IL_000c:  brtrue.s   IL_005113   IL_000e:  ldc.i4     0x10014   IL_0013:  ldstr      "WriteLine"15   IL_0018:  ldnull16   IL_0019:  ldtoken    Loong.Dynamic.Program17   IL_001e:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)18   IL_0023:  ldc.i4.219   IL_0024:  newarr     [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo20   IL_0029:  stloc.121   IL_002a:  ldloc.122   IL_002b:  ldc.i4.023   IL_002c:  ldc.i4.s   3324   IL_002e:  ldnull25   IL_002f:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,26                                                                                                                                                                              string)27   IL_0034:  stelem.ref28   IL_0035:  ldloc.129   IL_0036:  ldc.i4.130   IL_0037:  ldc.i4.031   IL_0038:  ldnull32   IL_0039:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,33                                                                                                                                                                              string)34   IL_003e:  stelem.ref35   IL_003f:  ldloc.136   IL_0040:  call       class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,37                                                                                                                                                                string,38                                                                                                                                                                class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>,39                                                                                                                                                                class [mscorlib]System.Type,40                                                                                                                                                                class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)41   IL_0045:  call       class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)42   IL_004a:  stsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> Loong.Dynamic.Program/'<Main>o__SiteContainer0'::'<>p__Site1'43   IL_004f:  br.s       IL_005144   IL_0051:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> Loong.Dynamic.Program/'<Main>o__SiteContainer0'::'<>p__Site1'45   IL_0056:  ldfld      !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>>::Target46   IL_005b:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> Loong.Dynamic.Program/'<Main>o__SiteContainer0'::'<>p__Site1'47   IL_0060:  ldtoken    [mscorlib]System.Console48   IL_0065:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)49   IL_006a:  ldloc.050   IL_006b:  ldfld      object Loong.Dynamic.DynamicUser::Nickname51   IL_0070:  callvirt   instance void class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>::Invoke(!0,52                                                                                                                                                                              !1,53                                                                                                                                                                              !2)54   IL_0075:  nop55   IL_0076:  call       string [mscorlib]System.Console::ReadLine()56   IL_007b:  pop57   IL_007c:  ret58 } // end of method Program::Main

由此可见,编译器需要做的优化工作是很多的。复杂的操作也会带来效能上可见的损耗。

在下一篇文章中我会继续和大家探讨下DLR 和常用的两个类DynamicObject、ExpandoObject。

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!