C#中的委托与事件[翻译]

委托与事件
   
Ganesh Nataraj最近写了一篇解释委托与事件的文章,在坊间流传较广,今天翻译成中文与大家共享,如有不妥之处,欢迎留言讨论。
   
C#中的委托类似于CC++中的函数指针。程序设计人员可以使用委托将方法的引用压缩到委托对象中,委托对象能被传递给调用该方法引用的代码而无须知道哪个方法将在编译时被调用。与CC++中的指针不同的是,委托是面向对象的、类型安全的、受保护的。
   
委托声明时定义一个返回压缩方法的类型,其中包含一组特定的描述和返回类型。对于静态方法而言,委托对象压缩其调用的方法。对于实例方法(instance methods)而言,委托对象将压缩一个实例和实例的一个方法。如果一个委托对象有一组适当的描述,可以调用带描述的委托。
   
委托有趣而实用的一个特征就是它不用知道也无需关心它引用对象的类,任何对象都可以,关键的是方法的描述类型和引用类型要与委托的匹配。这使委托特别适合一些匿名的请求。

注意:委托以调用方的安全许可身份运行,而不是以声明方的许可运行。
   
下面有两个委托的示例:
   
1向大家说明如何声明、实例化和调用一个委托;
    例2向大家说明如何联合两个委托。
1
 
    这个例子说明如何声明、实例化和使用委托。BookDB类压缩了一个包含各种书籍的书店数据库,它对外暴露ProcessPaperbackBooks方法,用以查找数据库中所有平装本的书籍并调用委托,使用委托来调用ProcessBookDelegateTest类使用这个类来打印处平装本书籍的标题和平均价格。
 
    委托的使用促进了书店数据库与客户端代码之间功能性的良好分离。客户端代码不用知晓书是如何存的如何找到平装本,而书店的代码不用知道查找到该平装书并提供给客户端后将会被如何处理。代码如下(为了部影响理解,代码保持原样):

  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.95mtrue);
104         bookDB.AddBook("The Unicode Standard 2.0"
105            "The Unicode Consortium"39.95mtrue);
106         bookDB.AddBook("The MS-DOS Encyclopedia"
107            "Ray Duncan"129.95mfalse);
108         bookDB.AddBook("Dogbert's Clues for the Clueless"
109            "Scott Adams"12.00mtrue);
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);

如例中所示,委托可以被同步调用,也可以使用BeginInvokeEndInvoke异步调用。

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. 接口

    委托与接口在都能促成规范与执行的分离,有相似之处。那声明时候使用委托声明时候使用接口呢?大体依据以下原则:

如下情况宜使用委托:

  • 只调用单个方法时.
  • 当一个类需要方法说明的多重执行时.
  • 期望使用静态方法执行规范时.
  • 期望得到一个类似事件的模式时.
  • 调用者无需知道无需获取定义方法的对象时
  • 只想给少数既定组件分发执行规范时.
  • 想要简单的组成结构时.

如下情况宜使用接口:

  • 当规范定义了一组需要调用的相关方法时.
  • 一个类仅代表性地执行一次规范时.
  • 接口的调用者想映射接口类型以获取其他类或接口时

posted on 2005-12-20 11:52 Zeus 阅读(2526) 评论(13)  编辑 收藏 所属分类: Translate

评论

#1楼  2005-12-20 16:44 老燕      

与楼主商榷:

这么理解正确否?更简单否?

·明确两个概念(BookDB类:委托方张三,Test类:受托方李律师)
·张三(BookDB类)告诉李律师(Test类):我要告王五(processBook方法),李律师胸脯一拍:没问题,这事我按民事诉讼程序(PrintTitle方法或AddBookToTotal)来办
·分工:委托方定义处理机制;受托方根据实际情况灵活实现。
·例子:可以参考一下EventHandle委托


  回复  引用  查看    

#2楼  2005-12-20 22:10 giogio [未注册用户]

这个不就是MSDN里面那篇“Delegates Tutorial”么?
MSDN链接:ms-help://MS.MSDNQTR.2003FEB.1033/csref/html/vcwlkDelegatesTutorial.htm   回复  引用    

#3楼  2005-12-20 22:25 giogio [未注册用户]

俺晕……

原文:
“Instantiating a delegate Once a delegate type has been declared, a delegate object must be created and associated with a particular method. Like all other objects, a new delegate object is created with a new expression. When creating a delegate, however, the argument passed to the new expression is special — it is written like a method call, but without the arguments to the method. ”

译文:
“实例化一个委托: 挡一个委托的类型被声明后,必须创建委托对象并与一个特定的方相关联。和其他对象一样,需要一起创建新的委托对象和新的表达式。”

少了一大片不说,new的理解也不对,因为MSDN里面那个new是黑体,说明是指保留字new……



  回复  引用    

#4楼  2005-12-20 22:51 -Zeus-      

在giogio提到之前我没看到MSDN的那篇文章,上面这篇翻译的是从rssforum社区里Ganesh Nataraj的一片回答别人问题的帖子,我觉得讲的不错所以就翻译了,可能MSDN中的是后来经过整理的,所以不同。   回复  引用  查看    

#5楼  2005-12-21 09:59 giogio [未注册用户]

所以一定要贴出原文链接,这样的话就能知道是他没说全,还是你觉得不好理解而没有翻译。   回复  引用    

#6楼  2005-12-21 11:07 _bugSharp [未注册用户]

giogio你就知道攻击和批评,你觉得不好,你可以写一篇更好的啊。

只要对内容做评论!
人不可能作到100%,难道什么就不做了。

希望楼主继续努力,为大家带来更多,更好的文章!   回复  引用    

#7楼  2005-12-21 13:25 -Zeus-      

谢谢各位兄弟的支持!
小弟水平有限,如果大家发现我写的文章中有什么错误敬请指出,多谢!   回复  引用  查看    

#8楼  2005-12-21 13:30 -Zeus-      

老燕的解释比较形象而且理解正确。   回复  引用  查看    

#9楼  2005-12-22 20:31 windwolf      

第一段中的“压缩”看得莫不着头脑
我猜原文大概是wrap吧 楼主把它翻译成包装多顺耳啊   回复  引用  查看    

#10楼  2005-12-22 23:02 -Zeus-      

文中encapsulate我翻译为压缩了,想过包装但觉不妥。谢谢windwolf指出!   回复  引用  查看    

#11楼  2005-12-29 10:57 砸就忽悠瘸了 [未注册用户]

委托是具有相同参数签名的方法变量:
可以从委托的声明中看出:delegate void MyDelegate(string s);

使用方法来实例化委托 : a = new MyDelegate(Hello);

使用委托变量时 是和使用普通函数是相同的 : a("A");

通俗点说 委托就是方法的变量 包含一个方法,那就和普通的变量一样,如果包含多个那就类似于数组
  回复  引用    

#12楼  2006-04-07 15:32 realmax [未注册用户]

encapsulate (vt)装入胶囊,压缩 (vi)形成胶囊 -- 金山词霸2005的翻译

呵呵...个人认为翻译为"封装"会不会好一点呢?   回复  引用    

#13楼  2006-04-10 02:37 -Zeus      

最开始采用的就是封装,后来又觉不妥替换成压缩了。
唉,语言鸿沟真的很麻烦。   回复  引用  查看    


标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)