Membuat Kamus konstan dalam C #


128

Apa cara paling efisien untuk membuat pemetaan s (yang tidak pernah berubah saat runtime) konstanstring menjadiint s yang ?

Saya sudah mencoba menggunakan Kamus const , tetapi itu tidak berhasil.

Saya bisa menerapkan pembungkus abadi dengan semantik yang sesuai, tetapi itu masih tampak tidak sepenuhnya benar.


Bagi mereka yang bertanya, saya menerapkan IDataErrorInfo di kelas yang dibuat dan saya sedang mencari cara untuk membuat pencarian columnName ke dalam array deskriptor saya.

Saya tidak sadar (salah ketik saat menguji! D'oh!) Yang beralih menerima string, jadi itulah yang akan saya gunakan. Terima kasih!


Jawaban:


180

Membuat kamus konstan yang dihasilkan waktu kompilasi yang benar-benar kompilasi di C # sebenarnya bukan tugas yang mudah. Sebenarnya, tidak ada jawaban di sini yang benar-benar mencapai itu.

Ada satu solusi yang memenuhi persyaratan Anda, meskipun tidak selalu bagus; ingat bahwa sesuai dengan spesifikasi C #, tabel switch-case dikompilasi ke tabel lompatan hash konstan. Artinya, mereka adalah kamus konstan, bukan serangkaian pernyataan if-else. Jadi pertimbangkan pernyataan sakelar kasus seperti ini:

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

Inilah yang Anda inginkan. Dan ya, saya tahu, itu jelek.


9
Ini kode yang dihasilkan, memiliki karakteristik kinerja yang baik, dapat dihitung waktu kompilasi, dan memiliki properti bagus lainnya untuk use case saya juga. Saya akan melakukannya seperti ini!
David Schmitt

4
hanya berhati-hatilah bahwa mengembalikan tipe yang tidak bernilai, bahkan seperti ini, akan mematahkan kelas Anda.
Tom Anderson

35
switch-case mengagumkan, sampai Anda perlu "foreach" melalui nilai-nilai.
Alex

6
Tidak memiliki banyak fungsi bagus yang dimiliki Kamus.
Zinan Xing

1
@ TomAnderson: Konstruk akan berperilaku sebagai tidak berubah jika item yang dikembalikan adalah tipe nilai (bisa berubah atau tidak), atau jika mereka adalah objek kelas yang tidak dapat diubah.
supercat

37

Ada beberapa koleksi berharga yang tidak berubah dalam kerangka saat ini. Saya dapat memikirkan satu opsi yang relatif bebas rasa sakit di .NET 3.5:

Gunakan Enumerable.ToLookup()- Lookup<,>kelas tidak dapat diubah (tetapi multi-nilai pada rhs); Anda dapat melakukan ini dari yang Dictionary<,>cukup mudah:

    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
Ide yang menarik! Saya bertanya-tanya seberapa bagus kinerja Parse (). Saya khawatir hanya seorang profiler yang dapat menjawabnya.
David Schmitt

10

Ini adalah hal terdekat yang bisa Anda dapatkan di "Kamus CONST":

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

Kompiler akan cukup pintar untuk membuat kode sebersih mungkin.


8

Jika menggunakan 4.5+ Framework saya akan menggunakan ReadOnlyDictionary (juga ReadOnly Collection untuk daftar) untuk melakukan pemetaan / konstanta readonly. Diimplementasikan dengan cara berikut.

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 }, };Beginilah cara saya melakukannya.
Sanket Sonavane

@SanketSonavane a readonly Dictionary<TKey, TValue>tidak setara dengan a ReadOnlyDictionary<TKey, TValue>. Padahal, cara mereka mereka "hanya baca" cukup banyak yang saling bertolak belakang. Lihat: dotnetfiddle.net/v0l4aA .
Broots Waymb

2

Mengapa tidak menggunakan ruang nama atau kelas untuk mengumpulkan nilai Anda? Mungkin tidak sempurna, tetapi sangat bersih.

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

Sekarang Anda dapat mengakses .ParentClass.FooDictionary.Key1, dll.


Karena ini tidak mengimplementasikan antarmuka IDataErrorInfo.
David Schmitt

1

Sepertinya tidak ada antarmuka standar yang tidak dapat diubah untuk kamus, jadi membuat pembungkus sepertinya satu-satunya pilihan yang masuk akal, sayangnya.

Edit : Marc Gravell menemukan ILookup yang saya lewatkan - yang akan memungkinkan Anda untuk setidaknya menghindari membuat pembungkus baru, meskipun Anda masih perlu mengubah Kamus dengan .ToLookup ().

Jika ini adalah kebutuhan yang dibatasi oleh skenario tertentu, Anda mungkin lebih baik dengan antarmuka yang lebih berorientasi bisnis-logika:

interface IActiveUserCountProvider
{
    int GetMaxForServer(string serverName);
}

1

Saya tidak yakin mengapa tidak ada yang menyebutkan ini tetapi dalam C # untuk hal-hal yang saya tidak dapat menetapkan const, saya menggunakan properti read-only statis.

Contoh:

public static readonly Dictionary<string, string[]> NewDictionary = new Dictionary<string, string[]>()
        {
            { "Reference1", Array1 },
            { "Reference2", Array2 },
            { "Reference3", Array3 },
            { "Reference4", Array4 },
            { "Reference5", Array5 }
        };

karena isi kamus masih bisa berubah, dan dialokasikan saat runtime.
David Schmitt

0

Hanya ide lain karena saya mengikat kotak kombo 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;

Untuk mendapatkan nilai int dari nama tampilan dari :

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

pemakaian:

var z = (int)EnumHelper<DateRange>.GetValueFromName("Last 7 Days");

-2

Kenapa tidak:

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
Karena ini cukup mahal dibandingkan dengan alternatif yang tersedia dalam kondisi yang ditentukan.
David Schmitt

Juga karena ini bahkan tidak dapat dikompilasi
AustinWBryan

@ AustinWBryan ya itu mengkompilasi
derHugo
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.