【问题标题】:Is there a workaround for generic type constraint of "special class" Enum in C# 3.0? [duplicate]C# 3.0 中“特殊类”枚举的泛型类型约束是否有解决方法? [复制]
【发布时间】:2009-09-10 08:31:29
【问题描述】:

更新:请参阅此问题的底部以了解 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# 解决方法

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

见:

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

【问题讨论】:

  • 也许除了非常罕见的情况外,在所有情况下都不需要这样的约束。谁知道...如果是这样的话,我会同意他们的:)
  • @Skurmedel:奇怪的是它被明确禁止......换句话说,规范变得更加复杂来限制它。我不明白为什么。也许 Eric Lippert 会解释 :)
  • 是的,我同意 Jon Skeet 的观点,尽管它可能只是被忽略了(他们对约束使用了与类继承相同的继承检查)......但我真的不熟悉使用 C# 的内部工作原理。
  • Eric Lippert 之前发布过关于 C# 中的枚举类型约束的文章:stackoverflow.com/questions/1331739/enum-type-constraints-in-c/…
  • 我明白了。我可以从他的回答中看出,即使有关闭编译器错误的选项,代码仍然无法工作?除非是这种情况,否则我不明白为什么要强制执行此限制(能够显式关闭 CS0702 错误会很好。)

标签: c# .net generics extension-methods


【解决方案1】:

编辑:现在可以通过 ildasm/ilasm 获得支持此功能的库:UnconstrainedMelody。


C# 团队的成员之前曾表示,他们希望能够支持where T : Enum 和where T : Delegate,但优先级从来都不够高。 (我不确定首先有限制的原因是什么,诚然......)

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

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

这会丢失对“枚举”的编译时检查,但会检查您在两个地方使用的类型相同。当然,它也有检查的执行时间惩罚。您可以通过对在静态构造函数中引发异常的实现使用通用嵌套类型来避免第一次调用后的执行时间损失:

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# 引用类库作为另一种选择。

【讨论】:

  • 我认为限制扩展方法的范围很重要,因此由于我的问题中提到的原因,我不会使用where T : struct(123.HasFlags(456) 编译得很好。)我喜欢 Greco 的解决方案,但我不能证明为一种方法创建整个库是合理的。是否可以在编译时设置一个进程将 C++/CLI “注入”到 C# 库中?
  • @Blixt:嗯,你可以研究二进制重写,但它可能会很丑。我想知道开源项目是否会对一堆“可以用委托和枚举做的有用的事情”感兴趣......
  • 好吧,如果有一种“简单”的方法,我只会这样做=) 是的,从我在 Google 上找到的讨论来看,这个问题还不止于此很少见,我肯定会喜欢这样的图书馆。
  • 您可以使用 ILMerge (research.microsoft.com/en-us/people/mbarnett/ilmerge.aspx) 将相当陈旧的 C++/CLI 程序集包含到您的主代码中。
  • @Yngvar:嗯,现在是 codeblog.jonskeet.uk,所以:codeblog.jonskeet.uk/2009/09/10/…
【解决方案2】:

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

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

【讨论】:

【解决方案3】:

我忍不住想尝试一下 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);
        }
    }
}

【讨论】:

    【解决方案4】:

    您可以使用 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
    {
        // ...
    } 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多