我是靠谱客的博主 文静微笑,最近开发中收集的这篇文章主要介绍C# 委托与事件注意事项及坑,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

delegate 委托,是C#关键字,功能是函数指针,动态调用=>函数的实例的引用
Func   有返回类型的泛型委托
Action 无返回类型的泛型委托
Delegate 是一个类.委托类

Delegate的作用

        当你不知道委托的类型时,可以用Delegate变量来保存,表示

class A
{
    public delegate void Callback();
    private Callback cb;
    void Start()
    {
        Delegate D;
        Action A1 = () => { };
        Action<int, int, long> A2 = (x, y, z) => { };
        Func<int> F1 = delegate () { return 666; };
        D = A1;
        D = A2;
        D = F1;
        D = cb;
    }
}

Delegate 和 delegate 互转 

   Delegate是一个类.
    delegate 是C#关键字
    public delegate void Callback(); //这个我们自定义的delegate
    //Delegate可以转 Action,也就是说Delegate可以转成delegate的函数声明.
    Delegate d;
    Action<T, U, V, X> Action = d as Action<T, U, V, X>;
    
Delegate =>转 delegate
    public delegate void Callback();
    private Callback cb;
    Delegate d = cb;
    cb = d as Callback;
    
delegate => 转  Delegate
    public delegate void Callback();
    private Callback cb;
    Delegate d = (Callback)cb;


                 

泛型委托 Func 与 Action 定义

Func<Result>  有返回类型的泛型委托;Result等于返回的类型,T代表arg参数的类型
Action<T>       无返回类型的泛型委托,T代表arg参数的类型

​
public delegate TResult Func<TResult>();
public delegate void Action();
public delegate void Callback(); //这个我们自定义的delegate.

Func<TResult>           public delegate TResult Func<TResult>(); 
Func<T,TResult>         public delegate TResult Func<T, TResult>(T arg);
Func<T1,T2,TResult>
Func<T1,T2,T3,TResult>
Func<T1,T2,T3,...T16,TResult>
Action              public delegate void Action();
Action<T>           public delegate void Action<T>(T obj);
Action<T1,T2>       public delegate void Action<T1, T2>(T1 arg1, T2 arg2);
Action<T1,T2,T3>    public delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3);
Action<T1,T2,T3,...T16>


​

匿名方法

​无返回值
没有参数的写法
delegate{} //匿名方式创建的委托
()=>{} //lambda方式创建的委托, lambda表达式简化了匿名方法的书写,去掉了delegate关键字并加入 '=>' 运算符.
1个参数的写法
x=>{log(x);} //lambda方式创建的委托
delegate(string x) {log(x);} //匿名方式创建的委托
(string str) => {log(x);}; //lambda方式创建的委托 , lambda表达式简化了匿名方法的书写,去掉了    
delegate关键字并加入 '=>' 运算符.



有返回值的
Func的匿名方法
Func<int> F1 = delegate () { return 666; };
Func<string, string> change = delegate(string s) { return s.ToUpper(); };
​

事件一般用法

public class Date : MonoBehaviour  //事件改变的地方
{
    Action<int> hpChange;
    public int _hp = 99;

    public void AddHpChangeEvent(Action<int> hpEvent) //参数为函数的变量
    {
        hpChange += hpEvent;
    }

    public int Hp
    {
        get{return _hp;}
        set
        {
            _hp = value;
            if (hpChange != null)
                hpChange(_hp);
        }
    }
}
public class UIHp : MonoBehaviour  //需要监听事件的地方
{
    void Start()
    {
        Date date = GetComponent<Date>();

        date.AddHpChangeEvent(hp=>{
            Slider.value = hp / 100f;
        });
    }

}

Action用法2

public class Date   //事件改变的地方
{
    public Action<int> hpChange;
    public int _hp = 99;

    public int Hp
    {
        get { return _hp; }
        set
        {
            _hp = value;
            if (hpChange != null)
                hpChange(_hp);
        }
    }
}
public class UIHp : MonoBehaviour  //需要监听事件的地方
{

    public void HpChange(int currt_hp)
    {
        Slider slider = transform.GetComponent<Slider>();
        slider.value = currt_hp / 100f;
    }

}

public class Player : MonoBehaviour  //需要监听事件的地方
{
    public void Init()
    {
        Date date = new Date();
        var uiHp = transform.GetComponent<UIHp>();
        int hp = 100;
        date.hpChange += (hp2) =>
         {
             uiHp.HpChange(hp);
         };
    }
}

升级到事件中心,所有事件集中定义  ,进阶1

​
​public class EventCenter
{

    //==============================================================================================================================
    //普通攻击事件,攻击方,被攻击方,伤害数值
    static Action<object, object, int> AttackEvent;//普通攻击事件,攻击方,被攻击方,伤害数值  
    //添加监听事件   //放在OnEnable()函数里,EventCenter.AddAttackEvent(AttackEventReceive);
    public static void Add_Attack_Event(Action<object, object, int> Event)
    {
        AttackEvent += Event;
    }
    //移除监听事件 //一般放在Disable()函数里
    public static void Remove_Attack_Event(Action<object, object, int> Event)
    {
        AttackEvent -= Event;
    }
    //事件发起
    public static void Sent_Attack_Event(object player, object enemey, int arg)
    {
        if (AttackEvent != null)
        {
            AttackEvent(player, enemey, arg);
        }
    }
    //攻击事件接收
    void Receive_Attack_Event(object player, object enemey, int arg)
    {

    }
    //==============================================================================================================================
}


public class Enemy : MonoBehaviour
{
    void OnEnable()
    {
        EventCenter.Add_Attack_Event(Receive_Attack_Event);
    }

    void OnDisable()
    {
        EventCenter.Remove_Attack_Event(Receive_Attack_Event);
    }

    //攻击事件接收
    void Receive_Attack_Event(object player, object enemey, int arg)
    {

    }
}

​

委托字典  事件的封装 ,进阶2

Dictionary<EGameEvent, Delegate> dict;

Dictionary<枚举, 委托>  字典。

枚举就是我们定义的事件类型,事件可能有成百上千个都可以自己定义。

优点 通过定义一个枚举就可以添加一个事件,而不用写杂七杂八的。

缺点 匿名函数没办法取消监听,普通函数能取消。

public enum EGameEvent 
{
    Begin,
    #region 常用事件
    MainPlayer_EnterGame,           // 主角进入游戏
    Notice_SystemTopFinish,
    Notice_SystemMidFinish,
    Notice_OperTip,
    #endregion
    #region Packet Message
    Msg_Server_Return,              // 服务器返回消息
    ResourceFileReady,
    DBFileLoadReady,
    #endregion
    #region Object属性更新
    Attr_Name,
    Attr_ModelId,
    Attr_Hp,
    Attr_Exp,
    Attr_GameMoney,
    Attr_RealMoney,
    Attr_Power,
    Attr_ArmourItemID,
    Attr_WeaponItemID,
    Attr_Dynamic,                   // 所有保存在 DynamicAttrMgr 内的属性更新, 任何一个动态属性更新了, 都会发送这个消息	
    #endregion
    #region UI公用消息
    UI_ShowMainUI,                  // 显示主要UI
    UI_HideAllUI,                   // 隐藏主要UI
    UI_Show,                        // 显示界面
    UI_Hide,                        // 关闭界面
    UI_Toggle,                      // 显示/关闭界面
    UI_ReqOriginal,                 // 请求原始UI资源
    UI_MuliShow,                    //多对象UI显示
    UI_MuliHide,                    //多对象关闭
    UI_OnOriginal,                  // 原始UI资源准备好
    UI_OnCreated,                   // 界面创建完毕
    UI_OnShow,                      // UI成功显示
    UI_OnHide,                      // UI成功隐藏
    #endregion
    #region 登陆界面
    Login_CheckAccount,             // 验证账号密码
    #endregion
    #region 选择服务器界面
    ServerList_AddServerInfo,       // 增加显示服务器信息
    ServerList_SelectServer,        // 选择服务器
    #endregion
    #region 角色创建界面
    CharOper_CreatePlayer,          // 界面事件	- 创建角色
    CharOper_SelectClassSex,        // 界面事件 - 选择职业和性别
    CharOper_RandomName,            // 界面事件 - 随机一个名字
    #endregion
}
/*
 * Advanced C# messenger by Ilya Suzdalnitski. V1.0
 * 
 * Based on Rod Hyde's "CSharpMessenger" and Magnus Wolffelt's "CSharpMessenger Extended".
 * 
 * Features:
 	* Prevents a MissingReferenceException because of a reference to a destroyed message handler.
 	* Option to log all messages
 	* Extensive error detection, preventing silent bugs
 * 
 * Usage examples:
      1. EventCenterDict.Broadcast<int>(EGameEvent.playerUpGrade,date.lv);//发起事件
         EventCenterDict.AddListener<int>(EGameEvent.playerUpGrade, x =>{Debug.Log("恭喜你升到"+x+"级");});//注册监听事件
 * 
 * Messenger cleans up its evenTable automatically upon loading of a new level.
 * 
 * Don't forget that the messages that should survive the cleanup, should be marked with Messenger.MarkAsPermanent(string)
 * 
 */
using System;
using System.Collections.Generic;
using UnityEngine;
public static class EventCenterDict
{
    public static bool isDebugLog = true;
    static public Dictionary<EGameEvent, Delegate> mEventTable = new Dictionary<EGameEvent, Delegate>();
    //Message handlers that should never be removed, regardless of calling Cleanup
    static public List<EGameEvent> mPermanentMessages = new List<EGameEvent>();
    //Marks a certain message as permanent.
    static public void MarkAsPermanent(EGameEvent eventType)
    {
        Debug.Log("Messenger MarkAsPermanent t"" + eventType + """);
        mPermanentMessages.Add(eventType);
    }
    static public void Cleanup()
    {
		if(isDebugLog) Debug.Log("MESSENGER Cleanup. Make sure that none of necessary listeners are removed.");
        List<EGameEvent> messagesToRemove = new List<EGameEvent>();
        foreach (KeyValuePair<EGameEvent, Delegate> pair in mEventTable)
        {
            bool wasFound = false;
            foreach (EGameEvent message in mPermanentMessages)
            {
                if (pair.Key == message)
                {
                    wasFound = true;
                    break;
                }
            }
            if (!wasFound)
                messagesToRemove.Add(pair.Key);
        }
        foreach (EGameEvent message in messagesToRemove)
        {
            mEventTable.Remove(message);
        }
    }
    static public void PrEGameEventEventTable()
    {
        Debug.Log("ttt=== MESSENGER PrEGameEventEventTable ===");
        foreach (KeyValuePair<EGameEvent, Delegate> pair in mEventTable)
        {
            Debug.Log("ttt" + pair.Key + "tt" + pair.Value);
        }
        Debug.Log("n");
    }
    static  void OnListenerAdding(EGameEvent eventType, Delegate listenerBeingAdded)
    {
        if (isDebugLog)
            Debug.Log("MESSENGER OnListenerAdding t"" + eventType + ""t{" + listenerBeingAdded.Target + " -> " + listenerBeingAdded.Method + "}");
        if (!mEventTable.ContainsKey(eventType))
        {
            mEventTable.Add(eventType, null);
        }
        Delegate d = mEventTable[eventType];
        if (d != null && d.GetType() != listenerBeingAdded.GetType())
        {
            throw new ListenerException(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));
        }
    }
    static  void OnListenerRemoving(EGameEvent eventType, Delegate listenerBeingRemoved)
    {
        if (isDebugLog)
            Debug.Log("MESSENGER OnListenerRemoving t"" + eventType + ""t{" + listenerBeingRemoved.Target + " -> " + listenerBeingRemoved.Method + "}");
        if (mEventTable.ContainsKey(eventType))
        {
            Delegate d = mEventTable[eventType];
            if (d == null)
            {
                throw new ListenerException(string.Format("Attempting to remove listener with for event type "{0}" but current listener is null.", eventType));
            }
            else if (d.GetType() != listenerBeingRemoved.GetType())
            {
                throw new ListenerException(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));
            }
        }
        else
        {
            throw new ListenerException(string.Format("Attempting to remove listener for type "{0}" but Messenger doesn't know about this event type.", eventType));
        }
    }
    static public void OnListenerRemoved(EGameEvent eventType)
    {
        if (mEventTable[eventType] == null)
        {
            mEventTable.Remove(eventType);
        }
    }
    static  void OnBroadcasting(EGameEvent eventType)
    {
#if REQUIRE_LISTENER
        if (!mEventTable.ContainsKey(eventType)) {
        }
#endif
    }
    static public BroadcastException CreateBroadcastSignatureException(EGameEvent eventType)
    {
        return new BroadcastException(string.Format("Broadcasting message "{0}" but listeners have a different signature than the broadcaster.", eventType));
    }
    public class BroadcastException : Exception
    {
        public BroadcastException(string msg)
            : base(msg)
        {
        }
    }
    public class ListenerException : Exception
    {
        public ListenerException(string msg): base(msg)
        {
        }
    }
    //No parameters
    static public void AddListener(EGameEvent eventType,Action handler)
    {
        OnListenerAdding(eventType, handler);
        mEventTable[eventType] = (Action)mEventTable[eventType] + handler;
    }
    //Single parameter
    static public void AddListener<T>(EGameEvent eventType, Action<T> handler)
    {
        OnListenerAdding(eventType, handler);
        mEventTable[eventType] = (Action<T>)mEventTable[eventType] + handler;
    }
    //Two parameters
    static public void AddListener<T, U>(EGameEvent eventType, Action<T, U> handler)
    {
        OnListenerAdding(eventType, handler);
        mEventTable[eventType] = (Action<T, U>)mEventTable[eventType] + handler;
    }
    //Three parameters
    static public void AddListener<T, U, V>(EGameEvent eventType, Action<T, U, V> handler)
    {
        OnListenerAdding(eventType, handler);
        mEventTable[eventType] = (Action<T, U, V>)mEventTable[eventType] + handler;
    }
    //Four parameters
    static public void AddListener<T, U, V, X>(EGameEvent eventType, Action<T, U, V, X> handler)
    {
        OnListenerAdding(eventType, handler);
        mEventTable[eventType] = (Action<T, U, V, X>)mEventTable[eventType] + handler;
    }
    //No parameters
    static public void RemoveListener(EGameEvent eventType, Action handler)
    {
        OnListenerRemoving(eventType, handler);
        mEventTable[eventType] = (Action)mEventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }
    //Single parameter
    static public void RemoveListener<T>(EGameEvent eventType, Action<T> handler)
    {
        OnListenerRemoving(eventType, handler);
        mEventTable[eventType] = (Action<T>)mEventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }
    //Two parameters
    static public void RemoveListener<T, U>(EGameEvent eventType, Action<T, U> handler)
    {
        OnListenerRemoving(eventType, handler);
        mEventTable[eventType] = (Action<T, U>)mEventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }
    //Three parameters
    static public void RemoveListener<T, U, V>(EGameEvent eventType, Action<T, U, V> handler)
    {
        OnListenerRemoving(eventType, handler);
        mEventTable[eventType] = (Action<T, U, V>)mEventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }
    //Four parameters
    static public void RemoveListener<T, U, V, X>(EGameEvent eventType, Action<T, U, V, X> handler)
    {
        OnListenerRemoving(eventType, handler);
        mEventTable[eventType] = (Action<T, U, V, X>)mEventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }
    //No parameters
    static public void Broadcast(EGameEvent eventType)
    {
        if (isDebugLog)
            Debug.Log("MESSENGERt" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "tttInvoking t"" + eventType + """);
        OnBroadcasting(eventType);
        Delegate d;
        if (mEventTable.TryGetValue(eventType, out d))
        {
            Action Action = d as Action;
            if (Action != null)
            {
                Action();
            }
            else
            {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }
    //Single parameter
    static public void Broadcast<T>(EGameEvent eventType, T arg1)
    {
        if (isDebugLog)
            Debug.Log("MESSENGERt" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "tttInvoking t"" + eventType + """);
        OnBroadcasting(eventType);
        Delegate d;
        if (mEventTable.TryGetValue(eventType, out d))
        {
            Action<T> Action = d as Action<T>;
            if (Action != null)
            {
                Action(arg1);
            }
            else
            {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }
    //Two parameters
    static public void Broadcast<T, U>(EGameEvent eventType, T arg1, U arg2)
    {
        if (isDebugLog)
            Debug.Log("MESSENGERt" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "tttInvoking t"" + eventType + """);
        OnBroadcasting(eventType);
        Delegate d;
        if (mEventTable.TryGetValue(eventType, out d))
        {
            Action<T, U> Action = d as Action<T, U>;
            if (Action != null)
            {
                Action(arg1, arg2);
            }
            else
            {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }
    //Three parameters
    static public void Broadcast<T, U, V>(EGameEvent eventType, T arg1, U arg2, V arg3)
    {
        if (isDebugLog)
            Debug.Log("MESSENGERt" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "tttInvoking t"" + eventType + """);
        OnBroadcasting(eventType);
        Delegate d;
        if (mEventTable.TryGetValue(eventType, out d))
        {
            Action<T, U, V> Action = d as Action<T, U, V>;
            if (Action != null)
            {
                Action(arg1, arg2, arg3);
            }
            else
            {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }
    //Four parameters
    static public void Broadcast<T, U, V, X>(EGameEvent eventType, T arg1, U arg2, V arg3, X arg4)
    {
        if (isDebugLog)
            Debug.Log("MESSENGERt" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "tttInvoking t"" + eventType + """);
        OnBroadcasting(eventType);
        Delegate d;
        if (mEventTable.TryGetValue(eventType, out d))
        {
            Action<T, U, V, X> Action = d as Action<T, U, V, X>;
            if (Action != null)
            {
                Action(arg1, arg2, arg3, arg4);
            }
            else
            {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }
}

事件的封装 ,进阶3

优点 可以取消对匿名函数的监听

缺点 涉及到一个装箱与拆箱的过程,频繁发送事件,效率会没有直接调用高。

/*
 2019.09.05
 by zack

//注册监听事件   EventSystem.RegisterEvent(事件名称, (参数))
 EventSystem.RegisterEvent(111, (a, b) =>
        {
            Debug.Log("111");
        });
 
        EventSystem.RegisterEvent("aa", (a, b) =>
        {
            Debug.Log("aa");
        });
 
        EventSystem.RegisterEvent(1.2, (a, b) =>
        {
            Debug.Log("1.2");
        });
 
// 发启事件
EventSystem.SendEvent(111);
EventSystem.SendEvent("aa");
EventSystem.SendEvent(1.2f);
 
// 取消监听111事件
EventSystem.UnRegisterEvent(111);

缺点
事件涉及到一个装箱与拆箱的过程,频繁发送事件,效率会没有直接调用高。


例子2:
// 注册事件
EventSystem.RegisterEvent("update_textbox", (a, new_text) =>
{
    textBox1.AppendText(new_text[0] as string);

});

// 发启事件
EventSystem.SendEvent("update_textbox", "update_textbox1_text1");

*/


namespace Wb.EventSystem
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;


    public class EventSystem : Singleton<EventSystem>
    {
        private readonly Dictionary<IConvertible, ListenerWrap> mAllListenerMap = new Dictionary<IConvertible, ListenerWrap>(50);

        private EventSystem() { }

        public void Register<T>(T key, OnEvent fun) where T : IConvertible
        {
            if (!mAllListenerMap.ContainsKey(key))
            {
                ListenerWrap wrap = new ListenerWrap();
                mAllListenerMap.Add(key, wrap);
                wrap.Add(fun);
                return;
            }

            mAllListenerMap[key].Add(fun);
        }


        public void UnRegister<T>(T key, OnEvent fun) where T : IConvertible
        {
            // Debug.Log("UnRegister " + key);
            if (mAllListenerMap.ContainsKey(key)) mAllListenerMap[key].Remove(fun);
        }

        public void UnRegister<T>(T key) where T : IConvertible
        {
            if (mAllListenerMap.ContainsKey(key))
            {
                mAllListenerMap[key].RemoveAll();
                mAllListenerMap[key] = null;
                mAllListenerMap.Remove(key);
            }
        }

        public void Send<T>(T key, params object[] param) where T : IConvertible
        {
            if (mAllListenerMap.ContainsKey(key)) mAllListenerMap[key].Fire(key, param);
        }

        public void UnRegisterAll()
        {
            foreach (var listener in mAllListenerMap) UnRegister(listener.Key);
            mAllListenerMap.Clear();
        }


        public static void SendEvent<T>(T key, params object[] param) where T : IConvertible
        {
            Instance.Send(key, param);
        }
        /// <summary>
        /// 注册监听事件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">事件名称</param>
        /// <param name="fun">参数数组</param>
        public static void RegisterEvent<T>(T key, OnEvent fun) where T : IConvertible
        {
            Instance.Register(key, fun);
        }

        public static void UnRegisterEvent<T>(T key, OnEvent fun) where T : IConvertible
        {
            Instance.UnRegister(key, fun);
        }

        public static void UnRegisterEvent<T>(T key) where T : IConvertible
        {
            Instance.UnRegister(key);
        }

        public static void UnRegisterAllEvent()
        {
            Instance.UnRegisterAll();
        }
    }

    public delegate void OnEvent(IConvertible key, params object[] param);
    public partial class ListenerWrap
    {
        private List<OnEvent> mEventList = new List<OnEvent>();

        public void Fire(IConvertible key, params object[] param)
        {
            foreach (var func in mEventList) func(key, param);
        }

        public void Add(OnEvent listener)
        {
            if (mEventList.Contains(listener)) return;
            mEventList.Add(listener);
        }

        public void Remove(OnEvent listener)
        {
            if (mEventList.Contains(listener)) mEventList.Remove(listener);
        }

        public void RemoveAll()
        {
            mEventList.Clear();
        }
    }

    public class EventIDisposable : IDisposable
    {
        IConvertible key;
        OnEvent fun;
        public EventIDisposable(IConvertible key, OnEvent fun)
        {
            this.key = key;
            this.fun = fun;
        }

        public void Dispose()
        {
            EventSystem.UnRegisterEvent(key, fun);
        }
    }
 
   
 
    public static class SingletonCreator
    {
        public static T CreateSingleton<T>() where T : class, ISingleton
        {
            // 获取私有构造函数
            var ctors = typeof(T).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);

            // 获取无参构造函数
            var ctor = Array.Find(ctors, c => c.GetParameters().Length == 0);

            if (ctor == null)
            {
                throw new Exception("Non-Public Constructor() not found! in " + typeof(T));
            }

            // 通过构造函数,常见实例
            var retInstance = ctor.Invoke(null) as T;
            retInstance.OnSingletonInit();

            return retInstance;
        }
    }

    public abstract class Singleton<T> : ISingleton where T : Singleton<T>
    {
        protected static T mInstance;

        static object mLock = new object();

        protected Singleton()
        {
        }

        public static T Instance
        {
            get
            {
                lock (mLock)
                {
                    if (mInstance == null)
                    {
                        mInstance = SingletonCreator.CreateSingleton<T>();
                    }
                }

                return mInstance;
            }
        }

        public virtual void Dispose()
        {
            mInstance = null;
        }

        public virtual void OnSingletonInit()
        {
        }
    }

    public interface ISingleton
    {
        void OnSingletonInit();
    }
}

事件中心模块


制作成就系统
任务记录
达成某种条件等等
减少代码量,减少复杂性,降低程序的耦合度
核心思路:设置事件中心将事件加进去事件发生时通知监听者
原理:基于字典和委托
KEY–事件的名字(玩家死亡,怪物死亡)
value–监听这个事件的对应的委托函数们
因为委托可以通过+=和-=所以可以有很多委托
需要事件的监听方法:两个参数,事件名和其监听者的委托函数
事件触发函数:得到哪个函数被触发了 参数是name 找到委托函数并且触发

public class EventCenter : BaseManager<EventCenter>
{
    private Dictionary<string,UnityAction> eventDic =new Dictionary<string, UnityAction>();
    public void AddEventListener(string name,UnityAction action)
    {
        if(eventDic.ContainsKey(name))
        {
            eventDic[name]+=action;
        }
        else
        {
            eventDic.Add(name,action);
        }

    }
    public void EventTrigger(string name)
    {
        if(eventDic.ContainsKey(name))
        {
            eventDic[name]();
        }

    }
}

事件中心会由开始到程序结束都存在
将UnityAction中传入Object参数增强事件系统的通用性

public class EventCenter : BaseManager<EventCenter>
{
    private Dictionary<string,UnityAction<object>> eventDic =new Dictionary<string, UnityAction<object>>();
    public void AddEventListener(string name,UnityAction<object> action)
    {
        if(eventDic.ContainsKey(name))
        {
            eventDic[name]+=action;
        }
        else
        {
            eventDic.Add(name,action);
        }
    }
    public void EventTrigger(string name,object info)
    {
        if(eventDic.ContainsKey(name))
        {
            eventDic[name](info);
        }
    }
    public void RemoveEventListener(string name,UnityAction<object> action)
    {
        if(eventDic.ContainsKey(name))
            eventDic[name]-=action;
    }
    public void Clear()
    {
        eventDic.Clear();

    }
}

对事件中心的优化以及避免装箱拆箱

方法是使用泛型类型,字典中存储泛型的基类接口来实现减少传入参数为object带来的装箱拆箱所造成的消耗,并且使用重载的方法处理不带有类型参数的情况

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public interface IEventInfo
{

}
public class EventInfo<T> : IEventInfo
{
    public UnityAction<T> actions;
    public EventInfo( UnityAction<T> action)
    {
        actions += action;
    }
}
public class EventInfo : IEventInfo
{
    public UnityAction actions;

    public EventInfo(UnityAction action)
    {
        actions += action;
    }
}
/// <summary>
/// 事件中心 单例模式对象
/// 1.Dictionary
/// 2.委托
/// 3.观察者设计模式
/// 4.泛型
/// </summary>
public class EventCenter : BaseManager<EventCenter>
{
    //key —— 事件的名字(比如:怪物死亡,玩家死亡,通关 等等)
    //value —— 对应的是 监听这个事件 对应的委托函数们
    private Dictionary<string, IEventInfo> eventDic = new Dictionary<string, IEventInfo>();

    /// <summary>
    /// 添加事件监听
    /// </summary>
    /// <param name="name">事件的名字</param>
    /// <param name="action">准备用来处理事件 的委托函数</param>
    public void AddEventListener<T>(string name, UnityAction<T> action)
    {
        //有没有对应的事件监听
        //有的情况
        if( eventDic.ContainsKey(name) )
        {
            (eventDic[name] as EventInfo<T>).actions += action;
        }
        //没有的情况
        else
        {
            eventDic.Add(name, new EventInfo<T>( action ));
        }
    }

    /// <summary>
    /// 监听不需要参数传递的事件
    /// </summary>
    /// <param name="name"></param>
    /// <param name="action"></param>
    public void AddEventListener(string name, UnityAction action)
    {
        //有没有对应的事件监听
        //有的情况
        if (eventDic.ContainsKey(name))
        {
            (eventDic[name] as EventInfo).actions += action;
        }
        //没有的情况
        else
        {
            eventDic.Add(name, new EventInfo(action));
        }
    }


    /// <summary>
    /// 移除对应的事件监听
    /// </summary>
    /// <param name="name">事件的名字</param>
    /// <param name="action">对应之前添加的委托函数</param>
    public void RemoveEventListener<T>(string name, UnityAction<T> action)
    {
        if (eventDic.ContainsKey(name))
            (eventDic[name] as EventInfo<T>).actions -= action;
    }

    /// <summary>
    /// 移除不需要参数的事件
    /// </summary>
    /// <param name="name"></param>
    /// <param name="action"></param>
    public void RemoveEventListener(string name, UnityAction action)
    {
        if (eventDic.ContainsKey(name))
            (eventDic[name] as EventInfo).actions -= action;
    }

    /// <summary>
    /// 事件触发
    /// </summary>
    /// <param name="name">哪一个名字的事件触发了</param>
    public void EventTrigger<T>(string name, T info)
    {
        //有没有对应的事件监听
        //有的情况
        if (eventDic.ContainsKey(name))
        {
            //eventDic[name]();
            if((eventDic[name] as EventInfo<T>).actions != null)
                (eventDic[name] as EventInfo<T>).actions.Invoke(info);
            //eventDic[name].Invoke(info);
        }
    }
    /// <summary>
    /// 事件触发(不需要参数的)
    /// </summary>
    /// <param name="name"></param>
    public void EventTrigger(string name)
    {
        //有没有对应的事件监听
        //有的情况
        if (eventDic.ContainsKey(name))
        {
            //eventDic[name]();
            if ((eventDic[name] as EventInfo).actions != null)
                (eventDic[name] as EventInfo).actions.Invoke();
            //eventDic[name].Invoke(info);
        }
    }
    /// <summary>
    /// 清空事件中心
    /// 主要用在 场景切换时
    /// </summary>
    public void Clear()
    {
        eventDic.Clear();
    }
}

注意事项及坑

1.添加监听时,回调方法并不会立即
执行

for (int i = 0; i < list.Count - 1; i++)
{
    //第一段
    list[i].GetComponent<Button>().onClick.AddListener(
        () =>
        {
            OnClick(list[i + 1]);
        }
        );
 
    //第二段
    Button button = list[i + 1].GetComponent<Button>();
    list[i].GetComponent<Button>().onClick.AddListener(
        () =>
        {
            OnClick(button.gameObject);
        }
        );
 
}
 
两次碰到问题,
都是下意识的写出了第一种写法,在点击触发回调时,引用到的对象
都是数组的最后一个对象。原因是添加监听时,回调方法并不会立即
执行,所以对应的i的值也不会立即传入,而i的所有值在内存中都是
同一个地址,所以当点击触发时,i已经变成了list.count-2,所
以引用的对象都是数组的最后一个对象了。
 
第二种写法则是传入了对应的对象,每个对象都有自己的地址,以不
会有上述问题。

2.Task的异步回调一定要放在整个Task.Run()执行之后

    private void Awake()
    {
        Debug.LogError("Awake");
        TaskEvent(() =>
        {
            Debug.LogError(gameObject.GetComponent<Transform>().forward);
        });
    }
 
    private async void TaskEvent(Action callBack)
    {
        await Task.Run(() =>
        {
            Debug.LogError("Task Start");
            callBack.Invoke();
        });
        Debug.LogError("Task End");
    }
	
很简单,使用Task异步的方式完成一些事之后,执行一个Action的回调继续干另一件事。

很常规的操作和应用场景。

但是按上述这么写完之后执行,会报下面的错误

 直译就是Unity告诉我们,回调中干的事情,只能放在Unity主线程中。

我们需要修改回调中的内容么?这个思路显然是错误的,回调对于这个任务来说是抽象的,任务不应该关心,也不知道回调的内容;回调想干的事儿也不应该被Task限制。

先继续分析这个问题。

当我们把回调中的组件获取及成员变量调用删掉后,只单纯的打一个字符串LOG时,这个问题就不存在了。

这说明跨线程的Action行为本身是可以走通的,那么问题就变成了,跨线程的方法及属性调用上。

private async void TaskEvent(Action callBack)
{
    await Task.Run(() =>
    {
        Debug.LogError("Task Start");
    });
    callBack.Invoke();
    Debug.LogError("Task End");
}
其实最后的解决办法很简单,就是把回调从Task.Run()里挪了出来放在后面。

Task的异步在执行过程中,会把后续逻辑“停住”,待整个Task.Run()跑完之后,继续往下走。

这样出了子线程之后再调用主线程的内容,就没有问题了。

这个问题的产生主要是因为自身对Task相关内容不熟悉导致的。不过这个问题解决之后收货颇丰,总结一下:

1、Unity不支持跨线程的属性及方法调用,但是跨线程的Action本身是没有问题的;

2、Task的异步回调一定要放在整个Task.Run()执行之后,而不是放在Task.Run()的执行逻辑过程的最后。

最后

以上就是文静微笑为你收集整理的C# 委托与事件注意事项及坑的全部内容,希望文章能够帮你解决C# 委托与事件注意事项及坑所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(35)

评论列表共有 0 条评论

立即
投稿
返回
顶部