C# 委托
委托
定义:[修饰符] delegate <返回类型> <委托名称>([参数1,参数2……]);
委托是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值,如下面的示例所示
public delegate int PerformCalculation(int x, int y);
委托具有以下特点:
-
委托类似于 C++ 函数指针,但它是类型安全的。
-
委托允许将方法作为参数进行传递。
-
委托可用于定义回调方法。
-
委托可以链接在一起;例如,可以对一个事件调用多个方法。
-
方法不需要与委托签名精确匹配。有关更多信息,请参见协变和逆变。
-
C# 2.0 版引入了匿名方法的概念,此类方法允许将代码块作为参数传递,以代替单独定义的方法。
-
委托类型是密封的
构造委托对象时,通常提供委托将包装的方法的名称或使用匿名方法。实例化委托后,委托将把对它进行的方法调用传递给方法。调用方传递给委托的参数被传递给方法,来自方法的返回值(如果有)由委托返回给调用方。这被称为调用委托。可以将一个实例化的委托视为被包装的方法本身来调用该委托。
// Create a method for a delegate. public static void DelegateMethod(string message) { System.Console.WriteLine(message); } // Instantiate the delegate. Del handler = DelegateMethod; // Call the delegate. handler("Hello World");
不能从 Delegate 中派生委托类型,也不可能从中派生自定义类。由于实例化委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。这样,方法便可以将一个委托作为参数来接受,并且以后可以调用该委托。这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。以这种方式使用委托时,使用委托的代码无需了解有关所用方法的实现方面的任何信息。此功能类似于接口所提供的封装。
何时使用委托而不使用接口
参考:ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_csref/html/2e759bdf-7ca4-4005-8597-af92edf6d8f0.htm
委托命名方法 ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_csref/html/97de039b-c76b-4b9c-a27d-8c1e1c8d93da.htm
委托中的互变量和逆变量
将委托方法与委托签名匹配时,协变和逆变提供了一定程度的灵活性。协变允许将带有派生返回类型的方法用作委托,逆变允许将带有派生参数的方法用作委托。这使委托方法的创建变得更为灵活,并能够处理多个特定的类或事件。
协变
当委托方法的返回类型具有的派生程度比委托签名更大时,就称为协变委托方法。因为方法的返回类型比委托签名的返回类型更具体,所以可对其进行隐式转换。这样该方法就可用作委托。
协变使得创建可被类和派生类同时使用的委托方法成为可能。
此示例演示委托如何使用派生的返回类型。SecondHandler
返回的数据类型为 Dogs
类型,是委托签名返回类型 Mammals
的子集。因此,SecondHandler
返回的所有可能值都可以存储在 Mammals
类型的变量中。
class Mammals { } class Dogs : Mammals { } class Program { // Define the delegate. public delegate Mammals HandlerMethod(); public static Mammals FirstHandler() { return null; } public static Dogs SecondHandler() { return null; } static void Main() { HandlerMethod handler1 = FirstHandler; // Covariance allows this delegate. HandlerMethod handler2 = SecondHandler; } }
逆变
当委托方法签名具有一个或多个参数,并且这些参数的类型派生自方法参数的类型时,就称为逆变委托方法。因为委托方法签名参数比方法参数更具体,因此可以在传递给处理程序方法时对它们进行隐式转换。
这样逆变使得可由大量类使用的更通用的委托方法的创建变得更加简单。
class Mammals { } class Dogs : Mammals { } class Program { public delegate void HandlerMethod(Dogs sampleDog); public static void FirstHandler(Mammals elephant) { } public static void SecondHandler(Dogs sheepDog) { } static void Main(string[] args) { // Contravariance permits this delegate. HandlerMethod handler1 = FirstHandler; HandlerMethod handler2 = SecondHandler; } }
合并委托(多路广播委托)
delegate void Del(string s); class TestClass { static void Hello(string s) { System.Console.WriteLine(" Hello, {0}!", s); } static void Goodbye(string s) { System.Console.WriteLine(" Goodbye, {0}!", s); } static void Main() { Del a, b, c, d; // Create the delegate object a that references // the method Hello: a = Hello; // Create the delegate object b that references // the method Goodbye: b = Goodbye; // The two delegates, a and b, are composed to form c: c = a + b; // Remove a from the composed delegate, leaving d, // which calls only the method Goodbye: d = c - a; System.Console.WriteLine("Invoking delegate a:"); a("A"); System.Console.WriteLine("Invoking delegate b:"); b("B"); System.Console.WriteLine("Invoking delegate c:"); c("C"); System.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!
声明、实例化和使用委托
委托的声明
public delegate void Del<T>(T item); public void Notify(int i) { }
或
Del<int> d1 = new Del<int>(Notify);
或
Del<int> d2 = Notify;
下面的示例阐释声明、实例化和使用委托。BookDB
类封装一个书店数据库,它维护一个书籍数据库。它公开 ProcessPaperbackBooks
方法,该方法在数据库中查找所有平装书,并对每本平装书调用一个委托。所使用的 delegate 类型称为 ProcessBookDelegate
。Test
类使用该类输出平装书的书名和平均价格。
委托的使用促进了书店数据库和客户代码之间功能的良好分隔。客户代码不知道书籍的存储方式和书店代码查找平装书的方式。书店代码也不知道找到平装书后将对平装书进行什么处理。
// 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 TestBookDB { // Print the title of the book. static void PrintTitle(Book b) { System.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: System.Console.WriteLine("Paperback Book Titles:"); // Create a new delegate object associated with the static // method Test.PrintTitle: bookDB.ProcessPaperbackBooks(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(totaller.AddBookToTotal); System.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