Является ли глубоко вложенным Словарем антипаттерн?
У меня есть структура, которая может быть очень легко представлена с использованием трехмерного вложенного словаря, например
private static Dictionary<string, Dictionary<string, Dictionary<string,string>>> PrerenderedTemplates;
Если в структуре может быть что-то вроде этого
PrerenderedTemplates[instanceID][templategroup][templatepart]
Теперь я понимаю, что этот код трудно прочитать, потому что, глядя на инструкцию определения, вы не можете сказать, для чего он используется. Единственное преимущество, которое я могу увидеть при замене на Dictionary<string, PrerenderedTemplate>
, - это читаемость. Преобразование каждого вложения в собственный класс (например, class PrerenderedTemplate{} class TemplateGroup{} class TemplatePart{}
) добавило бы еще много строк кода для небольшого (если такового) вычислительного преимущества. Насколько я могу судить.
- Итак, мой подход "хорошо" или мне нужно пройти лишнюю милю и создать отдельные классы?
- Можно ли описать, как вложенный
Dictionary
работает в документации/комментариях
- Есть ли наилучшая практика для обработки такого рода вложенности?
- Имейте в виду, что это частный член, он не должен быть простым для людей, использующих класс.
Обновление
Итак, вдохновленный Резой, но неспособный использовать Tuples, я решил создать свой собственный генератор ключей и реализовать его шаблон следующим образом:
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
.
Ответы
Ответ 1
Я предлагаю другой выбор:
Dictionary<Tuple<string, string, string>, string> pt;
Доступ к словарю:
pt[Tuple.Create("id","group","part")]
ОБНОВЛЕНИЕ:
Значения кортежей, введенные в С# 7, наиболее привлекательны:
Dictionary<(string id, string group, string part), string> pt;
Доступ к словарю:
pt[("id", "group", "part")]
Ответ 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<string,string,string>
. Поскольку эта деталь реализации скрыта внутри вашего пользовательского словаря, вы можете изменить ее в любое время.
Вы можете использовать словарь, подобный этому
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
}
Ответ 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 подходит для цели, но лично мне не нравятся Tuples на основе их неоднозначных свойств и подробного синтаксиса.
Пользовательский класс с компаратором предлагает большую ясность и гибкость при любых изменениях требований.