與動態執行的C# 代碼進行通訊

來源:互聯網
上載者:User

1、簡介

能夠動態執行 C# 代碼是一件很酷的功能,比如,我們可以在控制台中輸入一行 C# 代碼,然後程式自動編譯並執行這一行代碼,將結果顯示給我們。這差不多就是一個最簡單的 C# 代碼解譯器了。

動態執行 C# 代碼又是一件很有用的功能,比如,我們可以將某些代碼寫在某個檔案之中,由程式集在執行時進行載入,改變這些代碼不用中止程式,當程式再次載入這些代碼時,就自動執行的是新代碼了。

下面,我將在寫一個簡單C# 代碼解譯器,然後將在 C# 代碼解譯器之中加入動態代碼與解譯器環境間的動態互動機制,來示範一個很好很強大的應用。

2、簡單的 C# 代碼解譯器

關於如何動態執行 C# 代碼在 Jailu.Net 的《如何用C#動態編譯、執行代碼》一文中講述的很清晰。採用該文所述方式寫一個 C# 代碼解譯器:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Globalization;
using Microsoft.CSharp;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Text;
using System.IO;
using System.Xml;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write(">> ");
            String cmd;
            Context cxt = new Context();
            while ((cmd = Console.ReadLine().Trim()) != "exit")
            {
                if (!String.IsNullOrEmpty(cmd))
                {
                    Console.WriteLine();
                    cxt.Invoke(cmd);
                }
                Console.Write("\n>> ");
            }
        }
    }

    public class Context
    {
        public CSharpCodeProvider CodeProvider { get; set; }
        public IDictionary<String, Assembly> Assemblys { get; set; }

        public Context()
        {
            CodeProvider = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
            Assemblys = new Dictionary<String, Assembly>();
            Assembly[] al = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly a in al)
            {
                AddAssembly(a);
            }
            AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
        }

        private void AddAssembly(Assembly a)
        {
            if (a != null)
            {
                Assemblys.Add(a.FullName, a);
            }
        }

        void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
        {
            Assembly a = args.LoadedAssembly;
            if (!Assemblys.ContainsKey(a.FullName))
            {
                AddAssembly(a);
            }
        }

        public CompilerParameters CreateCompilerParameters()
        {
            CompilerParameters cp = new CompilerParameters();
            cp.GenerateExecutable = false;
            cp.GenerateInMemory = true;
            if (Assemblys != null)
            {
                foreach (Assembly a in Assemblys.Values)
                {
                    cp.ReferencedAssemblies.Add(a.Location);
                }
            }
            return cp;
        }

        public void Invoke(String cmd)
        {
            String inputCmdString = cmd.Trim();
            if (String.IsNullOrEmpty(inputCmdString)) return;

            String fullCmd = BuildFullCmd(inputCmdString);

            CompilerResults cr = CodeProvider.CompileAssemblyFromSource(CreateCompilerParameters(), fullCmd);

            if (cr.Errors.HasErrors)
            {
                Boolean recompileSwitch = true;

                foreach (CompilerError err in cr.Errors)
                {
                    //CS0201 : Only assignment, call, increment, decrement, and new object expressions can be
                    //used as a statement
                    if (!err.ErrorNumber.Equals("CS0201"))
                    {
                        recompileSwitch = false;
                        break;
                    }
                }

                // 重新編譯
                if (recompileSwitch)
                {
                    String dynaName = "TempArg_Dynamic_" + DateTime.Now.Ticks.ToString();
                    inputCmdString = String.Format("  var {0} = ", dynaName) + inputCmdString;
                    inputCmdString += ";\n  System.Console.WriteLine(" + dynaName + ");";

                    fullCmd = BuildFullCmd(inputCmdString);
                    cr = CodeProvider.CompileAssemblyFromSource(CreateCompilerParameters(), fullCmd);
                }

                if (cr.Errors.HasErrors)
                {
                    Console.WriteLine("編譯錯誤:");
                    foreach (CompilerError err in cr.Errors)
                    {
                        Console.WriteLine(err.ErrorNumber);
                        Console.WriteLine(err.ErrorText);
                    }

                    return;
                }
            }

            Assembly assem = cr.CompiledAssembly;
            Object dynamicObject = assem.CreateInstance("Test.DynamicClass");
            Type t = assem.GetType("Test.DynamicClass");
            MethodInfo minfo = t.GetMethod("MethodInstance");
            minfo.Invoke(dynamicObject, null);
        }

        private String BuildFullCmd(String inputCmdString)
        {
            String fullCmd = String.Empty;

            fullCmd += @"
                namespace Test 
                { 
                    public class DynamicClass
                    {
                        public void MethodInstance()
                        {
                            " + inputCmdString + @";
                        }
                    }
                }";
            return fullCmd;
        }
    }
}

 編譯執行後就得到一個傻傻的 C# 代碼解析器,也可以當一個簡單的計算機用:

3、解譯器與所解釋的代碼之間進行變數互動

如果將所解釋的代碼中的某些變數儲存下來,供給以後的代碼用,這一解譯器的功能又會強大很多。假設這類變數名稱以$打頭,如:

$myblogname = “http://xiaotie.cnblogs.com”

將在解譯器環境中定義(如果該變數未存在)或賦值於(如果該變數已存在)一個名為 myblogname 的字串變數,指向字串“http://xiaotie.cnblogs.com”。而,System.Console.WriteLine($myblogname)則取出並列印出字串該變數所引用的。

簡單說來,也就是讓所解釋的代碼中能夠初始化並引用解譯器中的變數。

如何?呢?這是本文的重點。

首先,在 Context 類中定義一個SortedDictionary儲存變數,並提供索引訪問:

        public SortedDictionary<String, Object> Instances { get; set; }
        public Object this[String instanceName]
        {
            get
            {
                if (Instances.ContainsKey(instanceName))
                {
                    return Instances[instanceName];
                }
                else
                {
                    return null;
                }
            }
            set
            {
                if (Instances.ContainsKey(instanceName))
                {
                    Instances.Remove(instanceName);
                }
                Instances.Add(instanceName, value);
            }
        }

BuildFullCmd方法改變為:

 

        private String BuildFullCmd(String inputCmdString)
        {
            String fullCmd = String.Empty;

            fullCmd += @"
                    using Test;

                    public class DynamicClass
                    {
                        private Context m_context;

                        public void MethodInstance(Context context)
                        {
                            m_context = context;
                            " + inputCmdString + @";
                        }
                    }";
            return fullCmd;
        }

這樣,在動態產生的對象中,便可以引用Context對象。

對於inputCmdString 中未定義的外部變數,在第一次遇見時將$argname替換為一個隨機產生的內部變數,在代碼的最後,將這個內部變數儲存在 Context 中。

雖然通過 (Context[argname].GetType())(Context[argname]) 便可引用外部變數 $argname,但是這樣引用賦值時,編譯器會報錯。解決這個問題需要一個新的類:

    public class ObjectHelper<T>
    {
        private String m_objName;

        public Context Context { get; private set; }

        public T Obj
        {
            get
            {
                Object obj = Context[m_objName];
                return (T)obj;
            }
            set { Context[m_objName] = value; }
        }

        public ObjectHelper(Context cxt, String objName)
        {
            m_objName = objName;
            Context = cxt;
        }
    }

將inputCmdString中的外部變數$argname統一替換為(new ObjectHelper <m_context[“argname”].GetType()> (m_context, “argname”)).Obj" 即可實現在動態代碼中對已定義外部變數的引用。

上述對inputCmdString的預先處理代碼為:

            Regex re;

            // 處理未初始化的環境變數
            re = new Regex(@"^(\$)(\w)+");
            if (inputCmdString != null)
            {
                Match m = re.Match(inputCmdString);
                if (m != null && m.Length > 1)
                {
                    String outArgName = inputCmdString.Substring(m.Index, m.Length).Substring(1);
                    if (this[outArgName] == null)
                    {
                        String innerArgName = "TempArg_" + outArgName;
                        inputCmdString = "var " + inputCmdString.Replace("$" + outArgName, innerArgName);
                        inputCmdString += ";m_context[\"" + outArgName + "\"]=" + innerArgName + ";";
                    }
                }
            }

            // 處理其它環境變數
            re = new Regex(@"(\$)(\w)+");
            IDictionary<String, String> ArgsList = new Dictionary<String, String>();
            if (inputCmdString != null)
            {
                MatchCollection mc = re.Matches(inputCmdString);
                if (mc != null)
                {
                    foreach (Match m in mc)
                    {
                        if (m.Length > 1)
                        {
                            String outArgName = inputCmdString.Substring(m.Index, m.Length).Substring(1);
                            if (!ArgsList.ContainsKey(outArgName))
                            {
                                Object obj = this[outArgName];
                                if (obj == null) throw new Exception("不存在環境變數" + outArgName);
                                String innerArgName = String.Format(@"(new ObjectHelper<{0}>(m_context,""{1}"")).Obj", obj.GetType(), outArgName);
                                ArgsList.Add(outArgName, innerArgName);
                            }
                        }
                    }
                }

                foreach (String outArg in ArgsList.Keys)
                {
                    inputCmdString = inputCmdString.Replace("$" + outArg, ArgsList[outArg]);
                }
            }

這裡做了個簡化,即定義外部變數的格式必須為 $argname = value,其中 $argname 必須在行首。

這樣,對於:$myblogname = "http://xiaotie.cnblogs.com". 因為 myblogname 變數不存在,被解析為:
var TempArg_myblogname = "http://xiaotie.cnblogs.com";
m_context["myblogname"]=TempArg_myblogname;;

定義後,當再出現 $myblogname,則被解析為 (new ObjectHelper<System.String>(m_context,"myblogname")).Obj;

看看實際執行情況:

完整代碼於此下載。

4、一個很好很強大的應用—---打入.Net 程式內部,看看其執行情況。

採用上面的方法改進了 OrcShell(OrcShell詳情見我前面的隨筆: 實現簡單的CSharpShell -- OrcShell )。新版 OrcShell 程式於此下載(需要.Net 3.5)。基本上是一個可用的 小型 .Net Framework Shell 了,可以動態查看、建立、執行 .Net 的類型了。不過,自動提示與完成功能還沒有做,使用起來還是較不方便的。

help 指令可以查看常用指令列表:

lsc     列出當前命名空間中的類型和下屬命名空間。格式: lsc [name]
dirc    同 lsc
cdc     改變當前的命名空間,格式: cdc [.|..|name]
my      查看全部變數。格式:my。可通過$ArgName來引用變數。
alias   查看全部別名。格式:alias
use     添加命名空間。格式: use [namespace]
unuse   移除命名空間。格式:unuse [namespace]
import  匯入程式集,有兩種匯入方式: "import -f [fullpath]","import [partname]"

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.