ใครทราบวิธีแก้ปัญหาที่ดีสำหรับการขาดข้อ จำกัด ทั่วไปของ enum?


90

สิ่งที่ฉันต้องการทำมีดังนี้: ฉันมี enums ที่มีค่าสถานะรวมกัน

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo ) 
        where T:enum //the constraint I want that doesn't exist in C#3
    {    
        return (input & matchTo) != 0;
    }
}

ดังนั้นฉันสามารถทำได้:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

if( tester.IsSet( MyEnum.FlagA ) )
    //act on flag a

น่าเสียดายที่ C # เป็นของทั่วไปที่ข้อ จำกัด ไม่มีข้อ จำกัด enum มีเพียงคลาสและโครงสร้างเท่านั้น C # ไม่เห็น enums เป็นโครงสร้าง (แม้ว่าจะเป็นประเภทค่าก็ตาม) ดังนั้นฉันจึงไม่สามารถเพิ่มประเภทส่วนขยายเช่นนี้ได้

ไม่มีใครรู้วิธีแก้ปัญหา?


2
Keith: ดาวน์โหลด UnconstrainedMelody เวอร์ชัน 0.0.0.2 - ฉันได้ใช้ HasAll และ HasAny แล้ว สนุก.
Jon Skeet

คุณหมายถึงอะไรโดย“ C # ไม่เห็น enums เป็นโครงสร้าง”? คุณสามารถใช้ enum types เป็นพารามิเตอร์ประเภทที่ถูก จำกัด ให้structใช้ได้
Timwi

ตรวจสอบบทความนี้ที่นี่: codeproject.com/KB/cs/ExtendEnum.aspx วิธีการ 'IsValidEnumValue' หรือ 'IsFlagsEnumDefined' อาจเป็นคำตอบสำหรับคำถามของคุณ
dmihailescu

1
โหวตให้กับไอเดียนี้สำหรับผู้ใช้นี้หากคุณต้องการที่จะเห็นมันในตัว. net สักวันหนึ่ง
Matthieu

11
C # 7.3แนะนำข้อ จำกัด enum
Marc Sigrist

คำตอบ:


49

แก้ไข: ตอนนี้ใช้งานได้แล้วใน UnconstrainedMelody เวอร์ชัน 0.0.0.2

(ตามที่ร้องขอในบล็อกโพสต์ของฉันเกี่ยวกับข้อ จำกัด enumฉันได้รวมข้อเท็จจริงพื้นฐานไว้ด้านล่างเพื่อเป็นคำตอบแบบสแตนด์อโลน)

ทางออกที่ดีที่สุดคือการรอคอยสำหรับผมที่จะรวมไว้ในUnconstrainedMelody 1 นี่คือไลบรารีที่ใช้รหัส C # โดยมีข้อ จำกัด "ปลอม" เช่น

where T : struct, IEnumConstraint

และเปลี่ยนเป็น

where T : struct, System.Enum

ผ่านขั้นตอน postbuild

ไม่ควรเขียนยากเกินไปIsSet... แม้ว่าการจัดเตรียมแฟล็กทั้งแบบInt64อิงและแบบUInt64อิงอาจเป็นส่วนที่ยุ่งยาก (ฉันได้กลิ่นของวิธีการช่วยเหลือบางอย่างที่เกิดขึ้นโดยทั่วไปช่วยให้ฉันปฏิบัติต่อแฟล็ก enum ราวกับว่ามันมีประเภทพื้นฐานUInt64)

คุณต้องการให้พฤติกรรมเป็นอย่างไรถ้าคุณโทร

tester.IsSet(MyFlags.A | MyFlags.C)

เหรอ? ก็ควรตรวจสอบว่าทั้งหมดธงที่ระบุมีการตั้งค่า? นั่นคงเป็นความคาดหวังของฉัน

ฉันจะพยายามทำสิ่งนี้ระหว่างทางกลับบ้านคืนนี้ ... ฉันหวังว่าจะได้ใช้วิธีการ enum ที่มีประโยชน์อย่างรวดเร็วเพื่อให้ห้องสมุดมีมาตรฐานที่ใช้งานได้โดยเร็วจากนั้นผ่อนคลายสักหน่อย

แก้ไข: ฉันไม่แน่ใจว่าIsSetเป็นชื่ออะไร ตัวเลือก:

  • รวม
  • ประกอบด้วย
  • HasFlag (หรือ HasFlags)
  • IsSet (แน่นอนว่าเป็นตัวเลือก)

ยินดีต้อนรับ ฉันแน่ใจว่าจะต้องใช้เวลาสักพักก่อนที่สิ่งใดจะกลายเป็นหินอยู่ดี ...


1หรือส่งเป็นแพตช์แน่นอน ...


1
คุณต้องไปพูดถึง PostSharp LOL: o postsharp.org/blog/generic-constraints-for-enums-and-delegates
Sam Harwell

1
หรือง่ายกว่านั้น HasAny () และ HasAll ()
Keith

1
ใช่ฉันยอมรับว่ามันดีกว่านี้ colors.HasAny(Colors.Red | Colors.Blue)ดูเหมือนโค้ดที่อ่านได้มาก =)
Blixt

1
ใช่ฉันชอบ HasAny และ HasAll ด้วย จะไปกับที่.
Jon Skeet

5
ตั้งแต่ C # 7.3 (เผยแพร่พฤษภาคม 2018) จึงสามารถใช้ข้อ จำกัดwhere T : System.Enumได้ สิ่งนี้ถูกเขียนไว้แล้วที่อื่นในเธรด แค่คิดว่าฉันจะพูดซ้ำที่นี่
Jeppe Stig Nielsen


16

Darren ซึ่งจะใช้งานได้หากประเภทเป็นการแจงนับเฉพาะ - สำหรับการแจงนับทั่วไปในการทำงานคุณต้องส่งให้เป็น ints (หรือ uint ที่เป็นไปได้มากกว่า) เพื่อทำคณิตศาสตร์บูลีน:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}

1
และหากคุณมีจำนวนแฟล็กที่ไร้สาระคุณสามารถเรียก GetTypeCode () บนอาร์กิวเมนต์และ Convert ToUint64 ()
Kit

ยอดเยี่ยมการรวมกันของ 'Enum' และConvert.ToUInt32ฉันไม่พบที่อื่น AFAIK ซึ่งเป็นโซลูชัน Pre-Net-4 ที่ดีเพียงหนึ่งเดียวที่ใช้งานได้กับ VB BTW หากmatchToอาจมีแฟล็กบิตหลายตัวให้แทนที่!= 0ด้วย== Convert.ToUInt32(matchTo).
ToolmakerSteve

1
โปรดทราบว่าการConvert.ToUInt32ใช้กับ enum จะใช้การConvert.ToUInt32(object)โอเวอร์โหลดซึ่งหมายความว่า CLR จะใส่ค่าเหล่านี้ก่อนส่งต่อไปยังToUInt32เมธอด ในกรณีส่วนใหญ่สิ่งนี้จะไม่สำคัญ แต่เป็นเรื่องดีที่จะรู้ว่าคุณจะทำให้ GC ค่อนข้างยุ่งถ้าคุณใช้สิ่งนี้เพื่อแยกวิเคราะห์ enums นับล้านต่อวินาที
กรู

10

ที่จริงมันเป็นไปได้ด้วยกลอุบายที่น่าเกลียด อย่างไรก็ตามไม่สามารถใช้วิธีการขยายได้

public abstract class Enums<Temp> where Temp : class {
    public static TEnum Parse<TEnum>(string name) where TEnum : struct, Temp {
        return (TEnum)Enum.Parse(typeof(TEnum), name); 
    }
}
public abstract class Enums : Enums<Enum> { }

Enums.IsSet<DateTimeKind>("Local")

หากคุณต้องการคุณสามารถให้คอนEnums<Temp>สตรัคเตอร์ส่วนตัวและคลาสที่สืบทอดนามธรรมที่ซ้อนกันสาธารณะพร้อมกับTempas Enumเพื่อป้องกันเวอร์ชันที่สืบทอดมาสำหรับ non-enums


8

คุณสามารถทำได้โดยใช้ IL Weaving และExtraConstraints

ให้คุณเขียนโค้ดนี้

public class Sample
{
    public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
    {        
    }
    public void MethodWithEnumConstraint<[EnumConstraint] T>()
    {
    }
}

สิ่งที่รวบรวม

public class Sample
{
    public void MethodWithDelegateConstraint<T>() where T: Delegate
    {
    }

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}

7

ใน C # 7.3 คุณสามารถใช้ข้อ จำกัด Enum กับประเภททั่วไปได้:

public static TEnum Parse<TEnum>(string value) where TEnum : Enum
{
    return (TEnum) Enum.Parse(typeof(TEnum), value);
}

หากคุณต้องการใช้ Nullable enum คุณต้องออกจากข้อ จำกัด ของโครงสร้างดั้งเดิม:

public static TEnum? TryParse<TEnum>(string value) where TEnum : struct, Enum
{
    if( Enum.TryParse(value, out TEnum res) )
        return res;
    else
        return null;
}

4

นี่ไม่ได้ตอบคำถามเดิม แต่ตอนนี้มีวิธีการใน. NET 4 ที่เรียกว่าEnum.HasFlagซึ่งทำสิ่งที่คุณพยายามทำในตัวอย่างของคุณ


โหวตเพิ่มขึ้นเพราะ ณ จุดนี้ทุกคนควรใช้. NET 4 (หรือสูงกว่า) ดังนั้นจึงควรใช้วิธีนี้แทนที่จะพยายามแฮ็คด้วยกัน
CptRobby

โหวตแล้ว flagแต่ใช้วิธีการแก้ปัญหาของพวกเขามวยของการโต้แย้ง .NET 4.0 มีอายุห้าปีแล้ว
Jeppe Stig Nielsen

3

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


7
โดยที่ T: struct, IComparable, IFormattable, IConvertible - นี่คือสิ่งที่ใกล้เคียงที่สุดที่คุณจะได้รับ enum :)
Kit

1

การใช้รหัสเดิมของคุณในวิธีนี้คุณยังสามารถใช้การสะท้อนเพื่อทดสอบว่า T เป็น enum:

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo )
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("Must be an enum", "input");
        }
        return (input & matchTo) != 0;
    }
}

2
ขอบคุณ แต่นั่นจะเปลี่ยนปัญหาเวลาคอมไพล์ (ที่ข้อ จำกัด ) เป็นรันไทม์ (ข้อยกเว้นของคุณ) นอกจากนี้คุณยังต้องแปลงอินพุตเป็น ints ก่อนจึงจะสามารถทำอะไรกับมันได้
Keith

1

นี่คือรหัสบางส่วนที่ฉันเพิ่งทำขึ้นซึ่งดูเหมือนว่าจะใช้งานได้อย่างที่คุณต้องการโดยไม่ต้องทำอะไรบ้าๆเกินไป ไม่ได้ จำกัด เฉพาะ enums ที่ตั้งค่าเป็นแฟล็ก แต่อาจมีการตรวจสอบได้เสมอหากจำเป็น

public static class EnumExtensions
{
    public static bool ContainsFlag(this Enum source, Enum flag)
    {
        var sourceValue = ToUInt64(source);
        var flagValue = ToUInt64(flag);

        return (sourceValue & flagValue) == flagValue;
    }

    public static bool ContainsAnyFlag(this Enum source, params Enum[] flags)
    {
        var sourceValue = ToUInt64(source);

        foreach (var flag in flags)
        {
            var flagValue = ToUInt64(flag);

            if ((sourceValue & flagValue) == flagValue)
            {
                return true;
            }
        }

        return false;
    }

    // found in the Enum class as an internal method
    private static ulong ToUInt64(object value)
    {
        switch (Convert.GetTypeCode(value))
        {
            case TypeCode.SByte:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
                return (ulong)Convert.ToInt64(value, CultureInfo.InvariantCulture);

            case TypeCode.Byte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
                return Convert.ToUInt64(value, CultureInfo.InvariantCulture);
        }

        throw new InvalidOperationException("Unknown enum type.");
    }
}

0

หากมีคนต้องการ IsSet ทั่วไป (สร้างขึ้นจากกล่องได้ทันทีสามารถปรับปรุงได้) และหรือสตริงการแปลง Enum onfly (ซึ่งใช้ EnumConstraint ที่แสดงด้านล่าง):

  public class TestClass
  { }

  public struct TestStruct
  { }

  public enum TestEnum
  {
    e1,    
    e2,
    e3
  }

  public static class TestEnumConstraintExtenssion
  {

    public static bool IsSet<TEnum>(this TEnum _this, TEnum flag)
      where TEnum : struct
    {
      return (((uint)Convert.ChangeType(_this, typeof(uint))) & ((uint)Convert.ChangeType(flag, typeof(uint)))) == ((uint)Convert.ChangeType(flag, typeof(uint)));
    }

    //public static TestClass ToTestClass(this string _this)
    //{
    //  // #generates compile error  (so no missuse)
    //  return EnumConstraint.TryParse<TestClass>(_this);
    //}

    //public static TestStruct ToTestStruct(this string _this)
    //{
    //  // #generates compile error  (so no missuse)
    //  return EnumConstraint.TryParse<TestStruct>(_this);
    //}

    public static TestEnum ToTestEnum(this string _this)
    {
      // #enum type works just fine (coding constraint to Enum type)
      return EnumConstraint.TryParse<TestEnum>(_this);
    }

    public static void TestAll()
    {
      TestEnum t1 = "e3".ToTestEnum();
      TestEnum t2 = "e2".ToTestEnum();
      TestEnum t3 = "non existing".ToTestEnum(); // default(TestEnum) for non existing 

      bool b1 = t3.IsSet(TestEnum.e1); // you can ommit type
      bool b2 = t3.IsSet<TestEnum>(TestEnum.e2); // you can specify explicite type

      TestStruct t;
      // #generates compile error (so no missuse)
      //bool b3 = t.IsSet<TestEnum>(TestEnum.e1);

    }

  }

หากใครบางคนยังต้องการตัวอย่างที่ร้อนแรงในการสร้างข้อ จำกัด การเข้ารหัส Enum:

using System;

/// <summary>
/// would be same as EnumConstraint_T&lt;Enum>Parse&lt;EnumType>("Normal"),
/// but writen like this it abuses constrain inheritence on System.Enum.
/// </summary>
public class EnumConstraint : EnumConstraint_T<Enum>
{

}

/// <summary>
/// provides ability to constrain TEnum to System.Enum abusing constrain inheritence
/// </summary>
/// <typeparam name="TClass">should be System.Enum</typeparam>
public abstract class EnumConstraint_T<TClass>
  where TClass : class
{

  public static TEnum Parse<TEnum>(string value)
    where TEnum : TClass
  {
    return (TEnum)Enum.Parse(typeof(TEnum), value);
  }

  public static bool TryParse<TEnum>(string value, out TEnum evalue)
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {
    evalue = default(TEnum);
    return Enum.TryParse<TEnum>(value, out evalue);
  }

  public static TEnum TryParse<TEnum>(string value, TEnum defaultValue = default(TEnum))
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {    
    Enum.TryParse<TEnum>(value, out defaultValue);
    return defaultValue;
  }

  public static TEnum Parse<TEnum>(string value, TEnum defaultValue = default(TEnum))
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {
    TEnum result;
    if (Enum.TryParse<TEnum>(value, out result))
      return result;
    return defaultValue;
  }

  public static TEnum Parse<TEnum>(ushort value)
  {
    return (TEnum)(object)value;
  }

  public static sbyte to_i1<TEnum>(TEnum value)
  {
    return (sbyte)(object)Convert.ChangeType(value, typeof(sbyte));
  }

  public static byte to_u1<TEnum>(TEnum value)
  {
    return (byte)(object)Convert.ChangeType(value, typeof(byte));
  }

  public static short to_i2<TEnum>(TEnum value)
  {
    return (short)(object)Convert.ChangeType(value, typeof(short));
  }

  public static ushort to_u2<TEnum>(TEnum value)
  {
    return (ushort)(object)Convert.ChangeType(value, typeof(ushort));
  }

  public static int to_i4<TEnum>(TEnum value)
  {
    return (int)(object)Convert.ChangeType(value, typeof(int));
  }

  public static uint to_u4<TEnum>(TEnum value)
  {
    return (uint)(object)Convert.ChangeType(value, typeof(uint));
  }

}

หวังว่านี่จะช่วยใครสักคน


0

ฉันแค่ต้องการเพิ่ม Enum เป็นข้อ จำกัด ทั่วไป

ขณะนี้เป็นเพียงวิธีการช่วยเหลือเล็ก ๆ โดยใช้ ExtraConstraintsค่าใช้จ่ายมากเกินไปสำหรับฉัน

ฉันตัดสินใจที่จะเพียงแค่สร้างstructข้อ จำกัด IsEnumและเพิ่มการตรวจสอบรันไทม์สำหรับ สำหรับการแปลงตัวแปรจาก T เป็น Enum ฉันโยนมันเป็นวัตถุก่อน

    public static Converter<T, string> CreateConverter<T>() where T : struct
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("Given Type is not an Enum");
        return new Converter<T, string>(x => ((Enum)(object)x).GetEnumDescription());
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.