委托与事件
Ganesh Nataraj最近写了一篇解释委托与事件的文章,在坊间流传较广,今天翻译成中文与大家共享,如有不妥之处,欢迎留言讨论。
C#中的委托类似于C或C++中的函数指针。程序设计人员可以使用委托将方法的引用压缩到委托对象中,委托对象能被传递给调用该方法引用的代码而无须知道哪个方法将在编译时被调用。与C或C++中的指针不同的是,委托是面向对象的、类型安全的、受保护的。
委托声明时定义一个返回压缩方法的类型,其中包含一组特定的描述和返回类型。对于静态方法而言,委托对象压缩其调用的方法。对于实例方法(instance
methods)而言,委托对象将压缩一个实例和实例的一个方法。如果一个委托对象有一组适当的描述,可以调用带描述的委托。
委托有趣而实用的一个特征就是它不用知道也无需关心它引用对象的类,任何对象都可以,关键的是方法的描述类型和引用类型要与委托的匹配。这使委托特别适合一些匿名的请求。
注意:委托以调用方的安全许可身份运行,而不是以声明方的许可运行。
下面有两个委托的示例:
例1向大家说明如何声明、实例化和调用一个委托;
例2向大家说明如何联合两个委托。
例1
这个例子说明如何声明、实例化和使用委托。BookDB类压缩了一个包含各种书籍的书店数据库,它对外暴露PRocessPaperbackBooks方法,用以查找数据库中所有平装本的书籍并调用委托,使用委托来调用ProcessBookDelegate。Test类使用这个类来打印处平装本书籍的标题和平均价格。
委托的使用促进了书店数据库与客户端代码之间功能性的良好分离。客户端代码不用知晓书是如何存的如何找到平装本,而书店的代码不用知道查找到该平装书并提供给客户端后将会被如何处理。代码如下(为了部影响理解,代码保持原样):
1// bookstore.cs
2using System;
3// A set of
classes for handling a bookstore:
4namespace Bookstore
5{
6 using System.Collections;
7 //
Describes a book in the book list:
8 public struct
Book
9 {
10 public
string Title; // Title of the
book.
11 public string
Author; // Author of the
book.
12 public decimal
Price; // Price of the
book.
13 public bool
Paperback; // Is it
paperback?
14 public Book(string title,
string author, decimal price, bool
paperBack)
15
{
16 Title =
title;
17 Author =
author;
18 Price =
price;
19 Paperback =
paperBack;
20 }
21
}
22
23 // Declare a delegate type for processing
a book:
24 public delegate void ProcessBookDelegate(Book
book);
25
26 // Maintains a book
database.
27 public class BookDB
28
{
29 // List of all books in the
database:
30 ArrayList list = new
ArrayList();
31
32
// Add a book to the database:
33 public
void AddBook(string title, string author, decimal price, bool
paperBack)
34
{
35 list.Add(new
Book(title, author, price,
paperBack));
36
}
37
38 // Call a passed-in
delegate on each paperback book to process
it:
39 public void
ProcessPaperbackBooks(ProcessBookDelegate
processBook)
40
{
41 foreach (Book b in
list)
42
{
43
if
(b.Paperback)
44
45
// Calling the
delegate:
46
processBook(b);
47
}
48 }
49
}
50}
51// Using the Bookstore classes:
52namespace
BookTestClient
53{
54 using
Bookstore;
55
56 // Class to total and average
prices of books:
57 class
PriceTotaller
58
{
59 int countBooks =
0;
60 decimal priceBooks =
0.0m;
61 internal void
AddBookToTotal(Book book)
62
{
63 countBooks +=
1;
64 priceBooks +=
book.Price;
65
}
66 internal decimal
AveragePrice()
67
{
68 return priceBooks
/ countBooks;
69
}
70 }
71 // Class to test the book
database:
72 class Test
73
{
74 // Print the title of the
book.
75 static void PrintTitle(Book
b)
76
{
77
Console.WriteLine(" {0}",
b.Title);
78
}
79 // Execution starts
here.
80 static void
Main()
81
{
82 BookDB bookDB =
new BookDB();
83 //
Initialize the database with some
books:
84
AddBooks(bookDB);
85
// Print all the titles of
paperbacks:
86
Console.WriteLine("Paperback Book
Titles:");
87 // Create
a new delegate object associated with the
static
88 //
method
Test.PrintTitle:
89
bookDB.ProcessPaperbackBooks(new
ProcessBookDelegate(PrintTitle));
90
// Get the average price of a paperback by
using
91 // a
PriceTotaller
object:
92
PriceTotaller totaller = new
PriceTotaller();
93 //
Create a new delegate object associated with the
nonstatic
94 //
method AddBookToTotal on the object
totaller:
95
bookDB.ProcessPaperbackBooks(new
ProcessBookDelegate(totaller.AddBookToTotal));
96
Console.WriteLine("Average Paperback Book Price:
${0:#.##}",
97
totaller.AveragePrice());
98
}
99 // Initialize the book database with
some test books:
100 static void
AddBooks(BookDB bookDB)
101
{
102 bookDB.AddBook("The C
Programming
Language",
103
"Brian W. Kernighan and Dennis M. Ritchie", 19.95m,
true);
104
bookDB.AddBook("The Unicode Standard
2.0",
105
"The Unicode Consortium", 39.95m,
true);
106
bookDB.AddBook("The MS-DOS
Encyclopedia",
107
"Ray Duncan", 129.95m,
false);
108
bookDB.AddBook("Dogbert‘s Clues for the
Clueless",
109
"Scott Adams", 12.00m, true);
110
}
111 }
112}
113
输出:
平装书的标题:
The C Programming Language
The Unicode
Standard 2.0
Dogbert‘s Clues for the Clueless
平均价格: $23.97
讨论:
委托的声明
委托可声明如下:
public delegate void ProcessBookDelegate(Book book);
声明一个新的委托类型。每个委托类型可包含委托描述的数量和类型,包含被压缩方法返回值的类型。不管是否需要一组类型描述或返回值类型,必须声明一个新的委托类型。
实例化一个委托: 挡一个委托的类型被声明后,必须创建委托对象并与一个特定的方法相关联。和其他对象一样,需要一起创建新的委托对象和新的表达式。
看看这段:
bookDB.ProcessPaperbackBooks(new
ProcessBookDelegate(PrintTitle));
声明一个关联静态方法Test.PrintTitle的委托对象。
再看看这段:
bookDB.ProcessPaperbackBooks(new
ProcessBookDelegate(totaller.AddBookToTotal));
创建一个委托对象关联totaller对象上的非静态方法 AddBookToTotal。新的委托对象马上被传递给ProcessPaperbackBooks方法。
请注意,委托一旦被创建后,其关联的方法将不能再被更改,因为委托对象是不可变的。
调用委托:委托对象被创建后会被传递给调用委托的其他代码。通过委托对象名称和其后跟随的括号化描述来调用委托对象,示例如下。
processBook(b);
如例中所示,委托可以被同步调用,也可以使用BeginInvoke和EndInvoke异步调用。
例2(示范如何联合两个委托)
这个例子示范了委托的构成,委托对象的一个有用属性是他们可以使用”+”运算符来进行联合,联合委托调用组成它的两个委托,只有类型相同的委托才可以联合。”-”操作符用于从联合委托中移除一个委托。示例代码如下:
1// compose.cs
2using System;
3delegate void
MyDelegate(string s);
4class
MyClass
5{
6 public static void Hello(string
s)
7
{
8 Console.WriteLine("
Hello, {0}!", s);
9 }
10 public
static void Goodbye(string s)
11
{
12 Console.WriteLine("
Goodbye, {0}!", s);
13 }
14 public
static void Main()
15
{
16 MyDelegate a, b, c,
d;
17 // Create the delegate object
a that references
18 // the
method Hello:
19 a = new
MyDelegate(Hello);
20 // Create the
delegate object b that
references
21 // the method
Goodbye:
22 b = new
MyDelegate(Goodbye);
23 // The two
delegates, a and b, are composed to form
c:
24 c = a +
b;
25 // Remove a from the composed
delegate, leaving d,
26 //
which calls only the method
Goodbye:
27 d = c -
a;
28 Console.WriteLine("Invoking
delegate a:");
29
a("A");
30
Console.WriteLine("Invoking delegate
b:");
31
b("B");
32
Console.WriteLine("Invoking delegate
c:");
33
c("C");
34
Console.WriteLine("Invoking delegate
d:");
35
d("D");
36 }
37}
38
输出:
调用委托 a:
Hello, A!
调用委托b:
Goodbye,
B!
调用委托c:
Hello, C!
Goodbye, C!
调用委托d:
Goodbye, D!
委托与事件
对于给组件的“听众”来通知该组件的发生的事件而言,使用委托特别适合。
委托 VS. 接口
委托与接口在都能促成规范与执行的分离,有相似之处。那声明时候使用委托声明时候使用接口呢?大体依据以下原则:
如下情况宜使用委托:
只调用单个方法时.
当一个类需要方法说明的多重执行时.
期望使用静态方法执行规范时.
期望得到一个类似事件的模式时.
调用者无需知道无需获取定义方法的对象时
只想给少数既定组件分发执行规范时.
想要简单的组成结构时.
如下情况宜使用接口:
当规范定义了一组需要调用的相关方法时.
一个类仅代表性地执行一次规范时.
接口的调用者想映射接口类型以获取其他类或接口时
原文:http://www.cnblogs.com/rosesmall/p/3680453.html