我们在以前的文章中看到了委托以及它们的实现。但是如果你在网页上搜索有关委托的信息,你肯定会注意到它们总是与“event”结构相关联。
联机事件教程使得事件尽管与常规的委托实例有关系,但是还是有许多区别。事件经常被解释得就好像它们是一种特殊的类型或结构。但是我们将看到它们只是委托类型的一种修饰器,它们仅仅是添加了一些编译器强制执行的限制和两个存取器(与属性的get和set相似)。
首先看看事件 vs 常规委托
当我完成了前一篇关于委托的文章时,另一个C#构造也进入了我的计划:事件。事件看起来确实与委托有关,我没能找出它们之间的不同。
从它们的语法看来,事件就好像是一个留有代表多播委托的委托组合字段。它们同样支持委托的(+和-)组合操作。
在接下来的例子程序(没有任何有用的功能)中,我们将看见msgNotifier(使用event结构)和msgNotifier2(普通委托)看起来有个一致的意图和目的。
代码
namespace EventAndDelegate
{
delegate void MsgHandler(string s);
class Class1
{
public static event MsgHandler msgNotifier;
public static MsgHandler msgNotifier2;
[STAThread]
static void Main(string[] args)
{
Class1.msgNotifier += new MsgHandler(PipeNull);
Class1.msgNotifier2 += new MsgHandler(PipeNull);
Class1.msgNotifier("test");
Class1.msgNotifier2("test2");
}
static void PipeNull(string s)
{
return;
}
}
}
查看在上述代码中Main方法的IL代码,你会注意到msgNotifier和msgNotifier2都是委托,msgNotifier2使用的是同样的方式。
代码
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 95 (0x5f)
.maxstack 4
IL_0000: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier
IL_0005: ldnull
IL_0006: ldftn void EventAndDelegate.Class1::PipeNull(string)
IL_000c: newobj instance void EventAndDelegate.MsgHandler::.ctor(object,
native int)
IL_0011: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_0016: castclass EventAndDelegate.MsgHandler
IL_001b: stsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier
IL_0020: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier2
IL_0025: ldnull
IL_0026: ldftn void EventAndDelegate.Class1::PipeNull(string)
IL_002c: newobj instance void EventAndDelegate.MsgHandler::.ctor(object,
native int)
IL_0031: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_0036: castclass EventAndDelegate.MsgHandler
IL_003b: stsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier2
IL_0040: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier
IL_0045: ldstr "test"
IL_004a: callvirt instance void EventAndDelegate.MsgHandler::Invoke(string)
IL_004f: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier2
IL_0054: ldstr "test2"
IL_0059: callvirt instance void EventAndDelegate.MsgHandler::Invoke(string)
IL_005e: ret
} // end of method Class1::Main
查看一下在MSDN上的C#关键字,它证明了event仅仅是一个修饰符。问题是这样使用后会带来什么方面的不同呢?
事件增加的值
事件与接口
首先,一个事件可以包含在接口声明中,而一个字段(译注:意指普通委托)不能。这是引入event修饰符后最重要的行为改变。例如:
代码
interface ITest
{
event MsgHandler msgNotifier; // compiles
MsgHandler msgNotifier2; // error CS0525: Interfaces cannot contain fields
}
class TestClass : ITest
{
public event MsgHandler msgNotifier; // When you implement the interface, you need to implement the event too
static void Main(string[] args) {}
}
事件引用
更多的是,一个事件仅能被包含其声明的类调用,然而委托字段可以任何有权限访问它的人调用。例如:
代码
using System;
namespace EventAndDelegate
{
delegate void MsgHandler(string s);
class Class1
{
public static event MsgHandler msgNotifier;
public static MsgHandler msgNotifier2;
static void Main(string[] args)
{
new Class2().test();
}
}
class Class2
{
public void test()
{
Class1.msgNotifier("test"); // error CS0070: The event 'EventAndDelegate.Class1.msgNotifier' can only appear on the left hand side of += or -= (except when used from within the type 'EventAndDelegate.Class1')
Class1.msgNotifier2("test2"); // compiles fine
}
}
}
在引用上这个限制是非常强的。甚至从声明事件的父类继承的派生类也不被允许触发事件。处理这种事情的一个方法是定义一个protected virtual方法来触发事件。
事件存取器
同时,事件将伴随着一对存取方法,它们有一个add和remove方法。这与属性非常相似,属性也提供了一对get和set方法。
你被允许重载引用自MSDN的关于C#事件修饰符的例2和例3中显示的那些存取器,尽管我没有看到例2有什么用处,但是你可以假设你能够针对某些通知编写自定义的添加方法或写入日志,例如,当一个监听者加入到你的事件中。
add和remove存取器需要同时自定义,否则将产生CS0065错误('Event.TestClass.msgNotifier' : 事件属性必须同时包含add和remove存取器)。
查看前一个例子的IL代码,里面的事件存取器并没有自定义,我注意到编译器自动生成的针对msgNotifier事件的方法(add_msgNotifier和remove_msgNotifier)。但是它们并没有使用,无论何时事件被访问,相同的IL代码将会被复制(内联方式)。
但是当你自定义这些存取器后再查看IL代码时,你将注意到自动生成的存取器当你访问事件的时候使用了。例如,代码如下:
代码
using System;
namespace Event
{
public delegate void MsgHandler(string msg);
interface ITest
{
event MsgHandler msgNotifier; // compiles
MsgHandler msgNotifier2; // error CS0525: Interfaces cannot contain fields
}
class TestClass : ITest
{
public event MsgHandler msgNotifier
{
add
{
Console.WriteLine("hello");
msgNotifier += value;
}
}
static void Main(string[] args)
{
new TestClass().msgNotifier += new MsgHandler(TestDel);
}
static void TestDel(string x)
{
}
}
}
下面的是针对Main方法的IL代码:
代码
{
.entrypoint
// Code size 23 (0x17)
.maxstack 4
IL_0000: newobj instance void Event.TestClass::.ctor()
IL_0005: ldnull
IL_0006: ldftn void Event.TestClass::TestDel(string)
IL_000c: newobj instance void Event.MsgHandler::.ctor(object,
native int)
IL_0011: call instance void Event.TestClass::add_msgNotifier(class Event.MsgHandler)
IL_0016: ret
} // end of method TestClass::Main
事件签名
最后,尽管C#允许,.net框架增加一个被用于事件的关于委托签名的限制。这个签名应为foo(object source, EventArgs e),这里的source表示触发事件的对象,e包含一些关于事件的附加信息。
结论
我们已经看到event关键字是一个针对委托声明的修饰符,它允许它被包含在一个接口,限制它从声明它的类中引用,提供一对可定义的存取器(add和remove)并且强制委托签名(当在.net框架中使用时)。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/12639172/viewspace-623971/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/12639172/viewspace-623971/
553

被折叠的 条评论
为什么被折叠?



