本做法主要在介紹使用 delegate 的好處,可以先複習一下 lambda 還沒有出現前的寫法。

首先建立一個名稱為 Callback 的委派,這個委派需要輸入一個 inputString 字串,並且輸出一個字串。 接下來建立一個方法 UppercaseNewString 讀取輸入的字串改輸出成大寫字串。 最後將 Callback 委派實例化,完成後就可以直接呼叫建立的 callback 委派方法。

public delegate string Callback(string inputString);
void Main()
{
	Callback callback = UppercaseNewString;

	callback("hello, world!");
}

public string UppercaseNewString(string inputString)
{
	Console.WriteLine(inputString.ToUpper());
	return inputString.ToUpper();
}

另一種常用的做法就是再自定義一個處理方法,並把實例的委派當成參數傳入此方法。

public delegate string Callback(string inputString);
void Main()
{
	Callback callback = UppercaseNewString;

	MethodWithCallback("hello,", " world!", callback);
}

public string UppercaseNewString(string inputString)
{
	Console.WriteLine(inputString.ToUpper());
	return inputString.ToUpper();
}

public void MethodWithCallback(string param1, string param2, Callback callback)
{
	callback("Output: " + param1 + param2);
}

接下來可以進一步使用 匿名方法UppercaseNewString 這個方法省略掉。

public delegate string Callback(string inputString);
void Main()
{
	Callback callback = delegate(string inputString)
	{
		return inputString.ToUpper();
	};

	callback("hello, world!").Dump();
}

要再進一步簡化需要先了解 .NET 內建的委派類型 Predicate<T>Action<T>Func<>

public delegate bool Predicate<in T>(T obj);

public delegate void Action<in T>(T obj);

public delegate TResult Func<out TResult>();
public delegate TResult Func<in T1, out TResult>();
public delegate TResult Func<in T1, in T2, out TResult>();
...

從宣告中可以看出這三個類型背後也都是 delegate,跟我們自己寫的委派一樣的意思,只是使用 .NET 內建的類型可以省掉我們自行宣告的步驟。

  1. Predicate: 這個委派需要最多傳入一個 T 的類型並回傳 bool 類型。
  2. Action: 這個委派需要最多傳入一個 T 的類型但沒有回傳值。
  3. Func: 這個委派需最少需要一個 TResult 回傳類型。

從上面的說明可以得出 Predicate<string> 這個寫法與 Func<string, bool> 背後的意思是相同的。

所以我們可以建立一個 Func<string, string> 委派,我們就可以不用自己宣告了。

void Main()
{
	Func<string, string> callback = delegate(string inputString)
	{
		return inputString.ToUpper();
	};

	callback("hello, world!").Dump();
}

接下來就到了 lambda 出現後我們可以再進一步簡化,delegate(string inputString) 這個輸入值其實可從 Func<string, string> 推測出來輸入與輸出類型, 因此可以直接簡化成 (inputString) 最後使用 lambda 的 => 省略掉大括號與 return

void Main()
{
	Func<string, string> convertMethod = (inputString) => inputString.ToUpper();

	convertMethod("hello, world!").Dump();
}

如果只有一個參數可以把括號進一步移除完成精簡。

void Main()
{
	Func<string, string> callback = inputString => inputString.ToUpper();

	callback("hello, world!").Dump();
}

學會了 delegate 之後可以去看一下 LINQ 裡面的方法都是同樣的概念,例如這個 List 的 RemoveAll 方法就需要輸入一個 Predicate

public int RemoveAll(Predicate<T> match)

所以我們建立一個 1 ~ 200 的 List,之後建立一個 Predicate,判斷傳入類型為 int 並回傳一個 bool,所以只要傳入的值小於 50 就會從 List 裡面移除。

void Main()
{
	List<int> numbers = Enumerable.Range(1,200).ToList();
	Predicate<int> removePredicate = inputInt => inputInt < 50;
	numbers.RemoveAll(removePredicate);
}

你會發現這個 removePredicate 其實也可以省略,最後變成我們平常在用的寫法。

void Main()
{
	List<int> numbers = Enumerable.Range(1, 200).ToList();
	numbers.RemoveAll(i => i < 50);
}

Multicast Delegates

接下來要討論的是 Multicast Delegates,這個概念是在講其實每個委派背後都是 Delegates[]。

例如這個範例,removePredicate 就有三個委派並且會根據加入的順序依序執行,我們可以用 GetInvocationList 查看調用的順序與方法。

void Main()
{
	List<int> numbers = Enumerable.Range(1, 200).ToList();
	Func<List<int>, bool> removeFunc = (i) => CheckIntUnder50(i);
	removeFunc += (i) => CheckIntUnder100(i);
	removeFunc += (i) => CheckIntUnder150(i);
	Console.WriteLine(removeFunc.GetInvocationList());
}

但需要注意 delegate 並不會捕捉例外,因此只要拋出例外就會終止呼叫,之後的委派就不會繼續執行。

void Main()
{
	List<int> numbers = Enumerable.Range(1, 200).ToList();
	Func<List<int>, bool> removeFunc = (i) => CheckIntUnder50(i);
	removeFunc += (i) => CheckIntUnder100(i);
	removeFunc += (i) => CheckIntUnder150(i);
	removeFunc(numbers);
}

public bool CheckIntUnder50(List<int> i)
{
	i.RemoveAll(x => x < 50);
	return true;
}

public bool CheckIntUnder100(List<int> i)
{
	throw new ArgumentException();
}

public bool CheckIntUnder150(List<int> i)
{
	i.RemoveAll(x => x < 150);
	return true;
}

Summary

這個做法複習了 Delegate 的使用方法,還有這幾個內建的委派類型 Predicate<T>Action<T>Func<>,這個做法可以用在類似 HTTP 任務完成後需要 callback 的場合或者 observer 模式與 Publisher/Subscriber 等設計模式。