C#动态编译指的是在程序运行期间,将字符串形式的C#代码实时编译为可执行的程序集,并且可以直接调用编译后生成的方法或类型,这项技术能够极大提升程序的灵活性,支持动态扩展功能。

C#动态编译的两种主流实现方案
目前C#中实现动态编译主要有两种常用方式,分别是传统的CodeDom编译方案和微软推出的Roslyn编译方案,二者各有适用场景。
1. CodeDom编译方案
CodeDom是.NET Framework自带的代码生成与编译命名空间,不需要额外引入第三方依赖,适合旧版本.NET项目使用,但是功能相对有限,对C#新语法支持不足。
使用CodeDom实现动态编译的核心步骤如下:
- 创建
CSharpCodeProvider实例,用于提供C#代码编译能力 - 配置编译参数,包括输出程序集路径、引用的程序集等
- 调用
CompileAssemblyFromSource方法执行编译 - 从编译生成的程序集中获取类型并调用方法
下面是完整的CodeDom动态编译实例源码:
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
namespace DynamicCompileDemo
{
class CodeDomCompileExample
{
static void Main(string[] args)
{
// 待编译的C#代码片段
string code = @"
using System;
namespace DynamicNamespace
{
public class DynamicClass
{
public int Add(int a, int b)
{
return a + b;
}
public void PrintHello()
{
Console.WriteLine(""动态编译的方法执行成功"");
}
}
}";
// 创建C#代码编译提供者
using (CSharpCodeProvider provider = new CSharpCodeProvider())
{
// 配置编译参数
CompilerParameters parameters = new CompilerParameters();
// 添加引用的基础程序集
parameters.ReferencedAssemblies.Add("System.dll");
// 不生成可执行文件,生成动态程序集
parameters.GenerateInMemory = true;
// 不生成调试信息
parameters.IncludeDebugInformation = false;
// 执行编译
CompilerResults result = provider.CompileAssemblyFromSource(parameters, code);
// 检查编译是否有错误
if (result.Errors.HasErrors)
{
Console.WriteLine("编译失败,错误信息:");
foreach (CompilerError error in result.Errors)
{
Console.WriteLine($"行{error.Line}:{error.ErrorText}");
}
return;
}
// 获取编译后的程序集
Assembly assembly = result.CompiledAssembly;
// 获取动态生成的类型
Type dynamicType = assembly.GetType("DynamicNamespace.DynamicClass");
// 创建类型实例
object instance = Activator.CreateInstance(dynamicType);
// 获取Add方法并执行
MethodInfo addMethod = dynamicType.GetMethod("Add");
object addResult = addMethod.Invoke(instance, new object[] { 10, 20 });
Console.WriteLine($"Add方法执行结果:{addResult}");
// 获取PrintHello方法并执行
MethodInfo printMethod = dynamicType.GetMethod("PrintHello");
printMethod.Invoke(instance, null);
}
}
}
}
2. Roslyn编译方案
Roslyn是微软推出的新一代.NET编译器平台,支持C#全量语法,编译速度更快,还能提供丰富的语法分析能力,适合.NET Core及以上版本的项目,需要额外引入NuGet包Microsoft.CodeAnalysis.CSharp。
使用Roslyn实现动态编译的核心步骤如下:
- 引入Roslyn相关NuGet包
- 创建语法树,解析输入的C#代码片段
- 配置编译选项,添加引用的元数据引用
- 调用编译方法生成程序集
- 加载程序集并调用目标方法
下面是完整的Roslyn动态编译实例源码:
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
namespace DynamicCompileDemo
{
class RoslynCompileExample
{
static void Main(string[] args)
{
// 待编译的C#代码片段
string code = @"
using System;
namespace DynamicNamespace
{
public class DynamicClass
{
public int Multiply(int a, int b)
{
return a * b;
}
public string GetCurrentTime()
{
return DateTime.Now.ToString(""yyyy-MM-dd HH:mm:ss"");
}
}
}";
// 解析C#代码生成语法树
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
// 获取引用的核心程序集元数据
string coreLibPath = typeof(object).GetTypeInfo().Assembly.Location;
MetadataReference[] references = new MetadataReference[]
{
MetadataReference.CreateFromFile(coreLibPath)
};
// 创建编译对象
CSharpCompilation compilation = CSharpCompilation.Create(
"DynamicAssembly",
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
);
// 将编译结果输出到内存流
using (MemoryStream ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
// 检查编译是否有错误
if (!result.Success)
{
Console.WriteLine("编译失败,错误信息:");
foreach (Diagnostic diagnostic in result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error))
{
Console.WriteLine(diagnostic.GetMessage());
}
return;
}
// 重置内存流位置并加载程序集
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(ms.ToArray());
// 获取动态生成的类型
Type dynamicType = assembly.GetType("DynamicNamespace.DynamicClass");
// 创建类型实例
object instance = Activator.CreateInstance(dynamicType);
// 获取Multiply方法并执行
MethodInfo multiplyMethod = dynamicType.GetMethod("Multiply");
object multiplyResult = multiplyMethod.Invoke(instance, new object[] { 5, 6 });
Console.WriteLine($"Multiply方法执行结果:{multiplyResult}");
// 获取GetCurrentTime方法并执行
MethodInfo timeMethod = dynamicType.GetMethod("GetCurrentTime");
object timeResult = timeMethod.Invoke(instance, null);
Console.WriteLine($"当前时间:{timeResult}");
}
}
}
}
两种动态编译方案的对比
为了帮助开发者选择合适的动态编译方案,下面从多个维度对两种方案进行对比:
| 对比维度 | CodeDom方案 | Roslyn方案 |
|---|---|---|
| 适用框架 | .NET Framework | .NET Core、.NET 5+ |
| 新语法支持 | 仅支持旧版C#语法 | 支持全量C#新语法 |
| 额外依赖 | 无,框架内置 | 需要引入Roslyn NuGet包 |
| 编译性能 | 较低 | 较高 |
| 语法分析能力 | 弱 | 强,支持语法检查、补全 |
动态编译的注意事项
在实际项目中使用动态编译功能时,需要注意以下几点:
- 安全问题:动态编译的代码如果来自外部输入,可能存在恶意代码注入风险,需要对输入代码做严格的校验和沙箱隔离
- 性能问题:动态编译本身有一定的性能开销,频繁编译相同代码时建议增加缓存机制,避免重复编译
- 异常处理:编译失败、方法调用失败的场景都需要做好异常捕获,避免程序崩溃
- 程序集卸载:动态编译生成的程序集默认会加载到应用域中,无法单独卸载,如果需要频繁更新动态代码,建议使用独立的应用域实现卸载
动态编译是C#中非常实用的高级特性,合理运用可以解决很多场景下的灵活扩展需求,开发者可以根据项目的框架版本和功能需求选择合适的实现方案。