C#委派的學習筆記_delegate_委派的定義_物件&類別層級方法引用、作為參數傳遞、委派陣列

 
  1. 委託(委派),從字面上來理解,就是將事情交由他人代為執行。
  2. 當自己有需求時,並不親自動手,而是請別人來完成,這就是委派的概念。
  3. 在 C# 中,「委派」的意義與字面上相差無幾,只不過在 C# 裡,委託與方法息息相關。
  4. 方法是定義在類別(Class)中的,用來實作邏輯功能,也可以理解為實際執行工作的「人」。

因此,在 C# 中可以簡單地理解為:委託就是用來呼叫方法的一種方式,而且委託與事件(Event)有密切關聯。在 .NET 框架中,定義委託的方式與定義類別相似,凡是能定義類別的地方,也都可以定義委託。

此外,適用於類別的修飾詞(如 public、private 等)也可以用於委託。委派本質上是一種安全的類型,並且會明確定義返回值型別及參數清單,這一點與方法也很相似。

委派的定義
基本語法
定義委派時需使用關鍵字 delegate,基本語法如下:
delegate 資料型別 委派名稱(參數型別 參數名稱, …);
  • 使用 delegate 關鍵字來定義委派。
  • 委派可以定義在類別內,也可以定義在類別外。只要是能定義類別的地方,也都能定義委託。
  • 委派必須指定一個回傳資料型別,這個型別要與其綁定的方法相容。
  • 委派必須命名,命名規則與一般變數或類別一致。
  • 委派可以帶有參數,參數的型別與命名方式與方法的參數規則相同。
  • 委派的定義只包含簽章(方法名稱、參數與回傳型別),不包含實際的程式實作內容。
在 C# 中,只要是可以定義類別(class)的地方,就可以用來定義委派。

委派(Delegate)是一種特殊的資料型別,其作用是儲存一個或多個方法的參考位址(方法指標)。

委派定義示範
//更多委派定義的示範
delegate int ProductDelegate1(int intA);
delegate void ProductDelegate2();
delegate string ProductDelegate3(int intB, string strA);
delegate List<int> ProductDelegate4(int[] intArray);

  • 這些委託的定義都是正確的,委託可以有返回值,也可以沒有返回值;而委託的參數也可以為空,如果沒有參數,則使用空括號 () 即可。
  • 你可以這樣來理解委託與方法之間的關係:當你要執行某個方法,但你不直接呼叫這個方法,而是透過委託來執行這個方法。
  • 在這種情況下,你需要先將方法綁定(掛接)到委託上,這樣委託才能正確執行對應的方法。
  • 掛接方法時,必須確保委託的定義與方法的定義在簽章上完全一致(即返回值與參數型別一致),否則將無法綁定。

委派的呼叫使用(物件&類別層級方法引用)

  • 在使用委託之前,必須先使用 delegate 關鍵字進行定義,接著再進行實體化(建立委託實例)
  • 在實體化委託時,必須傳入一個參數,這個參數就是要綁定的方法名稱,也就是指定委託要呼叫哪一個方法
  • 傳入方法名稱時,不可以加上圓括號 (),因為你是在傳遞方法的參考,而不是呼叫它。
  • 此外,該方法的回傳型別必須與委託的回傳型別一致,否則會產生編譯錯誤。
委派存取物件(實體)層級方法的示範
委派定義寫法1.將委派變數透過new 先實體化
Product.cs 類別示範

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoCourse
{
    delegate int CountDelegate(int sum);//定義委派
    internal class Product
    {
        //定義方法(副程式)
        //這裡所定義的方法 GetCount() 符合 CountDelegate 委託的定義要求
        //因此它是可以被 CountDelegate 所引用並呼叫的。
        public int GetCount(int x)
        {
            return x * 800;
        }
    }
}

GetCount() 是定義在 Product 類別中的物件實體層級的方法,因此在使用該方法時,必須先建立 Product 類別的實體(Instance),才能透過該實體來呼叫 GetCount() 方法。

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoCourse
{
    /// <summary>
    /// 主程式
    /// </summary>
    internal class Program
    {

        static void Main(string[] args)
        {
            //實體化新建的Product物件
            Product product = new Product();
            //實體化委派並指向特定方法
            CountDelegate countDelegate = new CountDelegate(product.GetCount);
            //調用方法
            int count = countDelegate(400);
            Console.WriteLine(count);
        }
    }
}

new出來實體化的CountDelegate委派,需傳入一個必填參數,也就是方法的名稱。

當我們在實體化委託並傳入方法時,實際上就是把方法「附加」到委託上,接著再由委託來執行該方法。也就是說,委託會代替我們去完成方法的呼叫與執行。

在呼叫委託時,我們通常會在委託變數後面加上圓括號 () 傳入參數來執行方法,這種寫法是 C# 提供的簡化語法。其實,最原始、完整的寫法是使用 Invoke() 方法來呼叫委託對應的方法



這個概念可以用一個生活中的例子來比喻:

假設你要寄一封信給家人或朋友,你不需要親自搭火車去送信,而是將信件交給郵局(郵局就像委託),讓郵局替你完成送信的任務。

同樣地,在程式中,你不直接呼叫方法,而是透過委託來轉交這個任務,讓委託幫你執行方法,這就是委託的核心概念。

委派定義寫法2.簡化操作
在 實體化委託 時,其實可以不使用 new 關鍵字,而是直接將方法名稱(也就是方法位址)賦值給委託變數。這種省略 new 的簡化方式稱為「委託推斷(Delegate Inference)」。

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoCourse
{
    /// <summary>
    /// 主程式
    /// </summary>
    internal class Program
    {

        static void Main(string[] args)
        {
            //實體化新建的Product物件
            Product product = new Product();
            //實體化委派並指向特定方法
            //CountDelegate countDelegate = new CountDelegate(product.GetCount);
            CountDelegate countDelegate = product.GetCount;
            //調用方法
            //int count = countDelegate(400);
            int count = countDelegate.Invoke(300);
            Console.WriteLine(count);
        }
    }
}

Ref:

上面示範是根據物件層級方法,至於靜態方法是否也可以去透過委派引用呢?
答案是可以的

委派存取靜態方法的示範
建立一個 Student.cs 的類別檔案,並在其中新增一個委託以及一個與該委託相符的靜態方法。
GetNameById() 使用 static 修飾,是一個靜態方法。
接著在 Program.cs 中,透過委託來呼叫這個靜態方法。


Student.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoCourse
{
    delegate string StudentDelegate(int id, string name);
    internal class Student
    {
        //靜態方法
        public static string GetNameById(int id, string name)
        {
            return $"ID={id},姓名={name}";
        }
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoCourse
{
    /// <summary>
    /// 主程式
    /// </summary>
    internal class Program
    {

        static void Main(string[] args)
        {
            StudentDelegate studentDelegate = Student.GetNameById;
            var stuInfo = studentDelegate(100, "王大陸");
            Console.WriteLine(stuInfo);
        }
    }
}


委派作為參數傳遞

由於委託可以引用方法,本質上就是引用方法的記憶體位址
而且委託本身又可以當作類別來使用,因此可以將委託物件作為參數傳遞給其他方法。
當我們將委託物件作為參數傳遞時,其實就是實現了「將方法作為參數」傳入另一個方法的目的,這也是委託最主要、最常見的用途之一。


這邊定義一個Apple.cs 類別檔案

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoCourse
{
    delegate double AppleDelegate(int sum, double price);
    internal class Apple
    {
        public double Amount(int sum,double price)
        {
            return sum * price;
        }

        public static double GetAmount(AppleDelegate appleDelegate)
        {
            //藉由委派去呼叫方法
            double d = appleDelegate(100, 89.60);
            return d;
        }
    }
}

GetAmount() 方法帶有一個參數,這個參數的型別是 AppleDelegate 委託。
當呼叫 GetAmount() 方法時,所傳入的實際參數是一個已經實體化的 AppleDelegate 物件。
對於 AppleDelegate 委託而言,在實體化的過程中必須綁定一個方法,也就是說,當 AppleDelegate 的實例被傳入 GetAmount() 方法時,它其實已經帶有一個可執行的方法參考。
因此,在 GetAmount() 方法的內部,就可以透過該委託來呼叫對應的方法。

在Program.cs 主程式中去嘗試呼叫GetAmount方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoCourse
{
    /// <summary>
    /// 主程式
    /// </summary>
    internal class Program
    {

        static void Main(string[] args)
        {
            Apple apple = new Apple();
            AppleDelegate appleDelegate = apple.Amount;
            double amount = Apple.GetAmount(appleDelegate);
            Console.WriteLine(amount);
        }
    }
}

這邊可以看到委派其實就是一種方法的變數載體,在此我們把他以參數形式傳入。



委派陣列

可以將多個方法放入委託陣列(delegate array)中,這樣就能在同一個地方集中處理多個方法的呼叫。這種做法可以讓你透過迴圈或統一邏輯,一次性地對多個方法進行調用,使程式結構更清晰、可維護性更高。

這在需要對一組具有相同簽章的方法進行批次操作時特別實用。
建立Flower.cs類別程式
在該類中創建 1 個委託和 2 個方法
然後再在 Flower 類中創建一個 Call() 方法,並在該方法內定義一個委派陣列
委託也是一種類型,可以作為陣列的類型來儲存更多的委託實例。
此代碼中,totalDelegates 委託陣列儲存了 2 個委託實例,並且都攜帶了方法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoCourse
{
    internal class Flower
    {
        public delegate double TotalDelegate(int sum, double price);
        public double SingleTotal(int sum, double price)
        {
            return sum * price;
        }
        public double DoubleTotal(int sum, double price)
        {
            return 2 * (sum * price);
        }

        public void Call()
        {
            TotalDelegate[] totalDelegates =
            {
                new TotalDelegate(SingleTotal),
                new TotalDelegate(DoubleTotal)
            };
            foreach (var total in totalDelegates)
            {
                double d = total(100, 3.99);
                Console.WriteLine(d);
            }
        }
    }
}
對於委託陣列,透過 foreach 迴圈陣列中的每個方法,統一進行調用,從而處理方法中的資料。



Program.cs主程式去實體化Flower物件後再呼叫Call方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoCourse
{
    /// <summary>
    /// 主程式
    /// </summary>
    internal class Program
    {

        static void Main(string[] args)
        {
            Flower flower = new Flower();
            flower.Call();
        }
    }
}

呼叫結果可以看到,藉由委派陣列,可將具有相同返回型別和傳入參數的方法給統一集中管控,並進行特定時機的呼叫。


留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

(2021年度)駕訓學科筆試準備題庫歸納分析_法規是非題

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念