C#编程指南系列 - 委托 Delegate

概念

委托是一种引用方法的类型,委托类似于 C++ 函数指针,但它是类型安全的。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值,如下面的示例所示:

 

public delegate int PerformCalculation(int x, int y);
public delegate void Del<T>(T item);
public void Notify(int i) { }

 

 

 

与委托的签名(由返回类型和参数组成)匹配的任何方法都可以分配给该委托。这样就可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。只要知道委托的签名,便可以分配自己的委托方法。将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。

下面具体介绍一下委托的一些应用。

委托允许将方法作为参数进行传递

请看下面例子:

BookDB 类封装一个书店数据库,它维护一个书籍数据库。它公开 ProcessPaperbackBooks 方法,该方法在数据库中查找所有平装书,并对每本平装书调用一个委托。所使用的 delegate 类型称为 ProcessBookDelegateTest 类使用该类输出平装书的书名和平均价格。

委托的使用促进了书店数据库和客户代码之间功能的良好分隔。客户代码不知道书籍的存储方式和书店代码查找平装书的方式。书店代码也不知道找到平装书后将对平装书进行什么处理

 

// Declare a delegate type for processing a book:
public delegate void ProcessBookDelegate(Book book);

// Maintains a book database.
public class BookDB
{
/// <summary>
/// Call a passed-in delegate on each paperback book to process it:
/// </summary>
/// <param name="processBook">以委托ProcessBookDelegate为参数类型</param>
public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
{
}
  }

将静态方法或实例方法绑定到委托

// Class to total and average prices of books:
class PriceTotaller
{
/// <summary>
/// 实例方法 AddBookToTotal
/// </summary>
/// <param name="book"></param>
internal void AddBookToTotal(Book book)
{。。。}
}

// Class to test the book database:
class TestBookDB
{
// Print the title of the book.
/// <summary>
/// 静态方法 PrintTitle
/// </summary>
/// <param name="b"></param>
static void PrintTitle(Book b)
{。。。 }

// Execution starts here.
static void Main()
{
BookDB bookDB
= new 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:
// 委托封装静态方法 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:
//委托封装实例方法totaller.AddBookToTotal
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

System.Console.WriteLine(
"Average Paperback Book Price: ${0:#.##}",
totaller.AveragePrice());
}
}

 

  • 实例化委托。

    声明了委托类型后,必须创建委托对象并使之与特定方法关联。在上面的示例中,这是通过将 PrintTitle 方法传递给 ProcessPaperbackBooks 方法来完成的,如下所示:

    bookDB.ProcessPaperbackBooks(PrintTitle);

     

    这将创建与静态方法 Test.PrintTitle 关联的新委托对象。类似地,对象 totaller 的非静态方法 AddBookToTotal 是按如下方式传递的:

    bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

    在两个示例中,都向 ProcessPaperbackBooks 方法传递了一个新的委托对象。

    委托一旦创建,它的关联方法就不能更改;委托对象是不可变的。

  • 调用委托。

    创建委托对象后,通常将委托对象传递给将调用该委托的其他代码。通过委托对象的名称(后面跟着要传递给委托的参数,括在括号内)调用委托对象。下面是委托调用的示例:

    processBook(b);
  • 示例代码

   https://files.cnblogs.com/jiahello/DelegateDemo.rar

 

合并委托(多路广播委托)

本示例演示如何组合多路广播委托。委托对象的一个用途在于,可以使用 + 运算符将它们分配给一个要成为多路广播委托的委托实例。组合的委托可调用组成它的那两个委托。只有相同类型的委托才可以组合。

-运算符可用来从组合的委托移除组件委托。

 

ProcessBookDelegate del = PrintTitle;
del
+= totaller.AddBookToTotal;

微软官网示例:

 

 

delegate void Del(string s);

class TestClass
{
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 b that references
// the method Goodbye:
b = 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;

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");
}
}

 

输出

Invoking delegate a:
Hello, A!
Invoking delegate b:
Goodbye, B!
Invoking delegate c:
Hello, C!
Goodbye, C!
Invoking delegate d:
Goodbye, D!

命名方法和匿名方法

在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法。C# 2.0 引入了匿名方法。

要将代码块传递为委托参数,创建匿名方法则是唯一的方法。例如:

 

// Create a handler for a click event
button1.Click += delegate(System.Object o, System.EventArgs e)
{ System.Windows.Forms.MessageBox.Show(
"Click!"); };

// Create a delegate instance
delegate void Del(int x);

// Instantiate the delegate using an anonymous method
Del d = delegate(int k) { /* ... */ };
如果使用匿名方法,则不必创建单独的方法,因此减少了实例化委托所需的编码系统开销。

例如,如果创建方法所需的系统开销是不必要的,在委托的位置指定代码块就非常有用。启动新线程即是一个很好的示例。无需为委托创建更多方法,线程类即可创建一个线程并且包含该线程执行的代码。

void StartThread()
{
System.Threading.Thread t1 = new System.Threading.Thread
(delegate()
{
System.Console.Write("Hello, ");
System.Console.WriteLine("World!");
});
t1.Start();
}
备注 匿名方法的参数的范围是 anonymous-method-block
在目标在块外部的匿名方法块内使用跳转语句(如 gotobreakcontinue)是错误的。在目标在块内部的匿名方法块外部使用跳转语句(如 gotobreakcontinue)也是错误的。

如果局部变量和参数的范围包含匿名方法声明,则该局部变量和参数称为该匿名方法的外部变量或捕获变量。例如,下面代码段中的 n 即是一个外部变量:

int n = 0;
Del d
= delegate() { System.Console.WriteLine("Copy #:{0}", ++n); };

 

与局部变量不同,外部变量的生命周期一直持续到引用该匿名方法的委托符合垃圾回收的条件为止。对 n 的引用是在创建该委托时捕获的。

匿名方法不能访问外部范围的 refout 参数。

anonymous-method-block 中不能访问任何不安全代码。

示例

下面的示例演示实例化委托的两种方法:

  • 使委托与匿名方法关联。
  • 使委托与命名方法 (DoWork) 关联。

两种方法都会在调用委托时显示一条消息。

// Declare a delegate
delegate void Printer(string s);

class TestClass
{
static void Main()
{
// Instatiate the delegate type using an anonymous method:
Printer p = delegate(string j)
{
System.Console.WriteLine(j);
};

// Results from the anonymous delegate call:
p("The delegate using the anonymous method is called.");

// The delegate instantiation using a named method "DoWork":
p = new Printer(TestClass.DoWork);

// Results from the old style delegate call:
p("The delegate using the named method is called.");
}

// The method associated with the named delegate:
static void DoWork(string k)
{
System.Console.WriteLine(k);
}
}
输出

The delegate using the anonymous method is called.

The delegate using the named method is called.


posted @ 2010-06-04 13:42  禅酷  阅读(317)  评论(0编辑  收藏  举报