惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Blog — PlanetScale
Blog — PlanetScale
Webroot Blog
Webroot Blog
T
Troy Hunt's Blog
S
Secure Thoughts
S
Security @ Cisco Blogs
S
Security Affairs
Forbes - Security
Forbes - Security
W
WeLiveSecurity
H
Hacker News: Front Page
T
Threatpost
Google Online Security Blog
Google Online Security Blog
S
Schneier on Security
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - Franky
腾讯CDC
IT之家
IT之家
博客园 - 聂微东
L
LINUX DO - 最新话题
罗磊的独立博客
Hacker News - Newest:
Hacker News - Newest: "LLM"
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 三生石上(FineUI控件)
Hacker News: Ask HN
Hacker News: Ask HN
C
CXSECURITY Database RSS Feed - CXSecurity.com
C
Cybersecurity and Infrastructure Security Agency CISA
C
CERT Recently Published Vulnerability Notes
Know Your Adversary
Know Your Adversary
V
Vulnerabilities – Threatpost
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园_首页
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Cisco Talos Blog
Cisco Talos Blog
S
SegmentFault 最新的问题
酷 壳 – CoolShell
酷 壳 – CoolShell
Hugging Face - Blog
Hugging Face - Blog
L
LINUX DO - 热门话题
美团技术团队
G
GRAHAM CLULEY
T
The Exploit Database - CXSecurity.com
AI
AI
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Jina AI
Jina AI
Help Net Security
Help Net Security
N
News | PayPal Newsroom
月光博客
月光博客
Spread Privacy
Spread Privacy
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
N
News and Events Feed by Topic

博客园 - China Soft

.NET 程序保护实战系列01-流水线架构与保护引擎总览 试官问:你用 AI 编程半年了,那怎么保证 Claude Code 写出来的代码是对的? C#实现控制台多区域输出 园友特惠| 1Panel 企业版 & AI 一体机限量放售,最高直降 5100! 上位机程序发布打包成安装包---Inno Setup 这是一篇学习笔记,分享给大家,如果大家喜欢,也会开启USB的专辑 我问了 AI 一个问题:编码能力贬值后,什么能力值钱? Visual Studio 2026(VS2026) 密钥/激活码 Dddify:给 ASP.NET Core 项目一套轻量、清晰、可落地的 DDD 基础设施 C# ESP32/STM32 轻量 Web 能力库:PicoServer.Nano 高性能百度OCR ONNX Runtime C#实现 别再说 AI 开发就是调接口了!5 种主流模式一次讲清 使用Cursor实现管理系统登录界面的快速开发 在 Avalonia 中编写高性能动画 傻子可懂的 Harness Engineering 入门教程 + 项目实战,一次搞懂 AI 编程工程化! 提升 Text2SQL 准确率 AI/Vibe Coding,本质是软件人工时代向软件工业时代发展 体验优先:十分钟使用 Python+LangChain 玩转阿里通义千问 大模型基础(二):必懂5大基础概念《Token、上下文窗口、Embedding、预训练、微调》 大模型基础(一):什么是LLM? Prompt、Agent、Function Call、Skill、MCP,傻傻分不清楚? 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 作为一线研发,我快被全网的 AI 焦虑逼疯了。 Qt QtWebEngine 白屏的解决方案 都是微软亲儿子,WPF凭啥干不掉WinForm?这3个场景说明白了 大模型看大模型:推理Token的能耗用电量比对 WorkBuddy:从“我是谁”到“帮我干活” .NET 高级开发 | 开发 .NET 诊断工具、链路追踪原理 Prompt 焚诀——一个模板,终结你和 AI 的所有沟通问题 [项目]涉案资产信息化管理平台(山东某执法单位定制开发) 大模型RAG实战,从被骂不靠谱到成为部门MVP,这是我的踩坑全记录 白帽子为什么几乎都绕不开 httpx:一款 HTTP 资产探测工具的技术价值 从Prompt工程到Skill工程:Agent Skills开放标准彻底改变了AI协作方式 MAUI项目在Android平台通过U盘实现软件更新 微软竟然出了免费的 AI 应用开发课?!我已经学上了 一文彻底搞懂 OpenClaw 的架构设计与运行原理(万字图文) AI到底聪明在哪——从手机人脸识别说起 硬核科普:为何小米任天堂封杀 Root?揭秘操作系统从启动到“夺权”的底层游戏
.NET 高级开发 | C# 中的动态代码:反射、EMIT、表达式树、Roslyn、Source Generators
China Soft · 2026-04-15 · via 博客园 - China Soft

动态代码

C# 中实现动态代码的方式有很多,比如 反射、表达式树、EMIT、Roslyn、Source Generators 等,C# 各类框架中几乎都有动态代码技术的使用,比如依赖注入、对象关系映射、AOP 技术等。由于动态代码技术在 C# 中的使用场景非常广泛,因此在本章中,笔者将会介绍多种动态代码技术,以及完成部分实践,完成常见几种框架技术的编写方法。

阅读本章内容之前,需要读者熟练掌握反射技术,需要学习反射技术,可以参考笔者的在线电子书课程: https://reflect.whuanle.cn/

EMIT

EMIT 是一种使用 C# 编排生成 IL 代码的技术,IL 是 .NET 平台的中间语言,由于 IL 的高性能的特点,很多框架都使用 EMIT 技术动态生成代码,最广泛的使用是编写 AOP 框架。在本节中,笔者将会介绍 AOP 的实现原理,以及使用 EMIT 编写一个简单的 AOP 程序。

创建控制台项目,引入 CZGL.AOP 包,示例代码请参考 Demo.CZGLAOP 项目。

有以下接口和类型:

public interface ITest
{
    void MyMethod();
}
public class Test : ITest
{
    public virtual string A { get; set; }
    public Test()
    {
        Console.WriteLine("构造函数没问题");
    }
    public virtual void MyMethod()
    {
        Console.WriteLine("运行中");
    }
}

我们希望,在执行 MyMethod 方法时,能够在执行前后打印出日志,这时可以先编写一个特性类,继承 ActionAttribute ,实现 Before 和 After 接口。

public class LogAttribute : ActionAttribute
{
    public override void Before(AspectContext context)
    {
        Console.WriteLine("--执行前--");
    }

    public override object After(AspectContext context)
    {
        Console.WriteLine("--执行后--");
        if (context.IsMethod)
            return context.MethodResult;
        else if (context.IsProperty)
            return context.PropertyValue;
        return null;
    }
}

然后改造 Test 类型。

[Interceptor]
public class Test : ITest
{
    [Log]
    public virtual string A { get; set; }
    public Test()
    {
        Console.WriteLine("构造函数");
    }
    [Log]
    public virtual void MyMethod()
    {
        Console.WriteLine("运行中");
    }
}

然后创建 AOP 类型:

ITest test1 = AopInterceptor.CreateProxyOfInterface<ITest, Test>();
test1.MyMethod();
Test test2 = AopInterceptor.CreateProxyOfClass<Test>();
test2.MyMethod();

运行项目,会输出:

构造函数
--执行前--
运行中
--执行后--
构造函数
--执行前--
运行中

AOP 实现原理

AOP(Aspect-oriented Programming) 即面向切片编程,在 C# 中有动态 AOP 和静态 AOP 两种,如果是在程序启动后生成的,为动态 AOP,这类框架有 Castle 、AspectCore 等,它们都使用了 EMIT 技术,在代码编译时即生成的,为静态 AOP,这类框架有 Fody 等。

实现 AOP 的前提

请看如下所示的代码,当调用 Voice() 方法时,请思考控制台会打印什么内容。

public class Program
{
    static void Main()
    {
        Animal c = new Cat();
        Console.WriteLine(c.Voice());
    }

	public abstract class Animal
	{
		public string Voice() => "null";
	}

    public class Cat: Animal
    {
        public new string Voice() => "喵";
    }
}

如果你有运行代码,会发现打印结果是 null,虽然 c 是 Cat 类型,但是这里我们使用的是 Animal 类型,CLR 首先判断 Voice 方法是否为抽象方法或虚方法,如果不是则直接调用,不会往子类中查找。

我们使用工具查看Animal 中 Voice 方法的 IL 代码:

// Methods
.method public hidebysig 
        instance string Voice () cil managed 

那么,同样的代码,在 java 中,又会发生什么呢?

public class Main {
    public static void main(String[] args) {
       Animal animal = new Cat();
       System.out.println(animal.Voice());
    }
}


class Animal{
    public String Voice(){
    return "null";
    }
}

class Cat extends Animal{
    public String Voice(){
    return "喵";
    }
}

运行这段代码后会发现,打印出来的是 。因为 java 中的方法默认是虚方法,而 C# 中的 方法需要加上关键字 virtual 才是虚方法。

为了能够在使用父类方法时,执行的是子类的代码,我们需要将代码改成:

public abstract class Animal
{
	public virtual string Voice() => "null";
}

public class Cat : Animal
{
	public override string Voice() => "喵";
}

那么,虚方法跟实现 AOP 有啥关系呢?其实,使用 EMIT 技术编写 AOP 框架的思路很简单,那就是继承,比如我们要给 A 类型的 A 方法实现 AOP,那么 A 方法就必须得是抽象方法或虚方法,然后我们通过 EMIT 技术生成一个类型 B 继承 A,然后创建 A 类型时实际上创建的是 B 类型。此时,调用 A 中的 A 方法,CLR 会执行 B 中的 A 方法。

A a = new B();

在使用 EMIT 技术实现 AOP 之前,我们可以通过容器依赖注入来领会 AOP 是如何通过继承实现的。

有个 UserService 服务,实现了登录过程。

public class UserService
{
	public virtual bool Login(string name, string passeword)
	{
		return true;
	}
}

然后我们使用一个新的类型重写 Login 方法,在执行方法之前和之后打印日志。

public class UserServiceAop : UserService
{
	public override bool Login(string name, string passeword)
	{
		Console.WriteLine($"用户开始登录:{name}");
		var result = base.Login(name, passeword);
		Console.WriteLine($"用户 {name} 登录结果 {result}");
		return result;
	}
}

然后通过注入服务实现 AOP:

IServiceCollection ioc = new ServiceCollection();
ioc.AddScoped<UserService, UserServiceAop>();
var services = ioc.BuildServiceProvider();

var userService = services.GetRequiredService<UserService>();
userService.Login("工良", "123456");

如果需要 AOP 的是接口,那就更加简单,我们只需要生成一个继承接口的类型即可,完全不需要继承父类,而父类也不需要标记为虚方法或抽象方法。

public interface IUserService
{
	bool Login(string name, string passeword);
}

public class UserService : IUserService
{
	public bool Login(string name, string passeword)
	{
		return true;
	}
}

public class UserServiceAop : IUserService
{
	private readonly UserService _service;
	public UserServiceAop()
	{
		_service = new UserService();
	}
	public bool Login(string name, string passeword)
	{
		Console.WriteLine($"用户开始登录:{name}");
		var result = _service.Login(name, passeword);
		Console.WriteLine($"用户 {name} 登录结果 {result}");
		return result;
	}
}

IServiceCollection ioc = new ServiceCollection();
ioc.AddScoped<IUserService, UserServiceAop>();
var services = ioc.BuildServiceProvider();

var userService = services.GetRequiredService<IUserService>();
userService.Login("工良", "123456");

实际上,接口方法本身就是抽象方法,所以因此无需处理接口即可直接使用 AOP 生成代理类型。

// Methods
   .method public hidebysig newslot abstract virtual 
           instance bool Login (
                string name,
                string passeword
            ) cil managed 
        {
        } 

通过这个例子你应该可以领会到使用 EMIT 技术实现 AOP ,最基础最本质的是实现一个新的类型基础父类或接口。由于 AOP 的类型是动态生成的,我们在开发是无法创建,所以 AOP 一般需要结合 IOC 使用,我们可以拦截容器中的 <IUserService, UserService>,替换成 <IUserService, UserServiceAop>。或者提供一个工厂服务,用于获取 AOP 后的对象。

EMIT 实现 AOP

本节代码可以在 Demo8.Console、Demo8.FxConsole 中查看。

ILSpy 是一个反编译工具,能够帮助我们查看代码生成的 IL,这样一来,即使我们对 IL 不熟悉,也可以通过编译好的 IL 代码中抄过来。

在 Visual Studio 中 安装扩展 ILSpy ,安装或手动下载(https://github.com/icsharpcode/ILSpy)。

image-20230817193711194

创建一个项目,然后创建接口和类型。

public interface IUserService
{
	bool Login(string name, string passeword);
}

public class UserService : IUserService
{
	public bool Login(string name, string passeword)
	{
		return true;
	}
}
public class UserServiceAop : IUserService
{
	private readonly UserService _service;
	public UserServiceAop()
	{
		_service = new UserService();
	}
	public bool Login(string name, string passeword)
	{
		Console.WriteLine($"用户开始登录:{name}");
		var result = _service.Login(name, passeword);
		Console.WriteLine($"用户 {name} 登录结果 {result}");
		return result;
	}
}

然后编译项目生成 dll 文件,将 Demo8.Console.dll 拖动放到 ILSpy 中,你可以查看到 UserServiceAop 的全部 IL 代码,我们只需要抄即可!

image-20230817194216915

接下来,我们开始使用 EMIT 技术,动态生成一个 UserServiceAop 类型。

首先是动态构建程序集并创建一个新的类型。

public static Assembly Build()
{
	

创建类型后,首先给类型添加构造函数。

private static void BuildCtor(TypeBuilder typeBuilder, FieldBuilder fieldBuilder)
{
	

然后生成 Login 方法。

private static void BuildMethod(TypeBuilder typeBuilder, FieldBuilder fieldBuilder)
{
	var baseMethod = typeof(UserService).GetMethod("Login");
	

最后通过依赖注入添加服务:

static void Main()
{
	var assembly = AopHelper.Build();
	var newType = assembly.GetType("Aop.UserServiceAop");

	IServiceCollection ioc = new ServiceCollection();
	ioc.Add(new ServiceDescriptor(typeof(IUserService), newType, ServiceLifetime.Scoped));
	var services = ioc.BuildServiceProvider();

	var userService = services.GetRequiredService<IUserService>();
	userService.Login("工良", "123456");
}

到目前为止,我们已经介绍了如何使用 EMIT 动态生成一个新的类型,我们的例子仅仅是针对 IUserService 生成一个 UserServiceAop 类型,。

表达式树

表达式树的应用很广泛,表达式树的使用方法主要分为两种情况,生成动态代码执行,比如延迟查询、yeild return,第二种是解析表达式,比如编写 ORM 框架。

表达式树生成

笔者写过表达式树系列的教程,欢迎阅读 https://ex.whuanle.cn/

表达式树是程序运行时动态生成代码的一种方法,通过定义各种表达式来组合代码逻辑,最后编译执行。

在第八章中,编写事件总线方法时,我们就使用了表达式树组装方法,以便将不同形式的方法统一起来。

比如,有以下代码。

int Sum(int i, int j, int x, int y)
{
	return (i * j) + (x * y);
}

如果我们需要使用表达式树的形式编写该逻辑,示例代码如下:

ParameterExpression a = Expression.Parameter(typeof(int), "i");
ParameterExpression b = Expression.Parameter(typeof(int), "j");

Expression r1 = Expression.Multiply(a, b);      

为了使用表达式树表示代码,需要将表达式树做得很复杂,需要表达常量变量,逻辑运算、条件控制、循环控制等等,还要访问变量的字段、属性、方法等。

笔者将表达式树的学习分为以下部分:

  • 变量、常量、赋值
  • 运算符
  • 条件控制
  • 循环控制
  • 对象、泛型、集合和实例化
  • 访问对象成员,字段、属性、函数

表达式树主要分为三个逻辑,其中最重要的是组合表达式树,然后是编译、执行。

由于表达式树的本身很复杂,因此,本书只是简单介绍表达式树如何使用,以及使用场景,只需要掌握简单的表达式树编写即可,以便理解后面的表达式树如何进行解析。

如果我们碰到需要编写表达式树的场景,我们可以像编写 AOP 一样,首先简化 C# 代码,然后借助工具先查出这段 C# 代码生成的表达式树的大概写法,然后再借鉴这些表达式树到项目当中。

比如,我们可以使用 LinqPad ,首先将需要生成表达式树的代码放到编辑器中,然后查看该代码对应的表达式树。

image-20230822192017955

变量常量和赋值

在 C# 中,变量分为以下几种类型:

  • 值类型(Value types)
  • 引用类型(Reference types)
  • 指针类型(Pointer types)

一般上,只用到值类型和引用类型,这里不会说到指针类型。

创建一个变量:

ParameterExpression varA = Expression.Variable(typeof(int), "x");

创建一个函数参数:

ParameterExpression varB = Expression.Parameter(typeof(int), "y");

创建一个常量:

ConstantExpression constant = Expression.Constant(100);
ConstantExpression constant1 = Expression.Constant(100, typeof(int));

逻辑运算

在 C# 中,算术运算符,有以下类型

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其他运算符

由于 C# 中的运算符非常多,笔者就不一一介绍了,推荐读者阅读 https://ex.whuanle.cn/4.operator.html

比如,要将两个 int 类型的变量相加或相减,可以使用 Expression.Add 和 Expression.Subtract

ParameterExpression a = Expression.Parameter(typeof(int), "a");
ParameterExpression b = Expression.Parameter(typeof(int), "b");

调用方法

Expression.Call 是表达式树种调用类型方法的接口,其定义如下:

static MethodCallExpression Call(Expression? instance, MethodInfo method, params Expression[]? arguments)

有如下 C# 代码:

int a = 100;
int b = 200;

var ab = a + b;
Console.WriteLine(ab);

使用表达式树调用 Console.WriteLine() 静态方法打印信息:

ParameterExpression a = Expression.Parameter(typeof(int), "a");
ParameterExpression b = Expression.Parameter(typeof(int), "b");

Expression.Call 也可以调用实例的方法:

public class Test
{
	public void Print(string info)
	{
		Console.WriteLine(info);
	}
}

Test test = new Test();
test.Print("打印出来");
// Test test
ParameterExpression test = Expression.Variable(typeof(Test), "test");

// test.Print("打印出来");
MethodCallExpression method = Expression.Call(
	test,
	typeof(Test).GetMethod("Print", new Type[] { typeof(string) }),
	Expression.Constant("打印出来")
	);

编写对象映射框架

示例代码请参考 Demo8.Mapper 项目。

// 利用泛型缓存,提升访问速度,以及简化缓存结构
public static class TypeMembers<TTarget>
        where TTarget : class
{
    private static readonly MemberInfo[] MemberInfos;

    public static MemberInfo[] Members => MemberInfos;
    static TypeMembers()
    {
        MemberInfos = typeof(TTarget)
        .GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
        .Where(x => (x is FieldInfo) || (x is PropertyInfo)).ToArray();
    }
}
public class MapperBuilder<TSource, TTarget>
        where TSource : class
        where TTarget : class, new()
{
    
var builder = new MapperBuilder<A, B>();
public class A
{
	public string C { get; set; }
	public int D { get; set; }
}
public class B
{
	public string C { get; set; }
	public string D { get; set; }
}

// 单独设置表达式赋值
public MapperBuilder<TSource, TTarget> Set<TValue, TField>(Func<TSource, TValue> buildValue,
Expression<Func<TTarget, TField>> targetField)
{
	MemberInfo p = GetMember(targetField);
	MapExpressions[p] = buildValue;
	return this;
}

// 构建对象映射
public void Build()
{
	List<Expression> exList = new List<Expression>();

	

完整使用示例:

var builder = new MapperBuilder<A, B>();
// c.D = a.D.ToString()
builder.Set(a => a.D.ToString(), b => b.D);
builder.Build();

A a = new A()
{
	C = "C",
	D = 123
};
var b = builder.Map(a);

表达式树解析

日常业务开发少不了 ORM 框架,ORM 框架为我们的操作数据库代理了极大的便利,ORM 中也大量使用了表达式树技术,不过跟动态生成代码截然相反,ORM 使用的是解析表达式树。

本节讲解如何使用表达式树完成一个简单的 ORM 框架,示例代码请参考 Demo8.ORM 项目。

请在 mysql 数据库中执行以下 SQL,以便创建和插入表数据:

CREATE TABLE `Test`  (
  `Id` int NOT NULL,
  `Name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`Id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;


INSERT INTO `Test` VALUES (1, '工良');
INSERT INTO `Test` VALUES (2, '工良');
INSERT INTO `Test` VALUES (3, '工良');
INSERT INTO `Test` VALUES (4, '工良');

为了让示例项目更简单,该 ORM 只包含简单的查询功能。

使用示例:

var context = new MyDBContext(connction);
var list = await context.Select<Test>()
.Where(x => x.Id >= 2 && x.Name.Contains("工良") && x.Id < 4)
.ToListAsync();

创建上下文类型 MyDBContext :

public class MyDBContext
{
    protected readonly IDbConnection _connction;
    public MyDBContext(IDbConnection connction)
    {
        _connction = connction;
    }

    public MyDBContext<T> Select<T>() where T : class, new()
    {
        return new MyDBContext<T>(_connction);
    }
}

泛型 MyDBContext<T> 用于构造和生成 SQL 语句:

public class MyDBContext<T> : MyDBContext
    where T : class, new()
{
    public MyDBContext(IDbConnection connction) : base(connction)
    {
    }

    private readonly StringBuilder _strBuilder = new StringBuilder();

    public MyDBContext<T> Where(Expression<Func<T, bool>> predicate)
    {
        var bin = predicate.Body as BinaryExpression;
        ArgumentNullException.ThrowIfNull(bin);
        var content = $"{Parse(bin.Left)} {GetChar(bin.NodeType)} {Parse(bin.Right)}";
        _strBuilder.Append(content);
        return this;
    }

    

通过 nuget 包引入 MySqlConnector,通过 MyDBContext 查询从数据库查询数据。

public class Test
{
    public int Id { get; set; }

    public string Name { get; set; }
}

public class Program
{
    static async Task Main()
    {
        var builder = new MySqlConnectionStringBuilder
        {
            Server = "localhost:3306",
            Database = "test",
            UserID = "root",
            Password = "123456",
            SslMode = MySqlSslMode.Required,
        };
        IDbConnection connction = new MySqlConnection(builder.ConnectionString);

        var context = new MyDBContext(connction);
        var list = await context.Select<Test>()
        .Where(x => x.Id >= 2 && x.Name.Contains("工良") && x.Id < 4)
        .ToListAsync();
    }
}

Roslyn

Roslyn 是一种用来编译代码和分析代码的技术,主要有两种使用方式,一种是动态编译代码,与 EMIT 技术不同的是, Roslyn 可以直接编译字符串代码,也可以编译完整的项目代码,而不需要 .NET SDK 或 MSBuild,国内有开发者利用 Natasha 技术编写了 Natasha 框架。另一种使用方式是作为代码分析器,使用非常广泛,.NET 5 以后的版本都自带了代码分析器,我们在 IDE 中可以看到。

image-20230822201501803

在后面的章节中笔者会使用介绍如何编写一个代码分析器,所以在本章中,笔者只介绍如何使用 Roslyn 编译代码。

使用 Roslyn

示例代码请参考 Demo8.Roslyn 项目。

首先通过 nuget 引入 Microsoft.CodeAnalysis.CSharp 包。

创建一个 DomainOptions 类型,用来存储编译程序集时的配置。

public class DomainOptions
{

    // 当前要编译的程序集是何种类型的项目
    public OutputKind OutputKind { get; set; } = OutputKind.DynamicallyLinkedLibrary;

    

然后创建一个构造器,用来构造 DomainOptions 类型。

// 程序集编译配置构建器
public class DomainOptionBuilder
{
    private readonly DomainOptions _option = new DomainOptions();
    internal LanguageVersion LanguageVersion => _option.LanguageVersion;
    internal string[] Environments => _option.Environments.ToArray();
    internal CSharpCompilationOptions Build()
    {
        if (_option.Environments.Count == 0)
        {
            _option.Environments.Add(_option.OptimizationLevel == OptimizationLevel.Debug ? "DEBUG" : "RESEALE");
        }

        return new CSharpCompilationOptions(
          concurrentBuild: true,
          metadataImportOptions: MetadataImportOptions.All,
          outputKind: _option.OutputKind,
          optimizationLevel: _option.OptimizationLevel,
          allowUnsafe: _option.AllowUnsafe,
          platform: _option.Platform,
          checkOverflow: _option.CheckOverflow,
          assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default);
    }

    

最后,编写一个编译器,用来编译代码成程序集。

// 程序集编译构建器
public class CompilationBuilder
{
	

然后在 Main 方法中,我们开始进行编译。

定义字符串代码:

const string code =
"""
using System;
namespace MySpace
{
	public class Test
	{
		public int Sum(int a, int b)
		{
			return a + b;
		}
	}
}
""";

配置编译选项:

// 编译选项
// 编译选项可以不配置
DomainOptionBuilder option = new DomainOptionBuilder()
	.WithPlatform(Platform.AnyCpu)                     // 生成可移植程序集
	.WithDebug(false)                                  

最后编译代码并获取编译结果:

// 编译代码
var isSuccess = CompilationBuilder.CreateDomain(code,
   assemblyPath: "./",
   assemblyName: "test.dll",
   option: option,
   out var messages);

检查编译结果以及动态调用程序集:

// 编译失败,输出错误信息
if (!isSuccess)
{
	foreach (var item in messages)
	{
		Console.WriteLine(
$"""
        ID:{item.Id}
        严重程度:{item.Severity}     
        位置:{item.Location.SourceSpan.Start}~{item.Location.SourceSpan.End}
        消息:{item.Descriptor.Title}   {item}
        """);
	}
	return;
}

使用 Natasha

Natasha 开源项目地址:https://github.com/dotnetcore/Natasha

基于 Roslyn 的 C# 动态程序集构建库,该库允许开发者在运行时使用 C# 代码构建域 / 程序集 / 类 / 结构体 / 枚举 / 接口 / 方法等,使得程序在运行的时候可以增加新的模块及功能。Natasha 集成了域管理/插件管理,可以实现域隔离,域卸载,热拔插等功能。

示例项目在 Demo8.Natasha 中。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="DotNetCore.Compile.Environment" Version="3.2.0" />
    <PackageReference Include="DotNetCore.Natasha.CSharp" Version="5.2.2.1" />
  </ItemGroup>

</Project>
static void Main()
{
	const string code =
"""
using System;
namespace MySpace
{
	public class Test
	{
		public int Sum(int a, int b)
		{
			return a + b;
		}
	}
}
""";

	

Source Generators

Source Generators 是一种很复杂的技术,用于生成源代码,Source Generators 技术的基础是 Roslyn。

https://wengier.com/SourceGeneratorPlayground/

首先创建一个用于生成代码的项目,名为 Demo8.SGBuild,Demo8.SGBuild.csproj 文件内容如下所示:

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFramework>netstandard2.0</TargetFramework>
		<LangVersion>11.0</LangVersion>
	</PropertyGroup>

	<ItemGroup>
		<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" PrivateAssets="all" />
		<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
	</ItemGroup>

</Project>

然后我们实现一个源代码生成器,在项目编译时,代码生成器会查找项目中 Main 方法所在的命名空间,然后生成一个 Test 类型存储在 MyAOP.cs 中,新生成的代码会随着项目一起编译。

[Generator]
public class MySourceGenerator : ISourceGenerator
{
	public void Execute(GeneratorExecutionContext context)
	{
		

然后我们再创建一个 Demo8.UseSG 项目,引用 Demo8.SGBuild 。

	<ItemGroup>
		<ProjectReference Include="..\Demo8.SGBuild\Demo8.SGBuild.csproj"
						  OutputItemType="Analyzer"
						  ReferenceOutputAssembly="false" />
	</ItemGroup>

Demo8.UseSG 中的代码示例如下:

class Program
{
	static void Main(string[] args)
	{
		var assembly = typeof(Program).Assembly;
		var testType = assembly.GetTypes().FirstOrDefault(x => x.GetInterfaces().Any(i => i == typeof(ITest)));
		var test = Activator.CreateInstance(testType) as ITest;
		var sum = test.Sum(1, 2);
		Console.WriteLine(sum);
	}
}

public interface ITest
{
	int Sum(int a, int b);
}