在C#3.0中是否存在针对“特殊类”Enum的泛型类型约束的解决方法?

时间:2009-09-10 08:31:30

标签: c# .net generics extension-methods

  

更新:有关C#解决方法,请参阅此问题的底部。

你好,

考虑以下扩展方法:

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
}

正如您所知,这将在编译时抛出错误,因为通常不允许类从System.Enum继承。问题是使用enum关键字指定的任何枚举实际上都是从System.Enum继承的,因此上述代码是将扩展方法限制为枚举的理想方法。

现在显而易见的解决方法是使用Enum代替T,但是你失去了泛型类型的好处:

MyEnum e;
e.HasFlags(MyOtherEnum.DoFunkyStuff);

上面的代码会使用泛型类型抛出编译时错误,而它只能使用Enum类型抛出运行时错误(如果我实现它就会这样做。)

是否有可用于关闭约束检查的编译器选项,还是有其他一些很好的方法可以做到这一点?

在提出建议之前,我想说我不会使用where T : struct或其他类似内容,因为那时你可以做123.HasFlags(456)之类的奇怪内容。

我很难理解为什么会出现这个错误......这与使用where T : System.Object时遇到的问题相同,但为此你有where T : class ...为什么没有where T : enum

  

C#workaround

     

Jon Skeet已经开始研究一个库,该库将具有约束的类编译为IEnumConstraint,然后将其替换为System.Enum后构建。我相信,这是目前最接近解决这个问题的人。

     

请参阅:

           

如果这种解决方法不可行,则必须将库编写为C ++ / CLI代码,这不会限制可用于泛型类型约束的内容(请参阅下面的答案中的代码。)

4 个答案:

答案 0 :(得分:10)

编辑:现在可以通过ildasm / ilasm支持此库:UnconstrainedMelody


C#团队的成员之前已经表示他们喜欢能够支持where T : Enumwhere T : Delegate,但它从未有过足够高的优先级。 (我不确定首先有限制的原因是什么,不可否认......)

C#中最实用的解决方法是:

public static bool HasFlags<T>(this T value, T flags) where T : struct
{
    if (!(value is Enum))
    {
        throw new ArgumentException();
    }
    // ...
}

失去编译时检查“enum-ness”,但检查你在两个地方都使用相同的类型。当然,它也有检查的执行时间惩罚。在第一次调用之后,您可以通过在实现中使用通用嵌套类型来避免执行时间损失,该实现会在静态构造函数中抛出异常:

public static bool HasFlags<T>(this T value, T flags) where T : struct
{
    if (!(value is Enum))
    {
        throw new ArgumentException();
    }
    return EnumHelper<T>.HasFlags(value, flags);
}

private class EnumHelper<T> where T : struct
{
    static EnumHelper()
    {
        if (!typeof(Enum).IsAssignableFrom(typeof(T))
        {
            throw new InvalidOperationException(); // Or something similar
        }
    }

    internal static HasFlags(T value, T flags)
    {
        ...
    }
}

正如Greco所提到的,您可以在C ++ / CLI中编写该方法,然后从C#引用类库作为另一种选择。

答案 1 :(得分:3)

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

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.Parse<DateTimeKind>("Local")

如果您愿意,可以将Enums<Temp>私有构造函数和公共嵌套抽象继承类Temp作为Enum,以防止非枚举的继承版本。

答案 2 :(得分:2)

我无法抗拒参加C ++的解决方案,而且自从我开始工作以来,我想我会和其余的人一起分享它!

这是C ++代码(我的C ++非常生疏,所以请指出任何错误,特别是如何定义参数):

#include "stdafx.h"

using namespace System;
using namespace System::Runtime::CompilerServices;

namespace Blixt
{
namespace Utilities
{
    [Extension]
    public ref class EnumUtility abstract sealed
    {
    public:
        generic <typename T> where T : value class, Enum
        [Extension]
        static bool HasFlags(T value, T flags)
        {
            __int64 mask = Convert::ToInt64(flags);
            return (Convert::ToInt64(value) & mask) == mask;
        }
    };
}
}

用于测试的C#代码(控制台应用程序):

using System;
using Blixt.Utilities;

namespace Blixt.Playground
{
    [Flags]
    public enum Colors : byte
    {
        Black = 0,
        Red = 1,
        Green = 2,
        Blue = 4
    }

    [Flags]
    public enum Tastes : byte
    {
        Nothing = 0,
        Sour = 1,
        Sweet = 2,
        Bitter = 4,
        Salty = 8
    }

    class Program
    {
        static void Main(string[] args)
        {
            Colors c = Colors.Blue | Colors.Red;
            Console.WriteLine("Green and blue? {0}", c.HasFlags(Colors.Green | Colors.Red));
            Console.WriteLine("Blue?           {0}", c.HasFlags(Colors.Blue));
            Console.WriteLine("Green?          {0}", c.HasFlags(Colors.Green));
            Console.WriteLine("Red and blue?   {0}", c.HasFlags(Colors.Red | Colors.Blue));

            // Compilation error:
            //Console.WriteLine("Sour?           {0}", c.HasFlags(Tastes.Sour));

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey(true);
        }
    }
}

答案 3 :(得分:1)

您可以使用IL Weaving和ExtraConstraints

来实现这一目标

允许您编写此代码

public static bool HasFlags<[EnumConstraint] T>(this T value, T flags)
{
    // ...
} 

汇编了什么

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
}