C#学习笔记(一)(委托与事件专题 )

128 阅读4分钟

委托概述

什么是委托

  • 委托是一种数据类型,像类一样(可以定义委托类型变量),委托类型变量是一个存储方法的变量;
public delagate 返回值类型 委托类型名(存储的方法的参数)
public delegate void MyDelegate();
namespace DelegateDemo1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 2. 声明了一个委托类型变量,并new了一个委托对象,并传递了一个无参无返回值的方法进去
            // 即m1 保存了M1方法
            MyDelegate m1 = new MyDelegate(M1);

            // 3. 调用m1委托的时候,就相当于调用了M1方法
            m1();
        }
        static void M1()
        {
            Console.WriteLine("M1是一个无参无返回值的方法!");
        }
    }
    // 1. 定义了一个委托类型,用来保存无参 无返回值的方法
    // 委托要定义在命名空间中,和class是一个级别的
    public delegate void MyDelegate();

    public class MyClass
    {

    }
}

使用场景

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateDemo1
{
    public delegate void WriteTimeDelegate();
    public class ProxyPerson
    {
        // 调用DoSomething时,需要一个委托类型的方法
        // 由于定义委托时,定义的是一个无参无返回值类型的委托,因此,这个参数也就是一个无参无返回值的方法
        public void DoSomething(WriteTimeDelegate writeTimeDelegate)
        {
            Console.WriteLine("==========================");
            Console.WriteLine("=======复杂逻辑代码=========");
            // 想实现的功能:当执行完第二句代码时,输出一下系统时间
            if(writeTimeDelegate != null)
            {
                writeTimeDelegate();
            }
            Console.WriteLine("=======复杂逻辑代码=========");
            Console.WriteLine("==========================");
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateDemo1
{
    public class DelegatePerson1
    {
        public void WriteTimeToDB()
        {
            Console.WriteLine($".sql,{System.DateTime.Now.ToString()}");
        }
    }
    public class DelegatePerson2
    {
        public void WriteTimeToFile()
        {
            Console.WriteLine($".txt,{System.DateTime.Now.ToString()}");
        }
    }
}
namespace DelegateDemo1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ProxyPerson proxyPerson = new ProxyPerson();
            DelegatePerson1 delegatePerson1 = new DelegatePerson1();   
            proxyPerson.DoSomething(delegatePerson1.WriteTimeToDB);

            DelegatePerson2 delegatePerson2 = new DelegatePerson2();
            proxyPerson.DoSomething(delegatePerson2.WriteTimeToFile);
        }

    }
}

意义

  • 代码复用,把能复用的代码复用,把变化的部分封装起来,用传进来的委托变量(就是方法)代替;
  • 有点像是Vue里的插槽,在可复用的代码中留了个插槽,将来在调用时,想在这个插槽中写什么都可以通过委托传进来。
  • Winform的控件就是使用委托来实现的。

案例 窗体之间传值

namespace WinFormDelegate
{
    public delegate void MyDelegate(string text);
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void button1_Click(object sender, EventArgs e)
        {
            Form2 f2 = new Form2(textBox1.Text.Trim(), UpdateText);
            f2.Show();
        }
        public void UpdateText(string val)
        {
            this.textBox1.Text = val;
        }
    }
    public delegate void UpdateTextDelegate(string val);
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WinFormDelegate
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }
        public Form2(string n, UpdateTextDelegate updateTextDelegate) :this()
        {
            this.textBox1.Text = n;
            this._updateText = updateTextDelegate;
        }
        private UpdateTextDelegate _updateText;
        private void button1_Click(object sender, EventArgs e)
        {
            _updateText(this.textBox1.Text);
            this.Close();
        }

    }
}
  • 这个例子中,Form1把操作它自己的方法传递传递给Form2,就好像在它自己身上留了个空,先去调用Form2,操作Form2,当Form2想要做的事件做完后,再调用Form1的方法,把主线拉回到Form1,在这个过程中就可以实现窗体之间的通信了。

匿名方法 与 Lambda表达式

  • 由于委托变量需要方法作为参数,那么就有这么一种场景,这一个方法只需要使用一次,这种情况下,不必声明方法的名称,只需要创建一个匿名方法即可,而Lambda表达式就是对匿名方法的简化。
  • 匿名方法不能直接在类中定义,而是在给委托变量赋值的时候,就现做一个方法作为委托变量的方法。
namespace DelegateDemo1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 定义匿名方法的关键字 delegate
            MyDelegate myDelegate = delegate ()
            {
                Console.WriteLine("这是一个匿名方法");
            };
            myDelegate();

            MyDelegate1 myDelegate1 = delegate (string msg)
            {
                Console.WriteLine($"这是一个有参无返回值的匿名方法,{msg}");
            };
            myDelegate1("你好");

            MyDelegate2 myDelegate2 = delegate (int n1, int n2, int n3)
            {
                return n1 + n2 + n3;
            };
            int result = myDelegate2(1,2,3);
            Console.WriteLine(result);
        }
    }
    public delegate void MyDelegate();
    public delegate void MyDelegate1(string msg);
    public delegate int MyDelegate2(int n1,int n2,int n3);
}
  • Lambda表达式由于委托限制了传参的类型,所以从来不需要声明类型。
namespace DelegateDemo1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 定义匿名方法的关键字 delegate
            MyDelegate myDelegate = () =>
            {
                Console.WriteLine("这是一个Lambda表达式");
            };
            myDelegate();

            MyDelegate1 myDelegate1 = msg =>
            {
                Console.WriteLine($"这是一个有参无返回值的Lambda表达式,{msg}");
            };
            myDelegate1("你好");

            MyDelegate2 myDelegate2 = (n1, n2, n3) =>
            {
                return n1 + n2 + n3;
            };
            int result = myDelegate2(1,2,3);
            Console.WriteLine(result);
        }
    }
    public delegate void MyDelegate();
    public delegate void MyDelegate1(string msg);
    public delegate int MyDelegate2(int n1,int n2,int n3);
}

泛型委托

  • C# 已经定义好了委托,不需要我们再去声明一个委托类型
  • Action 用于声明无参无返回值类型的委托变量
  • Action<> 用于声明有参(最多16个参数重载)无返回值类型的委托变量
  • Func<> 只有泛型版本的,没有非泛型版本的,因为必须告诉Func返回值类型,就通过参数声明;
  • Func<> 最后一个参数一定是返回值类型
namespace DelegateDemo1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 定义匿名方法的关键字 delegate
            Action myDelegate = () =>
            {
                Console.WriteLine("这是一个Lambda表达式");
            };
            myDelegate();

            Action<string> myDelegate1 = msg =>
            {
                Console.WriteLine($"这是一个有参无返回值的Lambda表达式,{msg}");
            };
            myDelegate1("你好");

            Func<int,int,int,int> myDelegate2 = (n1, n2, n3) =>
            {
                return n1 + n2 + n3;
            };
            int result = myDelegate2(1,2,3);
            Console.WriteLine(result);
        }
    }
}

多播委托(委托链,委托的组合)

  • 一个委托可以存储多个方法;
namespace DelegateDemo1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Action<string> myDelegate1 = M1;
            myDelegate1 += M2;
            myDelegate1 += M3;
            myDelegate1 += M4;

            myDelegate1 -= M3;
            myDelegate1("你好");
        }
        static void M1(string msg)
        {
            Console.WriteLine($"M1 {msg}");
        }
        static void M2(string msg)
        {
            Console.WriteLine($"M2 {msg}");
        }
        static void M3(string msg)
        {
            Console.WriteLine($"M3 {msg}");
        }
        static void M4(string msg)
        {
            Console.WriteLine($"M4 {msg}");
        }
    }
}
  • 用ILSpy打开;
  • 发现IL是用combine连接在一起的,好像字符串一样,并没有创建新的;
  • 而是每次创建一个委托对象,每次跟之前的委托对象一起放进一个委托数组中;
  • 多播委托在调用时,一次遍历委托对象,最终实现把所有委托对象都调用完成;
  • 委托跟字符串一样,具有不可变性;
private static void Main(string[] args)  
{  
    Action<string> myDelegate1 = M1;  
    myDelegate1 = (Action<string>)Delegate.Combine(myDelegate1, new Action<string>(M2));  
    myDelegate1 = (Action<string>)Delegate.Combine(myDelegate1, new Action<string>(M3));  
    myDelegate1 = (Action<string>)Delegate.Combine(myDelegate1, new Action<string>(M4));  
    myDelegate1 = (Action<string>)Delegate.Remove(myDelegate1, new Action<string>(M3));  
    myDelegate1("你好");  
}
  • 其实委托就是一个类,并且是一个密封类sealed;
  • 用ILSpy可看见,它继承MulticastDelegate:Delegate,并且有Invoke,BeginInvoke,EndInvoke三个方法
public abstract class MulticastDelegate : Delegate
public virtual extern IAsyncResult BeginInvoke(AsyncCallback callback, object @object);
public virtual extern void EndInvoke(IAsyncResult result);
public virtual extern void Invoke();
  • 实现多播委托
  • 参照ILSpy实现
namespace DelegateDemo1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            MyDelegate md =  new MyDelegate(M1);
            md = (MyDelegate)Delegate.Combine(md, new MyDelegate(M2));
            md = (MyDelegate)Delegate.Combine(md, new MyDelegate(M3));
            md = (MyDelegate)Delegate.Combine(md, new MyDelegate(M4));

            Delegate[] delegates = md.GetInvocationList();
            for (int i = 0; i < delegates.Length; i++)
            {
                (delegates[i] as MyDelegate)("你好");
            }
        }
        static void M1(string msg)
        {
            Console.WriteLine($"M1 {msg}");
        }
        static void M2(string msg)
        {
            Console.WriteLine($"M2 {msg}");
        }
        static void M3(string msg)
        {
            Console.WriteLine($"M3 {msg}");
        }
        static void M4(string msg)
        {
            Console.WriteLine($"M4 {msg}");
        }
    }

    public delegate void MyDelegate(string msg);
}