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

时间:2017-10-03 15:57:20

标签: c# .net roslyn

在Roslyn Scripting 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;
}

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

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

2 个答案:

答案 0 :(得分:1)

授予脚本访问全局变量的权限。

创建ProjectInfo时,将HostObjectType设置为全局类的类型。您可能还需要将MetadataReference添加到定义主机对象类型的程序集中。现在,HostObjectType的成员将对脚本可见。

使用全局变量调用脚本。

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

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

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

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

答案 1 :(得分:0)

添加到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作为第二个参数。