何时使用委托而不使用接口
委托和接口都允许类设计器分离类型声明和实现。给定的接口可由任何类或结构继承和实现。可以为任何类中的方法创建委托,前提是该方法符合委托的方法签名。接口引用或委托可由不了解实现该接口或委托方法的类的对象使用。既然存在这些相似性,那么类设计器何时应使用委托,何时又该使用接口呢?
在以下情况中使用委托:
l 当使用事件设计模式时
l 当封装静态方法可取时
l 当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时
l 需要方便的组合
l 当类可能需要该方法的多个实现时
在以下情况中使用接口:
l 当存在一组可能被调用的相关方法时
l 当类只需要方法的单个实现时
l 当使用接口的类想要将该接口强制转换为其他接口或类类型时
l 当正在实现的方法链接到类的类型或标识时:例如比较方法
使用单一方法接口而不使用委托的一个很好的示例是IComparable。IComparable声明CompareTo方法,该方法返回一个整数,以指定相同类型的两个对象之间的小于、等于或大于关系。IComparable可用作排序算法的基础,虽然将委托比较方法用作排序算法的基础是有效的,但是并不理想。因为进行比较的能力属于类,而比较算法不会在运行时改变,所以单一方法接口是理想的。
委托中的协变和逆变
将方法签名与委托类型匹配时,协变和逆变提供了一定程度的灵活性。协变允许方法具有的派生返回类型比委托中定义的更多。逆变允许方法具有的派生参数类型比委托类型中的更少。
示例1(协变)
本示例演示如何将委托与具有返回类型的方法一起使用,这些返回类型派生自委托签名中的返回类型。由SecondHandler返回的数据类型是Dogs类型,它是由委托中定义的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;
}
}
示例2(逆变)
本示例演示如何将委托与具有某个类型的参数的方法一起使用,这些参数是委托签名参数类型的基类型。通过逆变,以前必须使用若干个不同处理程序的地方现在只要使用一个事件处理程序即可。如,现在可以创建一个接收EventArgs输入参数的事件处理程序,然后,可以将该处理程序与发送MouseEventArgs类型(作为参数)的Button.MouseClick事件一起使用,也可以将该处理程序与发送KeyEventArgs参数的TextBox.KeyDown事件一起使用。
public Form1()
{
InitializeComponent();
lastActivity = new System.DateTime();
this.textBox1.KeyDown += this.MultiHandler; //works with KeyEventArgs
this.button1.MouseClick += this.MultiHandler; //works with MouseEventArgs
}
// Event hander for any event with an EventArgs or
// derived class in the second parameter
private void MultiHandler( object sender, System.EventArgs e)
{
lastActivity = System.DateTime.Now;
}
如何:合并委托(多路广播委托)
本示例演示如何组合多路广播委托。委托对象的一个用途在于,可以使用+运算符将它们分配给一个要成为多路广播委托的委托实例。组合的委托可调用组成它的那两个委托。只有相同类型的委托才可以组合。
-运算符可用来从组合的委托移除组件委托。
class TestClass2 {
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 a that references
//the method Goodbye:
b = Goodbye;
//The two delegates, a and b, are composed to from 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");
}
}
如何:声明、实例化和使用委托
委托的声明如下所示:
public delegate void Del <T>(T item);
public void Notify(int i) { }
Del <int> d1 = new Del <int>(Notify);
在C# 2.0中,还可以使用下面的简化语法来声明委托:
Del <int> d2 = Notify;
下面的示例阐释声明、实例化和使用委托。BookDB类封装一个书店数据库,它维护一个书籍数据库。它公开ProcessPaperbackBooks方法,该方法在数据库中查找所有平装书,并对每本平装书调用一个委托。所使用的delegate类型称为ProcessBookDelegate。Test类使用该类输出平装书的书名和平均价格。
委托的使用促进了书店数据库和客户代码之间功能的良好分隔。客户代码不知道书籍的存储方式和书店代码查找平装书的方式。书店代码也不知道找到平装书后将对平装书进行什么处理。
示例
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);
}
}
}
可靠编程
l 声明委托
下列语句:
public delegate void ProcessBookDelegate(Book book);
声明一个新的委托类型。每个委托类型都描述参数的数目和类型,以及它可以封装的方法的返回值类型。每当需要一组新的参数类型或新的返回值类型时,都必须声明一个新的委托类型。
l 实例化委托。
声明了委托类型后,必须创建委托对象并使之与特定方法关联。在上面的示例中,这是通过将PrintTitle方法传递给ProcessPaperbackBooks方法来完成的,如下所示:
bookDB.ProcessPaperbackBooks(PrintTitle);
这将创建与静态方法Test.PrintTitle关联的新委托对象。类似地,对象totaller的非静态方法AddBookToTotal是按如下方式传递的:
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);
在两个示例中,都向ProcessPaperbackBooks方法传递了一个新的委托对象。
委托一旦创建,它的关联方法就不能更改;委托对象是不可变的。
l 调用委托。
创建委托对象后,通常将委托对象传递给将调用该委托的其他代码。通过委托对象的名称(后面跟着要传递给委托的参数,括在括号内)调用委托对象。下面是委托调用的示例:
processBook(b);
与本示例一样,可以通过使用BeginInvoke和EndInvoke方法同步或异步调用委托。