读书人

C#深入剖析(1)事件

发布时间: 2012-03-13 11:21:12 作者: rapoo

【原创】C#深入剖析(1)——事件
C#深入剖析(1)——事件

准备写一个系列文章,深入探讨C#及.Net中的某些特性。

第一篇 事件

事件相信每个人都不陌生,随便一个WinForm程序,就会使用大量的事件,比如:

C# code
class MainForm : Form    {        public MainForm()        {            this.Click += new EventHandler(MainForm_Click);        }        private void MainForm_Click(object sender, EventArgs e)        {        }}
当然,还可以对代码进行简化,如类型的自动推断,匿名方法,Lambda表达式等。这个事件大概的工作流程为:当用户单击窗体时,操作系统向应用程序发送一系列消息,如左键按下和左键抬起,应用程序将通过GetMessage等方法最终将消息提交到窗口过程(WndProc),窗口过程通过处理消息,当发现产生了连续的左键按下和左键抬起的消息后,
就知道产生了单击事件,于是去调用窗体的OnClick方法,该方法会去检测一下是否订阅了Click事件,如果订阅了,就会去调用相应的事件处理程序,这个过程是通过委托实现的。

下面我从语法角度来分析一下事件:
事件是类、结构或接口中的一个成员,它有两种定义形式:
一、
C# code
event MethodInvoker OneEvent;

二、
C# code
event MethodInvoker OneEvent        {            add            {            }            remove            {            }        }其中MethodInvoker
是一个没有参数和返回值的委托,它只是用来约束事件处理程序的的形式,你可以任意定义一个,例如你可以使用Action来代替。
事件包括两个访问器,其中add访问器会在订阅事件时触发,remove访问器在取消事件订阅时触发。
对于第一种定义形式,系统会自动提供add及remove访问器,同时会提供如下字段:
C# code
private MethodInvoker OneEvnet;
该字段的类型为委托的类型,字段名跟事件名相同(一个类中拥有同名成员,C#编译器是不允许的,但是系统可以)。
C# code
    class Demo    {        public void InvokeEvent()        {            if (OneEvent != null)                OneEvent();//调用事件            if (TwoEvent != null)            {                string str = TwoEvent(217);//调用事件                MessageBox.Show(str);            }        }        public event MethodInvoker OneEvent;        public event Func<int, string> TwoEvent;}        private void button1_Click(object sender, EventArgs e)        {            Demo de = new Demo();            de.OneEvent += delegate            {                MessageBox.Show("事件被调用");            };            de.TwoEvent += arg => arg.ToString();            de.InvokeEvent();        }

可以看出,这里事件类似于方法和委托,可以传递参数并被调用。事实上,这只是编译器的一种包装,这里其实使用的正是前面提到的同名的委托字段。而如果是在一个类中访问另一个类中的事件,或者如下面将要提到的自己提供访问器的情况,由于不存在同名的委托字段,事件就不能再这样使用了,而只能出现在+=和-=运算符的左侧。

至少有两个理由使得我们需要自己提供访问器:
1.希望在订阅或取消事件时执行一段代码。
2.前面提到,如果不提供访问器,每定义一个事件,系统就会生成一个同名的委托字段,如果事件特别多,这就是一项巨大的开销,而如果定义了访问器,则不再提供,此时我们可以用一种统一的方式来处理,从而节省资源,实际上,WinForm就是这样处理的。

C# code
   class Demo    {        public void InvokeEvent()        {            if(ehl[oneEvent]!=null)                ehl[oneEvent].DynamicInvoke();//调用事件            if (ehl[twoEvent] != null)            {                string str = ehl[twoEvent].DynamicInvoke(217) as string;//调用事件                MessageBox.Show(str);            }        }        EventHandlerList ehl = new EventHandlerList();        static readonly object oneEvent = new object();        static readonly object twoEvent = new object();        public event MethodInvoker OneEvent        {            //在add和remove访问器中,类似属性,存在一个value,表示要订阅和取消的委托            add            {                //我这里的条件没有什么实际意义,只是想说明可以在访问器中执行代码                //示例中,起到一个筛选的作用,只有那些以”On”开头,并且定义于其他类中的方法才能被订阅                if (value.Method.Name.StartsWith("On") && value.Target != this)                    ehl.AddHandler(oneEvent,value);            }            remove            {                //对不起,禁止你取消静态方法(为什么禁止取消静态方法?没有理由,只用于举例^-^)                if (!value.Method.IsStatic)                    ehl.RemoveHandler(oneEvent,value);            }        }        public event Func<int, string> TwoEvent        {            add            {                ehl.AddHandler(twoEvent,value);            }            remove            {                ehl.RemoveHandler(twoEvent,value);            }        }        public void OnCall()        {            MessageBox.Show("Demo.OnCall");        }    }    class Pro    {        public void Call()        {            MessageBox.Show("Pro.Call");        }        public void OnCall()        {            MessageBox.Show("Pro.OnCall");        }        public static void OnCalls()        {            MessageBox.Show("Pro.Static.OnCalls");        }}        private void button1_Click(object sender, EventArgs e)        {            Pro pr = new Pro();            Demo de = new Demo();            de.OneEvent += pr.Call;//不以”On”开头,不会订阅            de.OneEvent += pr.OnCall;//成功订阅            de.OneEvent += Pro.OnCalls;//成功订阅            de.OneEvent += de.OnCall;//只有定义在其他类中的方法才会被订阅            de.InvokeEvent();            de.OneEvent -= pr.Call;//未订阅,谈不上取消            de.OneEvent -= pr.OnCall;//成功取消            de.OneEvent -= Pro.OnCalls;//静态方法不会被取消            de.OneEvent -= de.OnCall; //未订阅,谈不上取消            de.InvokeEvent();    } 

可以看到,只有pr.OnCall和Pro.OnCalls订阅成功了,并且Pro.OnCalls不能被取消。

再来简单说说WinForm事件:
WinForm的根是组件(Component),可视的组件称为控件(Control)。
Component上定义了一个受保护的属性Events,用于管理事件列表。
Control上定义了一系列静态的私有字段,为事件列表提供索引键,字段名基本上是:
Event事件名
如EventClick,EventEnter等。
举一个应用:
如何获取一个事件订阅的所有方法列表,以及如何在不知道事件处理程序方法名的情况下取消事件,或者更现实一点,如何取消匿名方法(在不声明一个委托引用的前提下,匿名方法显然不可能通过-=运算符来取消)。

C# code
       private void button1_Click(object sender, EventArgs e)        {            PropertyInfo pi= typeof(Component).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);            EventHandlerList ehl = pi.GetValue(button2, null) as EventHandlerList;            FieldInfo fi = typeof(Control).GetField("EventClick", BindingFlags.NonPublic | BindingFlags.Static);            object key=fi.GetValue(null);            Delegate del= ehl[key];            foreach (Delegate de in del.GetInvocationList())            {                Console.WriteLine(de.Method.Name);//订阅的事件                ehl.RemoveHandler(key, de);//取消订阅            }        }
这段代码可以显示button2的Click事件订阅的所有方法,并且在执行该段代码后,button2的Click事件将失效。
也许有人会有这样的疑问:GetInvocationList获得的委托数组中,某个委托如果还是包括多个方法链怎么办?微软为大家想得非常周到了——数组中的每个委托都仅表示一种方法。
另外,如果我们需要取消所有事件,不用遍历,直接调用EventHandlerList.Dispose方法即可。

关于WinForm事件,还有一个有趣的现象:
当我们拖曳一个控件到窗体时,双击该控件就会进入某个事件处理程序,例如双击Button控件,会进入Click事件;双击TextBox控件,会进入TextChanged事件。你是否思考过怎么通过编程的方法知道会进入哪个事件呢?
其实这叫做默认事件(类似的,还有一个默认属性的概念),是一个特性:DefaultEventAttribute
C# code
    [DefaultEvent("Click")]    class Control    {}

Click是应用于Control类的默认事件,Button则继承了这个特性,而TextBoxBase这个类将这个特性修改为TextChanged, TextBox又从TextBoxBase继承过来这个特性。
既然知道了原理,要去检索,就很简单了,直接反射就行了。另外,其实系统提供有专门的方法:
C# code
TypeDescriptor.GetDefaultEvent。

现在来看看TreeView控件的默认事件:
C# code
Attribute attr= Attribute.GetCustomAttribute(typeof(TreeView),typeof(DefaultEventAttribute));DefaultEventAttribute de = attr as DefaultEventAttribute;MessageBox.Show(de.Name);

或者
MessageBox.Show(TypeDescriptor.GetDefaultEvent(typeof(TreeView)).Name);

结果正是AfterSelect。


[解决办法]
好,感谢奉献!


[解决办法]
顶起来慢慢看
[解决办法]
学习
[解决办法]
感谢楼主,学习
[解决办法]
多谢分享
[解决办法]
好贴要顶!!!
[解决办法]
慢慢看
[解决办法]
顶起来,不错
[解决办法]
分享
[解决办法]
不错不错,学习
[解决办法]
谢谢 学习了
[解决办法]
呵呵,支持楼主,
不过怎么第一章就讲事件了?也不先讲一下委托?



[解决办法]
多谢分享
[解决办法]
帮顶 学习了 我预见这个好帖会火起来的.
[解决办法]
顶起来学习。
[解决办法]
学习,该恶补一下原理基础了。
[解决办法]
学习
收藏
转载
帮顶
[解决办法]
感谢分享
学习
[解决办法]
学习,恶补
[解决办法]
学习,再学习。
[解决办法]
mark
[解决办法]
UP
[解决办法]
mark
[解决办法]
支持,期待~
[解决办法]
感谢,学习
[解决办法]
不错,多谢楼主分享!
[解决办法]
多谢楼主给的学习机会
[解决办法]
支持
[解决办法]
此贴必顶
[解决办法]
学习
[解决办法]
帮顶。。学习一下!!!
[解决办法]
呵呵,好顶下啊
[解决办法]
UP~~
学习~~~
[解决办法]
咯.....
[解决办法]
抢代码抢分抢美女
[解决办法]
好,学习了
[解决办法]
呵呵,收藏了…楼主好像是要我们接分的…呵呵
[解决办法]
mark
[解决办法]
谢谢分享,继续发扬!
[解决办法]
帮顶。。学习一下!!!
[解决办法]
顶了
[解决办法]
多谢!!!!
[解决办法]
不错,继续!!!
[解决办法]
JF~~~~
[解决办法]
先顶后看。
[解决办法]
感谢 还要多学习啊
------解决方案--------------------


顶,支持一下
[解决办法]
感谢,支持!!
lz是好人!
[解决办法]
mark
[解决办法]
前面一直没看明白...后面终于碰到点熟悉的了...
[解决办法]
谢谢。帮顶
[解决办法]
顶!奉献精神!!
[解决办法]
很详细 顶
[解决办法]
学习了
[解决办法]
顶下~
[解决办法]
顶,要学习的
[解决办法]
学习。分不要了
[解决办法]
xue xi
[解决办法]
先来顶一下,这贴要入精华了...
[解决办法]
学习学习
[解决办法]
学习了
[解决办法]
回了再看。学习
[解决办法]
学习了
[解决办法]
多谢分享
[解决办法]
mark
[解决办法]
好好学习,写得很好
[解决办法]
学习。。
[解决办法]
thx
[解决办法]
很好
[解决办法]
Mark!
[解决办法]
UP
[解决办法]
学习了!
[解决办法]
楼主写的漂亮

[解决办法]
楼主你太伟大了,我怎么瞧着你像大海一样啊!
放心我不晕船,呵呵

好,支持楼主
[解决办法]
好贴
[解决办法]
写得很好,受益匪浅啊!谢谢。
[解决办法]
UP!
[解决办法]
up
[解决办法]
不错的东东,支持!
[解决办法]
好贴,自己写的还是抄书的?

舞蝶飞数码创意

.net开发的图像处理软件。
图片合成flash
图片合成ppt
图片合成avi视频
免费照片边框

[解决办法]
LZ的奉贤精神真是可贵~学习了


[解决办法]
接着收藏
[解决办法]
学习+收藏
[解决办法]
ding
[解决办法]

[解决办法]
学习ing...
[解决办法]
好像是这样。。。。
[解决办法]
没看完,先Mark
[解决办法]
我要顶的更高~~~~~
[解决办法]
学习,顶起
[解决办法]
谢谢,非常感谢!!!
[解决办法]
学习,支持
[解决办法]
mark!!!!
[解决办法]
mark!!!!
[解决办法]
绝对火贴~~~ 支持~!
[解决办法]
学习了

读书人网 >C#

热点推荐