【问题标题】:Is a deep nested Dictionary an antipattern?深度嵌套字典是反模式吗?
【发布时间】:2012-03-07 09:54:49
【问题描述】:

我有一个结构可以很容易地使用三层嵌套字典来表示,就像这样

private static Dictionary<string, Dictionary<string, Dictionary<string,string>>> PrerenderedTemplates;

这种结构可能会用到这样的地方

PrerenderedTemplates[instanceID][templategroup][templatepart]

现在,我意识到这段代码很难阅读,因为通过查看定义语句,您无法判断它的用途。将其更改为Dictionary&lt;string, PrerenderedTemplate&gt; 时我真正看到的唯一优势是可读性。将每个嵌套转换为自己的类(例如class PrerenderedTemplate{} class TemplateGroup{} class TemplatePart{})将添加更多代码行,但计算优势很小(如果有的话)。据我所知。

  • 那么,我的方法“可以”还是应该加倍努力并创建单独的课程?
  • 是否可以在文档/cmets 中介绍嵌套 Dictionary 的工作原理
  • 有处理这种嵌套的最佳做法吗?
  • 请记住,这是一个私有成员,对于使用该类的人来说,它不需要直截了当。

更新

因此,受 Reza 的启发,但无法使用元组,我决定创建自己的密钥生成器并像这样实现他的模式:

private Dictionary<string, string> PrerenderedTemplates;
private string GetPrerenderedTemplateKey(string InstanceId, string FeatureId, string OptionId)
{
    return new StringBuilder(instanceId)
    .Append(FormatTools.LIST_ENTRY_DELIMITER)
    .Append(templategroup)
    .Append(FormatTools.LIST_ENTRY_DELIMITER)
    .Append(templatepart).ToString();
}

其中FormatTools.LIST_ENTRY_DELIMITER 是Unicode 专用字符0xe04d

【问题讨论】:

  • 由于前两个嵌套本质上只是标识符,也许我可以使用简单的 Dictionary。所以PrerenderedTemplates["instance1"]["fruit"]["banana"] 可以只表示为PrerenderedTemplates["instance1_fruit_banana"],就像命名空间一样。
  • 您是否需要能够使用PrerenderedTemplates 来列出您的模板组或模板部分?有点像PrerenderedTemplates[instanceID].KeysPrerenderedTemplates[instanceID][templateGroup]?如果是这样,那么这可能是处理它的最简单方法。
  • @M.Babcock,好吧,我正在循环一个对象集合,这些对象包含指向如何呈现模板的元数据。在渲染该模板之前,我想检查我的字典以确保它之前没有被渲染过。如果没有,我会渲染它并将结果添加到我的字典中。 (模板变更管理在别处处理)
  • @Iain Fraser 我喜欢您的第一个选项,其中包含您不允许在键语言中作为分隔符的特殊字符。我过去做过,它对我有用。

标签: c# oop data-structures dictionary


【解决方案1】:

我提供另一种选择:

Dictionary<Tuple<string, string, string>, string> pt;

访问字典:

pt[Tuple.Create("id","group","part")]

更新

C# 7 中引入的

Value Tuples 最引人注目:

Dictionary<(string id, string group, string part), string> pt;

访问字典:

pt[("id", "group", "part")]

【讨论】:

  • 很好,我一定会试一试,让你知道结果如何!
  • 我不懂你的意思!!
  • 对不起,Reza,我告诉过你我喜欢你的方法,我会尝试一下。我应该尽量不要使用这么多的俚语。
  • 我应该指定我使用的是 C# v3.5,而 Tuple 仅在 C# v4.0 中引入。我也很想用你的模式:(
  • 没有什么能阻止你将类似的类用作键 - 实现 Equals 和 GetHashCode ,你就可以开始了(添加其他东西,如 == 会很好,但可选,即示例stackoverflow.com/questions/569903/multi-value-dictionary)。将键连接为字符串的更简洁的解决方案,也有点复杂。
【解决方案2】:

我会创建一个自定义字典。像这样的

public class TrippleKeyDict
{
    private const string Separator = "<|>";
    private Dictionary<string, string> _dict = new Dictionary<string, string>();

    public string this[string key1, string key2, string key3]
    {
        get { return _dict[GetKey(key1, key2, key3)]; }
        set { _dict[GetKey(key1, key2, key3)] = value; }
    }

    public void Add(string key1, string key2, string key3, string value)
    {
        _dict.Add(GetKey(key1, key2, key3), value);
    }

    public bool TryGetValue(string key1, string key2, string key3, out string result)
    {
        return _dict.TryGetValue(GetKey(key1, key2, key3), out result);
    }

    private static string GetKey(string key1, string key2, string key3)
    {
        return String.Concat(key1, Separator, key2, Separator, key3);
    }
}

如果您认为连接字符串不够安全,因为键可能包含分隔符,那么请使用您自己的键类型或 Touple&lt;string,string,string&gt; 作为键。由于此实现细节隐藏在您的自定义字典中,您可以随时更改它。

你可以像这样使用字典

var dict = new TrippleKeyDict();

// Using the Add method
dict.Add(instanceID, templategroup, templatepart, "some value");

// Using the indexer
dict[instanceID, templategroup, templatepart] = "xy";
string result = dict[instanceID, templategroup, templatepart];

// Using the TryGetValue method
if (dict.TryGetValue(instanceID, templategroup, templatepart, out result)) {
    // Do something with result
}

【讨论】:

  • 我明白你的意思。 hello_world &gt; hooray &gt; stuffhello &gt; world_hooray &gt; stuff 会发生冲突。两者都将使用密钥“hello_world_hooray_stuff”...
  • 我使用"|" 作为分隔符。你可以使用另一个你知道它从未在你的密钥中使用过的,比如"&lt;|&gt;"
  • 我正在构建的框架使用一个非常晦涩的 unicode 字符作为分隔符。敢说我会用! :)
  • 哇,今天学到了:en.wikipedia.org/wiki/…
  • 我不能使用 touples,因为我使用 .NET 3.5,但是将字典定义为 Dictionary&lt;Touple&lt;string,string,string&gt;,string&gt;,正如 Reza Arab 所展示的那样,并将其合并到 TrippleKeyDict 中应该不会太难。
【解决方案3】:

我想提供一种替代方法,使用 SortedDictionary 和自定义比较器:

    public class PrerenderedTemplate
    {
        public string instanceID;
        public string templategroup;
        public string templatepart;

        public PrerenderedTemplate(string id, string tempGroup, string tempPart)
        {
            instanceID = id;
            templategroup = tempGroup;
            templatepart = tempPart;
        }

        // custom comparer instance used as argument 
        // to SortedDictionary constructor
        public class Comparer : IComparer<PrerenderedTemplate>
        {
            public int Compare(PrerenderedTemplate x, PrerenderedTemplate y)
            {
                int compare = 0;
                if (compare == 0) compare = x.instanceID.CompareTo(y.instanceID);
                if (compare == 0) compare = x.templategroup.CompareTo(y.templategroup);
                if (compare == 0) compare = x.templatepart.CompareTo(y.templatepart);
                return compare;
            }
        }
    }

这样使用:

    var dictionary = new SortedDictionary<PrerenderedTemplate, string>(new PrerenderedTemplate.Comparer());

    dictionary.Add(new PrerenderedTemplate("1", "2", "3"), "123");
    dictionary.Add(new PrerenderedTemplate("4", "5", "6"), "456");
    dictionary.Add(new PrerenderedTemplate("7", "8", "9"), "789");

    Assert.AreEqual<string>(dictionary[new PrerenderedTemplate("7", "8", "9")], "789");

RezaArab 的回答是符合目的的,但我个人不喜欢元组,因为它们具有模棱两可的属性和冗长的语法。

如果任何需求发生变化,带有比较器的自定义类会提供更高的清晰度和灵活性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-29
    • 2020-06-07
    相关资源
    最近更新 更多