C#字典的奇怪行为

本文关键字:字典 | 更新日期: 2023-09-27 18:23:59

我有以下简化程序

using System;
using System.Collections.Generic;
using System.Text;
class ItemClass {
    public int Id = 0;
    public int[] Childs = new int[] { };
    public int Count = 0;
}
class Class1 {
    Dictionary<int, ItemClass> _Items = new Dictionary<int, ItemClass> { };
    private void AddRecursive(int ItemID, int count, ref Dictionary<int, ItemClass> ItemList) {
        if (!_Items.ContainsKey(ItemID)) {
            return;
        }
        ItemClass _item = _Items[ItemID];
        if (!ItemList.ContainsKey(ItemID)) {
            ItemList.Add(ItemID, _item);
        }
        ItemList[ItemID].Count += count;
        if (_item.Childs.Length > 0) {
            foreach (int tmpItem in _item.Childs) {
                AddRecursive(tmpItem, ItemList[ItemID].Count, ref ItemList);
            }
        }
    }
    public void Add(int item, int[] childs) {
        if (!_Items.ContainsKey(item)) {
            _Items.Add(item, new ItemClass() { Id = item, Childs = childs, Count = 0 });
        }
    }
    public Dictionary<int, ItemClass> MakeList(int ItemID) {
        if (!_Items.ContainsKey(ItemID)) {
            return new Dictionary<int, ItemClass> { };
        }
        Dictionary<int, ItemClass> ItemList = new Dictionary<int, ItemClass> { };
        AddRecursive(ItemID, 1, ref ItemList);
        return ItemList;
    }
}

class Program {
    static void Main(string[] args) {
        Class1 class1 = new Class1();
        class1.Add(1111, new int[] { });
        class1.Add(2222, new int[] { 1111 });
        class1.Add(3333, new int[] { 1111, 2222 });
        Dictionary<int, ItemClass> items1 = class1.MakeList(3333);
        foreach (ItemClass item in items1.Values) {
            Console.WriteLine(item.Id + "  " + item.Count);
        }
        Console.WriteLine("");
        Dictionary<int, ItemClass> items2 = class1.MakeList(3333);
        foreach (ItemClass item in items2.Values) {
            Console.WriteLine(item.Id + "  " + item.Count);
        }
        Console.ReadKey();
    }
}

它有一个简单的任务,即计数项目并显示项目列表及其计数。当我第一次调用MakeList函数时,结果是意料之中的。

预期:

3333  1
1111  2
2222  1
3333  1
1111  2
2222  1

实际

3333  1
1111  2
2222  1
3333  2
1111  7
2222  3

当我重新声明变量ItemList时,我希望在第二次调用该函数时看到相同的结果,但这就像上次调用的结果被缓存并重复使用一样。

为什么会发生这种情况,为什么会有这种行为?有什么办法可以避免吗?

C#字典的奇怪行为

实际上它并没有什么奇怪的。当您调用AddRecursive方法时,您刚刚修改了ItemClass对象中Count属性的值。

假设您真的想得到您想要的,您只需要深度克隆ItemClass对象,或者只需创建新实例并复制属性的原始值。

ItemClass _item = new ItemClass() { Childs = _Items[ItemID].Childs, Count = _Items[ItemID].Count, Id = _Items[ItemID].Id };

而不是这个

Itemclass _item = _Items[ItemId]

您正在声明ItemList,但使用来自内部_Items:ItemClass _item = _Items[ItemID];的相同对象,并对相同对象递增计数。这就是为什么部分:)。避免的部分可能是创建新项目。

您的问题是在运行AddRecursive时更改了_Items字典中ItemClass实例的Count属性。

去掉可变的Count属性(它实际上不应该在ItemClass中),并将代码简化为

class ItemClass 
{
    public int Id = 0;
    public int[] Childs = new int[] { };
}
class Class1 
{
    Dictionary<int, ItemClass> _Items = new Dictionary<int, ItemClass>();
    private void AddRecursive(int itemId, Dictionary<int, int> itemList) 
    {
        if (!_Items.ContainsKey(itemId)) 
            return;
        if (!itemList.ContainsKey(itemId)) 
            itemList.Add(itemId, 1);
        else
            itemList[itemId] += 1;
        var item = _Items[itemId];
        if (item.Childs.Length > 0) 
            foreach (int tmpItem in item.Childs)
                AddRecursive(tmpItem, itemList);
    }
    public void Add(int item, int[] childs) 
    {
        if (!_Items.ContainsKey(item)) 
            _Items.Add(item, new ItemClass() { Id = item, Childs = childs });
    }
    public Dictionary<int, int> MakeList(int itemId)
    {
        if (!_Items.ContainsKey(itemId)) 
            return new Dictionary<int, int>();
        var itemList = new Dictionary<int, int>();
        AddRecursive(itemId, itemList);
        return itemList;
    }
}