You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 23:25:20