zoukankan      html  css  js  c++  java
  • C# 委托和事件

      委托、匿名方法、委托的发布和订阅、事件的发布和订阅、EventHandler类、Windows事件概述

      通常我们将数据对象(或其引用)作为方法的参数进行传递,而使用委托可以实现将方法本身作为参数进行传递。

      当对象在运行过程中遇到一些特定的事情,通常需要使用事件来进行处理,事件可以使用.NET框架自身提供的,也可以实现用户自定义

    1.委托

      为了实现方法的参数化,提出了委托的概念。

      1)概述

      C#中委托是一种引用类型,只不过在委托对象的引用中存放的是对方法的引用。

      委托声明语法格式如下:

    【修饰符】 delegate 【返回类型】 【委托名称】 (【参数列表】)
    
    public delegate int MyDelegate(int x ,int y);

      一个与委托相匹配的方法的参数必须与委托的参数相同,完全相同,且这二者的返回类型相同。

      2)一个小demo

      

        public class Test_Delegate
        {
            public int Add(int x, int y)
            {
                return x + y;
            }
        }
        class Program
        {
            public delegate int MyDelegate(int x, int y);//定义一个委托类型
            static void Main(string[] args)
            {
                Test_Delegate td = new Test_Delegate();
                MyDelegate md = td.Add;//创建委托类型的实例md,并绑定到Add方法
                int sum = md(2, 3);//委托的调用
                Console.WriteLine("result:" + sum);
                Console.Read();
            }
        }

      结果:

      上面代码中MyDekegate自定义委托类型继承自System.MulticastDelete,且该自定义委托类型包含一个Invoke方法,该方法接受两个整形参数,并返回一个整数值,与Add方法完全相同。实际上程序在进行委托调用时就是调用了Invoke方法,所以上面的委托调用完全可以写成下面的这种形式:

      int sum = md.Invoke(2, 3);//委托的调用

      这种形式也更方便来理解。

      这是最最简单的demo

    class Program
        {
            static int Add(int x, int y)
            {
                return x + y;
            }
            public delegate int MyDelegate(int x, int y);//定义一个委托类型
            static void Main(string[] args)
            {
                MyDelegate md = Add;//创建委托类型的实例md,并绑定到Add方法
                int sum = md(2, 3);//委托的调用
                Console.WriteLine("result:" + sum);
                Console.Read();
            }
        }

    2.匿名方法

      为了简化委托的可操作性,在C#中提出了匿名方法的概念

      1)概述

      匿名方法允许一个与委托关联的代码被内联地写入使用委托的位置,这使得代码对与委托的实例很直接。匿名方法还共享了对本地语句包含的函数成员的访问。

      使用匿名方法不必创建单独的方法。原先是委托绑定一个方法,现在可以直接将方法的代码块作为参数传给委托而不必调用方法。

      匿名方法的语法格式如下:

    delegate (【参数列表】)
    {
      【代码块】
    };
    delegate (string j)
    {
      Console.WriteLine(j);
    };

      2)一个小demo来体现两种的不同

      

     delegate void DeleOutput(string s);//自定义委托类型
        class Program
        {
            static void NamedMethod(string k)//与委托相匹配的命名方法
            {
                Console.WriteLine(k);
            }
            static void Main(string[] args)
            {
                //委托的引用指向匿名方法 delegate(string j){}
                DeleOutput del = delegate (string j)
                {
                    Console.WriteLine(j);
                };
                del.Invoke("Anonymous Methods");//委托对象del调用匿名方法
                //del("匿名方法被调用");
                Console.Write("
    ");
                del = NamedMethod;//委托绑定到NamedMethod
                del("Named Method");//委托对象del调用命名方法
                //del.Invoke("NamedMethod被调用");
                Console.ReadLine();
            }
        }

      结果:

     3.委托的发布和订阅

      1)概述

         由于委托能够引用方法,而且能够链接和删除其他委托对象,因而就能够通过委托来实现事件的“发布和订阅”这两个必要的过程。通过委托来实现事件处理的过程需要以下4个步骤:

      (1)定义委托类型,并在发布者类中定义一个该类型的公有成员

      (2)在订阅者类中定义委托处理方法

      (3)订阅者对象将其事件处理方法链接到发布者对象的委托成员(一个委托类型的引用)上

      (4)发布者对象在特定的情况下激发委托操作,从而自动调用订阅者对象的委托处理方法

      下面以学校铃声为例

      2)小demo

      

       public delegate void RingEvent(int ringKind);//声明一个委托类型
    
        //定义一个委托发布者类SchoolRIng,并在该类中定义一个RingEvent类型的共有成员
        //即委托成员,用来进行委托发布,然后定义一个成员方法Jow,用来实现激发委托操作
        public class SchoolRing//定义发布者类
        {
            public RingEvent OnbellSound;//委托发布
            public void Jow(int ringKind)//实现打铃操作
            {
                if (ringKind == 1 || ringKind == 2)
                {
                    Console.Write(ringKind == 1 ? "Class Begin,":"Class Over,");
                    if (OnbellSound != null)//不等于空,说明它已经订阅了具体的方法
                        OnbellSound(ringKind);//回调OnBellSound委托所订阅的具体方法
                    else
                        Console.WriteLine("ringKind is wrong!");
                }
            }
        }
        //定义一个Student类来对听到铃声做相应的动作,在该类中定义一个铃声事件的处理方法SchoolJow
        //并在某一个激发时刻或状态链接到SchoolRing对象的OnBellSound的委托上。另外,在订阅完毕后
        //通过CancelSubscribe方法删除订阅
        public class Student//定义订阅者类
        {
            public void SubscribeToRing(SchoolRing schoolRing)//学生订阅铃声这个委托事件
            {
                schoolRing.OnbellSound += SchoolJow;//通过委托的链接操作进行订阅操作
            }
            public void SchoolJow(int ringKind)//事件的处理方法
            {
                if (ringKind == 2)
                {
                    Console.WriteLine("Student rest");
                }
                else if (ringKind == 1)
                {
                    Console.WriteLine("Student learn");
                }
            }
            public void CancelSubscribe(SchoolRing schoolRing)//取消订阅铃声动作
            {
                schoolRing.OnbellSound -= SchoolJow;
            }
        }
        //当发布者SchoolRIng类的对象调用其Jow方法进行打铃时,就会自动调用Student对象的
        //SchoolJow这个事件的处理方法
        class Program
        {
            static void Main(string[] args)
            {
                SchoolRing sr = new SchoolRing();
                Student st = new Student();
                st.SubscribeToRing(sr);//学生订阅学校铃声
                Console.WriteLine("Please input ringKind(1.Begin Class;2.End Class):");
                sr.Jow(Convert.ToInt32(Console.ReadLine()));//开始打铃动作
                Console.Read();
            }
        }

       result:  

      通过上面的这个实例,通过发布者来发布这个委托,然后定义一个事件触发器,在这个触发器被击发后(比如执行这个触发器方法),会调用这个委托,然后委托根据自身的订阅情况,再进行回调委托(事件)的处理方法,因为委托已经通过”+=“符号链接到该处理方法上了。

     3.事件的发布和订阅

      委托可以进行发布和订阅,从而使不同的对象对特定的情况作出反应。但这种机制存在一个问题,即外部对象可以任意修改已发布的委托(因为这个委托仅是一个普通的类级共有成员),这也会影响到其他对象对委托的订阅(使委托丢掉了其他的订阅),比如,在进行委托订阅的时候使用“=”符号,而不是“+=”,或者在订阅时,设置委托指向一个空引用,这些都对委托的安全性造成严重的威胁,如下面的示例代码所示:

     public void SubscribeToRing(SchoolRing schoolRing)//学生订阅铃声这个委托事件
            {
                //通过赋值运算符进行订阅,使委托OnbellSound丢掉了其他的订阅
                schoolRing.OnbellSound = SchoolJow;
            }
     public void SubscribeToRing(SchoolRing schoolRing)//学生订阅铃声这个委托事件
            {
               
                schoolRing.OnbellSound = null;//取消委托订阅的所有内容
            }

      为了解决这个问题,C#提供了专门的事件处理机制,以保证事件订阅的可靠性,其做法是在发布委托的定义中加上event关键字,其他代码不变。例如:

     public event RingEvent OnbellSound;//事件发布

       在经过简单的修改后,其他类型再使用OnbellSound委托时,就只能够将其放在复合赋值运算符“+=”或“-=”的左侧,直接使用“=”运算符,编译系统会报错。例如,下面的代码都是错误的:

     schoolRing.OnbellSound = null;//系统会报错的
     schoolRing.OnbellSound = SchoolJow;//系统会报错的

      事件是一种特殊的类型,发布者在发布一个事件后,订阅者对它只能作自身的订阅或取消,而不能干涉其他订阅者。

      事件也是类的一种特殊成员:即使是公有事件,除了其所属类型之外,其他类型只能对其进行订阅或者取消,别的任何操作都是不允许的,因此时间具有特殊的封装性。和一般委托成员不同,某个类型的事件只能由自身触发。例如,在Student的成员方法中,使用如下代码来直接调用School对象的OnbellSound事件是不被允许的。比如“schoolRing.OnbellSound(2)”这个代码是错误的,因为OnbellSound这个委托只能在包含其自身定义的发布者类种被调用。

     4.EventHandler类

      1)概述

      在事件发布和订阅的过程中,定义事件的类型(委托类型)是一件重复性的工作。为此.NET类库中定义了一个Eventhandler委托类型,并建议尽量使用该类型作为事件的委托类型。该委托类型的定义为:

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

      其中object类型的参数sender表示引发事件的对象,由于事件成员只能由类型本身(即事件的发布者)触发,因此在触发时传递给该参数的值通常为this。例如:可将 SchoolRing类的OnbellSound 事件定义为 EventHandler 委托类型,那么触发该事件的代码就是“OnbellSound(this,null);”。

      2)小demo

        /// <summary>
        /// EventHandler定义
        /// </summary>
    
        public delegate void EventHandle(object sender,EventArgs e);
    
        /// <summary>
        /// 而SchoolRing的实例在触发OnBellSound事件时,就可以将该类型(RingEventArgs)的对象
        /// 作为参数传递给EventHandler委托,下面看激发OnBellSound事件的主要代码
        /// </summary>
        public class SchoolRing//定义发布者类
        {
            public event EventHandle OnbellSound;//委托发布
            public void Jow(int ringKind)//打铃方法
            {
                if (ringKind == 1 || ringKind == 2)
                {
                    Console.Write(ringKind == 1 ? "Class Begin,":"Class Over,");
                    if (OnbellSound != null)//不等于空,说明它已经订阅了具体的方法
                        //为了安全,事件只能由类型本身触发(this)
                        OnbellSound(this,new RingEventArgs(ringKind));//回调OnBellSound委托所订阅的具体方法
                    else
                        Console.WriteLine("ringKind is wrong!");
                }
            }
        }
    
        public class Student//定义订阅者类
        {
            public void SubscribeToRing(SchoolRing schoolRing)//学生订阅铃声这个委托事件
            {
               
                schoolRing.OnbellSound += SchoolJow;
            }
            /// <summary>
            /// 事件的订阅者可以通过参数来了解哪个是对象触发的事件(这里当然是事件的发布者)。
            /// 不过在访问对象时通常要进行强制类型转换。例如,Student类对OnbellSound 事件的处理方法则需修改为:
            /// </summary>
            public void SchoolJow(object sender,EventArgs e)//事件的处理方法
            {
                if (((RingEventArgs)e).RingKind == 2)//e强制转换为RingEventArgs类型
                {
                    Console.WriteLine("Student rest");
                }
                else if (((RingEventArgs)e).RingKind == 1)
                {
                    Console.WriteLine("Student learn");
                }
            }
            public void CancelSubscribe(SchoolRing schoolRing)//取消订阅铃声动作
            {
                schoolRing.OnbellSound -= SchoolJow;
            }
        }
        ///EventHandler 委托的第二个参数e表示事件中包含的数据。如果发布者还要向订阅者传递额外的事件数据,
        ///那么就需要定义EventArgs类型的派生类。例如,由于需要把打铃参数(1或2)传入到事件中,
        ///则可以定义如下的RingEventArgs类:
        public class RingEventArgs : EventArgs
        {
            private int ringKind;//描述铃声种类字段
            public int RingKind
            {
                get { return ringKind; }//获取打铃参数
            }
            public RingEventArgs(int ringKind)
            {
                this.ringKind = ringKind;//在构造器中初始化铃声参数
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                SchoolRing sr = new SchoolRing();
                Student st = new Student();
                st.SubscribeToRing(sr);
                Console.WriteLine("Please input ringKind(1.Begin Class;2.End Class):");
                sr.Jow(Convert.ToInt32(Console.ReadLine()));
                Console.Read();
            }
        }

    5.Windows事件概述

      1)概述

        事件在Windows这样的图形界面程序中有着极其广泛的应用,事件响应是程序与用户交互的基础。用户的绝大多数操作,如移动鼠标、单机鼠标、选择菜单命令等,都可以触发相关的控件事件。以Button 控件为例,其成员Click就是一个EventHandler类型的事件:

    public event EventHandler Click;

        用户单击按钮时,Button对象就会调用其保护成员OnClick(它包含了激发Click事件的代码),并通过它来触发Click事件:

    protected virtual void onClick(EventArgs e)
    {
        if (Click != null)
            Click(this, e);
    }

        此时,如果在程序中定义了响应事件的处理方法,那么单击按钮就能够执行其中的代码。

      2)小demo1

        在Form1窗体包含了一个名为Button1的按钮,那么可以在窗体的构造方法中关联事件处理方法,并且在方法代码中执行所需要的功能。代码如下:

        

         public Form1()
            {
                InitializeComponent();
                button1.Click += new EventHandler(button1_Click);  //关联事件处理方法
            }
            private void button1_Click(object sender, EventArgs e)
            {
                this.Close();
            }

      3)小demo2  

         在Windows窗体中包含了3个按钮控件和一个文本控件,3个按钮的Click事件共用一个事件的处理方法Button。代码如下:

     public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                button1.Click += new EventHandler(button_Click);  //关联事件处理方法
                button2.Click += new EventHandler(button_Click);
                button3.Click += new EventHandler(button_Click);
            }
            private void button_Click(object sender, EventArgs e)  //事件处理方法
            {
                textBox1.Text="You pressed" + ((Button)sender).Text;
            }
        }

          效果图:

          

    MrNou
  • 相关阅读:
    python读取配置文件
    日志截取
    QJsonObject
    OpenStack
    生成器迭代器正则视频笔记
    使用Qt.createComponent 动态加载组件
    linux 远程执行命令
    Django 建立工程连接数据库
    arm基础1
    QSetting的用法
  • 原文地址:https://www.cnblogs.com/yangsirc/p/8376707.html
Copyright © 2011-2022 走看看