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
原文地址:https://www.cnblogs.com/yangsirc/p/8376707.html