【问题标题】:Roslyn, how can I instantiate a class in a script during runtime and invoke methods of that class?Roslyn,如何在运行时在脚本中实例化一个类并调用该类的方法?
【发布时间】:2017-11-10 09:00:27
【问题描述】:

我了解如何在 C# 中使用 Roslyn 执行整个脚本,但我现在想要完成的是在脚本中编译一个类,实例化它,将其解析为接口,然后调用编译和实例化的类实现的方法.

Roslyn 是否公开了此类功能?有人能指点我这种方法吗?

谢谢

【问题讨论】:

    标签: c# roslyn runtime-compilation


    【解决方案1】:

    我认为你可以做你想做的事,例如:

    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();
        }
    }
    

    除了返回从脚本创建的类型之外,您还可以使用全局变量并以这种方式返回:

    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();
        }
    }
    

    编辑以回答您的评论。

    如果我知道脚本中类的名称和类型怎么办?我的 理解是 script.Compile() 将编译好的程序集添加到 广汽?我不正确吗?如果我然后简单地使用 Activator.CreateInstance(typeofClass) 这不能解决我的问题 甚至无需运行脚本

    已编译的程序集不会添加到 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"));
    

    但请注意,这并不稳定,因为如果您在应用程序域中编译多个脚本(甚至多次编译相同的脚本) - 会生成多个此类程序集,因此很难区分它们。如果这对您来说不是问题 - 您可以使用这种方式(但请确保您正确测试所有这些)。

    在你找到生成的程序集之后——问题还没有结束。您所有的脚本内容都在包装类下编译。我看到它被命名为“Submission#0”,但我不能保证它总是这样命名。所以假设你的脚本中有Test 类。它将是该包装器的子类,因此真实类型名称将是“Submission#0+Test”。因此,要从生成的程序集中获取您的类型,最好这样做:

    var testType = asmAfterCompile.GetTypes().Single(c => c.Name == "Test");
    

    我认为这种方法比以前的方法更脆弱,但如果以前的方法不适合您 - 试试这个。

    cmets 中建议的另一种选择:

    script.Compile();
    var stream = new MemoryStream();
    var emitResult = script.GetCompilation().Emit(stream);
    if (emitResult.Success) {
        var asm = Assembly.Load(stream.ToArray());
    }
    

    这样你自己创建程序集,因此不需要在当前应用程序域中搜索它。

    【讨论】:

    • 太棒了,正是我想要的,非常感谢。
    • @MattWolf ScriptGlobals 是答案代码本身定义的类(不属于任何 roslyn 程序集)。
    • 抱歉,刚刚看到,在您看来,这是在脚本中不返回任何值/类型的情况下创建实例的最佳方式吗?
    • @MattWolf 好吧,至少我没有找到另一种方法来做到这一点(你也可以在脚本中使用局部变量,但这非常相似)所以我认为是的。如果您不想手动将类型添加到 MyTypes - 也许您可以在脚本中执行 Assembly.GetExecutingAssembly() 并从那里获取所有类型(然后仍然将结果分配给某个全局变量)。
    • @MattWolf 我更新了答案,因为评论太长了。
    猜你喜欢
    • 2011-01-25
    • 2019-05-20
    • 2023-03-15
    • 2012-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多