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!


1

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.


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

15
enum Constants
{
    Abc = 1,
    Def = 2,
    Ghi = 3
}

...

int i = (int)Enum.Parse(typeof(Constants), "Def");

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.


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

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.


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

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

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

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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.