程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 與動態執行的C#代碼進行通訊

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

編輯:關於C#

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]"

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved