C#中的delegate 委托(转载)
在c#中,event与delegate是两个非常重要的概念。因为在Windows应用程序中,对事件的使用非常频繁,而事件的实现依赖于delegate。
下面是对网上一些比较好的关于delegage的资料的整理,以及自己的一些想法。
委托概述
委托是一种安全地封装方法的类型,它与 C 和 C++ 中的函数指针类似。与 C 中的函数指针不同,委托是面向对象的、类型安全的和保险的。委托的类型由委托的名称定义。
一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值,如下面的示例所示:
public delegate void Del(string message);
与委托的签名(由返回类型和参数组成)匹配的任何方法都可以分配给该委托。这样就可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。只要知道委托的签名,便可以分配自己的委托方法。
调用委托
构造委托对象时,通常提供委托将包装的方法的名称或使用匿名方法。实例化委托后,委托将把对它进行的方法调用传递给方法。调用方传递给委托的参数被传递给方法,来自方法的返回值(如果有)由委托返回给调用方。这被称为调用委托。可以将一个实例化的委托视为被包装的方法本身来调用该委托。例如:
// Create a method for a delegate.
public static void DelegateMethod(string message)
{
System.Console.WriteLine(message);
}
// Instantiate the delegate.
Del handler = DelegateMethod;
// Call the delegate.
handler("Hello World");
将委托构造为包装实例方法时,该委托将同时引用实例和方法。除了它所包装的方法外,委托不了解实例类型,所以只要任意类型的对象中具有与委托签名相匹配的方法,委托就可以引用该对象。将委托构造为包装静态方法时,它只引用方法。
回调
由于实例化委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。这样,方法便可以将一个委托作为参数来接受,并且以后可以调用该委托。这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。以这种方式使用委托时,使用委托的代码无需了解有关所用方法的实现方面的任何信息。此功能类似于接口所提供的封装。
回调的另一个常见用法是定义自定义的比较方法并将该委托传递给排序方法。它允许调用方的代码成为排序算法的一部分。下面的示例方法使用 Del 类型作为参数:
public void MethodWithCallback(int param1, int param2, Del callback)
{
callback("The number is: " + (param1 + param2).ToString());
}
然后可以将上面创建的委托传递给该方法:
MethodWithCallback(1, 2, handler);
在控制台中将收到下面的输出:
The number is: 3
使用委托的好处
委托允许类设计器分离类型声明和实现。
在将委托用作抽象概念时,MethodWithCallback 不需要直接调用控制台 -- 设计它时无需考虑控制台。MethodWithCallback 的作用只是准备字符串并将该字符串传递给其他方法。此功能特别强大,因为委托的方法可以使用任意数量的参数。
将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。例如,可以向排序算法传递对比较两个对象的方法的引用。分离比较代码使得可以采用更通用的方式编写算法。
如何使用委托
1. 声明委托
声明一个新的委托类型。每个委托类型都描述参数的数目和类型,以及它可以封装的方法的返回值类型。每当需要一组新的参数类型或新的返回值类型时,都必须声明一个新的委托类型。
2. 实例化委托
声明了委托类型后,必须创建委托对象并使之与特定方法关联。方法的签名应与委托定义的签名一致。
委托对象可以关联静态方法,也可以关联非静态方法。
委托一旦创建,它的关联方法就不能更改;委托对象是不可变的。
3. 调用委托
创建委托对象后,通常将委托对象传递给将调用该委托的其他代码。通过委托对象的名称(后面跟着要传递给委托的参数,括在括号内)调用委托对象。
下面是一个示例:
using System;
public class SamplesDelegate
{
// Declares a delegate for a method that takes in an int and returns a String.
public delegate String myMethodDelegate(int myInt);
// Defines some methods to which the delegate can point.
public class mySampleClass
{
// Defines an instance method.
public String myStringMethod(int myInt)
{
if (myInt > 0)
return ("positive");
if (myInt < 0)
return ("negative");
return ("zero");
}
// Defines a static method.
public static String mySignMethod(int myInt)
{
if (myInt > 0)
return ("+");
if (myInt < 0)
return ("-");
return ("");
}
}
public static void Main()
{
// Creates one delegate for each method.
mySampleClass mySC = new mySampleClass();
myMethodDelegate myD1 = new myMethodDelegate(mySC.myStringMethod);
myMethodDelegate myD2 = new myMethodDelegate(mySampleClass.mySignMethod);
// Invokes the delegates.
Console.WriteLine("{0} is {1}; use the sign /"{2}/".", 5, myD1(5), myD2(5));
Console.WriteLine("{0} is {1}; use the sign /"{2}/".", -3, myD1(-3), myD2(-3));
Console.WriteLine("{0} is {1}; use the sign /"{2}/".", 0, myD1(0), myD2(0));
}
}
/*
This code produces the following output:
5 is positive; use the sign "+".
-3 is negative; use the sign "-".
0 is zero; use the sign "".
*/
多路广播
调用委托时,它可以调用多个方法。这称为多路广播。若要向委托的方法列表(调用列表)中添加额外的方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。例如:
MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;
//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;
此时,allMethodsDelegate 在其调用列表中包含三个方法 -- Method1、Method2 和 DelegateMethod。原来的三个委托 d1、d2 和 d3 保持不变。调用 allMethodsDelegate 时,将按顺序调用所有这三个方法。如果委托使用引用参数,则引用将依次传递给三个方法中的每个方法,由一个方法引起的更改对下一个方法是可见的。如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。如果委托具有返回值和/或输出参数,它将返回最后调用的方法的返回值和参数。若要从调用列表中移除方法,请使用减法运算符或减法赋值运算符(“-”或“-=”)。例如:
//remove Method1
allMethodsDelegate -= d1;
// copy AllMethodsDelegate while removing d2
Del oneMethodDelegate = allMethodsDelegate - d2;
多路广播委托广泛用于事件处理中。事件源对象向已注册接收该事件的接收方对象发送事件通知。为了为事件注册,接收方创建了旨在处理事件的方法,然后为该方法创建委托并将该委托传递给事件源。事件发生时,源将调用委托。然后,委托调用接收方的事件处理方法并传送事件数据。给定事件的委托类型由事件源定义。
本示例演示如何组合多路广播委托。委托对象的一个用途在于,可以使用 + 运算符将它们分配给一个要成为多路广播委托的委托实例。组合的委托可调用组成它的那两个委托。只有相同类型的委托才可以组合。
运算符可用来从组合的委托移除组件委托。
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");
}
}
他人对Delegate的总结
下面是sam1111在他的博客文章《C#中的delegate和event》中对delegate的总结,觉得很好。
文章网址:http://blog.csdn.net/sam1111/archive/2002/04/15/9773.aspx
delegate是C#中的一种类型,它实际上是一个能够持有对某个方法的引用的类。与其它的类不同,delegate类能够拥有一个签名(signature),并且它只能持有与它的签名相匹配的方法的引用。它所实现的功能与C/C++中的函数指针十分相似。它允许传递一个类A的方法m给另一个类B的对象,使得类B的对象能够调用这个方法m。但与函数指针相比,delegate有许多函数指针不具备的优点。首先,函数指针只能指向静态函数,而delegate既可以引用静态函数,又可以引用非静态成员函数。在引用非静态成员函数时,delegate不但保存了对此函数入口指针的引用,而且还保存了调用此函数的类实例的引用。其次,与函数指针相比,delegate是面向对象、类型安全、可靠的受控(managed)对象。也就是说,runtime能够保证delegate指向一个有效的方法,你无须担心delegate会指向无效地址或者越界地址。
自己的理解
“delegate是C#中的一种类型。”delegate与class是类似的,class定义一种类型,delegate也定义一种类型。class可以定义各种各样的类,如classA、classB,而delegate可以定义各种各样的代理,如delegate1,delegate2。与class不同的是,delegate的定义没有字段、属性、方法等,只有签名(返回值及参数)。
“它实际上是一个能够持有对某个方法的引用的类。”delegate对象可以持有对某个方法的引用,这个方法的签名必须与代理类型的签名一致(这就是“delegate定义回调方法的接口”这一说法的原由)。代理对象持有对这个方法的引用,当调用代理对象时,即实现对这个方法的调用。之所以能通过调用代理对象来实现对方法的调用,是因为在实例化代理对象时,把传入方法的地址赋给了代理对象,使得当调用代理对象时,内存中的指令指针即指向传入方法的入口,执行传入方法的方法体。
利用代理来实现多路广播时,即把多个方法的引用(即内存地址)保存到代理的方法引用队列。调用代理对象时,根据代理对象的方法引用队列,内存中的指令指针即逐个指向每个方法的入口,按次序执行每个方法的方法体。
事件的发布与订阅。所谓事件,就是指当某个特定的事情发生时,类或对象通过事件通知关注此事情的类或对象。发送(或引发)事件的类称为“发行者”,接收(或处理)事件的类称为“订户”。事件的实现是依赖于代理的。事件的实现原理即,订户把响应事件的方法传递给发行者,当特定的事情发生时,发行者能够调用这些响应事件的方法。在代码级的实现即,发行者定义一个delegate类型,提供一个public的delegate对象作为字段或属性;订户(可以是多个)通过将响应事件的方法传递给delegate类型来实例化一个delegate对象,并通过+=运算符,将delegate对象赋值给发行者的delegate对象,实际上就是多路广播。当特定的事情发生时,发行者调用delegate对象,即调用所有订户的响应事件的方法。