มีวิธีการตรวจสอบว่า int เป็น enum ทางกฎหมายใน C # หรือไม่?


168

ฉันได้อ่านโพสต์ SO เล็กน้อยและดูเหมือนว่าการทำงานพื้นฐานส่วนใหญ่หายไป

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

78นี้ทำให้ไม่มีข้อยกเว้นก็มีความสุขในการจัดเก็บ มีวิธีการตรวจสอบค่าที่จะเข้าสู่ enum หรือไม่?


2
ซ้ำซ้อนที่เป็นไปได้ของValidate Enum Values
Erik

คำตอบ:


272

เช็คเอาท์ Enum.IsDefined

การใช้งาน:

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

นี่คือตัวอย่างจากหน้านั้น:

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

ตัวอย่างแสดงผลลัพธ์ต่อไปนี้:

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

@matti: แปลง "78" เป็นตัวเลขที่LoggingLevelใช้แทนพื้นที่เก็บข้อมูลจากนั้นแสดงว่าเป็นLoggingLevelค่า enum
thecoop

9
ดูเหมือนว่าIsDefinedจะไม่ทำงานสำหรับสมาชิก enum ที่ถูก bitwised
Saeed Neamati

29

การแก้ปัญหาข้างต้นไม่ได้เกี่ยวข้องกับ[Flags]สถานการณ์

วิธีการแก้ปัญหาของฉันด้านล่างอาจมีปัญหาประสิทธิภาพการทำงานบางอย่าง (ฉันแน่ใจว่าใครสามารถเพิ่มประสิทธิภาพในรูปแบบต่างๆ) แต่เป็นหลักมันก็จะพิสูจน์ว่า enum ค่าที่ถูกต้องหรือไม่

มันขึ้นอยู่กับสมมติฐานที่สาม:

  • ค่า Enum ใน C # ได้รับอนุญาตให้เป็นintอย่างอื่นเท่านั้น
  • ชื่อ Enum ใน C # จะต้องเริ่มต้นด้วยตัวอักษร
  • ไม่มีชื่อ enum ที่ถูกต้องสามารถเป็นได้ด้วยเครื่องหมายลบ: -

การเรียกToString()ใช้ enum ส่งคืนทั้งintค่าหนึ่งหากไม่มีการจับคู่ enum (แฟล็กหรือไม่) หากจับคู่ค่า enum ที่ได้รับอนุญาตมันจะพิมพ์ชื่อของการจับคู่ (es)

ดังนั้น:

[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"

ด้วยกฎสองข้อนี้เราสามารถสันนิษฐานได้ว่าถ้า. NET Framework ทำงานได้อย่างถูกต้องว่าการเรียกใช้ToString()เมธอดenum ที่ถูกต้องจะส่งผลให้บางสิ่งที่มีตัวอักษรเป็นตัวอักษรตัวแรก:

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

ใคร ๆ ก็เรียกมันว่า "แฮ็ค" แต่ข้อดีก็คือโดยอาศัยการปฏิบัติตามEnumมาตรฐานของ Microsoft และ C # คุณจึงไม่ต้องพึ่งพารหัสหรือเช็คที่อาจเป็นรถของคุณเอง ในสถานการณ์ที่ประสิทธิภาพไม่สำคัญอย่างยิ่งสิ่งนี้จะบันทึกข้อความที่น่ารังเกียจswitchหรือเช็คอื่น ๆ มากมาย!

แก้ไข

ขอบคุณ @ChaseMedallion ที่ชี้ให้เห็นว่าการใช้งานดั้งเดิมของฉันไม่สนับสนุนค่าลบ สิ่งนี้ได้รับการแก้ไขและมีการทดสอบ

และการทดสอบเพื่อสำรอง:

[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
ขอบคุณสำหรับสิ่งนี้ฉันมีปัญหาที่คล้ายกันในการจัดการกับชุดค่าผสมที่ถูกต้อง เป็นอีกทางเลือกหนึ่งในการตรวจสอบอักขระตัวแรกของ enum คุณสามารถลอง int.TryParse (enumValue.ToString ()) ... ถ้ามันล้มเหลวคุณมีชุดธงที่ถูกต้อง แม้ว่านี่อาจช้ากว่าโซลูชันของคุณก็ตาม
MadHenchbot

การนำไปใช้งานนี้ล้มเหลวในการตรวจสอบค่าลบอย่างถูกต้องเนื่องจากการตรวจสอบเป็นตัวอักษรที่ไม่ใช่ตัวเลข
ChaseMedallion

จับดี!! ฉันจะอัปเดตคำตอบของฉันเพื่อรองรับเช่นนี้ขอบคุณ @ChaseMedallion
joshcomley

ฉันชอบวิธีนี้ที่สุดวิธีแก้ปัญหาทางคณิตศาสตร์ที่นำเสนอใช้ได้เฉพาะในกรณีที่[Flags]มีค่าจำนวนเต็มที่สมเหตุสมผล
MrLore

17

คำตอบที่ยอมรับได้จะเป็นEnum.IsDefinedแต่นั่นคือ a: a bit ช้าถ้าใช้ในการวนรอบที่แน่นและ b: ไม่มีประโยชน์สำหรับ[Flags]enums

โดยส่วนตัวแล้วฉันจะหยุดกังวลเกี่ยวกับสิ่งนั้นและswitchจดจำได้อย่างเหมาะสม:

  • ถ้ามันก็โอเคที่จะไม่รับรู้ทุกอย่าง (และไม่ทำอะไรเลย) จากนั้นอย่าเพิ่มdefault:(หรือมีdefault:คำอธิบายที่ว่างเปล่าว่าทำไม)
  • หากมีพฤติกรรมเริ่มต้นที่เหมาะสมให้ใส่ไว้ใน default:
  • มิฉะนั้นจัดการสิ่งที่คุณรู้และทิ้งข้อยกเว้นสำหรับส่วนที่เหลือ:

ชอบมาก

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

ไม่คุ้นเคยกับ enums [ธง] และประสิทธิภาพไม่ใช่ปัญหาดังนั้นคำตอบของคุณดูเหมือนจะเป็นเหตุผลว่าทำไม enums ถูกประดิษฐ์ขึ้นในตอนแรก;) ดูที่ "คะแนน" ของคุณหรือสิ่งที่พวกเขาถูกเรียกดังนั้นคุณต้องมีจุด . พนันว่าคุณไม่ได้รับอะไรเลย แต่ลองคิดถึงสถานการณ์ของการอ่านไฟล์กำหนดค่าที่มี 257 ค่าในการป้องกัน enum เดียว นับไม่ถ้วน enums อื่น ๆ นับสิบ จะมีจำนวนมากกรณีแถว ...
ถ่านม.

@matti - ฟังดูเป็นตัวอย่างสุดขั้ว; ดีซีเรียลไลเซชันเป็นพื้นที่ผู้เชี่ยวชาญอยู่แล้ว - เอ็นจิ้นการทำให้เป็นอนุกรมส่วนใหญ่จะให้การตรวจสอบ enum ฟรี
Marc Gravell

@matti - ในบันทึกด้านข้าง ฉันจะบอกว่ารักษาคำตอบตามความดีของแต่ละคน บางครั้งฉันทำผิดพลาดอย่างสมบูรณ์และบางคนที่มี "ตัวแทน 17" ก็สามารถให้คำตอบที่สมบูรณ์แบบได้เช่นกัน
Marc Gravell

คำตอบของสวิตช์เร็ว แต่ไม่ใช่คำทั่วไป
Eldritch Conundrum



4

เพื่อจัดการกับ[Flags]คุณยังสามารถใช้โซลูชันนี้จาก C # Cookbook :

ก่อนอื่นให้เพิ่มALLค่าใหม่ให้กับ enum ของคุณ:

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

จากนั้นตรวจสอบว่ามีค่าอยู่ในALL:

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

2

วิธีหนึ่งที่จะทำคือต้องพึ่งการคัดเลือกและ enum ต่อการแปลงสตริง เมื่อแคสต์ int เป็นประเภท Enum int จะถูกแปลงเป็นค่า enum ที่สอดคล้องกันหรือผลลัพธ์ enum จะมีค่า int เป็นค่าหากไม่ได้กำหนดค่า enum สำหรับ int

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

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

ไม่ได้ทดสอบสำหรับเคสขอบใด ๆ


1

เป็นคนอื่น ๆ กล่าวว่าEnum.IsDefinedผลตอบแทนที่falseแม้ว่าคุณจะมีการรวมกันที่ถูกต้องของธงบิตสำหรับ enum FlagsAttributeตกแต่งด้วย

น่าเศร้าวิธีเดียวที่จะสร้างวิธีการคืนค่าจริงสำหรับค่าสถานะบิตที่ถูกต้องคือความยาวบิต:

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

คุณอาจต้องการแคชผลลัพธ์ของGetCustomAttributeในพจนานุกรม:

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

โปรดทราบว่ารหัสข้างต้นใช้Enumข้อ จำกัดใหม่Tที่ใช้ได้เฉพาะตั้งแต่ C # 7.3 คุณต้องส่งobject valueเวอร์ชั่นเก่าและโทรหาGetType()มัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.