Динамическая компиляция кода в C#

    • .NET
    • 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); 
            } 
        } 
    } 
}

Запускаем и проверяем:

First Sample

Первое, на что стоит обратить внимание – это использование двух пространств имен (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() ) );         } 
    } 
}

Добавленные строки помечены красным, если мы попробуем запустить предыдущий пример с измененным компилируемым кодом, то теперь мы увидим ошибки компиляции:

Compiler Error

Чтобы компиляция удалась, необходимо добавить в параметры компиляции ссылку на сборку System.Core.dll

compilerParams.ReferencedAssemblies.Add("System.Core.Dll");

И теперь все будет работать:

Linq Sample

Используем созданную сборку в коде

Теперь попробуем скомпилировать сборку 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);

Результат:

Final Result

Скачать пример:ConsoleCompiler.zip.

Информацию о динамической компиляции и основные примеры я взял отсюда: Saveen Reddy's blog - A Walkthrough of Dynamically Compiling C# code (Английский).

See Also