是否有针对“特殊类”的泛型类型约束的解决方法? C# 3.0 中的枚举?

发布于 2024-08-04 07:07:50 字数 1561 浏览 11 评论 0原文

更新:请参阅此问题的底部以获取 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 代码,这不会限制可用于泛型类型约束的内容(请参阅下面我的答案中的代码。)

Update: See the bottom of this question for a C# workaround.

Hi there,

Consider the following extension method:

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

This will, as you may know, throw an error at compile-time, since a class is not normally allowed to inherit from System.Enum. The problem is that any enumeration specified using the enum keyword does in fact inherit from System.Enum, so the above code would be the ideal way to limit an extension method to enumerations only.

Now the obvious work-around here is to use Enum instead of T, but then you lose the benefits of generic types:

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

The above code would throw a compile-time error using generic types, while it can only throw a runtime error using the Enum type (if I implement it to do so.)

Are there any compiler options that can be used to turn off the constraint check, or is there some other nifty way to do this?

Before it is suggested, I would like to say that I will not be using where T : struct or some such, since then you'd be able to do weird stuff like 123.HasFlags(456).

I'm stumped as to why this error exists at all... It's the same problem you'd get using where T : System.Object, but for that you have where T : class... Why is there no where T : enum?

C# workaround

Jon Skeet has started work on a library that compiles classes with a constraint to an IEnumConstraint, which is then replaced with System.Enum post-build. This is, I believe, the closest one can get to working around this issue at this time.

See:

If this workaround is unfeasible, you will have to write your library as C++/CLI code, which does not limit what can be used for generic type constraints (see the code in my answer below.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

三生路 2024-08-11 07:07:50

编辑:现在可以通过 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();
    }
    // ...
}

失去对“枚举性”的编译时检查,但保留检查您在两个地方都使用相同的类型。当然,它也有检查的执行时间损失。您可以通过使用泛型嵌套类型来实现在静态构造函数中抛出异常,从而避免第一次调用后的执行时间损失:

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

EDIT: A library is now available supporting this via ildasm/ilasm: UnconstrainedMelody.


Members of the C# team have previously said they'd like to be able to support where T : Enum and where T : Delegate, but that it's never been a high enough priority. (I'm not sure what the reasoning is for having the restriction in the first place, admittedly...)

The most practical workaround in C# is:

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

That loses compile-time checking for the "enum-ness" but keeps the check that you're using the same type in both places. It has the execution-time penalty of the check as well, of course. You can avoid that execution-time penalty after the first call by using a generic nested type for the implementation which throws the exception in a static constructor:

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

As Greco mentions, you could write the method in C++/CLI and then reference the class library from C# as another option.

倾城泪 2024-08-11 07:07:50

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

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

Actually, it is possible, with an ugly trick.
However, it cannot be used for extension methods.

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")

If you want to, you can give Enums<Temp> a private constructor and a public nested abstract inherited class with Temp as Enum, to prevent inherited versions for non-enums.

旧梦荧光笔 2024-08-11 07:07:50

我无法抗拒尝试 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);
        }
    }
}

I couldn't resist having a go at the C++ work-around, and since I got it to work I figured I'd share it with the rest of ya!

Here's the C++ code (my C++ is very rusty so please point out any errors, in particular how the arguments are defined):

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

And the C# code for testing (console application):

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);
        }
    }
}
错爱 2024-08-11 07:07:50

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

You can achieve this using IL Weaving and ExtraConstraints

Allows you to write this code

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

What gets compiled

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
} 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文