时间:2016-10-26来源:本站原创作者:佚名

dynamic关键字和动态语言运行时(DLR)是C#4和Microsoft.NETFramework4中的重大新增功能。这些功能在宣布时就引起了人们的极大兴趣,并伴随着许多疑问。同时人们也给出了很多答案,但这些答案现在已散布于各种文档以及各种技术博客和文章之中。这样,人们在各种论坛和会议上总是一遍又一遍地提出相同的问题。

  本文全面概述了C#4中新增的动态功能,并且深入探讨了这些功能如何同其他语言和框架功能(例如反射或隐式类型化变量)一起使用。鉴于已有大量信息可用,我有时会重新使用一些经典示例,并提供指向原始源的链接。我还将提供指向相关内容的大量链接,供您进一步阅读。

  什么是“动态”?

  编程语言有时可划分为静态类型化语言和动态类型化语言。C#和Java经常被认为是静态类型化语言的例子,而Python、Ruby和JavaScript是动态类型化语言的例子。

  一般而言,动态语言不执行编译时类型检查,仅在运行时识别对象的类型。这种方法有利有弊:代码编写起来往往更快、更容易,但同时,由于您不会获得编译器错误,只能通过单元测试和其他方法来确保应用程序正常运行。

C#最初是作为纯静态语言创建的,但C#4添加了一些动态元素,用以改进与动态语言和框架之间的互操作性。C#团队考虑了多种设计选项,但最终确定添加一个新关键字来支持这些功能:dynamic。

dynamic关键字可充当C#类型系统中的静态类型声明。这样,C#就获得了动态功能,同时仍然作为静态类型化语言而存在。若要了解为何以及如何做出了这样的决定,请参考PDC09(microsoftpdc.   当您使用dynamic关键字时,您就告诉编译器关闭编译时检查。网上以及MSDN文档中(msdn.microsoft.   如您所见,可以将不同类型的对象分配给已声明为dynamic的变量。这段代码会通过编译,并在运行时确定对象的类型。不过,下面的代码也会通过编译,但在运行时会引发异常:

dynamicd=test;//Thefollowinglinethrowsanexceptionatruntime.d++;

  原因是相同的:编译器不知道该对象的运行时类型,因此无法告诉您递增操作在此情况下不受支持。

  缺少编译时类型检查也会导致IntelliSense功能无效。由于C#编译器不知道对象的类型,因此它无法枚举该对象的属性和方法。正如在用于VisualStudio的IronPython工具中那样,通过附加的类型推断可能会解决此问题,但目前C#不提供这种类型推断。

  但是,在许多可能获益于动态功能的方案中,由于代码使用了字符串文本而导致IntelliSense还是不可用。本文在后面将对这一问题进行更详细的讨论。

Dynamic、Object还是Var?

  那么,dynamic、object和var之间的实际区别是什么?何时应使用它们?下面是每个关键字的简短定义和一些示例。

  关键字object表示System.Object类型,它是C#类层次结构中的根类型。此关键字经常在编译时无法确定对象类型时使用,而这种情况经常在各种互操作性情形中发生。

  您需要使用显式转换将已声明为object的变量转换为特定类型:

objectobjExample=10;Console.WriteLine(objExample.GetType());

  显然,这将输出System.Int32。但是,因为静态类型为System.Object,所以您在这里需要一个显式转换:

objExample=(int)objExample+10;

  您可以赋予不同类型的值,因为它们都是从System.Object继承的:

objExample=test

  从C#3.0起,关键字var开始用于隐式类型化局部变量以及匿名类型。此关键字经常与LINQ结合使用。当使用var关键字声明变量时,将在编译时根据初始化字符串推断该变量的类型。在运行时无法更改该变量的类型。如果编译器不能推断类型,它会生成一个编译错误:

varvarExample=10;Console.WriteLine(varExample.GetType());

  这段代码会输出System.Int32,与静态类型相同。

  在下面的示例中,因为varExample的静态类型为System.Int32,所以不需要转换:

varExample=varExample+10;

  下面一行不进行编译,因为只能将整数赋给varExample:

varExample=test;

C#4中引入的dynamic关键字可使某些传统上依赖于object关键字的情形更容易编写和维护。实际上,动态类型在后台使用System.Object类型。但与object不同的是,动态类型不需要在编译时执行显式转换操作,因为它仅在运行时识别类型:

dynamicdynamicExample=10;Console.WriteLine(dynamicExample.GetType());

  此段代码会输出System.Int32。

  在下面这一行中不需要转换,因为仅在运行时识别类型:

dynamicExample=dynamicExample+10;

  可以将不同类型的值赋给dynamicExample:

dynamicExample=test;

在C#常见问题解答博客(bit.ly/c95hpl)上,提供了关于关键字object和dynamic之间差别的详细博客文章。

  有时会引起混淆的是,所有这些关键字可以一起使用,即它们不是互相排斥的。例如,我们来看一看下面的代码:

dynamicdynamicObject=newObject();varanotherObject=dynamicObject;

anotherObject的类型是什么?我的回答是:dynamic。请记住,在C#类型系统中,dynamic实际上是一个静态类型,因此,编译器将为anotherObject推断此类型。务必要知道,var关键字不过是一个指令,它让编译器根据变量的初始化表达式推断类型;var不是类型。

  动态语言运行时

  说起C#语言环境中的“dynamic”这一术语,它通常指下面两个概念之一:C#4中的dynamic关键字或DLR。虽然这两个概念是相关的,但也务必要了解它们之间的差别。

DLR有两个主要目的。首先,它实现动态语言和.NETFramework之间的互操作。其次,它将动态行为引入C#和VisualBasic之中。

DLR的创建吸取了构建IronPython(ironpython.net)时的经验教训(IronPython是在.NETFramework上实现的第一种动态语言)。在构建IronPython时,工作团队发现他们可以针对多种语言重复使用他们的实现,因此,他们为.NET动态语言创建了一个公共基础平台。与IronPython一样,DLR已成为一个开源项目,其源代码目前在dlr.codeplex.   后来,.NETFramework4中也纳入了DLR,以支持C#和VisualBasic中的动态功能。如果您只需要C#4中的dynamic关键字,那么使用.NETFramework就可以了。在大多数情况下,仅凭.NETFramework即可处理与DLR之间的所有交互。但是,如果您希望实现新的动态语言或将其迁移到.NET,则可以获益于开源项目中额外的帮助程序类,该开源项目为语言实现人员提供了更多功能和服务。

  在静态类型化语言中使用Dynamic

  我们并不期待每个人都尽可能使用动态而不是静态类型声明。编译时检查是一个强大的工具,对它的使用多多益善。而且,再次指出,C#中的动态对象不支持IntelliSense,这对总体工作效率可能会有些影响。

  同时,在出现dynamic关键字和DLR之前,有一些方案在C#中曾经难以实现。在以前的大多数情况下,开发人员使用System.Object类型和显式转换,同样不能很好地利用编译时检查和IntelliSense。下面是一些例子。

  人们最熟知的一个情况是,有时必须使用object关键字来实现与其他语言或框架的互操作性。通常,您必须依靠反射来获取对象的类型以及访问其属性和方法。语法有时难以阅读,因此代码难以维护。此时使用动态功能可能比使用反射更加容易和方便。

AndersHejlsberg在PDC08(channel9.msdn.   该函数返回一个计算器,但系统在编译时不知道此计算器对象的精确类型。代码所依赖的唯一事情是此对象应具有Add方法。请注意,此方法无法使用IntelliSense,因为您以字符串文本的形式提供了方法名称。

  使用dynamic关键字,代码就很简单了:

dynamiccalc=GetCalculator();intsum=calc.Add(10,20);

假设情况没有变化:存在某种我们希望其具有Add方法的未知类型的对象。与上一个示例一样,此方法也不能使用IntelliSense。但语法阅读和使用起来要容易很多,看上去就像在调用一个普通的.NET方法。

  动态方法包

  可以利用动态功能的另外一个例子是创建动态方法包,动态方法包就是可在运行时添加和删除属性及方法的对象。

.NETFramework4有一个新的命名空间:System.Dynamic。此命名空间实际上是DLR的一部分。System.Dynamic.ExpandoObject和System.Expando.DynamicObject类与新的dynamic关键字相结合,有助于以清晰和易于阅读的方式来创建动态结构和层次结构。

  例如,下面说明了如何使用ExpandoObject类来添加属性和方法:

dynamicexpando=newExpandoObject();expando.SampleProperty=Thispropertywasaddedatruntime;expando.SampleMethod=(Action)(()=Console.WriteLine(expando.SampleProperty));expando.SampleMethod();

  要了解更加深入的方案,您一定要看看关于ExpandoObject和DynamicObject类的MSDN文档。同时,还有一些值得一看的文章,比如由BillWagner撰写的文章“动态方法包”(msdn.microsoft.   类包装

  您可以为自己的库提供更好的语法,或为现有库创建包装。与前两个方案相比,这是一个更高级的方案,并且需要对DLR具体内容有更深入的了解。

  对于简单情况,可以使用DynamicObject类。在这个类中,可以将方法和属性的静态声明与动态调度进行混合。这样,您就可以在一个类属性中存储一个要为其提供更佳语法的对象,但通过动态调度来处理针对该对象的所有操作。

  例如,请看一下图1中的DynamicString类,该类包装了一个字符串,并在通过反射实际调用所有方法之前显示这些方法的名称。

publicclassDynamicString:DynamicObject{stringstr;publicDynamicString(stringstr){this.str=str;}publicoverrideboolTryInvokeMember(InvokeMemberBinderbinder,object[]args,outobjectresult){Console.WriteLine(Callingmethod:{0},binder.Name);try{result=typeof(string).InvokeMember(binder.Name,BindingFlags.InvokeMethod

BindingFlags.Public

BindingFlags.Instance,null,str,args);returntrue;}catch{result=null;returnfalse;}}}

  若要实例化该类,应使用dynamic关键字:

dynamicdStr=newDynamicString(Test);Console.WriteLine(dStr.ToUpper());Console.ReadLine();

  当然,这个特定示例出于演示目的而设计,不具有实际效率。但是,如果您拥有已严重依赖于反射的API,就可以如此处所示将所有通过反射进行的调用打包,以便针对API的最终用户隐藏这些调用。

  有关更多示例,请参见MSDN文档(msdn.microsoft.   如前所述,DynamicObject类是由DLR提供的。生成动态对象所需要的仅仅是DynamicObject或ExpandoObject。但是,某些动态对象具有用于访问成员和调用方法的复杂绑定逻辑。这种对象需要实现IDynamicMetaObjectProvider接口并提供其自己的动态调度。这是一种高级方案,感兴趣的读者可以读一下由BillWagner撰写的文章“实现动态接口”(msdn.microsoft.   可编写脚本的应用程序

  脚本是向应用程序提供可扩展性的一种强大方法。MicrosoftOffice可作为这方面的一个好例子:由于VisualBasicforApplications(VBA)的存在,可以使用大量的宏、加载项和插件。现在,DLR提供了一组公用的语言宿主API,因此可让您创建可编写脚本的应用程序。

  例如,您可以创建一个应用程序,使用户能够自己在其中添加功能而不需要主产品提供新功能,例如向游戏中添加新的字符和映射,或向业务应用程序添加新的图表。

  您必须使用来自dlr.codeplex.   有关使用此功能的详细信息,请观看PDC09(microsoftpdc.   识别动态对象

  如何区分动态对象与其他对象?一个简便方法是使用内置的IDE功能。您可以将鼠标光标悬停在对象上以查看其声明类型,或检查IntelliSense是否可用。

  然而在运行时,情况会变得更加复杂。您无法检查变量是否是通过dynamic关键字声明的—动态对象的运行时类型是它所存储的值的类型,您无法获取其静态类型声明。这种情况与将变量声明为object时的情况相同:在运行时,您只能获取变量所存储的值的类型;无法判断此变量最初是否声明为object。

  运行时所能确定的是对象是否来自DLR。知道这种情况可能十分重要,因为像ExpandoObject和DynamicObject类型的对象可在运行时改变其行为,例如,添加和删除属性及方法。

  此外,也无法使用标准反射方法来获取有关这些对象的信息。如果向ExpandoObject类的实例添加属性,则无法通过反射获取该属性:

dynamicexpando=newExpandoObject();expando.SampleProperty=Thispropertywasaddedatruntime;PropertyInfodynamicProperty=expando.GetType().GetProperty(SampleProperty);//dynamicPropertyisnull.

  有利的方面是,在.NETFramework4中,所有可动态添加和删除成员的对象都必须实现一个特定接口:System.Dynamic.IDynamicMetaObjectProvider。DynamicObject和ExpandoObject类也实现了这个接口。不过,这并不表示任何使用dynamic关键字声明的对象都实现此接口:

dynamicexpando=newExpandoObject();Console.WriteLine(expandoisIDynamicMetaObjectProvider);//Truedynamictest=test;Console.WriteLine(testisIDynamicMetaObjectProvider);//False

因此,如果将动态功能与反射一起使用,则请记住,反射不适用于动态添加的属性和方法,并且最好检查正在反射的对象是否实现了IDynamicMetaObjectProvider接口。

  动态功能与COM互操作

C#团队在C#4版本中专门考虑的COM互操作方案是针对MicrosoftOffice应用程序(如Word和Excel)进行编程。他们的目的是让这一任务在C#中变得像在VisualBasic中那样容易和自然。这也是VisualBasic和C#共同发展策略的一部分,这个策略旨在实现两种语言的功能对等,并相互借鉴最佳、最具效率的解决方案。

  若需了解详细信息,请参阅ScottWiltamuth的VisualStudio博客文章“C#和VB共同发展”(bit.ly/bFUpxG)。

  显示了一段C#4代码,该代码向Excel工作表的第一个单元格中添加一个值,然后向第一列应用AutoFit方法。每行下面的注释显示了C#3.0及更早版本的中的等效代码。

//Addthislinetothebeginningofthefile://usingExcel=Microsoft.Office.Interop.Excel;varexcelApp=newExcel.Application();excelApp.Workbooks.Add();//excelApp.Workbooks.Add(Type.Missing);excelApp.Visible=true;Excel.RangetargetRange=excelApp.Range[A1];//Excel.RangetargetRange=excelApp.get_Range(A1,Type.Missing);targetRange.Value=Name;//targetRange.set_Value(Type.Missing,Name);targetRange.Columns[1].AutoFit();//((Excel.Range)targetRange.Columns[1,Type.Missing]).AutoFit();

  此示例有趣的地方是,您在代码中的任何位置都看不到dynamic关键字。实际上,该关键字仅在下面一行中用到:

targetRange.Columns[1].AutoFit();//((Excel.Range)targetRange.Columns[1,Type.Missing]).AutoFit();

  在C#3.0版中,targetRange.Columns[1,Type.Missing]返回object,这便是需要向Excel.Range转换的原因。但在C#4和VisualStudio中,这样的调用将以静默方式转换为动态调用。因此,C#4中targetRange.Columns[1]的类型实际上是dynamic。

  另一个突出特点是,C#4中的COM互操作改进不仅限于dynamic。由于其他一些新增功能(例如索引属性以及命名参数和可选参数),其他所有行中的代码也有所改进。由ChrisBurrows撰写的MSDN杂志文章“.NETFramework4中的新增C#功能”(msdn.microsoft.   从哪里可以获取更多信息?

  希望本文已涵盖您对C#4中的dynamic关键字可能有的大部分疑问,但我确信本文并非面面俱到。如果您有意见、问题或建议,敬请光临dlr.codeplex.







































沈阳白癜风医院
山东治疗白癜风医院

转载请注明原文网址:http://www.helimiaopu.com/hjpz/1165.html

------分隔线----------------------------