Tại sao ép kiểu int thành giá trị enum không hợp lệ KHÔNG ném ngoại lệ?


124

Nếu tôi có một enum như vậy:

enum Beer
{
    Bud = 10,
    Stella = 20,
    Unknown
}

Tại sao nó không ném một ngoại lệ khi truyền một ngoại lệ intnằm ngoài các giá trị này thành một kiểu Beer?

Ví dụ: đoạn mã sau không đưa ra ngoại lệ, nó xuất '50' vào bảng điều khiển:

int i = 50;
var b = (Beer) i;

Console.WriteLine(b.ToString());

Tôi thấy điều này kỳ lạ ... bất cứ ai có thể làm rõ?


27
Lưu ý rằng bạn luôn có thể kiểm tra xem một giá trị có hợp lệ hay không với Enum.IsDefined .
Tim Schmelter

mát Tôi đã không biết rằng, có lẽ sẽ sử dụng nó trong các vấn đề Tôi hiện đang cố gắng giải quyết
jcvandan


1
Được ủng hộ vì làm cho Stella có giá trị gấp đôi Bud.
Dan Bechard

Câu trả lời:


81

Lấy từ sự nhầm lẫn với phân tích cú pháp một Enum

Đây là quyết định của những người đã tạo ra .NET. Một enum được hỗ trợ bởi một kiểu giá trị ( int, short, byte, vv), và vì vậy nó có thể thực sự có giá trị gì đó có giá trị đối với những loại giá trị.

Cá nhân tôi không phải là một fan hâm mộ của cách này hoạt động, vì vậy tôi đã thực hiện một loạt các phương pháp tiện ích:

/// <summary>
/// Utility methods for enum values. This static type will fail to initialize 
/// (throwing a <see cref="TypeInitializationException"/>) if
/// you try to provide a value that is not an enum.
/// </summary>
/// <typeparam name="T">An enum type. </typeparam>
public static class EnumUtil<T>
    where T : struct, IConvertible // Try to get as much of a static check as we can.
{
    // The .NET framework doesn't provide a compile-checked
    // way to ensure that a type is an enum, so we have to check when the type
    // is statically invoked.
    static EnumUtil()
    {
        // Throw Exception on static initialization if the given type isn't an enum.
        Require.That(typeof (T).IsEnum, () => typeof(T).FullName + " is not an enum type.");
    }

    /// <summary>
    /// In the .NET Framework, objects can be cast to enum values which are not
    /// defined for their type. This method provides a simple fail-fast check
    /// that the enum value is defined, and creates a cast at the same time.
    /// Cast the given value as the given enum type.
    /// Throw an exception if the value is not defined for the given enum type.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumValue"></param>
    /// <exception cref="InvalidCastException">
    /// If the given value is not a defined value of the enum type.
    /// </exception>
    /// <returns></returns>
    public static T DefinedCast(object enumValue)

    {
        if (!System.Enum.IsDefined(typeof(T), enumValue))
            throw new InvalidCastException(enumValue + " is not a defined value for enum type " +
                                           typeof (T).FullName);
        return (T) enumValue;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="enumValue"></param>
    /// <returns></returns>
    public static T Parse(string enumValue)
    {
        var parsedValue = (T)System.Enum.Parse(typeof (T), enumValue);
        //Require that the parsed value is defined
        Require.That(parsedValue.IsDefined(), 
            () => new ArgumentException(string.Format("{0} is not a defined value for enum type {1}", 
                enumValue, typeof(T).FullName)));
        return parsedValue;
    }

    public static bool IsDefined(T enumValue)
    {
        return System.Enum.IsDefined(typeof (T), enumValue);
    }

}


public static class EnumExtensions
{
    public static bool IsDefined<T>(this T enumValue)
        where T : struct, IConvertible
    {
        return EnumUtil<T>.IsDefined(enumValue);
    }
}

Bằng cách này, tôi có thể nói:

if(!sEnum.IsDefined()) throw new Exception(...);

... hoặc là:

EnumUtil<Stooge>.Parse(s); // throws an exception if s is not a defined value.

Biên tập

Ngoài giải thích được đưa ra ở trên, bạn phải nhận ra rằng phiên bản .NET của Enum tuân theo mô hình lấy cảm hứng từ C hơn là phiên bản lấy cảm hứng từ Java. Điều này làm cho nó có thể có các enum "Cờ Bit" có thể sử dụng các mẫu nhị phân để xác định xem một "cờ" cụ thể có hoạt động trong một giá trị enum hay không. Nếu bạn phải xác định mọi sự kết hợp có thể có của các cờ (tức là MondayAndTuesday, MondayAndWednesdayAndThursday), chúng sẽ vô cùng tẻ nhạt. Vì vậy, có khả năng sử dụng các giá trị enum không xác định có thể thực sự hữu ích. Nó chỉ yêu cầu thêm một chút công việc khi bạn muốn có một hành vi không nhanh trên các loại enum không sử dụng các loại thủ thuật này.


Đẹp ... Tôi không phải là fan hâm mộ của cách nó hoạt động một trong hai, có vẻ kỳ lạ đối với tôi
jcvandan

3
@dormisher: "'Thật là điên rồ, nhưng vẫn có phương pháp để không." Xem bản chỉnh sửa của tôi.
StriplingWarrior

2
@StriplingWarrior, để quan tâm. Tương đương với Java là EnumSet.of (Thứ Hai, Thứ Tư, Thứ Năm). Bên trong EnumSet chỉ là một đoạn dài. Vì vậy, cung cấp một API đẹp mà không làm giảm nhiều hiệu quả.
Iain


@StriplingWarrior trong phương thức phân tích cú pháp của bạn, bạn đang cố gọi IsDefined giống như một phương thức mở rộng. Tuy nhiên, phương thức IsDefined không thể được coi là một phương thức mở rộng vì cách bạn cố gắng tạo lớp EnumUtil với các ràng buộc (để thực hiện kiểm tra tĩnh cho enum, điều mà tôi rất thích). tuy nhiên trên thực tế, nó nói với tôi rằng người ta không thể tạo một phương thức mở rộng trên một lớp chung. Có ý kiến ​​gì không?
CJC

55

Enums thường được dùng làm cờ:

[Flags]
enum Permission
{
    None = 0x00,
    Read = 0x01,
    Write = 0x02,
}
...

Permission p = Permission.Read | Permission.Write;

Giá trị của p là số nguyên 3, không phải là giá trị của enum, nhưng rõ ràng là giá trị hợp lệ.

Cá nhân tôi thà thấy một giải pháp khác; Tôi thà có khả năng tạo kiểu số nguyên "mảng bit" kiểu "tập hợp giá trị riêng biệt" dưới dạng hai tính năng ngôn ngữ khác nhau hơn là gộp cả hai thành "enum". Nhưng đó là những gì mà các nhà thiết kế khung và ngôn ngữ gốc đã nghĩ ra; kết quả là chúng ta phải cho phép các giá trị không được khai báo của enum là giá trị pháp lý.


19
Nhưng 3 loại "rõ ràng là một giá trị hợp lệ" củng cố quan điểm của OP. Thuộc tính [Flags] đó có thể yêu cầu trình biên dịch tìm kiếm một enum đã xác định hoặc một giá trị hợp lệ. Chỉ cần nói.
LarsTech

15

Câu trả lời ngắn gọn: Các nhà thiết kế ngôn ngữ đã quyết định thiết kế ngôn ngữ theo cách này.

Câu trả lời dài: Section 6.2.2: Explicit enumeration conversionscủa Đặc tả ngôn ngữ C # cho biết:

Việc chuyển đổi kiểu liệt kê rõ ràng giữa hai kiểu được xử lý bằng cách coi bất kỳ kiểu enum tham gia nào là kiểu cơ bản của kiểu enum đó, sau đó thực hiện chuyển đổi kiểu số ẩn hoặc rõ ràng giữa các kiểu kết quả. Ví dụ: cho một kiểu enum E với và kiểu cơ bản là int, một chuyển đổi từ E sang byte được xử lý như một chuyển đổi số rõ ràng (§6.2.1) từ int sang byte và chuyển đổi từ byte sang E được xử lý như một chuyển đổi số ẩn (§6.1.2) từ byte sang int.

Về cơ bản, enum được coi là kiểu cơ bản khi nói đến hoạt động chuyển đổi. Theo mặc định, kiểu cơ bản của enumInt32, có nghĩa là chuyển đổi được xử lý chính xác như chuyển đổi sang Int32. Điều này có nghĩa là mọi giá inttrị hợp lệ đều được phép.

Tôi nghi ngờ điều này được thực hiện chủ yếu vì lý do hiệu suất. Bằng cách tạo enummột kiểu tích phân đơn giản và cho phép bất kỳ chuyển đổi kiểu tích phân nào, CLR không cần thực hiện tất cả các kiểm tra bổ sung. Điều này có nghĩa là việc sử dụng một enumkhông thực sự có bất kỳ tổn thất nào về hiệu suất khi so sánh với việc sử dụng một số nguyên, do đó giúp khuyến khích việc sử dụng nó.


Bạn có thấy điều này lạ không? Tôi nghĩ một trong những điểm chính của enum là nhóm một cách an toàn một loạt các số nguyên mà chúng ta cũng có thể gán các ý nghĩa riêng lẻ. Đối với tôi, việc cho phép một kiểu enum giữ bất kỳ giá trị số nguyên nào sẽ đánh bại mục đích của nó.
jcvandan

@dormisher: Tôi chỉ chỉnh sửa và thêm bớt một chút ở cuối. Có một chi phí liên quan đến việc cung cấp "sự an toàn" mà bạn đang theo đuổi. Nói như vậy, với mức sử dụng bình thường , đây thực sự không phải là vấn đề.
Reed Copsey

9

Từ tài liệu :

Biến kiểu Ngày có thể được gán bất kỳ giá trị nào trong phạm vi của kiểu cơ bản; các giá trị không bị giới hạn trong các hằng số được đặt tên.

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.