Có cách nào để kiểm tra xem int có phải là enum hợp pháp trong C # không?


167

Tôi đã đọc một vài bài viết SO và có vẻ như hầu hết các hoạt động cơ bản đều bị thiếu.

public enum LoggingLevel
{
    Off = 0,
    Error = 1,
    Warning = 2,
    Info = 3,
    Debug = 4,
    Trace = 5
};

if (s == "LogLevel")
{
    _log.LogLevel = (LoggingLevel)Convert.ToInt32("78");
    _log.LogLevel = (LoggingLevel)Enum.Parse(typeof(LoggingLevel), "78");
    _log.WriteDebug(_log.LogLevel.ToString());
}

Điều này không gây ra ngoại lệ, rất vui khi lưu trữ 78. Có cách nào để xác nhận giá trị đi vào enum không?


2
Bản sao có thể có của các giá trị Enum hợp lệ
Erik

Câu trả lời:


271

Kiểm tra Enum.IsDefined

Sử dụng:

if(Enum.IsDefined(typeof(MyEnum), value))
    MyEnum a = (MyEnum)value; 

Đây là ví dụ từ trang đó:

using System;    
[Flags] public enum PetType
{
   None = 0, Dog = 1, Cat = 2, Rodent = 4, Bird = 8, Reptile = 16, Other = 32
};

public class Example
{
   public static void Main()
   {
      object value;     
      // Call IsDefined with underlying integral value of member.
      value = 1;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with invalid underlying integral value.
      value = 64;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with string containing member name.
      value = "Rodent";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with a variable of type PetType.
      value = PetType.Dog;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = PetType.Dog | PetType.Cat;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with uppercase member name.      
      value = "None";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = "NONE";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with combined value
      value = PetType.Dog | PetType.Bird;
      Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = value.ToString();
      Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
   }
}

Ví dụ hiển thị đầu ra sau:

//       1: True
//       64: False
//       Rodent: True
//       Dog: True
//       Dog, Cat: False
//       None: True
//       NONE: False
//       9: False
//       Dog, Bird: False

@matti: Chuyển đổi "78" thành bất kỳ đại diện số nào LoggingLevelsử dụng làm bộ lưu trữ, sau đó trình bày dưới dạng LoggingLevelgiá trị enum.
thecoop

9
Có vẻ như IsDefinedkhông làm việc cho các thành viên enum bitwised.
Saeed Neamati

29

Các giải pháp trên không đối phó với các [Flags]tình huống.

Giải pháp của tôi dưới đây có thể có một số vấn đề về hiệu suất (tôi chắc chắn rằng một người có thể tối ưu hóa theo nhiều cách khác nhau) nhưng về cơ bản, nó sẽ luôn chứng minh liệu giá trị enum có hợp lệ hay không .

Nó dựa trên ba giả định:

  • Giá trị Enum trong C # chỉ được phép là int, hoàn toàn không có gì khác
  • Tên Enum trong C # phải bắt đầu bằng ký tự chữ cái
  • Không có tên enum hợp lệ có thể có dấu trừ: -

Gọi ToString()enum trả về intgiá trị nếu không có enum (cờ hoặc không) được khớp. Nếu một giá trị enum được phép được khớp, nó sẽ in tên của (các) kết quả khớp.

Vì thế:

[Flags]
enum WithFlags
{
    First = 1,
    Second = 2,
    Third = 4,
    Fourth = 8
}

((WithFlags)2).ToString() ==> "Second"
((WithFlags)(2 + 4)).ToString() ==> "Second, Third"
((WithFlags)20).ToString() ==> "20"

Với hai quy tắc này, chúng ta có thể giả sử rằng nếu .NET Framework thực hiện đúng công việc của mình thì mọi cuộc gọi đến ToString()phương thức của enum hợp lệ sẽ dẫn đến một cái gì đó có ký tự chữ cái là ký tự đầu tiên của nó:

public static bool IsValid<TEnum>(this TEnum enumValue)
    where TEnum : struct
{
    var firstChar = enumValue.ToString()[0];
    return (firstChar < '0' || firstChar > '9') && firstChar != '-';
}

Người ta có thể gọi nó là "hack", nhưng ưu điểm là bằng cách dựa vào việc thực hiện các Enumtiêu chuẩn và C # của chính Microsoft , bạn không dựa vào mã hoặc kiểm tra lỗi tiềm ẩn của chính mình. Trong các tình huống mà hiệu suất không đặc biệt quan trọng, điều này sẽ tiết kiệm rất nhiều switchtuyên bố khó chịu hoặc kiểm tra khác!

Biên tập

Cảm ơn @ChaseMedallion đã chỉ ra rằng việc triển khai ban đầu của tôi không hỗ trợ các giá trị âm. Điều này đã được khắc phục và kiểm tra cung cấp.

Và các bài kiểm tra để sao lưu nó:

[TestClass]
public class EnumExtensionsTests
{
    [Flags]
    enum WithFlags
    {
        First = 1,
        Second = 2,
        Third = 4,
        Fourth = 8
    }

    enum WithoutFlags
    {
        First = 1,
        Second = 22,
        Third = 55,
        Fourth = 13,
        Fifth = 127
    }

    enum WithoutNumbers
    {
        First, // 1
        Second, // 2
        Third, // 3
        Fourth // 4
    }

    enum WithoutFirstNumberAssigned
    {
        First = 7,
        Second, // 8
        Third, // 9
        Fourth // 10
    }


    enum WithNagativeNumbers
    {
        First = -7,
        Second = -8,
        Third = -9,
        Fourth = -10
    }

    [TestMethod]
    public void IsValidEnumTests()
    {
        Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
        Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
        Assert.IsTrue(((WithFlags)(1 | 4 | 2)).IsValid());
        Assert.IsTrue(((WithFlags)(2)).IsValid());
        Assert.IsTrue(((WithFlags)(3)).IsValid());
        Assert.IsTrue(((WithFlags)(1 + 2 + 4 + 8)).IsValid());

        Assert.IsFalse(((WithFlags)(16)).IsValid());
        Assert.IsFalse(((WithFlags)(17)).IsValid());
        Assert.IsFalse(((WithFlags)(18)).IsValid());
        Assert.IsFalse(((WithFlags)(0)).IsValid());

        Assert.IsTrue(((WithoutFlags)1).IsValid());
        Assert.IsTrue(((WithoutFlags)22).IsValid());
        Assert.IsTrue(((WithoutFlags)(53 | 6)).IsValid());   // Will end up being Third
        Assert.IsTrue(((WithoutFlags)(22 | 25 | 99)).IsValid()); // Will end up being Fifth
        Assert.IsTrue(((WithoutFlags)55).IsValid());
        Assert.IsTrue(((WithoutFlags)127).IsValid());

        Assert.IsFalse(((WithoutFlags)48).IsValid());
        Assert.IsFalse(((WithoutFlags)50).IsValid());
        Assert.IsFalse(((WithoutFlags)(1 | 22)).IsValid());
        Assert.IsFalse(((WithoutFlags)(9 | 27 | 4)).IsValid());

        Assert.IsTrue(((WithoutNumbers)0).IsValid());
        Assert.IsTrue(((WithoutNumbers)1).IsValid());
        Assert.IsTrue(((WithoutNumbers)2).IsValid());
        Assert.IsTrue(((WithoutNumbers)3).IsValid());
        Assert.IsTrue(((WithoutNumbers)(1 | 2)).IsValid()); // Will end up being Third
        Assert.IsTrue(((WithoutNumbers)(1 + 2)).IsValid()); // Will end up being Third

        Assert.IsFalse(((WithoutNumbers)4).IsValid());
        Assert.IsFalse(((WithoutNumbers)5).IsValid());
        Assert.IsFalse(((WithoutNumbers)25).IsValid());
        Assert.IsFalse(((WithoutNumbers)(1 + 2 + 3)).IsValid());

        Assert.IsTrue(((WithoutFirstNumberAssigned)7).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)8).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)9).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)10).IsValid());

        Assert.IsFalse(((WithoutFirstNumberAssigned)11).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)6).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)(7 | 9)).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)(8 + 10)).IsValid());

        Assert.IsTrue(((WithNagativeNumbers)(-7)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-8)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-9)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-10)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(-11)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(7)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(8)).IsValid());
    }
}

1
Cảm ơn vì điều này, tôi đã có một vấn đề tương tự liên quan đến các kết hợp cờ hợp lệ. Để thay thế cho việc kiểm tra ký tự đầu tiên của enum, bạn cũng có thể thử int.TryPude (enumValue.ToString ()) ... Nếu thất bại, bạn có một bộ cờ hợp lệ. Điều này thực sự có thể chậm hơn giải pháp của bạn, mặc dù.
MadHenchbot

Việc triển khai này không xác thực chính xác các giá trị âm, vì kiểm tra dành cho các ký tự không có chữ số
ChaseMedallion

Nắm bắt tốt!! Tôi sẽ cập nhật câu trả lời của mình để phù hợp với điều đó, cảm ơn bạn @ChaseMedallion
joshcomley

Tôi thích giải pháp này nhất, các mẹo toán học được trình bày chỉ hoạt động nếu [Flags]có các giá trị nguyên hợp lý.
MrLore

17

Câu trả lời chính tắc sẽ là Enum.IsDefined, nhưng đó là: hơi chậm nếu được sử dụng trong một vòng lặp chặt chẽ và b: không hữu ích cho [Flags]enums.

Cá nhân, tôi sẽ ngừng lo lắng về điều đó, và chỉ cần switchnhớ một cách thích hợp:

  • nếu không thể nhận ra mọi thứ (và không làm gì cả), thì đừng thêm default:(hoặc default:giải thích tại sao)
  • nếu có một hành vi mặc định hợp lý, hãy đặt nó trong default:
  • mặt khác, xử lý những cái bạn biết và ném một ngoại lệ cho phần còn lại:

Thích như vậy:

switch(someflag) {
    case TriBool.Yes:
        DoSomething();
        break;
    case TriBool.No:
        DoSomethingElse();
        break;
    case TriBool.FileNotFound:
        DoSomethingOther();
        break;
    default:
        throw new ArgumentOutOfRangeException("someflag");
}

không quen thuộc với enles [Cờ] và hiệu suất không phải là vấn đề vì vậy câu trả lời của bạn có vẻ giống như lý do tại sao enum được phát minh ngay từ đầu;) nhìn vào "điểm" của bạn hoặc bất cứ điều gì chúng được gọi để bạn phải có điểm ở đó . Đặt cược bạn không nhận được chúng vì không có gì, nhưng hãy suy nghĩ về tình huống đọc tệp cấu hình trong đó có 256 giá trị trong một lần liệt kê. Hãy để một mình hàng chục enum khác. Sẽ có rất nhiều trường hợp ...
char m

@matti - đó là một ví dụ cực đoan; deserialization là một lĩnh vực chuyên gia - hầu hết các công cụ tuần tự hóa cung cấp xác nhận enum miễn phí.
Marc Gravell

@matti - trên một ghi chú bên; Tôi muốn nói câu trả lời dựa trên giá trị cá nhân của họ. Đôi khi tôi nhận được những điều hoàn toàn sai, và ai đó với "rep 17" cũng có thể đưa ra một câu trả lời hoàn hảo như nhau .
Marc Gravell

Câu trả lời chuyển đổi là nhanh, nhưng không chung chung.
Câu hỏi hóc búa Eldritch

8

Sử dụng:

Enum.IsDefined ( typeof ( Enum ), EnumValue );


4

Để đối phó với [Flags]bạn, bạn cũng có thể sử dụng giải pháp này từ C # Cookbook :

Đầu tiên, thêm một ALLgiá trị mới vào enum của bạn:

[Flags]
enum Language
{
    CSharp = 1, VBNET = 2, VB6 = 4, 
    All = (CSharp | VBNET | VB6)
}

Sau đó, kiểm tra xem giá trị nằm trong ALL:

public bool HandleFlagsEnum(Language language)
{
    if ((language & Language.All) == language)
    {
        return (true);
    }
    else
    {
        return (false);
    }
}

2

Một cách để làm là dựa vào chuyển đổi và enum để chuyển đổi chuỗi. Khi truyền int thành kiểu Enum, int được chuyển đổi thành giá trị enum tương ứng hoặc enum kết quả chỉ chứa int dưới dạng giá trị nếu giá trị enum không được xác định cho int.

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

int statusCode=2;
NetworkStatus netStatus = (NetworkStatus) statusCode;
bool isDefined = netStatus.ToString() != statusCode.ToString();

Không được thử nghiệm cho bất kỳ trường hợp cạnh.


1

Như những người khác nói, Enum.IsDefined trả về falsengay cả khi bạn có một tổ hợp cờ bit hợp lệ cho một enum được trang trí bằngFlagsAttribute .

Đáng buồn thay, cách duy nhất để tạo một phương thức trả về giá trị true các cờ bit là hơi dài:

public static bool ValidateEnumValue<T>(T value) where T : Enum
{
    // Check if a simple value is defined in the enum.
    Type enumType = typeof(T);
    bool valid = Enum.IsDefined(enumType, value);
    // For enums decorated with the FlagsAttribute, allow sets of flags.
    if (!valid && enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true)
    {
        long mask = 0;
        foreach (object definedValue in Enum.GetValues(enumType))
            mask |= Convert.ToInt64(definedValue);
        long longValue = Convert.ToInt64(value);
        valid = (mask & longValue) == longValue;
    }
    return valid;
}

Bạn có thể muốn lưu trữ kết quả của GetCustomAttribute trong từ điển:

private static readonly Dictionary<Type, bool> _flagEnums = new Dictionary<Type, bool>();
public static bool ValidateEnumValue<T>(T value) where T : Enum
{
    // Check if a simple value is defined in the enum.
    Type enumType = typeof(T);
    bool valid = Enum.IsDefined(enumType, value);
    if (!valid)
    {
        // For enums decorated with the FlagsAttribute, allow sets of flags.
        if (!_flagEnums.TryGetValue(enumType, out bool isFlag))
        {
            isFlag = enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true;
            _flagEnums.Add(enumType, isFlag);
        }
        if (isFlag)
        {
            long mask = 0;
            foreach (object definedValue in Enum.GetValues(enumType))
                mask |= Convert.ToInt64(definedValue);
            long longValue = Convert.ToInt64(value);
            valid = (mask & longValue) == longValue;
        }
    }
    return valid;
}

Lưu ý rằng mã ở trên sử dụng Enumràng buộc mới Tchỉ có sẵn kể từ C # 7.3. Bạn cần phải vượt qua một object valuephiên bản cũ hơn và gọi GetType()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.