背景

之前写了一篇文 【抬杠.NET】如何进行IL代码的开发 介绍了几种IL代码的开发方式。

  • 创建IL项目
  • C#项目混合编译IL
  • 使用InlineIL.Fody
  • 使用DynamicMethod(ILGenerator)

我个人比较喜欢IL和C#在同一个项目的方式(毕竟单单为了一点点IL代码新建一个IL项目也挺麻烦的),所以一直在用InlineIL.Fody。后来在使用过程中发现了一些它的限制,而如果转而使用混合编译的方式呢,又无法对C#代码进行debug了(因为最终的pdb文件实际上是根据IL源码生成的)。
因此,我使用Fody编写了一个插件,叫做MixedIL.Fody,彻底解决了这些问题。

InlineIL.Fody的限制:如何为无公共setter的自动属性赋值

以AssemblyKeyNameAttribute为例,这是.Net类库里的一个特性。它有个无公共setter的属性Name,那么如何为这个属性赋值呢。

namespace System.Reflection
{
[AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
public sealed class AssemblyKeyNameAttribute : Attribute
{
public AssemblyKeyNameAttribute(string keyName)
{
KeyName = keyName;
} public string KeyName { get; }
}
}

我们知道,自动属性会有个编译器生成的字段。所以可以用反射获取到该字段,然后赋值即可,如下:

var attribute = new AssemblyKeyNameAttribute("name");
var field = typeof(AssemblyKeyNameAttribute).GetField("<KeyName>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
field.SetValue(attribute, "newName");

那如果不用发射呢?可以使用IL代码实现:

.class public abstract sealed auto ansi beforefieldinit System.ObjectExtensions
{
.method public hidebysig static void SetKeyName(class [System.Runtime]System.Reflection.AssemblyKeyNameAttribute attribute, string keyName) cil managed
{
.maxstack 8
ldarg.0
ldarg.1
stfld string [System.Runtime]System.Reflection.AssemblyKeyNameAttribute::'<KeyName>k__BackingField'
ret
}
}

上面的IL代码相当于实现了一个静态方法:

public static class ObjectExtensions
{
public static void SetKeyName(AssemblyKeyNameAttribute attribute, string keyName);
}

所以用InlineIL.Fody实现如下:

public static void SetKeyName(AssemblyKeyNameAttribute attribute, string keyName)
{
IL.Emit.Ldarg(nameof(attribute));
IL.Emit.Ldarg(nameof(keyName));
IL.Emit.Stfld(FieldRef.Field(TypeRef.Type<AssemblyKeyNameAttribute>(), "<KeyName>k__BackingField"));
}

然而编译的时候会报错,Fody/InlineIL: Field '<KeyName>k__BackingField' not found。原因在于AssemblyKeyNameAttribute虽然是个公共类,但是和上面写的SetKeyName方法不在同一个程序集,而私有字段在跨程序集访问时会多一些额外的限制(反射没有这方面的限制)。例如,如果使用DynamicMethod实现上述IL代码,需要指定其构造方法的一个参数skipVisibility为true。此外,使用Expression甚至无法绕过改限制。

实现MixedIL.Fody

MixedIL.Fody是一款基于Fody的插件,其原理很简单,就是使用MSBuild增加编译步骤:用Microsoft.NETCore.ILAsm编译IL代码文件,然后将这步生成的dll内的各个方法的il指令填充到C#代码生成的dll内即可。相比上篇文章里介绍的混合编译,使用这个这种方法,项目内C#代码也可以正常调试。该插件的使用方法可以参考MixedIL.Fody的项目介绍。

上一节的需求可以使用此类库实现如下:

  • 编写C#函数桩,无方法体。
using System.Reflection;
using MixedIL; namespace System; public static class ObjectExtensions
{
/// <summary>
/// Set the property <see cref="AssemblyKeyNameAttribute.KeyName"/> that doesn't have setter by its BackingField.
/// </summary>
/// <param name="attribute"></param>
/// <param name="keyName"></param>
[MixedIL]
public static extern void SetKeyName(this AssemblyKeyNameAttribute attribute, string keyName);
}
  • 在这个项目内,创建一个.il文件,将上节中的il代码写入这个文件。
  • il代码访问其他程序集的私有字段也需要绕开限制,所以还需要为该程序集增加一个特性[assembly: IgnoresAccessChecksTo("System.Private.CoreLib")]而IgnoresAccessChecksToAttribute这个特性已经包含在MixedIL.Fody内了。
  • 最后编译这个程序集即可。

这个例子可以在这里找到:MixedIL.Example

总结

本文由一个InlineIL.Fody的限制,引出了MixedIL.Fody这个类库的创建动机和介绍。

最后我重新总结一下IL开发的各种方法的优缺点。

方法 优点 缺点 应用场景
创建IL项目 原生IL 创建的时候较为复杂 较多代码需IL实现
C#项目混合编译IL 原生IL 无法调试项目内的C#代码 少量方法需IL实现
使用InlineIL.Fody 纯C#编写体验 某些场景不支持 少量方法需IL实现
使用DynamicMethod 运行时生成代码,灵活 性能有损耗,需缓存一些对象 需运行时生成代码
使用MixedIL.Fody 原生IL - 少量方法需IL实现

【抬杠.NET】如何进行IL代码的开发(续)的更多相关文章

  1. 【抬杠.NET】如何进行IL代码的开发

    背景 在有些时候,由于C#的限制,或是追求更高的性能,我们可以编写IL代码来达到我们的目的.本文将介绍几种IL代码开发的几种方式,环境为visual studio 2019 + net5.0 sdk. ...

  2. 【小白学C#】浅谈.NET中的IL代码

    一.前言 前几天群里有位水友提问:”C#中,当一个方法所传入的参数是一个静态字段的时候,程序是直接到静态字段拿数据还是从复制的函数栈中拿数据“.其实很明显,这和方法参数的传递方式有关,如果是引用传递的 ...

  3. 详解.NET IL代码

    一.前言 IL是什么? Intermediate Language (IL)微软中间语言 C#代码编译过程? C#源代码通过LC转为IL代码,IL主要包含一些元数据和中间语言指令: JIT编译器把IL ...

  4. 用ildasm/ilasm修改IL代码

    原文地址:http://www.cnblogs.com/dudu/archive/2011/05/17/ildasm_ilasm_il.html 在开发中遇到这样一个场景,需要修改一个dll文件(.N ...

  5. 认识IL代码---从开始到现在 <第二篇>

    ·IL代码分析方法 ·IL命令解析 ·.NET学习方法论 1.引言 自从『你必须知道.NET』系列开篇以来,受到大家很多的关注和支持,给予了anytao巨大的鼓励和动力.俱往昔,我发现很多的园友都把目 ...

  6. IL代码

    浅析.NET IL代码   一.前言 IL是什么? Intermediate Language (IL)微软中间语言 C#代码编译过程? C#源代码通过LC转为IL代码,IL主要包含一些元数据和中间语 ...

  7. IL代码完结篇

    读懂IL代码就这么简单(三)完结篇   一 前言 写了两篇关于IL指令相关的文章,分别把值类型与引用类型在 堆与栈上的操作区别详细的写了一遍这第三篇也是最后一篇,之所以到第三篇就结束了,是因为以我现在 ...

  8. 一、源代码-面向CLR的编译器-托管模块-(元数据&IL代码)

    本文脉络图如下: 1.CLR(Common Language Runtime)公共语言运行时简介 (1).公共语言运行时是一种可由多种编程语言一起使用的"运行时". (2).CLR ...

  9. 详解.NET IL代码(一)

    本文主要介绍IL代码,内容大部分来自网上,进行整理合并的. 一.IL简介 为什么要了解IL代码? 如果想学好.NET,IL是必须的基础,IL代码是.NET运行的基础,当我们对运行结果有异议的时候,可以 ...

随机推荐

  1. spring程序开发步骤

    1.使用spring框架之前的开发步骤 2.使用spring之后的开发步骤 3.文字描述 1.导入Spring开发的基本依赖 2.编写Dao接口和实现类 3.创建spring核心配置文件 4.在spr ...

  2. linux磁盘概述

    硬盘简史 世界上第一块硬盘出生在1956年,至今已有61年半个多世纪的历史.它由IBM公司制造,世界上第一块硬盘:350RAMAC.盘片直径为24英寸,盘片数为50片,重量则是上百公斤,相当于两个冰箱 ...

  3. Markdown基础语法规则

    你好,世界.粗体,斜体,测试,弟弟,H2O 论文题目 一级标题 二级标题 三级标题 1 2 3 点击此链接打开网址 公式 \(y = \sin x\) \[ y = \frac{1}{x} \] dd ...

  4. 基础设施即代码(IAC),Zalando Postgres Operator 简介

    Postgres Operator 在由 Patroni 提供支持的 Kubernetes (K8s) 上提供易于运行的高可用性 PostgreSQL 集群.它仅通过 Postgres 清单 (CRD ...

  5. MySQL基础入门(1)

    MySQL基础入门(1) 为什么学习MySQL 关系数据库管理系统(Relational Database Management System, RDBMS)是一种极为重要的工具,其应用十分广泛,从商 ...

  6. Django模板相关

    1.母版 想象一个举着火炬的手,除了火炬这个手还能举棒球棍.举雷神之锤.举拖拉机钥匙等等,举得东西不同给人整体感觉就不同. 母版就相当于这个手(实际为一个html文件),其他相关的html文件就相当于 ...

  7. Azure Terraform(十一)Azure DevOps Pipeline 内的动态临时变量的使用

    思路浅析 在我们分析的 Azure Terraform 系列文中有介绍到关于 Terraform 的状态文件远程存储的问题,我们在  Azure DevOps Pipeline 的 Task Job ...

  8. ZooKeeper 到底解决了什么问题?

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 目标 ZooKeeper 很流行,有个基本的疑问: Zo ...

  9. .NET性能优化-使用结构体替代类

    前言 我们知道在C#和Java明显的一个区别就是C#可以自定义值类型,也就是今天的主角struct,我们有了更加方便的class为什么微软还加入了struct呢?这其实就是今天要谈到的一个优化性能的T ...

  10. IIS项目部署和发布

    VS2019如何把项目部署和发布 这里演示:通过IIS文件publish的方式部署到Windows本地服务器上 第一步(安装IIS) 1.在自己电脑上搜索Windows功能里的[启用或关闭Window ...