dynamic介绍:
前言:
最近公司某项目中类型定义不能在编译期间确定,表结构为动态可变类型。有参考C#4.0最新特性,故成此作。
1. dynamic和var
dynamic是C#4.0新增关键字,和以前动态确定类型的关键字var还是有所不同,以下是区别
dynamic |
var |
|
类型确定时机 |
可推迟到运行期确定数据类型 |
在编译期必须确定数据类型 |
类型改变与否 |
同一dynamic参数可以,针对不同的类型 |
编译期一旦确定了参数类型,则不能改变了 |
函数声明 |
函数声明时参数dynamic是可用的 |
函数声明参数不能使用var |
总结:var类型修饰的参数需要能在编译时确定参数类型,而dynamic可推迟到运行时确定;如果var不能确定类型则编译错误,若dynamic不能确定类型则抛出RuntimeBinderException
2. dynamic和class
一般地,实例化的class可以用dynamic修饰,比如:
1 class ClassA{
2 public int Property1{get;set;}
3 public int Property2{get;set;}
4 public int Method1(){…}
5 public static int Static_Method1(){…};
6 }
7
8 dynamic d=new ClassA();
9 d.Property1=23;
10 d.Property2=@”Hello, Word!”;
11 d.Method1();
以上代码是没有问题,不过dynamic修饰的参数不能调用该类型的static函数。虽说static函数一般为ClassA::Static_Method1()这样的调用方式,不会出现d.Static_Method1()这种状况,但是在“扩展方法”中就会出现这种状况了,如果实现了如下扩展方法:
1 static class ExpandMethod{
2 public static int Add_ClassA(this ClassA a){…}
3 }
这样子的调用d.Add_ClassA()在运行期会抛出RuntimeBinderException这样的异常,如果声明为非dynamic参数ClassA a=new ClassA()这样就可以正常的调用a.Add_ClassA()。
3. dynamic和类属性方法
没有的属性和方法是不能调用的。d.Property3=30, d.Method2()这样的代码虽说在编译器不会报警,但是在运行期会有RuntimeBinderException异常。
若不能确定类方法属性,则需要ExpandoObject类,下面是示例:
1 dynamic expando=new ExpandoObject();
2 expando.Property1=true;
3 expando.Property2=23;
4 expando.Property3=@”Hello Expando!”;
5 delegate int int_add(int a, int b);
6 expando.Method1=new int_add((a,b)=>(a+b));
7 expando.Property4=expando.Method1(12,34);
以上代码能为expando添加公用属性和方法,这也是动态类的一种创建方式。需要注意的是添加共用方法时不能直接添加lambda表达式,dynamic需接收delegate类型。
ExpandoObject里面有一个类似Dictionary<string, object>的结构(确切的说有2个列表,一个保存key,一个保存value),参数名和参数value会一一对应的。
通过上述方式就可以动态定义一个类的属性和方法了。
4. 自定义dynamic动态类
ExpandoObject类型的参数所有方法和属性都是动态的,但是如果我们需要创建一个类型,有些属性是可以在设计时定下来的,但有些类型不能在设计里定下来,对于这种情况我们可以继承ExpandoObject类:
1 class Myclass1:ExpandoObject{
2 int Ensure_Property1;
3 int Ensure_Method1;
4 }
5
6 dynamic dx=new Myclass1();
7 dx.Dynamic_Property1=1;
其实实现这种动态添加属性的根本是实现IDynamicMetaObjectProvider接口,该接口在DLR(动态语言运行时)里定义,ExpandoObject就是实现了这个接口的:
1 class ExpandoObject:IDynamicMetaObjectProvider{}
2 public ExpandoObject(){};
3 }
PS:ExpandoObject还会实现其它接口,没有在此写出来。
如果想要在动态类中添加函数,除了先前的dalegate方法外,还可以使用DynamicObject类型提供的TryGetMember方法;DynamicObject类型也是实现了IDynamicMetaObjectProvider接口,不过里面还添加了易于使用的方法,用户可以override想要修改的方法,从而定制自己的类型工作方式:
1 public class DynamicObject : IDynamicMetaObjectProvider
2 {
3 [TargetedPatchingOptOut ( "Performance critical to inline this type of method across NGen image boundaries" )]
4 protected DynamicObject ( );
5 public virtual IEnumerable<string> GetDynamicMemberNames ( );
6 public virtual DynamicMetaObject GetMetaObject ( Expression parameter );
7 public virtual bool TryBinaryOperation ( BinaryOperationBinder binder, object arg, out object result );
8 public virtual bool TryConvert ( ConvertBinder binder, out object result );
9 public virtual bool TryCreateInstance ( CreateInstanceBinder binder, object[ ] args, out object result );
10 public virtual bool TryDeleteIndex ( DeleteIndexBinder binder, object[ ] indexes );
11 public virtual bool TryDeleteMember ( DeleteMemberBinder binder );
12 public virtual bool TryGetIndex ( GetIndexBinder binder, object[ ] indexes, out object result );
13 public virtual bool TryGetMember ( GetMemberBinder binder, out object result );
14 public virtual bool TryInvoke ( InvokeBinder binder, object[ ] args, out object result );
15 public virtual bool TryInvokeMember ( InvokeMemberBinder binder, object[ ] args, out object result );
16 public virtual bool TrySetIndex ( SetIndexBinder binder, object[ ] indexes, object value );
17 public virtual bool TrySetMember ( SetMemberBinder binder, object value );
18 public virtual bool TryUnaryOperation ( UnaryOperationBinder binder, out object result );
19 }
不用全部方法都实现,只需实现你会用到的就好,比如用户修改TryGetMember方法从而定制自己的属性geter可以如下:
(摘自Programming C#4.0)
1 using System;
2 using System.Dynamic;
3 public class CustomDynamic:DynamicObject{
4 private static DateTime FirstSighting=new DateTime(1947,3,13);
5 public override bool TryGetMember(GetMemberBinder binder, out object result){
6 var compare=binder.IgnoreCase?StringComparer.InvariantCultureIgnoreCase:StringComparer.InvariantCulture;
7 if(compare.Compare(binder.Name, “Brigadoon”)==0){
8 //Brigadoon famous for appearing only once eary 100 years.
9 DateTime today=DateTime.Now.Date;
if(today.DayOfYear==FirstSighting,DayOfYear){
10 //Right day, what about the year?
11 int yearsSinceFirstSighting = today.Year-FirstSighting.Year;
12 if(yearsSinceFirstSighting%100==0){
13 result=”Welcome to Brigadoon. Please drive carefully.”;
14 return true;
15 }
16 }
17 }
18 return base.TryGetMember(binder, out result);
19 }
20 }
这样的话调用dx.Brigadoon时就会处理了。
GetMemberBinder中binder.Name能知道属性名字,binder.IgnoreCase能知道属性是否大小写敏感,默认是Case_Sensitive的,不过某些语言(比如VB)是Case_Insensitive。如此,实现DynamicObject类的对应方法能使类型更加灵活。
总结:
dynamic能较好的处理动态类型,配置动态类。特别是程序运行前不能确定类结构的情况,使用dynamic能很好的解决这个问题。
不过dynamic不能滥用,如果程序能确定类构造形式,则还是推荐使用静态的声明,首先静态声明不影响程序运行效率,而且静态声明能够在编译期检查出类型错误,从而避免运行时函数崩溃。