繁体   English   中英

.NET中的嵌套字典集合

[英]Nested Dictionary collection in .NET

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

根据我的测试,我已经提出了以下解决方案,据我所知,这个解决方案没有中断。

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);

您可以使用标准Dictionary执行此操作,您只需声明嵌套:

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

为与vb6一起使用而创建的原始Dictionary COM对象将通过创建具有相应名称的Dictionary类型的新项来响应尝试访问不存在的项。 这种方法允许将某些内容存储到MyDict["Foo"]["Bar"]而无需先创建MyDict["Foo"] 这种方法的问题在于,当执行对MyDict["Foo"]["Bar"]的写入时,人们想要向MyDict添加"Foo" ,如果有人试图评估,则不希望创建这样的项目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方法将是一个不可变的“空”项。 因此,使用此方法将避免创建空项。

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

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

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

使用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";

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

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

我认为,你的案例是使用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;
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM