這幾天比較清閒,剛好前段時間買了本CLR Via C#,在.Net界大家都知道Jeffrey大師的這本經典著作,慚愧一直沒有拜讀。
不過在C#著作中經典的非常多。讀書的那時候就是通過《C#與.NET 3.0高階程式設計》這本書接觸C#的,也被稱為C#聖經。
CLR Via C#中的委託說的非常好,比較深入,讓人耳目一新,回味無窮的感覺,看這本書的時候一定要記得帶上ILDasm和
Reflector,有了他們就可以讓我們更加深入的看一些問題,讀書的方式有很多種,提問式的,提煉式的,因為任何東西都遵循
“八二原則“,100個字中,有營養的文字也就區區20個而已,抓住了就理解了,好了,就亂扯到這裡了。
Q: 什麼是委託
A: 委託就是一個繼承自MulticastDelegate的類,不信可以用 ILdasm 看一下。
1 public delegate void FlyAction();
Q: 我從ILdasm中看到了Invoke方法,但是我在Delegate卻沒有看到該方法的定義?為什麼?
A: 這其實是混淆了delegate關鍵字和FCL中的Delegate型別,而對於delegate關鍵字,編譯器和CLR給我們做了很多的優化,
並且掩蓋了很多複雜的細節,而FCL中的Delegate並沒有。
Q: 我知道可以將方法作為引數傳遞給委託,然後可以在任何地方通過委託的invoke來執行那個作為引數的方法,請問是如何做到的?
A:既然可以在任何地方引用這個引數的方法,那就要看看方法是如何入侵到委託的。
① 先看看例項程式碼:
1 namespace Demo 2 { 3 class Program 4 { 5 public delegate void FlyAction(); 6 7 static void Main(string[] args) 8 { 9 Bird bird = new Bird(); 10 11 FlyAction action = new FlyAction(bird.Fly); 12 13 action.Invoke(); 14 } 15 } 16 17 public class Bird 18 { 19 public static Random rand = new Random(); 20 21 public void Fly() 22 { 23 return "i can fly " + rand.Next(); 24 } 25 } 26 }
從第一個QA中我們也看到了,其實委託是就是類,那我new一個類時,bird.fly其實就是類的建構函式的引數。
② 再來看看生成的IL中的建構函式。
這時候問題就出來了,為什麼這裡有兩個引數,而new FlyAction(bird.Fly) 中卻是一個引數,這裡貌似是有問題的,不符合語法規
則,其實這裡還是編譯器提供了一箇中間層,通過這個中間層做了一個轉化,從而給我們隱藏了具體邏輯。其實在delegate中提供了
一個Target和Method屬性,當我們傳入bird.Fly時,Target記錄了Bird這個類,Method記錄了Bird中的Fly方法,所以invoke的時
候會自動觸發Bird類中的Fly方法。證據如下:
這裡補充一點,當new FlyAction的時候如果是靜態方法,那麼Target=null的,編譯器只需要通過Method就能找到觸發的方法。
Q:為什麼在多播委託中,不建議回撥方法有返回值,即使有返回值,多播中的方法也只能返回最後一個值,如果我想獲取每個方法
的返回值,應該怎麼處理? 例項程式碼如下:
1 class Program 2 { 3 public delegate string FlyAction(); 4 5 static void Main(string[] args) 6 { 7 Bird bird = new Bird(); 8 9 FlyAction action1 = new FlyAction(bird.Fly); 10 11 FlyAction action2 = new FlyAction(bird.Fly); 12 13 FlyAction action3 = new FlyAction(bird.Fly); 14 15 action1 += action2; 16 17 action1 += action3; 18 19 Console.WriteLine(action1.Invoke()); 20 21 var result = action1.GetInvocationList(); 22 23 Console.Read(); 24 } 25 } 26 27 public class Bird 28 { 29 public static Random rand = new Random(); 30 31 public string Fly() 32 { 33 return "i can fly " + rand.Next(); 34 } 35 }
A: 既然提到了“多播”,其實就是唬人了,內部原始碼裡面就是維護了一個List,將“多播”中的方法都放入到List中,Invoke的時候,
就迴圈遍歷下List來依次呼叫裡面的方法,這就是為什麼建議不要用“有返回值“的方法。
下面我們可以通過 GetInvocationList 來獲取這個list裡面的方法。
然後我們再來看看這個GetInvocationList 裡面的程式碼是怎麼寫的。
看到了this._invocationList和for迴圈,是不是有一種徹底明白的感覺,如果你想獲取每個方法的返回值,那隻能通過
GetInvocationList拿出來後,自己手工處理了,只有這樣才能拿到“多播委託”中每個方法的返回值。
Q:請問下最後一個問題,問完就睡覺,請問委託可以動態建立嗎?
A:可以的。Delegate中提供了CreateDelegate方法,就是可以動態建立的,舉個例子你就知道了。
1 class Program 2 { 3 public delegate string FlyAction(); 4 5 static void Main(string[] args) 6 { 7 Bird bird = new Bird(); 8 9 //找到類下的方法 10 var method = typeof(Bird).GetMethod("Fly", BindingFlags.Instance | BindingFlags.Public); 11 12 var mydelegate = (FlyAction)Delegate.CreateDelegate(typeof(FlyAction), bird, method); 13 14 var result = mydelegate.Invoke(); 15 } 16 } 17 18 public class Bird 19 { 20 public string Fly() 21 { 22 return "i can fly " + new Random().Next(); 23 } 24 }