zoukankan      html  css  js  c++  java
  • C#中的“代理”和“事件”

     

    事件(event)是一个非常重要的概念,我们的程序时刻都在触发和接收着各种事件:鼠标点击事件,键盘事件,以及处理操作系统的各种事件。所谓事件就是由某个对象发出的消息。比如用户按下了某个按钮,某个文件发生了改变,socket上有数据到达。触发事件的对象称作发送者(sender),捕获事件并且做出响应的对象称作接收者(receiver),一个事件可以存在多个接受者。

    在异步机制中,事件是线程之间进行通信的一个非常常用的方式。比如:用户在界面上按下一个按钮,执行某项耗时的任务。程序此时启动一个线程来处理这个任务,用户界面上显示一个进度条指示用户任务执行的状态。这个功能就可以使用事件来进行处理。可以将处理任务的类作为消息的发送者,任务开始时,发出“TaskStart”事件,任务进行中的不同时刻发出“TaskDoing”事件,并且携带参数说明任务进行的比例,任务结束的时候发出“TaskDone”事件,在画面中接收并且处理这些事件。这样实现了功能,并且界面和后台执行任务的模块耦合程度也是最低的。

    具体说C#语言,事件的实现依赖于“代理”(delegate)的概念,先了解一下代理。

    代理(delegate)

    delegate是C#中的一种类型,它实际上是一个能够持有对某个方法的引用的类。与其它的类不同,delegate类能够拥有一个签名(signature),并且它只能持有与它的签名相匹配的方法的引用。它所实现的功能与C/C++中的函数指针十分相似。它允许你传递一个类A的方法m给另一个类B的对象,使得类B的对象能够调用这个方法m。但与函数指针相比,delegate有许多函数指针不具备的优点。首先,函数指针只能指向静态函数,而delegate既可以引用静态函数,又可以引用非静态成员函数。在引用非静态成员函数时,delegate不但保存了对此函数入口指针的引用,而且还保存了调用此函数的类实例的引用。其次,与函数指针相比,delegate是面向对象、类型安全、可靠的受控(managed)对象。也就是说,runtime能够保证delegate指向一个有效的方法,你无须担心delegate会指向无效地址或者越界地址。

    实现一个delegate是很简单的,通过以下3个步骤即可实现一个delegate:

    1.  声明一个delegate对象,它应当与你想要传递的方法具有相同的参数和返回值类型。

    2.  创建delegate对象,并将你想要传递的函数作为参数传入。

    3.  在要实现异步调用的地方,通过上一步创建的对象来调用方法。

    下面是一个简单的例子:

    public class MyDelegateTest 



        
    // 步骤1,声明delegate对象 

        
    public delegate void MyDelegate(string name); 

        
    // 这是我们欲传递的方法,它与MyDelegate具有相同的参数和返回值类型 

        
    public static void MyDelegateFunc(string name) 

        


            Console.WriteLine(
    "Hello, {0}", name); 

        }
     

        
    public static void Main () 

        


            
    // 步骤2,创建delegate对象 

            MyDelegate md 
    = new MyDelegate(MyDelegateTest.MyDelegateFunc); 

            
    // 步骤3,调用delegate 

            md(
    "sam1111"); 

        }
     

    }



    输出结果是:Hello, sam1111

    下面我们来看看事件是如何处理的:

    事件(event)

    C#中的事件处理实际上是一种具有特殊签名的delegate,象下面这个样子:

    public delegate void MyEventHandler(object sender, MyEventArgs e);

    其中的两个参数,sender代表事件发送者,e是事件参数类。MyEventArg类用来包含与事件相关的数据,所有的事件参数类都必须从System.EventArgs类派生。当然,如果你的事件不含特别的参数,那么可以直接用System.EventArgs类作为参数。

    结合delegate的实现,我们可以将自定义事件的实现归结为以下几步:

    1:定义对象类型,它有两个参数,第一个参数是事件发送者对象,第二个参数是事件参数类对象。

    2:定义事件参数类,此类应当从System.EventArgs类派生。如果事件不带参数,这一步可以省略。

    3:定义事件处理方法,它应当与delegate对象具有相同的参数和返回值类型。

    4:用event关键字定义事件对象,它同时也是一个delegate对象。

    5:用+=操作符添加事件到事件队列中(-=操作符能够将事件从队列中删除)。

    6:在需要触发事件的地方用调用delegate的方式写事件触发方法。一般来说,此方法应为protected访问限制,既不能以public方式调用,但可以被子类继承。名字是可以是OnEventName

    7:在适当的地方调用事件触发方法触发事件。

    下面是一个例子,例子模仿容器和控件的模式,由控件触发一个事件,在容器中捕捉并且进行处理。

    事件的触发者:

    /// <summary> 
    /// 事件的触发者 
    /// </summary> 

    public class Control 


        
    public delegate void SomeHandler(object sender, System.EventArgs e); 

        
    /*
         * 可以采用系统提供的System.EventHandler, 这里为了说明情况使用了自己定义的delegate 
         * 如果需要在事件的参数中使用自己定义的类型,也要自己定义delegate 
         
    */
     
        
    //public event System.EventHandler SomeEvent; 
        public event SomeHandler SomeEvent; 
     
        
    public Control() 
        

            
    //这里使用的delegate必须与事件中声名的一致 
            
    //this.SomeEvent += new System.EventHandler(this.Control_SomeEvent); 
            this.SomeEvent += new SomeHandler(this.ProcessSomeEvent); 
        }
     

        
    public void RaiseSomeEvent() 
        

            EventArgs e 
    = new EventArgs(); 
            Console.Write(
    "Please input 'a':"); 
            
    string s = Console.ReadLine(); 

            
    //在用户输入一个小a的情况下触发事件,否则不触发 
            if (s == "a"
            

                SomeEvent(
    this, e); 
            }
     
        }
     

        
    //事件的触发者自己对事件进行处理,这个方法的参数必须和代理中声名的一致 
        private void ProcessSomeEvent(object sender, EventArgs e) 
        

            Console.WriteLine(
    "hello"); 
        }
     
    }



    事件的接收者:

    /// <summary> 
    /// 事件的接收和处理者 
    /// </summary> 

    class Container 

        
    private Control ctrl = new Control(); 

        
    public Container() 
        

            
    //这里使用的delegate必须与事件中声名的一致 
            
    //ctrl.SomeEvent += new EventHandler(this.OnSomeEvent); 
            ctrl.SomeEvent += new Control.SomeHandler(this.ResponseSomeEvent); 
            ctrl.RaiseSomeEvent(); 
        }
     

        
    public static void Main() 
        

            Container pane 
    = new Container(); 

            
    //这个readline是暂停程序用的,否则画面会一闪而过什么也看不见 
            Console.ReadLine(); 
        }
     

        
    //这是事件的接受者对事件的响应 
        private void ResponseSomeEvent(object sender, EventArgs e) 
        

            Console.WriteLine(
    "Some event occur!"); 
        }
     
    }



    程序运行的结果如下:

    please input 'a':a

    hello

    Some event occur!

     

    事件的应用

    例如有下面的需求需要实现:程序主画面中弹出一个子窗口。此时主画面仍然可以接收用户的操作(子窗口是非模态的)。子窗口上进行某些操作,根据操作的结果要在主画面上显示不同的数据。我发现一些程序员这样实现这个功能:

    主画面弹出子窗口后,将自己的指针交给子画面,然后在子画面中使用这个指针,调用主画面提供的方法,改变主画面上的数据显示。这样虽然可以达到目的,但是各个模块之间产生了很强的耦合。一般说来模块之间的调用应该是单方向的:模块A调用了模块B,模块B就不应该反向调用A,否则就破坏了程序的层次,加强了耦合程度,也使得功能的改变和追加变得很困难。

    这时正确的做法应该是在子窗口的操作过程中发出各种事件,而由主窗口捕捉这些事件进行处理,各个模块专心的做自己的事情,不需要过问其他模块的事情。

  • 相关阅读:
    人生转折点:弃文从理
    人生第一站:大三暑假实习僧
    监听器启动顺序和java常见注解
    java常识和好玩的注释
    182. Duplicate Emails (Easy)
    181. Employees Earning More Than Their Managers (Easy)
    180. Consecutive Numbers (Medium)
    178. Rank Scores (Medium)
    177. Nth Highest Salary (Medium)
    176. Second Highest Salary(Easy)
  • 原文地址:https://www.cnblogs.com/zhangzheny/p/1003389.html
Copyright © 2011-2022 走看看