【.NET】利用 IL 魔法实现随心随意的泛型约束

众所周知,C# 只支持对 基类/接口/class/struct/new() 以及一些 IDE 魔法的约束,比如这样

1
2
3
4
5
6
7
8
9
public static string Test<T>(T value) where T : ITest
{
return value.Test();
}

public interface ITest
{
string Test();
}

但是如果我们想要随心所欲的约束就不行了

1
2
3
4
public static string Test<T>(T value) where T : { string Test(); }
{
return value.Test();
}

最近无聊乱折腾 MSIL,弄出来好多不能跑的魔法,虽然不能跑但是反编译出的 C# 看着很神奇,其中正好就有想看看能不能弄个神奇的泛型出来,于是我胡写了一段代码

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
.assembly _
{
}

.class public Test
{
.method public void .ctor()
{
ldarg.0
call instance void object::.ctor()
ret
}

.method public static void Main()
{
.entrypoint
newobj instance void Test::.ctor()
call string Test::Test<class Test>(!!0)
call void [mscorlib]System.Console::WriteLine(string)
ret
}

.method public static string Test<T>(!!T t)
{
ldarg.s t
callvirt instance string !!T::Test()
ret
}

.method public string Test()
{
ldstr "Call instance string Test::Test()"
ret
}
}

反编译出来是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test
{
public static void Main()
{
Console.WriteLine(Test(new Test()));
}

public unsafe static string Test<T>(T t)
{
return ((T*)t)->Test();
}

public string Test()
{
return "Call instance string Test::Test()";
}
}

这段代码是无法运行的,在 .NET Framework 会直接无返回,而在 Mono 会报错

[ERROR] FATAL UNHANDLED EXCEPTION: System.MissingMethodException: Method not found: string .T_REF.Test()
at Test.Main () [0x00005] in <ddf64a5d94ef4722be4197eb692d9478>:0

于是我就当这是 .NET 泛型的局限性了,后来有群友提醒我说约束会影响运行时,于是我就尝试加上约束

1
2
3
4
5
6
.method public static string Test<(Test) T>(!!T t)
{
ldarg.s t
callvirt instance string !!T::Test()
ret
}

发现真的能跑了(Framework 依然无返回。。。),于是我就看看能不能同时约束两个类型

1
2
3
4
5
6
.method public static string Test<(Test, Test2) T>(!!T t)
{
ldarg.s t
callvirt instance string !!T::Test()
ret
}

Mono 成功输出

Call instance string Test::Test()
Call instance string Test2::Test()

而 Framework 直接运行时约束了。。。

未经处理的异常: System.Security.VerificationException: 方法 Test.Test: 类型参数“Test”与类型参数“T”的约束冲突。
在 Test.Main()

很明显 Mono 给泛型开了洞

随后测试发现,只要约束的类有相关成员就可以正常调用,于是我就利用抽象类做接口

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
84
85
86
87
88
89
90
91
92
93
.assembly _
{
}

.class public Test
{
.field string Test

.method public void .ctor()
{
ldarg.0
ldstr "This is Test"
stfld string Test::Test
ldarg.0
call instance void object::.ctor()
ret
}

.method public static void Main()
{
.entrypoint
.locals init (
class Test test,
class Test2 test2
)
newobj instance void Test::.ctor()
stloc.s test
ldloc.s test
call void Test::Test<class Test>(!!0)
ldloc.s test
call string Test::Test<class Test>(!!0)
call void [mscorlib]System.Console::WriteLine(string)
newobj instance void Test2::.ctor()
stloc.s test2
ldloc.s test2
call void Test::Test<class Test2>(!!0)
ldloc.s test2
call string Test::Test<class Test2>(!!0)
call void [mscorlib]System.Console::WriteLine(string)
ret
}

.method public static void Test<(WithTest) T>(!!T t)
{
ldarg.s t
ldfld string !!T::Test
call void [mscorlib]System.Console::WriteLine(string)
ret
}

.method public static string Test<(WithTest) T>(!!T t)
{
ldarg.s t
callvirt instance string !!T::Test()
ret
}

.method public string Test()
{
ldstr "Call instance string Test::Test()"
ret
}
}

.class public Test2
{
.field string Test

.method public void .ctor()
{
ldarg.0
ldstr "This is Test2"
stfld string Test2::Test
ldarg.0
call instance void object::.ctor()
ret
}

.method public newslot virtual string Test()
{
ldstr "Call instance string Test2::Test()"
ret
}
}

.class public abstract WithTest
{
.field string Test

.method public newslot abstract virtual string Test()
{
}
}

正确输出

This is Test
Call instance string Test::Test()
This is Test2
Call instance string Test2::Test()

Framework 自然还是炸的

最后反编译出来长这样

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
public class Test
{
private string m_Test = "This is Test";

public static void Main()
{
Test t = new Test();
global::Test.Test<Test>(t);
Console.WriteLine(global::Test.Test<Test>(t));
Test2 t2 = new Test2();
global::Test.Test<Test2>(t2);
Console.WriteLine(global::Test.Test<Test2>(t2));
}

public unsafe static void Test<T>(T t) where T : WithTest
{
Console.WriteLine(((T*)t)->Test);
}

public unsafe static string Test<T>(T t) where T : WithTest
{
return ((T*)t)->Test();
}

public string Test()
{
return "Call instance string Test::Test()";
}
}

public class Test2
{
private string m_Test = "This is Test2";

public virtual string Test()
{
return "Call instance string Test2::Test()";
}
}

public abstract class WithTest
{
private string m_Test;

public abstract string Test();
}

当然,这种操作仅限娱乐,经测试 .NET Framework 和 .NET Core App 都会卡在约束,所以 .NET 是别想有随意的约束了,不过 C# 题案 “Roles and extensions” 倒是给出了曲线实现方案

【.NET】利用 IL 魔法实现随心随意的泛型约束 作者 @where-where 2024年4月17日 发表于 博客园,转载请注明出处