泛型委派(General Delegates)之研究

泛型委派(或稱通用委派)(General Delegates)與黏巴達表示式(Lambda Expressions)兩者,在C# 3.0時代後之程式碼,簡直是烽火連天的出現在各大論壇,好像現在的程式不寫成這樣就不夠潮的感覺,沒有了它LINQ可是連動都沒辦法動喔!

泛型(通用)委派General Delegates分成四種型態:(Since .NET Framework 3.5)

  1. 不需要傳回值的委派:Action, Action<T>, Action<T1, T2>, … Action<T1, …, T16>
  2. 需要傳回值的委派:Func<TResult>, Func<T, TResult>, Func<T1, T2, TResult>, … Func<T1, …, T16, TResult>
  3. 需要傳回布林值的委派(特別型態):Predicate<T>
  4. 事件的委派,T為事件參數:EventHandler, EventHandler<TEventArgs>

話不多說,直接看下列程式碼吧!

using System;

namespace DelegateTesting
{
	class Program
	{
		//基本型委派聲明
		public delegate void BasicDelegate(int x);

		static void Main(string[] args)
		{
			//基本型委派:需要聲明(delegate)
			//這樣寫也可以 BasicDelegate oTemp_0 = new BasicDelegate(Delegate_0);
			BasicDelegate oTemp_0 = Delegate_0;
			oTemp_0(10);

			//泛型委派:Action式:不需要聲明(delegate)
			Action<int> oTemp_1 = new Action<int>(Action_1);
			oTemp_1(20);

			//泛型委派:Action式:不需要聲明(delegate):雙參數
			Action<int, int> oTemp_2 = Action_2;
			oTemp_2(30, 40);

			//泛型委派:Action式:不需要聲明(delegate):三參數:使用匿名函式(黏巴達表示式、Lambda Expressions)
			//如此一來可以省掉委派對像之函數宣告
			Action<string, bool, int> oTemp_3 = (x, y, z) => {
				string cTemp;
				cTemp  = "我叫:" + x + "、";
				cTemp += "我是:" + y + "生、";	//這裡是示範,不要太在乎
				cTemp += "今年:" + z + "歲。";
				Console.WriteLine(cTemp);
			};
			oTemp_3("王小明", true, 50);

			//泛型委派:Func式:不需要聲明(delegate):雙參數
			Func<int, bool> oTemp_4 = x => { return (x > 100); };
			Console.WriteLine("60是否有大於100: {0}", oTemp_4(60));

			//泛型委派:Func式:不需要聲明(delegate):三參數
			Func<string, int, System.Collections.ArrayList> oTemp_5 = (x, y) => {
				System.Collections.ArrayList oAL = new System.Collections.ArrayList();
				for (int z = 0; z < y; z++) { oAL.Add(x + ";運行次數: " + z); }
				return oAL;
			};
			foreach (var item in oTemp_5("迴圈", 7)) { Console.WriteLine(item); }

			//泛型委派:Predicate式:不需要聲明(delegate):單參數(永遠只有一個參數)
			Predicate<int> oTemp_6 = x => {
				if (x > 100) { return true; } else { return false; }
			};
			Console.WriteLine("80是否有大於100: {0}", oTemp_6(80));

			Console.Read();
		}

		public static void Delegate_0(int x) { Console.WriteLine("Basic Delegate: {0}", x); }
		public static void Action_1(int x) { Console.WriteLine("General Delegate (Action): {0}", x); }
		public static void Action_2(int x, int y) { Console.WriteLine("General Delegate (Action): {0}", x + y); }

	}
}

以下是輸出結果:

Basic Delegate: 10
General Delegate (Action): 20
General Delegate (Action): 70
我叫:王小明、我是:True生、今年:50歲。
60是否有大於100: False
迴圈;運行次數: 0
迴圈;運行次數: 1
迴圈;運行次數: 2
迴圈;運行次數: 3
迴圈;運行次數: 4
迴圈;運行次數: 5
迴圈;運行次數: 6
80是否有大於100: False

第一種形態是最原始的委派寫法,通常出現在C# 1.0時代,這樣的寫法我個人覺得沒有什麼大的不妥,甚至覺得易讀性是很夠的(至少一個還沒有接觸過委派寫法的人來看,應該都看的懂)。但是缺點就是程式設計師必須要在很多地方開刀,例如你要先聲明(delegate)一個function(BasicDelegate),然後再用這個function去模擬類別實體化一個操作函數(oTemp_0),然後再將它派掛給一個正規的function去運行(Delegate_0),光用說就很累了,更別說要做很多程式設計師最不願意的「命名」工作。

進入到C# 2.0至3.0時代後,這樣的寫法有了演變,例如泛型委派的寫法,就徹底的讓delegate這個字眼消失,然後Lambda Expressions這種匿名函式的寫法,又讓獨立的function宣告必要性消失,讓C# 1.0時代的問題都慢慢的被解決了。當然,原本的優點也瞬間變成缺點,就是程式的易讀性降低了。

泛型委派Action<T, T>在編譯時期發生錯誤

可能會有使用者在VisualStudio中編輯泛型委派Action<T, T>程式碼時,有發現很奇怪的問題,那就是Action單參數時可以正常工作,雙參數時(例如:Action<T, T>)就會發生問題,如下圖發生的問題:

錯誤訊息載明:使用泛型類型'System.Action'時需要1個類型引數,這問題就是:請把你的.NET Framework 2.0運行環境升到.NET Framework 3.5以上。原因不需要多說明,請見下圖:

相關參考資料:

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

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

C# AnonymousFunctions LambdaExpressions GeneralDelegates Action Func Predicate