您的位置:新葡亰496net > 奥门新萄京娱乐场 > 信托和事件,逐步边缘化的长兄

信托和事件,逐步边缘化的长兄

发布时间:2019-06-16 03:49编辑:奥门新萄京娱乐场浏览(73)

    事件是C#的基础之一,学好事件对于了解.NET框架大有好处。

      如果对事件一点都不了解或者是模棱两可的话,建议先去看张子阳的委托与事件的文章(比较长,或许看完了,也忘记看这一篇了,没事,我会原谅你的),废话不多说,开始进入正题。本记录不是记录传统的事件(CLR事件),而是记录WPF中常用的事件——路由事件,由于路由事件“传播”的时间是沿着可视树传播的,所以在记录开始之前还是了解一下逻辑树和可视树。

    [转载]C#委托和事件(Delegate、Event、EventHandler、EventArgs),

    原文链接:

    事件最常见的比喻就是订阅,即,如果你订阅了我的博客,那么,当我发布新博客的时候,你就会得到通知。

     一、逻辑树和可视树

      在WPF中有两种树:逻辑树(Logical Tree)和可视树(Visual Tree),XAML是表达WPF的一棵树。逻辑树完全是由布局组件和控件构成。如果我们把逻辑树延伸至Template组件级别,我们就得到了可视树,所以可视树把树分的更细致。由于本记录重在记录事件,所以不做过多表述逻辑树和可视树的内容。关于逻辑树和可视树的区别可以参考。

    原文链接:

    14.1、委托

    当要把方法作为实参传送给其他方法的形参时,形参需要使用委托。委托是一个类型,是一个函数指针类型,这个类型将该委托的实例化对象所能指向的函数的细节封装起来了,即规定了所能指向的函数的签名,也就是限制了所能指向的函数的参数和返回值。当实例化委托的时候,委托对象会指向某一个匹配的函数,实质就是将函数的地址赋值给了该委托的对象,然后就可以通过该委托对象来调用所指向的函数了。利用委托,程序员可以在委托对象中封装一个方法的引用,然后委托对象作为形参将被传给调用了被引用方法的代码,而不需要知道在编译时刻具体是哪个方法被调用。

    一般的调用函数,我们都不会去使用委托,因为如果只是单纯的调用函数,使用委托更麻烦一些;但是如果想将函数作为实参,传递给某个函数的形参,那么形参就一定要使用委托来接收实参,一般使用方法是:在函数外面定义委托对象,并指向某个函数,再将这个对象赋值给函数的形参,形参也是该委托类型的对象变量,函数里面再通过形参来调用所指向的函数。

    一、了解C#中的预定义事件处理机制

    而这个过程就是事件,或者说是事件运行的轨迹。

    二、路由事件

    14.1、委托

    当要把方法作为实参传送给其他方法的形参时,形参需要使用委托。委托是一个类型,是一个函数指针类型,这个类型将该委托的实例化对象所能指向的函数的细节封装起来了,即规定了所能指向的函数的签名,也就是限制了所能指向的函数的参数和返回值。当实例化委托的时候,委托对象会指向某一个匹配的函数,实质就是将函数的地址赋值给了该委托的对象,然后就可以通过该委托对象来调用所指向的函数了。利用委托,程序员可以在委托对象中封装一个方法的引用,然后委托对象作为形参将被传给调用了被引用方法的代码,而不需要知道在编译时刻具体是哪个方法被调用。

    一般的调用函数,我们都不会去使用委托,因为如果只是单纯的调用函数,使用委托更麻烦一些;但是如果想将函数作为实参,传递给某个函数的形参,那么形参就一定要使用委托来接收实参,一般使用方法是:在函数外面定义委托对象,并指向某个函数,再将这个对象赋值给函数的形参,形参也是该委托类型的对象变量,函数里面再通过形参来调用所指向的函数。

    14.1.1、定义委托

    语法如下

    delegate  result-type   Identifier ([parameters]);

    说明:

    result-type:返回值的类型,和方法的返回值类型一致

    Identifier:委托的名称

    parameters:参数,要引用的方法带的参数

    小结

    当定义了委托之后,该委托的对象一定可以而且也只能指向该委托所限制的函数。即参数的个数、类型、顺序都要匹配,返回值的类型也要匹配。

    因为定义委托相当于是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在一个类的内部定义,那么此时就要通过该类的类名来调用这个委托(委托必须是public、internal),也可以在任何类的外部定义,那么此时在命名空间中与类的级别是一样的。根据定义的可见性,可以在委托定义上添加一般的访问修饰符:当委托定义在类的外面,那么可以加上public、internal修饰符;如果委托定义到类的内部,那么可以加上public、 private、 protected、internal。一般委托都是定义在类的外面的。

        在写代码前我们先来熟悉.net框架中和事件有关的类和委托,了解C#中预定义事件的处理。

    事件是发散,以我的博客为核心,向所有订阅者发送消息。我们把这种发散称之为[多播]。

    2.1、小记事件

      如果看完了委托与事件的文章,相信会对事件有更进一步的认识了,但还是要把一些基础的地方再记录一下。一个事件要有下面几个要素,才会变的有意义:

      • 事件的拥有者(sender)——即消息的发送者。
      • 事件发送的消息(EventAgs)
      • 事件的响应者——消息的接收者(对象)。
      • 响应者的处理器——消息的接受者要对消息作出处理的方法(方法名)。
      • 响应者对发送者事件的订阅

    14.1.1、定义委托

    语法如下

    delegate  result-type   Identifier ([parameters]);

    说明:

    result-type:返回值的类型,和方法的返回值类型一致

    Identifier:委托的名称

    parameters:参数,要引用的方法带的参数

    小结

    当定义了委托之后,该委托的对象一定可以而且也只能指向该委托所限制的函数。即参数的个数、类型、顺序都要匹配,返回值的类型也要匹配。

    因为定义委托相当于是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在一个类的内部定义,那么此时就要通过该类的类名来调用这个委托(委托必须是public、internal),也可以在任何类的外部定义,那么此时在命名空间中与类的级别是一样的。根据定义的可见性,可以在委托定义上添加一般的访问修饰符:当委托定义在类的外面,那么可以加上public、internal修饰符;如果委托定义到类的内部,那么可以加上public、 private、 protected、internal。一般委托都是定义在类的外面的。

    14.1.2、实例化委托

    Identifier  objectName  =  new  Identifier( functionName);

    实例化委托的实质就是将某个函数的地址赋值给委托对象。在这里:

    Identifier :这个是委托名字。

    objectName :委托的实例化对象。

    functionName:是该委托对象所指向的函数的名字。对于这个函数名要特别注意:定义这个委托对象肯定是在类中定义的,那么如果所指向的函数也在该类中,不管该函数是静态还是非静态的,那么就直接写函数名字就可以了;如果函数是在别的类里面定义的public、

    internal,但是如果是静态,那么就直接用类名.函数名,如果是非静态的,那么就类的对象名.函数名,这个函数名与该对象是有关系的,比如如果函数中出现了this,表示的就是对当前对象的调用。

        EventArgs是包含事件数据的类的基类,用于传递事件的细节。

    最常见的事件用途是窗体编程,在Windows窗体应用程序和WPF应用程序中。

    2.2 初试路由事件

      我们建立一个winform项目,然后在窗体上添加一个按钮,双击添加一个处理器,会发现private void btn_Click(object sender, EventArgs e)处理器要处理消息是EventArgs类型的,这里对应的是传统的事件(我们叫它CLR事件)。同样我们建立一个WPF项目,然后添加一个按钮,双击添加一个处理器,会发现private void Button_Click(object sender, RoutedEventArgs e)处理器要处理的消息是RoutedEventArgs类型的,这里对应的是路由事件。两者有什么区别呢。让我们先看看CLR事件的弊端,就如(this.btn.Click = new System.EventHandler(this.btn_Click);)每一个消息都是从发送到响应的一个过程,当一个处理器要用多次,必须建立显式的点对点订阅关系(窗体对按钮事件的订阅,如果是再有一个按钮的话,就要再来一次订阅);还有一个弊端是:事件的宿主必须能够直接访问事件的响应者,不然无法建立订阅关系(如有两个组件,点击组件一的按钮,想让组件二响应事件,那么就让组件二向组件一的按钮暴露一个可以访问的事件,这样如果再多几个嵌套,会出现事件链,有暴露如果暴露不当就存在着威胁)。路由事件除了能很好的解决上面的问题,还有一个是路由事件在有路的情况下,能很好的按照规定的方式传播事件,因为XAML的树状结构,构成了一条条的道路,所以在WPF中,引入了路由事件。举个例子:如果窗体要以相同的方式处理两个按钮的事件,我们就可以用一句代码就搞定了,this.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.ButtonClicked));这样就减少代码量。下面通过一个例子初试一下路由事件。 给出XAML代码:

    <Window x:Class="Chapter_06.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid x:Name="GridRoot" Background="Lime">
            <Grid x:Name="gridA" Margin="10" Background="Blue">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <Canvas x:Name="canvasLeft" Grid.Column="0" Background="Red" Margin="10">
                    <Button x:Name="buttonLeft" Content="left" Width="40" Height="100" Margin="10"/>
                </Canvas>
                <Canvas x:Name="canvasRight" Grid.Column="1" Background="Yellow" Margin="10">
                    <Button x:Name="buttonRight" Content="right" Width="40" Height="100" Margin="10" />
                </Canvas>
            </Grid>
        </Grid>
    </Window>
    

      我们点击按钮时,无论是buttonLeft还是buttonRight单击都能显示按钮的名称。两个按钮到顶部的window有唯一条路,左边的按钮对应的路:buttonLeft->canvasLeft->gridA->GridRoot->Window,右边按钮对应的路:buttonRight->canvasRight->gridA->GridRoot->Window。如果GridRoot订阅两个处理器,那么处理器应该是相同的。后台代码为:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace Chapter_06
    {
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.GridRoot.AddHandler(Button.ClickEvent,new RoutedEventHandler(this.ButtonClicked));
            }
            private void ButtonClicked(object sender, RoutedEventArgs e)
            {
                MessageBox.Show((e.OriginalSource as FrameworkElement).Name);
            }
        }
    }
    

      下面先解释一下路由事件是怎么沿着可视树来传播的,当Button被点击,Button就开始发送消息了,可视树上的元素如果订阅了Button的点击事件,那么才会根据消息来作出相应的反应,如果没有订阅的话,就无视它发出的消息,当然我们还可以控制它的消息的传播方式,是从树根到树叶传播,还是树叶向树根传播以及是直接到达目的传播,不仅如此,还能控制消息传到某个元素时,停止传播。具体的会在后面记录到。其次是this.GridRoot.AddHandler(Button.ClickEvent,new RoutedEventHandler(this.ButtonClicked));订阅事件时,第一个参数是路由事件类型,在这里用的是Button的ClickEvent,就像依赖属性一样,类名加上依赖属性,这里是类名加上路由事件。另外一个是e.OriginalSource与e.Source的区别。由于消息每传一站,都要把消息交个一个控件(此控件成为了消息的发送地点),e.Source为逻辑树上的源头,要想获取原始发消息的控件(可视树的源头)要用e.OriginalSource。最后说明一下,怎么在XAML中添加订阅事件。直接用<Grid x:Name="gridA" Margin="10" Background="Blue" Button.Click="ButtonClicked">在.net平台上不能智能提示Button,因为Click是继承与ButtonBase的事件,XAML只认识含有Click事件的元素,但是要大胆的写下去才能成功。运行上面代码,点击左边按钮,效果如图1:

     

    图片 1

    图1

    默认的路由消息里面属性有四个,如图2,可以自行转到定义看一下其属性代表的意义。

    图片 2

    图2

     

    14.1.2、实例化委托

    Identifier  objectName  =  new  Identifier( functionName);

    实例化委托的实质就是将某个函数的地址赋值给委托对象。在这里:

    Identifier :这个是委托名字。

    objectName :委托的实例化对象。

    functionName:是该委托对象所指向的函数的名字。对于这个函数名要特别注意:定义这个委托对象肯定是在类中定义的,那么如果所指向的函数也在该类中,不管该函数是静态还是非静态的,那么就直接写函数名字就可以了;如果函数是在别的类里面定义的public、

    internal,但是如果是静态,那么就直接用类名.函数名,如果是非静态的,那么就类的对象名.函数名,这个函数名与该对象是有关系的,比如如果函数中出现了this,表示的就是对当前对象的调用。

    14.1.3、委托推断

    C# 2.0用委托推断扩展了委托的语法。当我们需要定义委托对象并实例化委托的时候,就可以只传送函数的名称,即函数的地址:

    Identifier  objectName  =  functionName;

    这里面的functionName与14.1.2节中实例化委托的functionName是一样的,没什么区别,满足上面的规则。

    C#编译器创建的代码是一样的。编译器会用objectName检测需要的委托类型,因此会创建Identifier委托类型的一个实例,用functionName即方法的地址传送给Identifier的构造函数。

    注意:

    不能在functionName后面加括号和实参,然后把它传送给委托变量。调用方法一般会返回一个不能赋予委托变量的普通对象,除非这个方法返回的是一个匹配的委托对象。总之:只能把相匹配的方法的地址赋予委托变量。

    委托推断可以在需要委托实例化的任何地方使用,就跟定义普通的委托对象是一样的。委托推断也可以用于事件,因为事件基于委托(参见本章后面的内容)。

        EventHandler是一个委托声明如下

    当在窗体中点击按钮,移动鼠标等事件时,相应的后台程序会收到通知,再执行代码。

     2.3自定义路由事件

      通过上面的小试路由事件,应该对路由事件有些了解了,下面就记录一下如何自定义一个路由事件。大致分为三个步骤:1.声明并注册路由事件,2.为路由事件添加CLR事件包装,3.创建可以激发路由事件的方法。回忆一下依赖属性,前两个步骤应该和路由事件很相似吧。下面将三个步骤分开来说明:

    第一步:声明并注册路由事件       

           //***Event为路由事件名,类型为路由事件类型
           //注册事件用的是EventManager.RegisterRoutedEvent
          //第一个参数为事件名(下面的***都为同一个单词)
           //第二个参数为事件传播的策略,有三种策略:Bubble(冒泡式),Tunnel(隧道式),Direct(直达式)分别对应上面的三种青色字体的三种方式
           //第三个参数用于指定事件处理器的类型,该类型必须为委托类型,并且不能为 null。
           //第四个参数为路由事件的宿主    
           public static readonly RoutedEvent ***Event = EventManager.RegisterRoutedEvent("***", RoutingStrategy.Bubble,
                                                                 typeof(***RouteEventHandler), typeof(ClassName));
    

    第二步:为路由事件添加CLR事件包装

           /*包装事件
            *这里与传统的数据差别是把 =和-=换成了AddHandler和RemovedHandler
            */
            public event RoutedEventHandler ***
            {
                add { this.AddHandler(***Event, value); }
                remove { this.RemoveHandler(***Event, value); }
            }
    

    第三步:创建可以激发路由事件的方法

            /*对于控件的事件,一般是重写宿主事件对应的方法(如Button的click事件和OnClick()方法相对应):新建消息,并把消息与路由事件相关联,
             *通过调用元素的RaiseEvent方法把时间传送出去(这里与包装器的CRL事件毫不相干),在CLR事件是用Invoke方法,下面以按钮为例
             */
    
            protected override void OnClick()
            {
                base.OnClick();
                ***EventArgs args = new ***EventArgs(***Event, this);
                this.RaiseEvent(args);
            }
    

     下面我们就来实现一个简单的自定义路由功能,当路由飘过一个控件的时间,显示通过该控件的时间。 上面介绍的差不多了,所以就直接上代码,有需要解释的话,再一个个解释。

    下面是XAML代码:

    <Window x:Class="DefineEvent.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:DefineEvent" 
            Title="Routed Event" x:Name="window_1" Height="350" Width="525" local:TimeButton.ReportTime="ReportTimeHandler" >
        <Grid x:Name="grid_1" local:TimeButton.ReportTime="ReportTimeHandler" >
            <Grid x:Name="grid_2" local:TimeButton.ReportTime="ReportTimeHandler"  >
                <Grid x:Name="grid_3" local:TimeButton.ReportTime="ReportTimeHandler"  >
                    <StackPanel x:Name="stackPanel_1" local:TimeButton.ReportTime="ReportTimeHandler" >
                        <ListBox x:Name="listBox" />
                        <local:TimeButton x:Name="timeButton" Width="200" Height="80" Content="显示到达某个位置的时间" ReportTime="ReportTimeHandler"/>
                    </StackPanel>
                </Grid>
            </Grid>
        </Grid>
    </Window>
    

    下面是CS代码

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using System.ComponentModel;
    
    namespace DefineEvent
    {
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        delegate void ReportTimeRouteEventHandler(object sender, ReportTimeEventArgs e);
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
            private void ReportTimeHandler(object sender, ReportTimeEventArgs e)
            {
                FrameworkElement element = sender as FrameworkElement;
                string timeStr = e.ClickTime.ToString("yyyyMMddHHmmss");
                string content = string.Format("{0}到达{1}", timeStr, element.Name);
                this.listBox.Items.Add(content);
            }
        }
        //创建消息类型,在此可以附加自己想要的信息
        public class ReportTimeEventArgs : RoutedEventArgs
        {
            public ReportTimeEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { }
            public DateTime ClickTime { get; set; }
        }
       public class TimeButton : Button
        {
            //1、为元素声明并注册事件
            public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Bubble,
                                                                 typeof(ReportTimeRouteEventHandler), typeof(TimeButton));
    
           //2、包装事件
            public event RoutedEventHandler ReportTime
            {
                add { this.AddHandler(ReportTimeEvent,value); }
                remove { this.RemoveHandler(ReportTimeEvent,value); }
            }
            //3、创建激发事件的方法
            protected override void OnClick()
            {
                base.OnClick();
                ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent,this);
                args.ClickTime = DateTime.Now;
                this.RaiseEvent(args);
            }
        }
    }
    

    运行单击按钮的效果为图3:

    图片 3

    图3

      注意下面的一行代码,在声明和定义路由事件时,第三个参数,委托的类型和处理器方法的的参数一定要相同,不然会报错,可以把下面一句中的ReportTimeRouteEventHandler换成RoutedEventHandler试试,会出现:无法从文本“ReportTimeHandler”创建“ReportTime”。”,行号为“5”,行位置为“30”,的问题,这里主要的原因就是委托的参数和RoutedEventHandler的参数不一致,虽然都是(sender,  e);但是e的类型已经发生了变化,成为了ReportTimeEventArgs类型的。所以在使用之前,声明一个委托就可以了。还有个方法是使用EventHandler<ReportTimeEventArgs>替换ReportTimeRouteEventHandler,其实二者的用法差不多,只是不同的写法,但是是我感觉第一种写法会更好理解。具体关于EventHandler<ReportTimeEventArgs>的含义请参考。我们同样可以使用让第二个参数改变成另外两种类型的看看测试结果。

    public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Tunnel,
                                                                 typeof(ReportTimeRouteEventHandler), typeof(TimeButton));
    

     如果我们希望当事件传递到grid_2上面就停止了,我们可以这样做:在ReportTimeHandler函数中添加代码。

                if (element.Name == "grid_2")
                    e.Handled = true;
    

    14.1.3、委托推断

    C# 2.0用委托推断扩展了委托的语法。当我们需要定义委托对象并实例化委托的时候,就可以只传送函数的名称,即函数的地址:

    Identifier  objectName  =  functionName;

    这里面的functionName与14.1.2节中实例化委托的functionName是一样的,没什么区别,满足上面的规则。

    C#编译器创建的代码是一样的。编译器会用objectName检测需要的委托类型,因此会创建Identifier委托类型的一个实例,用functionName即方法的地址传送给Identifier的构造函数。

    注意:

    不能在functionName后面加括号和实参,然后把它传送给委托变量。调用方法一般会返回一个不能赋予委托变量的普通对象,除非这个方法返回的是一个匹配的委托对象。总之:只能把相匹配的方法的地址赋予委托变量。

    委托推断可以在需要委托实例化的任何地方使用,就跟定义普通的委托对象是一样的。委托推断也可以用于事件,因为事件基于委托(参见本章后面的内容)。

    14.1.4、匿名方法

    到目前为止,要想使委托工作,方法必须已经存在。但实例化委托还有另外一种方式:即通过匿名方法。

    用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化委托时,就有区别了。下面是一个非常简单的控制台应用程序,说明了如何使用匿名方法:

    using System;

    namespace Wrox.ProCSharp.Delegates

    {

      class Program

      {

        delegate string DelegateTest(string val);

        static void Main()

        {

          string mid = ", middle part,";

          //在方法中定义了方法

          DelegateTest  anonDel = delegate(string param)

          {

            param = mid;

            param = " and this was added to the string.";

            return param;

          };

          Console.WriteLine(anonDel("Start of string"));

        }

      }

    }

    委托DelegateTest在类Program中定义,它带一个字符串参数。有区别的是Main方法。在定义anonDel时,不是传送已知的方法名,而是使用一个简单的代码块:它前面是关键字delegate,后面是一个参数:

    delegate(string param)

    {

      param = mid;

      param = " and this was added to the string.";

      return param;

    };

    匿名方法的优点是减少了要编写的代码。方法仅在有委托使用时才定义。在为事件定义委托时,这是非常显然的。(本章后面探讨事件。)这有助于降低代码的复杂性,尤其是定义了好几个事件时,代码会显得比较简单。使用匿名方法时,代码执行得不太快。编译器仍定义了一个方法,该方法只有一个自动指定的名称,我们不需要知道这个名称。

    在使用匿名方法时,必须遵循两个规则:

    1、在匿名方法中不能使用跳转语句跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能跳到该匿名方法的内部。

    2、在匿名方法内部不能访问不安全的代码。另外,也不能访问在匿名方法外部使用的ref和out参数。但可以使用在匿名方法外部定义的其他变量。方法内部的变量、方法的参数可以任意的使用。

    如果需要用匿名方法多次编写同一个功能,就不要使用匿名方法。而编写一个指定的方法比较好,因为该方法只需编写一次,以后可通过名称引用它。

             public delegate void EventHandler( object sender , EventArgs e )

    事件的定义

     三、总结

      本篇记录的内容虽然不多,但是感觉记录的时间特别费力,主要是因为对事件的几个组成部分还不是非常熟练,而且在理解路由事件时,还要先理解逻辑树与可视树。最终还是把这一章看完了,但这个只是开始。

      文章主要记录了路由事件的在可视树上的传播以及自定义路由事件的实现。如果在文章有不同的见解或建议,欢迎交流! 下一篇:《深入浅出WPF》笔记——命令篇

     

    14.1.4、匿名方法

    到目前为止,要想使委托工作,方法必须已经存在。但实例化委托还有另外一种方式:即通过匿名方法。

    用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化委托时,就有区别了。下面是一个非常简单的控制台应用程序,说明了如何使用匿名方法:

    using System;

    namespace Wrox.ProCSharp.Delegates

    {

      class Program

      {

        delegate string DelegateTest(string val);

        static void Main()

        {

          string mid = ", middle part,";

          //在方法中定义了方法

          DelegateTest  anonDel = delegate(string param)

          {

            param = mid;

            param = " and this was added to the string.";

            return param;

          };

          Console.WriteLine(anonDel("Start of string"));

        }

      }

    }

    委托DelegateTest在类Program中定义,它带一个字符串参数。有区别的是Main方法。在定义anonDel时,不是传送已知的方法名,而是使用一个简单的代码块:它前面是关键字delegate,后面是一个参数:

    delegate(string param)

    {

      param = mid;

      param = " and this was added to the string.";

      return param;

    };

    匿名方法的优点是减少了要编写的代码。方法仅在有委托使用时才定义。在为事件定义委托时,这是非常显然的。(本章后面探讨事件。)这有助于降低代码的复杂性,尤其是定义了好几个事件时,代码会显得比较简单。使用匿名方法时,代码执行得不太快。编译器仍定义了一个方法,该方法只有一个自动指定的名称,我们不需要知道这个名称。

    在使用匿名方法时,必须遵循两个规则:

    1、在匿名方法中不能使用跳转语句跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能跳到该匿名方法的内部。

    2、在匿名方法内部不能访问不安全的代码。另外,也不能访问在匿名方法外部使用的ref和out参数。但可以使用在匿名方法外部定义的其他变量。方法内部的变量、方法的参数可以任意的使用。

    如果需要用匿名方法多次编写同一个功能,就不要使用匿名方法。而编写一个指定的方法比较好,因为该方法只需编写一次,以后可通过名称引用它。

    14.1.5、多播委托

    前面使用的每个委托都只包含一个方法调用,调用委托的次数与调用方法的次数相同,如果要调用多个方法,就需要多次给委托赋值,然后调用这个委托。

    委托也可以包含多个方法,这时候要向委托对象中添加多个方法,这种委托称为多播委托,多播委托有一个方法列表,如果调用多播委托,就可以连续调用多个方法,即先执行某一个方法,等该方法执行完成之后再执行另外一个方法,这些方法的参数都是一样的,这些方法的执行是在一个线程中执行的,而不是每个方法都是一个线程,最终将执行完成所有的方法。

    如果使用多播委托,就应注意对同一个委托调用方法链的顺序并未正式定义,调用顺序是不确定的,不一定是按照添加方法的顺序来调用方法,因此应避免编写依赖于以特定顺序调用方法的代码。如果要想确定顺序,那么只能是单播委托,调用委托的次数与调用方法的次数相同。

    多播委托的各个方法签名最好是返回void;否则,就只能得到委托最后调用的一个方法的结果,而最后调用哪个方法是无法确定的。

    多播委托的每一个方法都要与委托所限定的方法的返回值、参数匹配,否则就会有错误。

    我自己写代码测试,测试的结果目前都是调用顺序和加入委托的顺序相同的,但是不排除有不同的时候。 

    delegate result-type Identifier ([parameters]); 

        注意这里的参数,前者是一个对象(其实这里传递的是对象的引用,如果是button1的click事件则sender就是button1),后面是包含事件数据的类的基类。

    信托和事件,逐步边缘化的长兄。官方对事件的说明是这样的:类或对象可以通过事件向其他类或对象通知发生的相关事情。

    14.1.5、多播委托

    前面使用的每个委托都只包含一个方法调用,调用委托的次数与调用方法的次数相同,如果要调用多个方法,就需要多次给委托赋值,然后调用这个委托。

    委托也可以包含多个方法,这时候要向委托对象中添加多个方法,这种委托称为多播委托,多播委托有一个方法列表,如果调用多播委托,就可以连续调用多个方法,即先执行某一个方法,等该方法执行完成之后再执行另外一个方法,这些方法的参数都是一样的,这些方法的执行是在一个线程中执行的,而不是每个方法都是一个线程,最终将执行完成所有的方法。

    如果使用多播委托,就应注意对同一个委托调用方法链的顺序并未正式定义,调用顺序是不确定的,不一定是按照添加方法的顺序来调用方法,因此应避免编写依赖于以特定顺序调用方法的代码。如果要想确定顺序,那么只能是单播委托,调用委托的次数与调用方法的次数相同。

    多播委托的各个方法签名最好是返回void;否则,就只能得到委托最后调用的一个方法的结果,而最后调用哪个方法是无法确定的。

    多播委托的每一个方法都要与委托所限定的方法的返回值、参数匹配,否则就会有错误。

    我自己写代码测试,测试的结果目前都是调用顺序和加入委托的顺序相同的,但是不排除有不同的时候。 

    delegate result-type Identifier ([parameters]); 

    14.1.5.1、委托运算符 =

    Identifier  objectName  =  new  Identifier( functionName);

    或者

    Identifier  objectName  =  functionName;

    这里的“=”号表示清空 objectName 的方法列表,然后将 functionName 加入到     objectName 的方法列表中。

        下面我们研究一下Button类看看其中的事件声明(使用WinCV工具查看),以Click事件为例。

    换成正常语言就是,事件可以定义成静态的或普通的,所以事件就可以由声明的对象调用,也可以直接通过类调用静态事件。

    14.1.5.1、委托运算符 =

    Identifier  objectName  =  new  Identifier( functionName);

    或者

    Identifier  objectName  =  functionName;

    这里的“=”号表示清空 objectName 的方法列表,然后将 functionName 加入到     objectName 的方法列表中。

    14.1.5.2、委托运算符  =

    objectName   =  new  Identifier( functionName1);

    或者

    objectName   =  functionName1;

    这里的“ =”号表示在原有的方法列表不变的情况下,将 functionName1  加入到     objectName 的方法列表中。可以在方法列表中加上多个相同的方法,执行的时候也会执行完所有的函数,哪怕有相同的,就会多次执行同一个方法。

    注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

    Identifier  objectName    =  new  Identifier( functionName1);或者

    Identifier  objectName  =  functionName1;就会报错。

             public event EventHandler Click;

    事件是C#中的一种类型,除了框架为我们定义好的事件外,我们还可以自定义事件,用event关键字来声明。

    14.1.5.2、委托运算符  =

    objectName   =  new  Identifier( functionName1);

    或者

    objectName   =  functionName1;

    这里的“ =”号表示在原有的方法列表不变的情况下,将 functionName1  加入到     objectName 的方法列表中。可以在方法列表中加上多个相同的方法,执行的时候也会执行完所有的函数,哪怕有相同的,就会多次执行同一个方法。

    注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

    Identifier  objectName    =  new  Identifier( functionName1);或者

    Identifier  objectName  =  functionName1;就会报错。

    14.1.5.3、委托运算符 -=:

    objectName  -=  new  Identifier( functionName1);

    或者

    objectName  -=  functionName1;

    这里的“-=”号表示在 objectName 的方法列表中减去一个functionName1。可以在方法列表中多次减去相同的方法,减一次只会减一个方法,如果列表中无此方法,那么减就没有意义,对原有列表无影响,也不会报错。

    注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

    Identifier  objectName    -=  new  Identifier( functionName1);或者

    Identifier  objectName  -=  functionName1;就会报错。

        这里定义了一个EventHandler类型的事件Click

    下面我们来看最基础的事件定义。

    14.1.5.3、委托运算符 -=:

    objectName  -=  new  Identifier( functionName1);

    或者

    objectName  -=  functionName1;

    这里的“-=”号表示在 objectName 的方法列表中减去一个functionName1。可以在方法列表中多次减去相同的方法,减一次只会减一个方法,如果列表中无此方法,那么减就没有意义,对原有列表无影响,也不会报错。

    注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

    Identifier  objectName    -=  new  Identifier( functionName1);或者

    Identifier  objectName  -=  functionName1;就会报错。

    14.1.5.4、委托运算符  、-:

    Identifier  objectName  =  objectName  functionName1 - functionName1;或者

    Identifier  objectName  =  new  Identifier( functionName1) functionName1 - functionName1;

    对于这种 、-表达式,在第一个符号 或者-的前面必须是委托而不能是方法,后面的 、-左右都随便。这个不是绝对规律,还有待进一步的研究。

        前面的内容都是C#在类库中已经为我们定义好了的。下面我们来看编程时产生的代码。

    public delegate void TestDelegate(string message);                                                  
    public event TestDelegate testEvent;
    
    14.1.5.4、委托运算符  、-:

    Identifier  objectName  =  objectName  functionName1 - functionName1;或者

    Identifier  objectName  =  new  Identifier( functionName1) functionName1 - functionName1;

    对于这种 、-表达式,在第一个符号 或者-的前面必须是委托而不能是方法,后面的 、-左右都随便。这个不是绝对规律,还有待进一步的研究。

    14.1.5.5、多播委托的异常处理

    通过一个委托调用多个方法还有一个大问题。多播委托包含一个逐个调用的委托集合。如果通过委托调用的一个方法抛出了异常,整个迭代就会停止。下面是MulticastIteration示例。其中定义了一个简单的委托DemoDelegate,它没有参数,返回void。这个委托调用方法One()和Two(),这两个方法满足委托的参数和返回类型要求。注意方法One()抛出了一个异常:

    using System;

    namespace Wrox.ProCSharp.Delegates

    {

    public delegate void DemoDelegate();

    class Program

    {

    static void One()

    {

    Console.WriteLine("One");

    throw new Exception("Error in one");

    }

    static void Two()

    {

    Console.WriteLine("Two");

    }

    在Main()方法中,创建了委托d1,它引用方法One(),接着把Two()方法的地址添加到同一个委托中。调用d1委托,就可以调用这两个方法。异常在try/catch块中捕获:

    static void Main()

    {

    DemoDelegate d1 = One;

    d1 = Two;

    try

    {

    d1();

    }

    catch (Exception)

    {

    Console.WriteLine("Exception caught");

    }

    }

    }

    }

    委托只调用了第一个方法。第一个方法抛出了异常,所以委托的迭代会停止,不再调用Two()方法。当调用方法的顺序没有指定时,结果会有所不同。

    One

    Exception Caught

    注意:

    多播委托包含一个逐个调用的委托集合。如果通过委托调用的一个方法抛出了异常,整个迭代就会停止。即如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。

    在这种情况下,为了避免这个问题,应手动迭代方法列表。Delegate类定义了方法GetInvocationList(),它返回一个Delegate对象数组。现在可以使用这个委托调用与委托直接相关的方法,捕获异常,并继续下一次迭代。

    static void Main()

    {

    DemoDelegate d1 = One;

    d1 = Two;

    Delegate[] delegates = d1.GetInvocationList();

    foreach (DemoDelegate d in delegates)

    {

    try

    {

    d();

    }

    catch (Exception)

    {

    Console.WriteLine("Exception caught");

    }

    }

    }

    修改了代码后运行应用程序,会看到在捕获了异常后,将继续迭代下一个方法。

    One

    Exception caught

    Two

    注意:其实如果在多播委托的每个具体的方法中捕获异常,并在内部处理,而不抛出异常,一样能实现多播委托的所有方法执行完毕。这种方式与上面方式的区别在于这种方式的宜昌市在函数内部处理的,上面那种方式的异常是在函数外面捕获并处理的。

            private void button1_Click(object sender, System.EventArgs e)
            {
                ...
    信托和事件,逐步边缘化的长兄。        }

    我们首先定义了一个委托,然后利用event关键字,定义一个事件。

    14.1.5.5、多播委托的异常处理

    通过一个委托调用多个方法还有一个大问题。多播委托包含一个逐个调用的委托集合。如果通过委托调用的一个方法抛出了异常,整个迭代就会停止。下面是MulticastIteration示例。其中定义了一个简单的委托DemoDelegate,它没有参数,返回void。这个委托调用方法One()和Two(),这两个方法满足委托的参数和返回类型要求。注意方法One()抛出了一个异常:

    using System;

    namespace Wrox.ProCSharp.Delegates

    {

    public delegate void DemoDelegate();

    class Program

    {

    static void One()

    {

    Console.WriteLine("One");

    throw new Exception("Error in one");

    }

    static void Two()

    {

    Console.WriteLine("Two");

    }

    在Main()方法中,创建了委托d1,它引用方法One(),接着把Two()方法的地址添加到同一个委托中。调用d1委托,就可以调用这两个方法。异常在try/catch块中捕获:

    static void Main()

    {

    DemoDelegate d1 = One;

    d1 = Two;

    try

    {

    d1();

    }

    catch (Exception)

    {

    Console.WriteLine("Exception caught");

    }

    }

    }

    }

    委托只调用了第一个方法。第一个方法抛出了异常,所以委托的迭代会停止,不再调用Two()方法。当调用方法的顺序没有指定时,结果会有所不同。

    One

    Exception Caught

    注意:

    多播委托包含一个逐个调用的委托集合。如果通过委托调用的一个方法抛出了异常,整个迭代就会停止。即如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。

    在这种情况下,为了避免这个问题,应手动迭代方法列表。Delegate类定义了方法GetInvocationList(),它返回一个Delegate对象数组。现在可以使用这个委托调用与委托直接相关的方法,捕获异常,并继续下一次迭代。

    static void Main()

    {

    DemoDelegate d1 = One;

    d1 = Two;

    Delegate[] delegates = d1.GetInvocationList();

    foreach (DemoDelegate d in delegates)

    {

    try

    {

    d();

    }

    catch (Exception)

    {

    Console.WriteLine("Exception caught");

    }

    }

    }

    修改了代码后运行应用程序,会看到在捕获了异常后,将继续迭代下一个方法。

    One

    Exception caught

    Two

    注意:其实如果在多播委托的每个具体的方法中捕获异常,并在内部处理,而不抛出异常,一样能实现多播委托的所有方法执行完毕。这种方式与上面方式的区别在于这种方式的宜昌市在函数内部处理的,上面那种方式的异常是在函数外面捕获并处理的。

    14.1.6、通过委托对象来调用它所指向的函数

    1、委托实例的名称,后面的括号中应包含调用该委托中的方法时使用的参数。

    2、调用委托对象的Invoke()方法,Invoke后面的括号中应包含调用该委托中的方法时使用的参数。

    注意:实际上,给委托实例提供括号与调用委托类的Invoke()方法完全相同。因为Invoke()方法是委托的同步调用方法。

     

    注意:不管是多播委托还是单播委托,在没有特殊处理的情况下,在一个线程的执行过程中去调用委托(委托对象所指向的函数),调用委托的执行是不会新起线程的,这个执行还是在原线程中的,这个对于事件也是一样的。当然,如果是在委托所指向的函数里面去启动一个新的线程那就是另外一回事了。

        这是我们和button1_click事件所对应的方法。注意方法的参数符合委托中的签名(既参数列表)。那我们怎么把这个方法和事件联系起来呢,请看下面的代码。

    整体上看,好像就是在定义一个委托,只是在委托的定义之前,加了个event关键字。

    14.1.6、通过委托对象来调用它所指向的函数

    1、委托实例的名称,后面的括号中应包含调用该委托中的方法时使用的参数。

    2、调用委托对象的Invoke()方法,Invoke后面的括号中应包含调用该委托中的方法时使用的参数。

    注意:实际上,给委托实例提供括号与调用委托类的Invoke()方法完全相同。因为Invoke()方法是委托的同步调用方法。

     

    注意:不管是多播委托还是单播委托,在没有特殊处理的情况下,在一个线程的执行过程中去调用委托(委托对象所指向的函数),调用委托的执行是不会新起线程的,这个执行还是在原线程中的,这个对于事件也是一样的。当然,如果是在委托所指向的函数里面去启动一个新的线程那就是另外一回事了。

    14.2、事件

            this.button1.Click = new System.EventHandler(this.button1_Click);

    没错,事件的定义就是这样,因为要声明一个事件,需要两个元素:

    14.2、事件

    14.2.1、自定义事件

        把this.button1_Click方法绑定到this.button1.Click事件。

    一,标识提供对事件的响应的方法的委托。

    14.2.1、自定义事件

    14.2.1.1、声明一个委托:

    Delegate result-type delegateName ([parameters]);

    这个委托可以在类A内定义也可以在类A外定义。

        下面我们研究一下C#事件处理的工作流程,首先系统会在为我们创建一个在后台监听事件的对象(如果是 button1的事件那么监听事件的就是button1),这个对象用来产生事件,如果有某个用户事件发生则产生对应的应用程序事件,然后执行订阅了事件 的所有方法。

    二,一个类,用存储事件的数据。即,事件要定义在类中。

    14.2.1.1、声明一个委托:

    Delegate result-type delegateName ([parameters]);

    这个委托可以在类A内定义也可以在类A外定义。

    14.2.1.2、声明一个基于某个委托的事件

    Event delegateName  eventName;

    eventName不是一个类型,而是一个具体的对象,这个具体的对象只能在类A内定义而不能在类A外定义。

    二、简单的自定义事件(1)

    下面我们来为这个事件赋值。

    14.2.1.2、声明一个基于某个委托的事件

    Event delegateName  eventName;

    eventName不是一个类型,而是一个具体的对象,这个具体的对象只能在类A内定义而不能在类A外定义。

    14.2.1.3、在类A中定义一个触发该事件的方法

    ReturnType  FunctionName([parameters])

    {

         ……

    If(eventName != null)

    {

    eventName([parameters]);

    或者eventName.Invoke([parameters]);

    }

    ……

    }

    触发事件之后,事件所指向的函数将会被执行。这种执行是通过事件名称来调用的,就像委托对象名一样的。

    触发事件的方法只能在A类中定义,事件的实例化,以及实例化之后的实现体都只能在A类外定义。

        首先我们需要定义一个类来监听客户端事件,这里我们监听键盘的输入。

    public void Init()
    {   
        testEvent  = new TestDelegate(EventSyntax_testEvent); 
        testEvent  = EventSyntax_testEvent; 
    }
    private void EventSyntax_testEvent(string message)
    {
        Console.WriteLine(message);
    }
    
    14.2.1.3、在类A中定义一个触发该事件的方法

    ReturnType  FunctionName([parameters])

    {

         ……

    If(eventName != null)

    {

    eventName([parameters]);

    或者eventName.Invoke([parameters]);

    }

    ……

    }

    触发事件之后,事件所指向的函数将会被执行。这种执行是通过事件名称来调用的,就像委托对象名一样的。

    触发事件的方法只能在A类中定义,事件的实例化,以及实例化之后的实现体都只能在A类外定义。

    14.2.1.4、初始化A类的事件

    在类B中定义一个类A的对象,并且让类A对象的那个事件指向类B中定义的方法,这个方法要与事件关联的委托所限定的方法吻合。

        定义一个委托。

    如代码所示,我们使用了 =这个符号来为事件赋值,赋值的内容是一个委托和一个函数。

    14.2.1.4、初始化A类的事件

    在类B中定义一个类A的对象,并且让类A对象的那个事件指向类B中定义的方法,这个方法要与事件关联的委托所限定的方法吻合。

    14.2.1.5、触发A类的事件

    在B类中去调用A类中的触发事件的方法:用A类的对象去调用A类的触发事件的方法。

            public delegate void UserRequest(object sender,EventArgs e);

    其中 =我们将他理解为【添加】。

    14.2.1.5、触发A类的事件

    在B类中去调用A类中的触发事件的方法:用A类的对象去调用A类的触发事件的方法。

    14.2.1.6、程序实例

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Threading;

    using System.Windows.Forms;

    namespace DelegateStudy

    {

        public delegate void DelegateClick (int a);

        public class butt

        {

            public event DelegateClick Click;

            public void OnClick(int a)

            {

                if(Click != null)

                    Click.Invoke(a);

                   //Click(a);//这种方式也是可以的

                MessageBox.Show("Click();");

            }

        }

        class Frm

        {

            public static void Btn_Click(int a)

            {

                for (long i = 0; i < a; i )

                    Console.WriteLine(i.ToString());

            }

            static void Main(string[] args)

            {

                butt b = new butt();

               //在委托中,委托对象如果是null的,直接使用 =符号,会报错,但是在事件中,初始化的时候,只能用 =

                b.Click = new DelegateClick (Fm_Click); //事件是基于委托的,所以委托推断一样适用,下面的语句一样有效:b.Click = Fm_Click;

                //b.Click(10);错误:事件“DelegateStudy.butt.Click”只能出现在 = 或 -= 的左边(从类型“DelegateStudy.butt”中使用时除外)

                b.OnClick (10000);                       

                MessageBox.Show("sd234234234");

                Console.ReadLine();

            }

       }

    }

        前面的object用来传递事件的发生者,后面的EventArgs用来传递事件的细节,现在暂时没什么用处,一会后面的例子中将使用。

    代码中,我们使用两种赋值模式,但实际上都是为事件testEvent添加一个委。

    14.2.1.6、程序实例

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Threading;

    using System.Windows.Forms;

    namespace DelegateStudy

    {

        public delegate void DelegateClick (int a);

        public class butt

        {

            public event DelegateClick Click;

            public void OnClick(int a)

            {

                if(Click != null)

                    Click.Invoke(a);

                   //Click(a);//这种方式也是可以的

                MessageBox.Show("Click();");

            }

        }

        class Frm

        {

            public static void Btn_Click(int a)

            {

                for (long i = 0; i < a; i )

                    Console.WriteLine(i.ToString());

            }

            static void Main(string[] args)

            {

                butt b = new butt();

               //在委托中,委托对象如果是null的,直接使用 =符号,会报错,但是在事件中,初始化的时候,只能用 =

                b.Click = new DelegateClick (Fm_Click); //事件是基于委托的,所以委托推断一样适用,下面的语句一样有效:b.Click = Fm_Click;

                //b.Click(10);错误:事件“DelegateStudy.butt.Click”只能出现在 = 或 -= 的左边(从类型“DelegateStudy.butt”中使用时除外)

                b.OnClick (10000);                       

                MessageBox.Show("sd234234234");

                Console.ReadLine();

            }

       }

    }

    14.2.2、控件事件

    基于Windows的应用程序也是基于消息的。这说明,应用程序是通过Windows来与用户通信的,Windows又是使用预定义的消息与应用程序通信的。这些消息是包含各种信息的结构,应用程序和Windows使用这些信息决定下一步的操作。

    比如:当用户用鼠标去点击一个windows应用程序的按钮的时候,windows操作系统就会捕获到这个点击按钮的动作,这个时候它会根据捕获到的动作发送一个与之对应的预定义的消息给windows应用程序的这个按钮,windows应用程序的按钮消息处理程序会处理接收到的消息,这个程序处理过程就是根据收到的消息去触发相应的事件,事件被按钮触发后,会通知所有的该事件的订阅者来接收这个事件,从而执行相应的的函数。

    在MFC等库或VB等开发环境推出之前,开发人员必须处理Windows发送给应用程序的消息。VB和今天的.NET把这些传送来的消息封装在事件中。如果需要响应某个消息,就应处理对应的事件。

        下面定义一个此委托类型类型的事件

    第二种将函数直接【添加】到事件中,编译时也会把函数转换成委托【添加】到事件中。

    14.2.2、控件事件

    基于Windows的应用程序也是基于消息的。这说明,应用程序是通过Windows来与用户通信的,Windows又是使用预定义的消息与应用程序通信的。这些消息是包含各种信息的结构,应用程序和Windows使用这些信息决定下一步的操作。

    比如:当用户用鼠标去点击一个windows应用程序的按钮的时候,windows操作系统就会捕获到这个点击按钮的动作,这个时候它会根据捕获到的动作发送一个与之对应的预定义的消息给windows应用程序的这个按钮,windows应用程序的按钮消息处理程序会处理接收到的消息,这个程序处理过程就是根据收到的消息去触发相应的事件,事件被按钮触发后,会通知所有的该事件的订阅者来接收这个事件,从而执行相应的的函数。

    在MFC等库或VB等开发环境推出之前,开发人员必须处理Windows发送给应用程序的消息。VB和今天的.NET把这些传送来的消息封装在事件中。如果需要响应某个消息,就应处理对应的事件。

    14.2.2.1、控件事件委托EventHandler

    在控件事件中,有很多的委托,在这里介绍一个最常用的委托EventHandler,.NET Framework中控件的事件很多都基于该委托,EventHandler委托已在.NET Framework中定义了。它位于System命名空间:

    Public delegate void EventHandler(object sender,EventArgs e);

            public event UserRequest OnUserRequest;

    系统提供事件

    14.2.2.1、控件事件委托EventHandler

    在控件事件中,有很多的委托,在这里介绍一个最常用的委托EventHandler,.NET Framework中控件的事件很多都基于该委托,EventHandler委托已在.NET Framework中定义了。它位于System命名空间:

    Public delegate void EventHandler(object sender,EventArgs e);

    14.2.2.2、委托EventHandler参数和返回值

    事件最终会指向一个或者多个函数,函数要与事件所基于的委托匹配。事件所指向的函数(事件处理程序)的命名规则:按照约定,事件处理程序应遵循“object_event”的命名约定。object就是引发事件的对象,而event就是被引发的事件。从可读性来看,应遵循这个命名约定。

    首先,事件处理程序总是返回void,事件处理程序不能有返回值。其次是参数,只要是基于EventHandler委托的事件,事件处理程序的参数就应是object和EventArgs类型:

    第一个参数接收引发事件的对象,比如当点击某个按钮的时候,这个按钮要触发单击事件最终执行这个函数,那么就会把当前按钮传给sender,当有多个按钮的单击事件都指向这个函数的时候,sender的值就取决于当前被单击的那个按钮,所以可以为几个按钮定义一个按钮单击处理程序,接着根据sender参数确定单击了哪个按钮:

    if(((Button)sender).Name =="buttonOne")

    第二个参数e是包含有关事件的其他有用信息的对象。

        下面我们来做一个死循环

    C#的框架都很经典,而每个经典框架都为我们提供了一些经典事件。

    14.2.2.2、委托EventHandler参数和返回值

    事件最终会指向一个或者多个函数,函数要与事件所基于的委托匹配。事件所指向的函数(事件处理程序)的命名规则:按照约定,事件处理程序应遵循“object_event”的命名约定。object就是引发事件的对象,而event就是被引发的事件。从可读性来看,应遵循这个命名约定。

    首先,事件处理程序总是返回void,事件处理程序不能有返回值。其次是参数,只要是基于EventHandler委托的事件,事件处理程序的参数就应是object和EventArgs类型:

    第一个参数接收引发事件的对象,比如当点击某个按钮的时候,这个按钮要触发单击事件最终执行这个函数,那么就会把当前按钮传给sender,当有多个按钮的单击事件都指向这个函数的时候,sender的值就取决于当前被单击的那个按钮,所以可以为几个按钮定义一个按钮单击处理程序,接着根据sender参数确定单击了哪个按钮:

    if(((Button)sender).Name =="buttonOne")

    第二个参数e是包含有关事件的其他有用信息的对象。

    14.2.2.3、控件事件的其他委托

    控件事件还有其他的委托,比如在窗体上有与鼠标事件关联的委托:

    Public delegate void MouseEventHandler(object sender,MouseEventArgs e);

    public event MouseEventHandler MouseDown;

    this.MouseDown = new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown);

    private void Form1_MouseDown(object sender, MouseEventArgs e){};

    MouseDown事件使用MouseDownEventArgs,它包含鼠标的指针在窗体上的的X和Y坐标,以及与事件相关的其他信息。

    控件事件中,一般第一个参数都是object sender,第二个参数可以是任意类型,不同的委托可以有不同的参数,只要它派生于EventArgs即可。

     

    由于事件必须[标识响应方法的委托],所以这些事件所使用的委托都有一个共同的特点,命名中包含Event。

    14.2.2.3、控件事件的其他委托

    控件事件还有其他的委托,比如在窗体上有与鼠标事件关联的委托:

    Public delegate void MouseEventHandler(object sender,MouseEventArgs e);

    public event MouseEventHandler MouseDown;

    this.MouseDown = new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown);

    private void Form1_MouseDown(object sender, MouseEventArgs e){};

    MouseDown事件使用MouseDownEventArgs,它包含鼠标的指针在窗体上的的X和Y坐标,以及与事件相关的其他信息。

    控件事件中,一般第一个参数都是object sender,第二个参数可以是任意类型,不同的委托可以有不同的参数,只要它派生于EventArgs即可。

    14.2.2.4、程序实例

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Threading;

     

    namespace SecondChangeEvent1

    {

        // 该类用来存储关于事件的有效信息外,

        // 还用来存储额外的需要传给订阅者的Clock状态信息

        public class TimeInfoEventArgs : EventArgs

        {

            public TimeInfoEventArgs(int hour,int minute,int second)

            {

                this.hour = hour;

                this.minute = minute;

                this.second = second;

            }

            public readonly int hour;

            public readonly int minute;

            public readonly int second;

        }

     

        // 定义名为SecondChangeHandler的委托,封装不返回值的方法,

        // 该方法带参数,一个clock类型对象参数,一个TimeInfoEventArgs类型对象

        public delegate void SecondChangeHandler(

           object clock,

           TimeInfoEventArgs timeInformation

        );

        // 被其他类观察的钟(Clock)类,该类发布一个事件:SecondChange。观察该类的类订阅了该事件。

        public class Clock

        {

            // 代表小时,分钟,秒的私有变量

            int _hour;

     

            public int Hour

            {

                get { return _hour; }

                set { _hour = value; }

            }

            private int _minute;

     

            public int Minute

            {

                get { return _minute; }

                set { _minute = value; }

            }

            private int _second;

     

            public int Second

            {

                get { return _second; }

                set { _second = value; }

            }

     

            // 要发布的事件

            public event SecondChangeHandler SecondChange;

     

            // 触发事件的方法

            protected void OnSecondChange(

               object clock,

               TimeInfoEventArgs timeInformation

            )

            {

                // Check if there are any Subscribers

                if (SecondChange != null)

                {

                    // Call the Event

                    SecondChange(clock, timeInformation);

                }

            }

     

            // 让钟(Clock)跑起来,每隔一秒钟触发一次事件

            public void Run()

            {

                for (; ; )

                {

                    // 让线程Sleep一秒钟

                    Thread.Sleep(1000);

     

                    // 获取当前时间

                    System.DateTime dt = System.DateTime.Now;

     

                    // 如果秒钟变化了通知订阅者

                    if (dt.Second != _second)

                    {

                        // 创造TimeInfoEventArgs类型对象,传给订阅者

                        TimeInfoEventArgs timeInformation =

                           new TimeInfoEventArgs(

                           dt.Hour, dt.Minute, dt.Second);

     

                        // 通知订阅者

                        OnSecondChange(this, timeInformation);

                    }

     

                    // 更新状态信息

                    _second = dt.Second;

                    _minute = dt.Minute;

                    _hour = dt.Hour;

     

                }

            }

        }

     

     

        /* ======================= Event Subscribers =============================== */

     

        // 一个订阅者。DisplayClock订阅了clock类的事件。它的工作是显示当前时间。

        public class DisplayClock

        {

            // 传入一个clock对象,订阅其SecondChangeHandler事件

            public void Subscribe(Clock theClock)

            {

                theClock.SecondChange =

                   new SecondChangeHandler(TimeHasChanged);

            }

     

            // 实现了委托匹配类型的方法

            public void TimeHasChanged(

               object theClock, TimeInfoEventArgs ti)

            {

     

                Console.WriteLine("Current Time: {0}:{1}:{2}",

                   ti.hour.ToString(),

                   ti.minute.ToString(),

                   ti.second.ToString());

            }

        }

     

        // 第二个订阅者,他的工作是把当前时间写入一个文件

        public class LogClock

        {

            public void Subscribe(Clock theClock)

            {

                theClock.SecondChange =

                   new SecondChangeHandler(WriteLogEntry);

            }

     

            // 这个方法本来应该是把信息写入一个文件中

            // 这里我们用把信息输出控制台代替

            public void WriteLogEntry(

               object theClock, TimeInfoEventArgs ti)

            {

                Clock a = (Clock)theClock;

                Console.WriteLine("Logging to file: {0}:{1}:{2}",

                   a.Hour.ToString(),

                   a.Minute.ToString(),

                   a.Second.ToString());

            }

        }

     

        /* ======================= Test Application =============================== */

     

        // 测试拥有程序

        public class Test

        {

            public static void Main()

            {

                // 创建clock实例

                Clock theClock = new Clock();

     

                // 创建一个DisplayClock实例,让其订阅上面创建的clock的事件

                DisplayClock dc = new DisplayClock();

                dc.Subscribe(theClock);

     

                // 创建一个LogClock实例,让其订阅上面创建的clock的事件

                LogClock lc = new LogClock();

                lc.Subscribe(theClock);

     

                // 让钟跑起来

                theClock.Run();

            }

        }

    }

     1图片 4   public void Run()
     2图片 5     {
     3图片 6    bool finished=false;
     4图片 7    do
     5图片 8    {
     6图片 9     if (Console.ReadLine()=="h")
     7图片 10    {
     8图片 11    OnUserRequest(this,new EventArgs());
     9图片 12    }  
    10图片 13    }while(!finished);
    11图片 14     }

    比如EventHandler,CancelEventHandler,RoutedEventHandler,ContextMenuEventHandler等。

    14.2.2.4、程序实例

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Threading;

     

    namespace SecondChangeEvent1

    {

        // 该类用来存储关于事件的有效信息外,

        // 还用来存储额外的需要传给订阅者的Clock状态信息

        public class TimeInfoEventArgs : EventArgs

        {

            public TimeInfoEventArgs(int hour,int minute,int second)

            {

                this.hour = hour;

                this.minute = minute;

                this.second = second;

            }

            public readonly int hour;

            public readonly int minute;

            public readonly int second;

        }

     

        // 定义名为SecondChangeHandler的委托,封装不返回值的方法,

        // 该方法带参数,一个clock类型对象参数,一个TimeInfoEventArgs类型对象

        public delegate void SecondChangeHandler(

           object clock,

           TimeInfoEventArgs timeInformation

        );

        // 被其他类观察的钟(Clock)类,该类发布一个事件:SecondChange。观察该类的类订阅了该事件。

        public class Clock

        {

            // 代表小时,分钟,秒的私有变量

            int _hour;

     

            public int Hour

            {

                get { return _hour; }

                set { _hour = value; }

            }

            private int _minute;

     

            public int Minute

            {

                get { return _minute; }

                set { _minute = value; }

            }

            private int _second;

     

            public int Second

            {

                get { return _second; }

                set { _second = value; }

            }

     

            // 要发布的事件

            public event SecondChangeHandler SecondChange;

     

            // 触发事件的方法

            protected void OnSecondChange(

               object clock,

               TimeInfoEventArgs timeInformation

            )

            {

                // Check if there are any Subscribers

                if (SecondChange != null)

                {

                    // Call the Event

                    SecondChange(clock, timeInformation);

                }

            }

     

            // 让钟(Clock)跑起来,每隔一秒钟触发一次事件

            public void Run()

            {

                for (; ; )

                {

                    // 让线程Sleep一秒钟

                    Thread.Sleep(1000);

     

                    // 获取当前时间

                    System.DateTime dt = System.DateTime.Now;

     

                    // 如果秒钟变化了通知订阅者

                    if (dt.Second != _second)

                    {

                        // 创造TimeInfoEventArgs类型对象,传给订阅者

                        TimeInfoEventArgs timeInformation =

                           new TimeInfoEventArgs(

                           dt.Hour, dt.Minute, dt.Second);

     

                        // 通知订阅者

                        OnSecondChange(this, timeInformation);

                    }

     

                    // 更新状态信息

                    _second = dt.Second;

                    _minute = dt.Minute;

                    _hour = dt.Hour;

     

                }

            }

        }

     

     

        /* ======================= Event Subscribers =============================== */

     

        // 一个订阅者。DisplayClock订阅了clock类的事件。它的工作是显示当前时间。

        public class DisplayClock

        {

            // 传入一个clock对象,订阅其SecondChangeHandler事件

            public void Subscribe(Clock theClock)

            {

                theClock.SecondChange =

                   new SecondChangeHandler(TimeHasChanged);

            }

     

            // 实现了委托匹配类型的方法

            public void TimeHasChanged(

               object theClock, TimeInfoEventArgs ti)

            {

     

                Console.WriteLine("Current Time: {0}:{1}:{2}",

                   ti.hour.ToString(),

                   ti.minute.ToString(),

                   ti.second.ToString());

            }

        }

     

        // 第二个订阅者,他的工作是把当前时间写入一个文件

        public class LogClock

        {

            public void Subscribe(Clock theClock)

            {

                theClock.SecondChange =

                   new SecondChangeHandler(WriteLogEntry);

            }

     

            // 这个方法本来应该是把信息写入一个文件中

            // 这里我们用把信息输出控制台代替

            public void WriteLogEntry(

               object theClock, TimeInfoEventArgs ti)

            {

                Clock a = (Clock)theClock;

                Console.WriteLine("Logging to file: {0}:{1}:{2}",

                   a.Hour.ToString(),

                   a.Minute.ToString(),

                   a.Second.ToString());

            }

        }

     

        /* ======================= Test Application =============================== */

     

        // 测试拥有程序

        public class Test

        {

            public static void Main()

            {

                // 创建clock实例

                Clock theClock = new Clock();

     

                // 创建一个DisplayClock实例,让其订阅上面创建的clock的事件

                DisplayClock dc = new DisplayClock();

                dc.Subscribe(theClock);

     

                // 创建一个LogClock实例,让其订阅上面创建的clock的事件

                LogClock lc = new LogClock();

                lc.Subscribe(theClock);

     

                // 让钟跑起来

                theClock.Run();

            }

        }

    }

    14. 3、小结

    (1)、在定义事件的那个类A里面,可以任意的使用事件名,可以触发;在别的类里面,事件名只能出现在 = 或 -= 的左边来指向函数,即只能实例化,不能直接用事件名触发。但是可以通过A类的对象来调用A类中的触发事件的函数。这是唯一触发事件的方式。

    (2)、不管是多播委托还是单播委托,在没有特殊处理的情况下,在一个线程的执行过程中去调用委托(委托对象所指向的函数),调用委托的执行是不会新起线程的,这个执行还是在原线程中的,这个对于事件也是一样的。当然,如果是在委托所指向的函数里面去启动一个新的线程那就是另外一回事了。

    (3)、事件是针对某一个具体的对象的,一般在该对象的所属类A中写好事件,并且写好触发事件的方法,那么这个类A就是事件的发布者,然后在别的类B里面定义A的对象,并去初始化该对象的事件,让事件指向B类中的某一个具体的方法,B类就是A类事件的订阅者。当通过A类的对象来触发A类的事件的时候(只能A类的对象来触发A类的事件,别的类的对象不能触发A类的事件,只能订阅A类的事件,即实例化A类的事件),作为订阅者的B类会接收A类触发的事件,从而使得订阅函数被执行。一个发布者可以有多个订阅者,当发布者发送事件的时候,所有的订阅者都将接收到事件,从而执行订阅函数,但是即使是有多个订阅者也是单线程。

       此代码不断的要求用户输入字符,如果输入的结果是h,则触发OnUserRequest事件,事件的触发者是本身(this),事件细节无(没有传递任何参数的EventArgs实例)。我们给这个类取名为UserInputMonitor。

    其中最经典的就是EventHandler和RoutedEventHandler。

    14. 3、小结

    (1)、在定义事件的那个类A里面,可以任意的使用事件名,可以触发;在别的类里面,事件名只能出现在 = 或 -= 的左边来指向函数,即只能实例化,不能直接用事件名触发。但是可以通过A类的对象来调用A类中的触发事件的函数。这是唯一触发事件的方式。

    (2)、不管是多播委托还是单播委托,在没有特殊处理的情况下,在一个线程的执行过程中去调用委托(委托对象所指向的函数),调用委托的执行是不会新起线程的,这个执行还是在原线程中的,这个对于事件也是一样的。当然,如果是在委托所指向的函数里面去启动一个新的线程那就是另外一回事了。

    (3)、事件是针对某一个具体的对象的,一般在该对象的所属类A中写好事件,并且写好触发事件的方法,那么这个类A就是事件的发布者,然后在别的类B里面定义A的对象,并去初始化该对象的事件,让事件指向B类中的某一个具体的方法,B类就是A类事件的订阅者。当通过A类的对象来触发A类的事件的时候(只能A类的对象来触发A类的事件,别的类的对象不能触发A类的事件,只能订阅A类的事件,即实例化A类的事件),作为订阅者的B类会接收A类触发的事件,从而使得订阅函数被执行。一个发布者可以有多个订阅者,当发布者发送事件的时候,所有的订阅者都将接收到事件,从而执行订阅函数,但是即使是有多个订阅者也是单线程。

    原文链接: 14.1、委托 当要把方法作为实...

       下面我们要做的是定义客户端的类
    
        首先得实例化UserInputMonitor类
    

    EventHandler:

           UserInputMonitor monitor=new UserInputMonitor();

    EventHandler定义如下

        然后我们定义一个方法。

    [SerializableAttribute]
    [ComVisibleAttribute(true)]
    public delegate void EventHandler(
     object sender,
     EventArgs e
    )
    

           private void ShowMessage(object sender,EventArgs e)
          {
              Console.WriteLine("HaHa!!");
          }

    他包含了两个参数,即当我们为事件添加EventHandler委托后,再去触发该事件;被触发的委托将得到object sender和EventArgs e两个参数。

         最后要做的是把这个方法和事件联系起来(订阅事件),我们把它写到库户端类的构造函数里。

    sender:代表源,即触发该事件的控件。

         Client(UserInputMonitor m)
         {
          m.OnUserRequest =new UserInputMonitor.UserRequest(this.ShowMessage);
          //m.OnUserRequest =new m.UserRequest(this.ShowMessage);

    e:代表事件参数,即触发该事件后,事件为被触发的委托,传递了一些参数,以方便委托在处理数据时,更便捷。

          //注意这种写法是错误的,因为委托是静态的

    根据这个原理,我们可以分析出很多东西。

         }

    比如,当控件DataGrid的事件被触发时,只要查看一下sender的真实类型,就可以知道,到底是DataGrid触发的事件,还是DataGridRow或DataGridCell触发的了。

         下面创建客户端的实例。

    RoutedEventHandler:

             new Client(monitor);

    RoutedEventHandler即路由事件,他的定义如下

         对了,别忘了让monitor开始监听事件。

    public delegate void RoutedEventHandler(
     Object sender,
     RoutedEventArgs e
    )
    

            monitor.run();

    RoutedEventHandler也为我们提供了sender和e两个参数。

         大功告成,代码如下:

    但RoutedEventHandler特别之处是,他的sender并不一定是真实的源,因为他是一个冒泡路由事件,即上升事件。

     

    这里如果大家有好奇心去看官方文档,那么会在相关的介绍中看到两个单词sender和source。

     1图片 15using System;
     2图片 16class UserInputMonitor
     3图片 17{
     4图片 18public delegate void UserRequest(object sender,EventArgs e);
     5图片 19//定义委托
     6图片 20public event UserRequest OnUserRequest;
     7图片 21//此委托类型类型的事件
     8图片 22public void Run()
     9图片 23{
    10图片 24bool finished=false;
    11图片 25do
    12图片 26{
    13图片 27if (Console.ReadLine()=="h")
    14图片 28{
    15图片 29OnUserRequest(this,new EventArgs());
    16图片 30}  
    17图片 31}while(!finished);
    18图片 32}
    19图片 33}
    20图片 34
    21图片 35public class Client
    22图片 36{
    23图片 37public static void Main()
    24图片 38{
    25图片 39UserInputMonitor monitor=new UserInputMonitor();
    26图片 40new Client(monitor);
    27图片 41monitor.Run();
    28图片 42}
    29图片 43private void ShowMessage(object sender,EventArgs e)
    30图片 44{
    31图片 45Console.WriteLine("HaHa!!");
    32图片 46}
    33图片 47Client(UserInputMonitor m)
    34图片 48{
    35图片 49m.OnUserRequest =new UserInputMonitor.UserRequest(this.ShowMessage);
    36图片 50//m.OnUserRequest =new m.UserRequest(this.ShowMessage);
    37图片 51//注意这种写法是错误的,因为委托是静态的
    38图片 52}
    39图片 53}
    40图片 54

    通过这两个单词,我们会清晰的了解路由事件。简单描述一下sender和source,它们一个是发送者,一个是源。

     

    在EventHandler中,sender即source,因为它是直接事件。而在冒泡事件中,sender不一定等于source。即发送者不一定是源。

    三、进一步研究C#中的预定义事件处理机制

    下面我们用WPF来看看路由事件。

        可能大家发现在C#中有些事件和前面的似乎不太一样。例如

    我们首先在XAML页面定义一个RadioButton按钮,然后设置他的模板是Button。然后分别定义各自的Click方法。

          private void textBox1_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
          {

    Xaml页面如下:

          }

     <RadioButton Click="btnParent_Click">
                <RadioButton.Template>
                    <ControlTemplate>
                        <StackPanel>
                            <TextBlock Text="我的名字" ></TextBlock>
                            <Button Content="Kiba518"   Click="btnClild_Click" ></Button>
                        </StackPanel>
                    </ControlTemplate>
                </RadioButton.Template> 
    </RadioButton> 
    

          this.textBox1.KeyPress =newSystem.Windows.Forms.KeyPressEventHandler(this.textBox1_KeyPress);

    cs文件事件如下:

        这里使用了KeyPressEventArgs而不是EventArgs作为参数。这里使用了KeyEventHandler委托,而不是EventHandler委托。

     private void btnParent_Click(object sender, RoutedEventArgs e)
     {
         string type = sender.GetType().ToString();//RadioButton
     }
    
     private void btnClild_Click(object sender, RoutedEventArgs e)
     {
         string type = sender.GetType().ToString();//Button
     }
    

        KeyPressEventArgs是EventArgs的派生类,而KeyEventHandler的声明如下

    运行起来,我们点击按钮,通过断点我们可以看到,我们点击的按钮触发了btnClild_Click和btnParent_Click事件

          public delegate void KeyEventHandler( object sender **KeyEventArgs );**

    顺序是先btnClild_Click后btnParent_Click。

       是参数为KeyEventArgs的委托。那为什么KeyPress事件要这么做呢,我们可以从两个类的构造函数来找答案。

    通过获取sender的类型,我也可以看到,btnClild_Click的sender类型是Button,而btnParent_Click的sernder类型是RadioButton。

           public EventArgs();

    事件驱动编程

           public KeyPressEventArgs(char keyChar);

    事件驱动编程这个概念给我的感觉很怪,因为一直用C#,而C#的很多框架都是事件驱动的,所以一直觉得事件驱动是理所当然。

        这里的keyData是什么,是用来传递我们按下了哪个键的,哈。

    而当事件驱动设计这个词经常出现后,反而感觉怪怪的。

        我在KeyEventArgs中又发现了属性

    就好像,天天吃大米饭,突然有一天,所有人都说大米饭好香的感觉一样,你一听就感觉怪怪的。

           public char KeyChar { get; }

    因为事件驱动对于C#开发而言,实在太普通了。当然,这也得益于微软框架做的实在是太好了。

        进一步证明了我的理论。下面我们来做一个类似的例子来帮助理解。

    所以,我也不知道如何在C#里讲事件驱动编程。因为使用C#的框架就是使用事件驱动编程。

    四、简单的自定义事件(2)

    事件和委托到底是什么关系?

        拿我们上面做的例子来改。

    事件是用来多播的,并且用委托来为事件赋值,可以说,事件是基于委托来实现的。

        我们也定义一个EventArgs(类似KeyEventArgs)取名MyEventArgs,定义一个构造函数public MyEventArgs(char keyChar),同样我们也设置相应的属性。代码如下

    但委托中也有多播,那为什么要单独弄出来一个事件呢?

     

    首先,存在即合理,事件一定有他存在的意义。 

     1图片 55using System;
     2图片 56class MyMyEventArgs:EventArgs
     3图片 57{
     4图片 58private char keyChar;
     5图片 59public MyMyEventArgs(char keyChar)
     6图片 60{
     7图片 61this.keychar=keychar;
     8图片 62}
     9图片 63public char KeyChar
    10图片 64{
    11图片 65get
    12图片 66{
    13图片 67return keyChar;
    14图片 68}
    15图片 69}
    16图片 70}
    17图片 71
    18图片 72

    事件存在的意义

     

    我对事件存在的意义是这样理解的。我们在C#编写框架时,几乎不用委托的多播,因为委托的多播和事件存在严重的二义性。虽然编写框架的人学会了使用委托的多播,但使用框架的同事可能并还不太熟练,而且C#框架中,大多是使用事件来进行多播的。

    因为现在要监听多个键了,我们得改写监听器的类中的do...while部分。改写委托,改写客户端传递的参数。好了最终代码如下,好累

    所以委托的多播和事件一起使用的框架,会造成使用这个框架的初级开发者很多困惑,而这种困惑,会产生很多不必要的问题。

     

    比如, 你定义了一个委托,另一个开发者用这个委托做了个多播,当第三个开发者来维护这段代码时,如果他是新手,不了解委托的多播,那就很有可能只修改了委托调用的代码。而没有去同步多播这个委托的代码。那系统就产生了隐藏的bug。

     1图片 73using System;
     2图片 74class MyEventArgs:EventArgs
     3图片 75{
     4图片 76private char keyChar;
     5图片 77public MyEventArgs(char keyChar)
     6图片 78{
     7图片 79this.keyChar=keyChar;
     8图片 80}
     9图片 81public char KeyChar
    10图片 82{
    11图片 83get
    12图片 84{
    13图片 85return keyChar;
    14图片 86}
    15图片 87}
    16图片 88}
    17图片 89
    18图片 90class UserInputMonitor
    19图片 91{
    20图片 92public delegate void UserRequest(object sender,MyEventArgs e);
    21图片 93//定义委托
    22图片 94public event UserRequest OnUserRequest;
    23图片 95//此委托类型类型的事件
    24图片 96public void Run()
    25图片 97{
    26图片 98bool finished=false;
    27图片 99do
    28图片 100{
    29图片 101string inputString= Console.ReadLine();
    30图片 102if (inputString!="") 
    31图片 103OnUserRequest(this,new MyEventArgs(inputString[0]));
    32图片 104}while(!finished);
    33图片 105}
    34图片 106}
    35图片 107
    36图片 108
    37图片 109public class Client
    38图片 110{
    39图片 111public static void Main()
    40图片 112{
    41图片 113UserInputMonitor monitor=new UserInputMonitor();
    42图片 114new Client(monitor);
    43图片 115monitor.Run();
    44图片 116}
    45图片 117private void ShowMessage(object sender,MyEventArgs e)
    46图片 118{
    47图片 119Console.WriteLine("捕捉到:{0}",e.KeyChar);
    48图片 120}
    49图片 121Client(UserInputMonitor m)
    50图片 122{
    51图片 123m.OnUserRequest =new UserInputMonitor.UserRequest(this.ShowMessage);
    52图片 124//m.OnUserRequest =new m.UserRequest(this.ShowMessage);
    53图片 125//注意这种写法是错误的,因为委托是静态的
    54图片 126}
    55图片 127}
    56图片 128

    那么,事件和委托到底是什么关系呢?

    事件与委托的确存在千丝万缕的关系,怎么讲都是正确的。但,C#开发者只需要记住,他们俩没关系即可。在C#事件是事件,委托是委托。两者就如同int和string一样,没有任何关系。

    原因很简单,学习的过程中尽量降低概念混淆。而且,在C#开发中,好的架构者也通常会将事件和委托分离,所以,就认为事件和委托没有关系即可。

    结语

    其实事件很好理解,一点不复杂。我在写这篇文章的过程中,也没想到什么特别的或者说比较高级的用法。

    但真实的应用场景中,我的感觉是,随着MVVM的成长,事件其实在被逐渐抛弃。虽然微软做了很多经典的事件驱动框架。但那都是过去了。

    比如WPF虽然支持事件驱动,但MVVM在WPF下的表现堪称完美,所以WPF下的事件几乎没有人用了。

    再比如前端的Angularjs等框架,提供了优质的MVVM使用效果,也让新的前端设计师逐渐放弃了事件。

    所以,事件在未来的编程中,很可能将不在有那么重要的地位了。但学好事件,对于我们理解微软框架,还是有很大帮助的。

    C#语法——元组类型

    C#语法——泛型的多种应用

    C#语法——await与async的正确打开方式

    C#语法——委托,架构的血液

    我对C#的认知。


    注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
    若您觉得这篇文章还不错,请点击下右下角的【推荐】,非常感谢!
    如果您觉得这篇文章对您有所帮助,那就不妨支付宝小小打赏一下吧。 

    图片 129

     

    本文由新葡亰496net发布于奥门新萄京娱乐场,转载请注明出处:信托和事件,逐步边缘化的长兄

    关键词: