初识委托
委托的作用:Microsoft .NET Framework通过委托来提供回调函数机制。C和C++程序员长期以来利用"函数指针"将对方法的引用作为实参传给另一个方法。C#使用委托提供相似的功能,委托允许捕捉对方法的引用。
namespace delegation.simple
{
// 定义一个委托
internal delegate int Feedback(int value);
class Program
{
// 委托方法1
private static int FeedbackMethod1(int value)
{
Console.WriteLine("执行了委托方法1");
return value;
}
// 委托方法2
private int FeedbackMethod2(int value)
{
Console.WriteLine("执行了委托方法2");
return value;
}
private static int Counter(int from, int to, Feedback fb)
{
int result = 0;
for (int val = from; val <= to; val++)
{
if (fb != null)
{
result += fb(val);//等价于fb.Invoke(val);
}
}
return result; //result=6
}
// 为委托回调静态方法
private static void StaticDelegateDemo()
{
Console.WriteLine("---Static Delegate Demo ---");
int result = Counter(1, 3, new Feedback(Program.FeedbackMethod1));
Console.WriteLine(result);
}
// 为委托回调实例方法
private static void InstanceDelegateDemo()
{
Console.WriteLine("---Instance Delegate Demo ---");
Program p = new Program();
int result = Counter(1, 3, new Feedback(p.FeedbackMethod2));
Console.WriteLine(result);
}
// 委托链
private static void ChainDelegateDemo1(Program p)
{
Console.WriteLine("---Chain Delegate Demo ---");
Feedback fb1 = new Feedback(FeedbackMethod1);
Feedback fb2 = new Feedback(p.FeedbackMethod2);
// fbChain相当于一个委托对象集合
Feedback fbChain = null;
fbChain += fb1;
fbChain += fb2;
int result1 = Counter(1, 3, fbChain);
Console.WriteLine();
fbChain -= new Feedback(FeedbackMethod1);
int result2 = Counter(1, 3, fbChain);
Console.WriteLine("{0} and {1}", result1, result2);
}
static void Main(string[] args)
{
StaticDelegateDemo();
InstanceDelegateDemo();
ChainDelegateDemo1(new Program());
}
}
}
用委托回调静态方法
请看看StaticDelegateDemo()
用委托回调实例方法
请看看InstanceDelegateDemo()
委托链
上例运行结果:
来自《CLR via C# 第4版》:
所以当我执行了6次方法,运算结果还是6。
委托协变性和逆变性
协变性是指方法能返回从委托的返回类型派生的一个类型。
逆变性是指方法获取的参数可以是委托的参数类型的基类。
例如下面这个委托:
delegate Object MyCallback(FileStream s);
完全可以构造该委托类型的一个实例并绑定具有以下原型的方法:
String SomeMethod(Stream s);
其中返回类型(String)派生自委托的返回类型(Object)。
参数类型(Stream)是委托的参数类型(FileStream)的基类。
用委托回调多个方法(委托链)
请查看ChainDelegateDemo1();委托链是委托对象的集合。fb(val)等价于fb.Invoke(val),Invoke方法会执行一个循环来遍历数组中的所有元素,并依次调用每个委托包装的方法。
委托的局限性
在委托链中,Invoke方法包含了对数组中的所有项进行遍历的代码。但除了最后一个返回值,其他所有回调方法的返回值都会被丢弃。如果被调用的委托中有一个抛出了异常或阻塞了相当长一段时间,则链中后续的所有对象都调用不了。
取得对委托链调用的更多控制
为了解决这个算法有时候不胜其任,可以用GetInvocationList实例方法,用于显式调用链中的每一个委托,并允许你使用需要的任何算法。
可以很容易地写一个算法来显式调用数组中每个对象。以下代码进行了演示:
internal sealed class Light{
public String SwitchPosition() {
return "The light is off";
}
}
internal sealed class Fan {
public String Speed() {
throw new InvalidOperationException("The fan broke due to overheating");
}
}
internal sealed class Speaker {
public String Volume() {
return "The volume is loud";
}
}
class Program
{
//定义委托来查询一个组件的状态
private delegate String GetStatus();
static void Main(string[] args)
{
GetStatus getStatus = null;
getStatus += new GetStatus(new Light().SwitchPosition);
getStatus += new GetStatus(new Fan().Speed);
getStatus += new GetStatus(new Speaker().Volume);
//显示整理好的状态报告,反映这3个组件的状态
Console.WriteLine(GetComponentStatusReport(getStatus));
Console.ReadLine();
}
private static String GetComponentStatusReport(GetStatus status) {
if(status == null) { return null; }
StringBuilder report = new StringBuilder();
//获得一个数组,其中每个元素都是链中的委托
Delegate[] delegates = status.GetInvocationList ();
//显示的遍历数组中的每一个委托
foreach (GetStatus getStatus in delegates) {
try {
report.AppendFormat("{0}{1}{1}", getStatus.Invoke(),Environment.NewLine);
} catch (Exception e) {
Object component = getStatus.Target;
report.AppendFormat("Failed to get status from {1}{2}{0} Error:{3}{0}{0}",
Environment.NewLine,
((component == null)? "" : component.GetType() + "."),
getStatus.Method.Name,e.Message);
}
}
return report.ToString();
}
}
运行代码,结果输出:
委托定义不要太多(泛型委托)
.NET Framework提供了17个Action委托:
public delegate void Action();
public delegate void Action<T>(T obj);
public delegate void Action<T1, T2>(T1 arg1, T2 arg2);
......
还提供了17个Func函数,允许回调返回返回值:
public delegate TResult Func<TResult>();
public delegate TResult Func<T, TResult>(T arg);
......
建议尽量使用这些委托类型,而不是在代码中定义更多的委托类型。这样可减少系统中的类型数量,同时简化代码。
然而,如需使用ref或out关键字以传引用的方法传递参数,就可能不得不定义自己的委托:
delegate void Bar(ref int z);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。