一、事件与委托简介

事件与委托是c#中的一个重要的特性,使用这两个特性可以轻易的实现函数式编程、发布者-订阅者模式等诸多功能。事件和委托就像一个函数的容器,可以存储函数并调用。如果你有c/c++或java语言的基础的话,你可以暂时将c#中的事件与委托理解为c的函数指针,c++中的仿函数或者java中的函数式接口,当然,这种说法并不严谨只是为了暂时方便理解,至于区别我们稍后再谈。简单总结一下,事件与委托就是函数的容器,可以存储并调用其存储的函数。

二、Delegate、Event、Func、Action的联系和区别

Delegate其实就是c#中的委托,Func和Action是c#提供好的两种委托类型,Func委托可以存储多个类型参数,一个返回值类型的函数,Action委托可以存储多个类型参数,无返回值类型的函数。而Event则是Delegate的一种特殊的实现方式Delegate类型的引用可以直接赋值给其它Delegate类型的引用前提是两个Delegate存储的函数类型是相同的),但Event则不允许直接赋值。具体联系和区别看下图。

三、delegate (委托)

前文已经说过,delegate可以理解为函数的容器,接下来介绍一下delegate的声明和用法。

1.delegate的声明

delegate的声明格式如下:

delegate 返回值类型 委托名(参数列表)

举个例子:

public delegate void DelegateTest(string str);

delegate为声明委托的关键字,看起来很像方法的签名。

2.delegate的用法

因为delegate其实是一个引用类型,所以我们首先要实例化,然后就可以为其绑定函数或方法,最后便可以调用该委托中的方法。接着上面的例子,我们可以在类中创建一个函数类型和委托一样的方法,然后再把它绑定给该委托。

//声明委托类型
public delegate void DelegateTest(string str);

public class MainClass {
    //委托实例化
    public static DelegateTest delegateTest;
    
    //创建一个和委托存储相同函数类型的方法
    public static void MyMethod(string str) {
        Console.WriteLine(str);
    }
    
    
    public static void Main(string[] args) {

        //为委托绑定方法
        delegateTest += MyMethod;

        //调用委托绑定的方法可以直接通过"()"调用
        
        delegateTest("test");
        
        //也可以通过委托的Invoke方法调用
        delegateTest.Invoke("test");
    }
}

 四、Event(事件)

事件是delegate的一种特殊实例,与原初的delegate的实例区别在于,delegate之间可以直接用"="赋值,但这样会导致原委托中绑定的方法清空,很不安全,因此,event在此基础上做出改进,将=赋值私有化,即事件无法使用=直接赋值。事件的声明也很简单,在实例化委托时,在委托类型前加上"event"关键字即可。

public delegate void DelegateTest(string str);

public class MainClass {

    //加上event关键字便可声明为事件
    public static event DelegateTest delegateTest;

    public static void MyMethod(string str) {
        Console.WriteLine(str);
    }

    public static void Main(string[] args) {
        delegateTest += MyMethod;
        delegateTest("test");

        delegateTest.Invoke("test");
    }
}

五、Action

action是c#预先封装好的一种委托类型,使用action我们就可以不用预先声明delegate委托,可以直接将action按照需求实例化进行使用。

action用于存储拥有多个参数而无返回值的函数,action为泛型引用,提供了16种不同的版本,也就是泛型参数个数从1到16,也就是说,action最多可以支持拥有16个参数的函数。

action的使用格式如下:

Action<参数类型, 参数类型......(最多16个)> 名字;

举个例子:

public class MainClass {

    //声明Action,可以绑定两个整型,一个string参数且无返回值的函数
    public static Action<int, int, string> MyAction;

    public static void Main(string[] args) {

        //绑定函数,这里使用lambda表达式
        MyAction += (a, b, str) => {
            //TODO
        };

        //调用Action绑定的方法
        MyAction(1, 3, "test");
        MyAction.Invoke(1, 3, "test");
    }
}

 

六、Func

func也是c#预先封装好的一种委托类型,其声明和用法和Action几乎相同,和Action的区别再于Func的最后一个泛型参数是返回值类型,也就是说,func可以支持拥有多个参数,一个返回值的函数

举个例子:

public class MainClass {

    //声明Func,能绑定两个整型参数,一个string类型返回值的函数
    public static Func<int, int, string> MyFunc;

    public static void Main(string[] args) {

        //绑定函数,这里使用lambda表达式
        MyFunc += (a, b) => {
            //TODO
            return "str";
        };
        
        //调用Func绑定的方法
        MyFunc(1, 3);
        MyFunc?.Invoke(1, 3);
    }
}

 

 七、多播

在C#中,多播是一种委托的特性,允许一个委托对象持有多个方法引用,并在调用委托时依次调用这些方法。多播委托可以将多个方法绑定到同一个委托实例上,然后通过调用委托来依次执行这些方法。一次调用可以触发多个方法的执行。举个例子:

 

//声明委托
public delegate void DelegateTest(string str);

public class MainClass {

    //委托实例化
    public static DelegateTest MyDelegate;

    //定义3个方法
    public static void Method1(string str) {
        Console.WriteLine("method1 : " + str);
    }
    
    public static void Method2(string str) {
        Console.WriteLine("method2 : " + str);
    }
    
    public static void Method3(string str) {
        Console.WriteLine("method3 : " + str);
    }


    public static void Main(string[] args) {

        //给MyDelegate绑定多个方法
        MyDelegate += Method1;
        MyDelegate += Method2;
        MyDelegate += Method3;

        //调用
        MyDelegate.Invoke("test");
    }
}

运行结果如下:

 

当我们调用MyDelegate中的方法时,其绑定的方法将会全部依次执行,需要注意的是,如果是有返回值的委托进行多播,那么调用委托后返回的值为最后绑定的函数或方法的返回值

 

八、对委托事件的简单应用

这里笔者将会举一个发布订阅模式的简单例子来帮助大家更加深入的理解事件和委托。

我们创建一个敌人类(Enemy)作为发布者,其行为是攻击玩家,并且内部有一个委托的引用。再创建一个玩家类(Player)其内部是受到攻击时的行为的方法,我们需要把玩家受到攻击的方法绑定到敌人类的委托里,这样,当敌人攻击玩家时便可以通过调用委托来通知玩家。具体代码如下。

public class Enemy {
    //声明委托
    public delegate void PlayerAction();

    //实例化委托
    public PlayerAction PlayerActions;

    //调用委托中的方法(通知订阅者)
    public void AttackPlayer() {
        this.PlayerActions?.Invoke();
    }
}

public class Player {
    public void Attacked() {
        //TODO
        Console.WriteLine("玩家受到攻击");
    }
}

public class MainClass {
    public static void Main(string[] args) {
        Enemy enemy = new Enemy();
        Player player = new Player();

        //订阅
        enemy.PlayerActions += player.Attacked;
        enemy.AttackPlayer();
    }
}

运行结果如下:

九、总结

c#中的事件与委托是函数的容器,可以绑定多个函数或方法,其中Event是委托的特殊实例化,而Action和Func则是对委托的两种预先封装,两者都支持多个参数的函数,区别在于Action绑定的函数无返回值,而Func绑定的函数有一个返回值。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐