商城首页欢迎来到中国正版软件门户

您的位置:首页 >c#如何动态编译代码_c#动态编译代码项目实例附完整源码

c#如何动态编译代码_c#动态编译代码项目实例附完整源码

  发布于2026-05-02 阅读(0)

扫一扫,手机访问

CSharpCodeProvider 在 .NET 6+ 中已彻底废弃,必须改用 Microsoft.CodeAnalysis(Roslyn)动态编译

c#如何动态编译代码_c#动态编译代码项目实例附完整源码

如果你还在尝试使用 CSharpCodeProvider 来动态编译代码,那么有一个坏消息:在 .NET 6 及更高版本中,这条路已经彻底走不通了。直接运行会报出 Type or namespace name 'CSharpCodeProvider' could not be found 的错误。别白费力气了,它已经是历史,只存在于 .NET Framework 和早期的 .NET Core 3.1 时代。

为什么 CSharpCodeProvider 编译失败或根本找不到

这往往是开发者遇到的第一个拦路虎。从 .NET 5 开始,微软就默认移除了对 CodeDOM 的支持,到了 .NET 6+ 更是完全删除了 Microsoft.CSharp 命名空间下的编译器类。即便你尝试在项目文件中添加 true 配置,或者手动引用 System.CodeDom NuGet 包,CSharpCodeProvider 的构造函数也会直接抛出 NotSupportedException 异常。

  • 原生 CSharpCodeProvider 仅在 net48netcoreapp3.1 项目中可用。
  • 目标框架为 net5.0 及以上的项目,必须转向使用 Microsoft.CodeAnalysis(即 Roslyn)。
  • 如果你的项目目标框架是 net6.0,却还在代码里写 new CSharpCodeProvider()

用 Roslyn 替代:编译字符串代码到内存程序集

新的解决方案核心是使用 CSharpCompilation 配合 Emit 方法,将输出写入 MemoryStream,最后通过 AssemblyLoadContext.Load 加载。流程比旧方式稍显繁琐,但带来的好处是显而易见的:控制力更强、完全跨平台,并且规避了旧方案中的反射漏洞风险。

  • 首先,需要安装 NuGet 包:Microsoft.CodeAnalysis.CSharp(建议使用 v4.0+ 版本)。
  • 必须显式添加所有依赖的程序集引用。例如,System.Console 来自 System.Runtime.dll,不能简单地只引用一个模糊的 "System.dll"
  • 一个常用的快捷方式是使用 MetadataReference.CreateFromFile(Assembly.GetExecutingAssembly().Location) 来引用当前程序集。但这里有个陷阱:如果执行程序集是单文件发布(即设置了 PublishTrimmed=true),那么 .Location 属性会返回空字符串。这时就需要回退方案,例如使用 typeof(object).Assembly.Location
  • 下面是一个关键代码片段的示例:
var syntaxTree = CSharpSyntaxTree.ParseText(code);
var references = new List {
    MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
    MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
    // 其他必要引用...
};
var compilation = CSharpCompilation.Create(
    assemblyName: $"dyn_{Guid.NewGuid():N}",
    syntaxTrees: new[] { syntaxTree },
    references: references,
    options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

using var ms = new MemoryStream();
var result = compilation.Emit(ms);
if (!result.Success) {
    var errors = result.Diagnostics.Where(d => d.IsWarningAsError || d.Severity == DiagnosticSeverity.Error);
    throw new InvalidOperationException(string.Join("\n", errors.Select(e => e.ToString())));
}
ms.Seek(0, SeekOrigin.Begin);
var assembly = AssemblyLoadContext.Default.LoadFromStream(ms);

动态调用方法时类型名和命名空间容易错

旧方法常常依赖于硬编码的类型名,比如 "DynamicClass""Test"。但在 Roslyn 编译后,类型名完全由源代码中的 class 声明决定。如果代码里写的是 namespace MyNS { public class Worker { ... } },那么你就必须使用 assembly.GetType("MyNS.Worker") 来获取类型,漏掉命名空间就会得到 null

  • 一个实用的建议是:在构造源码字符串时,强制指定命名空间。例如:@"using System; namespace Dyn { public class Entry { public static void Run() { Console.WriteLine(""ok""); } } }"
  • 调用静态方法使用 type.GetMethod("Run").Invoke(null, null);调用实例方法则需要先 Activator.CreateInstance(type) 创建对象。
  • 如果源代码中包含泛型或异步方法,使用 GetMethod 时必须传入精确的方法签名。例如:GetMethod("RunAsync", new[] { typeof(CancellationToken) })

安全与性能上几个硬约束

动态编译绝非玩具,一旦计划在生产环境中使用,就必须设定清晰的边界:

  • 超时控制:Roslyn 编译过程本身不支持 CancellationToken。一个常见的做法是用 Task.Run(...).Wait(timeout) 将其包裹起来,超时则取消任务并释放相关的 AssemblyLoadContext
  • 内存泄漏:每次调用 LoadFromStream 都可能注册一个新的上下文。如果不调用 AssemblyLoadContext.Unload,这些程序集将永久驻留内存。但 .NET 6+ 的默认上下文是不可卸载的,因此建议自定义一个派生类,并重写设置 IsCollectible = true
  • 源码安全校验:绝对禁止将未经处理的用户输入直接送入 ParseText。即使有沙箱环境,unsafestackalloc 或 P/Invoke 等代码仍有可能逃逸限制。在生产环境中,务必对源码的抽象语法树(AST)节点进行白名单校验。

其实,最让人头疼的往往是调试环节:编译错误信息都藏在 Diagnostics 集合里,其中的行号对应的是原始字符串中的位置,而非文件行号。一旦出错,你就得自己动手,按 \n 拆分源码字符串并标注行号——这个细节很少被提及,但上线第一天就很可能撞上。

本文转载于:https://www.php.cn/faq/2333138.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。
  • Ubuntu JS日志中如何识别潜在的安全威胁 正版软件
    Ubuntu JS日志中如何识别潜在的安全威胁
    Ubuntu JS日志中识别潜在安全威胁的实用指南 日志排查,听起来像是大海捞针?其实不然。只要你知道去哪里看、看什么,那些隐藏在日志行间的安全威胁就会自己“跳”出来。这份指南,就是帮你快速定位并解读这些关键信号。 一 日志来源与定位 排查的第一步,是搞清楚日志都藏在哪儿。不同的日志,讲述着系统不同
    4分钟前 0
  • 如何配置Ubuntu JS日志记录策略 正版软件
    如何配置Ubuntu JS日志记录策略
    Ubuntu 上 Node.js 日志策略配置指南 日志管理,听起来像是运维的活儿,但对于一个健壮的Node.js应用来说,它绝对是开发阶段就必须规划好的核心环节。一套清晰的日志策略,能让你在问题发生时,快速定位根因,而不是在茫茫控制台输出里大海捞针。下面,我们就来聊聊在Ubuntu环境下,如何为你
    4分钟前 0
  • Ubuntu JS日志中常见的错误码有哪些 正版软件
    Ubuntu JS日志中常见的错误码有哪些
    Ubuntu JS日志中常见错误码与含义 在Ubuntu上跑Ja vaScript应用,无论是Node.js服务还是前端项目,控制台或日志文件里蹦出的错误信息,常常是排查问题的第一道线索。这些错误码看似冰冷,实则每个都指向一个特定的“案发现场”。今天,咱们就来把这些常见的“黑话”翻译一下,帮你快速定
    5分钟前 0
  • Ubuntu JS日志中哪些信息有助于调试 正版软件
    Ubuntu JS日志中哪些信息有助于调试
    Ubuntu环境下JS日志的关键调试信息 面对一个棘手的线上问题,如何从海量日志中快速定位到关键线索?答案就在于,你的日志是否记录了真正有助于调试的信息。今天,我们就来梳理一下,在Ubuntu环境中,一份高质量的Ja vaScript日志应该包含哪些核心要素。 一 核心必记录字段 想让日志成为你的“
    5分钟前 0
  • 如何通过Ubuntu JS日志排查性能问题 正版软件
    如何通过Ubuntu JS日志排查性能问题
    从日志入手定位 Ubuntu 上 JS 性能问题的实用流程 一 准备可观测性 排查性能问题,第一步不是盲目猜测,而是建立清晰的观测体系。这就像医生看病,先得有检查报告。对于运行在Ubuntu上的Ja vaScript应用,日志就是最核心的“体检报告”。 结构化与分级日志:告别杂乱无章的console
    6分钟前 0