代码之家  ›  专栏  ›  技术社区  ›  anakic

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

  •  0
  • anakic  · 技术社区  · 7 年前

    在使用工作区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 回复  |  直到 7 年前
        1
  •  2
  •   Matt Warren    7 年前

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

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

    每次提交还生成一个关于该类型的方法,该方法构造提交类并运行脚本体。此方法将对象[]作为参数。假设该数组的第一个元素是全局对象实例。如果有多个提交(当用作类似csi.exe的REPL时),其他元素将保留提交类实例。

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

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

        2
  •  1
  •   anakic    7 年前

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