共用方式為


System.Delegate 和 delegate 關鍵詞

以前

本文涵蓋 .NET 中支援委派的類別,以及這些類別如何對應至 delegate 關鍵詞。

什麼是委派?

將委派視為儲存方法參考的方法,類似於儲存物件參考的方式。 就像您可以將物件傳遞至方法一樣,您可以使用委派來傳遞方法參照。 當您想要撰寫彈性程序代碼,其中不同的方法可以「插入」以提供不同的行為時,這會很有用。

例如,假設您有一個計算機,可以在兩個數位上執行作業。 您可以使用委派來表示接受兩個數字並傳回結果的任何運算,而不是將加法、減法、乘法和除法硬編碼為不同的方法。

定義委派類型

現在讓我們看看如何使用 關鍵詞建立委派類型 delegate 。 當您定義委派類型時,基本上會建立範本,描述該委派中可以儲存何種方法。

您可以使用類似方法簽名的語法來定義委託類型,但開頭加入 delegate 關鍵字:

// Define a simple delegate that can point to methods taking two integers and returning an integer
public delegate int Calculator(int x, int y);

Calculator 委派可以保存對任何接受兩個 int 參數並返回 int 的方法的參考。

讓我們看看一個更實用的範例。 當您想要排序列表時,您必須告訴排序演算法如何比較項目。 讓我們來看看委派在 List.Sort() 方法中的協助作用。 第一個步驟是建立比較作業的委派類型:

// From the .NET Core library
public delegate int Comparison<in T>(T left, T right);

Comparison<T> 委派可以保存任何方法的參考,

  • 採用兩個類型為 T 的參數
  • int 傳回 (通常為 -1、0 或 1 表示「小於」、「等於」或「大於」)

當您定義類似這樣的委派類型時,編譯程式會自動產生衍生自 System.Delegate 且符合簽章的類別。 這個類別會為您處理儲存和調用方法參考所需的所有複雜性。

委派 Comparison 類型是泛型型別,這表示它可以使用任何類型 T。 如需泛型的詳細資訊,請參閱 泛型類別和方法

請注意,即使語法看起來類似於宣告變數,您實際上還是要宣告新的 類型。 您可以在類別內、直接在命名空間內,或甚至是全域命名空間中定義委派類型。

備註

不建議直接在全域命名空間中宣告委派類型(或其他類型)。

編譯程式也會為這個新類型產生新增和移除處理程式,讓這個類別的用戶端可以從實例的調用清單中新增和移除方法。 編譯程式會強制加入或移除方法的簽章符合宣告委派類型時所使用的簽章。

宣告委派的實例

定義委派類型之後,您可以建立該類型的實例(變數)。 將此視為建立一個「槽」,您可以在其中儲存方法的參考。

如同 C# 中的所有變數,您無法直接在命名空間或全域命名空間中宣告委派實例。

// Inside a class definition:
public Comparison<T> comparator;

這個變數的類型為 Comparison<T> (您稍早定義的委派類型),而變數的名稱為 comparator。 此時, comparator 尚未指向任何方法,就像等候填滿的空白位置一樣。

您也可以將委派變數宣告為局部變數或方法參數,就像任何其他變數類型一樣。

叫用委派

一旦您有指向某個方法的委派實例,即可透過委派呼叫該方法(調用)。 您可以像呼叫方法一樣,執行委派,從其叫用清單中調用方法。

以下是方法如何使用 Sort() 比較委派來判斷對象的順序:

int result = comparator(left, right);

在此行中,程式碼 呼叫 附加至委派之方法。 您可以將委派變數視為方法名稱,並使用一般方法呼叫語法加以呼叫。

不過,這行程式碼有一個不安全的假設:它假設目標方法已被新增至委派中。 如果沒有附加任何方法,則上述行會導致 NullReferenceException 擲回 。 用來解決此問題的模式比簡單的 Null 檢查更為複雜,而且稍後會在此 系列中討論。

指派、新增和移除呼叫目標

現在您已瞭解如何定義委派類型、宣告委派實例,以及叫用委派。 但是,您如何實際將方法連線到委派? 這是委派指派傳入的位置。

若要使用委派,您必須將方法指派給它。 您指派的方法必須與委派類型所定義的簽章相同(相同參數和傳回類型)。

讓我們看看一個實用的範例。 假設您想要依字串長度排序字串清單。 您必須建立一個符合如 Comparison<string> 委派簽章的比較方法:

private static int CompareLength(string left, string right) =>
    left.Length.CompareTo(right.Length);

此方法會採用兩個字串,並傳回整數,指出哪一個字元串為「更大」(在此案例中較長)。 方法被宣告為 private,這完全沒問題。 您不需要使方法成為公開介面的一部分,即可搭配委派使用它。

現在您可以將此方法傳遞至 List.Sort() 方法:

phrases.Sort(CompareLength);

請注意您使用不含括弧的方法名稱。 這會告訴編譯程式將方法參考轉換成稍後可叫用的委派。 每當需要比較兩個字串時,Sort()方法都會呼叫CompareLength方法。

您也可以藉由宣告委派變數並將 方法指派給它,來更明確:

Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);

這兩種方法都完成相同的工作。 第一種方法更簡潔,而第二種方法則讓委派指派更加明確。

針對簡單方法,通常會使用 Lambda 運算式 ,而不是定義個別的方法:

Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);

Lambda 運算式提供精簡的方式來定義內嵌的簡單方法。 在 稍後的章節中,會更詳細地說明使用 lambda 表達式做為委派目標。

到目前為止,這些範例會顯示具有單一目標方法的委派。 不過,委派物件可以支援叫用清單,這些清單有多個目標方法附加至單一委派物件。 這項功能特別適用於事件處理案例。

Delegate 和 MulticastDelegate 類別

在幕後,您所使用的委派功能是以 .NET Framework 中的兩個主要類別為基礎所建置: DelegateMulticastDelegate。 您通常不會直接使用這些類別,但它們會提供讓委派運作的基礎。

類別 System.Delegate 及其直接子類別 System.MulticastDelegate 提供建立委派、將方法註冊為委派目標的架構支援,以及叫用向委派註冊的所有方法。

以下是一個有趣的設計細節:System.DelegateSystem.MulticastDelegate 本身不是您可以使用的委派類型。 相反地,它們會做為您建立之所有特定委派類型的基類。 C# 語言可防止您直接繼承自這些類別,您必須改用 delegate 關鍵詞。

當您使用 delegate 關鍵詞宣告委派類型時,C# 編譯程式會自動使用您的特定簽章建立衍生自 MulticastDelegate 的類別。

為什麼這個設計?

此設計有其根基在 C# 和 .NET 的第一個版本。 設計小組有數個目標:

  1. 類型安全:小組想要在使用委派時,確保語言強制執行類型安全性。 這表示確保使用正確的類型和自變數數目叫用委派,並在編譯時期正確驗證傳回型別。

  2. 效能:藉由讓編譯程式產生代表特定方法簽章的具體委派類別,運行時間就可以優化委派調用。

  3. 簡單性:委派包含在 1.0 .NET 版本中,這是在引進泛型之前。 設計需要在時間限制內運作。

解決方案是讓編譯程式建立符合方法簽章的具體委派類別,確保類型安全性,同時隱藏您的複雜性。

使用委派方法

即使您無法直接建立衍生類別,您仍會偶爾使用DelegateMulticastDelegate類別上定義的方法。 以下是最重要的資訊:

您使用的每個委派都是衍生自 MulticastDelegate。 「多重廣播」委派表示,透過委派呼叫時,可以同時叫用多個方法目標。 原始設計考慮將只能調用單一方法的委派與可以調用多個方法的委派區分開來。 在實務上,事實證明這項區別不如原先認為的來得有用,因此 .NET 中的所有委派都支援多個目標方法。

使用委派時最常使用的方法如下:

  • Invoke():呼叫附加至委派的所有方法
  • BeginInvoke() / EndInvoke():用於異步調用模式(雖然 async/await 現在更常使用)

在大部分情況下,您不會直接呼叫這些方法。 相反地,您會在委派變數上使用 方法呼叫語法,如上述範例所示。 不過,如您稍後在此系列中看到,有一些模式可以直接配合這些方法使用。

總結

既然您已瞭解 C# 語言語法如何對應至基礎 .NET 類別,您已準備好探索在更複雜的案例中使用、建立和叫用強型別委派的方式。

下一步