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会显示可配置的界面:
-
点击"+"添加事件条目
-
拖拽GameObject到Object字段
-
选择组件和方法
-
可以设置参数值
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 性能优化建议
-
避免频繁事件触发:特别是在Update方法中
-
使用对象池:对于频繁创建/销毁的事件参数
-
缓存委托实例:避免每次调用都创建新的委托
-
谨慎使用匿名方法:可能导致难以调试的内存泄漏
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);
}
}
}
总结
-
委托:是基础,用于方法引用和回调
-
事件:基于委托,提供更好的封装,适合组件间通信
-
UnityEvent:Unity专用,提供编辑器集成,适合非程序员配置
在Unity开发中:
-
使用C#事件处理纯代码逻辑
-
使用UnityEvent处理需要在编辑器中配置的交互
-
两者可以结合使用,发挥各自优势