重學c#系列——委託和匿名函式[二十五]

敖毛毛 發表於 2022-11-24
C#

前言

簡單介紹一下什麼是委託。

正文

以前也寫過委託,這次算是重新歸檔,和新的補充吧。

https://www.cnblogs.com/aoximin/p/13940125.html

有些人說委託是函式指標的包裝,也有些人說委託是一個方法或多個方法的引用。

這都是沒有問題,委託是一個概念,微軟官方文件說委託是一種引用型別,表示對具有特定引數列表和返回型別的方法引用。

我覺得太囉嗦了,實際上就是方法的引用。

上面都是委託的概念,但是實現方式每種語言可能都不一樣。

比如c++ 和 c 用的是函式指標,而c# 用的是生成包裝類(等下IL),當然本質還是函式指標。

那麼來看下委託。

internal class Program
{
	delegate int TestDelegate(int a);

	static void Main(string[] args)
	{
		TestDelegate a = test;
		a(0);
	}

	public static int test(int a)
	{
		return 0;
	}
}

將test 給了委託a,然後呼叫的時候直接a()就可以了。

用起來十分簡單。

實際上對IL來說其實是沒有委託這個概念的,透過反編譯來看下原理。

private static void Main(string[] args)
{
	TestDelegate a = new TestDelegate(test);
	a(0);
}

實際上會生成TestDelegate這樣一個類,然後將test 引用新增進去。

來看下il。

重學c#系列——委託和匿名函式[二十五]

再看下TestDelegate是一個什麼樣的類。

重學c#系列——委託和匿名函式[二十五]

就是把呼叫的object 和 方法的引用放入包裝類中了,然後invoke 可以進行呼叫。

如果是多個方法的引用呢?

internal class Program
{
	delegate int TestDelegate(int a);

	static void Main(string[] args)
	{
		TestDelegate a = test;
		a += test;
		a(0);
	}

	public static int test(int a)
	{
		return 0;
	}
}

看下IL:

重學c#系列——委託和匿名函式[二十五]

原理就是又new了一個TestDelegate,然後用Delegate 將兩個相連。

Combine 是一個靜態方法哈。

重學c#系列——委託和匿名函式[二十五]

本質是呼叫a的combineImp這個方法。之所以有這個一個靜態方法是為了避免出現a為空的情況,如果a為空,直接把b給a啊。

這個是我們寫鏈式結構可以學習的,這樣就不用判斷宣告的時候是否為空。

然後c# 幫我們提取定義了很多委託,以至於我們幾乎不用去宣告委託。

比如Func 和 Action,Func 有返回值,Action沒有。

重學c#系列——委託和匿名函式[二十五]

下面介紹匿名函式,匿名函式有兩個要介紹的,他們分別是匿名方法和lambda表示式。

他們原理都一樣,都是生成匿名函式,只是寫法不一樣。

delegate int TestDelegate(int a);

static void Main(string[] args)
{
	TestDelegate a = delegate (int a)
   {
	   return 0;
   };
}

看下反編譯後的內容。

private static void Main(string[] args)
{
	TestDelegate a = <>c.<>9__1_0 ?? (<>c.<>9__1_0 = new TestDelegate(<>c.<>9.<Main>b__1_0));
}

那麼看下<>c 這個類:

重學c#系列——委託和匿名函式[二十五]

首先看到第一個框,那麼作者的意思是想把 <>c做成一個單例。

裡面有委託的引用。然後下面這個

b__1_0 就是生成的方法。

其實匿名方法還是編譯幫忙生成對應的方法名。

如果用lambda 表示式寫的話,那麼是這樣寫的:

重學c#系列——委託和匿名函式[二十五]

這種寫法編譯出來的程式碼一模一樣。只是不同寫法的問題。

值得注意的是匿名函式如果引用了外部的資訊,那麼會形成閉包。

比如說:

static void Main(string[] args)
{
	Student s = new Student();
	TestDelegate a = (a) => {
		s = null;

		return 0;
	};

	a += (b) => {
		return 0;
	};

	a += (c) => {
		return 0;
	};
}

首先b和c(第二個和第三個匿名)沒有引用外部物件,那麼都會生成在<>c這個類中。

第一個有外部引用生成了另外一個類。

重學c#系列——委託和匿名函式[二十五]

然後例項化<>c__DisplayClass1_0後,那麼會將s賦值進來。

重學c#系列——委託和匿名函式[二十五]

所以會形成這種閉包,這是值得注意的地方。

下一節委託的釋出訂閱與事件。