C# 实现eval,支持任意个数、任意基本类型的参数

js中有一个方法eval,能够随时执行用户编写的代码。

例如:js中的代码:

eval("alert('hello world');");

将会弹出一个写有hello world的提示框。

但C#中却没有对应的方法。

Google了一下,网站http://www.ckode.dk/programming/eval-in-c-yes-its-possible/基本实现了两个参数的eval。但是,对于不定参数并不适用。其实编写C#版的eval的难点在于c#是强类型语言。琢磨了许久,终于碰巧写出了。以下是相关类:

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

public class Arg
{
    public string _type;
    public string _name;
    public Arg(string type, string name)
    {
        this._type = type;
        this._name = name;
    }
}

public class EvalProvider
{
    private string errorMessage = "";

    public string getErrorMessage()
    {
        return this.errorMessage;
    }

    public MethodInfo CreateMethod(List<Arg> args, Type returnType, string code, string[] usingStatements = null, string[] assemblies = null)
    {
        var includeUsings = new HashSet<string>(new[] { "System" });
        includeUsings.Add(returnType.Namespace);
        string argStr = "";
        foreach (var arg in args)
        {
            try
            {
                Type t = GetTypeByString(arg._type);
                includeUsings.Add(t.Namespace);
                argStr += ", " + arg._type + " " + arg._name;
            }
            catch
            {
                errorMessage = "uncompleted arg type: " + arg._type;
                return null;
            }
        }
        if (!argStr.Equals(""))
        {
            argStr = argStr.Substring(2);
        }
        if (usingStatements != null)
            foreach (var usingStatement in usingStatements)
                includeUsings.Add(usingStatement);

        MethodInfo method;
        using (CSharpCodeProvider compiler = new CSharpCodeProvider())
        {
            var name = "F" + Guid.NewGuid().ToString().Replace("-", string.Empty);
            var includeAssemblies = new HashSet<string>(new[] { "system.dll" });
            if (assemblies != null)
                foreach (var assembly in assemblies)
                    includeAssemblies.Add(assembly);

            var parameters = new CompilerParameters(includeAssemblies.ToArray())
            {
                GenerateInMemory = true
            };

            string source = string.Format(@"
{0}
namespace {1}
{{
	public static class EvalClass
	{{
		public static {2} Eval({3})
		{{
			{4}
		}}
	}}
}}", GetUsing(includeUsings), name, returnType.Name, argStr, code);
            
            var compilerResult = compiler.CompileAssemblyFromSource(parameters, source);
            var compiledAssembly = compilerResult.CompiledAssembly;
            var type = compiledAssembly.GetType(string.Format("{0}.EvalClass", name));
            method = type.GetMethod("Eval");
        }
        return method;
    }

    private string GetUsing(HashSet<string> usingStatements)
    {
        StringBuilder result = new StringBuilder();
        foreach (string usingStatement in usingStatements)
        {
            result.AppendLine(string.Format("using {0};", usingStatement));
        }
        return result.ToString();
    }

    /// <summary>
    /// 根据字符串获得类型,目前只适合简单类型。若是复杂类型,可能会抛出异常
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public Type GetTypeByString(string type)
    {
        switch (type.ToLower())
        {
            case "bool":
                return Type.GetType("System.Boolean", true, true);
            case "byte":
                return Type.GetType("System.Byte", true, true);
            case "sbyte":
                return Type.GetType("System.SByte", true, true);
            case "char":
                return Type.GetType("System.Char", true, true);
            case "decimal":
                return Type.GetType("System.Decimal", true, true);
            case "double":
                return Type.GetType("System.Double", true, true);
            case "float":
                return Type.GetType("System.Single", true, true);
            case "int":
                return Type.GetType("System.Int32", true, true);
            case "uint":
                return Type.GetType("System.UInt32", true, true);
            case "long":
                return Type.GetType("System.Int64", true, true);
            case "ulong":
                return Type.GetType("System.UInt64", true, true);
            case "object":
                return Type.GetType("System.Object", true, true);
            case "short":
                return Type.GetType("System.Int16", true, true);
            case "ushort":
                return Type.GetType("System.UInt16", true, true);
            case "string":
                return Type.GetType("System.String", true, true);
            case "date":
            case "datetime":
                return Type.GetType("System.DateTime", true, true);
            case "guid":
                return Type.GetType("System.Guid", true, true);
            default:
                return Type.GetType(type, true, true);
        }
    }
}

由于其中的方法GetTypeByString不能获得所有的类型,因此该C#版的eval不能支持所有的类型,只支持一些基本类型。

调用方法:

EvalProvider eval=new EvalProvider();
List<Arg> argList = new List<Arg>();
argList.Add(new Arg("int", "a"));
argList.Add(new Arg("int", "b"));
argList.Add(new Arg("string", "c"));
var method= eval.CreateArgMethod(argList, 
                            eval.GetTypeByString("string"), 
                            @"return ""Hello world "" + (a + b) + c;");
object result = method.Invoke(null, new object[] { 2, 2 , " never mind!"});
Console.WriteLine((string)Convert.ChangeType(result, eval.GetTypeByString("string")));

输出:

Hello world 4 never mind!


0 条评论

    发表评论

    电子邮件地址不会被公开。 必填项已用 * 标注