C#委托(Delegate)学习日记
在.NET平台下,委托类型用来定义和响应应用程序中的回调。事实上,.NET委托类型是一个类型安全的对象,指向可以以后调用的其他方法。和传统的C++函数指针不同,.NET委托是内置支持多路广播和异步方法调用的对象。
委托类型包含3个重要信息:
- 它所调用的方法的名称
- 该方法的参数
- 该方法的返回值
1.定义一个委托类型
// 这个委托可以指向任何传入两个整数,返回整数的方法
public delegate int BinaryOp(int x,int y);
创建一个委托类型时,需要使用delegate关键字。当C#编译器处理委托类型时,它先自动产生一个派生自System.MulticastDelegate的密封类。
通过ildasm.exe来查看BinaryOp委托,如下图

编译器是如何确切的定义 Invoke(),BeginInvoke(),EndInvoke() 的呢?让我们看看下面这段代码:
sealed class BinaryOp : System.MulticastDelegate
{
public int Invoke(int x,int y); public IAsyncResult BeginInvoke(int x,int y,AsyncCallback cb,object state); public int EndInvoke(IasyncResult result);
}
Invoke() 方法定义的参数和返回值完全匹配BinaryOp委托的定义。
BeginInvoke() 成员签名的参数也基于BinaryOp委托;但BenginInvoke()方法将总是提供最后两个参数(AsyncCallback,object),用于异步方法的调用。
EndInvoke() 方法的返回值与初始的委托声明相同,总是以一个实现了IasyncResult接口的对象作为其唯一的参数。
我们再定义一个 带有 out/ref 参数的委托类型如下:
public delegate string MyOtherDelegate(out bool a,ref bool b,int c);
试想一下,产生的代码还和上面一样吗?好。
sealed class MyOtherDelegate: System.MulticastDelegate
{
public string Invoke(out bool a,ref bool b,int c); public IAsyncResult BeginInvoke(out bool a,ref bool b,int c,AsyncCallback cb,object state); public stringEndInvoke(out bool a,ref bool b,IasyncResult result);
}
我们发现,Invoke() 和 BeginInvoke() 方法的签名不出所料,但 EndInvoke() 略有差异,其中包括了委托类型定义的所有 out/ref 参数。
2.System.Delegate和System.MulticastDelegate基类
使用关键字创建委托的时候,也就间接的声明了一个派生自System.MulticastDelegate的类。
下面是System.MulticastDelegate部分成员源代码:
public abstract class MulticastDelegate : Delegate
{
// 用来在内部管理委托所维护的方法列表
private Object _invocationList;
private IntPtr _invocationCount; // 返回所指向的方法的列表
public override sealed Delegate[] GetInvocationList()
{
Contract.Ensures(Contract.Result<Delegate[]>() != null); Delegate[] del;
Object[] invocationList = _invocationList as Object[];
if (invocationList == null)
{
del = new Delegate[];
del[] = this;
}
else
{
// Create an array of delegate copies and each
// element into the array
int invocationCount = (int)_invocationCount;
del = new Delegate[invocationCount]; for (int i = ; i < invocationCount; i++)
del[i] = (Delegate)invocationList[i];
}
return del;
} // 重载的操作符
public static bool operator ==(MulticastDelegate d1, MulticastDelegate d2)
{
if ((Object)d1 == null)
return (Object)d2 == null; return d1.Equals(d2);
} public static bool operator !=(MulticastDelegate d1, MulticastDelegate d2)
{
if ((Object)d1 == null)
return (Object)d2 != null; return !d1.Equals(d2);
}
}
MulticastDelegate
下面是System.Delegate部分成员源代码:
public abstract class Delegate : ICloneable, ISerializable
{
// 与函数列表交互的方法
public static Delegate Combine(Delegate a, Delegate b)
{
if ((Object)a == null) // cast to object for a more efficient test
return b; return a.CombineImpl(b);
} public static Delegate Combine(params Delegate[] delegates)
{
if (delegates == null || delegates.Length == )
return null; Delegate d = delegates[];
for (int i = ; i < delegates.Length; i++)
d = Combine(d,delegates[i]); return d;
} public static Delegate Remove(Delegate source, Delegate value)
{
if (source == null)
return null; if (value == null)
return source; if (!InternalEqualTypes(source, value))
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis")); return source.RemoveImpl(value);
} public static Delegate RemoveAll(Delegate source, Delegate value)
{
Delegate newDelegate = null; do
{
newDelegate = source;
source = Remove(source, value);
}
while (newDelegate != source); return newDelegate;
} // 重载的操作符
public static bool operator ==(Delegate d1, Delegate d2)
{
if ((Object)d1 == null)
return (Object)d2 == null; return d1.Equals(d2);
} public static bool operator != (Delegate d1, Delegate d2)
{
if ((Object)d1 == null)
return (Object)d2 != null; return !d1.Equals(d2);
} // 扩展委托目标的属性
public MethodInfo Method
{
get
{
return GetMethodImpl();
}
}
public Object Target
{
get
{
return GetTarget();
}
} }
Delegate
| 继承成员 | 作用 |
| Method | 此属性返回System.Reflection.MethodInfo对象,用以表示委托维护的静态方法的详细信息 |
| Target | 如果方法调用是定义在对象级别的(而非静态方法),Target返回表示委托维护的方法对象。如果Target返回null,调用的方法是一个静态成员 |
| Combine() | 此静态方法给委托维护列表添加一个方法,在C#中,使用重载+=操作符作为简化符号调用此方法 |
| GetInvocationList() | 此方法返回一个System.Delegate类型的数组,其中数组中的每个元素都表示一个可调用的特定方法 |
|
Remove() RemoveAll() |
这些静态从调用列表中移除一个(所有)方法;在C#中,Remove方法可以通过使用重载-=操作符来调用 |
一. 创建简单的委托
好了,了解了以上这些委托的基础信息,我们开始创建属于我们的第一个委托:
public class MyDelegate
{
// 这个委托可以指向任何传入两个整数并返回一个整数的方法
private delegate int BinaryOp(int x, int y); // BinaryOp委托将指向的方法
private static int Add(int x, int y) => x + y; public static void Show()
{
// 创建一个指向 Add() 方法的BinaryOp委托对象
BinaryOp b = new BinaryOp(Add); // 使用委托对象间接调用Add()方法的两种方法:
Console.WriteLine($"b(2, 3)-->{b(2, 3)}");
Console.WriteLine($"b.Invoke(2,3)-->{ b.Invoke(2, 3)}"); DisplayDelegateInfo(b);
} // 将输出委托调用列表那个每个成员的名称
public static void DisplayDelegateInfo(Delegate delObj)
{
foreach (Delegate item in delObj.GetInvocationList())
{
// 若delObj委托指向的是静态方法,则 item.Target 为null
Console.WriteLine($"{item.Method},{item.Target}");
}
}
}
二.使用委托发送对象状态通知
以上的示例纯粹用来说明委托的作用,因为仅为两个数相加创建一个委托没有多大必要,为了更现实的委托应用,我们用委托来定义Car类,它可以通知外部实体当前引擎的状态。步骤如下:
- 定义将通知发送给调用者的委托类型
- 声明Car类中的每个委托类型的成员变量
- 在Car上创建辅助函数使调用者能指定由委托成员变量保存的方法
- 修改Accelerate()方法以在适当的情形下调用委托的调用列表。
定义Car类:
public class Car
{
// 内部状态数据
public int CurrentSpeed { get; set; } public int MaxSpeed { get; set; } public string PetName { get; set; } // 汽车是否可用
private bool CarIsDead; public Car()
{
MaxSpeed = ;
} public Car(string name, int maxSp, int currSp)
{
CurrentSpeed = currSp;
MaxSpeed = maxSp;
PetName = name;
} // 1. 定义委托类型
public delegate void CarEngineHandler(string msgForCaller); // 2. 定义每个委托类型的成员变量
private CarEngineHandler listOfHandlers; // 3. 向调用者添加注册函数
public void RegisterWithCarEngine(CarEngineHandler methodCall)
{
listOfHandlers = methodCall;
} // 4. 实现Accelerate()方法以在某些情况下调用委托的调用列表 public void Accelerate(int delta)
{
if (CarIsDead)
{
if (listOfHandlers != null)
{
listOfHandlers("sorry,this car is dead///");
}
}
else
{
CurrentSpeed += delta; if ((MaxSpeed - CurrentSpeed) == && listOfHandlers != null)
{
listOfHandlers("careful buddy ! gonna blow !");
} if (CurrentSpeed >= MaxSpeed)
{
CarIsDead = true;
}
else
{
Console.WriteLine($"CurrentSpeed={CurrentSpeed}");
}
}
}
}
注意:我们在调用 listOfHandlers 成员变量保存方法之前,需要坚持该变量是否为空,因为在调用 RegisterWithCarEngine() 方法分配这些对象是 调用者的任务。如果调用者没有调用这个方法而试图调用 listOfHandlers ,将引发空异常。
我们来看看具体该如何调用:
public class CarDelegate
{
public static void Show()
{
Console.WriteLine("******Delegate Car******"); // 创建一个Car对象
Car c1 = new Car("bmw", , ); // 告诉汽车,它想要向我们发送信息时条用哪个方法
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OncarEngineEvent)); // 加速,触发事件
Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
}
} // 要传入事件的方法
private static void OncarEngineEvent(string msg)
{
Console.WriteLine($"=>{msg}");
}
}
注意:RegisterWithCarEngine() 方法要求传入一个内嵌的 CarEngineHandler 的委托的实例,与其他委托一样,我们指定一个“所指向的方法”作为构造函数的参数。OncarEngineEvent() 方法与相关的委托完全匹配,也包含 string 类型的输入参数和 void 返回类型。
运行结果如下图:

a.支持多路广播
一个委托对象可以维护一个可调用方法的列表而不只是单独的一个方法。给委托对象添加多个方法时,重载+=操作符即可。为上面的Car类支持多路广播,修改RegisterWithCarEngine()方法,具体如下:
// 现在支持多路广播
// 注意蓝色"+="操作符,而非赋值操作符"="
public void RegisterWithCarEngine(CarEngineHandler methodCall)
{
listOfHandlers += methodCall;
}
如此一来,调用者就可以为同样的回调注册多个目标对象了,这个,第二个采用打印大写的传入信息。
public class CarDelegate
{
public static void Show()
{
Console.WriteLine("******Delegate Car******"); // 创建一个Car对象
Car c1 = new Car("bmw", , ); // 为通知注册多个目标
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OncarEngineEvent));
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OncarEngineEcentToUpper)); // 加速,触发事件
Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
}
} //现在在发送通知信息时,Car类将调用以下这两个方法
private static void OncarEngineEvent(string msg)
{
Console.WriteLine($"=>{msg}");
} private static void OncarEngineEcentToUpper(string msg)
{
Console.WriteLine($"message form car object=>{msg.ToUpper()}");
}
}
b.从委托调用列表中移除成员
Dlegate类还定义了静态Remove()方法,允许调用者动态从委托对象的调用列表中移除方法,这样一来,调用者就在运行是简单的“退订”某个已知的通知。你可以直接使用Delegate.Remove(),也可以 “-=” 操作符。
为Car类添加 UnRegisterWithCarEngine() 方法,允许调用者从调用列表中移除某个方法。
public void UnRegisterWithCarEngine(CarEngineHandler methodCall)
{
listOfHandlers -= methodCall;
}
调用如下:
public static void Show()
{
Console.WriteLine("******Delegate Car******"); // 创建一个Car对象
Car c1 = new Car("bmw", , ); // 告诉汽车,它想要向我们发送信息时条用哪个方法
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OncarEngineEcent)); // += 先绑定委托对象,稍后注销
Car.CarEngineHandler handler2 = new Car.CarEngineHandler(OncarEngineEcentToUpper);
c1.RegisterWithCarEngine(handler2); // 加速,触发事件
Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
} // -= 注销第二个处理程序
c1.UnRegisterWithCarEngine(handler2); // 看不到大写的消息了
Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
}
}
c.方法组转换语法
我们显示的创建了 Car.CarEngineHandler 委托对象的实例,以注册和注销引擎通知:
Car c1 = new Car("bmw", , );
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OncarEngineEcent));
Car.CarEngineHandler handler2 = new Car.CarEngineHandler(OncarEngineEcentToUpper);
c1.RegisterWithCarEngine(handler2);
c1.UnRegisterWithCarEngine(handler2);
如果要调用MulticastDelegate或Delegate总继承的任何成员,手工创建一个委托变量是最直接的方式。但是大多数情况下,我们并不依靠委托对象。我们可以使用C#提供的方法组转换的方法,它允许我们在调用以委托作为参数的方法时直接提供了与委托期望的签名想匹配的方法的名称(返回 void,参数 string),而不是创建委托对象。
public class CarDelegateMethodGroupConversion
{
public static void Show()
{
Console.WriteLine("******Delegate Car******");
Car c1 = new Car("slogbug", , );
// 注册简单的类型名称
c1.RegisterWithCarEngine(CallMethere); Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
} // 注册简单的类型名称
c1.UnRegisterWithCarEngine(CallMethere); Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
}
} private static void CallMethere(string msg) => Console.WriteLine($"=>{msg}");
}
四:泛型委托
如果我们希望定义一个委托类型来调用任何返回void并且接受单个参数的方法。如果这个参数可能会不同,我们就可以通过类型参数来构建。
下面我们看一个小示例:
public class GenericDelegate
{
public delegate void MyGenericDelegate<T>(T arg); public static void Show()
{
// 注册目标
MyGenericDelegate<string> stringTarget = new MyGenericDelegate<string>(StringTarget);
stringTarget("i am ok"); MyGenericDelegate<int> intTarget = new MyGenericDelegate<int>(IntTarget);
intTarget.Invoke();
} static void StringTarget(string arg) => Console.WriteLine($"StringTarget--> {arg.ToUpper()}"); static void IntTarget(int arg) => Console.WriteLine($"IntTarget--> {++arg}"); }
a. 泛型Action<> 和 Func<> 委托
从以上的学习中我们已经了解到,使用委托在应用程序中进行回调需要遵循以下步骤:
- 自定义一个与要指向的方法格式相匹配的委托
- 创建自定义委托的实例,将方法名作为构造函数的参数
- 通过调用委托对象的Invoke()方法来间接调用该方法
其实,这种方式通常会构建大量只用于当前任务的自定义委托。当委托名无关紧要的时候,我们可以使用框架内置的Action<> 和 Func<> 泛型委托,可指向至多传递16个参数的方法。
Action<>:无返回值: 定义 public delegate void Action<...>
public class MyActionDelegate
{
public static void Show()
{
// 使用Action<>委托来指向 DisplayMessage()
Action<string, ConsoleColor, int> actionTarget = new Action<string, ConsoleColor, int>(DisplayMessage);
actionTarget("actionTarget", ConsoleColor.Red, );
} // Action<> 委托的一个目标
private static void DisplayMessage(string msg, ConsoleColor txtColor, int printCount)
{
ConsoleColor previous = Console.ForegroundColor;
Console.ForegroundColor = txtColor; for (int i = ; i < printCount; i++)
{
Console.WriteLine(msg);
} Console.ForegroundColor = previous;
}
}
运行效果如下图:

Func<>:有返回值 public delegate TResult Func<..., out TResult>
public class FuncDelagate
{
public static void Show()
{
Func<int, int, int> funcTarget = new Func<int, int, int>(Add);
int result = funcTarget(, );
Console.WriteLine(result); Func<int, int, string> funcTarget2 = new Func<int, int, string>(SumToString);
string sumStr = funcTarget2(, );
Console.WriteLine(sumStr);
} static int Add(int x, int y) => x + y; static string SumToString(int x, int y) => (x + y).ToString();
}
运行结果:3,7
鉴于 Action<> 和 Func<> 节省了手工创建自定义委托的步骤,but 总是应该使用他们吗?
答案:“视情况而定”。
很多情况下 Action<> 和 Func<> 都是首选,但如果你觉得一个具有自定义名称的委托更有助于捕获问题范畴,那么构建自定义委托不过就是一行代码的事儿。
注:Linq中就大量的用到了 Action<> 和 Func<>。
本文参考《精通C#》
学无止境,望各位看官多多指教。
C#委托(Delegate)学习日记的更多相关文章
- .Net 委托 delegate 学习
一.什么是委托: 委托是寻址方法的.NET版本,使用委托可以将方法作为参数进行传递.委托是一种特殊类型的对象,其特殊之处在于委托中包含的只是一个活多个方法的地址,而不是数据. 二.使用委托: 关键 ...
- C#事件(Event)学习日记
event 关键字的来由,为了简化自定义方法的构建来为委托调用列表增加和删除方法. 在编译器处理 event 关键字的时候,它会自动提供注册和注销方法以及任何必要的委托类型成员变量. 这些委托成员变量 ...
- IOS开发使用委托delegate在不同窗口之间传递数据
IOS开发使用委托delegate在不同窗口之间传递数据是本文要介绍的内容,主要是来讲解如何使用委托delegate在不同窗口之间传递数据,具体内容来看详细内容.在IOS开发里两个UIView窗口之间 ...
- [.NET] C# 知识回顾 - 委托 delegate (续)
C# 知识回顾 - 委托 delegate (续) [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6046171.html 序 上篇<C# 知识回 ...
- [C#] C# 知识回顾 - 委托 delegate
C# 知识回顾 - 委托 delegate [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6031892.html 目录 What's 委托 委托的属性 ...
- Linux学习日记-使用EF6 Code First(四)
一.在linux上使用EF 开发环境 VS2013+mono 3.10.0 +EF 6.1.0 先检测一下EF是不是6的 如果不是 请参阅 Linux学习日记-EF6的安装升级(三) 由于我的数据库 ...
- C# 委托Delegate(一) 基础介绍&用法
本文是根据书本&网络 前人总结的. 1. 前言 定义&介绍: 委托Delegate是一个类,定义了方法的类型, 使得可以将方法当做另一个方法的参数来进行传递,这种将方法动态地赋给参数的 ...
- 为什么不能把委托(delegate)放在一个接口(interface)当中?
stackoverflow上有人问,为什么不能把委托放在一个接口当中? 投票最多的第一个答案第一句话说,“A Delegate is just another type, so you don't g ...
- C# 代理/委托 Delegate
本文转载自努力,努力,努力 1. 委托的定义:委托是函数的封装,它代表一"类"函数.他们都符合一定的签名:拥有相同的参数列表,返回值类型.同时,委托也可以看成是对函数的抽象,是函数 ...
- android学习日记05--Activity间的跳转Intent实现
Activity间的跳转 Android中的Activity就是Android应用与用户的接口,所以了解Activity间的跳转还是必要的.在 Android 中,不同的 Activity 实例可能运 ...
随机推荐
- 支持“***Context”上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库(http://go.microsoft.com/fwlink/?LinkId=238269)。
在用VS进行MVC开发的过程中遇到如下问题: 支持“***Context”上下文的模型已在数据库创建后发生更改.请考虑使用 Code First 迁移更新数据库(http://go.microsoft ...
- asp.net中利用session对象传递、共享数据[session用法]
下面介绍Asp.net中利用session对象传递.共享数据用法: 1.传递值: 首先定义将一个文本值或单独一个值赋予session,如下: session[“name”]=textbox1.text ...
- 修改Tomcat 6 默认的ROOT
1.找到conf/server.xml. 2.找到Host节点. 3.在该节点中添加子节点<Context path="" docBase="项目名称" ...
- 在JBoss中部署GeoServer
GeoServer一直就不能在 JBoss应用服务器中正常部署.最近我在一个国外的论坛上找到了该问题的解决方案.以下方法经测试,可以将GeoServer 2.1.3 成功部署在 JBoss 5.0 和 ...
- Android开发_控制硬加速hardwareAccelerated
控制硬加速 hardwareAccelerated也是一种优化的手段 从Android3.0 (API level11)开始,Android的2D显示管道被被设计得更加支持硬加速了.硬加速使用GPU承 ...
- PureMVC(JS版)源码解析(十二):Facade类
MVC设计模式的核心元素在PureMVC中体现为Model类.View类和Controller类.为了简化程序开发,PureMVC应用Facade模式. Facade是Model\View\Co ...
- android开发之自定义AutoCompleteTextView
AutoCompleteTextView,很多人都用过,有些情况下使用Google提供的ArrayAdapter作为适配器就可以完成需求,但是在实际开发中,我们经常需要开发自定义适配器来完成开发工作. ...
- Android(java)学习笔记185:xml文件生成
1.xml文件: 用元素描述数据,跨平台. 2.利用传统的方式创建xml文件,下面是一个案例: 设计思路:建立一个学生管理系统,创建xml文件保存学生信息: (1)首先是布局文件activity_ma ...
- 11.12 noip模拟试题
题目名称 加密 冒泡排序图 重建可执行文件名 encrypt bubble rebuild输入文件名 encrypt.in bubble.in rebuild.in输出文件名 encrypt.in b ...
- U3D 抛物线的方法
本文转载:http://www.manew.com/thread-44642-1-1.html 无论是愤怒的小鸟,还是弓箭发射功能,亦或者模拟炮弹受重力影响等抛物线轨迹,都可以使用本文的方法,模拟绝对 ...