Динамическая компиляция кода в C#
- modified:
- reading: 3 minutes
Использовать компилятор из кода C# достаточно просто. А вот зачем – это другой вопрос :).
Hello World
Напишем первый простой пример. Создаем консольное приложение и напишем следующий код:
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using Microsoft.CSharp;
namespace ConsoleCompiler
{
internal class Program
{
private static void Main(string[] args)
{
// Source code для компиляции
string source =
@" namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}
public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";
// Настройки компиляции
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);
CompilerParameters compilerParams = new CompilerParameters
{OutputAssembly = "D:\\Foo.EXE", GenerateExecutable = true};
// Компиляция
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);
// Выводим информацию об ошибках
Console.WriteLine("Number of Errors: {0}", results.Errors.Count);
foreach (CompilerError err in results.Errors)
{
Console.WriteLine("ERROR {0}", err.ErrorText);
}
}
}
}
Запускаем и проверяем:
Первое, на что стоит обратить внимание – это использование двух пространств имен (namespace):
- Microsoft.CSharp
- System.CodeDom.Compiler
В данных классах и содержится ключ к возможности компиляции. В нашем примере мы указываем что компилировать будем под .NET Framework 3.5, а так же указываем что мы хотим получить на выходе – Foo.exe, с возможностью запуска данного приложения.
Пример посложнее, используем Linq
Теперь давайте усложним наш пример, в компилируемый код добавим использование Linq:
string source =
@"using System.Collections.Generic; using System.Linq;
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}
public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
System.Console.WriteLine( string.Join("","", Enumerable.Range(0,10).Select(n=>n.ToString()).ToArray() ) ); }
}
}
Добавленные строки помечены красным, если мы попробуем запустить предыдущий пример с измененным компилируемым кодом, то теперь мы увидим ошибки компиляции:
Чтобы компиляция удалась, необходимо добавить в параметры компиляции ссылку на сборку System.Core.dll
compilerParams.ReferencedAssemblies.Add("System.Core.Dll");
И теперь все будет работать:
Используем созданную сборку в коде
Теперь попробуем скомпилировать сборку Foo.dll вместо исполняемого файла, а так же сразу же после компиляции загрузить и использовать скомпилированный метод. Компилируемый код мы изменим, сделаем его попроще:
string source =
@"
using System.Collections.Generic;
using System.Linq;
namespace Foo
public class Bar
{
public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
System.Console.WriteLine( string.Join("","", Enumerable.Range(0,10).Select(n=>n.ToString()).ToArray() ) );
}
}
";
Изменим настройки компилятора, теперь будем собирать dll файл:
const string outputAssembly = "D:\\Foo.dll";
CompilerParameters compilerParams = new CompilerParameters {OutputAssembly = outputAssembly, GenerateExecutable = false};
После компиляции и проверки ошибок используя Reflection (не забываем подключить пространство имен - using System.Reflection) вызовем метод Foo.Bar.SayHello() скомпилированной dll:
Console.WriteLine("Try Assembly:");
Assembly assembly = Assembly.LoadFile(outputAssembly);
Type type = assembly.GetType("Foo.Bar");
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(null, null);
Результат:
Скачать пример:ConsoleCompiler.zip.
Информацию о динамической компиляции и основные примеры я взял отсюда: Saveen Reddy's blog - A Walkthrough of Dynamically Compiling C# code (Английский).