Delegate、Action、Func、Predicate介紹
起因
最近因為被一位上課認識朋友啟發開始我的部落格之路,
最近在上91的自動測試與TDD實務開發時寫功課剛好遇到的問題。
題目 :
一個任意型別Collection,使用者可以輸入要針對欄位、幾個為一組進行加總,結果產出一個IEnumerable<int>回傳。
題目內的欄位這項需求十分的有趣,要如何讓使用者可以自己指定要哪一欄位呢?
- 直接傳欄位名稱進去比對 (Bad Idea! 如果輸入錯誤怎辦!? )
- 使用委派方式,讓使用者自己去處理欄位
計算的Method :
public IEnumerable<int> GetOrderSetSum(int groupSize,IEnumerable<Order> order,Func<Order,int> orderType)
{
var count = 0;
while (count <= order.Count())
{
yield return order.Skip(count).Take(groupSize).Sum(orderType);
count += groupSize;
}
}
呼叫的Method :
[TestMethod()]
public void GroupSize_Is_3_CalGroupCost_Should_Be_6_15_24_21()
{
//arrange
var target = new CalGroupTotal();
var groupSize = 3;
var order = GetOrderList();
//act
var actual = target.GetOrderSetSum(groupSize,order, x => x.Cost);
//assert
var excepted = new List<int> { 6, 15, 24, 21 };
actual.Should().Equal(excepted);
}
在這兩段Code當中,只是將Production Code的指定欄位的Linq部分交由呼叫端去設定,這樣設計的方式較具有彈性。
正文
以下就開始進入今天的正題了,在當初設計這種API呼叫的方式的時候其實還不知道要用哪種委派,
那時弱弱的我只聽見腦海裏面的Delegate Q_Q,後來翻翻MSDN才發現有種四種委派(Delegate、Action、Func、Predicate)就深入探討了一下。
1.Delegate
MSDN註解
-
委派是事件的基礎。
-
將委派與具名方法或匿名方法建立關聯,即可具現化 (Instantiated) 委派。
-
委派必須以具有相容傳回型別和輸入參數的方法或 Lambda 運算式具現化。
MSDN範例(註解改成中文 by powen)
// 1.宣告 delegate -- 定義簽章:
delegate double MathAction(double num);
class DelegateTest
{
// 2.一般的方法跟delegate的簽章相符:
static double Double(double input)
{
return input * 2;
}
static void Main()
{
// 3.藉由方法名稱 Instantiate delegate
MathAction ma = Double;
// 4.呼叫 delegate ma
double multByTwo = ma(4.5);
Console.WriteLine("multByTwo: {0}", multByTwo);
// 3.藉由匿名方法 Instantiate delegate
MathAction ma2 = delegate(double input)
{
return input * input;
};
// 4.呼叫 delegate square
double square = ma2(5);
Console.WriteLine("square: {0}", square);
// 3.藉由lambda expression Instantiate delegate
MathAction ma3 = s => s * s * s;
// 4.呼叫 delegate cube
double cube = ma3(4.375);
Console.WriteLine("cube: {0}", cube);
}
// 輸出:
// multByTwo: 9
// square: 25
// cube: 83.740234375
}
2.Action(無回傳值)
MSDN註解
-
您可以使用這個委派以傳遞做為參數的方法,而不必明確宣告自訂委派。
-
封裝的方法必須對應由這個委派所定義的方法簽章。這表示封裝方法必須沒有參數且沒有傳回值。
-
一般而言,這種方法是用於執行作業。
MSDN範例(註解改成中文 by powen)
1.直接實體化並指定方法
using System;
using System.Windows.Forms;
public class Name
{
private string instanceName;
public Name(string name)
{
this.instanceName = name;
}
public void DisplayToConsole()
{
Console.WriteLine(this.instanceName);
}
//1.建立沒有回傳值的方法
public void DisplayToWindow()
{
MessageBox.Show(this.instanceName);
}
}
public class testTestDelegate
{
public static void Main()
{
Name testName = new Name("Koani");
//2.實體化Action並指派方法
Action showMethod = testName.DisplayToWindow;
showMethod();
}
}
2.匿名方法指派給Action
using System;
using System.Windows.Forms;
public class Name
{
private string instanceName;
public Name(string name)
{
this.instanceName = name;
}
public void DisplayToConsole()
{
Console.WriteLine(this.instanceName);
}
public void DisplayToWindow()
{
MessageBox.Show(this.instanceName);
}
}
public class Anonymous
{
public static void Main()
{
Name testName = new Name("Koani");
//Action實體化並直接將方法內容也撰寫在這,通常用於一次性呼叫
Action showMethod = delegate() { testName.DisplayToWindow();} ;
showMethod();
}
}
3.Lambda運算式指派給Action
using System;
using System.Windows.Forms;
public class Name
{
private string instanceName;
public Name(string name)
{
this.instanceName = name;
}
public void DisplayToConsole()
{
Console.WriteLine(this.instanceName);
}
public void DisplayToWindow()
{
MessageBox.Show(this.instanceName);
}
}
public class LambdaExpression
{
public static void Main()
{
Name testName = new Name("Koani");
//● 使用Lambda運算式指派給Action,程式碼更加簡潔
//● 通常用於一次性應用
Action showMethod = () => testName.DisplayToWindow();
showMethod();
}
}
3.Func(有回傳值)
MSDN註解
-
您可以使用這個委派表示可做為參數傳遞的方法,而不必明確宣告自訂委派。
-
封裝的方法必須對應由這個委派所定義的方法簽章。
-
這表示封裝方法必須沒有參數且必須傳回值。
基本語法 (以一個參數為例):
public delegate TResult Func<in T, out TResult>(
T arg
)
MSDN範例(註解改成中文 by powen)
1.直接實體化並指定方法
using System;
public class GenericFunc
{
public static void Main()
{
//藉由Func<T,TResult>委派執行實體化
Func<string, string> convertMethod = UppercaseString;
string name = "Dakota";
Console.WriteLine(convertMethod(name));
}
private static string UppercaseString(string inputString)
{
return inputString.ToUpper();
}
}
2.匿名方法指派給Func
using System;
public class Anonymous
{
public static void Main()
{
Func<string, int, string[]> extractMeth = delegate(string s, int i)
{ char[] delimiters = new char[] {' '};
return i > 0 ? s.Split(delimiters, i) : s.Split(delimiters);
};
string title = "The Scarlet Letter";
// 使用Func實體化並委派給匿名方法
foreach (string word in extractMeth(title, 5))
Console.WriteLine(word);
}
}
3.Lambda運算式指派給Func
using System;
public class LambdaExpression
{
public static void Main()
{
char[] separators = new char[] {' '};
Func<string, int, string[]> extract = (s, i) =>
i > 0 ? s.Split(separators, i) : s.Split(separators) ;
string title = "The Scarlet Letter";
// 使用Func實體化並委派給Lambda運算式
foreach (string word in extract(title, 5))
Console.WriteLine(word);
}
}
4.Predicate(回傳布林值)
-
通常, Predicate<T> 委派使用 Lambda 運算式。
-
定義一組準則的方法,並判斷指定的物件是否符合這些準則。
1.定義Predicate並指派方法
using System;
using System.Drawing;
public class Example
{
public static void Main()
{
// Create an array of Point structures.
Point[] points = { new Point(100, 200),
new Point(150, 250), new Point(250, 375),
new Point(275, 395), new Point(295, 450) };
// 定義 Predicate<T> delegate.
Predicate<Point> predicate = FindPoints;
// 尋找第一個點 X * Y 大於 100000
Point first = Array.Find(points, predicate);
// 輸出第一點大於 100000
Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y);
}
private static bool FindPoints(Point obj)
{
return obj.X * obj.Y > 100000;
}
}
// The example displays the following output:
// Found: X = 275, Y = 395
2.使用Lambda運算式表示Predicate<T>委派
using System;
using System.Drawing;
public class Example
{
public static void Main()
{
// Create an array of Point structures.
Point[] points = { new Point(100, 200),
new Point(150, 250), new Point(250, 375),
new Point(275, 395), new Point(295, 450) };
// 尋找第一個點 X * Y 大於 100000
Point first = Array.Find(points, x => x.X * x.Y > 100000 );
// 輸出第一點大於 100000
Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y);
}
}
// The example displays the following output:
// Found: X = 275, Y = 395
結論:
由上面介紹可以得知後面三種方法基本上都是由delegate延伸出來的,
都是C#內簡化程式碼實作的衍生委派作法,
我們可以根據自己使用的情境去決定該使用哪種委派,
-
Action (無回傳值)
-
Func (參數傳入、有回傳值)
-
Predicate(回傳布林值)
簡單跟各位介紹以上四種差異,
謝謝收看,
有緣再會啦 XD