Delegate and Event

Delegate

C# 中的一种类型,可以视为方法的 “指针”,将方法作为参数传递。

不过另一种理解,委托就像是一个可以动态改变行为的容器(在不同的引用下),就像星之卡比,卡比吃什么就变成什么。

NO BODY

"A delegate declaration looks like a method declaration, except that it doesn’t have an implementation block."

Invoke

使用委托被称为 “Invoke”(而不是 “use”)

// 定义一个委托类型(定义卡比可以获得什么样的能力)
public delegate void AttackAction(float power);

// 创建委托实例(卡比本身)
AttackAction attackDelegate;

// 各种可能的方法(各种能力)
void FireAttack(float power) { /* 发射火球 */ }
void IceAttack(float power) { /* 释放冰块 */ }
void SwordAttack(float power) { /* 挥舞宝剑 */ }

// 委托引用某个方法(卡比吸入某个敌人)
attackDelegate = FireAttack;  // 卡比获得火焰能力

// 调用委托(卡比使用当前能力)
attackDelegate(1.5f);  // 使用火焰能力

// 改变委托引用(卡比吸入新敌人)
attackDelegate = IceAttack;  // 卡比获得冰冻能力

// 再次调用委托(卡比使用新能力)
attackDelegate(1.5f);  // 使用冰冻能力

但是和星之卡比不同的是,委托可以是多播的,它可以通过 += 和 -= 操作符吞下或者移除很多方法,然后同时具有这些方法的能力。这样子比较类似于林克在王国之泪中打下神庙可以有的英杰之力。

// 定义一个委托类型
public delegate void SuperPower(float intensity);

// 创建委托实例
SuperPower powers = null;

// 添加多个方法
powers += FireAttack;     // 添加火焰能力
powers += IceAttack;      // 添加冰冻能力
powers += LightningAttack; // 添加闪电能力

// 调用委托时,会按添加顺序依次执行所有方法
powers?.Invoke(2.0f); // 依次使用火焰、冰冻和闪电能力

// 第二种调用办法
if (delVal != null)
{
    powers(2.0f);
}

Immutable

委托实际上是不可变的,当使用 += 和 -= 对 invoking list 进行更新时,实际上是创建了一个新的委托实例。只是这个过程对 coder 是透明的

Event

在 Publisher - Subscriber 模式下,作为 Publisher 的 event 和作为 Subscriber 的 event handler 互相配合,创建低耦合的代码。

event 维护一个私有的 delegate。event 只具备增减 handlers 和被触发的操作。当 event 被触发时,其 invoke 其中的 delegate,然后依次调用 invocation list 中的方法:

Structure and terminology of a class with an event

An Event Is a Member

event 是一个成员(因此必须在一个 class 或者 structure 中),而不是一个 type。

// 逢七必过

delegate void Handler(int number); // define event

class SevenGame
{
    public event Handler achor; // declare event

    public void Start()
    {
        for (int i = 0; i != 100; i++)
        {
            if (i % 7 == 0 || i % 10 == 7)
            {
                achor?.Invoke(i); // invoke event
                continue;
            }
            Console.WriteLine(i);
        }
    }
}

class Seven
{
    void divSeven(int num)
    {
        if (num % 7 == 0)
        {
            Console.WriteLine($"{num}: 可以被七整除");
        }
    }
    
    void isSeven(int num)
    {
        if (num % 10 == 7)
        {
            Console.WriteLine($"{num}: 中间有七");
        }
    }

    public Seven(SevenGame game)
    {
        // initialize: invoking list
        game.achor += isSeven;
        game.achor += divSeven;
    }
}

class Program
{
    static void Main(string[] args)
    {
        SevenGame game = new SevenGame();
        Seven playSeven = new Seven(game);
        game.Start();
    }
}

Unity 还提供了自己的事件系统,如 UnityEvent(在 UnityEngine.Events 命名空间中),它是基于 C# 委托机制的封装,提供了 Inspector 中可视化配置的能力

add invoking list at interface
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

[System.Serializable]
public class EventVector3 : UnityEvent<Vector3> {} // define event

public class MouseManager : MonoBehaviour
{
    private RaycastHit hitInfo;
    
    // declare event
    public EventVector3 onMouseClick;
    
    private void Update()
    {
        SetCursorTexture();
        MouseControl();
    }

    // 进行射线检测,返回值 hitInfo(预先声明)
    void SetCursorTexture()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray, out hitInfo))
        {
            // ...
            // 切换鼠标贴图
        }
    }

    // if 鼠标点击为地面,触发事件
    void MouseControl()
    {
        if (Input.GetMouseButtonDown(0) && hitInfo.collider != null)
        {
            if (hitInfo.collider.gameObject.CompareTag("Ground"))
            {
                onMouseClick.Invoke(hitInfo.point); // invoke envent
            }
        }
    }
}

Last updated