创建泛型方法将 T 约束为枚举类型

创建泛型方法将 T 约束为枚举类型

技术背景

在开发过程中,我们有时需要创建泛型方法,并将泛型类型参数 T 约束为枚举类型。然而,在早期的 C# 版本中,直接对泛型类型参数进行枚举约束并不容易实现。随着 C# 版本的不断更新,对枚举约束的支持也在不断完善。

实现步骤

C# ≥ 7.3

从 C# 7.3 开始(可在 Visual Studio 2017 ≥ v15.7 中使用),可以直接使用 Enum 约束。

示例代码

1
2
3
4
5
6
public static TEnum Parse<TEnum>(string value)
where TEnum : struct, Enum
{
// 这里可以添加具体的解析逻辑
return (TEnum)Enum.Parse(typeof(TEnum), value);
}

使用方法

1
2
3
4
5
private enum MyEnum { Yes, No, Okay }
static void Main(string[] args)
{
var result = Parse<MyEnum>("Yes");
}

C# ≤ 7.2

在 C# 7.2 及更早版本中,可以通过滥用约束继承来实现真正的编译器强制枚举约束。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class EnumClassUtils<TClass>
where TClass : class
{
public static TEnum Parse<TEnum>(string value)
where TEnum : struct, TClass
{
return (TEnum)Enum.Parse(typeof(TEnum), value);
}
}

public class EnumUtils : EnumClassUtils<Enum>
{
}

使用方法

1
2
3
4
5
private enum SomeEnum { Value1, Value2 }
static void Main(string[] args)
{
var result = EnumUtils.Parse<SomeEnum>("Value1");
}

使用 MSIL 实现

可以编写 MSIL 代码来生成一个具有枚举约束的泛型方法。

MSIL 代码示例

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
// license: http://www.apache.org/licenses/LICENSE-2.0.html
.assembly MyThing{}
.class public abstract sealed MyThing.Thing
extends [mscorlib]System.Object
{
.method public static !!T GetEnumFromString<valuetype .ctor ([mscorlib]System.Enum) T>(string strValue,
!!T defaultValue) cil managed
{
.maxstack 2
.locals init ([0] !!T temp,
[1] !!T return_value,
[2] class [mscorlib]System.Collections.IEnumerator enumerator,
[3] class [mscorlib]System.IDisposable disposer)
// if(string.IsNullOrEmpty(strValue)) return defaultValue;
ldarg strValue
call bool [mscorlib]System.String::IsNullOrEmpty(string)
brfalse.s HASVALUE
br RETURNDEF // return default it empty

// foreach (T item in Enum.GetValues(typeof(T)))
HASVALUE:
// Enum.GetValues.GetEnumerator()
ldtoken !!T
call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator()
stloc enumerator
.try
{
CONDITION:
ldloc enumerator
callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
brfalse.s LEAVE

STATEMENTS:
// T item = (T)Enumerator.Current
ldloc enumerator
callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
unbox.any !!T
stloc temp
ldloca.s temp
constrained. !!T

// if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
callvirt instance string [mscorlib]System.Object::ToString()
callvirt instance string [mscorlib]System.String::ToLower()
ldarg strValue
callvirt instance string [mscorlib]System.String::Trim()
callvirt instance string [mscorlib]System.String::ToLower()
callvirt instance bool [mscorlib]System.String::Equals(string)
brfalse.s CONDITION
ldloc temp
stloc return_value
leave.s RETURNVAL

LEAVE:
leave.s RETURNDEF
}
finally
{
// ArrayList's Enumerator may or may not inherit from IDisposable
ldloc enumerator
isinst [mscorlib]System.IDisposable
stloc.s disposer
ldloc.s disposer
ldnull
ceq
brtrue.s LEAVEFINALLY
ldloc.s disposer
callvirt instance void [mscorlib]System.IDisposable::Dispose()
LEAVEFINALLY:
endfinally
}

RETURNDEF:
ldarg defaultValue
stloc return_value

RETURNVAL:
ldloc return_value
ret
}
}

C# 调用 MSIL 生成的方法

1
2
3
4
5
6
using MyThing;
private enum MyEnum { Yes, No, Okay }
static void Main(string[] args)
{
var result = Thing.GetEnumFromString("Yes", MyEnum.No);
}

使用 F# 实现

F# 也可以实现泛型枚举约束。

示例代码

1
2
3
4
5
6
7
type MyThing =
static member GetEnumFromString<'T when 'T :> Enum> str defaultValue: 'T =
let str = if isNull str then String.Empty else str
Enum.GetValues(typedefof<'T>)
|> Seq.cast<_>
|> Seq.tryFind(fun v -> String.Compare(v.ToString(), str.Trim(), true) = 0)
|> function Some x -> x | None -> defaultValue

C# 调用 F# 方法

1
2
3
4
5
using System;
static void Main(string[] args)
{
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", StringComparison.Ordinal);
}

使用 Fody 和 ExtraConstraints.Fody

通过 Fody 的 Add-In ExtraConstraints.Fody 可以很简单地实现枚举约束。

示例代码

1
2
3
4
public void MethodWithEnumConstraint<[EnumConstraint] T>()
{
// 这里可以添加具体的方法逻辑
}

核心代码

C# 7.3 及以上版本的核心代码

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

C# 7.2 及以下版本的核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class EnumClassUtils<TClass>
where TClass : class
{
public static TEnum Parse<TEnum>(string value)
where TEnum : struct, TClass
{
return (TEnum)Enum.Parse(typeof(TEnum), value);
}
}

public class EnumUtils : EnumClassUtils<Enum>
{
}

最佳实践

  • 使用最新版本的 C#:如果项目允许,尽量使用 C# 7.3 及以上版本,因为它直接支持枚举约束,代码更简洁。
  • 添加错误处理:在解析枚举时,应该添加适当的错误处理逻辑,以避免程序崩溃。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
public static TEnum Parse<TEnum>(string value)
where TEnum : struct, Enum
{
try
{
return (TEnum)Enum.Parse(typeof(TEnum), value);
}
catch (ArgumentException)
{
// 处理解析失败的情况
return default(TEnum);
}
}
  • 使用扩展方法:可以将枚举解析方法封装为扩展方法,方便在项目中使用。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static class EnumExtensions
{
public static TEnum ParseEnum<TEnum>(this string value)
where TEnum : struct, Enum
{
try
{
return (TEnum)Enum.Parse(typeof(TEnum), value);
}
catch (ArgumentException)
{
return default(TEnum);
}
}
}

使用方法:

1
2
3
4
5
private enum MyEnum { Yes, No, Okay }
static void Main(string[] args)
{
var result = "Yes".ParseEnum<MyEnum>();
}

常见问题

编译错误:“Cannot use ‘System.Array’, ‘System.Delegate’, ‘System.Enum’, ‘System.ValueType’, ‘object’ as type parameter constraint”

这可能是因为使用的 ReSharper 版本不支持 C# 7.3。可以暂时关闭 ReSharper,方法是在“Tools -> Options -> ReSharper Ultimate -> General”中关闭。

运行时错误:“T must be an enumerated type”

这通常是因为泛型类型参数 T 不是枚举类型。在使用泛型方法时,要确保传入的类型是枚举类型。

无法找到 Fody 和 ExtraConstraints.Fody

需要在项目中添加 FodyExtraConstraints.Fody 的 NuGet 包。可以通过 Visual Studio 的 NuGet 包管理器来添加。


创建泛型方法将 T 约束为枚举类型
https://119291.xyz/posts/create-generic-method-constraining-t-to-an-enum/
作者
ww
发布于
2025年7月15日
许可协议