Создание постоянного словаря в C #

128

Каков наиболее эффективный способ создать постоянное (никогда не изменяется во время выполнения) сопоставление strings с ints?

Я пробовал использовать const Dictionary , но это не сработало.

Я мог бы реализовать неизменяемую оболочку с соответствующей семантикой, но это все еще кажется не совсем правильным.


Для тех, кто спросил, я реализую IDataErrorInfo в сгенерированном классе и ищу способ выполнить поиск columnName в моем массиве дескрипторов.

Я не знал (опечатка при тестировании! Ох!), Что переключатель принимает строки, так что я собираюсь использовать его. Спасибо!

Дэвид Шмитт
источник
1
здесь есть решение: stackoverflow.com/questions/10066708/…

Ответы:

181

Создание действительно сгенерированного во время компиляции словаря констант в C # на самом деле не является простой задачей. На самом деле ни один из ответов здесь не дает этого.

Однако есть одно решение, которое соответствует вашим требованиям, хотя и не обязательно хорошее; помните, что в соответствии со спецификацией C # таблицы вариантов переключения компилируются в таблицы переходов с постоянным хешем. То есть это постоянные словари, а не серия операторов if-else. Рассмотрим такой оператор switch-case:

switch (myString)
{
   case "cat": return 0;
   case "dog": return 1;
   case "elephant": return 3;
}

Это именно то, что вам нужно. И да, я знаю, это некрасиво.

Тамаш Чинеге
источник
9
В любом случае это сгенерированный код, имеет хорошие характеристики производительности, может быть вычислен во время компиляции, а также имеет другие полезные свойства для моего варианта использования. Я буду так делать!
Дэвид Шмитт
4
просто будьте осторожны, возвращение типов, не являющихся значениями, даже в таком случае, нарушит неизменяемость ваших классов.
Том Андерсон
35
switch-case великолепен, пока вам не понадобится «foreach» через значения.
Alex
6
В нем отсутствует множество приятных функций, которые есть в словарях.
Zinan Xing
1
@TomAnderson: конструкция будет вести себя как неизменяемая, если возвращаемые элементы являются типами значений (изменяемыми или нет) или если они являются неизменяемыми объектами класса.
supercat
37

В текущей структуре очень мало неизменяемых коллекций. Я могу придумать один относительно безболезненный вариант в .NET 3.5:

Использование Enumerable.ToLookup()- Lookup<,>класс неизменяемый (но многозначный по правому краю); вы можете сделать это Dictionary<,>довольно легко:

    Dictionary<string, int> ids = new Dictionary<string, int> {
      {"abc",1}, {"def",2}, {"ghi",3}
    };
    ILookup<string, int> lookup = ids.ToLookup(x => x.Key, x => x.Value);
    int i = lookup["def"].Single();
Марк Гравелл
источник
15
enum Constants
{
    Abc = 1,
    Def = 2,
    Ghi = 3
}

...

int i = (int)Enum.Parse(typeof(Constants), "Def");
Ричард Пул
источник
2
Интересная идея! Мне интересно, насколько хорошо работает вызов Parse (). Боюсь, что только профайлер может ответить на этот вопрос.
Дэвид Шмитт
10

Это самое близкое к "словарю CONST":

public static int GetValueByName(string name)
{
    switch (name)
    {
        case "bob": return 1;
        case "billy": return 2;
        default: return -1;
    }
}

Компилятор будет достаточно умен, чтобы собрать код как можно более чистым.

Тимоти Хоури
источник
8

При использовании 4.5+ Framework я бы использовал ReadOnlyDictionary (также ReadOnly Collection для списков), чтобы делать сопоставления / константы только для чтения. Это реализовано следующим образом.

static class SomeClass
{
    static readonly ReadOnlyDictionary<string,int> SOME_MAPPING 
        = new ReadOnlyDictionary<string,int>(
            new Dictionary<string,int>()
            {
                { "One", 1 },
                { "Two", 2 }
            }
        )
}        
Kram
источник
private static readonly Dictionary<string, string> _yourDictionaryName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase) { { "One",1 }, { "Two",2 }, { "Three",3 }, };Вот как я это сделал.
Sanket Sonavane
@SanketSonavane a readonly Dictionary<TKey, TValue>не эквивалентно a ReadOnlyDictionary<TKey, TValue>. Фактически, их способ «только для чтения» в значительной степени противоположен друг другу. См. Dotnetfiddle.net/v0l4aA .
Broots Waymb,
2

Почему бы не использовать пространства имен или классы для вложенности ваших значений? Он может быть несовершенным, но очень чистый.

public static class ParentClass
{
    // here is the "dictionary" class
    public static class FooDictionary
    {
        public const string Key1 = "somevalue";
        public const string Foobar = "fubar";
    }
}

Теперь вы можете получить доступ к .ParentClass.FooDictionary.Key1 и т. Д.

Джошуа
источник
Потому что это не реализует интерфейс IDataErrorInfo.
Дэвид Шмитт
1

Кажется, что не существует стандартного неизменяемого интерфейса для словарей, поэтому, к сожалению, создание оболочки кажется единственным разумным вариантом.

Изменить : Марк Гравелл нашел ILookup, который я пропустил, - это позволит вам, по крайней мере, избежать создания новой оболочки, хотя вам все равно нужно преобразовать словарь с помощью .ToLookup ().

Если это необходимость, ограниченная конкретным сценарием, вам может быть лучше с интерфейсом, более ориентированным на бизнес-логику:

interface IActiveUserCountProvider
{
    int GetMaxForServer(string serverName);
}
шлифовальный
источник
1

Я не уверен, почему никто не упомянул об этом, но в C # для вещей, которым я не могу назначить const, я использую статические свойства только для чтения.

Пример:

public static readonly Dictionary<string, string[]> NewDictionary = new Dictionary<string, string[]>()
        {
            { "Reference1", Array1 },
            { "Reference2", Array2 },
            { "Reference3", Array3 },
            { "Reference4", Array4 },
            { "Reference5", Array5 }
        };
Сулеман
источник
потому что содержимое словаря все еще изменяемо и выделяется во время выполнения.
Дэвид Шмитт
0

Еще одна идея, поскольку я привязываюсь к комбинированному списку winforms:

public enum DateRange {
    [Display(Name = "None")]
    None = 0,
    [Display(Name = "Today")]
    Today = 1,
    [Display(Name = "Tomorrow")]
    Tomorrow = 2,
    [Display(Name = "Yesterday")]
    Yesterday = 3,
    [Display(Name = "Last 7 Days")]
    LastSeven = 4,
    [Display(Name = "Custom")]
    Custom = 99
    };

int something = (int)DateRange.None;

Чтобы получить значение int из отображаемого имени из :

public static class EnumHelper<T>
{
    public static T GetValueFromName(string name)
    {
        var type = typeof(T);
        if (!type.IsEnum) throw new InvalidOperationException();

        foreach (var field in type.GetFields())
        {
            var attribute = Attribute.GetCustomAttribute(field,
                typeof(DisplayAttribute)) as DisplayAttribute;
            if (attribute != null)
            {
                if (attribute.Name == name)
                {
                    return (T)field.GetValue(null);
                }
            }
            else
            {
                if (field.Name == name)
                    return (T)field.GetValue(null);
            }
        }

        throw new ArgumentOutOfRangeException("name");
    }
}

использование:

var z = (int)EnumHelper<DateRange>.GetValueFromName("Last 7 Days");
Джина Марано
источник
-2

Почему нет:

public class MyClass
{
    private Dictionary<string, int> _myCollection = new Dictionary<string, int>() { { "A", 1 }, { "B", 2 }, { "C", 3 } };

    public IEnumerable<KeyValuePair<string,int>> MyCollection
    {
        get { return _myCollection.AsEnumerable<KeyValuePair<string, int>>(); }
    }
}

источник
3
Потому что это довольно дорого по сравнению с доступными альтернативами при указанных условиях.
Дэвид Шмитт
А также потому, что это даже не компилируется
AustinWBryan
@AustinWBryan, да, компилируется
derHugo