Unity实现支持泛型的事件管理以减少使用object作为参数带来的频繁装拆箱
如果不用C#自身的event关键字而是要自己实现一个可统一管理游戏中各种消息事件通知管理的系统模块EventManger时,通常都是把事件delegate的参数定义为object类型以适应所有的数据类型,然而这样做的后果就是在使用过程中存在很频繁的装拆箱操作。
实际是有办法实现支持泛型的事件管理的,关键点在于所有形式的delegate方法都是可以保存在类型为Delegate的变量上的,保存和调用时将Delegate强转为目标delegate就行了。简单示例如下:
1 public delegate void Act (); 2 public delegate void Act<T, U>(T t, U u); 3 4 5 Dictionary<int, Delegate> eventTable = new Dictionary<int, Delegate>(); 6 7 public void AddListener<T, U>(int eventType, Act<T, U> listenerBeingAdded) 8 { 9 if (!eventTable.ContainsKey(eventType)) 10 { 11 eventTable.Add(eventType, null); 12 } 13 14 Delegate d = eventTable[eventType]; 15 if (d != null && d.GetType() != listenerBeingAdded.GetType()) 16 { 17 Debug.LogError(string.Format("Attempting to add listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being added has type {2}", eventType, d.GetType().Name, listenerBeingAdded.GetType().Name)); 18 } 19 else 20 { 21 eventTable[eventType] = (Act<T, U>)eventTable[eventType] + listenerBeingAdded; 22 } 23 } 24 25 public void RemoveListen<T, U>(int eventType, Act<T, U> listenerBeingRemoved) 26 { 27 if (eventTable.ContainsKey(eventType)) 28 { 29 Delegate d = eventTable[eventType]; 30 31 if (d == null) 32 { 33 Debug.LogError(string.Format("Attempting to remove listener with for event type \"{0}\" but current listener is null.", eventType)); 34 } 35 else if (d.GetType() != listenerBeingRemoved.GetType()) 36 { 37 Debug.LogError(string.Format("Attempting to remove listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being removed has type {2}", eventType, d.GetType().Name, listenerBeingRemoved.GetType().Name)); 38 } 39 else 40 { 41 eventTable[eventType] = (Act<T, U>)eventTable[eventType] - listenerBeingRemoved; 42 if (eventTable[eventType] == null) 43 { 44 eventTable.Remove(eventType); 45 } 46 } 47 } 48 else 49 { 50 Debug.LogError(string.Format("Attempting to remove listener for type \"{0}\" but Messenger doesn't know about this event type.", eventType)); 51 } 52 } 53 54 public void Dispatch<T, U>(int eventType, T param1, U param2) 55 { 56 Delegate d; 57 if (eventTable.TryGetValue(eventType, out d)) 58 { 59 ((Act<T, U>)d)(param1, param2); 60 } 61 } 62 63 [ContextMenu("Test")] 64 void Test () 65 { 66 AddListener<int, string>(1, MyCallback); 67 Dispatch(1, 22, "333"); 68 RemoveListen<int, string>(1, MyCallback); 69 } 70 71 private void MyCallback (int n, string s) 72 { 73 Debug.Log(string.Format("param1 {0}, parma2 {1}", n, s)); 74 }
预定义多个不同参数个数的delegate,再分别重载几个Add、Remove、Dispatch支持不同类型delegate的方法就可以实现整套支持不同参数类型不同参数个数的消息管理功能了。
以上方法可以完全避免参数传递之间的拆装箱,但是稍微有点麻烦之处在于需要重载很多Add、Remove、Dispatch函数。有个简单点的作法是直接将Delegate作为这三个函数的参数而不是具体的delegate,但调用时直接传入具名函数是不能自动转换为Delegate的,需要对每个delegate作个简单的封装,具体如下:
1 public delegate void Act (); 2 public delegate void Act<T, U>(T t, U u); 3 4 5 Dictionary<int, Delegate> eventTable = new Dictionary<int, Delegate>(); 6 7 public void AddListener (int eventType, Delegate listenerBeingAdded) 8 { 9 if (!eventTable.ContainsKey(eventType)) 10 { 11 eventTable.Add(eventType, null); 12 } 13 Delegate d = eventTable[eventType]; 14 if (d != null && d.GetType() != listenerBeingAdded.GetType()) 15 { 16 Debug.LogError(string.Format("Attempting to add listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being added has type {2}", eventType, d.GetType(), listenerBeingAdded.GetType())); 17 } 18 else 19 { 20 eventTable[eventType] = Delegate.Combine(eventTable[eventType], listenerBeingAdded); 21 } 22 } 23 24 public void RemoveListen (int eventType, Delegate listenerBeingRemoved) 25 { 26 if (eventTable.ContainsKey(eventType)) 27 { 28 Delegate d = eventTable[eventType]; 29 if (d == null) 30 { 31 Debug.LogError(string.Format("Attempting to remove listener with for event type \"{0}\" but current listener is null.", eventType)); 32 } 33 else if (d.GetType() != listenerBeingRemoved.GetType()) 34 { 35 Debug.LogError(string.Format("Attempting to remove listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being removed has type {2}", eventType, d.GetType().Name, listenerBeingRemoved.GetType().Name)); 36 } 37 else 38 { 39 eventTable[eventType] = Delegate.Remove(eventTable[eventType], listenerBeingRemoved); 40 if (eventTable[eventType] == null) 41 { 42 eventTable.Remove(eventType); 43 } 44 } 45 } 46 else 47 { 48 Debug.LogError(string.Format("Attempting to remove listener for type \"{0}\" but Messenger doesn't know about this event type.", eventType)); 49 } 50 } 51 52 public void Dispatch (int eventType, params object[] n) 53 { 54 Delegate d; 55 if (eventTable.TryGetValue(eventType, out d)) 56 { 57 d.DynamicInvoke(n); 58 } 59 } 60 61 [ContextMenu("Test")] 62 void Test () 63 { 64 AddListener(1, ToDelegate<int, string>(MyCallback)); 65 AddListener(1, ToDelegate(MyCallback1)); 66 //或者 67 //Delegate dele = new Action<int, string>(MyCallback); 68 //AddListener(1, dele); 69 Dispatch(1, 22, "333"); 70 RemoveListen(1, ToDelegate<int, string>(MyCallback)); 71 //RemoveListen(1, dele); 72 } 73 74 private Act<T, U> ToDelegate<T, U>(Act<T, U> act) { return act; } 75 private Act ToDelegate(Act act) { return act; } 76 77 private void MyCallback (int n, string s) 78 { 79 Debug.Log(string.Format("param1 {0}, parma2 {1}", n, s)); 80 } 81 82 private void MyCallback1 () 83 { 84 Debug.Log("no param"); 85 }
此方法就需要对每个delegate写个简单的Getter函数(不同参数个数的原型都需要分别包装),或者new一个Action类似的Delegate(不同参数个数直接指明即可),让调用者自己给出具体的函数类别。相比方法1可以省去不少Add、Remove、Dispatch重载代码,但调用者调用时变得稍麻烦一些,同时由于Dispatch只能接受object[]参数导致了拆装箱,故还是推荐方法1。