首页 > 其他分享 >【.NET】利用 IL 魔法实现随心随意的泛型约束

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

时间:2024-04-17 17:24:32浏览次数:14  
标签:string instance void method Test IL 泛型 NET public

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

public static string Test<T>(T value) where T : ITest
{
    return value.Test();
}

public interface ITest
{
    string Test();
}

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

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

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

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

反编译出来是这样的

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 泛型的局限性了,后来有群友提醒我说约束会影响运行时,于是我就尝试加上约束

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

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

.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 给泛型开了洞

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

.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 自然还是炸的

最后反编译出来长这样

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" 倒是给出了曲线实现方案

标签:string,instance,void,method,Test,IL,泛型,NET,public
From: https://www.cnblogs.com/wherewhere/p/18140783

相关文章

  • 泛型new()约束
    在C#中,如果你有一个泛型类或方法,且其中需要创建类型T的实例,但是T并没有指定具有无参构造函数(new()约束),那么编译器不会允许你直接使用newT()来创建实例。例如,假设你有以下泛型类:Csharp1publicclassMyClass<T>2{3publicTCreateInstance()4{5//下面这行......
  • SpringBoot项目 file not present 终局!
    在写一个文件上传接口时,从一个老项目里copy出来了一个接口,死活报错filenotpresent,参考如下步骤排查确保请求的httpheader里面的文件字段名和接口定义一致如果使用postman,则确定key和接口保持一致在保证一切都是对的情况下,检查下项目是否配置了CommonsMultipartResolv......
  • 第十六章——处理锁、阻塞和死锁(3)——使用SQLServer Profiler侦测死锁
    前言:作为DBA,可能经常会遇到有同事或者客户反映经常发生死锁,影响了系统的使用。此时,你需要尽快侦测和处理这类问题。死锁是当两个或者以上的事务互相阻塞引起的。在这种情况下两个事务会无限期地等待对方释放资源以便操作。下面是死锁的示意图: 本文将使用SQLServerProfi......
  • Android Studio修改代码后直接点击run没生效,需要Rebuild或者删除build文件夹后再run才
    AndroidStudio修改代码后直接点击run没生效,需要Rebuild或者删除build文件夹后再run才生效的解决方法第一步:app->editconfigurations进入app配置选中app,滑动到下面的beforelaunch,点击+号,添加一个Gradle-awareMake给Task输入一个名称,或者不填留空使用原理名称,然......
  • WPF 使用DbUtility 数据库通用操作类
    依赖准备1.在WPF项目内首先通过NuGet包管理器进行安装需要操作的数据库依赖,我这里使用的是SQLite数据库所以安装的是System.Data.SQLite。2.安装后需要下载DbUtility.dll,下边是网盘地址https://www.123pan.com/s/TaoVjv-4aWHv.html3.下载后把文件放到项目根目录下,在软件内引用......
  • 修改kubernetes服务nodeport类型的端口范围
    原文链接编辑kube-apiserver.yaml文件vim/etc/kubernetes/manifests/kube-apiserver.yaml找到–service-cluster-ip-range这一行,在这一行的下一行增加如下内容--service-node-port-range=80-30000实际案例内容如apiVersion:v1kind:Podmetadata:creationTimestamp......
  • .Net6-利用IServiceProvider实现全局依赖注入
    需求背景:自定义类库程序中的类文件引用IService接口对象并实现依赖注入。1.修改应用程序Program.cs文件1varbuilder=WebApplication.CreateBuilder(args);2builder.Services.AddProgramService();345varapp=builder.Build();6InternalApp.ServiceProvider=a......
  • mysql如何批量kill慢查询
    原文链接:https://blog.csdn.net/lanyang123456/article/details/122277340Slowquery慢查询是指执行很慢的SQL语句。一般会设置一个阈值,例如,100ms,执行时间超过100ms的都会判定为慢查询。慢查询是一种危险的信号,MySQL服务可能很快不可用。当大量出现的时候,应该立即kill。......
  • linux puppeteer 截图提示缺少chrome-linux/chrome error while loading shared libra
    puppeteer/.local-chromium/linux-1002410/chrome-linux/chrome:errorwhileloadingsharedlibraries:libXdamage.so.1:cannotopensharedobjectfile:Nosuchfileordirectory按照错误对照进行安装执行,缺啥安啥......
  • 一个.NET内置依赖注入的小型强化版
    前言.NET生态中有许多依赖注入容器。在大多数情况下,微软提供的内置容器在易用性和性能方面都非常优秀。外加ASP.NETCore默认使用内置容器,使用很方便。但是笔者在使用中一直有一个头疼的问题:服务工厂无法提供请求的服务类型相关的信息。这在一般情况下并没有影响,但是内置容器支......