C#中的委托和事件

一:什么是委托

——委托是一种存储函数引用的类型,就像我们定义一个string str一样,这个str变量就是string类型。因为C#中没有函数类型,但是可以定义一个委托类型,把一个函数赋给这个委托,类似于C++中的函数指针
——委托的定义与类的定义类似,先定义,再声明,再创建实例,再使用,定义时需要加上delegate关键字但是不需要函数体

using System;

class MainClass
{
    //定义
    delegate void Fun1Del();
    //声明
    Fun1Del del1;

    static void Main()
    {
        del1 = Fun1();
        del1();
    }

    static void Fun1() { }
}

——当定义一个委托时,CLR会自动生成一个类,继承自System.MulticastDelegate,包含了构造函数、BeginInvoke、EndInvoke、Invoke,所以委托本质是一个Class。事件也类似,但是事件只对外部提供了一个add和一个remove接口,所以不能在外部调用方法
例如上面的委托例子,de1=Fun1()就相当于调用了构造函数,通过构造函数将Fun1方法的内存地址存起来。调用del1()时相当于调用了Invoke方法去调用当前存储的内存地址的方法


二:自定义委托

​using System;

class MainClass
{
    delegate void Fun1Del();
    delegate void Fun2Del(int num);
    delegate int Fun3Del(int num);

    static void Main()
    {
        Fun1Del del1 = Fun1;
        Fun2Del del2 = Fun2;
        Fun3Del del3 = Fun3;

        Fun1();
        Fun2(11);
        int num = Fun3(11);
    }

    static void Fun1() { }
    static void Fun2(int num) { }
    static int Fun3(int num) { return num; }
}

三:Action委托

Action是系统内置的委托类型,这样就不需要通过delegate定义委托类型,可以直接使用Action作为类型
Action只能指向一个没有返回值的方法

using System;

class MainClass
{
    static void Main()
    {
        Action action1 = Fun1;
        Action<int> action2 = Fun2;
        Action<int, string> action3 = Fun3;

        action1();
        action2(11);
        action3(11, "hi");
    }

    static void Fun1() { }
    static void Fun2(int num) { }
    static void Fun3(int num, string str) { }
}

四:Func委托

Func是系统内置的委托类型,这样就不需要通过delegate定义委托类型,可以直接使用Func作为类型
Func只能指向有返回值的方法,最后一个泛型参数是返回值类型

using System;

class MainClass
{
    static void Main()
    {
        Func<int> action1 = Fun1;
        Func<int, int> action2 = Fun2;
        Func<int, string, int> action3 = Fun3;

        action1();
        action2(11);
        action3(11, "hi");
    }

    static int Fun1() { return 11; }
    static int Fun2(int num) { return 11; }
    static int Fun3(int num, string str) { return 11; }
}

五:多播委托

多播委托是指一个委托指向多个方法,使用+=和-=去添加或移除委托,其实是调用了Delegate.Combine和Delegate.Remove方法,他会按照委托添加的顺序依次调用方法。对于有返回值的委托多播委托只能得到最后一个方法的返回结果

using System;

class MainClass
{
    static void Main()
    {
        Action action1 = Fun1;
        action1 += Fun2;
        action1();

        Func<int> action2 = Fun3;
        action2 += Fun4;
        Console.WriteLine(action2());
    }

    public static void Fun1() { Console.WriteLine("fun1"); }
    public static void Fun2() { Console.WriteLine("fun2"); }

    public static int Fun3() { Console.WriteLine("fun3"); return 11; }
    public static int Fun4() { Console.WriteLine("fun4"); return 111; }
}

使用GetInvocationList方法可以获取到当前委托中所有方法的委托


六:匿名方法

匿名方法就是没有名称没有返回值的方法,任何使用委托变量的地方都可以使用匿名方法,但是因为没有名字所以不能自己调用自己而只能通过委托去调用

using System;

class MainClass
{
    static void Main()
    {
        Action action1 = delegate { };
        Action<int> action2 = delegate (int num) { };
        Action<int, int> action3 = delegate (int num1, int num2) { };
        Func<int> action4 = delegate { return 11; };
    }
}

七:Lambda表达式

lambda表达式就是匿名方法的简写形式

using System;

class MainClass
{
    static void Main()
    {
        Action action1 = () => { };
        Action<int> action2 = (num) => { };
        Action<int, int> action3 = (num1, num2) => { };
        Func<int> action4 = () => { return 11; };
    }
}

八:事件

事件可以称为是一种特殊签名的委托
需要注意的是,委托可以在任何类中调用而事件只能在类的内部调用

using System;
using System.Linq;

class MainClass
{
    static void Main()
    {
        Test test = new Test();

        test.action1();//可以调用
        test.action2();//报错,不能在类的外部调用
    }
}

public class Test
{
    public  Action action1;
    public event Action action2;
}

九:烧水案例

例如一个烧水壶,当水温超过95度时警报器会报警告诉你水的温度,显示器会显示当前的水温

using System;

class MainClass
{
    static void Main()
    {
        Kettle kettle = new Kettle();
        kettle.BoilWater();
    }
}

public class Kettle
{
    private int temperature = 96;

    public void BoilWater()
    {
        if (temperature > 95)
        {
            Alert(temperature);
            Show(temperature);
        }
    }

    private void Alert(int param)
    {
        Console.WriteLine("警告!!" + param);
    }

    private void Show(int param)
    {
        Console.WriteLine("当前温度:" + param);
    }
}

上面的例子虽然能完成我们之前描述的工作,但是却不够完美,首先烧水壶由三部分组成:烧水器、警报器、显示器,它们来自于不同厂商并进行了组装,那么,应该是烧水器仅仅负责烧水,它不能发出警报也不能显示水温,在水烧开时由警报器发出警报,由显示器显示水温,下面使用观察者模式对上面的列子进行优化

using System;

class MainClass
{
    static void Main()
    {
        Kettle kettle = new Kettle();
        Alarm alarm = new Alarm();
        Display display = new Display();

        kettle.BoilEvent += alarm.Alert;
        kettle.BoilEvent += display.Show;
        kettle.BoilWater();
    }
}

public class Kettle
{
    private int temperature = 96;

    public delegate void BoilHandler(int param);
    public event BoilHandler BoilEvent;

    public void BoilWater()
    {
        if (temperature > 95)
        {
            BoilEvent(temperature);
        }
    }
}

public class Alarm
{
    public void Alert(int param)
    {
        Console.WriteLine("警告!!" + param);
    }
}

public class Display
{
    public void Show(int param)
    {
        Console.WriteLine("当前温度:" + param);
    }
}

假如现在不只是想要获得烧水壶当前的温度,还想知道生厂商,那么就需要改动烧水壶整个类,使用.Net规范的写法如下

using System;

class MainClass
{
    static void Main()
    {
        Kettle kettle = new Kettle();
        Alarm alarm = new Alarm();
        Display display = new Display();

        kettle.BoilEvent += alarm.Alert;
        kettle.BoilEvent += display.Show;
        kettle.BoilWater();
    }
}

public class Kettle
{
    public class KettleEventArgs : EventArgs
    {
        public readonly int temperature;
        public KettleEventArgs(int temperature)
        {
            this.temperature = temperature;
        }
    }

    int temperature = 96;
    public string producer = "boshi";

    public delegate void BoiledEventHandler(object sender, KettleEventArgs e);
    public event BoiledEventHandler BoilEvent;

    public void BoilWater()
    {
        if (temperature > 95)
        {
            KettleEventArgs e = new KettleEventArgs(temperature);
            BoilEvent(this, e);
        }
    }
}

public class Alarm
{
    public void Alert(object sender, Kettle.KettleEventArgs e)
    {
        Console.WriteLine("警告!!" + e.temperature);
    }
}

public class Display
{
    public void Show(object sender, Kettle.KettleEventArgs e)
    {
        Console.WriteLine("当前温度:" + e.temperature);
    }
}

十:委托和事件的区别

——委托是修饰的是一个类型,事件修饰的是一个对象
——委托可以作为方法参数传递,事件不能
——委托在赋值时无论类内还是类外都可以用=或+=赋值,事件在类内可以使用=,在类外只能通过+=赋值
——委托可以在类内和类外调用,事件只能在类内调用
——委托用于回调函数、方法引用参数的传递,事件用于观察者模式、消息订阅等