任何人都知道缺少枚举泛型约束的好解决方法吗?


89

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

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

有谁知道解决方法?


2
Keith:下载UnconstrainedMelody的0.0.0.2版-我已经实现了HasAll和HasAny。请享用。
乔恩·斯基特

“ C#不会将枚举视为结构”是什么意思?您可以使用枚举类型作为约束的类型参数struct
Timwi's

在此处查看本文:codeproject.com/KB/cs/ExtendEnum.aspx'IsValidEnumValue '或'IsFlagsEnumDefined'方法可能是您问题的答案。
dmihailescu 2011年

1
投票给这个uservoice想法,如果您希望有一天看到它内置在.net中。
Matthieu 2014年

11
C#7.3引入了枚举约束。
Marc Sigrist '18

Answers:


48

编辑:这现在存在于UnconstrainedMelody的0.0.0.2版本中。

(按照我的博客文章中有关枚举约束的要求。为了提供独立答案,我在下面列出了基本事实。)

最好的解决方案是等待我将其包含在UnconstrainedMelody 1中。这是一个使用带有“假”约束的C#代码的库,例如

where T : struct, IEnumConstraint

并变成

where T : struct, System.Enum

通过后期构建步骤。

编写它应该不太困难IsSet...尽管同时满足Int64基于-和UInt64基于-的标志可能是棘手的部分。(我闻到了一些辅助方法的出现,基本上使我可以将任何标志枚举都视为基本类型为UInt64。)

如果您致电,您希望该行为如何?

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

?是否应该检查所有指定标志都已设置?那是我的期望。

我将在今晚回家的路上尝试这样做。我希望对有用的枚举方法进行快速的探讨,以使库快速达到可用的标准,然后再放松一下。

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

  • 包括
  • 包含
  • HasFlag(或HasFlags)
  • IsSet(肯定是一个选项)

欢迎思想。我敢肯定,要把所有东西都固定下来还需要一段时间...


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



1
或者实际上更简单的HasAny()和HasAll()
Keith

1
是的,我同意那会更好。colors.HasAny(Colors.Red | Colors.Blue)看起来很可读。=)
Blixt

1
是的,我也喜欢HasAny和HasAll。会的。
乔恩·斯基特

5
从C#7.3(2018年5月发布)开始,可以使用约束条件where T : System.Enum。这已经在线程的其他地方编写了;只是以为我会在这里重复一遍。
杰普·斯蒂格·尼尔森


16

达伦(Darren),如果类型是特定的枚举,那将起作用-为了使通用枚举起作用,您必须将它们转换为int(或更可能是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,它是唯一也可以在VB中使用的体面的Pre-Net-4解决方案。顺便说一句,如果matchTo可能有多个标志位,则替换!= 0== Convert.ToUInt32(matchTo)
制造商史蒂夫

1
请注意,Convert.ToUInt32与枚举一起使用将使用Convert.ToUInt32(object)重载,这意味着CLR首先将这些值装箱,然后再传递给ToUInt32方法。在大多数情况下,这无关紧要,但是很高兴知道,如果您正在使用类似的方法每秒解析数百万个枚举,则会使GC保持繁忙。
Groo 2014年

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,以防止非枚举的继承版本。


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

6

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

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

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

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已有5年历史了。
2015年

3

我的操作方式是将结构约束放入,然后在运行时检查T是否为枚举。这不能完全消除问题,但可以一定程度地减少问题


7
其中T:struct,IComparable,IFormattable,IConvertible,这是最接近枚举的方法:)
Kit

1

使用原始代码,在该方法内部,您还可以使用反射来测试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;
    }
}

2
谢谢,但这将编译时问题(where约束)变成了运行时问题(您的异常)。同样,您仍然需要先将输入转换为int,然后才能对其进行任何处理。
基思

1

这是我刚刚编写的一些代码,看起来像您想要的那样工作,而不必做任何太疯狂的事情。它不仅限于设置为Flags的枚举,而且如果需要的话,总可以有一个检查。

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());
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.