委托是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法
一样,具有参数和返回值,如下面的示例所示:
public delegate int PerformCalculation(int x, int y);
与委托的签名(由返回类型和参数组成)匹配的任何方法都可以分配给该委托。这样就可以通过编程方式来更改方法调用,还可以
向现有类中插入新代码。只要知道委托的签名,便可以分配自己的委托方法。
将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。
一、概述
构造委托对象时,通常提供委托将包装的方法的名称或使用匿名方法。实例化委托后,委托将把对它进行的方法调用传递给方法。
调用方传递给委托的参数被传递给方法,来自方法的返回值(如果有)由委托返回给调用方。这被称为调用委托。
委托类型派生自 Delegate 类。委托类型是密封的,不能从 Delegate 中派生委托类型,也不可能从中派生自定义类。由于实例化
委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。这样,方法便可以将一个委托作为参数
来接受,并且以后可以调用该委托。这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。
调用委托时,它可以调用多个方法。这称为多路广播。若要向委托的方法列表(调用列表)中添加额外的方法,
只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。若要从调用列表中移除方法,
请使用减法运算符或减法赋值运算符(“-”或“-=”)。
二、命名方法
委托可以与命名方法关联。使用命名的方法对委托进行实例化时,该方法将作为参数传递,例如:
delegate void Del(int x);
void DoWork(int k) { /* ... */ }
Del d = obj.DoWork;
这被称为使用命名的方法。使用命名方法构造的委托可以封装静态方法或实例方法。在以前的 C# 版本中,
使用命名的方法是对委托进行实例化的唯一方式。但是,在创建新方法的系统开销不必要时,C# 2.0 允许您对委托进行实例化,
并立即指定委托将在被调用时处理的代码块。这些被称为匿名方法。
备注
作为委托参数传递的方法必须与委托声明具有相同的签名。
委托实例可以封装静态或实例方法。
尽管委托可以使用 out 参数,但不推荐将其用于多路广播事件委托中,因为无法确定要调用哪个委托。
示例 1
以下是声明及使用委托的一个简单示例。注意,委托 Del 和关联的方法 MultiplyNumbers 具有相同的签名
delegate void Del(int i, double j);
class MathClass
{
static void Main()
{
MathClass m = new MathClass();
Del d = m.MultiplyNumbers;
System.Console.WriteLine("Invoking the delegate using 'MultiplyNumbers':");
for (int i = 1; i <= 5; i++)
{
d(i, 2);
}
}
void MultiplyNumbers(int m, double n)
{
System.Console.Write(m * n + " ");
}
}
输出
Invoking the delegate using 'MultiplyNumbers':
2 4 6 8 10
示例 2
在下面的示例中,一个委托被同时映射到静态方法和实例方法,并分别返回特定的信息。
delegate void Del();
class SampleClass
{
public void InstanceMethod()
{
System.Console.WriteLine("A message from the instance method.");
}
static public void StaticMethod()
{
System.Console.WriteLine("A message from the static method.");
}
}
class TestSampleClass
{
static void Main()
{
SampleClass sc = new SampleClass();
Del d = sc.InstanceMethod;
d();
d = SampleClass.StaticMethod;
d();
}
}
输出
A message from the instance method.
A message from the static method.
三、匿名方法
要将代码块传递为委托参数,创建匿名方法则是唯一的方法。例如:
button1.Click += delegate(System.Object o, System.EventArgs e)
{ System.Windows.Forms.MessageBox.Show("Click!"); };
或
delegate void Del(int x);
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();
}
示例
下面的示例演示实例化委托的两种方法:
使委托与匿名方法关联。
使委托与命名方法 (DoWork) 关联。
两种方法都会在调用委托时显示一条消息。
delegate void Printer(string s);
class TestClass
{
static void Main()
{
Printer p = delegate(string j)
{
System.Console.WriteLine(j);
};
p("The delegate using the anonymous method is called.");
p = new Printer(TestClass.DoWork);
p("The delegate using the named method is called.");
}
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.
四、委托中的协变和逆变
将委托方法与委托签名匹配时,协变和逆变提供了一定程度的灵活性。协变允许将带有
派生返回类型的方法用作委托,逆变允许将带有派生参数的方法用作委托。这使委托方法的
创建变得更为灵活,并能够处理多个特定的类或事件。
协变
当委托方法的返回类型具有的派生程度比委托签名更大时,就称为协变委托方法。因为
方法的返回类型比委托签名的返回类型更具体,所以可对其进行隐式转换。这样该方法就可
用作委托。
协变使得创建可被类和派生类同时使用的委托方法成为可能。
示例 1
class Mammals
{
}
class Dogs : Mammals
{
}
class Program
{
public delegate Mammals HandlerMethod();
public static Mammals FirstHandler()
{
return null;
}
public static Dogs SecondHandler()
{
return null;
}
static void Main()
{
HandlerMethod handler1 = FirstHandler;
HandlerMethod handler2 = SecondHandler;
}
}
说明
此示例演示委托如何使用派生的返回类型。SecondHandler 返回的数据类型为 Dogs 类型,是委托签名返回类型 Mammals 的子集。因此,SecondHandler 返回的所有可能值都可以存储在 Mammals 类型的变量中。
逆变
当委托方法签名具有一个或多个参数,并且这些参数的类型派生自方法参数的类型时,就称为逆变委托方法。因为委托方法签名参数比方法参数更具体,因此可以在传递给处理程序方法时对它们进行隐式转换。
这样逆变使得可由大量类使用的更通用的委托方法的创建变得更加简单。
示例 2
说明
此示例演示委托如何使用从委托签名参数类型派生的类型的参数。因为要求委托支持 Dogs 类型的参数,我们知道带有 Mammals 类型的参数的处理程序一定是可接受的,因为 Dogs 是 Mammals 的子集。
class Mammals
{
}
class Dogs : Mammals
{
}
class Program
{
public delegate void HandlerMethod(Dogs sampleDog);
public static void FirstHandler(Mammals elephant)
{
}
public static void SecondHandler(Dogs sheepDog)
{
}
static void Main(string[] args)
{
HandlerMethod handler1 = FirstHandler;
HandlerMethod handler2 = SecondHandler;
}
}