Roslyn,如何在运行时在脚本中实例化 class 并调用该 class 的方法?
Roslyn, how can I instantiate a class in a script during runtime and invoke methods of that class?
我了解如何在 C# 中使用 Roslyn 执行整个脚本,但我现在想要完成的是在脚本中编译一个 class,实例化它,将它解析为一个接口,然后调用方法编译和实例化的 class 实现。
Roslyn 是否提供此类功能?有人能指出我这种方法吗?
谢谢
我想你可以像这样做你想做的事:
namespace ConsoleApp2 {
class Program {
static void Main(string[] args) {
// create class and return its type from script
// reference current assembly to use interface defined below
var script = CSharpScript.Create(@"
public class Test : ConsoleApp2.IRunnable {
public void Run() {
System.Console.WriteLine(""test"");
}
}
return typeof(Test);
", ScriptOptions.Default.WithReferences(Assembly.GetExecutingAssembly()));
script.Compile();
// run and you get Type object for your fresh type
var testType = (Type) script.RunAsync().Result.ReturnValue;
// create and cast to interface
var runnable = (IRunnable)Activator.CreateInstance(testType);
// use
runnable.Run();
Console.ReadKey();
}
}
public interface IRunnable {
void Run();
}
}
除了 returning 您从脚本创建的类型,您还可以使用全局变量和 return 那样:
namespace ConsoleApp2 {
class Program {
static void Main(string[] args) {
var script = CSharpScript.Create(@"
public class Test : ConsoleApp2.IRunnable {
public void Run() {
System.Console.WriteLine(""test"");
}
}
MyTypes.Add(typeof(Test).Name, typeof(Test));
", ScriptOptions.Default.WithReferences(Assembly.GetExecutingAssembly()), globalsType: typeof(ScriptGlobals));
script.Compile();
var globals = new ScriptGlobals();
script.RunAsync(globals).Wait();
var runnable = (IRunnable)Activator.CreateInstance(globals.MyTypes["Test"]);
runnable.Run();
Console.ReadKey();
}
}
public class ScriptGlobals {
public Dictionary<string, Type> MyTypes { get; } = new Dictionary<string, Type>();
}
public interface IRunnable {
void Run();
}
}
编辑以回答您的评论。
what if I know the name and type of the class in the script? My
understanding is that script.Compile() adds the compiled assembly to
gac? Am I incorrect? If I then simply use
Activator.CreateInstance(typeofClass) would this not solve my problem
without even having to run the script
编译后的程序集不会添加到 gac - 它被编译并存储在内存中,类似于使用 Assembly.Load(someByteArray)
加载程序集的方式。无论如何,在您调用 Compile
之后,该程序集将加载到当前应用程序域中,因此您无需 RunAsunc()
即可访问您的类型。问题是这个程序集有一个神秘的名字,例如:ℛ*fde34898-86d2-42e9-a786-e3c1e1befa78#1-0
。要找到它,您可以这样做:
script.Compile();
var asmAfterCompile = AppDomain.CurrentDomain.GetAssemblies().Single(c =>
String.IsNullOrWhiteSpace(c.Location) && c.CodeBase.EndsWith("Microsoft.CodeAnalysis.Scripting.dll"));
但请注意,这并不稳定,因为如果您在应用程序域中编译多个脚本(甚至多次编译同一个脚本)- 会生成多个这样的程序集,因此很难区分它们。如果这对您来说不是问题 - 您可以使用这种方式(但请确保您正确测试了所有这些)。
找到生成的程序集后 - 问题还没有结束。您的所有脚本内容都在包装 class 下编译。我看到它的名字是 "Submission#0",但我不能保证它总是这样命名。因此,假设您的脚本中有 class Test
。它将是该包装器的子 class,因此实际类型名称将是 "Submission#0+Test"。因此,要从生成的程序集中获取类型,最好这样做:
var testType = asmAfterCompile.GetTypes().Single(c => c.Name == "Test");
我认为这种方法比以前的方法更脆弱,但如果以前的方法不适合你 - 试试这个。
评论中建议的另一种选择:
script.Compile();
var stream = new MemoryStream();
var emitResult = script.GetCompilation().Emit(stream);
if (emitResult.Success) {
var asm = Assembly.Load(stream.ToArray());
}
这样您就可以自己创建程序集,因此不需要在当前应用程序域中搜索它。
我了解如何在 C# 中使用 Roslyn 执行整个脚本,但我现在想要完成的是在脚本中编译一个 class,实例化它,将它解析为一个接口,然后调用方法编译和实例化的 class 实现。
Roslyn 是否提供此类功能?有人能指出我这种方法吗?
谢谢
我想你可以像这样做你想做的事:
namespace ConsoleApp2 {
class Program {
static void Main(string[] args) {
// create class and return its type from script
// reference current assembly to use interface defined below
var script = CSharpScript.Create(@"
public class Test : ConsoleApp2.IRunnable {
public void Run() {
System.Console.WriteLine(""test"");
}
}
return typeof(Test);
", ScriptOptions.Default.WithReferences(Assembly.GetExecutingAssembly()));
script.Compile();
// run and you get Type object for your fresh type
var testType = (Type) script.RunAsync().Result.ReturnValue;
// create and cast to interface
var runnable = (IRunnable)Activator.CreateInstance(testType);
// use
runnable.Run();
Console.ReadKey();
}
}
public interface IRunnable {
void Run();
}
}
除了 returning 您从脚本创建的类型,您还可以使用全局变量和 return 那样:
namespace ConsoleApp2 {
class Program {
static void Main(string[] args) {
var script = CSharpScript.Create(@"
public class Test : ConsoleApp2.IRunnable {
public void Run() {
System.Console.WriteLine(""test"");
}
}
MyTypes.Add(typeof(Test).Name, typeof(Test));
", ScriptOptions.Default.WithReferences(Assembly.GetExecutingAssembly()), globalsType: typeof(ScriptGlobals));
script.Compile();
var globals = new ScriptGlobals();
script.RunAsync(globals).Wait();
var runnable = (IRunnable)Activator.CreateInstance(globals.MyTypes["Test"]);
runnable.Run();
Console.ReadKey();
}
}
public class ScriptGlobals {
public Dictionary<string, Type> MyTypes { get; } = new Dictionary<string, Type>();
}
public interface IRunnable {
void Run();
}
}
编辑以回答您的评论。
what if I know the name and type of the class in the script? My understanding is that script.Compile() adds the compiled assembly to gac? Am I incorrect? If I then simply use Activator.CreateInstance(typeofClass) would this not solve my problem without even having to run the script
编译后的程序集不会添加到 gac - 它被编译并存储在内存中,类似于使用 Assembly.Load(someByteArray)
加载程序集的方式。无论如何,在您调用 Compile
之后,该程序集将加载到当前应用程序域中,因此您无需 RunAsunc()
即可访问您的类型。问题是这个程序集有一个神秘的名字,例如:ℛ*fde34898-86d2-42e9-a786-e3c1e1befa78#1-0
。要找到它,您可以这样做:
script.Compile();
var asmAfterCompile = AppDomain.CurrentDomain.GetAssemblies().Single(c =>
String.IsNullOrWhiteSpace(c.Location) && c.CodeBase.EndsWith("Microsoft.CodeAnalysis.Scripting.dll"));
但请注意,这并不稳定,因为如果您在应用程序域中编译多个脚本(甚至多次编译同一个脚本)- 会生成多个这样的程序集,因此很难区分它们。如果这对您来说不是问题 - 您可以使用这种方式(但请确保您正确测试了所有这些)。
找到生成的程序集后 - 问题还没有结束。您的所有脚本内容都在包装 class 下编译。我看到它的名字是 "Submission#0",但我不能保证它总是这样命名。因此,假设您的脚本中有 class Test
。它将是该包装器的子 class,因此实际类型名称将是 "Submission#0+Test"。因此,要从生成的程序集中获取类型,最好这样做:
var testType = asmAfterCompile.GetTypes().Single(c => c.Name == "Test");
我认为这种方法比以前的方法更脆弱,但如果以前的方法不适合你 - 试试这个。
评论中建议的另一种选择:
script.Compile();
var stream = new MemoryStream();
var emitResult = script.GetCompilation().Emit(stream);
if (emitResult.Success) {
var asm = Assembly.Load(stream.ToArray());
}
这样您就可以自己创建程序集,因此不需要在当前应用程序域中搜索它。