zoukankan      html  css  js  c++  java
  • 高级C#信使(译)

    高级C#信使


    作者:Ilya Suzdalnitski

    译自:http://wiki.unity3d.com/index.php/Advanced_CSharp_Messenger

    描述


    这是C#的一个高级版本的消息系统。当加载了一个新的场景(level)之后,它会自动清空事件表。这将防止程序员意外的调用已销毁的方法,从而有助于防止出现很多MissingReferenceExceptions。这个消息系统是基于Rod Hyde的CSharpMessenger和Magnus Wolffelt的CSharpMessenger Extended而研发的。

    前言


    我们的项目一旦引入了消息系统(CSharpMessenger Extended),我们就开始面对非常奇怪的BUGs。每次广播一个消息,U3D就会抛出MissingReferenceExceptions这个异常。提示会说消息处理器声明所在类已经被销毁。这个问题无处不在,并且没有一个合理的解释。不过,把消息处理器代码放到try-cache块中可以解决这个问题。我们知道,在代码中有大量的try-cache块不是一个好的解决方案。我们花费了一些时间,终于找到了问题的所在。

    MissingReferenceException的原因和解决方案

    是这样的,当加载一个新场景(level)(或者重新加载当前场景)时,MissingReferenceException的BUG就会出现。例如:我们有一个消息"start game",声明如下:

    public class MainMenu : MonoBehaviour {    
        void Start ()
        {
            Messenger.AddListener("start game", StartGame);
        }
     
        void StartGame()
        {
            Debug.Log("StartGame called in" + gameObject);  //This is the line that would throw an exception
        }
     
        void StartGameButtonPressed()
        {
            Messenger.Broadcast("start game");
        }
    }

    乍一看,代码完全没有问题。但是在重新加载了这个场景之后,U3D将会抛出一个异常,提示说MainMenu对象已经被销毁。但是没有代码会销毁MainMenu脚本对象。

    实际发生的是:

    1. 我们在信使中添加了一个"start game"消息监听器。
    2. StartGameButtonPressed方法被调用,广播了"start game"消息。
    3. 我们使用Application.LoadLevel重新加载了这个场景(level)。
    4. 重复第1步。
    5. 重复第2步。

    在相应的步骤,信使的事件表里是这个样子的:

    • 在第1步:{ "start game", mainMenu1- > StartGame(); }
    • 在第4步:{ "start game", mainMenu1- > StartGame(); } { "start game", mainMenu2- > StartGame(); }

    所以在第4步我们有两个"start game"消息处理器——第1个是已经销毁的MainMenu对象(在重新加载场景时被销毁),第2个是当前有效的MainMenu对象。结果是这样的,当我们在重新加载场景后广播"start game"消息时,信使把两个消息处理器(已经销毁的和当前有效的)都调用了。这里就是MissingReferenceException出现的源头。 那么解决方法显而易见——在卸载场景之后清空eventTable。在程序员方面不用做任何事来清空这个表,这一过程将自动完成。

    信使


    我们很高兴向你展示一个高级版本的C#消息系统。

    用法


    事件监听器

    void OnPropCollected( PropType propType ) {
        if (propType == PropType.Life)
            livesAmount++;
    }

    注册事件监听器

    void Start() {
        Messenger.AddListener< Prop >( "prop collected", OnPropCollected );
    }

    注销事件监听器

        Messenger.RemoveListener< Prop > ( "prop collected", OnPropCollected );

    广播事件

    public void OnTriggerEnter(Collider _collider) 
    {
        Messenger.Broadcast< PropType > ( "prop collected", _collider.gameObject.GetComponent<Prop>().propType );
    }

    清空信使


    当加载一个新的场景(level)时,信使会自动清空它的eventTable。这将确保信使的eventTable被清空,并且将使我们免于意外的MissingReferenceExceptions。如果你想手动清空管理器的eventTable,你可以调用Messenger.Cleanup()。

    永久信使

    如果你想要某个消息幸免于Cleanup,使用Messenger.MarkAsPermanent(string)来标记它既可。它可能用于这样的场合:某个类响应不同场景(level)的消息广播。

    杂项


    打印所有消息

    为了调试的目的,你可以把信使的shouldLogAllMessages设置成true。这样,在调用信使的任何方法时都会打印消息。

    从其他信使过渡

    为了快速把旧的消息系统CSharpMessenger转变成高级的消息系统,请执行下面的步骤:

    1. 在MonoDevelop中打开“在文件中查找/替换”对话框。
    2. 在查找域输入:Messenger<([^<>]+)>.([A-Za-z0-9_]+)
    3. 在替换域输入:Messenger.$2<$1>
    4. 选择域:所有解决方案
    5. 勾选“正则搜索”复选框
    6. 按下“替换”按钮

    代码


    想要信使顺利运作需要两个文件:Callback.cs和Messenger.cs。

    Callback.cs

    public delegate void Callback();
    public delegate void Callback<T>(T arg1);
    public delegate void Callback<T, U>(T arg1, U arg2);
    public delegate void Callback<T, U, V>(T arg1, U arg2, V arg3);

    Messenger.cs

    /*
     * 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. Messenger.AddListener<GameObject>("prop collected", PropCollected);
            Messenger.Broadcast<GameObject>("prop collected", prop);
         2. Messenger.AddListener<float>("speed changed", SpeedChanged);
            Messenger.Broadcast<float>("speed changed", 0.5f);
     * 
     * 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)
     * 
     */
     
    //#define LOG_ALL_MESSAGES
    //#define LOG_ADD_LISTENER
    //#define LOG_BROADCAST_MESSAGE
    #define REQUIRE_LISTENER
     
    using System;
    using System.Collections.Generic;
    using UnityEngine;
     
    static internal class Messenger {
        #region Internal variables
     
        //Disable the unused variable warning
    #pragma warning disable 0414
        //Ensures that the MessengerHelper will be created automatically upon start of the game.
        static private MessengerHelper messengerHelper = ( new GameObject("MessengerHelper") ).AddComponent< MessengerHelper >();
    #pragma warning restore 0414
     
        static public Dictionary<string, Delegate> eventTable = new Dictionary<string, Delegate>();
     
        //Message handlers that should never be removed, regardless of calling Cleanup
        static public List< string > permanentMessages = new List< string > ();
        #endregion
        #region Helper methods
        //Marks a certain message as permanent.
        static public void MarkAsPermanent(string eventType) {
    #if LOG_ALL_MESSAGES
            Debug.Log("Messenger MarkAsPermanent 	"" + eventType + """);
    #endif
     
            permanentMessages.Add( eventType );
        }
     
     
        static public void Cleanup()
        {
    #if LOG_ALL_MESSAGES
            Debug.Log("MESSENGER Cleanup. Make sure that none of necessary listeners are removed.");
    #endif
     
            List< string > messagesToRemove = new List<string>();
     
            foreach (KeyValuePair<string, Delegate> pair in eventTable) {
                bool wasFound = false;
     
                foreach (string message in permanentMessages) {
                    if (pair.Key == message) {
                        wasFound = true;
                        break;
                    }
                }
     
                if (!wasFound)
                    messagesToRemove.Add( pair.Key );
            }
     
            foreach (string message in messagesToRemove) {
                eventTable.Remove( message );
            }
        }
     
        static public void PrintEventTable()
        {
            Debug.Log("			=== MESSENGER PrintEventTable ===");
     
            foreach (KeyValuePair<string, Delegate> pair in eventTable) {
                Debug.Log("			" + pair.Key + "		" + pair.Value);
            }
     
            Debug.Log("
    ");
        }
        #endregion
     
        #region Message logging and exception throwing
        static public void OnListenerAdding(string eventType, Delegate listenerBeingAdded) {
    #if LOG_ALL_MESSAGES || LOG_ADD_LISTENER
            Debug.Log("MESSENGER OnListenerAdding 	"" + eventType + ""	{" + listenerBeingAdded.Target + " -> " + listenerBeingAdded.Method + "}");
    #endif
     
            if (!eventTable.ContainsKey(eventType)) {
                eventTable.Add(eventType, null );
            }
     
            Delegate d = eventTable[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 public void OnListenerRemoving(string eventType, Delegate listenerBeingRemoved) {
    #if LOG_ALL_MESSAGES
            Debug.Log("MESSENGER OnListenerRemoving 	"" + eventType + ""	{" + listenerBeingRemoved.Target + " -> " + listenerBeingRemoved.Method + "}");
    #endif
     
            if (eventTable.ContainsKey(eventType)) {
                Delegate d = eventTable[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(string eventType) {
            if (eventTable[eventType] == null) {
                eventTable.Remove(eventType);
            }
        }
     
        static public void OnBroadcasting(string eventType) {
    #if REQUIRE_LISTENER
            if (!eventTable.ContainsKey(eventType)) {
                throw new BroadcastException(string.Format("Broadcasting message "{0}" but no listener found. Try marking the message with Messenger.MarkAsPermanent.", eventType));
            }
    #endif
        }
     
        static public BroadcastException CreateBroadcastSignatureException(string 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) {
            }
        }
        #endregion
     
        #region AddListener
        //No parameters
        static public void AddListener(string eventType, Callback handler) {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Callback)eventTable[eventType] + handler;
        }
     
        //Single parameter
        static public void AddListener<T>(string eventType, Callback<T> handler) {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Callback<T>)eventTable[eventType] + handler;
        }
     
        //Two parameters
        static public void AddListener<T, U>(string eventType, Callback<T, U> handler) {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Callback<T, U>)eventTable[eventType] + handler;
        }
     
        //Three parameters
        static public void AddListener<T, U, V>(string eventType, Callback<T, U, V> handler) {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Callback<T, U, V>)eventTable[eventType] + handler;
        }
        #endregion
     
        #region RemoveListener
        //No parameters
        static public void RemoveListener(string eventType, Callback handler) {
            OnListenerRemoving(eventType, handler);   
            eventTable[eventType] = (Callback)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }
     
        //Single parameter
        static public void RemoveListener<T>(string eventType, Callback<T> handler) {
            OnListenerRemoving(eventType, handler);
            eventTable[eventType] = (Callback<T>)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }
     
        //Two parameters
        static public void RemoveListener<T, U>(string eventType, Callback<T, U> handler) {
            OnListenerRemoving(eventType, handler);
            eventTable[eventType] = (Callback<T, U>)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }
     
        //Three parameters
        static public void RemoveListener<T, U, V>(string eventType, Callback<T, U, V> handler) {
            OnListenerRemoving(eventType, handler);
            eventTable[eventType] = (Callback<T, U, V>)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }
        #endregion
     
        #region Broadcast
        //No parameters
        static public void Broadcast(string eventType) {
    #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
            Debug.Log("MESSENGER	" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "			Invoking 	"" + eventType + """);
    #endif
            OnBroadcasting(eventType);
     
            Delegate d;
            if (eventTable.TryGetValue(eventType, out d)) {
                Callback callback = d as Callback;
     
                if (callback != null) {
                    callback();
                } else {
                    throw CreateBroadcastSignatureException(eventType);
                }
            }
        }
     
        //Single parameter
        static public void Broadcast<T>(string eventType, T arg1) {
    #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
            Debug.Log("MESSENGER	" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "			Invoking 	"" + eventType + """);
    #endif
            OnBroadcasting(eventType);
     
            Delegate d;
            if (eventTable.TryGetValue(eventType, out d)) {
                Callback<T> callback = d as Callback<T>;
     
                if (callback != null) {
                    callback(arg1);
                } else {
                    throw CreateBroadcastSignatureException(eventType);
                }
            }
        }
     
        //Two parameters
        static public void Broadcast<T, U>(string eventType, T arg1, U arg2) {
    #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
            Debug.Log("MESSENGER	" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "			Invoking 	"" + eventType + """);
    #endif
            OnBroadcasting(eventType);
     
            Delegate d;
            if (eventTable.TryGetValue(eventType, out d)) {
                Callback<T, U> callback = d as Callback<T, U>;
     
                if (callback != null) {
                    callback(arg1, arg2);
                } else {
                    throw CreateBroadcastSignatureException(eventType);
                }
            }
        }
     
        //Three parameters
        static public void Broadcast<T, U, V>(string eventType, T arg1, U arg2, V arg3) {
    #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
            Debug.Log("MESSENGER	" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "			Invoking 	"" + eventType + """);
    #endif
            OnBroadcasting(eventType);
     
            Delegate d;
            if (eventTable.TryGetValue(eventType, out d)) {
                Callback<T, U, V> callback = d as Callback<T, U, V>;
     
                if (callback != null) {
                    callback(arg1, arg2, arg3);
                } else {
                    throw CreateBroadcastSignatureException(eventType);
                }
            }
        }
        #endregion
    }
     
    //This manager will ensure that the messenger's eventTable will be cleaned up upon loading of a new level.
    public sealed class MessengerHelper : MonoBehaviour {
        void Awake ()
        {
            DontDestroyOnLoad(gameObject);    
        }
     
        //Clean up eventTable every time a new level loads.
        public void OnLevelWasLoaded(int unused) {
            Messenger.Cleanup();
        }
    }
  • 相关阅读:
    二叉排序树
    索引顺序表查找(分块查找)
    wpf中的窗口
    递归算法以及汉诺塔
    Net中资源存储的设置
    AutoResetEvent 和ManualResetEvent
    WPF:跨应用程序会话保持和还原应用程序范围的属性
    Base64编码及其作用
    WPF中的应用程序级别Application
    Ajax经典学习教材,IBM官方Ajax教材
  • 原文地址:https://www.cnblogs.com/alwu007/p/3094644.html
Copyright © 2011-2022 走看看