关于c#:任何人都知道缺少枚举通用约束的好方法吗?

关于c#:任何人都知道缺少枚举通用约束的好方法吗?

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

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

1
2
3
4
5
6
7
8
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;
    }
}

所以我可以这样做:

1
2
3
4
MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

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

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

有人知道解决方法吗?


编辑:现在这是0.0.0.2版的UntrainedMelody。

(根据我博客上关于枚举约束的要求)。为了一个独立的答案,我在下面列出了基本事实。)

最好的解决方案是等待我将其包含在未经培训的日志中1。这是一个使用带有"假"约束的C代码的库,例如

1
where T : struct, IEnumConstraint

把它变成

1
where T : struct, System.Enum

通过后期构建步骤。

IsSet不应该太难…虽然同时满足基于Int64和基于UInt64的旗帜可能是棘手的部分。(我闻到一些辅助方法的味道,基本上允许我将任何标志枚举视为它有一个基本类型的UInt64)。

如果你打电话来的话,你想做什么?

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

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

今晚回家的路上我会尽力的…我希望对有用的枚举方法进行快速的测试,以使库快速达到可用的标准,然后放松一点。

编辑:顺便说一句,我不确定IsSet是什么名字。选项:

  • 包括
  • 包含
  • hasFlag(或hasFlags)
  • ISSET(这当然是一种选择)

思想是受欢迎的。我敢肯定,任何东西都要等一段时间才会变成石头…

或者作为补丁提交,当然……


Darren,如果类型是特定的枚举就可以了-要使一般的枚举工作,您必须将它们强制转换为int(或者更可能是uint)才能进行布尔数学:

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


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

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

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


事实上,这是可能的,用一个丑陋的伎俩。但是,它不能用于扩展方法。

1
2
3
4
5
6
7
8
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作为Enum来防止非枚举的继承版本。


你可以通过IL编织和课外培训来达到这个目的。

允许您编写此代码

1
2
3
4
5
6
7
8
9
public class Sample
{
    public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
    {        
    }
    public void MethodWithEnumConstraint<[EnumConstraint] T>()
    {
    }
}

编译的内容

1
2
3
4
5
6
7
8
9
10
public class Sample
{
    public void MethodWithDelegateConstraint<T>() where T: Delegate
    {
    }

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

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


我这样做的方法是放置一个结构约束,然后在运行时检查t是否是一个枚举。这并不能完全消除这个问题,但它确实在一定程度上减少了这个问题。


从C 7.3开始,可以对泛型类型使用枚举约束:

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

如果要使用可为空的枚举,则必须保留Orginial结构约束:

1
2
3
4
5
6
7
public static TEnum? TryParse<TEnum>(string value) where TEnum : struct, Enum
{
    if( Enum.TryParse(value, out TEnum res) )
        return res;
    else
        return null;
}

这里有一些代码,我刚刚做了,看起来像你想要的工作,而不必做任何太疯狂的事情。它不仅限于设置为标志的枚举,而且如果需要的话,也可能总是有一个签入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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.");
    }
}

使用原始代码,在方法内部还可以使用反射来测试t是否为枚举:

1
2
3
4
5
6
7
8
9
10
11
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;
    }
}

如果有人需要通用的ISset(即即时创建的ISset)和或字符串到枚举on fly转换(使用下面介绍的EnumConstraint):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
  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);

    }

  }

如果有人仍然需要示例hot来创建枚举编码约束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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));
  }

}

希望这能帮助别人。


我只想添加枚举作为通用约束。

虽然这只是一个使用ExtraConstraints的小助手方法,但对我来说开销有点太大。

我决定只创建一个struct约束,并为IsEnum添加一个运行时检查。为了将变量从t转换为枚举,我首先将其转换为对象。

1
2
3
4
5
    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());
    }


推荐阅读