Roslyn:是否可以将变量传递给文档(SourceCodeKind.Script)

Roslyn: is it possible to pass variables to documents (with SourceCodeKind.Script)

在 Roslyn 脚本 API 中,可以将值作为 "globals" 对象的属性传递给脚本。

使用工作区API时可以做类似的事情吗?

这是我的示例代码:

var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            .WithOverflowChecks(true).WithOptimizationLevel(OptimizationLevel.Release)
            .WithUsings("System", "System.Collections", "System.Collections.Generic", "System.Dynamic", "System.Linq");

string userCode = "... end user's code goes here...";

using (var workspace = new AdhocWorkspace() { })
{
    string projName = "NewProject132";
    var projectId = ProjectId.CreateNewId();
    var projectInfo = ProjectInfo.Create(
        projectId,
        VersionStamp.Create(),
        projName,
        projName,
        LanguageNames.CSharp,
        isSubmission: true,
        compilationOptions: options,
        metadataReferences: references,
        parseOptions: new CSharpParseOptions(kind: SourceCodeKind.Script, languageVersion: LanguageVersion.Latest));
    var project = workspace.AddProject(projectInfo);

    var id = DocumentId.CreateNewId(project.Id);


    /*
        how do I declare variables that are supposed to be visible to the user's code?
        */

    var solution = project.Solution.AddDocument(id, project.Name, userCode);
    var document = solution.GetDocument(id);

    //get syntax and semantic errors
    var syntaxTree = document.GetSyntaxTreeAsync().Result;
    foreach (var syntaxError in syntaxTree.GetDiagnostics())
    {
        //...
    }
    var model = document.GetSemanticModelAsync().Result;
    foreach (var syntaxError in model.GetDiagnostics(new TextSpan(0, userCode.Length)))
    {
        //...
    }
    var completionService = CompletionService.GetService(document);
    var completions = completionService.GetCompletionsAsync(document, userCode.Length - 1).Result;
}

正在使用用户脚本填充文档,但脚本需要能够从主机应用程序访问某些值。

作为最后的手段,我可​​以在用户脚本之前添加变量声明,但这似乎有点混乱,我想尽可能避免它。

让脚本访问全局变量。

创建 ProjectInfo 时,将 HostObjectType 设置为全局类型 class。您可能还需要向其中定义了宿主对象类型的程序集添加元数据引用。HostObjectType 的成员现在将对脚本可见。

使用全局变量调用脚本。

对于脚本提交,编译器合成一个 class,所有顶级方法作为其成员,顶级语句在逻辑上构成构造函数的主体(但实际上不是因为异步) .

每个提交还会在此类型上生成一个方法,该方法构造提交 class 并运行脚本的主体。此方法将对象[] 作为参数。该数组的第一个元素被假定为全局对象实例。其他元素保留提交 class 实例,以防有多个提交(当用作像 csi.exe 这样的 REPL 时)。

加载生成的程序集(通过Compilation.Emit创建)后,您可以通过反射调用此方法。

编辑:您可以在CSharpCompilationOptions中设置生成脚本的名称class。

添加到 Matt 的回答中,这是我执行脚本的代码片段:

var compilation = document.GetSemanticModelAsync().Result.Compilation;
using (MemoryStream ms = new MemoryStream())
{
    var emitResult = compilation.Emit(ms);
    var assembly = Assembly.Load(ms.GetBuffer());
    Type t = assembly.GetTypes().First();

    var res = t.GetMethod("<Factory>").Invoke(null, new object[] { new object[] { Activator.CreateInstance(_customType), null } });
}

要调用的方法是<Factory>,它是一个静态方法。第一个参数是我的全局对象。由于我之前没有提交过,所以我将 null 作为第二个参数传递。