c#使用dynamic类型优化反射的方法

什么是dynamic类型?

微软给出的官方文档中这样解释:在通过 dynamic 类型实现的操作中,该类型的作用是绕过编译时类型检查。 改为在运行时解析这些操作。 dynamic 类型简化了对 COM API(例如 Office Automation API)、动态 API(例如 IronPython 库)和 HTML 文档对象模型 (DOM) 的访问。在大多数情况下,dynamic 类型与 object 类型的行为类似。 但是,如果操作包含 dynamic 类型的表达式,那么不会通过编译器对该操作进行解析或类型检查。 编译器将有关该操作信息打包在一起,之后这些信息会用于在运行时评估操作。 在此过程中,dynamic类型的变量会编译为 object 类型的变量。 因此,dynamic 类型只在编译时存在,在运行时则不存在。

dynamic的出现让C#具有了弱语言类型的特性。编译器在编译的时候不再对类型进行检查,编译期默认dynamic对象支持你想要的任何特性。

下例中生成的类型是一致的:

dynamic dyn = "Fode";        Object obj = "Fode";
// Rest the mouse pointer over dyn and obj to see their
 // types at compile time.
 System.Console.WriteLine(dyn.GetType());
 System.Console.WriteLine(obj.GetType());

其输出结果都是String类型,可知CLR可以正确的识别出dynamic是哪种类型,在反编译看看其生成的IL代码:

IL_0000: nop
 IL_0001: ldstr "Fode"
 IL_0006: stloc.0
 IL_0007: ldstr "Fode"
 IL_000c: stloc.1
 IL_000d: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
 IL_0012: pop
 IL_0013: ret

JIT编译器将dynamic识别为String类型,并将其推算到运算栈中(IL代码中 ldstr(将新对象引用推送到存储在元数据中的字符串文字)、(stloc.*)从评估堆栈的顶部弹出当前值,并将其存储在索引*处的本地变量列表中),不同IL代码也不所谓,前文只是介绍dynamic这个类型关键字,只需要你知道他的类型是绕过编译器就可以,如以下操作,Object类型就会报编译的错误。但是,对于 dyn + 3,不会报告任何错误。 在编译时不会检查包含 dyn 的表达式,原因是 dyn 的类型为 dynamic。

  dynamic dyn = "Fode";
  Object obj = "Fode";
  dyn = dyn + 3;
  obj = obj + 3; //这句代码编译器会报错

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

static void Main(string[] args)
 {
  dynamic dyn = GetStudent();

  //正确的操作
  Console.WriteLine(dyn.Age);
  Console.WriteLine(dyn.Name);
  dyn.PrintName();

  //错误的操作
  //Console.WriteLine(dyn.Birthday); //该对象没有包含该属性
  //dyn.PrintAge(); //这行代码会报错误,访问级别不够
  Console.ReadKey();
 }
 static Student GetStudent()
 {
  Student student = new Student();
  student.Age = 21;
  student.Name = "Fode";
  return student;
 }

 class Student
 {
  public String Name { get; set; }
  public Int32 Age { get; set; }

  public void PrintName()
  {
  Console.WriteLine(this.Name);
  }

  private void PrintAge()
  {
  Console.WriteLine(this.Age);
  }
 }

如果运行时dyn对象不包含指定的特性(属性、字段、方法等),运行时会抛出一个运行时的错误 RuntimeBinderException。

注意:有人可能会将var关键字与dynamic进行比较。实际上,var和dynamic完全是两回事,两个不同的概念。var实际上是编译期间抛给我门的“语法糖”,一旦被编译,编译器会自动匹配var变量的实际类型,并用实际类型来替换给变量的声明,这看上去就好像我们在编码的时候用实际类型进行声明一样,优点也显而易见,当【赋值方】类型发生变化时,【实现方】无需改变其类型,因为var会自动去适配。而dynamic被编译后,实际上是一个Object类型,只不过编译器会对dynamic类型进行特殊处理,让它在编译期间不进行任何的类型检查,而是将类型检查放到了运行期。这从VS这个IDE就能看出,在编辑窗口中,var支持【智能感知】,因为vs能推断出var类型的实际类型;而dynamic声明的变量却不支持【智能感知】,因为对其运行期的类型一无所知。对dynamic变量使用【智能感知】会提示"此操作将在运行时解析"。

BB了这么久,重点来了,利用好了动态类型dynamic的这个特性,可以简化C#中的反射语法,更高深的优化将在以后的博客推出。在dynamic出现之前,我们先用基础反射获取一个类中的方法,并执行它:

static void Main()
 {
  DynamicObj obj = new DynamicObj();
  var fun = obj.GetType().GetMethod(nameof(obj.CallFun));
  Int32 result = (Int32)fun.Invoke(obj, new Object[] { "Fode" });
  Console.WriteLine(result);
  Console.ReadKey();
 } 

 public class DynamicObj
 {
  public Int32 CallFun(String str)
  {
  return str.Length;
  }
 }

其结果没有什么好讲的,就是一个用反射调用 CallFun() 方法的例子,而用dynamic之后,代码看上去更简洁了,并且在可控制的范围内减少了一次拆箱的操作,代码如下:

 dynamic dyn = new DynamicObj();
 Int32 result = dyn.CallFun("Fode");
 Console.WriteLine(result);

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

CodeTimer.Time("使用dynamic", 10000000, () => { //执行里面的代码10000000次
  dynamic dyn = new DynamicObj();
  Int32 result = dyn.CallFun("Fode");
  });

  CodeTimer.Time("使用基础反射", 10000000, () => { //执行里面的代码10000000次
  DynamicObj obj = new DynamicObj();
  var fun = obj.GetType().GetMethod(nameof(obj.CallFun));
  Int32 result = (Int32)fun.Invoke(obj, new Object[] { "Fode" });
  });
  Console.ReadKey();

其运行结果如下所示:

从以上结果看出,使用dynamic使用时间为481ms,基础反射使用时间为3063ms,CPU和时间上相差了5倍多,测试器 CodeTimer 的代码随后会贴出。

总结:

可以看到虽然用dynamic优化后的反射跟基础反射的相比,效率虽然在同一个数量级上。可是基础反射却没有dynamic代码简洁,因此建议:始终使用dynamic来简化反射实现(前提你知道你要是实现的类型),在往后的随笔,将会提出用ExpressionTree和Emit技术深度优化反射。

代码下载:http://xiazai.jb51.net/201812/yuanma/ConsoleApp2_jb51.rar

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • C#动态对象(dynamic)详解(实现方法和属性的动态)

    C#的动态对象的属性实现比较简单,如果要实现动态语言那种动态方法就比较困难,因为对于dynamic对象,扩展方法,匿名方法都是不能用直接的,这里还是利用对象和委托来模拟这种动态方法的实现,看起来有点javascript的对象味道: 1) 定义一个委托,参数个数可变,参数都是object类型:这里的委托多有个dynamic参数,代表调用这个委托的动态对象本身. public delegate object MyDelegate(dynamic Sender, params object[] PMs

  • 详解C# 匿名对象(匿名类型)、var、动态类型 dynamic

    随着C#的发展,该语言内容不断丰富,开发变得更加方便快捷,C# 的锋利尽显无疑.C# 语言从诞生起就是强类型语言,这一性质到今天不曾改变,我想以后也不会变.既然是强类型语言,那编写任一程序均要求满足下面的基本条件: 1.变量声明必须指明其类型 2.变量类型明确后,其类型在Runtime亦不能改变 代码如下: public class Student { public string Name { get; set; } public int Age { get; set; } public str

  • C# 反射与dynamic最佳组合示例代码

    在 C# 中反射技术应用广泛,至于什么是反射.........你如果不了解的话,请看下段说明,否则请跳过下段.广告一下:喜欢我文章的朋友请关注一下我的blog,这也有助于提高本人写作的动力. 反射:当你背对一个美女或帅哥却不能回头仔细观察研究时(纯属虚构,如有巧合.纯属雷同),一面小镜子就能满足你的需求.在 C# 编程过程中也经常遇到类似的情况:有一个别人写的 dll 类库你想使用却没程序文档资料......此时通过 C# Runtime 提供的功能,你可以把该 dll 类库加载到你的程序中,并

  • C# Dynamic关键字之:解析dynamic就是Object

    C# 4.0提供了一个dynamic 关键字,那么什么是dynamic,究竟dynamic是如何工作的呢? 从最简单的示例开始: 复制代码 代码如下: static void Main(string[] args)        {            dynamic dyn = 1;            object obj = 1;            //在编译时将鼠标放到 "dyn"  和"obj"中可以发现:             // dyn:局

  • C#中dynamic关键字的正确用法(推荐)

    dynamic是FrameWork4.0的新特性.dynamic的出现让C#具有了弱语言类型的特性.编译器在编译的时候不再对类型进行检查,编译期默认dynamic对象支持你想要的任何特性.比如,即使你对GetDynamicObject方法返回的对象一无所知,你也可以像如下那样进行代码的调用,编译器不会报错: dynamic dynamicObject = GetDynamicObject(); Console.WriteLine(dynamicObject.Name); Console.Writ

  • C# dynamic关键字的使用方法

    C#是一种类型安全的编程语言(所有表达式都能解析成某个类型的实例,在编译器生成的代码中,只会执行对这个类型有效的操作),和非类型安全的语言相比,类型安全的优势就体现出来了:1.许多错误能在编译时检测到,取保代码在执行它之前是正确的.2.编译时语言通常能生成更小,更快的代码.(在编译时进行更多的假设,并在IL和元数据中落实那些假设) 为了方便开发人员使用反射或者与基本组件通信,dynamic诞生了!一下代码展示了如何利用反射在一个String目标("根据我找类型")上调用一个方法(&qu

  • C# Dynamic关键字之:dynamic为什么比反射快的详解

    Main方法如下: 复制代码 代码如下: static void Main(string[] args){    dynamic str = "abcd";    Console.WriteLine(str.Length); Console.WriteLine();    Console.WriteLine(str.Substring(1)); Console.ReadLine();} 运行,结果如下: 使用reflactor 反编译下,可以看到: 完整代码如下: 复制代码 代码如下:

  • c#使用dynamic类型优化反射的方法

    什么是dynamic类型? 微软给出的官方文档中这样解释:在通过 dynamic 类型实现的操作中,该类型的作用是绕过编译时类型检查. 改为在运行时解析这些操作. dynamic 类型简化了对 COM API(例如 Office Automation API).动态 API(例如 IronPython 库)和 HTML 文档对象模型 (DOM) 的访问.在大多数情况下,dynamic 类型与 object 类型的行为类似. 但是,如果操作包含 dynamic 类型的表达式,那么不会通过编译器对该

  • Golang反射获取变量类型和值的方法详解

    目录 1. 什么是反射 2. reflect.Type 2.1 类型Type和种类Kind 2.2 引用指向元素的类型 2.3 结构体成员类型 3. reflect.Value 3.1 结构体的成员的值 3.2 遍历array.slice 3.3 遍历map 4. 反射的三大定律 4.1 从interface到反射对象 4.2 从反射对象到interface 4.3 通过反射修改对象,该对象值必须是可修改的 1. 什么是反射 反射是程序在运行期间获取变量的类型和值.或者执行变量的方法的能力. G

  • 在 .NET 平台使用 ReflectionDynamicObject 优化反射调用的代码详解

    基于封装的原则,API 的设计者会将部分成员(属性.字段.方法等)隐藏以保证健壮性.但总有需要直接访问这些私有成员的情况. 为了访问一个类型的私有成员,除了更改 API 设计还有就是使用反射技术: public class MyApi { public MyApi() { _createdAt = DateTime.Now; } private DateTime _createdAt; public int ShowTimes { get; private set; } public void

  • 利用lambda表达式树优化反射详解

    前言 本节重点不讲反射机制,而是讲lambda表达式树来替代反射中常用的获取属性和方法,来达到相同的效果但却比反射高效. 每个人都知道,用反射调用一个方法或者对属性执行SetValue和GetValue操作的时候都会比直接调用慢很多,这其中设计到CLR中内部的处理,不做深究.然而,我们在某些情况下又无法不使用反射,比如:在一个ORM框架中,你要将一个DataRow转化为一个对象,但你又不清楚该对象有什么属性,这时候你就需要写一个通用的泛型方法来处理,以下代码写得有点恶心,但不妨碍理解意思: //

  • java反射之方法反射的基本操作方法

    本文接上文"java反射之获取类的信息方法(推荐)",利用反射(invoke)来获取一个类中的方法来执行. 1.定义一个类,包含三个名称相同,参数不同的方法 class A{ public void print(){ System.out.println("Hello,World"); } public void print(int a,int b){ System.out.println(a+b); } public void print(String a,Str

  • java反射拼接方法名动态执行方法实例

    近期由于负责项目的一个模块,该模块下有很多分类,每个分类都有一个编码code,这个值是作为一个参数携带过来的.但是每个code确实对应一个方法的. code的值有很多个,自己又不想做ifelse或者switch判断于是就狂搜资料,主要让我发现利用java的反射机制可以完美的解决这个问题 测试代码如下:(可以携带多个参数哦) package com.escs.xmlutils; import java.lang.reflect.Method; public class Test { public

  • c#反射调用方法示例

    获取方法的相关信息的两种形式 反射是一种允许用户获得类信息的C#功能,Type对象映射它代表的底层对象: 在.Net 中, 一旦获得了Type对象,就可以使用GetMethods()方法获取此类型支持的方法列表:该方法的两种形式: MethodInfo [] GetMethods() MethodInfo [] GetMethods(BindingFlags bindingflas)  :它的参数带有一些限制 BindingFlags  是一个枚举 枚举成员 [DeclaredOnly,Inst

  • Android编程使用缓存优化ListView的方法

    本文实例讲述了Android编程使用缓存优化ListView的方法.分享给大家供大家参考,具体如下: ListView调用Adapter的getView方法获取每一个Item布局,将这些已经获得的Item布局放入缓存,将大大提高获取数据的效率,而且节省更多的流量,将数据进行缓存有两种方法是,一种是将内存缓存一种是sd卡缓存,在此分别进行演示. sd卡缓存: sd卡缓存是将下载的数据保存到sd卡中,当再次要获取数据时,首先要判断sd卡中是否存在,如果存在的话,就直接读取sd卡中的数据,如果不存在就

  • C#高效反射调用方法类实例详解

    C#高效反射调用方法类 1.创建一个业务类(HomeService),在类下创建3个方法 2.正常方式调用类的方法 3.反射方式调用类的方法 4.调用代码 5.调用结果 6.Service类方法代码 内容扩展: 1.正常方式调用类的方法 /// <summary> /// 正常调用类的方法(parm1) /// </summary> /// <returns></returns> public string GetNormalMethod_2() { Hom

随机推荐