2009-11-18 43 views
35

Có điều gì đó mà tôi không thể hiểu được trong C#. Bạn có thể bỏ ra một phạm vi ngoài phạm vi int thành một enum và trình biên dịch không nao núng. Hãy tưởng tượng này enum:Đúc ints vào enums trong C#

enum Colour 
{ 
    Red = 1, 
    Green = 2, 
    Blue = 3 
} 

Bây giờ, nếu bạn viết:

Colour eco; 
eco = (Colour)17; 

Trình biên dịch cho rằng đó là tốt. Và thời gian chạy cũng vậy. Uh?

Tại sao nhóm C# quyết định thực hiện việc này? Quyết định này bỏ lỡ điểm của việc sử dụng enums, tôi nghĩ rằng, trong tình huống như thế này:

void DoSomethingWithColour(Colour eco) 
{ 
    //do something to eco. 
} 

Trong một ngôn ngữ mạnh mẽ, đánh máy như C#, tôi muốn cho rằng eco sẽ luôn luôn giữ một giá trị pháp lý Colour. Nhưng đây không phải là trường hợp. Lập trình viên có thể gọi phương thức của tôi với giá trị 17 được gán cho eco (như trong đoạn mã trước), vì vậy mã trong phương thức của tôi không được giả định rằng eco giữ giá trị Colour hợp pháp. Tôi cần phải kiểm tra nó một cách rõ ràng và xử lý các giá trị đặc biệt như tôi xin vui lòng. Tại sao điều này?

Trong thiển ý của tôi, nó sẽ đẹp hơn nhiều nếu trình biên dịch đưa ra một lỗi (hoặc thậm chí là một cảnh báo) thông báo khi đúc một out-of phạm vi int thành một enum, nếu giá trị int được biết đến tại thời gian biên dịch. Nếu không, thời gian chạy phải ném một ngoại lệ tại câu lệnh gán.

Bạn nghĩ sao? Có lý do nào tại sao điều này là như vậy?

(. Lưu ý Đây là một câu hỏi I posted ages ago on my blog nhưng đã không có phản ứng nhiều thông tin.)

Trả lời

31

đoán về 'tại sao' luôn là nguy hiểm, nhưng xem xét việc này:

enum Direction { North =1, East = 2, South = 4, West = 8 } 
Direction ne = Direction.North | Direction.East; 

int value = (int) ne; // value == 3 
string text = ne.ToString(); // text == "3" 

Khi thuộc tính [Flags] được đặt trong phía trước của enum, dòng cuối cùng thay đổi thành

string text = ne.ToString(); // text == "North, East" 
+0

Điểm của bạn là gì? :-) Nếu bạn muốn làm cho SouthEast trở thành một hướng hợp lệ, tôi nghĩ bạn nên đưa nó vào khai báo enum là SouthEast = 10 chẳng hạn. Đồng ý? – CesarGon

+2

Chúng được gọi là bảng liệt kê cờ. Rất vaild thiết kế. –

+20

Không, không đồng ý. Các trường bit là việc sử dụng một enum hoàn toàn hợp lệ, rất nhiều để thuộc tính [Flags] thậm chí không cần thiết để sử dụng chúng như vậy. –

3

Khi bạn xác định enum, bạn chủ yếu đặt tên cho giá trị (s ugar nếu bạn sẽ). Khi bạn cast 17 to Color, bạn đang lưu trữ một giá trị cho một màu không có tên. Như bạn có thể biết cuối cùng nó chỉ là một lĩnh vực int anyways.

Điều này cũng giống như tuyên bố một số nguyên chỉ chấp nhận các giá trị từ 1 đến 100; ngôn ngữ duy nhất tôi từng thấy hỗ trợ mức kiểm tra này là CHILL.

+0

Không đồng ý. Có lẽ trong những ngày xa xưa của C++ nó là cú pháp đường. Nhưng trong .NET, enums là loại hạng nhất. Đó là toàn bộ điểm có loại enum! – CesarGon

+0

Bạn có thể tưởng tượng chi phí cho CLR để kiểm tra các enums hợp lệ không? Tôi nghĩ cuối cùng họ đã quyết định để nó không được kiểm soát bởi vì nó không đáng để kiểm tra nó. –

+0

Làm thế nào về thực tế bạn không còn có thể sử dụng bảng liệt kê cờ? –

4

Bạn không cần phải giải quyết ngoại lệ. Điều kiện tiên quyết cho phương thức này là người gọi nên sử dụng enum, không cast bất kỳ intilly willy nào vào enum nói. Đó sẽ là điên rồ. Không phải là điểm của enums không để sử dụng ints?

Bất kỳ nhà phát triển nào sẽ bỏ 17 đến màu enum đều cần 17 cú đá ngược mặt sau khi tôi quan tâm.

+0

Đồng ý về các cú đá. :-D Nhưng lập trình phòng thủ là một thực hành tốt. Và khi bạn đang thực hiện các giao diện công cộng cho thư viện lớp học của mình, bạn * biết * bạn nên kiểm tra tham số đầu vào. – CesarGon

+0

Vì vậy, bạn đặt một giá trị mặc định trên một chuyển đổi và ném một ngoại lệ hoặc thoát ra để trình gỡ rối. Có rất nhiều trường hợp vaild cho các giá trị số nguyên từ liệt kê. –

+0

@CesarGon: Vì vậy, hãy tự mình kiểm tra, trong ví dụ bạn cung cấp tất cả những gì bạn cần kiểm tra là giá trị nằm trong khoảng từ 1 đến 3. CmdrTallen có nhận xét hiển thị cách kiểm tra nói chung. – tloach

4

Đó là một trong nhiều lý do tại sao bạn không bao giờ nên gán giá trị nguyên cho enums của bạn. Nếu chúng có giá trị quan trọng cần được sử dụng trong các phần khác của mã, hãy biến enum thành một đối tượng.

+1

Cũng có nhiều lý do để bao gồm các giá trị. Bạn có thể tương tác với một hệ thống bên ngoài và chỉ sử dụng kiểu liệt kê để làm cho mã của bạn sạch hơn. –

+2

Tôi không nghĩ rằng có rất nhiều lý do. Tôi hiểu lý lẽ về giao tiếp với các hệ thống bên ngoài. Nhưng không có cách nào bạn có thể tranh luận rằng một enum với các giá trị được giao sẽ dẫn đến mã sạch hơn - giống như mã mùi hơn. Bạn có thể theo một mẫu đối tượng giá trị đơn giản và tạo các đối tượng dễ hiểu ... và, bạn biết đấy, hãy làm theo toàn bộ điều OOP? –

+3

Khi chuyển dữ liệu đến và từ DB bạn cần phải ép buộc enums từ int vào enum và ngược lại. – Guy

-1

Nó sẽ ném một lỗi bằng cách sử dụng Enum.Parse();

Enum parsedColour = (Colour)Enum.Parse(typeof(Colour), "17"); 

Có thể đáng để sử dụng để có lỗi khi chạy, nếu bạn muốn.

+2

Theo như tôi thấy, điều này không thực sự hiệu quả. Nó chỉ đặt parsedColour thành 17, thay vì ném một ngoại lệ. Một ngoại lệ IS được ném nếu bạn chuyển một số chuỗi ngẫu nhiên vào nó (ví dụ: "asdf"). Hay tôi đang thiếu một cái gì đó? –

+0

Sau khi thử nó, tôi cũng có thể đồng ý rằng điều này không hoạt động. Điều này không ném một lỗi thời gian chạy. Bạn có thể tự mình kiểm tra bằng cách thực hiện kiểm tra đơn vị nhanh chóng. – Gilles

1

Phiên bản ngắn:

Đừng làm điều này.

Cố gắng thay đổi enums thành ints chỉ cho phép các giá trị hợp lệ (có thể là dự phòng mặc định) yêu cầu các phương thức trợ giúp. Tại thời điểm đó bạn không có enum - bạn thực sự có một lớp học.

Đôi khi, nếu các int là không rõ ràng - như Bryan Rowe nói.

15

Không chắc chắn về lý do, nhưng gần đây tôi đã tìm thấy "tính năng" này cực kỳ hữu ích. Tôi đã viết một cái gì đó như thế này vào ngày khác

// a simple enum 
public enum TransmissionStatus 
{ 
    Success = 0, 
    Failure = 1, 
    Error = 2, 
} 
// a consumer of enum 
public class MyClass 
{ 
    public void ProcessTransmissionStatus (TransmissionStatus status) 
    { 
     ... 
     // an exhaustive switch statement, but only if 
     // enum remains the same 
     switch (status) 
     { 
      case TransmissionStatus.Success: ... break; 
      case TransmissionStatus.Failure: ... break; 
      case TransmissionStatus.Error: ... break; 
      // should never be called, unless enum is 
      // extended - which is entirely possible! 
      // remember, code defensively! future proof! 
      default: 
       throw new NotSupportedException(); 
       break; 
     } 
     ... 
    } 
} 

câu hỏi là, làm cách nào để kiểm tra mệnh đề trường hợp cuối cùng? Hoàn toàn hợp lý để giả sử ai đó có thể mở rộng TransmissionStatus và không cập nhật người tiêu dùng, như người nghèo ít MyClass ở trên. Tuy nhiên, tôi vẫn muốn xác minh hành vi của nó trong trường hợp này. Một cách là sử dụng tính năng truyền, chẳng hạn như

[Test] 
[ExpectedException (typeof (NotSupportedException))] 
public void Test_ProcessTransmissionStatus_ExtendedEnum() 
{ 
    MyClass myClass = new MyClass(); 
    myClass.ProcessTransmissionStatus ((TransmissionStatus)(10)); 
} 
+1

Bạn có nghiêm túc không? Tôi thấy rằng một hack nhanh chóng và bẩn thỉu, với tất cả sự tôn trọng. :-) – CesarGon

+4

Vì vậy, ... giả sử enums đã hoàn toàn loại an toàn với xác nhận giá trị, làm thế nào bạn sẽ đi về thử nghiệm điều kiện trước đó? Bạn có muốn thêm một giá trị "không được hỗ trợ" vào mọi enum bạn từng viết cho mục đích rõ ràng của kiểm tra mệnh đề mặc định? Nếu bạn hỏi tôi, mùi đó giống như mùi hôi thối lớn hơn nhiều;) –

+1

Thực tế, một giá trị không được hỗ trợ trong một enum rõ ràng là rõ ràng. Tôi nghĩ đó sẽ là một cách tốt hơn để làm bài kiểm tra, vâng. :-) – CesarGon

5

Tôi chắc chắn nhìn thấy điểm của Cesar và tôi nhớ điều này ban đầu cũng làm tôi bối rối. Theo ý kiến ​​của tôi enums, trong thực hiện hiện tại của họ, thực sự là một chút quá mức thấp và bị rò rỉ. Dường như với tôi rằng sẽ có hai giải pháp cho vấn đề.

1) Chỉ cho phép các giá trị tùy ý được lưu trữ trong một enum nếu định nghĩa của nó có FlagsAttribute. Bằng cách này, chúng ta có thể tiếp tục sử dụng chúng cho một bitmask khi thích hợp (và được khai báo một cách rõ ràng), nhưng khi được sử dụng đơn giản như phần giữ chỗ cho các hằng số, chúng ta sẽ nhận được giá trị được kiểm tra khi chạy.

2) Giới thiệu một loại nguyên thủy riêng biệt được gọi là say, bitmask, có thể cho phép bất kỳ giá trị ulong nào. Một lần nữa, chúng tôi hạn chế các enums chuẩn chỉ các giá trị được khai báo. Điều này sẽ có thêm lợi ích của việc cho phép trình biên dịch gán các giá trị bit cho bạn. Vì vậy, đây:

[Flags] 
enum MyBitmask 
{ 
    FirstValue = 1, SecondValue = 2, ThirdValue = 4, FourthValue = 8 
} 

sẽ tương đương như sau:

bitmask MyBitmask 
{ 
    FirstValue, SecondValue, ThirdValue, FourthValue 
} 

Sau khi tất cả, các giá trị cho bất kỳ bitmask là hoàn toàn có thể dự đoán, phải không? Là một lập trình viên, tôi hạnh phúc hơn khi có chi tiết này được trừu tượng hóa.

Tuy nhiên, đã quá muộn, tôi đoán chúng tôi đã bị mắc kẹt với việc triển khai hiện tại mãi mãi. :/

+0

Cảm ơn, Mikey. Đó là những gì tôi có ý nghĩa khi tôi nói rằng * thực * enums và bitfields ("lá cờ" enums) là trong thực tế, hai loại rất khác nhau của sự vật. C++ và các ngôn ngữ liên quan đã trated chúng như là một điều tương tự, và tôi sợ rằng C# đã thừa hưởng lỗ hổng đó. Nhưng ngữ nghĩa rõ ràng là khác nhau. Có lẽ Eric Lippert hay Jon Skeet có thể ghé qua và làm sáng tỏ vấn đề này. – CesarGon

0

nghĩ rằng tôi muốn chia sẻ mã tôi đã kết thúc sử dụng để xác nhận Enums, như cho đến nay chúng tôi dường như không có bất cứ điều gì ở đây mà làm việc ...

public static class EnumHelper<T> 
{ 
    public static bool IsValidValue(int value) 
    { 
     try 
     { 
      Parse(value.ToString()); 
     } 
     catch 
     { 
      return false; 
     } 

     return true; 
    } 

    public static T Parse(string value) 
    { 
     var values = GetValues(); 
     int valueAsInt; 
     var isInteger = Int32.TryParse(value, out valueAsInt); 
     if(!values.Select(v => v.ToString()).Contains(value) 
      && (!isInteger || !values.Select(v => Convert.ToInt32(v)).Contains(valueAsInt))) 
     { 
      throw new ArgumentException("Value '" + value + "' is not a valid value for " + typeof(T)); 
     } 

     return (T)Enum.Parse(typeof(T), value); 
    } 

    public static bool TryParse(string value, out T p) 
    { 
     try 
     { 
      p = Parse(value); 
      return true; 
     } 
     catch (Exception) 
     { 
      p = default(T); 
      return false; 
     } 

    } 

    public static IEnumerable<T> GetValues() 
    { 
     return Enum.GetValues(typeof (T)).Cast<T>(); 
    } 
} 
4

Đó là bất ngờ .. . những gì chúng ta thực sự muốn là để kiểm soát đúc ... ví dụ:

Colour eco; 
if(Enum.TryParse("17", out eco)) //Parse successfully?? 
{ 
    var isValid = Enum.GetValues(typeof (Colour)).Cast<Colour>().Contains(eco); 
    if(isValid) 
    { 
    //It is really a valid Enum Colour. Here is safe! 
    } 
} 
3
if (!Enum.IsDefined(typeof(Colour), 17)) 
{ 
    // Do something 
} 
0

Cũ câu hỏi, nhưng điều này nhầm lẫn cho tôi thời gian gần đây, và Google mang tôi đến đây.Tôi tìm thấy một bài viết với tìm kiếm sâu hơn mà cuối cùng đã làm cho nó bấm cho tôi, và nghĩ rằng tôi muốn trở lại và chia sẻ.

Ý chính là Enum là cấu trúc, có nghĩa là đó là loại giá trị (source). Nhưng về bản chất nó hoạt động như một kiểu dẫn xuất của kiểu cơ bản (int, byte, long, v.v.). Vì vậy, nếu bạn có thể nghĩ về một loại Enum giống như loại cơ bản của nó với một số chức năng/cú pháp bổ sung (như Otávio đã nói) thì bạn sẽ nhận thức được vấn đề này và có thể bảo vệ nó.

Nói về điều đó, đây là trái tim của một phương pháp khuyến nông, tôi đã viết để dễ dàng chuyển đổi/phân tích điều cần một Enum:

if (value != null) 
{ 
    TEnum result; 
    if (Enum.TryParse(value.ToString(), true, out result)) 
    { 
     // since an out-of-range int can be cast to TEnum, double-check that result is valid 
     if (Enum.IsDefined(typeof(TEnum), result.ToString())) 
     { 
      return result; 
     } 
    } 
} 

// deal with null and defaults... 

Biến value có các loại đối tượng, như phần mở rộng này có "quá tải" chấp nhận int, int ?, string, Enum và Enum ?. Tất cả chúng đều được đóng hộp và được gửi đến phương thức riêng để thực hiện phân tích cú pháp. Bằng cách sử dụng cả hai TryParseIsDefinedtheo thứ tự đó, tôi có thể phân tích tất cả các loại vừa được đề cập, bao gồm chuỗi trường hợp hỗn hợp và nó khá mạnh mẽ.

Cách sử dụng như sau (giả định NUnit):

[Test] 
public void MultipleInputTypeSample() 
{ 
    int source; 
    SampleEnum result; 

    // valid int value 
    source = 0; 
    result = source.ParseToEnum<SampleEnum>(); 
    Assert.That(result, Is.EqualTo(SampleEnum.Value1)); 

    // out of range int value 
    source = 15; 
    Assert.Throws<ArgumentException>(() => source.ParseToEnum<SampleEnum>()); 

    // out of range int with default provided 
    source = 30; 
    result = source.ParseToEnum<SampleEnum>(SampleEnum.Value2); 
    Assert.That(result, Is.EqualTo(SampleEnum.Value2)); 
} 

private enum SampleEnum 
{ 
    Value1, 
    Value2 
} 

Hy vọng rằng sẽ giúp ai đó.

Tuyên bố từ chối trách nhiệm: chúng tôi không sử dụng cờ/bitmask enums ... điều này chưa được thử nghiệm với kịch bản sử dụng đó và có thể sẽ không hoạt động.