Репозиторий на bitbucket

Твиттер

Анкета на linkedin

Скачать резюме

вторник, 12 марта 2013 г.

Выполнение C# кода "на лету"

В процессе разработки программного обеспечения иногда возникает потребность выполнить какую-то часть кода программы без полного построения (build-а) проекта. Это может потребоваться при использовании стороннего компонента и/или фреймворка, когда необходимо убедиться, работает ли код, который взаимодействует с этим компонентом, как нужно либо имеются какие-то проблемы (некорректное/неправильное использование, непонимание и т.д.). Также могут быть какие-то сложные вложенные циклы, ветвления, манипуляции с данными (разбор XML, HTML) которые хотелось бы проверить.

Для таких случаев решение будет в написании отдельного приложения или тестов, независимых от основной части проекта, служащих для проверки требуемого функционала. Это удовлетворит потребности, однако со временем этот код потеряет актуальность и наверняка будет удален.

В статье C# консоль для выполнения простых «скриптов» автор приводит консольное приложение, которое позволяет компилировать и выполнять введенный код.

Для решения задач, описанных выше, мне захотелось расширить данное приложение: в первую очередь сделать приложение оконным – чтобы можно было быстро копировать/вставлять и редактировать введенный текст и результаты. Также нужно добавить функционал, который позволит писать код, использующий сторонние сборки.

Исходный код класса, позволяющего выполнять части кода, a также некоторые поясняющие комментарии приведены ниже:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;

namespace CharpShell
{
   //Определения делегата для организации вывода результатов выполнения частей кода 
   public delegate void ExecuteLogHandler(object message);

   public class CharpExecuter
   {
       //Код готовый к выполнению
       string formatedProgramText;

       public string LastProgramText
       {
           get { return formatedProgramText; }
       }
       //Поле делегата объявлено статическим для того, чтобы можно было
       //вызывать из программы, которая будет компилироваться
       public static ExecuteLogHandler OnExecute;
       //Список сборок, которые будут подключатся при компиляции
       private List<string> refferences = new List<string>();

       public List<string> Refferences
       {
           get { return refferences; }
           set { refferences = value; }
       }
       //Список using определений, которые будут добавлены начало кода
       private List<string> usings = new List<string>();

       public List<string> Usings
       {
           get { return usings; }
           set { usings = value; }
       }
       //Предопределенные части программы. Добавляется публичный статический
       //метод ScriptMethod, который будет вызываться из основного приложения
       //внутри используется Stopwatch для вычисления времени выполнения программы
       //Также объявлен метод Log, который вызывает OnExecute во внешней сборке (см. ниже)
       readonly string header = @"
            namespace CScript
            {
                public class Script
                {
                    public static void ScriptMethod()
                    {
                        Stopwatch sw = new Stopwatch();
                        sw.Start();  
            ";

       readonly string footer = @" sw.Stop();Log(sw.Elapsed.ToString());
                    }
                    static void Log(object message)
                    {
                        if(CharpShell.CharpExecuter.OnExecute != null)
                            CharpShell.CharpExecuter.OnExecute(message);
                    }
                }
            }";

       public CharpExecuter(ExecuteLogHandler onExecute)
       {
           OnExecute += onExecute;
           //Инициализация сборок, которые будут добавлены по умолчанию
           refferences.AddRange(new string[]
                {
                    "System.dll",
                    "System.Core.dll",
                    "System.Net.dll",
                    "System.Data.dll",
                    "System.Drawing.dll",
                    "System.Windows.Forms.dll",
                    //Необходимо добавить свою сборку, чтобы можно было вызывать OnExecute
                    Assembly.GetAssembly(typeof(CharpExecuter)).Location,

                });
           //Инициализация using которые будут добавлены по умолчанию
           usings.AddRange(new string[]
            {
                    "System",
                    "System.IO",
                    "System.Net",
                    "System.Threading",
                    "System.Collections.Generic",
                    "System.Text",
                    "System.Text.RegularExpressions",
                    "System.ComponentModel",
                    "System.Data",
                    "System.Drawing",
                    "System.Diagnostics",
                    "System.Linq",
                    "System.Windows.Forms"
            });
       }
       //Выполнение кода
       public void Execute(List<string> code)
       {
           //Форматирование сырого кода (добавление предопределенный частей)
           FormatSources(code);
           //Выполнение
           Execute();
       }

       public void Execute()
       {
           Execute(formatedProgramText);
       }

       public void Execute(string program)
       {
           //Создание класса CSHarpProvider с указанием того, что сборка генерируется в памяти
           var CSHarpProvider = CSharpCodeProvider.CreateProvider("CSharp");
           CompilerParameters compilerParams = new CompilerParameters()
           {
               GenerateExecutable = false,
               GenerateInMemory = true,
           };
           //Добавление сборок для компиляции
           compilerParams.ReferencedAssemblies.AddRange(refferences.ToArray());
           //Компиляция
           var compilerResult = CSHarpProvider.CompileAssemblyFromSource(compilerParams, program);
           if (compilerResult.Errors.Count == 0)
           {
               OnExecute(string.Concat("Executing...", Environment.NewLine));
               try
               {
                   //Вызов метода ScriptMethod в сборке которая скомпилировалась
                   compilerResult.CompiledAssembly.GetType("CScript.Script").GetMethod("ScriptMethod").Invoke(null, null);
                   OnExecute(string.Empty);
                   OnExecute("Done.");
               }
               catch (Exception e)
               {
                   OnExecute(e.InnerException.Message + "rn" + e.InnerException.StackTrace);
               }
           }
           else
           {
               foreach (var oline in compilerResult.Output)
                   OnExecute(oline);
           }
       }
       //Форматирование кода (добавление предопределенных частей)
       public string FormatSources(string text)
       {
           string usings = FormatUsings();
           formatedProgramText = string.Concat(usings, header, text, footer);
           return formatedProgramText;
       }

       public string FormatSources(List<string> code)
       {
           StringBuilder sb = new StringBuilder(header);
           foreach (var sc in code)
           {
               sb.AppendLine(sc);
           }
           sb.AppendLine(footer);
           formatedProgramText = sb.ToString();
           return formatedProgramText;
       }
       //Форматирование определений using
       private string FormatUsings()
       {
           StringBuilder sb = new StringBuilder();
           foreach (string using_str in usings)
               sb.AppendFormat("using {0};{1}", using_str, Environment.NewLine);
           return sb.ToString();
       }
   }
}

Для компиляции кода в памяти используется класс CSharpCodeProvider. Для организации перенаправления вывода результатов используется свойство делегатного типа OnExecute, которое инициализируется в конструкторе.

Ниже приведен код использования класса в клиентском приложении:

//...
        CharpExecuter cs;
        //Инициализация
        private void Form1_Load(object sender, EventArgs e)
        {
            cs = new CharpExecuter(new ExecuteLogHandler(Log));
        }
        //Перенаправление вывода в textbox
        public void Log(object msg)
        {
            textBox2.Text += string.Concat(msg, Environment.NewLine);
        }
        //Выполнение введенного кода
        private void button1_Click(object sender, EventArgs e)
        {
            textBox2.Text = string.Empty;
            cs.FormatSources(textBox1.Text);
            cs.Execute();
        }
 //...

Остальная часть исходного текста описания не требует. Внешний вид и пример работы приложения приведен на рисунке


Приложение обладает следующими возможностями:
  • Позволяет быстро вставить/запустить код на языке С# и получить результаты, которые можно также быстро скопировать в буфер обмена для последующего использования
  • Сохранять получившуюся программу
  • Загружать код из файла
  • Изменять список using выражений и подключаемых сборок 
  • Определять и выводить время выполнения кода
Таким образом получилась небольшая удобная утилита для повседневной работы.

Вы можете скачать и изменить код на свое усмотрение. Полные исходные тексты находятся в репозитории на BitBucket здесь. Архив с исполняемыми файлами можно найти в разделе downloads


2 комментария:

  1. This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see
    http://go.microsoft.com/fwlink/?LinkID=533240

    ОтветитьУдалить
  2. Harrah's casino - DrMCD
    Hotel is Harrah's hotel casino 청주 출장안마 on lake of 전라북도 출장샵 fun, a short 여수 출장안마 drive from Harrahs Lake Tahoe and 문경 출장샵 the 공주 출장샵 beautiful Lake Tahoe Tahoe.

    ОтветитьУдалить