使用dynamic来简化反射实现

使用dynamic来简化反射实现

继上一篇“正确实现浅拷贝和深拷贝”后,本文继续看《编写高质量代码改善C#程序的157个建议》一书第15个建议“使用dynamic来简化反射实现”。喜欢本书请到各大商城购买原书,支持正版。

正文开始:


dynamic是 Framework 4.0的新特性。dynamic的出现让C#具有了弱语言类型的特性。编译器在编译的时候不再对类型进行检查,编译器默认dynamic对象支持开发者想要的任何特性。比如,即使你对GetDynamicObject方法返回的对象一无所知,也可以像如下这样进行代码的调用,编译器不会报错:

dynamic dynamicObject = GetDynamicobject ();
Console.WriteLine (dynamicObject.Name) ;
Console.WriteLine(dynamicObject.SampleMethod()) ;

当然,如果运行时dynamicObject不包含指定的这些特性(如上文中带返回值的方法SampleMethod),运行时程序会抛出一个RuntimeBinderException异常:

“System.Dynamic.ExpandoObject”未包含“SampleMethod”的定义。

注意﹐有人会将var这个关键字与dynamic进行比较。实际上,var和dynamic完全是两个概念,根本不应该放在一起比较。var实际上是编译期抛给我们的“语法糖”,一旦被编译,编译期会自动匹配 var变量的实际类型,并用实际类型来替换该变量的声明,这看上去就好像我们在编码的时候是用实际类型进行声明的。而dynamic被编译后,实际是一个object类型,只不过编译器会对dynamic类型进行特殊处理,让它在编译期间不进行任何的类型检查,而是将类型检查放到了运行期。

这从Visual Studio的编辑器窗口就能看出来。以 var声明的变量支持“智能感知”,因为Visual Studio能推断出 var类型的实际类型;而以dynamic声明的变量却不支持“智能感知”,因为编译器对其运行期的类型一无所知。对dynamic变量使用“智能感知”,会提示“此操作将在运行时解析”。

利用dynamic的这个特性,可以简化C#中的反射语法。在dynamic出现之前,假设存在类,代码如下所示:

public class DynamicSample
{
  public string name { get; set; }
  
  public int Add(itn a, int b)
  {
    return a + b;
  }
}

我们这样使用反射,调用方代码如下所示:

DynamicSample dynamicSample = new DynamicSample ();
var addMethod = typeof(DynamicSample).GetMethod("Add");
int re = (int)addMethod.Invoke(dynamicSample,new object[] { 1,2 }) ;

在使用dynamic后,代码看上去更简洁了,并且在可控的范围内减少了一次拆箱的机会,代码如下所示:

dynamic dynamicsample2 = new DynamicSample();
int re2 = dynamicSample2.Add(1, 2);

我们可能会对这样的简化不以为然,毕竟代码看起来并没有减少多少,但是,如果考虑到效率兼优美两个特性,那么dynamic的优势就显现出来了。如果对上面的代码执行1000000次,如下所示:

int times = 1000000;
DynamicSample reflectSample = new DynamicSample();
var addMethod = typeof(DynamicSample).GetMethod ("Add");
Stopwatch watchl = Stopwatch.StartNew();
for (var i =0; i < times; i++)
{
  addMethod.Invoke (reflectsample, new object[] { 1,2 });
}
Console.WriteLine(string.Format(”反射耗时:{0}毫秒", watch1.ElapsedMi1liseconds));

dynamic dynamicSample = new DynanicSample();
Stopwatch watch2 = Stopwatch.StartNew ();
for (int i = 0; i < times; i++)
{
  dynamicsample.Add(1,2);
  Console.WriteLine(string.Format("dynamic耗时:{0}毫秒", watch2.ElapsedMi1liseconds));
}

输出为:

反射耗时:2575 毫秒
dynamic耗时:76毫秒

可以看到,没有优化的反射实现,上面这个循环上的执行效率大大低于dynamic实现的效果。如果对反射实现进行优化,代码如下所示:

DynamicSample reflectSampleBetter = new DynamicSample();
var addMethod2 = typeof(DynamicSample).GetMethod("Add");
var delg = (Func<DynamicSample, int, int, int>)Delegate.CreateDelegate(typeof(Func<Dynamicsample,int, int,int>), addMethod2);
stopwatch watch3 = Stopwatch.StartNew();
for (var i = 0; i < times; i++)
{
  delg(reflectsampleBetter, 1, 2);
  Console.WriteLine(string.Format("优化的反射耗时:{0}毫秒", watch3.ElapsedMi1liseconds));
}

输出为:

优化的反射耗时:12毫秒

可以看到,优化后的反射实现,其效率和dynamic在一个数量级上。可是它带来了效率,却牺牲了代码的整洁度,这种实现在我看来是得不偿失的。所以,现在有了dynamic类型,建议大家:

始终使用dynamic来简化反射实现。


下一篇我们接着读第2章 集合和LINQ的”建议16:元素数量可变的情况下不应使用数组”`,欢迎关注微信公众号【乐趣课堂】。

原文出处:微信公众号【爱读书 乐趣课堂】

原文链接:https://mp.weixin.qq.com/s/dt34JP0LAbC-WSzu-rtsdQ

本文观点不代表Dotnet9立场,转载请联系原作者。

发表评论

登录后才能评论