Tworzenie stałego słownika w C #

132

Jaki jest najbardziej efektywny sposób tworzenia stałego (nigdy nie zmieniającego się w czasie wykonywania) odwzorowania strings na ints?

Próbowałem użyć const Dictionary , ale to nie wyszło.

Mógłbym zaimplementować niezmienny wrapper z odpowiednią semantyką, ale nadal nie wydaje się to całkowicie poprawne.


Dla tych, którzy o to pytali, implementuję IDataErrorInfo w wygenerowanej klasie i szukam sposobu, aby wyszukać columnName w mojej tablicy deskryptorów.

Nie byłem świadomy (literówka podczas testowania! D'oh!), Że przełącznik akceptuje ciągi, więc tego będę używał. Dzięki!

David Schmitt
źródło
1
jest tutaj rozwiązanie: stackoverflow.com/questions/10066708/ ...

Odpowiedzi:

183

Tworzenie prawdziwie generowanego w czasie kompilacji słownika stałego w języku C # nie jest tak naprawdę prostym zadaniem. Właściwie żadna z przedstawionych tutaj odpowiedzi naprawdę tego nie osiąga.

Jest jednak jedno rozwiązanie, które spełnia Twoje wymagania, choć niekoniecznie jest przyjemne; pamiętaj, że zgodnie ze specyfikacją C # tablice przypadków przełączników są kompilowane do postaci stałych tablic skoku z mieszaniem. Oznacza to, że są to stałe słowniki, a nie seria instrukcji jeśli-inaczej. Rozważ więc taką instrukcję przełączania:

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

To jest dokładnie to, czego chcesz. I tak, wiem, jest brzydki.

Tamas Czinege
źródło
9
Tak czy inaczej jest wygenerowany kod, ma dobrą charakterystykę wydajności, może być obliczany w czasie kompilacji i ma również inne fajne właściwości dla mojego przypadku użycia. Zrobię to w ten sposób!
David Schmitt
4
po prostu uważaj, aby zwracanie typów niebędących wartościami, nawet w ten sposób, złamie niezmienność klas.
Tom Anderson
36
przypadek przełącznika jest niesamowity, dopóki nie musisz „foreach” przez wartości.
Alex
6
Brakuje wielu fajnych funkcji, które mają słowniki.
Zinan Xing
1
@TomAnderson: Konstrukcja będzie zachowywać się jak niezmienna, jeśli zwrócone elementy są typami wartości (zmiennymi lub nie) lub jeśli są niezmiennymi obiektami klasy.
supercat
37

W obecnej strukturze istnieje bardzo niewiele niezmiennych kolekcji. Przychodzi mi do głowy jedna stosunkowo bezbolesna opcja w .NET 3.5:

Use Enumerable.ToLookup()- Lookup<,>klasa jest niezmienna (ale wielowartościowa po prawej stronie); możesz to zrobić w Dictionary<,>dość łatwy sposób:

    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();
Marc Gravell
źródło
15
enum Constants
{
    Abc = 1,
    Def = 2,
    Ghi = 3
}

...

int i = (int)Enum.Parse(typeof(Constants), "Def");
Richard Poole
źródło
2
Ciekawy pomysł! Zastanawiam się, jak dobrze działa wywołanie Parse (). Obawiam się, że tylko profiler może odpowiedzieć na to pytanie.
David Schmitt,
11

Oto najbliższa rzecz, jaką można znaleźć w „Słowniku CONST”:

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

Kompilator będzie wystarczająco inteligentny, aby zbudować kod tak czysty, jak to tylko możliwe.

Timothy Khouri
źródło
8

Jeśli korzystam z 4.5+ Framework, użyłbym ReadOnlyDictionary (także ReadOnly Collection dla list), aby wykonać mapowania / stałe tylko do odczytu. Jest zaimplementowany w następujący sposób.

static class SomeClass
{
    static readonly ReadOnlyDictionary<string,int> SOME_MAPPING 
        = new ReadOnlyDictionary<string,int>(
            new Dictionary<string,int>()
            {
                { "One", 1 },
                { "Two", 2 }
            }
        )
}        
Kram
źródło
private static readonly Dictionary<string, string> _yourDictionaryName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase) { { "One",1 }, { "Two",2 }, { "Three",3 }, };Tak to zrobiłem.
Sanket Sonavane
@SanketSonavane a readonly Dictionary<TKey, TValue>nie jest odpowiednikiem a ReadOnlyDictionary<TKey, TValue>. W rzeczywistości sposób, w jaki są one „tylko do odczytu”, jest w zasadzie przeciwieństwem. Zobacz: dotnetfiddle.net/v0l4aA .
Broots Waymb
2

Dlaczego nie używać przestrzeni nazw lub klas do zagnieżdżania wartości? Może być niedoskonały, ale jest bardzo czysty.

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

Teraz możesz uzyskać dostęp do .ParentClass.FooDictionary.Key1 itp.

Joshua
źródło
Ponieważ nie implementuje to interfejsu IDataErrorInfo.
David Schmitt,
2

Nie jestem pewien, dlaczego nikt o tym nie wspomniał, ale w C # dla rzeczy, których nie mogę przypisać const, używam statycznych właściwości tylko do odczytu.

Przykład:

public static readonly Dictionary<string, string[]> NewDictionary = new Dictionary<string, string[]>()
        {
            { "Reference1", Array1 },
            { "Reference2", Array2 },
            { "Reference3", Array3 },
            { "Reference4", Array4 },
            { "Reference5", Array5 }
        };
Suleman
źródło
ponieważ zawartość słownika jest nadal zmienna i jest przydzielana w czasie wykonywania.
David Schmitt
1

Wydaje się, że nie ma standardowego niezmiennego interfejsu dla słowników, więc niestety utworzenie opakowania wydaje się być jedyną rozsądną opcją.

Edycja : Marc Gravell znalazł ILookup, którego przegapiłem - pozwoli ci to przynajmniej uniknąć tworzenia nowego opakowania, chociaż nadal musisz przekształcić słownik za pomocą .ToLookup ().

Jeśli jest to potrzeba ograniczona do określonego scenariusza, lepiej byłoby, gdybyś miał interfejs bardziej zorientowany na logikę biznesową:

interface IActiveUserCountProvider
{
    int GetMaxForServer(string serverName);
}
Szlifierka
źródło
1

Po prostu kolejny pomysł, ponieważ wiążę się z combobox 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;

Aby uzyskać wartość int z nazwy wyświetlanej z :

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

stosowanie:

var z = (int)EnumHelper<DateRange>.GetValueFromName("Last 7 Days");
Gina Marano
źródło
-2

Dlaczego nie:

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

źródło
3
Ponieważ jest to dość drogie w porównaniu z dostępnymi alternatywami w określonych warunkach.
David Schmitt,
Również dlatego, że to się nawet nie kompiluje
AustinWBryan
@AustinWBryan tak, kompiluje
derHugo