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

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

相关推荐
小大宇6 分钟前
Python 集成 Conda
开发语言·python·conda
还是奇怪22 分钟前
Simon Willison 用 DSPy 优化 Datasette Agent 提示词:提示工程正在变成可测试的软件工程
java·开发语言·软件工程
小大宇1 小时前
python flask框架 SSE流式返回、跨域、报错
开发语言·python·flask
柒星栈2 小时前
PHP 源码怎么加密防破解?三套方案实战指南
开发语言·php·android studio
勉灬之2 小时前
Next.js + Prisma 跨平台部署踩坑记
开发语言·javascript·ecmascript
这是个栗子2 小时前
前端开发中的常用工具函数(九)
开发语言·javascript·ecmascript·at
蓝创工坊Blue Foundry3 小时前
图片文字提取到 Excel:批量任务如何先定义要交付的字段
运维·服务器·开发语言·数据库·自动化·ocr·excel
会飞的小新3 小时前
C 标准库之 <fenv.h> 详解与深度解析
c语言·开发语言·microsoft
雪的季节3 小时前
dir (obj) 与dir()
开发语言
暗暗别做白日梦3 小时前
Pulsar 消息同步机制
c#·linq