Рослин, как я могу создать экземпляр класса в скрипте во время выполнения и вызвать методы этого класса?
Я понимаю, как я могу выполнять целые сценарии с использованием 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());
}
Таким образом, вы создаете сборку самостоятельно, поэтому вам не нужно искать ее в текущем домене приложения.