[c#]委派-func and action and predicate

  • 2620
  • 0
  • C#
  • 2017-05-30

[c#]委派-func and action and predicate

前言

 

會想寫這篇文章,其實是因為我每次想用的時候,都會忘記語法,還要東翻西翻別人的文章,重新理解別人假設的例子,有時候還真的很難理解,所以我在此就記錄一下,有關委派的緣由我就不多解釋,我們可以看看huan lin老師的文章,寫得真是又好又清楚,小弟就不再誤導讀者了,請詳閱下面連結

C# 筆記:重訪委派-從 C# 1.0 到 2.0 到 3.0

 

模擬情境

 

我會先解釋一下什麼情況之下,我會需要使用到func或action的例子,首先筆者強調一下,重構非常重要,很多人物件導向和重構都還不熟,就先去讀設計模式,我非常不建議,因為你可能只學到形而已,無法領會真正的精要,設計模式有其使用的時機,就像小朱老師說的無招勝有招,有時候你只要懂得用,而不是一定要照著用,能解決你目前問題的就是好的設計模式,而不是死板板的去套哪種設計模式,也不是只要寫程式就一定得套任何一種設計模式,才是clean code。

 

接著來模擬一下我們重構常會遇到的情形,也就是一段程式碼裡面,很多都是一樣的,偏偏就有一小段或兩小段的程式碼又不相同,當然我舉的例子其實非常爛,也不需要這樣大費周章的去改成這種樣子,但我想要簡單的表達如何去處理這種困境,也希望能讓看的人很容易去理解狀況,假設我想要顯示一段文字,但裡面有一段想在調用端決定要使用加減乘除,傳統用物件導向的方式,可以像下面這樣的例子去做,我先顯示一下我目前的方案狀態,還有目前的程式碼狀況。

 

image

 

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);
        }
    }
}

 

image

 

可以看到以上的狀況我就可以在調用端決定我要呼叫哪段邏輯,這可以幫助我們彈性的去決定什麼時機要使用哪段邏輯。

 

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); //這裡我們就可以動態決定要使用何種方式
        }
    }
}

 

image

 

可以看到使用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的意思,我們假設另一個更無聊又白痴的例子,假設我現在只想在客戶端呼叫,但我不想要傳回結果,如下圖示例

 

image

 

程式碼如下

 

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

結論

這篇文介紹了比較新的委派實作方式,如果有任何錯誤的話,再請指導。