Tạo một từ điển không đổi trong C #


128

Cách hiệu quả nhất để tạo ánh xạ hằng (không bao giờ thay đổi trong thời gian chạy) của strings thành ints là gì?

Tôi đã thử sử dụng từ điển const , nhưng không được.

Tôi có thể thực hiện một trình bao bọc bất biến với ngữ nghĩa phù hợp, nhưng điều đó dường như vẫn chưa hoàn toàn đúng.


Đối với những người đã hỏi, tôi đang triển khai IDataErrorInfo trong một lớp được tạo và đang tìm cách để làm cho cột Tên tra cứu vào mảng mô tả của tôi.

Tôi đã không biết (lỗi đánh máy khi kiểm tra! D'O!) Công tắc đó chấp nhận chuỗi, vì vậy đó là những gì tôi sẽ sử dụng. Cảm ơn!


1
có một giải pháp ở đây: stackoverflow.com/questions/10066708/ từ

Câu trả lời:


180

Tạo một từ điển hằng số được tạo theo thời gian thực sự biên dịch trong C # không thực sự là một nhiệm vụ đơn giản. Trên thực tế, không có câu trả lời nào ở đây thực sự đạt được điều đó.

Có một giải pháp mặc dù đáp ứng yêu cầu của bạn, mặc dù không nhất thiết phải là một giải pháp tốt đẹp; hãy nhớ rằng theo đặc tả C #, các bảng trường hợp chuyển đổi được biên dịch thành các bảng nhảy băm không đổi. Đó là, chúng là từ điển không đổi, không phải là một loạt các câu lệnh if-other. Vì vậy, hãy xem xét một tuyên bố trường hợp chuyển đổi như thế này:

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

Đây chính xác là những gì bạn muốn. Và vâng, tôi biết, nó xấu.


9
Dù sao, nó được tạo mã, có các đặc tính hiệu năng tốt, có thể được tính toán theo thời gian biên dịch và cũng có các thuộc tính đẹp khác cho trường hợp sử dụng của tôi. Tôi sẽ làm nó như thế này!
David Schmitt

4
chỉ cần cẩn thận rằng trả về các loại không có giá trị, thậm chí như thế này, sẽ phá vỡ tính bất biến của các lớp của bạn.
Tom Anderson

35
trường hợp chuyển đổi là tuyệt vời, cho đến khi bạn cần "tìm kiếm" thông qua các giá trị.
Alex

6
Nó thiếu rất nhiều chức năng hay mà Từ điển có.
Zinan Xing

1
@TomAnderson: Cấu trúc sẽ hoạt động như bất biến nếu các mục được trả về là các loại giá trị (có thể thay đổi hoặc không) hoặc nếu chúng là các đối tượng lớp bất biến.
supercat

37

Có rất ít bộ sưu tập bất biến trong khuôn khổ hiện tại. Tôi có thể nghĩ về một tùy chọn tương đối không đau trong .NET 3.5:

Sử dụng Enumerable.ToLookup()- Lookup<,>lớp là bất biến (nhưng đa giá trị trên rhs); bạn có thể làm điều này từ một cách Dictionary<,>khá dễ dàng:

    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
Ý tưởng thú vị! Tôi đang tự hỏi làm thế nào tốt cuộc gọi Parse () thực hiện. Tôi sợ chỉ có một hồ sơ có thể trả lời đó.
David Schmitt

10

Đây là thứ gần nhất bạn có thể nhận được từ "CONST Dictionary":

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

Trình biên dịch sẽ đủ thông minh để xây dựng mã càng sạch càng tốt.


8

Nếu sử dụng 4.5+ Framework, tôi sẽ sử dụng ReadOnlyDipedia (cũng là Bộ sưu tập ReadOnly cho danh sách) để thực hiện ánh xạ / hằng số chỉ đọc. Nó được thực hiện theo cách sau.

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 }, };Đây là cách tôi đã làm nó.
Sanket Sonavane

@SanketSonavane a readonly Dictionary<TKey, TValue>không tương đương với a ReadOnlyDictionary<TKey, TValue>. Trên thực tế, cách họ "chỉ đọc" trái ngược hoàn toàn với nhau. Xem: dotnetfiddle.net/v0l4aA .
Broots Waymb

2

Tại sao không sử dụng không gian tên hoặc các lớp để lồng các giá trị của bạn? Nó có thể không hoàn hảo, nhưng nó rất sạch sẽ.

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

Bây giờ bạn có thể truy cập .ParentClass.FooDipedia.Key1, v.v.


Bởi vì điều này không thực hiện giao diện IDataErrorInfo.
David Schmitt

1

Dường như không có bất kỳ giao diện bất biến tiêu chuẩn nào cho từ điển, vì vậy, việc tạo một trình bao bọc có vẻ như là lựa chọn hợp lý duy nhất, thật không may.

Chỉnh sửa : Marc Gravell đã tìm thấy ILookup mà tôi đã bỏ lỡ - điều đó sẽ cho phép bạn ít nhất tránh việc tạo một trình bao bọc mới, mặc dù bạn vẫn cần phải chuyển đổi Từ điển với .ToLookup ().

Nếu đây là một nhu cầu bị ràng buộc trong một kịch bản cụ thể, bạn có thể tốt hơn với giao diện định hướng logic kinh doanh hơn:

interface IActiveUserCountProvider
{
    int GetMaxForServer(string serverName);
}

1

Tôi không chắc tại sao không ai đề cập đến điều này nhưng trong C # cho những thứ mà tôi không thể gán const, tôi sử dụng các thuộc tính chỉ đọc tĩnh.

Thí dụ:

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

bởi vì nội dung của từ điển vẫn có thể thay đổi và được phân bổ vào thời gian chạy.
David Schmitt

0

Chỉ là một ý tưởng khác vì tôi liên kết với 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;

Để lấy giá trị int từ tên hiển thị từ :

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

sử dụng:

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

-2

Tại sao không:

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
Bởi vì điều này là khá tốn kém so với các lựa chọn thay thế có sẵn trong các điều kiện quy định.
David Schmitt

Cũng bởi vì điều này thậm chí không được biên dịch
AustinWBryan

@AustinWBryan vâng, nó biên dịch
derHugo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.