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处理需要在编辑器中配置的交互

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

相关推荐
程序猿追2 小时前
kotlin-retry 接入鸿蒙后,怎样确认它真的会停止重试
开发语言·kotlin·harmonyos
我是人✓2 小时前
Java文件上传三方式对比:从传统到SpringBoot
java·开发语言
智能直播2 小时前
网络RTMP拉流不卡顿、声音不同步,一文讲透缓冲区、时间戳
开发语言·ffmpeg·php·音视频·视频编解码
墨云安全3 小时前
内网资产无法暴露到公网,怎么做安全检测?2026 年内网渗透测试的落地路径
开发语言·安全·网络安全·渗透测试·php
摇滚侠3 小时前
《计算机是如何工作的:人人都能懂的计算机软硬件工作原理》第 9 章 机器码与汇编语言 阅读笔记 9
java·开发语言·笔记
Cc.Y3 小时前
Java零基础入门:深入Java核心:文件IO与序列化全解
java·开发语言·python
jimy15 小时前
c++的 Type Traits (一)
开发语言·c++
Web极客码5 小时前
Python Ollama 本地 Agent 入门与订单查询实践
开发语言·jvm·python
kimnoic6 小时前
PHP 8.2的DNF类型怎么用才规范
开发语言·php
_Evan_Yao6 小时前
前后端联调怎么排查问题?从 Network 到后端日志,一步步定位错误
开发语言·后端·团队开发·需求分析