在学习委托之前我们首先看看一个简单的类,在这个类中定义了一个计算两数之和的方法,如下所示。
public class Sumclass
{
public int Sum(int a, int b)
{
return a + b;
}
}
class MainClass
{
static void Main()
{
Sumclass example = new Sumclass();
int sum=example.Sum(1,2);
System.Console.WriteLine("{0}", sum);
}
}
在上面的示例中,当你在实例上调用方法并为方法传递了正确的参数后,就会有正确的输出。
什么是委托?
通常当我们没有时间去完成一件事情的时候,可以委托给另一个人去做,另一个人就是我们的代理。同样的道理当一个类中的方法调用需要让别人执行时就要创建委托。
==========================
一 使用委托
==========================
★ 委托的概念
委托是一种引用方法的类型,或者说委托是一种安全地封装方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。
上面的定义包含了三个要点。
1 委托是一种数据类型。
2 委托能够引用方法,或者说委托封装方法并代理它。
3 委托是安全的。
★ 声明委托
声明委托类型请使用delegate关键字,如下;
public delegate void myDelegate(string message);
说明:
上式中声明了一个委托myDelegate,这个委托的返回值为void类型,只有一个参数,参数的类型及数量统称为参数列表。返回类型与参数列表合称为签名,当使用委托代理方法时委托的签名必须与方法的签名一致。
示例
public delegate int myDelegate(int a, int b);
public class Sumclass
{
public int Sum(int a, int b)
{
return a + b;
}
}
class MainClass
{
static void Main()
{
Sumclass A = new Sumclass();
//实例化委托并向其分配方法
myDelegate B = A.Sum;
//调用委托并将返回值保存到变量sum中
int sum = B(1, 2);
System.Console.WriteLine("{0}", sum);
}
}
示例解读:
示例为类Sumclass定义了一个委托,注意其签名与类中的方法签名要一致,在用于输出的类中创建该类的一个实例,然后将实例方法分配给委托实例B,这时委托实例B就完全可以替代类中的方法了。
★实例化委托
C#2.0 允许我们在进行委托实例化时,省略掉委托类型,而直接采用方法名,C#编译器会做出合理的推断。
C# 1.0中实例化委托时要用关键字New,例如上例中委托实例应该这样来写:
Mydelegate B=New Mydelegate(A.Sum)
C# 2.0中直接采用方法名,如上例中
myDelegate B = A.Sum
注意:用委托代理方法时,方法名的后边一定不能带有圆括号()。
示例
public delegate void Del(string message);
public class myclass
{
public static void DelegateMethod(string message)
{
System.Console.WriteLine(message);
}
static void Main()
{
Del handler = DelegateMethod;
handler("Hello World");
}
}
示例
public delegate void Del(string message);
public class myclass
{
public static void DelegateMethod(string message)
{
System.Console.WriteLine(message);
}
}
class MainClass
{
static void Main()
{
Del handler = myclass.DelegateMethod;
handler("Hello World");
}
}
★ 异步回调
委托类型派生自 .NET Framework 中的 Delegate 类。默认是密封的,不能从其中再派生新的委托类型,也不可能从中派生自定义类。由于实例化委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。这样,方法便可以将一个委托作为参数来接受,并且以后可以调用该委托。这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。以这种方式使用委托时,使用委托的代码无需了解有关所用方法的实现方面的任何信息。此功能类似于接口所提供的封装。
示例:
public delegate void myD(string message);
public class aClass
{
public void aMethod(string message)
{
System.Console.WriteLine(message);
}
public void bMethod(myD s)
{
s("ok! delegate");
}
}
public class textclass
{
static void Main()
{
aClass A = new aClass();
myD B = A.aMethod;
B("Hello World");
A.bMethod(B);
}
}
回调的另一个常见用法是定义自定义的比较方法并将该委托传递给排序方法。它允许调用方的代码成为排序算法的一部分。
示例:
public delegate void myDelegate(string message);
public class DelClass
{
public void DelegateMethod(string message)
{
System.Console.WriteLine(message);
}
public void MethodWithCallback(int param1, int param2, myDelegate callback)
{
callback("The number is: " + (param1 + param2).ToString());
}
}
public class textclass
{
static void Main()
{
DelClass obj = new DelClass();
myDelegate d = obj.DelegateMethod;
d("Hello World");
obj.MethodWithCallback(1,2,d);
}
}
说明:
在控制台中将收到下面的输出:The number is: 3 ,示例中
MethodWithCallback 的作用只是准备字符串并将该字符串传递给其他方法。此功能特别强大,因为委托的方法可以使用任意数量的参数。
★ 委托特点:
1 委托类似于 C++ 函数指针,但它是类型安全的。
2 委托允许将方法作为参数进行传递。
3 委托可用于定义回调方法。
4 委托可以链接在一起;例如,可以对一个事件调用多个方法。
5 方法不需要与委托签名精确匹配。(在协变和逆变里将讲到)
6 C# 2.0 版引入了匿名方法的概念,此类方法允许将代码块作为参数传递,以代替单独定义的方法。
一个委托delegate对象一次可以搭载多个方法,而不是一次一个。当我们唤起一个搭载了多个方法的委托,所有方法将以其被搭载到委托对象的顺序被依次唤起。一个委托delegate对象搭载的方法不需要属于同一个类,但它搭载的所有方法必须具有相同的签名。
==========================
二 命名方法
==========================
委托可以与命名方法关联。使用命名的方法对委托进行实例化时,该方法将作为参数传递,例如:
delegate void Del(int x);
void DoWork(int k) { /* ... */ }
Del d = obj.DoWork;
使用命名方法构造的委托可以封装静态方法或实例方法。作为委托参数传递的方法必须与委托声明具有相同的签名(由返回类型和参数组成)。委托实例可以封装静态或实例方法。
示例 1
以下是声明及使用委托的一个简单示例。注意,委托 Del 和关联的方法 MyNumbers 具有相同的签名
delegate void Del(int i, double j);
class MathClass
{
static void Main()
{
MathClass m = new MathClass();
Del d = m.MyNumbers;
System.Console.WriteLine("MyNumbers:");
for (int i = 1; i <= 5; i++)
{
d(i, 2);
}
}
void MyNumbers(int m, double n)
{
System.Console.Write(m * n + " ");
}
}
示例 2
在下面的示例中,一个委托被同时映射到静态方法和实例方法,并分别返回特定的信息。
delegate void Del();
class SampleClass
{
public void InstanceMethod()
{
System.Console.WriteLine("instance method.");
}
public static void StaticMethod()
{
System.Console.WriteLine("static method.");
}
}
class TestSampleClass
{
static void Main()
{
SampleClass sc = new SampleClass();
Del d = sc.InstanceMethod;
d();
d = SampleClass.StaticMethod;
d();
}
}
==========================
三 匿名方法
==========================
在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法。C# 2.0 引入了匿名方法。匿名方法允许我们以一种“内联”的方式来编写方法代码,将代码直接与委托实例相关联,从而使得委托实例化的工作更加直观和方便。创建匿名方法的语法如下:
delegate void Del(int x);
Del d = delegate(int k) { /* 代码块 */ };
当你要将代码块传递为委托参数时,创建匿名方法则是唯一的方法。使用匿名方法的好处是,不必创建单独的方法,因此减少了实例化委托所需的编码系统开销。注意大括号后边要加分号。
示例
下面的示例演示实例化委托的两种方法:使委托与匿名方法关联。使委托与命名方法 (DoWork) 关联。两种方法都会在调用委托时显示一条消息。
delegate void Printer(string s);
class TestClass
{
static void Main()
{
Printer p = delegate(string j)
{
System.Console.WriteLine(j);
};
p("a_ called.");
Printer S = new Printer(TestClass.DoWork);
S("b_called.");
}
static void DoWork(string k)
{
System.Console.WriteLine(k);
}
}
★匿名方法的几个相关问题:
1匿名方法的参数列表
·匿名方法可以在delegate关键字后跟一个参数列表(可以不指定),后面的代码块则可以访问这些参数:
Del d = delegate(int k) { /* 代码块 */ };
·注意“不指定参数列表”与“参数列表为空”的区别
Del d = delegate{ /* 代码块 */ }; // 正确!
Del d = delegate() { /* 代码块 */ };// 错误!
2 匿名方法的返回值
·如果委托类型的返回值类型为void,匿名方法里
便不能返回任何值;
delegate void MyDelegate();
MyDelegate d = delegate{
……
return;
};
·如果委托类型的返回值类型不为void,匿名方法
里返回的值必须和委托类型的返回值兼容:
delegate int MyDelegate();
MyDelegate d = delegate{
……
return 100;
};
3 匿名方法的外部变量
·一些局部变量和参数有可能被匿名方法所使用,
它们被称为“匿名方法的外部变量”。
·外部变量的生存期会由于“匿名方法的捕获效益”
而延长——一直延长到委托实例不被引用为止。
static void Foo(double factor) {
MyDelegate f=delegate(int x) {
factor +=0. 2; // factor为外部变量
return x * factor;
};
Invoke(f); // factor的生存期被延长
}
==========================
四 多路广播委托
==========================
委托对象的一个用途在于,可以使用 +或+= 运算符将它们分配给一个要成为多路广播委托的委托实例。组合的委托可调用组成它的那两个委托。只有相同类型的委托才可以组合。- 运算符可用来从组合的委托移除组件委托。
示例
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;
a = Hello;
b = Goodbye;
c = a + b;
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");
}
}
==========================
五 协变和逆变
==========================
将委托方法与委托签名匹配时,协变和逆变提供了一定程度的灵活性。协变允许将带有派生返回类型的方法用作委托,逆变允许将带有派生参数的方法用作委托。这使委托方法的创建变得更为灵活,并能够处理多个特定的类或事件。
★协变
当委托方法的返回类型具有的派生程度比委托签名更大时,就称为协变委托方法。因为方法的返回类型比委托签名的返回类型更具体,所以可对其进行隐式转换。这样该方法就可用作委托。协变使得创建可被类和派生类同时使用的委托方法成为可能。
示例 1
此示例演示委托如何使用派生的返回类型。SecondHandler 返回的数据类型为 Dogs 类型,是委托签名返回类型 Mammals 的子集。因此,SecondHandler 返回的所有可能值都可以存储在 Mammals 类型的变量中。
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;
}
}
示例解读:
1 创建两个类Mammals和Dogs,其中Dogs继承Mammals。
2 在类Program中定义了一个委托HandlerMethod,返回类型为Mammals
,在这个类中还定义了两个方法,其中FirstHandler的签名和委托是一致的,可以认为是我们前面讲过的签名方法。而SecondHandler方法与委托签名不一致,其返回类型Dogs是继承Mammals的,也就是说其返回类型具有的派生程度比委托签名更大。更具体,所以SecondHandler是协变委托方法,所以在将这个方法分配给委托实例时可以对其进行隐式转换。式子HandlerMethod handler2 = SecondHandler,中的Dogs类型方法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;
}
}
==========================
六 使用委托与使用接口
==========================
委托和接口都允许类设计器分离类型声明和实现。接口和委托有很多相似性,它们都可由不了解实现该接口或委托方法的类的对象使用。
★在以下情况中使用委托:
1 当使用事件设计模式时。
2 当封装静态方法可取时。
3 当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。
4 需要方便的组合。
5 当类可能需要该方法的多个实现时。
★在以下情况中使用接口:
1 当存在一组可能被调用的相关方法时。
2 当类只需要方法的单个实现时。
3 当使用接口的类想要将该接口强制转换为其他接口或类类型时。
4 当正在实现的方法链接到类的类型或标识时。