看看MSDN上是怎么说委托的!它没有什么神秘的,说它神秘是因为你没有仔细看去...
委托和接口都允许类设计器分离类型声明和实现。 任何类或结构都能继承和实现给定的接口。 可以为任何类上的方法创建委托,前提是该方法符合委托的方法签名。 接口引用或委托可由不了解实现该接口或委托方法的类的对象使用。 既然存在这些相似性,那么类设计器何时应使用委托,何时又该使用接口呢?
在以下情况下,请使用委托:
-
当使用事件设计模式时。
-
当封装静态方法可取时。
-
当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。
-
需要方便的组合。
-
当类可能需要该方法的多个实现时。
在以下情况下,请使用接口:
-
当存在一组可能被调用的相关方法时。
-
当类只需要方法的单个实现时。
-
当使用接口的类想要将该接口强制转换为其他接口或类类型时。
-
当正在实现的方法链接到类的类型或标识时:例如比较方法。
IComparable 或泛型版本 IComparable<T> 就是一个使用单一方法接口而不使用委托的很好的示例。 IComparable 声明 CompareTo 方法,该方法返回一个整数,指定相同类型的两个对象之间的小于、等于或大于关系。 IComparable可用作排序算法的基础。 虽然将委托比较方法用作排序算法的基础是有效的,但是并不理想。 因为进行比较的能力属于类,而比较算法不会在运行时改变,所以单一方法接口是理想的。
委托是一种安全地封装方法的类型,它与 C 和 C++ 中的函数指针类似。 与 C 中的函数指针不同,委托是面向对象的、类型安全的和保险的。 委托的类型由委托的名称定义。 下面的示例声明了一个名为 Del 的委托,该委托可以封装一个采用字符串作为参数并返回 void的方法。
public delegate void Del(string message);
构造委托对象时,通常提供委托将包装的方法的名称或使用匿名方法。 实例化委托后,委托将把对它进行的方法调用传递给方法。 调用方传递给委托的参数被传递给方法,来自方法的返回值(如果有)由委托返回给调用方。 这被称为调用委托。 可以将一个实例化的委托视为被包装的方法本身来调用该委托。 例如:
// Create a method for a delegate.publicstaticvoid DelegateMethod(string message) { System.Console.WriteLine(message); }
// Instantiate the delegate. Del handler = DelegateMethod; // Call the delegate. handler("Hello World");
委托类型派生自 .NET Framework 中的 Delegate 类。 委托类型是密封的,不能从 Delegate 中派生委托类型,也不可能从中派生自定义类。 由于实例化委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。 这样,方法便可以将一个委托作为参数来接受,并且以后可以调用该委托。 这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。 以这种方式使用委托时,使用委托的代码无需了解有关所用方法的实现方面的任何信息。 此功能类似于接口所提供的封装。 有关更多信息,请参见何时使用委托而不使用接口。
回调的另一个常见用法是定义自定义的比较方法并将该委托传递给排序方法。 它允许调用方的代码成为排序算法的一部分。 下面的示例方法使用 Del 类型作为参数:
public void MethodWithCallback(int param1, int param2, Del callback)
{
callback("The number is: " + (param1 + param2).ToString());
}
然后可以将上面创建的委托传递给该方法:
MethodWithCallback(1, 2, handler);
在控制台中将收到下面的输出:
The number is: 3
在将委托用作抽象概念时,MethodWithCallback 不需要直接调用控制台 -- 设计它时无需考虑控制台。 MethodWithCallback 的作用只是准备字符串并将该字符串传递给其他方法。 此功能特别强大,因为委托的方法可以使用任意数量的参数。
将委托构造为包装实例方法时,该委托将同时引用实例和方法。 除了它所包装的方法外,委托不了解实例类型,所以只要任意类型的对象中具有与委托签名相匹配的方法,委托就可以引用该对象。 将委托构造为包装静态方法时,它只引用方法。 考虑下列声明:
public class MethodClass { public void Method1(string message) { } public void Method2(string message) { } }
加上前面显示的静态 DelegateMethod,现在我们有三个方法可由 Del 实例进行包装。
调用委托时,它可以调用多个方法。 这称为多路广播。 若要向委托的方法列表(调用列表)中添加额外的方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。 例如:
MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;
//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;
此时,allMethodsDelegate 在其调用列表中包含三个方法 -- Method1、Method2 和 DelegateMethod。 原来的三个委托 d1、d2 和 d3 保持不变。 调用 allMethodsDelegate 时,将按顺序调用所有这三个方法。 如果委托使用引用参数,则引用将依次传递给三个方法中的每个方法,由一个方法引起的更改对下一个方法是可见的。 如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。 如果委托具有返回值和/或输出参数,它将返回最后调用的方法的返回值和参数。 若要从调用列表中移除方法,请使用减法运算符或减法赋值运算符(“-”或“-=”)。 例如:
//remove Method1 allMethodsDelegate -= d1; // copy AllMethodsDelegate while removing d2 Del oneMethodDelegate = allMethodsDelegate - d2;
由于委托类型派生自 System.Delegate,所以可在委托上调用该类定义的方法和属性。 例如,为了找出委托的调用列表中的方法数,您可以编写下面的代码:
int invocationCount = d1.GetInvocationList().GetLength(0);
在调用列表中具有多个方法的委托派生自 MulticastDelegate,这是 System.Delegate 的子类。 由于两个类都支持 GetInvocationList,所以上面的代码在两种情况下都适用。
多路广播委托广泛用于事件处理中。 事件源对象向已注册接收该事件的接收方对象发送事件通知。 为了为事件注册,接收方创建了旨在处理事件的方法,然后为该方法创建委托并将该委托传递给事件源。 事件发生时,源将调用委托。 然后,委托调用接收方的事件处理方法并传送事件数据。 给定事件的委托类型由事件源定义。 有关更多信息,请参见事件(C# 编程指南)。
在编译时,对分配了两种不同类型的委托进行比较将产生编译错误。 如果委托实例静态地属于类型 System.Delegate,则允许进行比较,但在运行时将返回 false。 例如:
delegate void Delegate1(); delegate void Delegate2(); static void method(Delegate1 d, Delegate2 e, System.Delegate f) { // Compile-time error. //Console.WriteLine(d == e); // OK at compile-time. False if the run-time type of f // is not the same as that of d. System.Console.WriteLine(d == f); }
匿名方法(C# 编程指南)
Visual Studio 2010
在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法。 C# 2.0 引入了匿名方法,而在 C# 3.0 及更高版本中,Lambda 表达式取代了匿名方法,作为编写内联代码的首选方式。 不过,本主题中有关匿名方法的信息同样也适用于 Lambda 表达式。 有一种情况下,匿名方法提供了 Lambda 表达式中所没有的功能。 您可使用匿名方法来忽略参数列表。 这意味着匿名方法可转换为具有各种签名的委托。 这对于 Lambda 表达式来说是不可能的。 有关 lambda 表达式的更多特定信息,请参见 Lambda 表达式(C# 编程指南)。
要将代码块传递为委托参数,创建匿名方法则是唯一的方法。 这里是两个示例:
// Create a handler for a click event.
button1.Click += delegate(System.Object o, System.EventArgs e)
{ System.Windows.Forms.MessageBox.Show("Click!"); };
如何:合并委托(多路广播委托)(C# 编程指南)
Visual Studio 2010
更新:2010 年 9 月
此示例演示如何创建多路广播的委托。 委派对象的一个有用的属性时多个对象可分配给一个委托实例通过使用 +运算符。 多路广播的委托包含指定的委托的列表。 在调用多路广播的委托时它将调用该委托将按顺序的列表中。 只有相同类型的委托可以进行组合。
-运算符可用于移除组件委托从 多路广播委托。
using System; // Define a custom delegate that has a string parameter and returns void. delegate void CustomDel(string s); class TestClass { // Define two methods that have the same signature as CustomDel. 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() { // Declare instances of the custom delegate. CustomDel hiDel, byeDel, multiDel, multiMinusHiDel; // In this example, you can omit the custom delegate if you // want to and use Action<string> instead. //Action<string> hiDel, byeDel, multiDel, multiMinusHiDel; // Create the delegate object hiDel that references the // method Hello. hiDel = Hello; // Create the delegate object byeDel that references the // method Goodbye. byeDel = Goodbye; // The two delegates, hiDel and byeDel, are combined to // form multiDel. multiDel = hiDel + byeDel; // Remove hiDel from the multicast delegate, leaving byeDel, // which calls only the method Goodbye. multiMinusHiDel = multiDel - hiDel; Console.WriteLine("Invoking delegate hiDel:"); hiDel("A"); Console.WriteLine("Invoking delegate byeDel:"); byeDel("B"); Console.WriteLine("Invoking delegate multiDel:"); multiDel("C"); Console.WriteLine("Invoking delegate multiMinusHiDel:"); multiMinusHiDel("D"); } } /* Output: Invoking delegate hiDel: Hello, A! Invoking delegate byeDel: Goodbye, B! Invoking delegate multiDel: Hello, C! Goodbye, C! Invoking delegate multiMinusHiDel: Goodbye, D! */
何:声明、实例化和使用委托(C# 编程指南)
Visual Studio 2010
更新:2010 年 7 月
在 C# 1.0 和以后,下面的示例所示,可以声明委托。
// Declare a delegate.delegatevoid Del(string str);
// Declare a method with the same signature as the delegate.staticvoid Notify(string name)
{
Console.WriteLine("Notification received for: {0}", name);
}
// Create an instance of the delegate.
Del del1 = new Del(Notify);
C# 2.0 提供了更简单的方法来编写在以前的声明,如下面的示例所示。
// C# 2.0 provides a simpler way to declare an instance of Del. Del del2 = Notify;
在 C# 2.0 和更高版本中,也可能是到下面的示例所示声明并初始化一个 委派,使用匿名方法。
// Instantiate Del by using an anonymous method.
Del del3 = delegate(string name)
{ Console.WriteLine("Notification received for: {0}", name); };
在 C# 3.0 和更高版本,委托可还声明和实例化使用一个 lambda 表达式,如下面的示例所示。
// Instantiate Del by using a lambda expression. Del del4 = name => { Console.WriteLine("Notification received for: {0}", name); };
有关更多信息,请参见Lambda 表达式(C# 编程指南)。
下面的示例阐释声明、实例化和使用委托。 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);
}
}
}
/* Output:
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);
每个委托类型都描述参数的数目和类型,以及它可以封装的方法的返回值类型。 每当需要一组新的参数类型或新的返回值类型时,都必须声明一个新的委托类型。
-
实例化委托。
声明了委托类型后,必须创建委托对象并使之与特定方法关联。 在上一个示例中,您通过按下面示例中的方式将 PrintTitle 方法传递到 ProcessPaperbackBooks 方法来实现这一点:
bookDB.ProcessPaperbackBooks(PrintTitle);
这将创建与静态方法 Test.PrintTitle 关联的新委托对象。 类似地,对象 totaller 的非静态方法 AddBookToTotal 是按下面示例中的方式传递的:
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);
在两个示例中,都向 ProcessPaperbackBooks 方法传递了一个新的委托对象。
委托创建后,它的关联方法就不能更改;委托对象是不可变的。
-
调用委托。
创建委托对象后,通常将委托对象传递给将调用该委托的其他代码。 通过委托对象的名称(后面跟着要传递给委托的参数,括在括号内)调用委托对象。 下面是委托调用的示例:
processBook(b);
与本例一样,可以通过使用 BeginInvoke 和 EndInvoke 方法同步或异步调用委托。