怕大家看了觉得乱七八糟不知道讲什么,先一句话描述下:如标题,在C#中委托是一种类型,如class一样也需要声明,定义,赋值,并与class在同一个层面
再介绍之前先看下相关内容的定义(我不是咬文嚼字,个人以为,有些东西的定义一定要没明白,不能含糊,不然只会是似懂非懂)
delegate :(关键字->类型->引用类型[class,interface等等这些都是引用类型关键字])
delegate 是一种可用于封装命名或匿名方法的引用类型。(并不是所有的引用类型都是class)
是一种定义方法签名的类型,可以与具有兼容签名的任何方法关联。
与委托的签名(由返回类型和参数组成)匹配的任何可访问类或结构中的任何方法都可以分配给该委托。
event:(关键字->修饰符[readonly,static,unsafe等等这些都是修饰符关键字])
event 关键字用于在发行者类中声明事件。 (所以被event修饰的多路广播委托[MulticastDelegate]才被称“事件”[这个事件并不是关 键字event,而是MulticastDelegate的一个特殊实例])
Delegate (类 -> System命名空间下 [程序集mscorlib])
MulticastDelegate(类-> System命名空间下 [程序集mscorlib])
EventHandler(委托 [他不是类,是委托])
实际上是一种.NET为我们预定义委托类型,后面会将到
现在来看看Delegate 里面是什么
可以清楚的看到Delegate 确实就是一个类,而且直接继承于Object(这个继承是IL层面的,C#层面是看不到的所以不用太在意)
里面有很多方法,不过我们可能能使用的也只有public的方法而已,而且这是一个抽象类我们也是无法直接实例化的(下面的同样也是)
再来看看MulticastDelegate里面是什么
很明显MulticastDelegate同样是个类而且直接继承与上面的Delegate
还多了2个私有字段 _invocationCount , _invocationList ,以及一些新的方法
由此可以看出这MulticastDelegate,跟Delegate,虽然名字很有迷惑性,但是他们不是委托,是class(下面会提到这个class跟我们委托之间的关系),而且正常情况下我们还用不到,不过经常有人把Delegate跟delegate混淆(完全不是一个东西,跟string,String可不一样)
可以看下EventHandler里面是什么
可以看到里面就是一个声明了一委托(这跟声明一个类是类似的),他是声明了一个delegate类型的数据,所以EventHandler就很清楚了就是一个委托类型的数据(只是声明而已,而delegate的声明是可以放在命名空间顶层跟其他数据结构的声明享受同意的待遇的)
(不过IL代码里依然用class修饰,我个人认为IL里面的class是IL的class,C#里面的class是C#的class,是不一样的东西。不能因为IL里面是用class修饰就一厢情愿的说这个东西是类,如果是这样Interface,enum,struck什么的也都是类了,最后他们都是2进制的数据,都是一样的东西)
现在让我们看看所谓委托的整个过程
第一步: 我们先定义/声明一个委托(注意是定义)
public delegate void delegateLoopChangeEventHandler(object yourTarget, string yourMessage);
就像这样(不能定义在接口或函数中,但是可以在struct里面定义)
我们看看CLR是怎么处理这句话的
可以很明白的看到他真的是一个委托那个公文包一样的小图标就是委托的意思
打开它看看里面有什么
可以看到里面有1个public 方法,跟3个好像跟方法和类似的东西(因为这2个框框组成的图标是什么含义我确实也不清楚)
可见委托里面也是可以有方法的(不过这些方法不是我们自己创建的,其实我们定义一个委托类型的数据体的时候就是一局话,而IDE为我们转换为IL的时候其实是有很多操作的)
我们来看下这一句的代码编译器是怎么理解的,IDE把它翻译成了什么
看到了吧,转化为IL代码的时候多了一些东西,在IL层面它是IL的class(一定要分清是IL的class,不是C#语法里的class)并且继承于MulticastDelegate
再次说明下这个delegateLoopChangeEventHandler 是一个委托,在IL里表现为class,而这个class跟C#里面的类,委托,接口不是一个环境的东西
第二步:我们要实例化这个委托(这里的操作类似于 Point myPt; FileStream myFs 这样)
public static delegateLoopChangeEventHandler OnLoopChange;
我是在类里实例化的,所以可以加上修饰符(测试中为了直接给main函数使用,所以才加了个static,实际环境还是建议少用静态的东西)。
然后我们可以看到这一句的IL代码也只有一句
当程序运行到此处的时候 OnLoopChange 的值是null,也就是说我们还没有对这个委托类型的数据进行初始化/赋值
第三步:我们来赋值
eg 1 : OnLoopChange = new delegateLoopChangeEventHandler((x, y) => { Console.WriteLine("myIndex" + myIndex++); });
eg 2 : OnLoopChange += new delegateLoopChangeEventHandler((x, y) => { Console.WriteLine("myIndex" + myIndex++); });
eg 3 : OnLoopChange =((x, y) => { Console.WriteLine("myIndex EX"); });
eg 4 : OnLoopChange = new delegateLoopChangeEventHandler(FunTest1);
可以看到赋值的操作的写法有很多,可以很随意,当然推荐最后一种,可以随时删除绑定的方法(当然不是说Lambda 表达式不好,除非你很清楚你的方法就只是要做1,2行代码的事情)
这个赋值没有什么好说的与其他类型的数据赋值也是一样的,还是来看下clr的il(随便找的一个委托类型的初始化,跟上面的不是一起的)
其实对IL不是很熟悉,简单说下,每当我们要赋值/初始化他大概是先把我们要绑定的函数移到堆栈,然后他是新建了个delegateLoopChange类型的数据(因为我们刚刚说了只是实例化后我们的委托实例是为null的),最后用堆栈里的这些数据替换了我们刚刚为null的委托字段。
再说明下 我们可以OnLoopChange =((x, y) => { Console.WriteLine("myIndex EX"); });这样直接赋值,是因为IDE会帮我们加上new delegateLoopChangeEventHandler(......),不熟悉的最好不要偷懒
然后委托实例的赋值我们可以使用+=,不过这个+=对null的实例于非null实例处理可不一样
如果为null就是上面的初始化过程。
如果不是null,他实际进行的操作是
简单的说就是它向我们的实例又添加了一个方法。(应该很容易想到相应的会有-=)。
当然我们也可以在实例化的时候同时给他赋值,也就是说第2步跟第,3步可以一起来做。
第四步:执行委托
执行就非常简单了,刚刚的委托直接OnLoopChange (null,null);操作起来就像是调用一个方法一样
也可以OnLoopChange .Invoke(null, null);
特别说明下这个Invoke不是System.Windows.Forms里Control类的Invoke,这里他是DomainOperationEntry下的
然后如果当前委托实例如果绑定了多个方法,都会逐个执行
现在顺便说一下Control下的Invoke
当然哪线程个执行OnLoopChange,OnLoopChange就在哪个线程上面执行,有的时候你有可能会操作UI,当是当前又不是UI线程,这个时候怎么办,的确我们可以关闭VS的UI线程检查一了百了,不过这肯定是不推荐的,首先很难保证不会有同时访问的情况,其次操作ui一般都很耗时
现在我们就可以使用Control下的Invoke。
方法也很简单 myFormControl1.Invoke(myFormControl1.myDelegate,new Object[] {myString}); 就这么一句就可以了(这个我直接摘的MSDN的)
简单的说就是 控件.Invoke(委托实例): 如果有参数直接加在后面就行了
说明下 Invoke给UI虽然是UI线程执行 不过当前函数会等待方法返回。
使用BeginInvoke可以解决
最后委托实例用完了怎么处理,一般出于习惯会-=,不过MSDN上也没有提交这个东西需要特别的销毁或释放,应该是被托管的,我们不用管
好了再来说一下"事件"(其实明白委托后,就不会在纠结事件跟委托的关系了)
MSDN上已经描述的非常清楚精准了
“事件是特殊类型的多路广播委托,仅可从声明它们的类或结构(发行者类)中调用”
所以很明白了事件就是委托,被一个关键字event修饰过的委托而已。(就好像 static int i =1 ; 你会说i不是int型数据么)
如果非要找区别,MSDN也说了,他只是不能在其他类中调用而已。
其实event就是微软多帮我们实现了写功能。
来分析他说的这句十分精确的话“仅可从声明它们的类或结构(发行者类)中调用”
乍一看不是很简单实现吗,吧这个委托的示例用private 来修饰不就可以了(事实上VS在遇到event的时候也是这么处理的)
不过不要忘了委托的功能,其他类会订阅这个问题(其实就是赋值或添加关联函数)
这样一来是不是很麻烦了,已经是private了 ,其他类肯定不能处理了,我们是不是要单独写函数暴露出去给调用方去使用 等等。
好了不用我们处理了,微软加个event修饰符帮我们解决
来看下微软是怎么做的
可以看到LoopChangeEvent这个特殊委托实例真的是用private在修饰,不过他在内部帮我们添加了2个方法,跟这么一个所谓的事件(其实2个静态方法是在事件里面的,也不知道它为什么显示在了同级),其实这个事件就是为了给这个private委托实例开放2个函数。
既然事件就是委托,使用方法也是完全一样的,也不重复写了(唯一的区别是“仅可从声明它们的类或结构(发行者类)中调用”)
值得注意的是即时被event修饰,如果把当前委托实例传给其他类,在其他类中也是可以触发委托(事件)的,只是不能直接把这个委托当作类的字段去使用(触发)
现在再回到前面的EventHandler ,应该很清楚了吧
其实他就是委托类型的数据的声明(存在的意义同样也只是为了方便我们或者说是规范我们)
MSDN上就很直白的写着
[SerializableAttribute] [ComVisibleAttribute(true)]
public delegate void EventHandler(Object sender,EventArgs e)
这不就是前面提到的委托的声明方法一样。所以他就是委托,微软帮我们预先声明了几种委托而已
EventHandler等等里面有event这个单词存在,是在提醒我们,这种委托类型的实例最好用event来修饰
最后总结下
如果要问事件是不是委托。
那必须先搞清楚这到底是在问什么
是要问event 跟 delegate 还是 Delegate 的区别
这些开头就有解释,根本不是一个类型的东西,绝对不一样。
那如果是问委托实例,跟事件实例呢
MSDN也说的很清楚了。事件是特殊类型的多路广播委托
如果要问委托是不是类
在C#委托绝对不会是类,要不然要delegate这个关键字干什么直接用class。
不过dclass跟delegate被翻译成IL的时候就都是属于IL的class的。
IL终究不是C#,不会影响结论:在C#委托 不是 类
委托的一般应用
跨类跨线程的消息通知(委托的执行是定义委托的类或线程,委托的赋值是其他类或线程,在没有invoke的情况下调用线程是执行线程,这种问题一般加事件修饰)
将委托传递给其他方法,让其他线程,类,方法去调用(这种调用方一般只调用,赋值也是其他模块做的,这也是通常理解的函数指针)
东西写的很乱,可以也有很多不完善或者有错误的地方。看起来肯定也很吃力,见谅,存在错误的地方请多多指正
参考文献:MSDN ,<一站式示例代码库编程规范>
合作编辑:无