簡體   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