委託(Delegate) (轉自acewang的專欄)

C# 中的委託類似於 C 或 C++ 中的函數指針。使用委託使程序員可以將方法引用封裝在委託對象內。然後可以將該委託對象傳遞給可調用所引用方法的代碼,而不必在編譯時知道將調用哪個方法。與 C 或 C++ 中的函數指針不同,委託是面向對象、類型安全的,並且是安全的。
委託聲明定義一種類型,它用一組特定的參數以及返回類型封裝方法。對於靜態方法,委託對象封裝要調用的方法。對於實例方法,委託對象同時封裝一個實例和該實例上的一個方法。如果您有一個委託對象和一組適當的參數,則可以用這些參數調用該委託。
委託的一個有趣且有用的屬性是,它不知道或不關心自己引用的對象的類。任何對象都可以;只是方法的參數類型和返回類型必須與委託的參數類型和返回類型相匹配。這使得委託完全適合“匿名”調用。
此教程包括兩個示例:
示例 1 展示如何聲明、實例化和調用委託。
示例 2 展示如何組合兩個委託。
此外,還討論以下主題:
委託和事件
委託與接口
示例 1
下面的示例闡釋聲明、實例化和使用委託。BookDB 類封裝一個書店數據庫,它維護一個書籍數據庫。它公開 ProcessPaperbackBooks 方法,該方法在數據庫中查找所有平裝書,併爲每本書調用一個委託。所使用的 delegate 類型稱爲 ProcessBookDelegateTest 類使用該類輸出平裝書的書名和平均價格。
委託的使用促進了書店數據庫和客戶代碼之間功能的良好分隔。客戶代碼不知道書籍的存儲方式和書店代碼查找平裝書的方式。書店代碼也不知道找到平裝書後將對平裝書進行什麼處理。
// 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!
委託和事件
委託非常適合於用作事件(從一個組件就該組件中的更改通知“偵聽器”)。
委託與接口
委託和接口的類似之處是,它們都允許分隔規範和實現。多個獨立的作者可以生成與一個接口規範兼容的多個實現。類似地,委託指定方法的簽名,多個作者可以編寫與委託規範兼容的多個方法。何時應使用接口,而何時應使用委託呢?
委託在以下情況下很有用:
調用單個方法。
一個類可能希望有方法規範的多個實現。
希望允許使用靜態方法實現規範。
希望類似事件的設計模式。
調用方不需要知道或獲得在其上定義方法的對象。
實現的提供程序希望只對少數選擇組件“分發”規範實現。
需要方便的組合。
接口在以下情況下很有用:
規範定義將調用的一組相關方法。
類通常只實現規範一次。
接口的調用方希望轉換爲接口類型或從接口類型轉換,以獲得其他接口或類。

 
發佈了6 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章