与动态执行的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]"

本文转自xiaotie博客园博客,原文链接http://www.cnblogs.com/xiaotie/archive/2008/03/01/1087448.html如需转载请自行联系原作者


xiaotie 集异璧实验室(GEBLAB)


相关文章
|
1月前
|
C# Windows
C#通过代码实现快捷键编辑
C#通过代码实现快捷键编辑
|
3月前
|
开发框架 .NET 编译器
C# 10.0中Lambda表达式的改进:更简洁、更灵活的代码编写体验
【1月更文挑战第21天】随着C#语言的不断发展,Lambda表达式作为一种简洁、高效的函数式编程工具,在C# 10.0中迎来了重要的改进。本文将详细探讨C# 10.0中Lambda表达式的新特性,包括参数类型的推断增强、自然类型的Lambda参数以及Lambda表达式的属性改进等。这些改进不仅简化了Lambda表达式的编写过程,还提升了代码的可读性和灵活性,为开发者带来了更优质的编程体验。
|
3月前
|
C# 开发者
C# 10.0中的文件范围命名空间:简化代码组织的新方式
【1月更文挑战第18天】C# 10.0引入了文件范围的命名空间,这是一种新的语法糖,用于更简洁地组织和管理代码。文件范围命名空间允许开发者在每个文件的基础上定义命名空间,而无需显式使用花括号包裹整个文件内容。本文将深入探讨文件范围命名空间的工作原理、使用场景以及它们为C#开发者带来的便利。
|
4月前
|
存储 人工智能 C#
【Unity 3D】C#中数组、集合、栈、队列、哈希表、字典的讲解(附测试代码)
【Unity 3D】C#中数组、集合、栈、队列、哈希表、字典的讲解(附测试代码)
36 0
|
5月前
|
开发框架 .NET C#
如何调试 C# Emit 生成的动态代码?
如何调试 C# Emit 生成的动态代码?
|
4月前
|
IDE C# 开发工具
C# | 多线程批量下载文件(创建N个线程同时批量下载文件,只需要几行代码而已)
批量下载文件时使用多线程可以有效缩短完成时间,本文将讲解如何使用C#+CodePlus扩展库快速完成多线程的文件下载。 大部分代码由IDE自动生成,需要我们自己编写的代码正好**10行**。也就是说,只需要10分钟,就可以手撸一个多线程的批量下载器。
88 0
C# | 多线程批量下载文件(创建N个线程同时批量下载文件,只需要几行代码而已)
|
2月前
|
数据采集 JSON 前端开发
从代码到内容:使用C#和Fizzler探索Instagram的深处
Instagram是一个流行的社交媒体平台,拥有数亿的用户和海量的图片和视频内容。如果您想要从Instagram上获取一些有用的信息或数据,您可能需要使用爬虫技术来自动化地抓取和分析网页内容。本文将介绍如何使用C#和Fizzler这两个强大的工具,来实现一个简单而高效的Instagram爬虫,从代码到内容,探索Instagram的深处。
|
3月前
|
存储 传感器 监控
工业相机如何实现实时和本地Raw格式图像和Bitmap格式图像的保存和相互转换(C#代码,UI界面版)
工业相机如何实现实时和本地Raw格式图像和Bitmap格式图像的保存和相互转换(C#代码,UI界面版)
32 0
|
3月前
|
存储 C# 容器
掌握 C# 变量:在代码中声明、初始化和使用不同类型的综合指南
变量是用于存储数据值的容器。 在 C# 中,有不同类型的变量(用不同的关键字定义),例如: int - 存储整数(没有小数点的整数),如 123 或 -123 double - 存储浮点数,有小数点,如 19.99 或 -19.99 char - 存储单个字符,如 'a' 或 'B'。Char 值用单引号括起来 string - 存储文本,如 "Hello World"。String 值用双引号括起来 bool - 存储具有两个状态的值:true 或 false
37 2
|
8月前
|
存储 C# 图形学
代码解析 C# 引用类型还是值类型
代码解析 C# 引用类型还是值类型