C#中的枚举类型约束

162

可能是重复问题:
有人知道缺少枚举泛型约束的好方法吗?

C#不允许在Enum上设置类型约束的原因是什么?我相信这背后肯定有一种方法,但我想了解为什么不可能。

下面是我想要做的事情(理论上)。

public static T GetEnum<T>(this string description) where T : Enum
{
...
}

请参见https://dev59.com/FHM_5IYBdhLWcg3wXyDZ。 - Taylor Leese
6
对于任何想知道这是在 C# 7.3 中发布的人:https://learn.microsoft.com/en-us/visualstudio/releasenotes/vs2017-Preview-relnotes#csharp。 - nawfal
使用where T : struct, Enum作为限制条件 - VBWebProfi
使用where T : struct, Enum作为限制条件 - undefined
6个回答

151

实际上,这是可能的,但需要使用一个不太雅观的技巧。 然而,它不能用于扩展方法。

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,以防止非枚举类型的继承版本。
请注意,你不能使用这个技巧来创建扩展方法。

1
这对委托有效,但它不会排除“Delegate”本身。 - SLaks
2
更加优雅的方式:https://dev59.com/ZnM_5IYBdhLWcg3wjj0p#5995541 - Andrei Sedoi
4
那并不妨碍你在编译时传递 intDateTime - SLaks
25
这真的很棒,而且在编译时检查可能的情况下,运行时检查永远不会比编译时检查更好。@bsnote - cwharris
2
@hypehuman: 为了防止您使用 Enums<SomeOtherType>(除了objectValueType),这是唯一符合: class且具有符合: struct的子类型的类型。 - SLaks
显示剩余3条评论

96

这是一个偶尔被请求的功能。

正如我经常指出的那样,所有的功能在有人设计、规格化、实现、测试、文档化和发布之前都是未实现的。到目前为止,没有人为此做过这些工作。并没有什么特别不寻常的原因;我们有很多其他事情要做,预算有限,而且这个功能从未超过语言设计团队的“这会很好吧?”讨论。

CLR不支持它,所以为了使其工作,除了语言工作外,我们还需要进行运行时工作。(请参见答案评论)

我可以看到有一些不错的使用案例,但没有一个是如此引人注目,以至于我们会为此工作,而不是处理更频繁请求或具有更强大和广泛使用案例的数百个其他功能。(如果我们要搞乱这段代码,我个人会将委托约束置于枚举约束之上。)


19
根据我对规范第166页的理解,CLR确实支持它。 - SLaks
42
以前我不知道这篇文章。在另一个问题中,该博客文章已经被链接:缺失的泛型(Parse方法)案例。它表明这个方法在IL中可用的。我记得以前尝试过并且有效,尽管那可能是针对Delegate的。如果这意味着可以从C# 5中移除限制,我很乐意进行更多(甚至是大量)的实验。(我假设C# 4现在已经被锁定了。) - Jon Skeet
14
我对你说CLR不支持枚举约束感到困惑,因为根据CLI ECMA文档的第166/167页,它确实支持枚举约束(http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf),除非CLR不符合CLI规范。Jon Skeet在“无限旋律”(http://code.google.com/p/unconstrained-melody/)中的工作也证实了这一点。请问您需要什么帮助吗? - RCIX
45
那么,你应该问自己哪种可能性更大?是规范出了问题,Jon的实现是虚构的,还是我对一年前与CLR团队的谈话记忆有误或理解有误? - Eric Lippert
13
值得一提的是,C# 7.3增加了枚举约束。 - Sören Kuklau
显示剩余23条评论

17
public static T GetEnum<T>(this string description) where T : struct
{
    return (T)Enum.Parse(typeof(T), description);
}

这是否回答了你的问题?


18
不行!问题是关于将类型参数T缩小为Enum。'struct'太宽泛了,包括int、float、double、DateTime和其他用户可以定义为结构体的类型。 - dmihailescu
2
您可以在运行时进行检查,如果喜欢的话。我使用了:!typeof(T).IsEnum - Fabio Milheiro
3
没错!这正是我需要来编写通用方法的。如果可以直接限定为枚举类型会更好,但我想在大多数情况下这并非必须。 - Mike Fuchs
我在第一行做了这样的事情:if (!typeof(T).IsEnum) { throw new InvalidOperationException("MethodBlah需要一个枚举!"); } - BrainSlugs83

8

使用ExtraConstraints进行IL编织。

您的代码

public static T GetEnum<[EnumConstraint] T>(this string description)
{
    ...
}

编译哪些内容

public static T GetEnum<T>(this string description) where T : Enum
{
    ...
}

1
Go @Simon,你的链接 https://github.com/SimonCropp/ExtraConstraints 已经失效。看了一下历史记录,它应该指向 https://github.com/Fody/ExtraConstraints 才对吧?最后一次提交是一个月前由 SimonCropp 完成的... - Wai Ha Lee
2
@WaiHaLee 谢谢。链接已修复。 - Simon

4

这是VB.NET版本的SLaks的优秀丑陋技巧,使用Imports作为“typedef”:

(类型推断按预期工作,但无法获取扩展方法。)
'Base namespace "EnumConstraint"
Imports Enums = EnumConstraint.Enums(Of System.Enum)

Public NotInheritable Class Enums(Of Temp As Class)
Private Sub New()
End Sub

Public Shared Function Parse(Of TEnum As {Temp, Structure})(ByVal Name As String) As TEnum
    Return DirectCast([Enum].Parse(GetType(TEnum), Name), TEnum)
End Function

Public Shared Function IsDefined(Of TEnum As {Temp, Structure})(ByVal Value As TEnum) As Boolean
    Return [Enum].IsDefined(GetType(TEnum), Value)
End Function

Public Shared Function HasFlags(Of TEnum As {Temp, Structure})(ByVal Value As TEnum, ByVal Flags As TEnum) As Boolean
    Dim flags64 As Long = Convert.ToInt64(Flags)
    Return (Convert.ToInt64(Value) And flags64) = flags64
End Function

End Class

Module Module1

Sub Main()

    Dim k = Enums.Parse(Of DateTimeKind)("Local")
    Console.WriteLine("{0} = {1}", k, CInt(k))
    Console.WriteLine("IsDefined({0}) = {1}", k, Enums.IsDefined(k))
    k = DirectCast(k * 2, DateTimeKind)
    Console.WriteLine("IsDefined({0}) = {1}", k, Enums.IsDefined(k))

    Console.WriteLine(" {0} same as {1} Or {2}: {3} ", IO.FileAccess.ReadWrite, IO.FileAccess.Read, IO.FileAccess.Write, _
                      Enums.HasFlags(IO.FileAccess.ReadWrite, IO.FileAccess.Read Or IO.FileAccess.Write))

    ' These fail to compile as expected:
    'Console.WriteLine(Enums.HasFlags(IO.FileAccess.ReadWrite, IO.FileOptions.RandomAccess))
    'Console.WriteLine(Enums.HasFlags(Of IO.FileAccess)(IO.FileAccess.ReadWrite, IO.FileOptions.RandomAccess))

    If Debugger.IsAttached Then _
        Console.ReadLine()
End Sub

End Module

输出:

Local = 2
IsDefined(Local) = True
IsDefined(4) = False
 ReadWrite same as Read Or Write: True

2
这里有一个奇怪的事情,就是有相当数量的通用枚举方法需要编写,它们的实现取决于枚举的“基础”类型。
通过枚举的“基础”类型 E,我指的是在调用 System.Type.GetTypeCode(System.Type) 获取 E 类型的 System.TypeCode 枚举成员的名称相同的位于 System 命名空间中的类型。如果该枚举是在 C# 中声明的,则它与其“继承”的类型相同(我不确定规范中是否正式称其为什么)。例如,下面的 Animal 枚举的基础类型是 System.Byte
public enum Animal : byte
{
    Moose,
    Squirrel
}

可以使用switch语句编写此类方法,但这样做确实很丑陋。您无法获得强类型参数或返回类型,其类型是枚举的基础类型。您必须重复元数据查找或进行一些缓存(例如,在包含该方法的泛型类型的静态构造函数中)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接