C# 委托/事件/UnityEvent 详解

1. 委托 (Delegate)

1.1 基本概念

委托是C#中的一种类型,它允许将方法作为参数传递,类似于C/C++中的函数指针,但类型安全。

1.2 委托声明与使用

基本语法:

csharp

复制代码
// 1. 声明委托类型
delegate void MyDelegate(string message);
delegate int CalculateDelegate(int a, int b);

// 2. 使用委托
public class DelegateExample
{
    // 定义与委托匹配的方法
    static void DisplayMessage(string msg)
    {
        Console.WriteLine($"Message: {msg}");
    }
    
    static int Add(int a, int b) => a + b;
    static int Multiply(int a, int b) => a * b;

    static void Main()
    {
        // 创建委托实例
        MyDelegate messageDelegate = new MyDelegate(DisplayMessage);
        // 调用委托
        messageDelegate("Hello Delegate!");
        
        // 多播委托示例
        CalculateDelegate calcDelegate = Add;
        calcDelegate += Multiply; // 添加另一个方法
        
        // 调用多播委托(会依次调用所有方法,但只返回最后一个结果)
        int result = calcDelegate(3, 4); // 返回Multiply的结果:12
    }
}
内置委托类型:

csharp

复制代码
// Action委托(无返回值)
Action<string> action1 = (msg) => Console.WriteLine(msg);
Action<int, int> action2 = (x, y) => Console.WriteLine(x + y);

// Func委托(有返回值)
Func<int, int, int> func1 = (a, b) => a + b;
Func<string, int> func2 = (s) => s.Length;

// Predicate委托(返回bool)
Predicate<int> isEven = (num) => num % 2 == 0;

2. 事件 (Event)

2.1 事件概念

事件是基于委托的特殊类型,提供了发布-订阅模式,增强封装性(外部只能订阅/取消订阅,不能触发)。

2.2 标准事件模式

csharp

复制代码
public class EventPublisher
{
    // 1. 定义事件委托(标准模式使用EventHandler<T>)
    public event EventHandler<MyEventArgs> MyEvent;
    
    // 2. 定义事件参数类
    public class MyEventArgs : EventArgs
    {
        public string Message { get; set; }
        public DateTime Time { get; set; }
    }
    
    // 3. 触发事件的方法
    protected virtual void OnMyEvent(string message)
    {
        MyEvent?.Invoke(this, new MyEventArgs 
        { 
            Message = message, 
            Time = DateTime.Now 
        });
    }
    
    public void DoSomething()
    {
        // 业务逻辑...
        OnMyEvent("Something happened!");
    }
}

public class EventSubscriber
{
    public void Subscribe(EventPublisher publisher)
    {
        // 订阅事件
        publisher.MyEvent += HandleEvent;
    }
    
    private void HandleEvent(object sender, EventPublisher.MyEventArgs e)
    {
        Console.WriteLine($"Event received: {e.Message} at {e.Time}");
    }
}

// 使用示例
var publisher = new EventPublisher();
var subscriber = new EventSubscriber();
subscriber.Subscribe(publisher);
publisher.DoSomething();

2.3 自定义委托事件

csharp

复制代码
public class TemperatureMonitor
{
    // 自定义委托
    public delegate void TemperatureChangedHandler(float oldTemp, float newTemp);
    
    // 基于自定义委托的事件
    public event TemperatureChangedHandler TemperatureChanged;
    
    private float _temperature;
    public float Temperature
    {
        get => _temperature;
        set
        {
            if (_temperature != value)
            {
                float oldTemp = _temperature;
                _temperature = value;
                TemperatureChanged?.Invoke(oldTemp, _temperature);
            }
        }
    }
}

3. UnityEvent

3.1 UnityEvent特点

UnityEvent是UnityEngine.Events命名空间下的类,专为Unity编辑器集成设计,支持序列化和可视化配置。

3.2 基本用法

csharp

复制代码
using UnityEngine;
using UnityEngine.Events;

public class UnityEventExample : MonoBehaviour
{
    // 1. 声明UnityEvent(无参数)
    [SerializeField]
    private UnityEvent onStartEvent;
    
    // 2. 声明带参数的UnityEvent
    [System.Serializable]
    public class StringEvent : UnityEvent<string> { }
    
    [SerializeField]
    private StringEvent onMessageEvent;
    
    // 3. 声明多个参数的UnityEvent
    [System.Serializable]
    public class DamageEvent : UnityEvent<GameObject, float, Vector3> { }
    
    [SerializeField]
    private DamageEvent onDamageTaken;
    
    void Start()
    {
        // 代码方式添加监听器
        onStartEvent.AddListener(OnStartHandler);
        onMessageEvent.AddListener(OnMessageHandler);
        
        // 触发事件
        onStartEvent.Invoke();
        onMessageEvent.Invoke("Hello from code!");
    }
    
    void OnStartHandler()
    {
        Debug.Log("Start event triggered!");
    }
    
    void OnMessageHandler(string message)
    {
        Debug.Log($"Message: {message}");
    }
    
    // 触发带多个参数的事件
    public void TakeDamage(float amount, Vector3 hitPoint)
    {
        onDamageTaken.Invoke(gameObject, amount, hitPoint);
    }
    
    void OnDestroy()
    {
        // 清理监听器(重要!避免内存泄漏)
        onStartEvent.RemoveAllListeners();
        onMessageEvent.RemoveAllListeners();
    }
}

3.3 编辑器中的可视化配置

在Unity Inspector中,UnityEvent会显示可配置的界面:

  1. 点击"+"添加事件条目

  2. 拖拽GameObject到Object字段

  3. 选择组件和方法

  4. 可以设置参数值

3.4 实际应用示例

csharp

复制代码
// 开关系统示例
public class Switch : MonoBehaviour
{
    public UnityEvent onTurnOn;
    public UnityEvent onTurnOff;
    
    private bool _isOn = false;
    
    public void Toggle()
    {
        _isOn = !_isOn;
        
        if (_isOn)
            onTurnOn.Invoke();
        else
            onTurnOff.Invoke();
    }
}

// 使用Switch的门
public class Door : MonoBehaviour
{
    public void Open()
    {
        // 开门动画/逻辑
        transform.position += Vector3.up * 2;
        Debug.Log("Door opened!");
    }
    
    public void Close()
    {
        // 关门动画/逻辑
        transform.position -= Vector3.up * 2;
        Debug.Log("Door closed!");
    }
}

// 在Unity编辑器中:
// 1. 将Switch脚本添加到开关对象
// 2. 将Door脚本添加到门对象
// 3. 在Switch的Inspector中:
//    - 拖拽门对象到onTurnOn事件
//    - 选择Door.Open方法
//    - 拖拽门对象到onTurnOff事件
//    - 选择Door.Close方法

4. 三者的比较与选择

特性 Delegate Event UnityEvent
封装性 低(外部可调用) 高(外部只能订阅)
序列化 不支持 不支持 支持
编辑器集成 可视化配置
性能 较低(有额外开销)
多播支持
使用场景 纯代码回调 代码事件系统 Unity编辑器交互

5. 最佳实践

5.1 内存管理

csharp

复制代码
public class EventManager : MonoBehaviour
{
    private Dictionary<string, UnityEvent> eventDictionary;
    
    void Start()
    {
        eventDictionary = new Dictionary<string, UnityEvent>();
    }
    
    public void StartListening(string eventName, UnityAction listener)
    {
        if (!eventDictionary.ContainsKey(eventName))
            eventDictionary[eventName] = new UnityEvent();
            
        eventDictionary[eventName].AddListener(listener);
    }
    
    public void StopListening(string eventName, UnityAction listener)
    {
        if (eventDictionary.ContainsKey(eventName))
            eventDictionary[eventName].RemoveListener(listener);
    }
    
    public void TriggerEvent(string eventName)
    {
        if (eventDictionary.ContainsKey(eventName))
            eventDictionary[eventName].Invoke();
    }
    
    void OnDestroy()
    {
        // 清理所有事件
        foreach (var unityEvent in eventDictionary.Values)
        {
            unityEvent.RemoveAllListeners();
        }
        eventDictionary.Clear();
    }
}

5.2 性能优化建议

  1. 避免频繁事件触发:特别是在Update方法中

  2. 使用对象池:对于频繁创建/销毁的事件参数

  3. 缓存委托实例:避免每次调用都创建新的委托

  4. 谨慎使用匿名方法:可能导致难以调试的内存泄漏

5.3 设计模式应用

csharp

复制代码
// 观察者模式实现
public interface IObserver
{
    void OnNotify(string eventType, object data);
}

public class Subject : MonoBehaviour
{
    private List<IObserver> observers = new List<IObserver>();
    
    public void RegisterObserver(IObserver observer)
    {
        observers.Add(observer);
    }
    
    public void UnregisterObserver(IObserver observer)
    {
        observers.Remove(observer);
    }
    
    protected void NotifyObservers(string eventType, object data = null)
    {
        foreach (var observer in observers)
        {
            observer.OnNotify(eventType, data);
        }
    }
}

总结

  1. 委托:是基础,用于方法引用和回调

  2. 事件:基于委托,提供更好的封装,适合组件间通信

  3. UnityEvent:Unity专用,提供编辑器集成,适合非程序员配置

在Unity开发中:

  • 使用C#事件处理纯代码逻辑

  • 使用UnityEvent处理需要在编辑器中配置的交互

  • 两者可以结合使用,发挥各自优势

相关推荐
.select.6 分钟前
c++ auto
开发语言·c++·算法
2401_884563249 分钟前
C++中的访问者模式高级应用
开发语言·c++·算法
消失的旧时光-194320 分钟前
C++ 多态核心三件套:虚函数、纯虚函数、虚析构函数(面试 + 工程完全指南)
开发语言·c++·面试·虚函数·纯虚函数·虚析构函数
青春易逝丶38 分钟前
策略模式
java·开发语言·策略模式
freexyn40 分钟前
Matlab入门自学七十四:坐标系转换,直角坐标、极坐标和球坐标的转换
开发语言·算法·matlab
Dxy12393102161 小时前
js如何把字符串转数字
开发语言·前端·javascript
_饭团1 小时前
字符串函数全解析:12 种核心函数的使用与底层模拟实现
c语言·开发语言·学习·考研·面试·蓝桥杯
Larry_Yanan1 小时前
Qt网络开发之基于 QWebEngine 实现简易内嵌浏览器
linux·开发语言·网络·c++·笔记·qt·学习
2401_831824961 小时前
嵌入式C++驱动开发
开发语言·c++·算法
qingcyb2 小时前
重复 id 对应的多个对象
开发语言·python