.NET中的嵌套字典集合

时间:2012-09-22 10:50:22

标签: c# .net collections dictionary

.NET Dictionary<TKey, TValue>对象允许分配键/值,如下所示:

Dictionary<string, string> dict = new Dictionary<string, string>();
dict["1"] = "foo";
dict["2"] = "bar";

但是我不能像这样使用词典:

Dictionary<string, string> dict = new Dictionary<string, string>();
dict["F1"]["F2"]["F3"] = "foo";
dict["2"]["X"] = "bar";

.NET中有一个集合允许我嵌套[],还是我必须创建自己的集合?

如果我必须创建自己的,我该怎么做?

修改

如果我可以拥有期望唯一键的实现,那么它也会很有用:

dict["F1"]["F2"]["F3"] = "foo";
dict["F1"]["F2"]["F3"] = "bar"; //result is "bar" because "foo" was overridden

以及可以多次使用密钥的实现

dict["F1"]["F2"]["F3"] = "foo";
dict["F1"]["F2"]["F3"] = "bar"; //result can be "foo" and "bar"

这可能吗?

编辑(根据Jon Skeet的提问):

我想使用这样的结构(作为一个非常粗略的例子):

json["data"]["request"]["name"] = "username";
json["data"]["request"]["pass"] = "password";

解析为

{ data: { request: { name: "username", pass: "password" } } }

同样会有XML等效的等价物。

5 个答案:

答案 0 :(得分:14)

根据我的测试,我已经提出了以下解决方案,根据我的测试,我已经提出了以下解决方案:

public class NestedDictionary<K, V> : Dictionary<K, NestedDictionary<K, V>>
    {
        public V Value { set; get; }

        public new NestedDictionary<K, V> this[K key]
        {
            set { base[key] = value; }

            get
            {
                if (!base.Keys.Contains<K>(key))
                {
                    base[key] = new NestedDictionary<K, V>();
                }
                return base[key];
            }
        }
    }

TEST:

NestedDictionary<string, string> dict = new NestedDictionary<string, string>();
dict["one"].Value = "Nest level 1";
dict["one"]["two"]["three"].Value = "Nest level 3";
dict["FieldA"]["FieldB"].Value = "Hello World";

Console.WriteLine(dict["one"].Value);
Console.WriteLine(dict["one"]["two"]["three"].Value);
Console.WriteLine(dict["FieldA"]["FieldB"].Value);

答案 1 :(得分:1)

为了使用vb6而创建的原始Dictionary COM对象将通过创建具有相应名称的Dictionary类型的新项来响应尝试访问不存在的项。此方法允许将某些内容存储到MyDict["Foo"]["Bar"],而无需先创建MyDict["Foo"]。这种方法的问题在于,当执行对"Foo"的写入时,人们希望将MyDict添加到MyDict["Foo"]["Bar"],如果有人试图例如,则不希望创建这样的项目。评估MyDict["Foo"]["Bar"].ValueOrDefault(someDefaultValue)

我使用过这样的集合,因为它们可以方便地对某些事物进行建模(概念上它们很像XML文档)。一种可行的方法是声明除了其他字典之外只包含其他字典的字典在语义上被视为非实体,可以在任何机会中删除。当隐式添加子集合时,在其添加的项目中设置一个标志,指示应该检查可能被删除的项目(或保留可能存在多少这样的项目的计数器)。然后以合理的频率,浏览字典并删除这些“死”项。

另一种方法是让字典中的索引器不返回实际项,而是返回“短暂索引器”类型,该类型保留对父对象的引用并具有内部方法GetNestedForReading,{{ 1}},SetNestedForReadingGetValue,链接回来。然后,SetValue语句将最终有效地执行Foo["Bar"]["Boz"] = "George";,而Foo.SetNestedForReading("Bar").SetValue("Boz", "George");将有效执行z = Foo["Bar"]["Boz"];。使用不存在的键调用Foo.GetNestedForReading("Bar").GetValue("Boz");方法将创建并返回一个新的嵌套项; SetNestedForReading方法将是一个不可变的“空”项。因此,使用此方法可以避免创建空项目。

虽然后一种方法比前者更复杂,但它还有另一个优点。可以让每个节点单独保存其集合,作为共享的深度不可变字典或非共享可变字典;如果GetNestedForReading调用看到嵌套对象是不可变的,它可以构造一个包含相同项的新的浅可变对象。如果将可变节点的克隆方法定义为使用所有子节点的(不可变)克隆创建新的不可变节点,并将不可变节点的克隆方法定义为返回自身,则克隆大多数不可变的树变得非常便宜。如果有一个新克隆的(因此不可变的)四级树,每个级别上有16个项目(总共65,536个叶子节点)并且所有节点都是共享不可变的,则更新叶子节点只需要替换一个叶子和另外四个节点与可变的。再次克隆树只需要为已被可变对象替换的节点创建新的不可变对象(例如复制五件事)。虽然人们可以拥有完全可变树的便利,但人们可以获得不可变树的效率优势。

我用这种方法看到的最大“问题”是,为了避免一些奇怪的行为,必须要求使用像GetNestedForWriting这样的语法。如果使用隐式转换运算符来避免该要求,则有人会期望像MyDict["Foo"]["Bar"].Value = "George"这样的语句将var st = MyThing["Foo"]["Bar"];定义为当时st保持的string快照;相反,它会将其定义为索引MyThing["Foo"]["Bar"]的东西。如果必须使用MyThing["Foo"]["Bar"]来读取或写入这种类型的字符串,那么变量不是字符串的事实就很明显了。如果使用隐式运算符来允许这样的赋值,则行为将是奇怪的。这太糟糕了,函数无法指定“不允许将此返回值用于类型推断”。

顺便说一句,索引器类型可以是类或通用结构。如果它是一个类,则对.Value嵌套foo["Bar"]["boz"]["baz"]...深的访问可能需要创建N个临时堆对象。如果它是一个通用结构,它将需要创建N结构,但更深层嵌套的结构会变得更大。对于合理的嵌套级别,通用结构可能会稍微提高效率,但类可能更容易使用。

答案 2 :(得分:0)

使用Dictionary作为TValue

var dict2 = new Dictionary<string, Dictionary<string, string>>();
var dict3 = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>();

例如:

var dict =
    new Dictionary<string, Dictionary<string, string>>
        {
            {
                "F1", new Dictionary<string, string>
                            {
                                {"F2", "foo"}
                            }
                }
        };


dict["F1"]["F2"] = "bar";

答案 3 :(得分:0)

您必须决定是否支持查找固定数量的字符串键,或者如果键数可能不同,则提供更通用的键机制。对于第一种情况,请尝试以下方法:

Dictionary<string,Dictionary<string,string>> dict =
    Dictionary<string,Dictionary<string,string>>();
dict["F1"]["F2"] = "foo";
Dictionary<string,Dictionary<string,Dictionary<string,string>>> dict2 =
    Dictionary<string,Dictionary<string,string>>();
dict2["F1"]["F2"]["F3"] = "bar";

对于第二种情况,您可以执行以下操作:

Dictionary<string[],string> dict = new Dictionary<string[],string>(new MyEqualityComparer());
dict[new string[] {"F1","F2"}] = "foo";
dict[new string[] {"F1","F2","F3"}] = "bar";

类MyEqualityComparer类似于:

public class MyEqualityComparer : IEqualityComparer<string[]>
{
    public int GetHashCode(string[]item)
    {
         int hashcode = 0;
         foreach (string s in item)
         { 
             hashcode |= s.GetHashCode();
         }
         return hashcode;
    }

    public bool Equals(string [] a, string [] b)
    {
         if (a.Length != b.Length)
             return false;
         for (int i = 0; i < a.Length; ++i)
         {
             if (a[i] != b[i])
                 return false;
         }
         return true;
   }

答案 4 :(得分:0)

我认为,您的案例是使用DynamicObject的好地方。我将在内部使用Dictionary<string, object>为json创建一个示例。

同样的想法也可以用于xml。

string json = @"{""Name"":""Joe"",
                 ""Age"":30,
                 ""Address"":{ ""City"":""NY"" }}";

dynamic dynObj = new DynamicJson(json);

Console.WriteLine(dynObj.Name);
Console.WriteLine(dynObj.Age);
Console.WriteLine(dynObj.Address.City);

-

public class DynamicJson : DynamicObject
{
    Dictionary<string, object> _Dict;

    public DynamicJson(string json)
    {
        _Dict = (Dictionary<string, object>)new JavaScriptSerializer().DeserializeObject(json); 
    }

    DynamicJson(Dictionary<string, object> dict)
    {
        _Dict = dict;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = null;
        object obj;
        if (!_Dict.TryGetValue(binder.Name, out obj)) return false;

        if (obj is Dictionary<string, object>)
        {
            result = new DynamicJson((Dictionary<string, object>)obj);
        }else
        {
            result = obj;
        }
        return true;
    }
}