Install
openclaw skills install csharp-dotnetcore-natashaThis skill should be used when developers need to create dynamic C# features at runtime, including dynamic class generation, dynamic method creation, accessing private members through dynamic compilation, and managing compilation metadata strategies using the Natasha library.
openclaw skills install csharp-dotnetcore-natashaThis 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.
Use this skill when:
Install the required NuGet packages in your project:
# Core compiler package (基础编译单元)
dotnet add package DotNetCore.Natasha.CSharp.Compiler
# Domain implementation package (域实现包)
dotnet add package DotNetCore.Natasha.CSharp.Compiler.Domain
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 DotNetCore.
Framework Support: .NET Core 3.1+, .NET 5.0+, .NET 6.0+, .NET 7.0+, .NET 8.0+
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 (内存程序集预热):
NatashaManagement
.GetInitializer()
.WithMemoryUsing() // Extract using-code from in-memory assemblies
.WithMemoryReference() // Extract metadata from in-memory assemblies
.Preheating<NatashaDomainCreator>();
Reference assembly initialization (引用程序集预热):
NatashaManagement
.GetInitializer()
.WithRefUsing() // Extract using-code from reference assembly files
.WithRefReference() // Extract metadata from reference assembly files (most complete)
.Preheating<NatashaDomainCreator>();
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:
NatashaManagement
.GetInitializer()
.WithMemoryUsing()
.WithMemoryReference()
.WithFileUsingCache() // Cache using-code to disk (use when dependencies are stable)
.Preheating<NatashaDomainCreator>();
Usage in smart mode:
AssemblyCSharpBuilder builder = new();
builder
.UseRandomLoadContext()
.UseSmartMode() // Merges current + default domain refs, enables semantic check
.Add("public class A { public int Value { get; set; } }");
var assembly = builder.GetAssembly();
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.
// No global Preheating() needed for simple mode
NatashaManagement.RegistDomainCreator<NatashaDomainCreator>();
AssemblyCSharpBuilder builder = new();
builder
.UseRandomLoadContext()
.UseSimpleMode() // No combined references, no semantic check
.ConfigLoadContext(ldc => ldc
.AddReferenceAndUsingCode(typeof(Math).Assembly) // Adds Math + all its deps
.AddReferenceAndUsingCode(typeof(MathF)) // Adds MathF's assembly
.AddReferenceAndUsingCode(typeof(object))) // Adds core runtime
.Add("public static class A { public static double Calc(double v) { return Math.Floor(v/0.3); } }");
var assembly = builder.GetAssembly();
Key points for simple mode:
AddReferenceAndUsingCode(typeof(T)) — loads T's assembly AND its dependency assemblies, plus extracts all using-code from themAddReferenceAndUsingCode(typeof(T).Assembly) — same but takes an Assembly objectusing 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)AppendExceptUsings("System.IO", "MyNamespace") to explicitly exclude certain using namespaces from being injected into the syntax treeUse when you provide your own complete metadata set (e.g., from Basic.Reference.Assemblies NuGet package) and handle using-code yourself.
// You prepare the metadata collection yourself, e.g. from Basic.Reference.Assemblies
IEnumerable<MetadataReference> myRefs = Basic.Reference.Assemblies.Net80.References.All;
AssemblyCSharpBuilder builder = new();
builder
.UseRandomDomain()
.WithSpecifiedReferences(myRefs) // Use ONLY these references (no domain refs)
.WithoutCombineUsingCode() // Do NOT auto-inject using-code
.WithReleaseCompile()
.Add("using System; using static System.Math; public static class A { public static int Test(int a, int b) { return a + b; } }");
// Note: when WithoutCombineUsingCode() is used, include using directives manually in your script
var assembly = builder.GetAssembly();
WithSpecifiedReferences makes the builder ignore both the default domain and current domain metadata, using only the explicitly provided references.
Initialize Natasha (once per application startup)
references/initialization-patterns.md for all supported initialization methodsCreate AssemblyCSharpBuilder
new AssemblyCSharpBuilder()UseRandomLoadContext() for isolation, UseNewLoadContext("name") for persistenceUseSmartMode() / UseSimpleMode() / customWithReleaseCompile() or WithDebugCompile()Add and Compile Code
.Add(csharpCode) to add code strings.GetAssembly() to compile and retrieve the assembly.CompileWithoutAssembly() to compile without injecting into the domainExtract and Use Generated Types
GetTypeFromShortName("ClassName") to retrieve compiled typesGetDelegateFromShortName<T>("ClassName", "MethodName") for delegates// Initialize (once at application startup)
NatashaManagement
.GetInitializer()
.WithMemoryUsing()
.WithMemoryReference()
.Preheating<NatashaDomainCreator>();
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, I'm {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);
AssemblyCSharpBuilder builder = new();
builder
.UseRandomLoadContext()
.UseSmartMode()
.Add(@"
public class MathHelper {
public static int Add(int a, int b) => a + b;
}
");
var assembly = builder.GetAssembly();
var addFunc = assembly.GetDelegateFromShortName<Func<int, int, int>>("MathHelper", "Add");
int result = addFunc(3, 5); // result: 8
Important: The delegate type parameter T must exactly match the method signature.
V9 introduces a cleaner API for private member access. Two key steps:
builder.WithPrivateAccess() to enable private compilation on the builderscript.ToAccessPrivateTree(...) to rewrite the syntax tree with access attributes// Add IgnoresAccessChecksToAttribute.cs to your project (see references/troubleshooting.md)
AssemblyCSharpBuilder builder = new();
builder
.UseRandomLoadContext()
.UseSmartMode()
.WithPrivateAccess(); // V9: enable private member access on the builder
string script = @"
public class Accessor {
public static int GetSecret(UserModel model) {
return model._secret; // private field
}
}
";
// Pass the target type (or namespace string, or instance) to ToAccessPrivateTree
builder.Add(script.ToAccessPrivateTree(typeof(UserModel)));
// OR: builder.Add(script.ToAccessPrivateTree("MyNamespace.Assembly", "OtherNamespace"));
// OR: builder.Add(script.ToAccessPrivateTree(instance1, instance2));
var assembly = builder.GetAssembly();
var getSecret = assembly.GetDelegateFromShortName<Func<UserModel, int>>("Accessor", "GetSecret");
Different scenarios require different load context management strategies:
UseRandomLoadContext()): Each compilation creates a new isolated load context. Use for most cases where isolation is desired.UseNewLoadContext("name")): Create a persistent named context for reuse across multiple compilations.UseExistLoadContext(context) or UseExistLoadContext(domain)): Compile in an existing context/domain, enabling cross-assembly type references.UseDefaultLoadContext()): Use the default shared context (least isolation).Reference: See references/context-management.md for detailed load context lifecycle patterns.
When reusing a builder for multiple compilations, V9 provides reuse APIs to skip re-creating expensive objects:
builder
.WithPreCompilationOptions() // Reuse previous CSharpCompilationOptions (debug/release flags)
.WithPreCompilationReferences() // Reuse previous metadata reference set
.WithRandomAssenblyName() // Generate new GUID assembly name for each run
.Add(newCode)
.GetAssembly();
Note: Use
WithoutPreCompilationOptions()(default) if you need to switch debug/release or unsafe/nullable between compilations.
Use CompileWithoutAssembly() when you only need the compilation result (e.g., validation, syntax check) without loading the assembly into the domain:
builder.Add("public class A {}").CompileWithoutAssembly();
// Assembly is compiled but NOT injected into the load context domain
V9 adds GetException() to retrieve compilation errors outside the compilation lifecycle:
var assembly = builder.GetAssembly(); // May suppress exceptions internally
var ex = builder.GetException(); // Retrieve if something went wrong
if (ex != null) { /* handle */ }
Prevent certain namespaces from being auto-injected into the syntax tree:
builder.AppendExceptUsings("System.IO", "System.Net", "MyConflictingNamespace");
builder.WithForceCleanOutput(); // Delete previous output file before recompiling
// Default: WithoutForceCleanOutput() — renames old file to repeate.{guid}.oldname
// Standard debug (sufficient for most debugging needs)
builder.WithDebugCompile(opt => opt.ForCore());
// Enhanced debug — more granular output including implicit conversions
builder.WithDebugPlusCompile(opt => opt.ForCore());
// Release with debug info embedded (production tracing)
builder.WithReleasePlusCompile();
Note: Before using dynamic debugging, disable [Address-level debugging] in Tools → Options → Debugging.
Configure compiler behavior through ConfigCompilerOption():
builder.ConfigCompilerOption(opt => opt
.AppendCompilerFlag(CompilerBinderFlags.IgnoreAccessibility) // Bypass access checks
.WithAllMetadata() // Access all metadata levels
.AppendNullableFlag(NullableContextOptions.Enable) // Enable nullable annotations
);
Reference: See references/compiler-options.md for complete compiler configuration options.
UseRandomLoadContext(), UseNewLoadContext(), etc.DotNetCore.Natasha.Domain) is used separately for plugin management via new NatashaDomain(key) or DomainManagementUseNewLoadContext() (AssemblyCSharpBuilder method) ≠ new NatashaDomain(key) (plugin system)AssemblyCSharpBuilder with UseRandomLoadContext() / UseNewLoadContext() / UseSmartMode()NatashaManagement.GetInitializer().WithMemoryUsing().WithMemoryReference().Preheating<NatashaDomainCreator>()UseRandomDomain(), UseNewDomain(), UseDefaultDomain() are marked [Obsolete] — use UseRandomLoadContext(), UseDefaultLoadContext() insteadWithFileUsingCache when stable: Only enable once the project's dependencies won't changeUseRandomLoadContext() to avoid load context pollutionGetException() after compilation to catch errors gracefullyWithPreCompilationOptions() + WithPreCompilationReferences() significantly reduce repeated compilation overheadWithFileUsingCache() speeds up application restarts when dependencies are stableThis skill includes the following reference materials:
references/initialization-patterns.md - Complete initialization method variationsreferences/context-management.md - Load context lifecycle and management patternsreferences/compiler-options.md - Compiler configuration options and flagsreferences/migration-guide.md - Migration from deprecated Template APIreferences/common-patterns.md - Real-world usage patterns and recipesreferences/troubleshooting.md - Common errors and solutionsCOMPILATION_ERROR_HANDLING.md - 完整错误处理指南(推荐)PRIVATE_MEMBER_ACCESS.md - 私有成员访问最佳实践REPEAT_COMPILE_OPTIMIZATION.md - 重复编译优化分析Reference these files when encountering specific scenarios or needing detailed configuration guidance.
DotNetCore.)Version: 3.3 Last Updated: 2026-03-30 Author Note: V3.3: 深入源码学习,揭秘 GetAvailableCompilation() 核心流程、UsingAnalysistor 智能纠错原理、MethodCreator 内部实现、泛型 MethodInfo 缓存技巧、ALC 域加载策略、事件驱动架构等。
Natasha 提供了多个官方扩展包,按需引入:
| 扩展包 | 用途 | NuGet |
|---|---|---|
DotNetCore.Natasha.CSharp.Extension.MethodCreator | 动态委托生成,最简洁的 ToFunc<T>() API | 封装了动态方法创建的简化流程 |
DotNetCore.Natasha.CSharp.Extension.CompileDirector | 编译"学习"机制,自适应优化 using code | 适合重复编译相似脚本 |
DotNetCore.Natasha.CSharp.Extension.HotReload | 热重载支持 | 运行时更新代码 |
DotNetCore.Natasha.CSharp.Extension.Codecov | 代码覆盖率支持 | 测试场景 |
Natasha 的 API 遵循严格的命名规范,理解它们能帮助你快速找到需要的 API:
| 系列 | 语义 | 示例 |
|---|---|---|
| With | 条件开关/附加值 | WithSmartMode(), WithPrivateAccess(), WithDebugCompile() |
| Set | 单向赋值 | SetAssemblyName(), SetDllFilePath() |
| Config | 组件深入配置 | ConfigCompilerOption(opt=>...), ConfigSyntaxOptions(opt=>...) |
| Use | 核心行为选择 | UseRandomLoadContext(), UseSmartMode(), UseSimpleMode() |
| Add | 添加内容 | Add(code), AddReferenceAndUsingCode(type) |
| Get | 获取结果 | GetAssembly(), GetTypeFromShortName() |
提示: 如果你找不到 API,先确定你要做什么(开关?赋值?配置?),然后去找对应的 With/Set/Config 系列。