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

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

相关推荐
w-w0w-w16 小时前
C++中vector的操作和简单实现
开发语言·数据结构·c++
Larry_Yanan16 小时前
Qt安卓开发(一)Qt6.10环境配置
android·开发语言·c++·qt·学习·ui
froginwe1116 小时前
ionic-range:深度解析与最佳实践
开发语言
Z1Jxxx16 小时前
整除整除整除
开发语言·c++·算法
superman超哥16 小时前
自定义迭代器的实现方法:深入Rust迭代器机制的核心
开发语言·后端·rust·编程语言·rust迭代器机制·自定义迭代器
2501_9216494916 小时前
主流金融数据API对比:如何获取精准、及时的IPO数据
开发语言·python·金融·restful
superman超哥16 小时前
IntoIterator Trait的转换机制:解锁Rust迭代器生态的关键
开发语言·后端·rust·编程语言·rust trait·rust迭代器·trait转换机制
墨月白16 小时前
【QT】 Lambda 表达式
开发语言·qt
没有天赋那就反复16 小时前
JAVA length
java·开发语言·算法
逑之16 小时前
C语言笔记13:数据在内存中的存储
c语言·开发语言·笔记