Natasha C# Dynamic Compilation Skill
Purpose
This skill enables developers to dynamically generate and compile C# code at runtime using the Natasha library. It supports creating dynamic classes, generating high-performance delegates, accessing private members of existing types, managing isolated compilation contexts, and precisely controlling metadata loading strategies.
When to Use This Skill
Use this skill when:
- - Dynamically creating classes with custom properties and methods at runtime
- Generating high-performance delegates for computational code without reflection overhead
- Accessing private fields or methods of existing types from dynamically compiled code
- Building expression trees or code generation utilities that require runtime C# compilation
- Creating extensible plugin systems where behavior is determined at runtime
- Optimizing performance-critical code paths through dynamic method generation
- Needing fine-grained control over metadata and using-code scope (lean metadata mode)
How to Use This Skill
Prerequisites
Install the required NuGet packages in your project:
CODEBLOCK0
Note: DotNetCore.Natasha.CSharp.Compiler.Domain inherits from DotNetCore.Natasha.Domain and implements the compilation binding interface required by Natasha compiler. All packages are prefixed with INLINECODE2
Framework Support: .NET Core 3.1+, .NET 5.0+, .NET 6.0+, .NET 7.0+, .NET 8.0+
Three Compilation Modes
Mode 1: Smart Mode (智能模式) — Recommended for Most Cases
Smart mode automatically merges metadata and using-code from the default domain + current domain, with semantic checking enabled.
Key decision: Memory Assembly vs Reference Assembly
| Dimension | Memory Assembly | Reference Assembly |
|---|
| Metadata coverage | Runtime types only | Complete metadata (including non-loaded assemblies) |
| Memory usage |
Lower | Higher |
| Startup speed | Faster | Slower |
| Recommended for | Most scenarios | Full metadata needed, memory/size not a concern |
Memory assembly initialization (内存程序集预热):
CODEBLOCK1
Reference assembly initialization (引用程序集预热):
CODEBLOCK2
Rule of thumb: If the user does not care about program size or memory consumption, use WithRefUsing().WithRefReference() — reference assemblies provide the most complete metadata coverage.
Using file cache (文件缓存优化):
When the project is stable (no more new dependencies being added), enable WithFileUsingCache() to write using-code into a Natasha.Namespace.cache file. On subsequent runs, Natasha reads from the cache instead of scanning assemblies, significantly speeding up startup:
CODEBLOCK3
Usage in smart mode:
CODEBLOCK4
Mode 2: Simple Mode (精简模式 / 自管理元数据模式)
Use simple mode when you need to precisely control which metadata participates in compilation. No global preheating metadata is used — you add only what you need via ConfigLoadContext.
CODEBLOCK5
Key points for simple mode:
- -
AddReferenceAndUsingCode(typeof(T)) — loads T's assembly AND its dependency assemblies, plus extracts all using-code from them - INLINECODE8 — same but takes an Assembly object
- The
using directives are automatically collected from the added assemblies in simple mode, so you don't need to write them explicitly in the script (unless using WithoutCombineUsingCode) - Use
AppendExceptUsings("System.IO", "MyNamespace") to explicitly exclude certain using namespaces from being injected into the syntax tree
Mode 3: Custom Compilation Mode (自定义编译模式)
Use when you provide your own complete metadata set (e.g., from Basic.Reference.Assemblies NuGet package) and handle using-code yourself.
CODEBLOCK6
WithSpecifiedReferences makes the builder ignore both the default domain and current domain metadata, using only the explicitly provided references.
Core Workflow
- 1. Initialize Natasha (once per application startup)
- Choose smart or simple mode based on your needs
- Reference: See
references/initialization-patterns.md for all supported initialization methods
- 2. Create AssemblyCSharpBuilder
- Instantiate
new AssemblyCSharpBuilder()
- Configure load context:
UseRandomLoadContext() for isolation,
UseNewLoadContext("name") for persistence
- Select compilation mode:
UseSmartMode() /
UseSimpleMode() / custom
- Set compilation level:
WithReleaseCompile() or INLINECODE21
- 3. Add and Compile Code
- Use
.Add(csharpCode) to add code strings
- Call
.GetAssembly() to compile and retrieve the assembly
- Or call
.CompileWithoutAssembly() to compile without injecting into the domain
- 4. Extract and Use Generated Types
- Use
GetTypeFromShortName("ClassName") to retrieve compiled types
- Use
GetDelegateFromShortName<T>("ClassName", "MethodName") for delegates
Three Core Usage Patterns
Pattern 1: Dynamic Class Generation
CODEBLOCK7
Pattern 2: Dynamic Delegate Generation
CODEBLOCK8
Important: The delegate type parameter T must exactly match the method signature.
Pattern 3: Accessing Private Members (V9 API)
V9 introduces a cleaner API for private member access. Two key steps:
- 1. Call
builder.WithPrivateAccess() to enable private compilation on the builder - Call
script.ToAccessPrivateTree(...) to rewrite the syntax tree with access attributes
CODEBLOCK9
Load Context Management
Different scenarios require different load context management strategies:
- - Random context (
UseRandomLoadContext()): Each compilation creates a new isolated load context. Use for most cases where isolation is desired. - Named context (
UseNewLoadContext("name")): Create a persistent named context for reuse across multiple compilations. - Existing context (
UseExistLoadContext(context) or UseExistLoadContext(domain)): Compile in an existing context/domain, enabling cross-assembly type references. - Default context (
UseDefaultLoadContext()): Use the default shared context (least isolation).
Reference: See references/context-management.md for detailed load context lifecycle patterns.
Advanced & V9 Features
Reuse Optimization for Repeated Compilations
When reusing a builder for multiple compilations, V9 provides reuse APIs to skip re-creating expensive objects:
CODEBLOCK10
Note: Use WithoutPreCompilationOptions() (default) if you need to switch debug/release or unsafe/nullable between compilations.
Compile Without Injecting Assembly
Use CompileWithoutAssembly() when you only need the compilation result (e.g., validation, syntax check) without loading the assembly into the domain:
CODEBLOCK11
External Exception Retrieval
V9 adds GetException() to retrieve compilation errors outside the compilation lifecycle:
CODEBLOCK12
Excluding Specific Using Namespaces
Prevent certain namespaces from being auto-injected into the syntax tree:
CODEBLOCK13
Forced Output Cleanup on Repeated Compilation
CODEBLOCK14
Debug Compilation Levels
CODEBLOCK15
Note: Before using dynamic debugging, disable [Address-level debugging] in Tools → Options → Debugging.
Compiler Options
Configure compiler behavior through ConfigCompilerOption():
CODEBLOCK16
Reference: See references/compiler-options.md for complete compiler configuration options.
Important Notes
Core Concepts
- - AssemblyCSharpBuilder is the main API for dynamic compilation in Natasha
- NatashaManagement handles global initialization and compiler setup
- Load Context is what AssemblyCSharpBuilder uses internally via
UseRandomLoadContext(), UseNewLoadContext(), etc. - Domain (from
DotNetCore.Natasha.Domain) is used separately for plugin management via new NatashaDomain(key) or INLINECODE45 - Do NOT confuse:
UseNewLoadContext() (AssemblyCSharpBuilder method) ≠ new NatashaDomain(key) (plugin system)
Modern API (Recommended)
- - Always use:
AssemblyCSharpBuilder with UseRandomLoadContext() / UseNewLoadContext() / INLINECODE51 - Initialize once: INLINECODE52
- Deprecated: Old methods like
UseRandomDomain(), UseNewDomain(), UseDefaultDomain() are marked [Obsolete] — use UseRandomLoadContext(), UseDefaultLoadContext() instead
Best Practices
- 1. Initialize once at application startup: Do not reinitialize on every compilation
- Cache delegates: Store compiled delegates for frequently used functions to avoid recompilation
- Choose the right mode:
- Smart mode + memory refs → fast startup, adequate metadata
- Smart mode + ref assembly refs → slowest startup, most complete metadata
- Simple mode → precise control, minimal footprint
- Custom mode → you own everything, maximum control
- 4. Use
WithFileUsingCache when stable: Only enable once the project's dependencies won't change - Isolate contexts: Use
UseRandomLoadContext() to avoid load context pollution - Handle errors: Use
GetException() after compilation to catch errors gracefully
Performance Considerations
- - Compiled delegates have zero reflection overhead after compilation
- First compilation has overhead for initializing the compilation service
- INLINECODE62 +
WithPreCompilationReferences() significantly reduce repeated compilation overhead - INLINECODE64 speeds up application restarts when dependencies are stable
- Subsequent compilations in the same named context are faster than random context compilations
Reference Files
This skill includes the following reference materials:
- -
references/initialization-patterns.md - Complete initialization method variations - INLINECODE66 - Load context lifecycle and management patterns
- INLINECODE67 - Compiler configuration options and flags
- INLINECODE68 - Migration from deprecated Template API
- INLINECODE69 - Real-world usage patterns and recipes
- INLINECODE70 - Common errors and solutions
- INLINECODE71 - 完整错误处理指南(推荐)
- INLINECODE72 - 私有成员访问最佳实践
- INLINECODE73 - 重复编译优化分析
Reference these files when encountering specific scenarios or needing detailed configuration guidance.
Additional Resources
- - Natasha GitHub: https://github.com/dotnetcore/Natasha
- Official Documentation: https://natasha.dotnetcore.xyz/zh-Hans/docs
- NuGet Package (Compiler): https://www.nuget.org/packages/DotNetCore.Natasha.CSharp.Compiler
- NuGet Package (Domain): https://www.nuget.org/packages/DotNetCore.Natasha.CSharp.Compiler.Domain
- Source Code: G:\Project\OpenSource\Natasha (all packages prefixed with
DotNetCore.)
Version: 3.3
Last Updated: 2026-03-30
Author Note: V3.3: 深入源码学习,揭秘 GetAvailableCompilation() 核心流程、UsingAnalysistor 智能纠错原理、MethodCreator 内部实现、泛型 MethodInfo 缓存技巧、ALC 域加载策略、事件驱动架构等。
扩展包说明
Natasha 提供了多个官方扩展包,按需引入:
| 扩展包 | 用途 | NuGet |
|---|
| INLINECODE75 | 动态委托生成,最简洁的 ToFunc<T>() API | 封装了动态方法创建的简化流程 |
| INLINECODE77 |
编译"学习"机制,自适应优化 using code | 适合重复编译相似脚本 |
|
DotNetCore.Natasha.CSharp.Extension.HotReload | 热重载支持 | 运行时更新代码 |
|
DotNetCore.Natasha.CSharp.Extension.Codecov | 代码覆盖率支持 | 测试场景 |
API 设计规范
Natasha 的 API 遵循严格的命名规范,理解它们能帮助你快速找到需要的 API:
| 系列 | 语义 | 示例 |
|---|
| With | 条件开关/附加值 | INLINECODE80 , WithPrivateAccess(), INLINECODE82 |
| Set |
单向赋值 |
SetAssemblyName(),
SetDllFilePath() |
|
Config | 组件深入配置 |
ConfigCompilerOption(opt=>...),
ConfigSyntaxOptions(opt=>...) |
|
Use | 核心行为选择 |
UseRandomLoadContext(),
UseSmartMode(),
UseSimpleMode() |
|
Add | 添加内容 |
Add(code),
AddReferenceAndUsingCode(type) |
|
Get | 获取结果 |
GetAssembly(),
GetTypeFromShortName() |
提示: 如果你找不到 API,先确定你要做什么(开关?赋值?配置?),然后去找对应的 With/Set/Config 系列。
Natasha C# 动态编译技能
目的
该技能使开发者能够使用 Natasha 库在运行时动态生成和编译 C# 代码。它支持创建动态类、生成高性能委托、访问现有类型的私有成员、管理隔离编译上下文以及精确控制元数据加载策略。
何时使用此技能
在以下情况下使用此技能:
- - 在运行时动态创建具有自定义属性和方法的类
- 为计算代码生成高性能委托,避免反射开销
- 从动态编译的代码访问现有类型的私有字段或方法
- 构建需要运行时 C# 编译的表达式树或代码生成工具
- 创建行为在运行时确定的可扩展插件系统
- 通过动态方法生成优化性能关键代码路径
- 需要精细控制元数据和使用代码范围(精简元数据模式)
如何使用此技能
先决条件
在项目中安装所需的 NuGet 包:
bash
核心编译包(基础编译单元)
dotnet add package DotNetCore.Natasha.CSharp.Compiler
域实现包(域实现包)
dotnet add package DotNetCore.Natasha.CSharp.Compiler.Domain
注意: DotNetCore.Natasha.CSharp.Compiler.Domain 继承自 DotNetCore.Natasha.Domain,并实现了 Natasha 编译器所需的编译绑定接口。所有包均以 DotNetCore. 为前缀。
框架支持: .NET Core 3.1+、.NET 5.0+、.NET 6.0+、.NET 7.0+、.NET 8.0+
三种编译模式
模式 1:智能模式 — 推荐用于大多数情况
智能模式自动合并默认域和当前域的元数据及 using 代码,并启用语义检查。
关键决策:内存程序集 vs 引用程序集
| 维度 | 内存程序集 | 引用程序集 |
|---|
| 元数据覆盖范围 | 仅运行时类型 | 完整元数据(包括未加载的程序集) |
| 内存使用 |
较低 | 较高 |
| 启动速度 | 较快 | 较慢 |
| 推荐用于 | 大多数场景 | 需要完整元数据,不关心内存/大小 |
内存程序集初始化(内存程序集预热):
csharp
NatashaManagement
.GetInitializer()
.WithMemoryUsing() // 从内存程序集中提取 using 代码
.WithMemoryReference() // 从内存程序集中提取元数据
.Preheating();
引用程序集初始化(引用程序集预热):
csharp
NatashaManagement
.GetInitializer()
.WithRefUsing() // 从引用程序集文件中提取 using 代码
.WithRefReference() // 从引用程序集文件中提取元数据(最完整)
.Preheating();
经验法则: 如果用户不关心程序大小或内存消耗,使用 WithRefUsing().WithRefReference() — 引用程序集提供最完整的元数据覆盖。
使用文件缓存(文件缓存优化):
当项目稳定后(不再添加新依赖),启用 WithFileUsingCache() 将 using 代码写入 Natasha.Namespace.cache 文件。后续运行时 Natasha 从缓存读取,无需扫描程序集,显著加快启动速度:
csharp
NatashaManagement
.GetInitializer()
.WithMemoryUsing()
.WithMemoryReference()
.WithFileUsingCache() // 将 using 代码缓存到磁盘(依赖稳定时使用)
.Preheating();
智能模式下的使用:
csharp
AssemblyCSharpBuilder builder = new();
builder
.UseRandomLoadContext()
.UseSmartMode() // 合并当前域和默认域引用,启用语义检查
.Add(public class A { public int Value { get; set; } });
var assembly = builder.GetAssembly();
模式 2:精简模式(自管理元数据模式)
当需要精确控制哪些元数据参与编译时,使用精简模式。不使用全局预热元数据 — 通过 ConfigLoadContext 仅添加所需内容。
csharp
// 精简模式不需要全局 Preheating()
NatashaManagement.RegistDomainCreator();
AssemblyCSharpBuilder builder = new();
builder
.UseRandomLoadContext()
.UseSimpleMode() // 不合并引用,不进行语义检查
.ConfigLoadContext(ldc => ldc
.AddReferenceAndUsingCode(typeof(Math).Assembly) // 添加 Math 及其所有依赖
.AddReferenceAndUsingCode(typeof(MathF)) // 添加 MathF 的程序集
.AddReferenceAndUsingCode(typeof(object))) // 添加核心运行时
.Add(public static class A { public static double Calc(double v) { return Math.Floor(v/0.3); } });
var assembly = builder.GetAssembly();
精简模式的关键点:
- - AddReferenceAndUsingCode(typeof(T)) — 加载 T 的程序集及其依赖程序集,并从中提取所有 using 代码
- AddReferenceAndUsingCode(typeof(T).Assembly) — 相同,但接受 Assembly 对象
- 在精简模式下,using 指令会自动从添加的程序集中收集,因此无需在脚本中显式编写(除非使用 WithoutCombineUsingCode)
- 使用 AppendExceptUsings(System.IO, MyNamespace) 显式排除某些 using 命名空间,使其不被注入语法树
模式 3:自定义编译模式
当您提供自己的完整元数据集(例如来自 Basic.Reference.Assemblies NuGet 包)并自行处理 using 代码时使用。
csharp
// 您自行准备元数据集合,例如来自 Basic.Reference.Assemblies
IEnumerable myRefs = Basic.Reference.Assemblies.Net80.References.All;
AssemblyCSharpBuilder builder = new();
builder
.UseRandomDomain()
.WithSpecifiedReferences(myRefs) // 仅使用这些引用(不使用域引用)
.WithoutCombineUsingCode() // 不自动注入 using 代码
.WithReleaseCompile()
.Add(using System; using static System.Math; public static class A { public static int Test(int a, int b) { return a + b; } });
// 注意:使用 WithoutCombineUsingCode() 时,请在脚本中手动包含 using 指令
var assembly = builder.GetAssembly();
WithSpecifiedReferences 使构建器忽略默认域和当前域的元数据,仅使用显式提供的引用。
核心工作流
- 1. 初始化 Natasha(每个应用程序启动时执行一次)
- 根据需求选择智能模式或精简模式
- 参考:参见 references/initialization-patterns.md 了解所有支持的初始化方法
- 2. 创建 AssemblyCSharpBuilder
- 实例化 new AssemblyCSharpBuilder()
- 配置加载上下文:使用 UseRandomLoadContext() 实现隔离,使用 UseNewLoadContext(name) 实现持久化
- 选择编译模式:UseSmartMode() / UseSimpleMode() / 自定义
- 设置编译级别:WithReleaseCompile() 或 WithDebugCompile()
- 3. 添加并编译代码
- 使用 .Add(csharpCode) 添加代码字符串
- 调用 .GetAssembly() 编译并获取程序集
- 或调用 .CompileWithoutAssembly() 编译但不注入域
- 4. 提取并使用生成的类型
- 使用 GetTypeFromShortName(ClassName) 检索编译后的类型
- 使用 GetDelegateFromShortName
(ClassName, MethodName) 获取委托
三种核心使用模式
模式 1:动态类生成
csharp
// 初始化(应用程序启动时执行一次)
NatashaManagement
.GetInitializer()
.WithMemoryUsing()
.WithMemoryReference()
.Preheating();
AssemblyCSharpBuilder builder = new();
builder
.UseRandomLoadContext()
.UseSmartMode();
builder.Add(@
public class DynamicPerson {
public string Name { get; set; }
public int Age { get; set; }
public string Greet() => $Hello, Im {Name}, age {Age};
}
);
var assembly = builder.GetAssembly();
var personType = assembly.GetTypeFromShortName(DynamicPerson);
var instance = Activator.CreateInstance(personType);
personType.GetProperty(Name)!.SetValue(instance, Alice);
var greeting = personType.GetMethod(Greet)!.Invoke(instance, null);
模式 2:动态委托生成
csharp
AssemblyCSharpBuilder builder = new();
builder
.UseRandomLoadContext()
.UseSmartMode()
.Add(@
public class MathHelper {
public static int Add