C# 中的委托類似于 C 或 C++ 中的函數指針。使用委托使程序員可以將方法引用封裝在委托對象內。然后可以將該委托對象傳遞給可調用所引用方法的代碼,而不必在編譯時知道將調用哪個方法。與 C 或 C++ 中的函數指針不同,委托是面向對象、類型安全的,并且是安全的。
委托聲明定義一種類型,它用一組特定的參數以及返回類型封裝方法。對于靜態方法,委托對象封裝要調用的方法。對于實例方法,委托對象同時封裝一個實例和該實例上的一個方法。如果您有一個委托對象和一組適當的參數,則可以用這些參數調用該委托。
委托的一個有趣且有用的屬性是,它不知道或不關心自己引用的對象的類。任何對象都可以;只是方法的參數類型和返回類型必須與委托的參數類型和返回類型相匹配。這使得委托完全適合“匿名”調用。
此教程包括兩個示例:
示例 1 展示如何聲明、實例化和調用委托。
示例 2 展示如何組合兩個委托。
此外,還討論以下主題:
委托和事件
委托與接口
示例 1
下面的示例闡釋聲明、實例化和使用委托。BookDB
類封裝一個書店數據庫,它維護一個書籍數據庫。它公開 ProcessPaperbackBooks
方法,該方法在數據庫中查找所有平裝書,并為每本書調用一個委托。所使用的 delegate 類型稱為 ProcessBookDelegate
。Test
類使用該類輸出平裝書的書名和平均價格。
委托的使用促進了書店數據庫和客戶代碼之間功能的良好分隔。客戶代碼不知道書籍的存儲方式和書店代碼查找平裝書的方式。書店代碼也不知道找到平裝書后將對平裝書進行什么處理。
// bookstore.cs

using System;


// A set of classes for handling a bookstore:

namespace Bookstore



{

using System.Collections;

// Describes a book in the book list:

public struct Book


{

public string Title; // Title of the book.

public string Author; // Author of the book.

public decimal Price; // Price of the book.

public bool Paperback; // Is it paperback?

public Book(string title, string author, decimal price, bool paperBack)


{

Title = title;

Author = author;

Price = price;

Paperback = paperBack;

}

}

// Declare a delegate type for processing a book:

public delegate void ProcessBookDelegate(Book book);

// Maintains a book database.

public class BookDB


{

// List of all books in the database:

ArrayList list = new ArrayList();

// Add a book to the database:

public void AddBook(string title, string author, decimal price, bool paperBack)


{

list.Add(new Book(title, author, price, paperBack));

}

// Call a passed-in delegate on each paperback book to process it:

public void ProcessPaperbackBooks(ProcessBookDelegate processBook)


{

foreach (Book b in list)


{

if (b.Paperback)

// Calling the delegate:

processBook(b);

}

}

}

}

// Using the Bookstore classes:

namespace BookTestClient



{

using Bookstore;

// Class to total and average prices of books:

class PriceTotaller


{

int countBooks = 0;

decimal priceBooks = 0.0m;

internal void AddBookToTotal(Book book)


{

countBooks += 1;

priceBooks += book.Price;

}

internal decimal AveragePrice()


{

return priceBooks / countBooks;

}

}

// Class to test the book database:

class Test


{

// Print the title of the book.

static void PrintTitle(Book b)


{

Console.WriteLine(" {0}", b.Title);

}

// Execution starts here.

static void Main()


{

BookDB bookDB = new BookDB();

// Initialize the database with some books:

AddBooks(bookDB);

// Print all the titles of paperbacks:

Console.WriteLine("Paperback Book Titles:");

// Create a new delegate object associated with the static

// method Test.PrintTitle:

bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));

// Get the average price of a paperback by using

// a PriceTotaller object:

PriceTotaller totaller = new PriceTotaller();

// Create a new delegate object associated with the nonstatic

// method AddBookToTotal on the object totaller:

bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));

Console.WriteLine("Average Paperback Book Price: ${0:#.##}",

totaller.AveragePrice());

}

// Initialize the book database with some test books:

static void AddBooks(BookDB bookDB)


{

bookDB.AddBook("The C Programming Language",

"Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);

bookDB.AddBook("The Unicode Standard 2.0",

"The Unicode Consortium", 39.95m, true);

bookDB.AddBook("The MS-DOS Encyclopedia",

"Ray Duncan", 129.95m, false);

bookDB.AddBook("Dogbert's Clues for the Clueless",

"Scott Adams", 12.00m, true);

}

}

}

輸出
Paperback Book Titles:
The C Programming Language
The Unicode Standard 2.0
Dogbert's Clues for the Clueless
Average Paperback Book Price: $23.97
代碼討論
聲明委托 以下語句:
public delegate void ProcessBookDelegate(Book book);
聲明一個新的委托類型。每個委托類型都描述參數的數目和類型,以及它可以封裝的方法的返回值類型。每當需要一組新的參數類型或新的返回值類型時,都必須聲明一個新的委托類型。
實例化委托 聲明了委托類型后,必須創建委托對象并使之與特定方法關聯。與所有其他對象類似,新的委托對象用 new 表達式創建。但創建委托時,傳遞給 new 表達式的參數很特殊:它的編寫類似于方法調用,但沒有方法的參數。
下列語句:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
創建與靜態方法 Test.PrintTitle
關聯的新的委托對象。下列語句:
bookDB.ProcessPaperbackBooks(new

ProcessBookDelegate(totaller.AddBookToTotal));

創建與對象 totaller
上的非靜態方法 AddBookToTotal
關聯的新的委托對象。在兩個例子中,新的委托對象都立即傳遞給 ProcessPaperbackBooks
方法。
請注意一旦創建了委托,它所關聯到的方法便永不改變:委托對象不可改變。
調用委托 創建委托對象后,通常將委托對象傳遞給將調用該委托的其他代碼。通過委托對象的名稱(后面跟著要傳遞給委托的參數,括在括號內)調用委托對象。下面是委托調用的示例:
processBook(b);
示例 2
本示例演示組合委托。委托對象的一個有用屬性是,它們可以“+”運算符來組合。組合的委托依次調用組成它的兩個委托。只可組合相同類型的委托,并且委托類型必須具有 void 返回值。“-”運算符可用來從組合的委托移除組件委托。
// compose.cs

using System;

delegate void MyDelegate(string s);

class MyClass



{

public static void Hello(string s)


{

Console.WriteLine(" Hello, {0}!", s);

}

public static void Goodbye(string s)


{

Console.WriteLine(" Goodbye, {0}!", s);

}

public static void Main()


{

MyDelegate a, b, c, d;

// Create the delegate object a that references

// the method Hello:

a = new MyDelegate(Hello);

// Create the delegate object b that references

// the method Goodbye:

b = new MyDelegate(Goodbye);

// The two delegates, a and b, are composed to form c,

// which calls both methods in order:

c = a + b;

// Remove a from the composed delegate, leaving d,

// which calls only the method Goodbye:

d = c - a;

Console.WriteLine("Invoking delegate a:");

a("A");

Console.WriteLine("Invoking delegate b:");

b("B");

Console.WriteLine("Invoking delegate c:");

c("C");

Console.WriteLine("Invoking delegate d:");

d("D");

}

}

輸出
Invoking delegate a:
Hello, A!
Invoking delegate b:
Goodbye, B!
Invoking delegate c:
Hello, C!
Goodbye, C!
Invoking delegate d:
Goodbye, D!
委托和事件
委托非常適合于用作事件(從一個組件就該組件中的更改通知“偵聽器”)。
委托與接口
委托和接口的類似之處是,它們都允許分隔規范和實現。多個獨立的作者可以生成與一個接口規范兼容的多個實現。類似地,委托指定方法的簽名,多個作者可以編寫與委托規范兼容的多個方法。何時應使用接口,而何時應使用委托呢?
委托在以下情況下很有用:
調用單個方法。
一個類可能希望有方法規范的多個實現。
希望允許使用靜態方法實現規范。
希望類似事件的設計模式。
調用方不需要知道或獲得在其上定義方法的對象。
實現的提供程序希望只對少數選擇組件“分發”規范實現。
需要方便的組合。
接口在以下情況下很有用:
規范定義將調用的一組相關方法。
類通常只實現規范一次。
接口的調用方希望轉換為接口類型或從接口類型轉換,以獲得其他接口或類。