Anyone know a good workaround for the lack of an enum generic constraint?

后端 未结 12 1293
情书的邮戳
情书的邮戳 2020-11-22 15:37

What I want to do is something like this: I have enums with combined flagged values.

public static class EnumExtension
{
    public static bool IsSet

        
相关标签:
12条回答
  • 2020-11-22 15:45

    As of C# 7.3, there is now a built-in way to add enum constraints:

    public class UsingEnum<T> where T : System.Enum { }
    

    source: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint

    0 讨论(0)
  • 2020-11-22 15:45

    As of C# 7.3, you can use the Enum constraint on generic types:

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

    If you want to use a Nullable enum, you must leave the orginial struct constraint:

    public static TEnum? TryParse<TEnum>(string value) where TEnum : struct, Enum
    {
        if( Enum.TryParse(value, out TEnum res) )
            return res;
        else
            return null;
    }
    
    0 讨论(0)
  • 2020-11-22 15:47

    Actually, it is possible, with an ugly trick. However, it cannot be used for extension methods.

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

    If you want to, you can give Enums<Temp> a private constructor and a public nested abstract inherited class with Temp as Enum, to prevent inherited versions for non-enums.

    0 讨论(0)
  • 2020-11-22 15:50

    Using your original code, inside the method you can also use reflection to test that T is an 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;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 15:50

    if someone needs generic IsSet (created out of box on fly could be improved on), and or string to Enum onfly conversion (which uses EnumConstraint presented below):

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

    If someone still needs example hot to create Enum coding constraint:

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

    hope this helps someone.

    0 讨论(0)
  • 2020-11-22 15:53

    Here's some code that I just did up that seems to work like you want without having to do anything too crazy. It's not restricted to only enums set as Flags, but there could always be a check put in if need be.

    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 讨论(0)
提交回复
热议问题