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