本做法主要在介紹使用 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 內建的類型可以省掉我們自行宣告的步驟。
- Predicate: 這個委派需要最多傳入一個 T 的類型並回傳 bool 類型。
- Action: 這個委派需要最多傳入一個 T 的類型但沒有回傳值。
- 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 等設計模式。