C#事件及事件驱动

笔记选自书《.NET4.0面向对象编程漫谈 基础篇》的章节——事件及事件驱动

Tag:事件与多路委托

从面向对象角度来说,事件是由对象发出的信息,它是一个信号,通知其他对象有事情发生。

激发与响应事件的载体都是对象。激发对象的对象被称为“事件源”,对这个事件进行响应的对象称为“响应者”,响应者必须提供一个“事件响应(或处理)方法”。

事件与多路委托
事件的主要特点是一对多关联,即一个事件可以有多个响应者。.NET的事件处理机制是基于多路委托实现的。
多路委托实现事件的例子:

        public delegate void MyEventDelegate(int value);
        public class EventSource
        {
            public MyEventDelegate handles;
        }
        public class EventResponsor
        {
            public void MyMethod(int i)
            {
                Console.WriteLine(i);
            }
        }
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            EventSource p=new EventSource();
            EventResponsor responsor1=new EventResponsor();
            EventResponsor responsor2=new EventResponsor();
            p.handles=System.Delegate.Combine(p.handles,new MyEventDelegate(responsor1.MyMethod)) as MyEventDelegate;
            p.handles=System.Delegate.Combine(p.handles,new MyEventDelegate(responsor2.MyMethod)) as MyEventDelegate;
            //p.handles+=new MyEventDelegate(responsor1.MyMethod);
            //p.handles+=new MyEventDelegate(responsor2.MyMethod);
            //or more brief ways
            //p.handles+=responsor1.MyMethod;
            //p.handles+=responsor2.MyMethod;
            p.handles(11);
            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }

先定义一个委托,接着定义事件源EventSource类,字段MyEventDelegate类型的handles为它的响应者清单,定义一个事件响应者类EventResponsor,里面包含了需要用来响应的方法
主函数中,声明一个事件源对象p和两个响应者responsor1和responsor2,然后调用Delegate类的静态方法组合多个委托变量(或者直接新建一个委托变量),最后手动触发事件,运行程序可以看到,有两个反应

上述例子中事件是手动触发的,而真实的事件不允许由外界触发,必须由事件源对象自己触发,为此引入关键字event,改动例子:

public delegate void MyEventDelegate(int value);        
public class EventSource        
{
            public event MyEventDelegate handles;
            public void FireEvent()
            {
                if(handles!=null)
                    handles(11);
            }
}
public class EventResponsor        
{
            public void MyMethod(int i)
            {
                Console.WriteLine(i);
            }       
 }       
 public static void Main(string[] args)        
{
            Console.WriteLine("Hello World!");
            EventSource p=new EventSource();
            EventResponsor responsor1=new EventResponsor();
            EventResponsor responsor2=new EventResponsor();
            p.handles+=new MyEventDelegate(responsor1.MyMethod);
            p.handles+=new MyEventDelegate(responsor2.MyMethod);
            //or more brief ways
            //p.handles+=responsor1.MyMethod;
            //p.handles+=responsor2.MyMethod;
            p.FireEvent();
            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
}

        改动地方:EventSource类的字段handles字段加了关键字event,添加了一个触发方法FireEvent(),组合委托变量时不能用Delegate的静态方法了,必须用追加委托的方法+=,触发事件的方法也变为引用事件源类提供的触发方法FireEvent()。

.NET事件实现机制剖析
书的作者进行了很认真的剖析,我记一下大致要点
给Form里面的Button添加事件后,在Initialize方法可以看到:

            Button btn = new Button();
            btn.MouseClick+=new MouseEventHandler(btn_MouseClick);

上述代码中的MouseClick是Button类的事件成员:
image
Button本身定义的事件只有两个,其它都继承自ButtonBase和IButtonControl,作用如上例中的handles,所以可以给Button添加多个响应函数。

作者的总结:所有的.NET可视化窗体控件的预定义事件,都是某一对应的“事件名+Handler”委托类型的变量。与此事件相关的信息封装在“事件名+Args”类型的事件参数中,此事件参数对象派生自EventArgs。


定义自己的事件:
自定义事件的基本方法步骤:
1、创建一个事件专用委托,此委托定义了事件响应方法的签名。
2、使用event关键字为对象定义一个事件字段
3、在合适的地方激发事件
示例代码:
image
从窗体:从窗体有一个按钮,为此按钮添加一个事件

        public delegate void ValueChanged(string value);
        public event ValueChanged ButtonClicked;
        private int counter;
        void button1_Click(object sender, EventArgs e)
        {
            counter++;
            if (ButtonClicked != null)
                ButtonClicked(counter.ToString());
        }

主窗体:
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            labelInfo = new Label();
            labelInfo.Dock = DockStyle.Fill;
            labelInfo.Font = new Font("Arail", 14);
            labelInfo.BackColor = Color.YellowGreen;
            labelInfo.TextAlign = ContentAlignment.MiddleCenter;
            this.Controls.Add(labelInfo);
            ChildForm c = new ChildForm();
            c.ButtonClicked += ShowInfo;
            c.Show();
        }
        private void ShowInfo(string value)
        {
            labelInfo.Text = value.ToString();
        }


分析:主窗体定义委托、事件、事件、事件触发机制,主窗体给从窗体的事件容器添加自定义事件函数。事件可以看成是从窗体类的一个属性或字段,到主窗体初始化从窗体类的时候给它的事件属性赋方法的值。

事件访问器:
默认情况下,当类声明事件时,编译器会将内存分配给一个事件字段,以存储事件信息。如果类具有许多未使用的事件,则它们会不必要的占用内存。
对于这种情况,可以使用.NET Framework提供的一个EventHandlerList类来减少内存占用。
EventHandlerList类可以看成是一个事件的集合,由它来保存各种事件的响应方法列表。
EventHandlerList对象可以保存多个事件的多个响应方法,不同的事件通过时间名字区分。只有需要响应的事件才拥有方法调用列表,才会在EventHandlerList对象中出现。
如果使用EventHandlerList对象来保存事件的响应方法,必须为每个事件编写特殊的时间访问器:

public delegate void OneDelegate(int value);
public class A
{
    private EventHandlerList events=new EventHandlerList();
    public event OneDelegate Event1
    {
        add{events.AddHandler("Event1",value);}
        remove{events.RemoveHandler("Event1",value);}
    }
}

可以看到,事件Event1不再是一个简单的带有event关键字的共有委托字段,而是一个事件类型的属性,add和remove为此事件属性的关键字。

激发事件:
定义了时间访问器的事件的激发方法与原先不同
(EventHandlerList对象名[事件名] as 定义事件的委托类型)(参数)
例子:

(events["Event1"] as OneDelegate)(100);


posted @ 2011-07-10 15:01  L Cooper  阅读(4419)  评论(3编辑  收藏  举报