• 从Delegate.CreateDelegate看值类型的实例方法


          前面一片随笔讲过用Delegate.CreateDelegate来提高多次反射效率的,使用代价较小的委托来代替反射的Invoke。
          在Delegate.CreateDelegate方法对实例方法有一些默认的转换,例如:String.Trim()这个实例方法可以转换成下面两种委托:
    string delegate TrimDelegate1();
    string delegate TrimDelegate2(string str);
          第一个委托是绑定到一个具体的字符串实例的,第二个委托是不绑定到一个具体的字符串实例的。
          在CreateDelegate的时候也略有不同,创建第一个委托的实例需要用Delegate的public static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method);方法(或类似需要firstArgument的重载),而创建第二个委托的实例时,需要用Delegate的public static Delegate CreateDelegate(Type type, MethodInfo method);方法(或类似不需要firstArgument的重载)。
          第二类委托比第一类委托更灵活,因为它们仅仅绑定到类,而不是实例,当需要变换实例时,仅仅只需要第一个参数传入另外一个实例就可以了,不需要重新绑定到对象(重新绑定需要比较高的开销,主要花费在BindToMethodInfo或BindToMethodName)。
          换而言之,第二类委托反映了一个对象的实例方法的真实实现,将this当成函数的第一个参数传入函数体。(熟悉IL的应该可以立即想到实例方法的Ldarg_0就是this,而静态方法的Ldarg_0却是函数的第一个参数。)
          但是,第二个委托却有一个问题存在,那就是对值类型无效。
          可以做个试验,目标方法是Point的Offset(Point pt)方法,委托为下面两个委托:
    void delegate OffsetDelegate1(Point pt);
    void delegate OffsetDelegate2(Point @this, Point pt);
          第一个委托可以创建出来,而第二个委托却创建不出来,无法正确的绑定到方法上。
          可以发现一样的方式对值类型就无效了,为什么哪?
          假想一下,JIT生成了一个本机代码的函数,这个函数的签名是第二个委托的签名,传入第一个Point,也就是this,再传入第二个Point也就是位移量,函数修改了第一个参数(也就是this)的值,然后返回。好,问题来了,第一个参数没有返回,对函数外而言,并不知道函数对第一个参数(也就是this)修改了,也不可能获得这个修改后的值,为什么,因为它是值类型,而且是参数是按值传递的,于是,函数对第一个参数的任何修改都仅仅是对函数体内那个参数的副本的修改,根本不会影响到外面的值。
          如何来避免这个问题?很简单,值类型已经是一个无法修改的事实了,那么,就只能修改第一个参数的传递方式,也就是改成按引用传递。这样任何函数内部对值的修改都将是有效的。
          换而言之,正确的第二类委托应该是:
    void delegate OffsetDelegate3(ref Point @this, Point pt);
          这个委托才可以被Delegate.CreateDelegate正确的绑定。
          也就是说,值类型的实例方法也引用类型的实例方法的一个重要区别是:值类型的实例方法的this是按引用传递的,而引用类型的实例方法的this是按值传递的。
          熟悉IL的话,可以想一下,Ldflda这个操作码,它获得的是一个字段的地址而不是一个字段的实际值,地址有什么用?如果这个字段是值类型,那就有用了,因为按引用传递传的是什么?不就是一个地址吗?完全没必要用Ldfld获得值,在变成引用,再调用函数,还要再用Stfld设置值。用一个Ldflda就把这些问题全搞定了,传过去就是本实例的某个字段的地址,被调用函数内部的修改也都是对这个字段的修改。
  • 相关阅读:
    Nginx 反向代理、负载均衡、页面缓存、URL重写及读写分离详解
    大型网站系统架构技术原理透析
    大中型网站架构探秘
    大型网站架构不得不考虑的10个问题
    (推荐)高并发高流量网站架构详解
    可扩展、高可用、负载均衡网站架构设计方案
    nginx内置常用变量
    Linux下nginx支持.htaccess文件实现伪静态的方法!
    扩展js,实现c#中的string.format方便拼接字符串
    Winform退出运行后,删除运行目录(批处理方法)
  • 原文地址:https://www.cnblogs.com/vwxyzh/p/819703.html
Copyright © 2020-2023  润新知