Приступаем к динамической компиляции. Потребуется информация о пути к системным библиотекам.

        //путь к используемому фреймворку

        static string frmPath = @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\";

        static string keyConsole = ""; //ввод с клавиатуры

        static object dynClassInstance = null; //объект типа скрипт   

        static System.Threading.Thread my_thread = null; //текущий поток со скриптом  

        readonly static List<System.Threading.Thread> lstThr = new List<Thread>(); //список всех потоков со скриптами

 

Добавляем пространства имен

//for dynamo

using System.CodeDom.Compiler;

using Microsoft.CSharp;

using System.Reflection;

using System.Threading;

 

Добавляем метод Process.

        //компилировать и выполнить скрипт из окна "комманд"

        public static void Process(string s)

        {

            //сборки

            string[] includeAssemblies = { "MathPanel.exe",

                frmPath + "System.dll",

                frmPath + "System.Xaml.dll",

                frmPath + "WindowsBase.dll",

                frmPath + "PresentationFramework.dll",

                frmPath + "PresentationCore.dll"

                , frmPath + "System.Drawing.dll"

                , frmPath + "System.Net.dll"

                , frmPath + "System.Net.Http.dll"

            };

            //пространства имен

            string[] includeNamespaces = { "MathPanel", "MathPanelExt", "System.Net.Sockets" };

            keyConsole = "";

            CompileDynamo(s, null, includeNamespaces, includeAssemblies);

            //создаем новый поток

            try

            {

                if (dynClassInstance != null)

                {

                    my_thread = new System.Threading.Thread(new System.Threading.ThreadStart(() => {

                        Type type = dynClassInstance.GetType();

                        MethodInfo methodInfo = type.GetMethod("Execute");

                        try

                        {

                            methodInfo.Invoke(dynClassInstance, null);

                            Dynamo.Console("Скрипт выполнен.");

                        }

                        catch (Exception yyy) { Dynamo.Console(yyy.ToString()); }

                        //my_thread = null;

                    }));

                    my_thread.Start();

                    lstThr.Add(my_thread);

                }

            }

            catch (Exception xxx) { Dynamo.Console(xxx.ToString()); }

        }

 

И вспомогательный метод CompileDynamo

        //создать объект типа скрипт

        static object CompileDynamo(string code, Type outType = null, string[] includeNamespaces = null, string[] includeAssemblies = null)

        {

            StringBuilder namespaces = null;

            object methodResult = null;

            dynClassInstance = null;

            using (CSharpCodeProvider codeProvider = new CSharpCodeProvider())

            {

                //ICodeCompiler codeCompiler = codeProvider.CreateCompiler();//obsolete!

                CompilerParameters compileParams = new CompilerParameters

                {

                    CompilerOptions = "/t:library",

                    GenerateInMemory = true

                };

 

                int ipos = code.IndexOf("///[DLL]");

                if (ipos > 0)

                {

                    int ipos2 = code.IndexOf("[/DLL]", ipos);

                    if (ipos2 > 0)

                    {

                        compileParams.ReferencedAssemblies.Add("MathPanel.exe");

                        string ass = code.Substring(ipos + 8, ipos2 - ipos - 8);

                        var arr = ass.Split(',');

                        foreach (string _assembly in arr)

                        {

                            compileParams.ReferencedAssemblies.Add(frmPath + _assembly.Trim());

                        }

                    }

                }

                else if (includeAssemblies != null && includeAssemblies.Length > 0)

                {

                    foreach (string _assembly in includeAssemblies)

                    {

                        compileParams.ReferencedAssemblies.Add(_assembly);

                    }

                }

 

                if (includeNamespaces != null && includeNamespaces.Length > 0)

                {

                    namespaces = new StringBuilder();

                    foreach (string _namespace in includeNamespaces)

                    {

                        namespaces.Append(string.Format("using {0};\n", _namespace));

                    }

                }

 

                if (code.IndexOf("namespace DynamoCode") < 0)

                    code = string.Format(

                        @"{1} 

                using System; 

                namespace DynamoCode{{ 

                    public class Script{{ 

                        public {2} Execute(){{ 

                            {3} {0}; 

                        }} 

                    }} 

                }}",

                        code,

                        namespaces != null ? namespaces.ToString() : null,

                        outType != null ? outType.FullName : "void",

                        outType != null ? "return" : string.Empty

                        );

                CompilerResults compileResult = codeProvider.CompileAssemblyFromSource(compileParams, code);

                //codeCompiler.CompileAssemblyFromSource(compileParams, code);

 

                if (compileResult.Errors.Count > 0)

                {

                    //throw new Exception(compileResult.Errors[0].ErrorText);

                    Console("compile error: " + compileResult.Errors[0].ErrorText);

                    return "compile error: " + compileResult.Errors[0].ErrorText;

                }

                System.Reflection.Assembly assembly = compileResult.CompiledAssembly;

                dynClassInstance = assembly.CreateInstance("DynamoCode.Script");

                /*Type type = dynClassInstance.GetType();

                MethodInfo methodInfo = type.GetMethod("Execute");

                methodResult = methodInfo.Invoke(dynClassInstance, null);*/

            }

            return methodResult;

        }

 

И меняем код обработчика кнопки

            if (!bReady)

            {

                MessageBox.Show("Не готово!");

                return;

            }

            Process(textBlock1.Text);

 

Запускаем программу, печатаем в окне команд

var hz = Math.Sqrt(3);

Dynamo.Console(hz.ToString());

 

Нажимаем «Выполнить», получаем 1,73205080756888 в окне сообщений.

Поясним детали. Обработчик кнопки считывает текст из окна команд и передает его в метод Process().

Process формирует список выполняемых модулей

string[] includeAssemblies

 

и список пространств имен

string[] includeNamespaces

 

Затем вызывает для компиляции кода метод CompileDynamo. В случае успеха

 (dynClassInstance != null) есть истина.

 

Тогда создаем новый поток с делегатом, в котором запускается метод Execute из скомпилированного кода.

                    my_thread = new System.Threading.Thread(new System.Threading.ThreadStart(() => {

                        Type type = dynClassInstance.GetType();

                        MethodInfo methodInfo = type.GetMethod("Execute");

                        try

                        {

                            methodInfo.Invoke(dynClassInstance, null);

                            Dynamo.Console("Done");

                        }

                        catch (Exception yyy) { Dynamo.Console(yyy.ToString()); }

                    }));

Стартуем поток и добавляем его в список (для последующей очистки).

                    my_thread.Start();

                    lstThr.Add(my_thread);

 

 

Собственно, компиляция происходит в методе CompileDynamo. Сначала создается объект codeProvider, который предоставляет доступ к экземплярам генератора и компилятора кода C#. Ему передаются параметры компиляции: создавать библиотеку в памяти.

 

using (CSharpCodeProvider codeProvider = new CSharpCodeProvider())

            {

                CompilerParameters compileParams = new CompilerParameters

                {

                    CompilerOptions = "/t:library",

                    GenerateInMemory = true

                };

 

Фрагмент позволяет указывать список нужных библиотек в компилируемом коде.

 

                int ipos = code.IndexOf("///[DLL]");

                if (ipos > 0)

                {

                    int ipos2 = code.IndexOf("[/DLL]", ipos);

                    if (ipos2 > 0)

                    {

                        compileParams.ReferencedAssemblies.Add("MathPanel.exe");

                        string ass = code.Substring(ipos + 8, ipos2 - ipos - 8);

                        var arr = ass.Split(',');

                        foreach (string _assembly in arr)

                        {

                            compileParams.ReferencedAssemblies.Add(frmPath + _assembly.Trim());

                        }

                    }

                }

 

 

Затем формируется окончательный код для компиляции. Если поставить точку прерывания, то можно зафиксировать код, который передается компилятору.

using MathPanel;

using MathPanelExt;

using System.Net.Sockets;

 

                using System; 

                namespace DynamoCode{ 

                    public class Script{ 

                        public void Execute(){ 

                             var hz = Math.Sqrt(3);

Dynamo.Console(hz.ToString());

                        } 

                    } 

                }

Таким образом происходит «обертывание» кода из окна команд в класс DynamoCode.Script с методом Execute.

 

Затем даем команду компилятору

 

CompilerResults compileResult = codeProvider.CompileAssemblyFromSource(compileParams, code);

 

В случае успеха создаем экземпляр сборки

 

                if (compileResult.Errors.Count > 0)

                {

                    Console("compile error: " + compileResult.Errors[0].ErrorText);

                    return "compile error: " + compileResult.Errors[0].ErrorText;

                }

                System.Reflection.Assembly assembly = compileResult.CompiledAssembly;

                dynClassInstance = assembly.CreateInstance("DynamoCode.Script");

 

 

Теперь можно написать что-то посложнее для выполнения. Например, генерация чисел от 1 до 20 и потом снова до 1.

//for Excel increment cell values

for(int i = 1; i <= 20; i++)

  Dynamo.Console(i.ToString());

for(int i = 19; i >= 1; i--)

  Dynamo.Console(i.ToString());

 

Копируем результат из окна сообщений и вставляем в Эксель. Удобно.

Рис.2.3. Копирование результата в Эксель

С реализацией первой кнопки «Выполнить» мы закончили. Переходим к другим кнопкам.