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

本文主要介绍了C#中委托的使用,通过两个示例说明如何声明、实例化、调用委托以及联合委托。还阐述了委托的声明、实例化和调用的具体方式,指出委托以调用方安全许可身份运行。此外,探讨了委托与事件的关系,以及委托和接口的适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

  1None.gif// bookstore.cs
  2None.gifusing System;
  3None.gif// A set of classes for handling a bookstore:
  4None.gifnamespace Bookstore 
  5ExpandedBlockStart.gifContractedBlock.gifdot.gif{
  6InBlock.gif   using System.Collections;
  7InBlock.gif   // Describes a book in the book list:
  8InBlock.gif   public struct Book
  9ExpandedSubBlockStart.gifContractedSubBlock.gif   dot.gif{
 10InBlock.gif      public string Title;        // Title of the book.
 11InBlock.gif      public string Author;       // Author of the book.
 12InBlock.gif      public decimal Price;       // Price of the book.
 13InBlock.gif      public bool Paperback;      // Is it paperback?
 14InBlock.gif      public Book(string title, string author, decimal price, bool paperBack)
 15ExpandedSubBlockStart.gifContractedSubBlock.gif      dot.gif{
 16InBlock.gif         Title = title;
 17InBlock.gif         Author = author;
 18InBlock.gif         Price = price;
 19InBlock.gif         Paperback = paperBack;
 20ExpandedSubBlockEnd.gif      }

 21ExpandedSubBlockEnd.gif   }

 22InBlock.gif
 23InBlock.gif   // Declare a delegate type for processing a book:
 24InBlock.gif   public delegate void ProcessBookDelegate(Book book);
 25InBlock.gif
 26InBlock.gif   // Maintains a book database.
 27InBlock.gif   public class BookDB
 28ExpandedSubBlockStart.gifContractedSubBlock.gif   dot.gif{
 29InBlock.gif      // List of all books in the database:
 30InBlock.gif      ArrayList list = new ArrayList();   
 31InBlock.gif
 32InBlock.gif      // Add a book to the database:
 33InBlock.gif      public void AddBook(string title, string author, decimal price, bool paperBack)
 34ExpandedSubBlockStart.gifContractedSubBlock.gif      dot.gif{
 35InBlock.gif         list.Add(new Book(title, author, price, paperBack));
 36ExpandedSubBlockEnd.gif      }

 37InBlock.gif
 38InBlock.gif      // Call a passed-in delegate on each paperback book to process it: 
 39InBlock.gif      public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
 40ExpandedSubBlockStart.gifContractedSubBlock.gif      dot.gif{
 41InBlock.gif         foreach (Book b in list) 
 42ExpandedSubBlockStart.gifContractedSubBlock.gif         dot.gif{
 43InBlock.gif            if (b.Paperback)
 44InBlock.gif
 45InBlock.gif            // Calling the delegate:
 46InBlock.gif               processBook(b);
 47ExpandedSubBlockEnd.gif         }

 48ExpandedSubBlockEnd.gif      }

 49ExpandedSubBlockEnd.gif   }

 50ExpandedBlockEnd.gif}

 51None.gif// Using the Bookstore classes:
 52None.gifnamespace BookTestClient
 53ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 54InBlock.gif   using Bookstore;
 55InBlock.gif
 56InBlock.gif   // Class to total and average prices of books:
 57InBlock.gif   class PriceTotaller
 58ExpandedSubBlockStart.gifContractedSubBlock.gif   dot.gif{
 59InBlock.gif      int countBooks = 0;
 60InBlock.gif      decimal priceBooks = 0.0m;
 61InBlock.gif      internal void AddBookToTotal(Book book)
 62ExpandedSubBlockStart.gifContractedSubBlock.gif      dot.gif{
 63InBlock.gif         countBooks += 1;
 64InBlock.gif         priceBooks += book.Price;
 65ExpandedSubBlockEnd.gif      }

 66InBlock.gif      internal decimal AveragePrice()
 67ExpandedSubBlockStart.gifContractedSubBlock.gif      dot.gif{
 68InBlock.gif         return priceBooks / countBooks;
 69ExpandedSubBlockEnd.gif      }

 70ExpandedSubBlockEnd.gif   }

 71InBlock.gif   // Class to test the book database:
 72InBlock.gif   class Test
 73ExpandedSubBlockStart.gifContractedSubBlock.gif   dot.gif{
 74InBlock.gif      // Print the title of the book.
 75InBlock.gif      static void PrintTitle(Book b)
 76ExpandedSubBlockStart.gifContractedSubBlock.gif      dot.gif{
 77InBlock.gif         Console.WriteLine("   {0}", b.Title);
 78ExpandedSubBlockEnd.gif      }

 79InBlock.gif      // Execution starts here.
 80InBlock.gif      static void Main()
 81ExpandedSubBlockStart.gifContractedSubBlock.gif      dot.gif{
 82InBlock.gif         BookDB bookDB = new BookDB();
 83InBlock.gif         // Initialize the database with some books:
 84InBlock.gif         AddBooks(bookDB);      
 85InBlock.gif         // Print all the titles of paperbacks:
 86InBlock.gif         Console.WriteLine("Paperback Book Titles:");
 87InBlock.gif         // Create a new delegate object associated with the static 
 88InBlock.gif         // method Test.PrintTitle:
 89InBlock.gif         bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
 90InBlock.gif         // Get the average price of a paperback by using
 91InBlock.gif         // a PriceTotaller object:
 92InBlock.gif         PriceTotaller totaller = new PriceTotaller();
 93InBlock.gif         // Create a new delegate object associated with the nonstatic 
 94InBlock.gif         // method AddBookToTotal on the object totaller:
 95InBlock.gif         bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));
 96InBlock.gif         Console.WriteLine("Average Paperback Book Price: ${0:#.##}",
 97InBlock.gif            totaller.AveragePrice());
 98ExpandedSubBlockEnd.gif      }

 99InBlock.gif      // Initialize the book database with some test books:
100InBlock.gif      static void AddBooks(BookDB bookDB)
101ExpandedSubBlockStart.gifContractedSubBlock.gif      dot.gif{
102InBlock.gif         bookDB.AddBook("The C Programming Language"
103InBlock.gif            "Brian W. Kernighan and Dennis M. Ritchie"19.95mtrue);
104InBlock.gif         bookDB.AddBook("The Unicode Standard 2.0"
105InBlock.gif            "The Unicode Consortium"39.95mtrue);
106InBlock.gif         bookDB.AddBook("The MS-DOS Encyclopedia"
107InBlock.gif            "Ray Duncan"129.95mfalse);
108InBlock.gif         bookDB.AddBook("Dogbert's Clues for the Clueless"
109InBlock.gif            "Scott Adams"12.00mtrue);
110ExpandedSubBlockEnd.gif      }

111ExpandedSubBlockEnd.gif   }

112ExpandedBlockEnd.gif}

113None.gif
 

输出:

    平装书的标题:

   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(示范如何联合两个委托)

这个例子示范了委托的构成,委托对象的一个有用属性是他们可以使用”+”运算符来进行联合,联合委托调用组成它的两个委托,只有类型相同的委托才可以联合。操作符用于从联合委托中移除一个委托。示例代码如下:

 

 1None.gif// compose.cs
 2None.gifusing System;
 3None.gifdelegate void MyDelegate(string s);
 4None.gifclass MyClass
 5ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 6InBlock.gif    public static void Hello(string s)
 7ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 8InBlock.gif        Console.WriteLine("  Hello, {0}!", s);
 9ExpandedSubBlockEnd.gif    }

10InBlock.gif    public static void Goodbye(string s)
11ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
12InBlock.gif        Console.WriteLine("  Goodbye, {0}!", s);
13ExpandedSubBlockEnd.gif    }

14InBlock.gif    public static void Main()
15ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
16InBlock.gif        MyDelegate a, b, c, d;
17InBlock.gif        // Create the delegate object a that references 
18InBlock.gif        // the method Hello:
19InBlock.gif        a = new MyDelegate(Hello);
20InBlock.gif        // Create the delegate object b that references 
21InBlock.gif        // the method Goodbye:
22InBlock.gif        b = new MyDelegate(Goodbye);
23InBlock.gif        // The two delegates, a and b, are composed to form c: 
24InBlock.gif        c = a + b;
25InBlock.gif        // Remove a from the composed delegate, leaving d, 
26InBlock.gif        // which calls only the method Goodbye:
27InBlock.gif        d = c - a;
28InBlock.gif        Console.WriteLine("Invoking delegate a:");
29InBlock.gif        a("A");
30InBlock.gif        Console.WriteLine("Invoking delegate b:");
31InBlock.gif        b("B");
32InBlock.gif        Console.WriteLine("Invoking delegate c:");
33InBlock.gif        c("C");
34InBlock.gif        Console.WriteLine("Invoking delegate d:");
35InBlock.gif        d("D");
36ExpandedSubBlockEnd.gif    }

37ExpandedBlockEnd.gif}

38None.gif

 

输出:

调用委托 a:
  Hello, A!
调用委托b:
  Goodbye, B!
调用委托c:
  Hello, C!
  Goodbye, C!
调用委托d:
  Goodbye, D!

委托与事件

      对于给组件的“听众”来通知该组件的发生的事件而言,使用委托特别适合。

委托 VS. 接口

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

如下情况宜使用委托:

  • 只调用单个方法时.

  • 当一个类需要方法说明的多重执行时.

  • 期望使用静态方法执行规范时.

  • 期望得到一个类似事件的模式时.

  • 调用者无需知道无需获取定义方法的对象时

  • 只想给少数既定组件分发执行规范时.

  • 想要简单的组成结构时.

如下情况宜使用接口:

  • 当规范定义了一组需要调用的相关方法时.

  • 一个类仅代表性地执行一次规范时.

  • 接口的调用者想映射接口类型以获取其他类或接口时

转载于:https://www.cnblogs.com/Zeus/archive/2005/12/20/300731.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值