从Delegate.CreateDelegate看值类型的实例方法
生活随笔
收集整理的這篇文章主要介紹了
从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就把這些問題全搞定了,傳過去就是本實例的某個字段的地址,被調用函數內部的修改也都是對這個字段的修改。
????? 在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就把這些問題全搞定了,傳過去就是本實例的某個字段的地址,被調用函數內部的修改也都是對這個字段的修改。
轉載于:https://www.cnblogs.com/vwxyzh/archive/2007/07/16/819703.html
總結
以上是生活随笔為你收集整理的从Delegate.CreateDelegate看值类型的实例方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ICMP (互联网控制消息协议 )是什么
- 下一篇: python基本语法:列表(列表和元组的