C#委托详解

委托概述

委托也叫代理(delegate),委托实际上是一个类,在使用委托的时候,你可以像对待一个类一样对待它。即先声明,再实例化。只是有点不同,类在实例化之后叫对象或实例,但委托在实例化后仍叫委托。委托定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。C#中如果将一个方法委托给一个委托,这个委托就可以全权代理这个方法的执行。说白了,委托就是第三方,调用者告诉第三方要做什么,然后调用者就不用管了,这个委托(第三方)就会去调用方法去帮你实现,用最通俗易懂的话来讲,你就可以把委托看成是用来执行方法(函数)的一个东西。委托是一个引用类型,所以它具有引用类型所具有的通性。它保存的不是实际值,而是保存对存储在托管堆(managed heap)中的对象的引用。那它保存的是对什么的引用呢?委托保存的是对函数(function)的引用。实际上类似于C++中的函数指针,因为C#中不存在指针,所以用代理可以完成一些原来在C++中用函数指针完成的操作,例如传递一个类A的方法m给另一个类B的对象,使得类B的对象能够调用这个方法m。但与函数指针相比,delegate有许多函数指针不具备的优点。首先,函数指针只能指向静态函数,而delegate既可以引用静态函数,又可以引用非静态成员函数。在引用非静态成员函数时,delegate不但保存了对此函数入口指针的引用,而且还保存了调用此函数的类实例的引用。其次,与函数指针相比,delegate是面向对象、类型安全、可靠的受控(managed)对象。也就是说,能够保证delegate指向一个有效的方法,你无须担心delegate会指向无效地址或者越界地址。

为什么要使用委托?

有人会问,我直接调用想用的函数就可以了,为什么非要使用委托这个中间人?是的,如果你做的事情,只需直接调用对应的函数就能完成了,使用委托确实是用法不当,那只是形式上使用委托,不是针对实际应用需要来使用委托。

1.解除耦合

在面向对象编程中,能解除耦合就是最大的好处。委托也可以说是观察者模式,这个模式的好处也可以说是委托的好处,它能使一个对象状态的改变立即通知其他对象,而这个对象和其它的对象是完全分离的,就是互相不知道对方的存在,这样在程序规模变大的情况下更加容易维护(每一个对象的修改不涉及另一个对象),更加容易扩展(理由同上),也更加灵活。一句话,就是达到解耦的目的。下面通过代码来解释:

代码实现这样的效果:点击FormA中的按钮后,在Form1中显示“FormA中的按钮被点击了”;点击FormB中的按钮后,在Form1中显示“FormB中的按钮被点击了”。

Form1代码如下:

public partial class Form1 : Form
    {
        public delegate void ShowWhichClick(string s);//定义一个委托
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            ShowWhichClick showWhichClick = new ShowWhichClick(ShowText);//声明并实例化一个委托,指向具有相同签名的ShowText函数
            FormA formA = new FormA(showWhichClick);//通过构造函数,向FormA中传递这个委托
            FormB formB = new FormB(showWhichClick);//通过构造函数,向FormB中传递这个委托
            formA.Show();
            formB.Show();
        }
        public void ShowText(string s)
        {
            this.textBox1.Text += "→";
            this.textBox1.Text += s;
        }
    }

FormA代码如下:

public partial class FormA : Form
    {
        private Form1.ShowWhichClick showWhichClick;//在FormA中也声明一个委托,用于存储通过构造函数传进来的Form1中声明的委托

        public FormA()
        {
            InitializeComponent();
        }

        public FormA(Form1.ShowWhichClick showWhichClick)
        {
            InitializeComponent();
            this.showWhichClick = showWhichClick;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            showWhichClick("FormA内的按钮被点击了");//调用委托,传参数
        }
    }

FormB代码如下:

public partial class FormB : Form
    {
        private Form1.ShowWhichClick showWhichClick;//在FormB中也声明一个委托,用于存储通过构造函数传进来的Form1中声明的委托

        public FormB()
        {
            InitializeComponent();
        }

        public FormB(Form1.ShowWhichClick showWhichClick)
        {
            InitializeComponent();
            this.showWhichClick = showWhichClick;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            showWhichClick("FormB内的按钮被点击了");//调用委托,传参数
        }
    }

解读:

欲实现这一功能,其实也可以不用委托,也可以把Form1的对象实例通过FormA和FormB的构造函数传进去,然后在FormA和FormB的点击事件函数(button1_Click)中调用ShowText方法……好像我这个例子举得不恰当,我再研究研究,明白了后再更新,众位大神是否有恰当的例子?

2.提高可扩展性

使用委托能够增强程序的可扩展性,下面通过一个Window Form的 ”文字抄写员“ 程序要解释下为什么。程序实现的功能是:在下方文本框输入文字,勾选“书写到”组合框中的“文本区1”或“文本区2”复选框后点击“开始”按钮,程序会自动将文本框中的文字”抄写“到对应的文本区中去。程序界面如下:

传统的实现代码为:


    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (checkBox1.Checked == true)
            {
                textBox1.Clear();
                textBox1.Refresh();
                // 调用方法WriteRichTextBox1向文本区1写入文字
                this.WriteTextBox1();
                textBox3.Focus();
                textBox3.SelectAll();
            }
            if (checkBox2.Checked == true)
            {
                textBox2.Clear();
                textBox2.Refresh();
                // 调用方法WriteRichTextBox2向文本区2写入文字
                this.WriteTextBox2();
                textBox3.Focus();
                textBox3.SelectAll();
            }
        }

        private void WriteTextBox1()
        {
            string data = textBox3.Text;
            for (int i = 0; i < data.Length; i++)
            {
                textBox1.AppendText(data[i].ToString());
                //间歇延时
                DateTime now = DateTime.Now;
                while (now.AddSeconds(1) > DateTime.Now)
                { }
            }
        }

        private void WriteTextBox2()
        {
            string data = textBox3.Text;
            for (int i = 0; i < data.Length; i++)
            {
                textBox2.AppendText(data[i].ToString());
                //间歇延时
                DateTime now = DateTime.Now;
                while (now.AddSeconds(1) > DateTime.Now)
                { }
            }
        }
    }

从代码中会发现WriteTextBox1()方法和WriteTextBox2()只有一行代码不一样的( textBox1.AppendText(data[i].ToString()); 和 textBox2.AppendText(data[i].ToString());),其他都完全一样,而这条语句的差别就在于向其中写入文本的控件对象不一样,一个是TextBox1和TextBox2,现在这样代码是实现了功能,但是我们试想下,如果要实现一个写入的文本框不止2个,而是好几十个甚至更多,那么不久要写出同样多数量的用于写入文本区的方法了吗?这样就不得不写重复的代码,导致代码的可读性就差,这样写代码也就是面向过程的一个编程方式,因为函数是对操作过程的一个封装,要解决这个问题,自然我们就想到面向对象 编程,此时我们就会想到把变化的部分封装起来,然后再把封装的对象作为一个对象传递给方法的参数的(这个思想也是一种设计模式——策略模式,关于设计模式系列会在后面也会给出的),下面就利用委托来重新实现下这个程序:

public partial class Form1 : Form
    {
        // 定义委托
        private delegate void WriteTextBox(char ch);
        // 声明委托
        private WriteTextBox writeTextBox;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (checkBox1.Checked == true)
            {
                textBox1.Clear();
                textBox1.Refresh();
                // 实例化委托
                writeTextBox = new WriteTextBox(WriteTextBox1);
                // 作为参数
                WriteText(writeTextBox);

                textBox3.Focus();
                textBox3.SelectAll();
            }
            if (checkBox2.Checked == true)
            {
                textBox2.Clear();
                textBox2.Refresh();
                // 实例化委托
                writeTextBox = new WriteTextBox(WriteTextBox2);
                // 作为参数
                WriteText(writeTextBox);

                textBox3.Focus();
                textBox3.SelectAll();
            }
        }

        private void WriteText(WriteTextBox writetextbox)
        {
            string data = textBox3.Text;
            for (int i = 0; i < data.Length; i++)
            {
                // 使用委托
                writetextbox(data[i]);
                DateTime now = DateTime.Now;
                while (now.AddSeconds(1) > DateTime.Now)
                { }
            }
        }

        private void WriteTextBox1(char ch)
        {
            textBox1.AppendText(ch.ToString());
        }
        private void WriteTextBox2(char ch)
        {
            textBox2.AppendText(ch.ToString());
        }
    }

引入委托后实现的代码中,我们通过WriteText方法来向文本区写入内容,它所执行的只是抽象的”写文本“操作,至于究竟向哪个文本框写入文字,对于编写WriteText方法的程序来说是不知道的,委托writeTextBox就像一个接口一样(面向对象设计原则中有一个很重要的原则就是——针对接口编程,不针对实现编程),屏蔽了操作对象的差别(方法到底是想向文本区1写入文本还是像文本区2写入文本,现在我方法里面不需要去关心,我只需要集中在实现”书写文本”这个操作,而不必纠结操作对象的选择)。假如不采用委托,则需要编写多个WriteText方法,不但重复代码量很大,而且如果这时突然告诉你要删掉某个文本框或者加入某些个文本框,你要做的改动会比较大,一个好的程序应该考虑到:在未来需要变动时,我们做的改动尽可能的少,而且改动时不容易出错,即扩展性强。

什么时候使用委托?

1.需要将方法作为一个函数的参数传递时(类似于C/C++的函数指针);
2.在两个不能直接调用的方法中作为桥梁,如:在多线程中的跨线程的方法调用就得用委托;
3.当不知道方法具体实现什么时使用委托,如:事件中使用委托,事件的执行都是通过委托来实现的。

如何使用委托?

 实现一个delegate是很简单的,通过以下3个步骤即可实现一个delegate:
1.声明一个delegate对象,它应当与你想要传递的方法具有相同的参数和返回值类型。
    声明一个代理的例子:
    public delegate int MyDelegate(string message);

2.创建delegate对象,并将你想要传递的函数作为参数传入。
     创建代理对象的方法:
    1). MyDelegate myDelegate = new MyDelegate(实例名.方法名);
    2). MyDelegate myDelegate = new MyDelegate(类名.方法名);
注:如果需要代理的方法是一个static静态方法的话,采用第2种方式,否则采用第1种方式。

3.在要实现异步调用的地方,通过上一步创建的对象来调用方法。
    可以直接使用代理调用代理所指向的方法:
    myDelegate(向方法传递的参数);

一些需要注意的事情

委托即“代理”(delegate):“代理”是类型安全的并且完全面向对象的。
(1)在C#中,所有的代理都是从System.Delegate类派生的(delegate是System.Delegate的别名)。
(2)代理隐含具有sealed属性,即不能用来派生新的类型。
(3)代理最大的作用就是为类的事件绑定事件处理程序。
(4)在通过代理调用函数前,必须先检查代理是否为空(null),若非空,才能调用函数。
(5)在代理实例中可以封装静态的方法,也可以封装实例方法。
(6)在创建代理实例时,需要传递将要映射的方法或其他代理实例以指明代理将要封装的函数原型(.NET中称为方法签名:signature)。注意,如果映射的是静态方法,传递的参数应该是类名.方法名,如果映射的是实例方法,传递的参数应该是实例名.方法名。
(7)只有当两个代理实例所映射的方法以及该方法所属的对象都相同时,才认为它们是想等的(从函数地址考虑)。
(8)多个代理实例可以形成一个代理链(多播委托),System.Delegate中定义了用来维护代理链的静态方法Combion,Remove,分别向代理链中添加代理实例和删除代理实例。
(9)代理三步曲:
a.生成自定义代理类:delegate int MyDelegate();
b.然后实例化代理类:MyDelegate d = new MyDelegate(MyClass.MyMethod);
c.最后通过实例对象调用方法:int ret = d();

 


参考:
1、https://www.jianshu.com/p/99fa81509dcb
2、https://baike.baidu.com/item/%E5%A7%94%E6%89%98/7900553?fr=aladdin
3、https://zhidao.baidu.com/question/86741687.html
4、https://bbs.csdn.net/topics/390244872
5、https://blog.csdn.net/weixin_39927996/article/details/83579044
6、https://zhidao.baidu.com/question/353522952.html
7、https://blog.csdn.net/wxfx888/article/details/78079970

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值