本专题概要:

  • 动态类型介绍
  • 为什么需要动态类型
  • 动态类型的使用
  • 动态类型背后的故事
  • 动态类型的约束
  • 实现动态行为
  • 总结

引言:

  终于迎来了我们C# 4中特性了,C# 4主要有两方面的改善——Com 互操作性的改进和动态类型的引入,然而COM互操作性这里就不详细介绍的,对于.Net 互操作性我将会在另外一个专题中详细和大家分享下我所了解到的知识,本专题就和大家分享C# 4中的动态类型,对于动态类型,我刚听到这个名词的时候会有这些疑问的——动态类型到底是什么的呢? 知道动态类型大概是个什么的时候,肯定又会有这样的疑问——C# 4中为什么要引入动态类型的?(肯定引入之后可以完成我们之前不能做的事情了,肯定是有好处的),下面就具体介绍了动态类型有哪些内容的。

一、动态类型介绍

  提到动态类型当然就要说下静态类型了,对于什么是静态类型呢? 大家都知道之前C#一直都是静态语言(指定的是没有引入动态类型之前,这里说明下,不是引入了动态类型后C#就是动态语言,只是引入动态类型后,为C#语言增添了动态语言的特性,C#仍然是静态语言),之所以称为静态语言,之前我们写代码时,例如 int i =5;这样的代码,此时i 我们已经明确知道它的类型为int了,然而这样的代码,变量的类型的确定是在编译时确定的,对应的,如果类型的确定是在执行时才确定的类型,这样的类型就是动态类型(C# 4.0中新添加了一个dynamic 关键字来定义我们的动态类型)。面对动态类型,C#编译器做的工作只是完成检查语法是否正确,但无法确定所调用的方法或属性是否正确(之所以会这样,主要还是因为动态类型是运行时才知道它的具体类型,所以编译器编译的时候肯定不知道类型,就没办法判断调用的方法或属性是不是存在和正确了,所以对于动态类型,将不能使用VS提供的智能提示的功能,这样写动态类型代码时就要求开发人员对于某个动态类型必须准确知道其类型后和所具有的方法和属性了,不能这些错误只能在运行程序的过程抛出异常的方式被程序员所发现。)

补充: 讲到dynamic关键字,也许大家会想到C# 3中的var关键字,这里这里补充说明下dynamic, var区别。var 关键字不过是一个指令,它告诉编译器根据变量的初始化表达式来推断类型。(记住var并不是类型),而C# 4中引入的dynamic是类型,但是编译时不属于CLR类型(指的int,string,bool,double等类型,运行时肯定CLR类型中一种的),它是包含了System.Dynamic.DynamicAttribute特性的System.Object类型,但与object又不一样,不一样主要体现在动态类型不会在编译时时执行显式转换,下面给出一段代码代码大家就会很容易看出区别了:

  1. object obj = 10;
  2. Console.WriteLine(obj.GetType());
  3. // 使用object类型此时需要强制类型转换,不能编译器会出现编译错误
  4. obj = (int)obj + 10;
  5. dynamic dynamicnum = 10;
  6. Console.WriteLine(dynamicnum.GetType());
  7. // 对于动态类型而言,编译时编译器根本不知道它是什么类型,
  8. // 所以编译器就判断不了dynamicnum的类型了,所以下面的代码不会出现编译时错误
  9. // 因为dynamicnum有可能是int类型,编译器不知道该变量的具体类型不能凭空推测类型
  10. // 当然也就不能提示我们编译时错误了
  11. dynamicnum = dynamicnum + 10;

二、为什么需要动态类型

  第一部分和大家介绍了什么是动态类型,对于动态类型,总结为一句话为——运行时确定的类型。然而大家了解了动态类型到底是什么之后,当然又会出现新的问题了,即动态类型有什么用的呢? C# 为什么好端端的引入动态类型增加程序员的负担呢? 事实并不是这样的,下面就介绍了动态类型到底有什么用,它并不是所谓给程序员带来负担,一定程度上讲是福音

2.1 使用动态类型可以减少强制类型转换

  从第一部分的补充也可以看到,使用动态类型不需要类型转换是因为编译器根本在编译时的过程知道什么类型,既然不知道是什么类型,怎么判断该类型是否能进行什么操作,所以也就不会出现类似“运算符“+”无法应用于“object”和“int”类型的操作数“或者”不存在int类型到某某类型的隐式转换“的编译时错误了,可能这点用户,开发人员可能并不觉得多好的,因为动态类型没有智能提示的功能。 但是动态类型减少了强制类型转换的代码之后,可读性还是会有所增强。(这里又涉及到个人取舍问题的, 如果自己觉得那种方式方便就用那种的,没必要一定要用动态类型,主要是看那种方式可以让自己和其他开发人员更好理解)

2.2 使用动态类型可以使C#静态语言中调用Python等动态语言

  对于这点,可能朋友有个疑问,为什么要在C#中使用Python这样的动态语言呢? 对于这个疑问,就和在C#中通过P/Invoke与本地代码交互,以及与COM互操作的道理一样,假设我们要实现的功能在C#类库中没有,然而在Python中存在时,此时我们就可以直接调用Python中存在的功能了。

三、动态类型的使用

前面两部分和大家介绍动态类型的一些基础知识的,了解完基础知识之后,大家肯定很迫不及待地想知道如何使用动态类型的,下面给出两个例子来演示动态类型的使用的。

3.1 C# 4 通过dynamic关键字来实现动态类型

  1. dynamic dyn = 5;
  2. Console.WriteLine(dyn.GetType());
  3. dyn = "test string";
  4. Console.WriteLine(dyn.GetType());
  5. dynamic startIndex = 2;
  6. string substring = dyn.Substring(startIndex);
  7. Console.WriteLine(substring);
  8. Console.Read();

运行结果为:

3.2 在C#中调用Python动态语言(要运行下面的代码,必须下载并安装IronPython,IronPython 是在 .NET Framework 上实现的第一种动态语言。http://ironpython.codeplex.com下载 )

  1. // 引入动态类型之后
  2. // 可以在C#语言中与动态语言进行交互
  3. // 下面演示在C#中使用动态语言Python
  4. ScriptEngine engine = Python.CreateEngine();
  5. Console.Write("调用Python语言的print函数输出: ");
  6. // 调用Python语言的print函数来输出
  7. engine.Execute("print 'Hello world'");
  8. Console.Read();

运行结果:

四、动态类型背后的故事

知道了如何在C#中调用动态语言之后,然而为什么C# 为什么可以使用动态类型呢?C#编译器到底在背后为我们动态类型做了些什么事情的呢? 对于这些问题,答案就是DLR(Dynamic Language Runtime,动态语言运行时),DLR使得C#中可以调用动态语言以及使用dynamic的动态类型。提到DLR时,可能大家会想到.Net Framework中的CLR(公共语言运行时),然而DLR 与CLR到底是什么关系呢?下面就看看.Net 4中的组件结构图,相信大家看完之后就会明白两者之间的区别:

从图中可以看出,DLR是建立在CLR的基础之上的,其实动态语言运行时是动态语言和C#编译器用来动态执行代码的库,它不具有JIT编译,垃圾回收等功能。然而DLR在代码的执行过程中扮演的是什么样的角色呢? DLR所扮演的角色就是——DLR通过它的绑定器(binder)和调用点(callsite),元对象来把代码转换为表达式树,然后再把表达式树编译为IL代码,最后由CLR编译为本地代码(DLR就是帮助C#编译器来识别动态类型)。 这里DLR扮演的角色并不是凭空想象出来的,而且查看它的反编译代码来推出来的,下面就具体给出一个例子来说明DLR背后所做的事情。C#源代码如下:

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. dynamic text = "test text";
  6. int startIndex = 2;
  7. string substring = text.Substring(startIndex);
  8. Console.Read();
  9. }
  10. }

通过Reflector工具查看生成的IL代码如下:

  1. private static void Main(string[] args)
  2. {
  3. object text = "test text";
  4. int startIndex = 2;
  5. if (<Main>o__SiteContainer0.<>p__Site1 == null)
  6. {
  7. // 创建用于将dynamic类型隐式转换为字符串的调用点        <Main>o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, string>>.Create(Binder.Convert(CSharpBinderFlags.None, typeof(string), typeof(Program)));
  8. }
  9. if (<Main>o__SiteContainer0.<>p__Site2 == null)
  10. {
  11. // 创建用于调用Substring函数的调用点        <Main>o__SiteContainer0.<>p__Site2 = CallSite<Func<CallSite, object, int, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.None, "Substring", null, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) }));
  12. }
  13. // 调用调用点,首先调用<>p_Site2,即Substring方法,再调用<>P_Site1来将结果进行转换    string substring = <Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, <Main>o__SiteContainer0.<>p__Site2.Target(<Main>o__SiteContainer0.<>p__Site2, text, startIndex));
  14. Console.Read();
  15. }
  16. //编译器生成的内嵌类型为
  17. [CompilerGenerated]
  18. private static class <Main>o__SiteContainer0
  19. {
  20. // Fields
  21. public static CallSite<Func<CallSite, object, string>> <>p__Site1;
  22. public static CallSite<Func<CallSite, object, int, object>> <>p__Site2;
  23. }

从IL代码中可以看出Main方法内包含两个动态操作,因为编译器生成的内嵌类型包含两个调用点(CallSite<T>,CallSite<T>即是System.Runtime.CompilerServices命名空间下的一个类,关于CallSite的具体信息可以查看MSDN中的介绍——CallSite<T> )字段,一个是调用Substring方法(即<>p__Site2),一个是将结果(编译时时dynamic)动态地转换为字符串(即<>p__Site1),下面给出动态类型的执行过程(注意DLR中有一个缓存的概念):

五、动态类型的约束

相信通过前面几部分的介绍大家已经对动态类型有了一定的了解的,尤其是第四部分的介绍之后,大家应该对于动态类型的执行过程也有了一个清晰的认识了,然而有些函数时不能通过动态绑定来进行调用的,这里就涉及到类型类型的约束:

5.1 不能用动态类型作为参数调用扩展方法

不能用动态类型作为参数来调用扩展方法的原因是——调用点知道编译器所知道的静态类型,但是它不知道调用所在的源文件在哪里,以及using指令引入了哪些命名空间,所以在编译时调用点就找不到哪些扩展方法可以使用,所以就会出现编译时错误。下面给出一个简单的示例程序:

  1. var numbers = Enumerable.Range(10, 10);
  2. dynamic number = 4;
  3. var error = numbers.Take(number);  // 编译时错误
  4. // 通过下面的方式来解决这个问题
  5. // 1. 将动态类型转换为正确的类型
  6. var right1 = numbers.Take((int)number);
  7. // 2. 用调用静态方法的方式来进行调用
  8. var right2 = Enumerable.Take(numbers, number);

.2 委托与动态类型不能隐式转换的限制

如果需要将Lambda表达式,匿名方法转化为动态类型时,此时编译器必须知道委托的确切类型,不能不加强制转化就把他们设置为Delegae或object变量,此时不同string,int类型(因为前面int,string类型可以隐式转化为动态类型,编译器此时会把他们设置为object类型。但是匿名方法和Lambda表达式不能隐式转化为动态类型),如果需要完成这样的转换,此时必须强制指定委托的类型,下面是一个演示例子:

  1. dynamic lambdarestrict = x => x + 1; // 编译时错误
  2. // 解决方案
  3. dynamic rightlambda =(Func<int,int>)( x=>x+1);
  4. dynamic methodrestrict = Console.WriteLine; // 编译时错误
  5. // 解决方案
  6. dynamic rightmethod =(Action<string>)Console.WriteLine;

5.3 动态类型不能调用构造函数和静态方法的限制——即不能对动态类型调用构造函数或静态方法,因为此时编译器无法指定具体的类型。

  1. dynamic s = new dynamic();

5.4 类型声明和泛型类型参数

不能声明一个基类为dynamic的类型,也不能将dynamic用于类型参数的约束,或作为类型所实现的接口的一部分,下面看一些具体的例子来加深概念的理解:

  1. // 基类不能为dynamic 类型
  2. class DynamicBaseType : dynamic
  3. {
  4. }
  5. // dynamic类型不能为类型参数的约束
  6. class DynamicTypeConstrain<T> where T : dynamic
  7. {
  8. }
  9. // 不能作为所实现接口的一部分
  10. class DynamicInterface : IEnumerable<dynamic>
  11. {
  12. }

六、实现动态的行为

介绍了这么动态类型,是不是大家都迫不及待地想知道如果让自己的类型具有动态的行为呢? 然而实现动态行为有三种方式:

  • 使用ExpandObject
  • 使用DynamicObject
  • 实现IDynamicMetaObjectProvider接口.

下面就从最简单的方式:

6.1 使用ExpandObject来实现动态的行为

  1. using System;
  2. // 引入额外的命名空间
  3. using System.Dynamic;
  4. namespace 自定义动态类型
  5. {
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. dynamic expand = new ExpandoObject();
  11. // 动态为expand类型绑定属性
  12. expand.Name = "Learning Hard";
  13. expand.Age = 24;
  14. // 动态为expand类型绑定方法
  15. expand.Addmethod = (Func<int, int>)(x => x + 1);
  16. // 访问expand类型的属性和方法
  17. Console.WriteLine("expand类型的姓名为:"+expand.Name+" 年龄为: "+expand.Age);
  18. Console.WriteLine("调用expand类型的动态绑定的方法:" +expand.Addmethod(5));
  19. Console.Read();
  20. }
  21. }
  22. }

运行的结果和预期的一样,运行结果为:

6.2 使用DynamicObject来实现动态行为

  1. static void Main(string[] args)
  2. {
  3. dynamic dynamicobj = new DynamicType();
  4. dynamicobj.CallMethod();
  5. dynamicobj.Name = "Learning Hard";
  6. dynamicobj.Age = "24";
  7. Console.Read();
  8. }
  9. class DynamicType : DynamicObject
  10. {
  11. // 重写方法,
  12. // TryXXX方法表示对对象的动态调用
  13. public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
  14. {
  15. Console.WriteLine(binder.Name +" 方法正在被调用");
  16. result = null;
  17. return true;
  18. }
  19. public override bool TrySetMember(SetMemberBinder binder, object value)
  20. {
  21. Console.WriteLine(binder.Name + " 属性被设置," + "设置的值为: " + value);
  22. return true;
  23. }
  24. }

运行结果为:

6.3 实现IDynamicMetaObjectProvider接口来实现动态行为

由于Dynamic类型在运行时来动态创建对象的,所以对该类型的每个成员的访问都会调用GetMetaObject方法来获得动态对象,然后通过这个动态对象来进行调用,所以实现IDynamicMetaObjectProvider接口,需要实现一个GetMetaObject方法来返回DynamicMetaObject对象,演示代码如下:

  1. static void Main(string[] args)
  2. {
  3. dynamic dynamicobj2 = new DynamicType2();
  4. dynamicobj2.Call();
  5. Console.Read();
  6. }
  7. public class DynamicType2 : IDynamicMetaObjectProvider
  8. {
  9. public DynamicMetaObject GetMetaObject(Expression parameter)
  10. {
  11. Console.WriteLine("开始获得元数据......");
  12. return new Metadynamic(parameter,this);
  13. }
  14. }
  15. // 自定义Metadynamic类
  16. public class Metadynamic : DynamicMetaObject
  17. {
  18. internal Metadynamic(Expression expression, DynamicType2 value)
  19. : base(expression, BindingRestrictions.Empty, value)
  20. {
  21. }
  22. // 重写响应成员调用方法
  23. public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
  24. {
  25. // 获得真正的对象
  26. DynamicType2 target = (DynamicType2)base.Value;
  27. Expression self = Expression.Convert(base.Expression, typeof(DynamicType2));
  28. var restrictions = BindingRestrictions.GetInstanceRestriction(self, target);
  29. // 输出绑定方法名
  30. Console.WriteLine(binder.Name + " 方法被调用了");
  31. return new DynamicMetaObject(self, restrictions);
  32. }
  33. }

运行结果为:

七、总结

  讲到这里动态类型的介绍就已经介绍完了,本专题差不多涵盖了动态类型中所有内容,希望通过本专题大家能够对C# 4.0中提出来的动态类型特性可以有进一步的了解,并且本专题也是这个系列中的最后一篇文章了,到这里C#基础知识系列也就结束了,后面我会整理出这个系列文章的一个索引,从而方便大家收藏,然而C#4中对COM互操作性也有很大的改善,关于互操作的内容将会在后面一个系列文章中和大家分享下我的学习体会。

转载于:https://blog.51cto.com/learninghard/1110708

[C#基础知识系列]专题十七:深入理解动态类型相关推荐

  1. [C#基础知识系列]专题十二:迭代器

    引言: 在C# 1.0中我们经常使用foreach来遍历一个集合中的元素,然而一个类型要能够使用foreach关键字来对其进行遍历必须实现IEnumerable或IEnumerable<T> ...

  2. [C# 基础知识系列]专题十四:深入理解Lambda表达式

    引言: 对于刚刚接触Lambda表达式的朋友们,可能会对Lambda表达式感到非常疑惑,它到底是个什么什么样的技术呢?以及它有什么好处和先进的地方呢?下面的介绍将会解除你这些疑惑. 一.Lambda表 ...

  3. [C#基础知识系列]专题十:全面解析可空类型

    引言: C# 2.0 中还引入了可空类型,可空类型也是值类型,只是可空类型是包括null的值类型的,下面就介绍下C#2.0中对可空类型的支持具体有哪些内容(最近一直都在思考如何来分享这篇文章的,因为刚 ...

  4. [C# 基础知识系列]专题五:当点击按钮时触发Click事件背后发生的事情

    引言: 当我们在点击窗口中的Button控件VS会帮我们自动生成一些代码,我们只需要在Click方法中写一些自己的代码就可以实现触发Click事件后我们Click方法中代码就会执行,然而我一直有一个疑 ...

  5. [C# 基础知识系列]专题十五:全面解析扩展方法

    引言:  C# 3中所有特性的提出都是更好地为Linq服务的, 充分理解这些基础特性后.对于更深层次地去理解Linq的架构方面会更加简单,从而就可以自己去实现一个简单的ORM框架的,对于Linq的学习 ...

  6. 【转】[C# 基础知识系列]专题四:事件揭秘

    引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听到"事件"这个概念的,尤其是写UI的时候,当我们点击一个按钮后VS就会自动帮我们生成一些后台的代码,然 ...

  7. [C# 基础知识系列]专题六:泛型基础篇——为什么引入泛型

    引言: 前面专题主要介绍了C#1中的2个核心特性--委托和事件,然而在C# 2.0中又引入一个很重要的特性,它就是泛型,大家在平常的操作中肯定会经常碰到并使用它,如果你对于它的一些相关特性还不是很了解 ...

  8. 链方法[C# 基础知识系列]专题三:如何用委托包装多个方法——委托链

    最近研究链方法,稍微总结一下,以后继续补充: 弁言: 上一专题分析了下编译器是如何来翻译委托的,从中间语言的角度去看委托,希望可以帮助大家进一步的理解委托,然而之前的分析都是委托只是封装一个方法,那委 ...

  9. [C#基础知识系列]专题十:全面解析可空类型[转]

    原文链接 主要内容: 1:空合并操作符(?? 操作符) ??操作符也就是"空合并操作符",它代表的意思是两个操作数,如果左边的数不为null时,就返回左边的数,如果左边的数为nul ...

最新文章

  1. 随机矩阵理论_MIMO 信道容量的理论模型
  2. classpath详解
  3. C++模板详解——使用篇
  4. Symfony2Book04:Doctrine03-对象关系映射(ORM)
  5. ZeroMQ接口函数之 :zmq - 0MQ 轻量级消息传输内核
  6. 纯靠技术,很难进入大厂了。。。
  7. Python-接口自动化(七)
  8. linux函数 取值溢出,Linux eCryptfs工具parse_tag_3_packet()函数堆溢出漏洞
  9. response.setContentType(“text/html;charset=utf-8“)后依然乱码的解决方法
  10. 【英语天天读】Develop Your Own Helping Rituals
  11. Fastlane基础介绍
  12. 771服务器cpu性能排行,771 cpu性能排行榜
  13. 创建 Windows XP 图标
  14. 一步一步搭建前端监控系统:如何记录用户行为?
  15. 大数据带来新机遇:如何利用大数据技术优化跨境电商运营?
  16. 解析mac苹果电脑与Windows电脑的区别
  17. 电池充放电1c指的是什么
  18. 阻止远程截屏_2020年创建优质技术截屏,演示和远程会议指南
  19. 手机Android存储性能优化架构分析
  20. html5 file对象,javascript – 什么是HTML5 File.slice方法呢?

热门文章

  1. smarty模板基本语法
  2. Seafile:用挂载盘客户端让企业在线编辑云端文件
  3. Mysql中的排序规则utf8_unicode_ci、utf8_general_ci的区别总结
  4. 006.递归和分治思想
  5. 关于union的那些事儿
  6. 编程异常——假设你报createSQLQuery is not valid without active transaction,...
  7. 如何在AjaxPro.net的AjaxMethod中使用session和cookie
  8. Oracle宣布终止所有Intel Itanium平台上的软件开发
  9. 别忘了,明天是BCH的压力测试日
  10. Web开发常规调试方法与常见问题分析