Рослин, как я могу создать экземпляр класса в скрипте во время выполнения и вызвать методы этого класса?

Я понимаю, как я могу выполнять целые сценарии с использованием Roslyn в C#, но сейчас я хочу скомпилировать класс внутри сценария, создать его экземпляр, проанализировать его в интерфейсе и затем вызвать методы, которые реализует скомпилированный и созданный экземпляр класса.

Рослин выставляет такую ​​функциональность? Можете ли вы, пожалуйста, указать мне на такой подход?

Спасибо

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() добавляет скомпилированную сборку в gac? Я не прав? Если я тогда просто использую 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");

Я считаю этот подход несколько более хрупким по сравнению с предыдущим, но если предыдущий не подходит для вас - попробуйте этот.

Еще одна альтернатива, предложенная в комментариях:

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

Таким образом, вы создаете сборку самостоятельно, поэтому вам не нужно искать ее в текущем домене приложения.

Другие вопросы по тегам