为什么从字典中获取动作<>的副本?

时间:2019-12-24 05:34:09

标签: c# dictionary unity3d

我有以下字典:

private Dictionary<string, Action<GameObject, int, int, int, int>> eventDictionary;

我希望保留一个动作字典(基本上是代表),以便每当我希望订阅事件名称时,我都可以为我的所有订阅者订阅同一事件。

这是我监听特定事件字符串的功能:

public static void StartListening(string eventName, Action<GameObject, int, int, int, int> listener)
{
    Action<GameObject, int, int, int, int> thisEvent = null;
    if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
    {
        thisEvent += listener;
        // the code reaches this 
    }
    else
    {
        thisEvent += listener;
        instance.eventDictionary.Add(eventName, thisEvent);
    }
}

现在我尝试

EventManager.StartListening("Move", Moved);
EventManager.StartListening("Move", Moved);
EventManager.StartListening("Move", Moved);
EventManager.StartListening("Move", Moved);

// Log here to get how many subscribers there are to the event "Move"
// Result: only 1 listener

只有第一个添加的侦听器将实际注册,其余的添加后将“消失”。我调试了该错误将近4个小时,然后才进行测试,以查看thisEvent += listener;行是否有故障。当我添加删除并随后将其添加回字典时,

public static void StartListening(string eventName, Action<GameObject, int, int, int, int> listener)
{
    Action<GameObject, int, int, int, int> thisEvent = null;
    if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
    {
        thisEvent += listener;
        instance.eventDictionary.Remove(eventName);
        instance.eventDictionary.Add(eventName, thisEvent);
    }
    else
    {
        thisEvent += listener;
        instance.eventDictionary.Add(eventName, thisEvent);
    }
}

代表们终于按预期加入了。

EventManager.StartListening("Move", Moved);
EventManager.StartListening("Move", Moved);
EventManager.StartListening("Move", Moved);
EventManager.StartListening("Move", Moved);

// Log here to get how many subscribers there are to the event "Move"
// Result: 4 listeners

这是我遇到过的最荒谬的错误之一。难道不是字典中不是字符串,整数等所有值都应该通过引用而不是值来检索吗?为什么我在这里获得Action的副本,而不是参考?

PS: GameObject是Unity类。 这是我的Moved函数:

public void Moved(GameObject invoker, int x, int z, int tx, int tz)
{
    //Some code here
}

1 个答案:

答案 0 :(得分:3)

  

这是我遇到过的最荒谬的错误之一。难道不是字典中不是字符串,整数等所有值都应该通过引用而不是值来检索吗?为什么我在这里获得Action的副本,而不是参考?

调用TryGetValue(eventName, out thisEvent)时,将提供Dictionary写入值的引用。您没有得到Dictionary内部内容的引用(我的意思是,您没有得到Dictionary结构的深层指针,这意味着分配给它不会修改Dictionary )。


也许委托可能是一个引用类型,这可能引起一些混淆。 是的,您得到的是对同一委托对象的引用,而不是新的引用。但是,委托添加会返回一个新的多播委托。

顺便说一句,string是引用类型。

请参阅:


  

我只是想知道为什么要这样设计。几乎所有语言都有字典,可在获取对象时为您提供参考。当我不确定是否为null时,应该如何获取对象?

内存安全。这种设计将不可能有孤立的指针或类似的指针。

尽管如此,如果没有替代的更新API,例如ConcurrentDictionary<TKey,TValue>.AddOrUpdate,也可以视为一种疏忽。 Dictionary并不意味着线程安全,即使效率较低,您也可以完成同一件事……这将解释为什么不认为它是必要的。

如果您正在寻找替代项(ConcurrentDictionary除外),则可以使用可变引用类型。例如List<Delegate>。在TryGetValue(eventName, out thisEvent)中,您仍将提供编写List<Delegate>的参考,但是,您可以对其进行突变。但是,您仍然需要处理它,如果不存在该密钥,您将需要进行初始化。 您不会有空值。

if (instance.eventDictionary.TryGetValue(eventName, out var thisEvent))
{
    thisEvent.Add(listener);
}
else
{
    instance.eventDictionary.Add
    (
        eventName,
        new List<Action<GameObject, int, int, int, int> {listener}
    );
}

  

这也让我感到疑惑,它是深克隆还是浅克隆?

如果要存储值类型,则将获得该值类型的副本。

如果要存储引用类型,则将获得引用的副本。我不会称其为克隆。它只是对同一对象的另一个引用。