C# 委托
委托
这篇教程论述了委托类型,它显示了如何委托映射到静态方法和实例方法,以及如何将他们相结合(多播)。
延伸阅读
教程
C#中的委托类似于C或C++中的函数指针,使用委托允许开发者将一个函数引用封装进一个委托对象,然后,委托对象可以作为参数传到需要调用这个函数的代码块中,编译时不需要知道哪个函数会被调用。和C或C++的区别在于,委托是面向对象、类型安全和过程安全的。
委托声明定义了一种将函数的参数和返回值封装的类型。对于静态方法,委托将方法封装以便于被调用。对于实例方法,委托将实例和方法一起封装进这个委托实例。如果你知道一个委托的实例和适当的参数,你就可以通过参数调用这个委托。
委托有一个既有趣有比较有用的特性是,他不知道或者不关心引用对象的类型,任何对象都会执行一样的操作,重要的是函数的参数类型和返回值是否和委托匹配。这让委托非常适合“匿名”调用。
注:委托在调用处而不是声明处的安全许可下运行。
本教程有两个例子:
- 实例1 演示如何声明、初始化和调用一个委托
- 实例2 演示如何结合两个委托
除此之外,还讨论了下面两个话题:
- 委托和事件
- 委托与接口
实例1
下面的例子阐明声明、实例化和如何使用一个委托。BookDB类封装了书店数据库中的书籍数据,它暴露了一个方法ProcessPaperbakcBooks,找到所有书籍数据然后为每个数据调用一个委托,委托的名字是ProcessBookDelegate。Test类使用这个类打印每本平装书的书名和计算平均价格。
委托的作用就是提升书店数据库和客户端代码的功能的良好分隔(耦合度)。客户端代码不知道书籍信息是如何存储也不知道bookstore中的代码是如何找到所有平装书的,bookesotre也不知道当他找到所有平装书后会执行什么流程。
--------------------------------------------------------------------------------------------------------------------
// bookstore.cs
using System;
// 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 Test
{
// Print the title of the book.
static void PrintTitle(Book b)
{
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:
Console.WriteLine("Paperback Book Titles:");
// Create a new delegate object associated with the static
// method Test.PrintTitle:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(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(new ProcessBookDelegate(totaller.AddBookToTotal));
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);
}
}
}
--------------------------------------------------------------------------------------------------------------------
输出
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);
声明一个新的委托类型。任何委托类型都是描述参数的数量和类型,以及他封装的方法的返回值类型。每当需要一组新的参数类型或返
回值类型,必须声明一个新的委托类型。
- 初始化委托 一旦声明了一种累托类型,就必须创建一个委托对象并指定一个特定的方法。像其他对象一样,用new表达式来创建一个新的委托对象。不过创建委托的时候,传递给new表达式的参数有些不同-写起来有点像函数调用,但是没有参数。
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
创建一个新的委托对象,指定静态方法Test.PrintTitle.
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));
创建一个新的委托对象指定一个totaller中的非静态的方法AddBookToTotal。这两种情况下,委托对象都会立即传送至ProcessPaperbakcBooks方法。
注意,一旦创建了一个委托,它所指定的函数标签就不能改变-委托对象是不可变的。
- 调用委托 一旦创建了一个累托类型,委托对象就会以它的工作方式传送给其他代码调用委托。委托对象使用委托对象名来调用,并且后面需要带参数表。例如:
processBook(b);
委托既能同步调用就想本例中一样,也能异步调用,异步调用使用 BeginInvoke 和 EndInvoke 方法。
实例2
本例论述了委托的组合,委托对象的一个有用的特性就是他们能通过“+”运算符来进行组合,组合委托会调用组成这个委托的两个委托,只有相同类型的委托才能被用来组合。
“-”运算符可以从一个组合委托移除一个委托组件。
--------------------------------------------------------------------------------------------------------------------
// compose.cs
using System;
delegate void MyDelegate(string s);
class MyClass
{
public static void Hello(string s)
{
Console.WriteLine(" Hello, {0}!", s);
}
public static void Goodbye(string s)
{
Console.WriteLine(" Goodbye, {0}!", s);
}
public static void Main()
{
MyDelegate a, b, c, d;
// Create the delegate object a that references
// the method Hello:
a = new MyDelegate(Hello);
// Create the delegate object b that references
// the method Goodbye:
b = new MyDelegate(Goodbye);
// The two delegates, a and b, are composed to form c:
c = a + b;
// Remove a from the composed delegate, leaving d,
// which calls only the method Goodbye:
d = c - a;
Console.WriteLine("Invoking delegate a:");
a("A");
Console.WriteLine("Invoking delegate b:");
b("B");
Console.WriteLine("Invoking delegate c:");
c("C");
Console.WriteLine("Invoking delegate d:");
d("D");
}
}
输出
Invoking delegate a: Hello, A! Invoking delegate b: Goodbye, B! Invoking delegate c: Hello, C! Goodbye, C! Invoking delegate d: Goodbye, D!
--------------------------------------------------------------------------------------------------------------------
委托和事件
委托是实现事件的理想形式 — 一个组件告诉坚挺着已经发生的变化。更多委托在事件中的应用,请看Events Tutorial.
委托与接口
委托和接口的相似之处是他们使声明和实现分离。多个独立的对象可以实现功能,因为他们实现了同一个接口。简单来说,一个委托声明了函数标签,作者就可以定义与这个标签兼容的函数。什么时候用接口?什么时候用委托?
这些情况下会用委托:
- 一个单独的方法被调用。
- 一个类可能想要包涵现某个函数声明的多个实现。
- 期望允许使用静态方法来实现声明。
- 期望使用事件设计模式 (for more information, see the Events Tutorial).
- 调用者不需要知道方法定义在哪个对象
- 功能的实现要定义到外部,而且这些实现只有几个可选项(其实就是通过委托,实现函数的外部实现,而且这些函数不能太多)
- 想要设计简单的组件cast to
这些情况下会用接口:
- 规范定义了一些关联的方法
- 类只实现一次了某个具有代表性的规范
- 接口的调用者想转换或者取得接口类型,以获取另一个接口或类。(类型转换)

浙公网安备 33010602011771号