【问题标题】:Anyone know a good workaround for the lack of an enum generic constraint?任何人都知道缺少枚举通用约束的好方法吗?
【发布时间】:2008-08-10 17:14:10
【问题描述】:

我想做的是这样的:我有带有组合标记值的枚举。

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# 的泛型 where 约束没有枚举限制,只有类和结构。 C# 不将枚举视为结构(即使它们是值类型),因此我无法添加这样的扩展类型。

有人知道解决方法吗?

【问题讨论】:

  • Keith:下载 UnconstrainedMelody 的 0.0.0.2 版 - 我已经实现了 HasAll 和 HasAny。享受吧。
  • “C# 不将枚举视为结构”是什么意思?您可以使用枚举类型作为类型参数,限制为 struct 就好了。
  • 在这里查看这篇文章:codeproject.com/KB/cs/ExtendEnum.aspx'IsValidEnumValue' 或 'IsFlagsEnumDefined' 方法可能是您问题的答案。
  • 投票给这个uservoice idea,如果你希望有一天看到它内置在.net 中。
  • C# 7.3 引入枚举约束。

标签: c# .net enums flags


【解决方案1】:

编辑:现在在 UnconstrainedMelody 的 0.0.0.2 版本中生效。

(根据我的blog post about enum constraints 的要求。为了独立的答案,我在下面包含了基本事实。)

最好的解决办法是等我把它加入UnconstrainedMelody1。这是一个库,它采用带有“假”约束的 C# 代码,例如

where T : struct, IEnumConstraint

把它变成

where T : struct, System.Enum

通过后期构建步骤。

编写IsSet 应该不会太难...尽管同时满足基于Int64UInt64 的标志可能是棘手的部分。 (我闻到了一些辅助方法的出现,基本上允许我将任何标志枚举视为具有UInt64 的基本类型。)

如果你打电话,你希望行为是什么

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

?它是否应该检查 all 指定的标志是否已设置?这将是我的期望。

今晚我会在回家的路上尝试这样做...我希望快速了解有用的枚举方法,以使库快速达到可用标准,然后放松一下。

编辑:顺便说一下,我不确定 IsSet 是不是一个名字。选项:

  • 包括
  • 包含
  • HasFlag(或 HasFlags)
  • IsSet(当然是一个选项)

欢迎提出想法。我敢肯定,无论如何,一切都将是一成不变的……


1 或者作为补丁提交,当然...

【讨论】:

  • 或者实际上更简单的 HasAny() 和 HasAll()
  • 是的,我同意这样更好。 colors.HasAny(Colors.Red | Colors.Blue) 看起来是非常易读的代码。 =)
  • 是的,我也喜欢 HasAny 和 HasAll。会去的。
  • 从 C# 7.3(2018 年 5 月发布)开始,可以使用约束 where T : System.Enum。这已经写在线程的其他地方了;只是想我会在这里重复一遍。
【解决方案2】:

从 C# 7.3 开始,现在有一种内置方法可以添加枚举约束:

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

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

【讨论】:

    【解决方案3】:

    Darren,如果类型是特定的枚举,这将起作用 - 要使一般枚举起作用,您必须将它们转换为 int(或更可能是 uint)来进行布尔数学运算:

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

    【讨论】:

    • 如果你有一个荒谬的标志,你可以在参数上调用 GetTypeCode() 和 Convert.ToUint64()
    • 太棒了,'Enum` 和 Convert.ToUInt32 的组合我在其他任何地方都没有找到。 AFAIK,它是唯一一个也适用于 VB 的体面的 Pre-Net-4 解决方案。顺便说一句,如果matchTo 可能有多个标志位,则将!= 0 替换为== Convert.ToUInt32(matchTo)
    • 请注意,与枚举一起使用的Convert.ToUInt32 将使用Convert.ToUInt32(object) 重载,这意味着CLR 将首先将这些值装箱,然后再传递给ToUInt32 方法。在大多数情况下,这无关紧要,但很高兴知道如果您使用这样的东西每秒解析数百万个枚举,您会让 GC 相当忙碌。
    【解决方案4】:

    实际上,这是可能的,但有一个丑陋的把戏。 但是,它不能用于扩展方法。

    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&lt;Temp&gt;一个私有构造函数和一个公共嵌套抽象继承类,Temp作为Enum,以防止非枚举的继承版本。

    【讨论】:

      【解决方案5】:

      从 C# 7.3 开始,您可以对泛型类型使用 Enum 约束:

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

      如果要使用 Nullable 枚举,则必须保留原始结构约束:

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

      【讨论】:

        【解决方案6】:

        您可以使用 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】:

          这没有回答最初的问题,但现在 .NET 4 中有一个名为 Enum.HasFlag 的方法,它可以执行您在示例中尝试执行的操作

          【讨论】:

          • 赞成,因为在这一点上,大多数人都应该使用 .NET 4(或更高版本),因此他们应该使用这种方法,而不是试图将其破解。
          • 赞成。然而,他们的解决方案使用了参数flag 的装箱。 .NET 4.0 现在已经 5 岁了。
          【解决方案8】:

          我这样做的方式是放置一个结构约束,然后在运行时检查 T 是否是一个枚举。这并不能完全消除问题,但确实会有所减少

          【讨论】:

          • where T : struct, IComparable, IFormattable, IConvertible -- 这是最接近枚举的:)
          【解决方案9】:

          使用您的原始代码,在方法内部您还可以使用反射来测试 T 是一个枚举:

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

          【讨论】:

          • 谢谢,但这会将编译时问题(where 约束)转变为运行时问题(您的例外)。此外,您仍然需要将输入转换为整数,然后才能对它们进行任何操作。
          【解决方案10】:

          这是我刚刚编写的一些代码,它似乎可以按照您的意愿工作,而无需做任何太疯狂的事情。它不仅限于设置为标志的枚举,但如果需要,总可以进行检查。

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

          【讨论】:

            【解决方案11】:

            如果有人需要通用 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);
            
                }
            
              }
            

            如果有人仍然需要 example hot 来创建 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));
              }
            
            }
            

            希望这对某人有所帮助。

            【讨论】:

              【解决方案12】:

              我只是想将 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());
                  }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-06-10
                • 1970-01-01
                • 2011-04-25
                • 2013-01-26
                相关资源
                最近更新 更多