[c#]委派-func and action and predicate
前言
會想寫這篇文章,其實是因為我每次想用的時候,都會忘記語法,還要東翻西翻別人的文章,重新理解別人假設的例子,有時候還真的很難理解,所以我在此就記錄一下,有關委派的緣由我就不多解釋,我們可以看看huan lin老師的文章,寫得真是又好又清楚,小弟就不再誤導讀者了,請詳閱下面連結
C# 筆記:重訪委派-從 C# 1.0 到 2.0 到 3.0
模擬情境
我會先解釋一下什麼情況之下,我會需要使用到func或action的例子,首先筆者強調一下,重構非常重要,很多人物件導向和重構都還不熟,就先去讀設計模式,我非常不建議,因為你可能只學到形而已,無法領會真正的精要,設計模式有其使用的時機,就像小朱老師說的無招勝有招,有時候你只要懂得用,而不是一定要照著用,能解決你目前問題的就是好的設計模式,而不是死板板的去套哪種設計模式,也不是只要寫程式就一定得套任何一種設計模式,才是clean code。
接著來模擬一下我們重構常會遇到的情形,也就是一段程式碼裡面,很多都是一樣的,偏偏就有一小段或兩小段的程式碼又不相同,當然我舉的例子其實非常爛,也不需要這樣大費周章的去改成這種樣子,但我想要簡單的表達如何去處理這種困境,也希望能讓看的人很容易去理解狀況,假設我想要顯示一段文字,但裡面有一段想在調用端決定要使用加減乘除,傳統用物件導向的方式,可以像下面這樣的例子去做,我先顯示一下我目前的方案狀態,還有目前的程式碼狀況。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
using Newtonsoft.Json;
using ConsoleApplication1.Calculate;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
GetNumber getNumber = new GetNumber();
Console.WriteLine(getNumber.ReturnString());
Console.ReadLine();
}
}
public class GetNumber
{
public string ReturnString()
{
return "結果為";
}
}
}
我想要顯示結果為加法然後在調用端傳入GetNumber.RetureString兩個數字運算後的結果顯示,我想要顯示"結果為加法(運算後的數字)",所以我定義了一個介面,然後四個實作介面的類別,分別是加減乘除,以下是介面和四個類別各自的程式碼
namespace ConsoleApplication1.Calculate
{
public interface ICalculate
{
string CalculateResult(int a, int b);
}
}
namespace ConsoleApplication1.Calculate
{
public class CalcuatePlus:ICalculate
{
public string CalculateResult(int a, int b)
{
return string.Format("加法:{0}", a + b);
}
}
}
namespace ConsoleApplication1.Calculate
{
public class CalculateMinus:ICalculate
{
public string CalculateResult(int a, int b)
{
return string.Format("減法:{0}", a - b);
}
}
}
namespace ConsoleApplication1.Calculate
{
public class CalculateMultiplied:ICalculate
{
public string CalculateResult(int a, int b)
{
return string.Format("乘法:{0}", a * b);
}
}
}
namespace ConsoleApplication1.Calculate
{
public class CalculateDivision:ICalculate
{
public string CalculateResult(int a, int b)
{
return string.Format("除法:{0}", a / b);
}
}
}
有蠻多段的程式碼,但大同小異,相信閱讀上應該沒有什麼大問題,然後我想在使用端決定要使用哪個類別,如下示例
using System;
using ConsoleApplication1.Calculate;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
GetNumber getNumber = new GetNumber(new CalcuatePlus());
Console.WriteLine(getNumber.ReturnString());
Console.ReadLine();
}
}
public class GetNumber
{
readonly ICalculate calculate;
public GetNumber(ICalculate calculate)
{
this.calculate = calculate;
}
public string ReturnString()
{
return "結果為" + calculate.CalculateResult(6,3);
}
}
}
可以看到以上的狀況我就可以在調用端決定我要呼叫哪段邏輯,這可以幫助我們彈性的去決定什麼時機要使用哪段邏輯。
Func的介紹
雖然上面可以動態的決定,但我個人比較喜歡使用委派的方式來處理這種狀況,接著為了方便識別,我再增加一個delegate的目錄夾,然後新增一支叫做FuncDelegate.cs的檔案,然後新增如下的程式碼
namespace ConsoleApplication1.Delegate
{
public class FuncDelegate
{
public string Plus(int a, int b)
{
return string.Format("加法:{0}", a + b);
}
public string Minus(int a, int b)
{
return string.Format("減法:{0}", a - b);
}
public string Multiplied(int a, int b)
{
return string.Format("乘法:{0}", a * b);
}
public string Division(int a, int b)
{
return string.Format("除法:{0}", a / b);
}
}
}
接著我把調用端的程式碼改成如下樣子
using System;
using ConsoleApplication1.Calculate;
using ConsoleApplication1.Delegate;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
FuncDelegate func = new FuncDelegate();
GetNumber getNumber = new GetNumber();
Console.WriteLine(getNumber.ReturnString(func.Plus, 6, 3)); //這邊我們動態決定了要使用加法,傳進去6+3
Console.ReadLine();
}
}
public class GetNumber
{
public string ReturnString(Func<int,int,string> func,int a,int b) //第一個和第二個皆是要傳入的參考,第三個則是要回傳的型別
{
return "結果為" + func(a,b); //這裡我們就可以動態決定要使用何種方式
}
}
}
可以看到使用func我們可以很彈性的決定何時要使用哪套邏輯傳進去,這邊可以解釋成傳一個方法進去做處理,下面則是一些對func的解釋,還有一些使用的方式。
Func是有傳回值的泛型委派
Func<int> 表示無參數,傳回值為int的委託
Func<object,string,int> 表示傳入參數為object, string 傳回值為int的委託
Func<object,string,int> 表示傳入參數為object, string 傳回值為int的委託
Func<T1,T2,,T3,int> 表示傳入參數為T1,T2,,T3(泛型)傳回值為int的委託
Func至少0個參數,至多16個參數,根據傳回值泛型返回。必須有傳回值,不可void
Action的介紹
Action則更單純,也就是說是一個無傳回值的方式,也就是void的意思,我們假設另一個更無聊又白痴的例子,假設我現在只想在客戶端呼叫,但我不想要傳回結果,如下圖示例
程式碼如下
using System;
namespace ConsoleApplication1.Delegate
{
public class ActionDelegate
{
public void Plus(int a, int b)
{
Console.WriteLine(string.Format("加法:{0}", a + b));
}
public void Minus(int a, int b)
{
Console.WriteLine(string.Format("減法:{0}", a - b));
}
public void Multiplied(int a, int b)
{
Console.WriteLine(string.Format("乘法:{0}", a * b));
}
public void Division(int a, int b)
{
Console.WriteLine(string.Format("除法:{0}", a / b));
}
}
}
using System;
using ConsoleApplication1.Calculate;
using ConsoleApplication1.Delegate;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
ActionDelegate action = new ActionDelegate();
GetNumber getNumber = new GetNumber();
Console.WriteLine(getNumber.ReturnString(action.Plus,6,3)); //這邊我們動態決定了要使用加法,傳進去6+3
Console.ReadLine();
}
}
public class GetNumber
{
public string ReturnString(Action action,int a,int b)
{
action(a, b); //在類別裡面直接印出結果
return "上面是動態呼叫的結果";
}
}
}
接著我們在看一下,Action的一些解釋和用法
Action是無傳回值的泛型委派。
Action 表示無參,無傳回值的委託
Action<int,string> 表示有傳入參數int,string無傳回值的委託
Action<int,string,bool> 表示有傳入參數int,string,bool無傳回值的委託
Action<int,int,int,int> 表示有傳入4個int型參數,無傳回值的委託
Action至少0個參數,至多16個參數,無傳回值。
Predicate的介紹
其實這個委派非常簡單解釋,也就是回傳一個bool,裡面的泛型則是要傳進去的參數,用lambda的extension的find來舉例
圖示看得很明白,這個就是一個Predicate的委派,下面是一個例子
var list = new List<string>()
{
"a",
"b",
"c"
};
Predicate<string> isA=x=>x=="a"; //定義一個委派型別,直接使用lambda語法
list.Find(isA).Dump(); //結果印出是a
結論
這篇文介紹了比較新的委派實作方式,如果有任何錯誤的話,再請指導。