2

.NETDictionary<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 等的等价物。

4

6 回答 6

15

由于需要任意长的嵌套,我提出了以下解决方案,据我所知,根据我的测试,它不会中断:

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];
            }
        }
    }

测试:

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);
于 2012-09-22T12:02:03.773 回答
12

你可以使用标准字典来做到这一点,你只需要声明嵌套:

Dictionary<string, Dictionary<string, string>> dict = ...
string test = dict["first"]["second"]

Dictionary<string, Dictionary<string, Dictionary<string, string>>> dict = ...
string test = dict["first"]["second"]["third"]

etc
于 2012-09-22T10:53:31.290 回答
1

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

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

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

虽然后一种方法比前一种方法更复杂,但它还有另一个优点。可以让每个节点单独将其集合保存为共享的深度不可变字典或非共享的可变字典;如果一个GetNestedForWritingcall 看到嵌套对象是不可变的,它可以构造一个新的浅可变对象来保存相同的项目。如果将可变节点的克隆方法定义为使用所有子节点的(不可变的)克隆创建一个新的不可变节点,并且将不可变节点的克隆方法定义为返回自身,那么克隆大部分不可变的树变得非常便宜。如果有一个新克隆的(因此不可变的)四级树,每级有 16 个项目(总共 65,536 个叶节点),并且所有节点都是共享不可变的,那么更新叶节点只需要替换一个叶和四个其他节点与可变的。再次克隆树只需要为已经被可变对象替换的节点创建新的不可变对象(例如复制五个东西)。尽管人们会拥有一棵完全可变的树的便利,

我在这种方法中看到的最大“问题”是,为了避免一些奇怪的行为,必须使用类似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临时堆对象。如果它是通用结构,则需要创建N结构,但嵌套更深的结构会变得更大。对于合理的嵌套级别,通用结构可能会更有效,但类可能更容易使用。

于 2012-09-22T17:28:34.623 回答
0

我认为,您的案例是使用的好地方DynamicObjectDictionary<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;
    }
}
于 2012-09-22T11:48:50.830 回答
0

使用Dictionaryas 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";
于 2012-09-22T10:54:44.653 回答
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;
   }
于 2012-09-22T11:00:31.217 回答