高级C#信使(译) - Unity维基百科

高级C#信使



作者:Ilya Suzdalnitski

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

  1. 描述
  2. 前言
    1. MissingReferenceException的原因和解决方案
  3. 信使
    1. 用法
      1. 事件监听器
      2. 注册事件监听器
      3. 注销事件监听器
      4. 广播事件
    2. 清空信使
      1. 永久信使
    3. 杂项
      1. 打印所有消息
      2. 从其他信使过渡
    4. 代码
      1. Callback.cs
      2. Messenger.cs

描述



这是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 \t\"" + 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("\t\t\t=== MESSENGER PrintEventTable ===");

        foreach (KeyValuePair<string, Delegate> pair in eventTable) {
            Debug.Log("\t\t\t" + pair.Key + "\t\t" + pair.Value);
        }

        Debug.Log("\n");
    }
    #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 \t\"" + eventType + "\"\t{" + 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 \t\"" + eventType + "\"\t{" + 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\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + 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\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + 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\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + 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\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + 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();
    }
}
时间: 2024-10-08 04:11:06

高级C#信使(译) - Unity维基百科的相关文章

业务流程建模标记法(维基百科)

原文 https://zh.wikipedia.org/wiki/%E4%B8%9A%E5%8A%A1%E6%B5%81%E7%A8%8B%E5%BB%BA%E6%A8%A1%E6%A0%87%E8%AE%B0%E6%B3%95 业务流程建模标记法(BPMN, Business Process Modeling Notation)[译注1]是工作流中特定业务流程的图形化表示法.它由业务流程管理倡议组织(BPMI, Business Process Management Initiative)开发

维基百科遭黑客大规模攻击,近1000万欧洲和中东用户受影响

近日维基百科持久中断的原因是黑客大规模恶意攻击,该中断导致欧洲和中东大部分地区的数百万用户无法访问维基百科,这归咎于“恶意黑客行为者”. 网络攻击于上周五在维基百科上进行,并持续到周六,在英国,波兰,荷兰,法国,德国和意大利维基百科网站已下线.据TechCrunch(美国科技类博客)称,Facebook网站上的一些用户报告表明,一些中东国家也受到了影响,近1000万用户损失惨重. 近日,维基百科背后的慈善组织维基媒体基金会的一位发言人说:”该网站由于身份不明的<黑客组织>的恶意攻击而被关闭.如

历史上的今天 API (数据来自维基百科)

历史上的今天 API (数据来自维基百科) API地址: http://history.lifetime.photo:81/api/history 参考/引用地址: 维基百科:https://zh.wikipedia.org/zh-cn/%E5%8E%86%E5%8F%B2%E4%B8%8A%E7%9A%84%E4%BB%8A%E5%A4%A9 IPIP5:http://www.ipip5.com/today/api.php?type=json 说明: 由于之前写的一个自动脚本用到了"历史上的今

[python学习] 简单爬取维基百科程序语言消息盒

文章主要讲述如何通过Python爬取维基百科的消息盒(Infobox),主要是通过正则表达式和urllib实现:后面的文章可能会讲述通过BeautifulSoup实现爬取网页知识.由于这方面的文章还是较少,希望提供一些思想和方法对大家有所帮助.如果有错误或不足之处,欢迎之处:如果你只想知道该篇文章最终代码,建议直接阅读第5部分及运行截图. 一. 维基百科和Infobox 你可能会疑惑Infobox究竟是个什么东西呢?下面简单介绍. 维基百科作为目前规模最大和增长最快的开放式的在线百科系统,其典型

使用JWPL (Java Wikipedia Library)操作维基百科数据

使用JWPL (Java Wikipedia Library)操作维基百科数据 1. JWPL介绍 JWPL(Java Wikipedia Library)是一个开源的访问wikipeida数据的Java API包,提供了快速访问维基百科中包含的消息,如重定向.类别.文章和链接结构的结构性访问接口.它提供的DataMachine 工具类可快速解析wiki格式文件,生成mysql的数据txt文件,可通过mysqlimport 导入到本地数据库中. JWPL介绍官网:https://dkpro.gi

维基百科的存在是一个奇迹

这两年,"情怀"这两个字的内涵在国人心中可能已经发生了变异:这多半是归因于一些商业结构在营销过程中对于这个曾经美好词汇的挟持.但是如果把"情怀"和"维基百科"这两个词汇联系在一起,也许人们依然会心悦诚服地敛容以对,收起看客式的调侃和讽刺,重新回到自己内心对于真正意义上的情怀的呼唤和敬畏. 维基百科,这个世界上最大的不以盈利为目的的网站,已经伴随着整个互联网的发展走过了整整15个年头.这期间有多少与互联网相关的公司或雄起成为巨头,或衰落黯然倒闭,沧

URL 编码转换 (中文-&gt;English)wiki ,Wikipedia,维基百科,PPT,PDF

wiki :URL 编码转换 (中文->English)wiki ,Wikipedia,维基百科, 1. 原始URL: https://zh.wikipedia.org/wiki/維基 2. 复制后得到的编码后的URL: https://zh.wikipedia.org/wiki/%E7%B6%AD%E5%9F%BA 1 使用微软提供的源代码,一切正常显示: PPT,pdf 1 <iframe src='https://view.officeapps.live.com/op/embed.asp

维基百科一年烧四千多万美元 太会花钱?才不是呢!

http://www.nowamagic.net/librarys/news/detail/1434近期你如果查阅维基百科,可能会看到页面顶部有一条小提示,是的,一年一度的维基百科社会捐款又开始了.作为一家非营利组织,维基的帐目是公开的,对于 12/13 财年(2012 年 7 月至 2013 年 6 月),维基在其页面上公开出来的运营预算为 4,200 万美金.这个数字在 Quora 上受到了一位提问者的强烈质疑,他认为维基由志愿者维护,页面形式也绝非花哨到需要大价钱设计维护,那么大家捐的钱都

爬取维基百科人物介绍,并使用pymysql存储到数据库

代码如下: from urllib.request import urlopen from bs4 import BeautifulSoup import re import datetime import random import pymysql.cursors # Connect to the database connection = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='数据库密码', d