C#委托和事件入门详解

Posted Nemo_XP

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#委托和事件入门详解相关的知识,希望对你有一定的参考价值。

经典实例:父窗体与子控件的相互调用

这里先复习一下父类和继承子类的相互调用.

class Program
    {
        static void Main(string[] args)
        {
            Student student = new Student() { Age = 9, GradeName = "四年级" };

            // 子类可直接调用父类非同名成员
            student.SayHello();// Show:我是Person,大家好

            // 子类通过转化父类型访问父同名方法
            ((Person)student).SayHi();// Show:我是person,9岁

            // 父类通过里氏转换调用子类成员(即虚方法实现多态):该person前提是student
            Person person = (Person)student;
            student.SayHi();  // Show:我是Student,在四年级

            Console.Read();
        }
    }

    public class Person
    {
        public int Age { get; set; }

        public void SayHello()
        {
            Console.WriteLine($"我是Person,大家好");
        }

        public virtual void SayHi()
        {
            Console.WriteLine($"我是person,{Age}岁");
        }
    }

    public class Student : Person
    {
        public string GradeName { get; set; }

        public new void SayHi()// override重写/new隐藏父类方法
        {
            Console.WriteLine($"我是Student,在{GradeName}");
        }
    }

然后,考虑父窗体和子控件如何相互调用成员
在这里插入图片描述
父窗体调用子控件很简单:
应用场景, 父窗体刷新的时候,让子控件的列表数据也刷新一下.

    public class FormA
    {
     public void Load()
        {
        ControlB controlB = new ControlB();
        FormA.Panel1.Add(controlB);
         // 因为子控件B为A的成员,只要将它的成员改为public,即可直接调用
            formA.controlB.FuncB();
        }
    }

    public class ControlB
    {
        public void FuncB() { Console.WriteLine("做了一堆事情"); }
    }

子控件如何调用父窗体成员:
应用场景:子控件保存数据后,希望父窗体内的列表刷新一下.
杀马特写法:

public class FormA
    {
        public void Load()
        {
            ControlB controlB = new ControlB(this);// 把自己传过去,就可以让子控件直接使用了
            FormA.Panel1.Add(controlB);
        }

        public void FuncA() { Console.WriteLine("做了两堆事情"); }
    }

    public class ControlB
    {
        FormA formBoss;
        public ControlB(FormA formA)
        {
            formBoss = formA;
        }
        public void DoSomethingAfterSaved()
        {
            formBoss.FuncA();// 父窗体某个刷新
        }
    }

委托正常写法(包含委托的理解)
子控件的委托=我的方法

namespace ConsoleApp5
{
    public delegate void DeleFunc();// 我是一个没参数的委托类型
    public class FormA
    {
        public void Load()
        {
            ControlB controlB = new ControlB();
            FormA.Panel1.Add(controlB);
            controlB.deleF = FuncA;// (我把方法甩(委托)给你了,你咋用都行,别烦我了)
        }
        public void FuncA() { Console.WriteLine("做了两堆事情"); }
    }

    public class ControlB
    {
        public DeleFunc deleF;// 我这个委托实体,已被别人赋值了一套方法
        public void DoSomethingAfterSaved()
        {
            // 保存后,调用父窗体的刷新的
            deleF(); deleF(); deleF(); deleF(); deleF();// 别人给我的,我可以随意劳役这个方法
        }
    }
}

所以为什么使用委托:我们访问不到的类,想使用他的方法(饭),可以给他一个委托(碗),让他把方法赋值给委托(把饭盛到碗里),这个委托(碗)是引用类型,我和你都访问到.

委托的别的用法(委托(方法的载体)作为参数)

public class ClassA
    {
        public void Load()
        {
            DeleFunc deleF;// 我这个委托实体
            ClassB cb = new ClassB();
            deleF = cb.FuncA;// (你把方法甩(委托)给我了,什么时候用取决于我)
            cb.Do(deleF);// 你喝什么由我做主
        }
    }

    public class ClassB
    {
        public void Do(DeleFunc funcRandom)
        {
            Console.WriteLine("我好无聊,不知道该干啥.老弟,你来安排我");
            funcRandom();
            Console.WriteLine("喝饱了,回家");
        }

        public void FuncA() { Console.WriteLine("我端起了一杯水,做了一些事喝了的方法"); }
        public void FuncB() { Console.WriteLine("我起了一瓶酒,喝了的方法"); }
    }

所以为什么使用委托:在我们编写程序的时候,程序的上下文是固定的,在某个关键的部分,不确定调用哪个函数,这时候需要让程序更换被调用函数,又不想两个函数耦合性太高,使用委托可实现间接调用,或者可替换调用.

有了委托了,特别方便了.但是为什么还要有事件,是委托有什么缺点么?
为什么要有事件?
看下面的例子

namespace 为什么使用事件
{
    public delegate void deleClick();
    class Sanlianji : Button // 定义一个可以点三下的按钮
    {
        int i = 0;
        //public event  deleClick dc;
        public deleClick dc;
        protected override void OnClick(EventArgs e)// 按钮点三下后, 开始执行别人传过来的委托(即点三下后,程序员想让我弄啥嘞)
        {
            i++;
            if (i == 3)
            {
                //执行委托
                dc();
                i = 0;
            }
        }
    }
}
namespace 为什么使用事件
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            sanlianji1.dc= func;// 点三下后,把func(点三下要做的事,赋值给委托),然后就实现了:点三下,弹出"你点击了三下特殊的按钮"
        }
        private void func()
        {
            MessageBox.Show("你点击了三下特殊的按钮");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 虽说实现了我们想要的功能,可是如果我们拉个普通按钮,直接调用委托(委托是公开的,啥时候谁想掉谁调用)如下
            sanlianji1.dc();// 出问题了,人家点三下才能弹出提示的美女(自定义按钮的功能),被一个猪(系统按钮)给拱了.还有可能被千千万万的猪任何时候都给拱了.
        }
    }
}

如上,所以我们做出来的委托,只想在我们想要的情况下调用,不能瞎调乱调, 因此,微软给我们弄了一个event关键词,在声明委托实体的时候加上event这个标志,就说明,这个委托比较害羞,藏在屋子里,外面的人想要给她沟通,必须按她的规矩来,通过+=赋值,-=取消委托
代码调整如下

namespace 为什么使用事件
{
    public delegate void deleClick();
    class Sanlianji : Button
    {
        int i = 0;
        public event deleClick dc;// 加上event后,就变成了事件(特殊的委托,不能随意=(盗用),只能通过+=(赋值)或-=(取消委托))
        protected override void OnClick(EventArgs e)
        {
            i++;
            if (i == 3)
            {
               //执行委托
                    if(dc!=null)// 可以通过这么写,来得到什么时候我们三点击是否会执行方法的效果
                     {
                        dc();
                      }
                i = 0;
            }
        }
    }
}
namespace 为什么使用事件
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            sanlianji1.dc = func;// 报错;通过赋值的方式赋值事件(特殊委托),不行了
            sanlianji1.dc += func;// 只能通过+=赋值了
        }
        private void func()
        {
            MessageBox.Show("你点击了三下");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            sanlianji1.dc();// 报错;通过直接调用事件(特殊委托),不行了
        }
    }
}

事件是什么样子的?
用我们经常见到事件的窗体举例
首先,系统命名了一个很标准的两参委托类型EventHandler
在这里插入图片描述

然后,系统定义了一个事件,规定了啥时候触发,啥时候调用这个委托
在这里插入图片描述

然后是赋值如下,

this.button1.Name = "button1";
//  右侧是正常的委托或者方法,左侧是事件,左侧限制了只有button1被点击了才能展示运行我们+=赋值的方法.
this.button1.Click += new System.EventHandler(this.button1_Click);
this.button1.Click += this.button1_Click;//这样也行的,方法赋值给委托嘛

所以,事件三小模块(都以按钮单击为例)
0.声明委托类型,创建委托实例(如上两张图)
1.调用体(什么时候调用)
如控制台Main方法中调用,按钮单击底层判断if(双击){写逻辑和调用体},或者我们自己的代码中写调用体
写法:
//…其他逻辑代码…
if (Click!=null)
{
Click();
}
//…其他逻辑代码…
2.执行内容体(符合单击条件后执行什么内容)
public void button1_Click(object sender,EventArgs e)
{
//…执行的个人代码逻辑内容…
}
3.事件赋值体(-=取消事件)
this.button1.Click += new System.EventHandler(this.button1_Click);
this.button1.Click += this.button1_Click;
而常见窗体事件,再双击后,仅需写执行内容体即可。

那日常生活中,事件怎么使用呢,我们怎么制造某种情况的事件呢
看下面这个例子:

namespace 委托与事件
{
    public delegate void CatShotEventHandler();// 委托类型
    class Program
    {
        static void Main(string[] args)
        {
            Cat cat = new Cat("Tom");
           
            Mouse mouse1 = new Mouse("Jerry");
            Mouse mouse2 = new Mouse("Jack");
            //  以下两行是事件赋值体
            cat.CatShot += new CatShotEventHandler(mouse1.Run);// 猫来了,老鼠要做的事儿
            cat.CatShot += new CatShotEventHandler(mouse2.Run);// 
            cat.shout();// 正常调用,以下两行是委托例子,不用看
           // CatShotEventHandler dele_catShout = new CatShotEventHandler(cat.shout);// 定义了一个正常的委托,赋值的方法为:猫来了
            //dele_catShout();// 调用这个委托方法,猫来了.方法内,如果已经给事件赋值,就执行赋值的方法,不执行.

            Console.ReadKey();
        }

        //static void cat_CatShot()
        //{
        //    throw new NotImplementedException();
        //}
    }
    class Cat
    {
        private string name;
        public Cat(string name)
        {
            this.name = name;
        }

        // 定义了一个事件(其实啊,就是定义了一个特殊的委托变量:特殊是指:不能随意=(盗用),只能通过+=(赋值)或-=(取消))
        public event  CatShotEventHandler CatShot;
      
        public void shout()//调用体
        {
            Console.WriteLine("喵喵,我是{0}",name );
            // 平时只是猫来,老鼠做什么不知道, 只有后来赋值了事件方法,猫来老鼠才逃.工作中的例子.平时双击的时候,我们想让系统执行我们造的特殊方法,但是有时候又不想,所以在双击事件里如此行写, 然后在需要双击执行我们方法之前+=,之后-=
            if (CatShot !=null)
            {
                CatShot();
            }
        }
    }

    class Mouse
    {
        private string name;
        public Mouse(string name)
        {
            this.name = name;
        }
        // 执行内容体
        public void Run()
        {
            Console.WriteLine("老猫来了,{0}快跑",name);
        }
    }
}

关于事件的自定义参数,如下
private void tabControlMain_SelectedPageChanged(object sender, DevExpress.XtraTab.TabPageChangedEventArgs e)
这些参数,我们可以用自定义类, 并如下调用
if (PersonSelectChanged != null && _PickingInvokeMethod) { PersonSelectChanged(null, new PersonSelectEventArgs() { Persons = GetSelectedPerson }); };

以上是关于C#委托和事件入门详解的主要内容,如果未能解决你的问题,请参考以下文章

C#入门详解(11)

《C#零基础入门之百识百例》(七十六) 委托事件实例练习1 -- 猫捉老鼠

《C#零基础入门之百识百例》(七十七) 委托事件实例练习2 -- 刘备招亲甘露寺

《C#零基础入门之百识百例》(七十八)委托事件实例练习3 -- 观察者模式

C#委托和事件详解

C# 事件详解