C#之委派之演進史與匿名函式之應用

匿名函式在這一兩年間,大量的出現在各式程式碼的論壇之中,因為之前不太碰觸到這一塊,因此一直沒有去仔細的研究,今天終於受不了,花了一些時間去讀MSDN文件並寫在這邊記錄起來,讓日後好查閱。

講到匿名函式(Anonymous Functions),一定要先講到委派(Delegate),委派最基本的思想就是定義一個函式A,當呼叫A時,透過指派的路徑派到B那邊工作,很難理解吧!在講白一點好了,如果你今天看到有一個寫好的FFFFFFFF(string s)函式不爽(例如這個函式名字很長),但是你又不確定其它的程式碼裡面是否有調用它,你就可以利用委派的方式指定一個叫作F的函式,並讓F(FFFFFFFF)成立,當你調用的時候就利用F("abcd")來進行即可,因此F可視為是FFFFFFFF的包皮!當然,這不是委派主要的應用方式(只是其一),在這邊舉這個例子是要告訴大家快速的觀念與宣告語法。委派真正的存在應用方式,很向C++的指標(Pointer)。

委派與匿名函式之間的寫法,是有版本的區別,不是你想怎麼寫就怎麼寫,以下的例子從C# 1.0舉到C# 3.0,委派寫法的逐漸演變史,其中要特別注意的是「=>」運算子,這個英文念「goes to」,英文正確的名稱叫「Lambda」,中文正確的名稱叫「黏巴達」。下圖是快打炫風2(Street Fighter II)凱爾(Guile)著名的黏巴達。

好了,廢話不多說,來看一下程式碼吧!運行環境是Console視窗

//Basic delegate define
public delegate void TestDelegate(string s);
/* C# 1.0 */
public static void ShowMsg(string s) { Console.WriteLine("C#1.0: " + s); }

static void Main(string[] args)
{
	/* C# 1.0 */
	TestDelegate oTempA = new TestDelegate(ShowMsg);
	oTempA(Console.ReadLine());

	/* C# 2.0 */
	TestDelegate oTempB = delegate(string s) { Console.WriteLine("C#2.0: " + s); };
	oTempB(Console.ReadLine());

	/* C# 3.0 */
	TestDelegate oTempC = x => { Console.WriteLine("C#3.0: " + x); };
	//單一Parameter下,x不需括號;複數參數就要使用括號,例如:(x, y)
	oTempC(Console.ReadLine());

	Console.ReadKey();
}

從上面的程式碼我們可以發現,「delegate(string s) {...};」這種寫法就是所謂的匿名函式(Anonymous Functions)寫法,而到了「x => {...};」就是所謂的黏巴達表示式(Lambda Expressions)了。可見版本越高寫法越來越精簡,至於可讀性嘛,見人見智嘍!或許這種寫法需要時間的消化吧!至於意義性嘛,在上面的程式碼看起來更弱了,事實上委派出現在一般的應用程式中的機會本來就不是很高,或者應該是說,絕大多數的案例裡,你可以用很傳統的C語言寫法來取代它。精簡與可讀,需要取得一定的平衡。

委派與匿名的應用範例

在某些時刻,我們想要鬆耦合或簡化某「函式名稱方法」之調用,例如兩數的四則運算,你可以用最傳統的函式寫法來完成,但是你就要取四種函式名稱供其調用,當你的函式名稱修改時,就是你苦腦的時刻了!因為可能你的系統裡面有一大堆的舊原始碼都有用到這個名稱。以下這個範例示範怎麼使用單一個函式名稱呼叫,來完成所要操作的功能,程式的重點會擺在把委派的函式實體化時,拿來當作類似C++的指標(pointer)在運作的概念。另外,這個程式用起來也會很像介面(interface)的運作方式。

//Basic delegate define
public delegate int cal(int a, int b);

static void Main(string[] args)
{
	int a = Convert.ToInt32(Console.ReadLine());
	int b = Convert.ToInt32(Console.ReadLine());
	string c = Console.ReadLine();
	
	//宣告一個委派實體
	cal myCal;
	//進行四則運算:黏巴達(Lambda)之匿名函式(Anonymous Functions)之內行式(inline)宣告
	switch (c)
	{
		case "-":
			myCal = (A, B) => { return (A - B); };
			break;
		case "*":
			myCal = (A, B) => { return (A * B); };
			break;
		case "/":
			myCal = (A, B) => { return (A / B); };
			break;
		default:
			myCal = (A, B) => { return (A + B); };
			break;
	}
	
	//真正調用myCal起來運算,非常精簡
	Console.WriteLine(myCal(a, b));

	Console.ReadKey();
}

再修改成更精簡的寫法,不過這樣一改,似乎已經失去Delegate委派應用的意義了。

//Basic delegate define
public delegate int cal(int a, int b, string c);

static void Main(string[] args)
{
	int a = Convert.ToInt32(Console.ReadLine());
	int b = Convert.ToInt32(Console.ReadLine());
	string c = Console.ReadLine();
	
	//宣告一個委派實體
	cal myCal = (A, B, C) =>
	{
		switch (C)
		{
			case "-":
				return (A - B);
				break;
			case "*":
				return (A * B);
				break;
			case "/":
				return (A / B);
				break;
			default:
				return (A + B);
				break;
		}
	};
	
	//真正調用myCal起來運算,非常精簡
	Console.WriteLine(myCal(a, b, c));

	Console.ReadKey();
}

然後我們靈機一動,改成這種寫法,雖然有點脫褲子放屁,不過這樣的寫法目的是為了要把Delegate委派的精神完整表達出來。

delegate int helpMeCalc(int x, int y);
class Program
{
	static int Add(int x, int y) { return x + y; }
	static int Sub(int x, int y) { return x - y; }
	static int Mul(int x, int y) { return x * y; }
	static int Div(int x, int y) { return x / y; }
	static void Main(string[] args)
	{
		Calc(1, 2, Add);
		Calc(3, 4, Sub);
		Calc(5, 6, Mul);
		Calc(8, 2, Div);
	}
	static void Calc(int x, int y, helpMeCalc d)
	{
		Console.WriteLine(d(x, y));
	}
}

接下來,我們當然要懶上加懶,不想再宣告Delegate委派的語句了,於是我們調用了泛型委派(General Delegates),看一下下面的程式碼,程式設計師說不心動是騙人的啦!。

static void Main(string[] args)
{
	Func<int, int, int> Add = (x, y) => { return x + y; };
	Func<int, int, int> Sub = (x, y) => { return x - y; };
	Func<int, int, int> Mul = (x, y) => { return x * y; };
	Func<int, int, int> Div = (x, y) => { return x / y; };
	Calc(1, 2, Add);
	Calc(3, 4, Sub);
	Calc(5, 6, Mul);
	Calc(8, 2, Div);
}
static void Calc(int x, int y, Func<int, int, int> d)
{
	Console.WriteLine(d(x, y));
}

慢慢的我們會發現,寫到最後委派似乎已經變成了關我(Class Methods)屁事,請自己處理!事實上在最終的使用上,委派的確就是走上這一條路,詳見相關參考資料。

相關參考資料:

delegate這個關鍵字進入到泛型領域後,會弱化為「聲明」之意義,有興趣的人可以接著看這篇文章:
泛型委派(通用委派)(General Delegates)之研究

利用Predicate泛型委派,進行類別方法委派之實作

微軟MSDN對於匿名函式的介紹:請點選這裡

C# AnonymousFunctions LambdaExpressions Delegate Lambda 黏巴達表示式 匿名函式