如何在C#源代码生成器中移植并执行用户代码?
针对Roslyn源代码生成器中Lambda表达式依赖处理的解决方案
1. 确定LambdaExpressionSyntax的所有依赖
要精准获取Lambda的全部依赖,必须结合Roslyn的语义分析(SemanticModel),而非仅靠语法树遍历。具体操作:
- 从目标Lambda节点出发,递归遍历所有子语法节点(如MemberAccess、Literal、GenericName等)
- 对每个节点调用
semanticModel.GetSymbolInfo(node).Symbol获取对应语义符号 - 收集命名空间:对非空符号,提取
symbol.ContainingNamespace,去重后作为需添加的using指令 - 收集程序集依赖:获取符号的
symbol.ContainingAssembly,记录其绝对路径(通过assembly.Location),排除重复引用 - 额外处理:lambda的类型参数、扩展方法定义程序集、嵌套类型父程序集等特殊场景,确保无遗漏
示例代码片段:
var lambdaSyntax = // 你的LambdaExpressionSyntax节点 var semanticModel = // 从生成器Context获取SemanticModel var requiredNamespaces = new HashSet<string>(); var requiredAssemblies = new HashSet<string>(); void Traverse(SyntaxNode node) { if (node is null) return; var symbolInfo = semanticModel.GetSymbolInfo(node); var symbol = symbolInfo.Symbol ?? symbolInfo.CandidateSymbols?.FirstOrDefault(); if (symbol != null) { // 收集命名空间 var ns = symbol.ContainingNamespace; while (ns != null && !string.IsNullOrEmpty(ns.Name)) { requiredNamespaces.Add(ns.ToDisplayString()); ns = ns.ContainingNamespace; } // 收集程序集 var assembly = symbol.ContainingAssembly; if (!string.IsNullOrEmpty(assembly.Location)) { requiredAssemblies.Add(assembly.Location); } } // 递归遍历子节点 foreach (var child in node.ChildNodes()) { Traverse(child); } } Traverse(lambdaSyntax.Body);
2. 调用CSharpScript.EvaluateAsync时正确添加依赖
解决"程序集未找到"的核心是使用程序集绝对路径而非仅名称,同时确保引用与当前编译环境一致:
- 用
ScriptOptions.Create()初始化选项,调用AddReferences添加收集到的程序集路径 - 调用
AddImports添加收集到的命名空间 - 系统程序集(如
System.Runtime)可通过typeof(Guid).Assembly.Location直接获取路径 - 自定义库引用需从生成器的
Compilation.References中找到对应PortableExecutableReference,提取其FilePath作为引用路径
示例代码:
var scriptOptions = ScriptOptions.Default; // 添加程序集引用 foreach (var assemblyPath in requiredAssemblies) { scriptOptions = scriptOptions.AddReferences(assemblyPath); } // 添加using指令 foreach (var ns in requiredNamespaces) { scriptOptions = scriptOptions.AddImports(ns); } // 构造执行代码,将lambda转为表达式树并获取字符串 var lambdaCode = lambdaSyntax.ToFullString(); var code = $"System.Linq.Expressions.Expression<Func<MyType, string>> expr = {lambdaCode}; expr.ToString()"; var result = await CSharpScript.EvaluateAsync<string>(code, scriptOptions);
3. 更简便的实现方式
无需借助CSharpScript,直接利用Roslyn自身API即可完成需求,避免额外编译开销:
- 手动解析表达式结构:遍历Lambda语法节点,直接解析成员访问、字符串插值等逻辑,生成对应字符串表示,完全不需要运行时编译
- 使用ExpressionTreeGenerator:引用
Microsoft.CodeAnalysis.CSharp.Workspaces包,通过ExpressionTreeGenerator类从LambdaSyntax和SemanticModel直接生成表达式树对象,无需手动处理依赖 - 复用当前编译环境:若需运行lambda,可基于生成器的
Compilation创建CSharpScript选项,自动继承所有引用和命名空间:var scriptOptions = ScriptOptions.FromCompilation(compilation); var result = await CSharpScript.EvaluateAsync<string>(lambdaSyntax.ToFullString(), scriptOptions);
内容的提问来源于stack exchange,提问作者Jim Noble
相关产品推荐
相关产品推荐

