如何在 C# 源代码生成器中移植和执行用户代码?

How Do I Transplant and Execute User Code in a C# Source Generator?

提问人:Jim Noble 提问时间:8/11/2023 更新时间:8/17/2023 访问量:59

问:

我正在创建一个 Roslyn .NET 源生成器。有了它,我正在检查用户代码中是否有在方法调用中作为参数给出的 lambda 表达式,如下所示:

// User Code
MyLibrary.MyMethod<Guid>(k => $"alfa/{k.SomeMethod()}");

我想在编译时在我的源代码生成器中,获取节点并将其移植到我将生成并运行的一些简单代码中,以询问它,例如它的运行时表达式树形状或调用它的方法:LambdaExpressionSyntaxk => $"alfa/{k.SomeMethod()}".ToString()

// Generated Code
using System;
using Etc;
Expression<Func<Guid, string>> expression = k => $"alfa/{k.SomeMethod()}";
return expression;

到目前为止,我已经发现了这种方法,这似乎是编译和执行代码的最简单方法。CSharpScript.EvaluateAsync()

不幸的是,这种(可能还有任何)方法要求我找出我尝试移植的表达式的依赖关系是什么,以便我可以生成适当的语句并将所需的库引用添加到编译中。using

例如,上面是一个扩展方法,它位于与检查的用户代码不同的类库中。它的命名空间和类库需要包含在 s 和编译引用中。SomeMethod()using

假设我有一个神奇的方法来获取引用的类型,到目前为止我所拥有的是这样的:

var referencedTypes = GetReferencedTypes(lambdaExpressionSyntax);

var result = CSharpScript
    .EvaluateAsync(
        code,
        ScriptOptions.Default
            .AddReferences(
                typeof(Func<,>).Assembly,
                typeof(Expression).Assembly)
            .AddReferences(referencedTypes
                .Select(t => t.ContainingAssembly.GetMetadata().GetReference())
                .ToArray()))
    .Result;

不幸的是,我尝试添加引用的方式似乎不起作用。我在运行时收到找不到程序集的错误。

所以我的问题是:

  1. 如何正确确定给定的所有依赖关系?LambdaExpressionSyntax
  2. 如何在调用时正确添加对这些依赖项的引用?CSharpScript.EvaluateAsync()
  3. 有没有更好或更简单的方法来实现我想要做的事情?
C# Roslyn 表达式树 源生成器 csharpscript

评论


答:

0赞 Jim Noble 8/17/2023 #1

一旦我意识到移植代码所需的指令和引用必须是我正在移植的用户代码的子集,我就能够非常简单地实现上述目标。因此,我的源代码生成器方法的相关部分变成了:usingExecute

var code = $$"""
    Expression<Func<{{keyType.Name}}, string>> expr = {{lambdaExpression}};
    return expr;
    """;

var imports = lambdaExpression.SyntaxTree
    .GetRoot()
    .DescendantNodes()
    .OfType<UsingDirectiveSyntax>()
    .Select(u => u.Name.ToString())
    .Append("System")
    .Append("System.Linq.Expressions")
    .Append(keyType.ContainingNamespace.ToString())
    .Distinct()
    .OrderBy(u => u)
    .ToList();

var options = ScriptOptions.Default
    .AddReferences(
        typeof(Func<,>).Assembly,
        typeof(Expression).Assembly)
    .AddReferences(
        compilation.References)
    .AddImports(imports);

var result = await CSharpScript.EvaluateAsync(code, options);

至关重要的是,我为 and 方法提供了从用户代码的语法树和编译中派生的值。AddReferencesAddImports

如果移植的用户代码引用了例如局部变量或类成员,这仍然不起作用。这些不是我的工作所支持的用例,因此我可能会为这些场合添加优雅的错误处理。