[置顶] Linq表达式树解析1

原文:http://www.codeproject.com/Articles/355513/Invent-your-own-Dynamic-LINQ-parser

LINQ Expression Tree

  • LINQ Expression Tree: http://msdn.microsoft.com/en-us/library/bb397951.aspx
  • LINQ Expressions: http://msdn.microsoft.com/en-us/library/system.linq.expressions.aspx
  • Expression Factory: http://msdn.microsoft.com/en-us/library/system.linq.expressions.expression.aspx

Dynamic LINQ

  • ScottGu's Blog http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

核心代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;

namespace SimpleExpression
{
    public abstract class PredicateParser
    {
        #region scanner
        /// <summary>tokenizer pattern: Optional-SpaceS...Token...Optional-Spaces</summary>
        private static readonly string _pattern = @"\s*(" + string.Join("|", new string[]
          {
              // operators and punctuation that are longer than one char: longest first
              string.Join("|", new string[] { "||", "&&", "==", "!=", "<=", ">=" }.Select(e => Regex.Escape(e))),
              @"""(?:\\.|[^""])*""",  // string
              @"\d+(?:\.\d+)?",       // number with optional decimal part
              @"\w+",                 // word
              @"\S",                  // other 1-char tokens (or eat up one character in case of an error)
          }) + @")\s*";
        /// <summary>get 1st char of current token (or a Space if no 1st char is obtained)</summary>
        private char Ch { get { return string.IsNullOrEmpty(Curr) ? ' ' : Curr[0]; } }
        /// <summary>move one token ahead</summary><returns>true = moved ahead, false = end of stream</returns>
        private bool Move() { return _tokens.MoveNext(); }
        /// <summary>the token stream implemented as IEnumerator<string></summary>
        private IEnumerator<string> _tokens;
        /// <summary>constructs the scanner for the given input string</summary>
        protected PredicateParser(string s)
        {
            _tokens = Regex.Matches(s, _pattern, RegexOptions.Compiled).Cast<Match>()
                      .Select(m => m.Groups[1].Value).GetEnumerator();
            Move();
        }
        protected bool IsNumber { get { return char.IsNumber(Ch); } }
        protected bool IsDouble { get { return IsNumber && Curr.Contains('.'); } }
        protected bool IsString { get { return Ch == '"'; } }
        protected bool IsIdent { get { char c = Ch; return char.IsLower(c) || char.IsUpper(c) || c == '_'; } }
        /// <summary>throw an argument exception</summary>
        protected void Abort(string msg) { throw new ArgumentException("Error: " + (msg ?? "unknown error")); }
        /// <summary>get the current item of the stream or an empty string after the end</summary>
        protected string Curr { get { return _tokens.Current ?? string.Empty; } }
        /// <summary>get current and move to the next token (error if at end of stream)</summary>
        protected string CurrAndNext { get { string s = Curr; if (!Move()) Abort("data expected"); return s; } }
        /// <summary>get current and move to the next token if available</summary>
        protected string CurrOptNext { get { string s = Curr; Move(); return s; } }
        /// <summary>moves forward if current token matches and returns that (next token must exist)</summary>
        protected string CurrOpAndNext(params string[] ops)
        {
            string s = ops.Contains(Curr) ? Curr : null;
            if (s != null && !Move()) Abort("data expected");
            return s;
        }
        #endregion
    }
    public class PredicateParser<TData> : PredicateParser
    {
        #region code generator

        private static readonly Type _bool = typeof(bool);
        private static readonly Type[] _prom = new Type[]
        { 
            typeof(decimal), typeof(double), typeof(float), 
            typeof(ulong), typeof(long), typeof(uint),
            typeof(int), typeof(ushort), typeof(char),
            typeof(short), typeof(byte), typeof(sbyte)
        };

        /// <summary>enforce the type on the expression (by a cast) if not already of that type</summary>
        private static Expression Coerce(Expression expr, Type type)
        {
            return expr.Type == type ? expr : Expression.Convert(expr, type);
        }

        /// <summary>casts if needed the expr to the "largest" type of both arguments</summary>
        private static Expression Coerce(Expression expr, Expression sibling)
        {
            if (expr.Type != sibling.Type)
            {
                Type maxType = MaxType(expr.Type, sibling.Type);
                if (maxType != expr.Type) expr = Expression.Convert(expr, maxType);
            }
            return expr;
        }

        /// <summary>returns the first if both are same, or the largest type of both (or the first)</summary>
        private static Type MaxType(Type a, Type b)
        {
            return a == b ? a : (_prom.FirstOrDefault(t => t == a || t == b) ?? a);
        }
        /// <summary>
        /// Code generation of binary and unary epressions, utilizing type coercion where needed
        /// </summary>
        private static readonly Dictionary<string, Func<Expression, Expression, Expression>> _binOp =
        new Dictionary<string, Func<Expression, Expression, Expression>>()
        {
            { "||", (a,b)=>Expression.OrElse(Coerce(a, _bool), Coerce(b, _bool)) },
            { "&&", (a,b)=>Expression.AndAlso(Coerce(a, _bool), Coerce(b, _bool)) },
            { "==", (a,b)=>Expression.Equal(Coerce(a,b), Coerce(b,a)) },
            { "!=", (a,b)=>Expression.NotEqual(Coerce(a,b), Coerce(b,a)) },
            { "<", (a,b)=>Expression.LessThan(Coerce(a,b), Coerce(b,a)) },
            { "<=", (a,b)=>Expression.LessThanOrEqual(Coerce(a,b), Coerce(b,a)) },
            { ">=", (a,b)=>Expression.GreaterThanOrEqual(Coerce(a,b), Coerce(b,a)) },
            { ">", (a,b)=>Expression.GreaterThan(Coerce(a,b), Coerce(b,a)) },
        };

        private static readonly Dictionary<string, Func<Expression, Expression>> _unOp =
        new Dictionary<string, Func<Expression, Expression>>()
        {
            { "!", a=>Expression.Not(Coerce(a, _bool)) },
        };

        /// <summary>create a constant of a value</summary>
        private static ConstantExpression Const(object v) { return Expression.Constant(v); }
        /// <summary>create lambda parameter field or property access</summary>
        private MemberExpression ParameterMember(string s) { return Expression.PropertyOrField(_param, s); }
        /// <summary>create lambda expression</summary>
        private Expression<Func<TData, bool>> Lambda(Expression expr) { return Expression.Lambda<Func<TData, bool>>(expr, _param); }
        /// <summary>the lambda's parameter (all names are members of this)</summary>
        private readonly ParameterExpression _param = Expression.Parameter(typeof(TData), "_p_");
        #endregion

        #region parser
        /// <summary>initialize the parser (and thus, the scanner)</summary>
        private PredicateParser(string s) : base(s) { }
        /// <summary>main entry point</summary>
        public static Expression<Func<TData, bool>> Parse(string s) { return new PredicateParser<TData>(s).Parse(); }
        private Expression<Func<TData, bool>> Parse() { return Lambda(ParseExpression()); }
        private Expression ParseExpression() { return ParseOr(); }
        private Expression ParseOr() { return ParseBinary(ParseAnd, "||"); }
        private Expression ParseAnd() { return ParseBinary(ParseEquality, "&&"); }
        private Expression ParseEquality() { return ParseBinary(ParseRelation, "==", "!="); }
        private Expression ParseRelation() { return ParseBinary(ParseUnary, "<", "<=", ">=", ">"); }
        private Expression ParseUnary()
        {
            return CurrOpAndNext("!") != null ? _unOp["!"](ParseUnary()) : ParsePrimary();
        }
        //private Expression ParseIdent() { return ParameterMember(CurrOptNext); } //不支持嵌套
        private Expression ParseIdent() //修改支持嵌套
        {
            Expression expr = ParameterMember(CurrOptNext);
            while (CurrOpAndNext(".") != null && IsIdent) expr = Expression.PropertyOrField(expr, CurrOptNext);
            return expr;
        }
        private Expression ParseString()
        {
            return Const(Regex.Replace(CurrOptNext, "^\"(.*)\"$", m => m.Groups[1].Value));
        }
        private Expression ParseNumber()
        {
            if (IsDouble) return Const(double.Parse(CurrOptNext));
            return Const(int.Parse(CurrOptNext));
        }
        private Expression ParsePrimary()
        {
            if (IsIdent) return ParseIdent();
            if (IsString) return ParseString();
            if (IsNumber) return ParseNumber();
            return ParseNested();
        }
        private Expression ParseNested()
        {
            if (CurrAndNext != "(") Abort("(...) expected");
            Expression expr = ParseExpression();
            if (CurrOptNext != ")") Abort("')' expected");
            return expr;
        }
        /// <summary>generic parsing of binary expressions</summary>
        private Expression ParseBinary(Func<Expression> parse, params string[] ops)
        {
            Expression expr = parse();
            string op;
            while ((op = CurrOpAndNext(ops)) != null) expr = _binOp[op](expr, parse());
            return expr;
        }
        #endregion
    }
}
测试代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;

namespace SimpleExpression
{
    public abstract class PredicateParser
    {
        #region scanner
        /// <summary>tokenizer pattern: Optional-SpaceS...Token...Optional-Spaces</summary>
        private static readonly string _pattern = @"\s*(" + string.Join("|", new string[]
          {
              // operators and punctuation that are longer than one char: longest first
              string.Join("|", new string[] { "||", "&&", "==", "!=", "<=", ">=" }.Select(e => Regex.Escape(e))),
              @"""(?:\\.|[^""])*""",  // string
              @"\d+(?:\.\d+)?",       // number with optional decimal part
              @"\w+",                 // word
              @"\S",                  // other 1-char tokens (or eat up one character in case of an error)
          }) + @")\s*";
        /// <summary>get 1st char of current token (or a Space if no 1st char is obtained)</summary>
        private char Ch { get { return string.IsNullOrEmpty(Curr) ? ' ' : Curr[0]; } }
        /// <summary>move one token ahead</summary><returns>true = moved ahead, false = end of stream</returns>
        private bool Move() { return _tokens.MoveNext(); }
        /// <summary>the token stream implemented as IEnumerator<string></summary>
        private IEnumerator<string> _tokens;
        /// <summary>constructs the scanner for the given input string</summary>
        protected PredicateParser(string s)
        {
            _tokens = Regex.Matches(s, _pattern, RegexOptions.Compiled)
                        .Cast<Match>()
                        .Select(m => m.Groups[1].Value)
                        .GetEnumerator();
            Move();
        }
        protected bool IsNumber { get { return char.IsNumber(Ch); } }
        protected bool IsDouble { get { return IsNumber && Curr.Contains('.'); } }
        protected bool IsString { get { return Ch == '"'; } }
        protected bool IsIdent { get { char c = Ch; return char.IsLower(c) || char.IsUpper(c) || c == '_'; } }
        /// <summary>throw an argument exception</summary>
        protected void Abort(string msg) { throw new ArgumentException("Error: " + (msg ?? "unknown error")); }
        /// <summary>get the current item of the stream or an empty string after the end</summary>
        protected string Curr { get { return _tokens.Current ?? string.Empty; } }
        /// <summary>get current and move to the next token (error if at end of stream)</summary>
        protected string CurrAndNext { get { string s = Curr; if (!Move()) Abort("data expected"); return s; } }
        /// <summary>get current and move to the next token if available</summary>
        protected string CurrOptNext { get { string s = Curr; Move(); return s; } }
        /// <summary>moves forward if current token matches and returns that (next token must exist)</summary>
        protected string CurrOpAndNext(params string[] ops)
        {
            string s = ops.Contains(Curr) ? Curr : null;
            if (s != null && !Move()) Abort("data expected");
            return s;
        }
        #endregion
    }
    public class PredicateParser<TData> : PredicateParser
    {
        #region code generator
        private static readonly Expression _zero = Expression.Constant(0); //add
        private static readonly Type _bool = typeof(bool);
        private static readonly Type[] _prom = new Type[]
        { 
            typeof(decimal), typeof(double), typeof(float), 
            typeof(ulong), typeof(long), typeof(uint),
            typeof(int), typeof(ushort), typeof(char),
            typeof(short), typeof(byte), typeof(sbyte)
        };

        /// <summary>enforce the type on the expression (by a cast) if not already of that type</summary>
        private static Expression Coerce(Expression expr, Type type)
        {
            return expr.Type == type ? expr : Expression.Convert(expr, type);
        }

        /// <summary>casts if needed the expr to the "largest" type of both arguments</summary>
        private static Expression Coerce(Expression expr, Expression sibling)
        {
            if (expr.Type != sibling.Type)
            {
                Type maxType = MaxType(expr.Type, sibling.Type);
                if (maxType != expr.Type) expr = Expression.Convert(expr, maxType);
            }
            return expr;
        }

        /// <summary>returns the first if both are same, or the largest type of both (or the first)</summary>
        private static Type MaxType(Type a, Type b)
        {
            return a == b ? a : (_prom.FirstOrDefault(t => t == a || t == b) ?? a);
        }

        /// <summary>produce comparison based on IComparable types</summary>
        private static Expression CompareToExpression(Expression lhs, Expression rhs, Func<Expression, Expression> rel)
        {
            lhs = Coerce(lhs, rhs);
            rhs = Coerce(rhs, lhs);
            Expression cmp = Expression.Call(
                lhs,
                lhs.Type.GetMethod("CompareTo", new Type[] { rhs.Type })
                    ?? lhs.Type.GetMethod("CompareTo", new Type[] { typeof(object) }),
                rhs
            );
            return rel(cmp);
        }

        /// <summary>
        /// Code generation of binary and unary epressions, utilizing type coercion where needed
        /// </summary>
        private static readonly Dictionary<string, Func<Expression, Expression, Expression>> _binOp =
        new Dictionary<string, Func<Expression, Expression, Expression>>()
        {
            { "||", (a,b)=>Expression.OrElse(Coerce(a, _bool), Coerce(b, _bool)) },
            { "&&", (a,b)=>Expression.AndAlso(Coerce(a, _bool), Coerce(b, _bool)) },
            //{ "==", (a,b)=>Expression.Equal(Coerce(a,b), Coerce(b,a)) },
            //{ "!=", (a,b)=>Expression.NotEqual(Coerce(a,b), Coerce(b,a)) },
            //{ "<", (a,b)=>Expression.LessThan(Coerce(a,b), Coerce(b,a)) },
            //{ "<=", (a,b)=>Expression.LessThanOrEqual(Coerce(a,b), Coerce(b,a)) },
            //{ ">=", (a,b)=>Expression.GreaterThanOrEqual(Coerce(a,b), Coerce(b,a)) },
            //{ ">", (a,b)=>Expression.GreaterThan(Coerce(a,b), Coerce(b,a)) },
            //Replace to=>
            { "==", (a,b)=>CompareToExpression(a, b, c=>Expression.Equal             (c, _zero)) },
            { "!=", (a,b)=>CompareToExpression(a, b, c=>Expression.NotEqual          (c, _zero)) },
            { "<",  (a,b)=>CompareToExpression(a, b, c=>Expression.LessThan          (c, _zero)) },
            { "<=", (a,b)=>CompareToExpression(a, b, c=>Expression.LessThanOrEqual   (c, _zero)) },
            { ">=", (a,b)=>CompareToExpression(a, b, c=>Expression.GreaterThanOrEqual(c, _zero)) },
            { ">",  (a,b)=>CompareToExpression(a, b, c=>Expression.GreaterThan       (c, _zero)) },    
            //To extend the parser=>
            { "+", (a,b)=>Expression.Add(Coerce(a,b), Coerce(b,a)) },
            { "-", (a,b)=>Expression.Subtract(Coerce(a,b), Coerce(b,a)) },
            { "*", (a,b)=>Expression.Multiply(Coerce(a,b), Coerce(b,a)) },
            { "/", (a,b)=>Expression.Divide(Coerce(a,b), Coerce(b,a)) },
            { "%", (a,b)=>Expression.Modulo(Coerce(a,b), Coerce(b,a)) },
        };

        private static readonly Dictionary<string, Func<Expression, Expression>> _unOp =
        new Dictionary<string, Func<Expression, Expression>>()
        {
            { "!", a=>Expression.Not(Coerce(a, _bool)) },
            //To extend the parser=>
            { "-", a=>Expression.Negate(a) },
        };

        /// <summary>create a constant of a value</summary>
        private static ConstantExpression Const(object v) { return Expression.Constant(v); }
        /// <summary>create lambda parameter field or property access</summary>
        private MemberExpression ParameterMember(string s) { return Expression.PropertyOrField(_param, s); }
        /// <summary>create lambda expression</summary>
        private Expression<Func<TData, bool>> Lambda(Expression expr) { return Expression.Lambda<Func<TData, bool>>(expr, _param); }
        /// <summary>the lambda's parameter (all names are members of this)</summary>
        private readonly ParameterExpression _param = Expression.Parameter(typeof(TData), "_p_");
        #endregion

        #region parser
        /// <summary>initialize the parser (and thus, the scanner)</summary>
        private PredicateParser(string s) : base(s) { }
        /// <summary>main entry point</summary>
        public static Expression<Func<TData, bool>> Parse(string s) { return new PredicateParser<TData>(s).Parse(); }
        private Expression<Func<TData, bool>> Parse() { return Lambda(ParseExpression()); }
        private Expression ParseExpression() { return ParseOr(); }
        private Expression ParseOr() { return ParseBinary(ParseAnd, "||"); }
        private Expression ParseAnd() { return ParseBinary(ParseEquality, "&&"); }
        private Expression ParseEquality() { return ParseBinary(ParseRelation, "==", "!="); }
        //private Expression ParseRelation() { return ParseBinary(ParseUnary, "<", "<=", ">=", ">"); }
        private Expression ParseRelation() { return ParseBinary(ParseSum, "<", "<=", ">=", ">"); }
        private Expression ParseSum() { return ParseBinary(ParseMul, "+", "-"); }
        private Expression ParseMul() { return ParseBinary(ParseUnary, "*", "/", "%"); }

        private Expression ParseUnary()
        {
            if (CurrOpAndNext("!") != null) return _unOp["!"](ParseUnary());
            if (CurrOpAndNext("-") != null) return _unOp["-"](ParseUnary());
            return ParsePrimary();
            //return CurrOpAndNext("!") != null ? _unOp["!"](ParseUnary()) : ParsePrimary();
        }
        //private Expression ParseIdent() { return ParameterMember(CurrOptNext); } //不支持嵌套
        private Expression ParseIdent() //修改支持嵌套
        {
            Expression expr = ParameterMember(CurrOptNext);
            while (CurrOpAndNext(".") != null && IsIdent) expr = Expression.PropertyOrField(expr, CurrOptNext);
            return expr;
        }
        private Expression ParseString()
        {
            return Const(Regex.Replace(CurrOptNext, "^\"(.*)\"$", m => m.Groups[1].Value));
        }
        private Expression ParseNumber()
        {
            if (IsDouble) return Const(double.Parse(CurrOptNext));
            return Const(int.Parse(CurrOptNext));
        }
        private Expression ParsePrimary()
        {
            if (IsIdent) return ParseIdent();
            if (IsString) return ParseString();
            if (IsNumber) return ParseNumber();
            return ParseNested();
        }
        private Expression ParseNested()
        {
            if (CurrAndNext != "(") Abort("(...) expected");
            Expression expr = ParseExpression();
            if (CurrOptNext != ")") Abort("')' expected");
            return expr;
        }
        /// <summary>generic parsing of binary expressions</summary>
        private Expression ParseBinary(Func<Expression> parse, params string[] ops)
        {
            Expression expr = parse();
            string op;
            while ((op = CurrOpAndNext(ops)) != null) expr = _binOp[op](expr, parse());
            return expr;
        }
        #endregion
    }
}

测试代码:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqExpTest
{
    /// <summary>
    /// Linq 表达式树 测试
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            var items = new List<Element>()
            {
#region
                //new Element("a", 1000,new List<CC>{new CC{email="fdsf@fd",age=12}}),
                //new Element("b", 900,new List<CC>{new CC{email="fdsf@fd",age=12}}),
                //new Element("c", 800,new List<CC>{new CC{email="fdsf@fd",age=12}}),
                //new Element("d", 700,new List<CC>{new CC{email="fdsf@fd",age=12}}),
                //new Element("e", 600,new List<CC>{new CC{email="fdsf@fd",age=12}}),
                //new Element("x", 500,new List<CC>{new CC{email="fdsf@fd",age=12}}),
                //new Element("y", 400,new List<CC>{new CC{email="fdsf@fd",age=12}}),
                //new Element("z", 300,new List<CC>{new CC{email="fdsf@fd",age=12}})
#endregion
                new Element("a", 1000,new CC{email="fdsf@fd",age=12}),
                new Element("b", 900,new CC{email="fdsf@fd",age=17}),
                new Element("c", 800,new CC{email="fdsf@fd",age=25}),
                new Element("d", 700,new CC{email="fdsf@fd",age=8}),
                new Element("e", 600,new CC{email="fdsf@fd",age=9}),
                new Element("x", 500,new CC{email="fdsf@fd",age=35}),
                new Element("y", 400,new CC{email="fdsf@fd",age=23}),
                new Element("z", 900,new CC{email="fdsf@fd",age=16})
            };

            //string s = "Name == \"x\" || Number >= 800 && PCC.age % 2==0 && PCC.age>15 && PCC.age*2<35";
            string s = "Name == \"x\" || Number >= 800 && PCC.age>15 && PCC.age*2<35";
            var pred = SimpleExpression.PredicateParser<Element>.Parse(s);
            Console.WriteLine("User Entry: {0}", s);
            Console.WriteLine("Expr Tree:  {0}", pred.ToString());

            var f = pred.Compile();
            Console.WriteLine("\r\n==============mark affected items==============");
            foreach (var item in items)
            {
                Console.WriteLine("{2} Name = {0}, Number = {1}, CC: email={3}, age={4}"
                    , item.Name, item.Number, f(item) ? "x" : " ", item.PCC.email, item.PCC.age);
            }
            Console.WriteLine("==============where-select==============");
            var q = from e in items where f(e) select e;
            foreach (var item in q)
            {
                Console.WriteLine("  Name = {0}, Number = {1},CC: email={2}, age={3}",
                    item.Name, item.Number, item.PCC.email, item.PCC.age);
            }

            Console.ReadKey();
        }
    }

    class Element
    {
        private string _name;
        private int _number;
        private CC _cc;
        //private IList<CC> _listcc;
        //public Element(string name, int number, List<CC> listcc)
        public Element(string name, int number, CC cc)
        {
            this._name = name;
            this._number = number;
            this._cc = cc;
            //this._listcc = listcc;
        }

        public string Name { get { return _name; } set { _name = value; } }
        public int Number { get { return _number; } set { _number = value; } }
        public CC PCC { get { return _cc; } set { _cc = value; } }
        //public IList<CC> ListCC { get { return _listcc; } set { _listcc = value; } }
    }
    class CC
    {
        public string email { get; set; }
        public int age { get; set; }
    }
}


输出结果:
[置顶] Linq表达式树解析1_第1张图片

string s = "Name == \"x\" || Number >= 800 && PCC.age % 2==0 && PCC.age>15 && PCC.age*2<35";

[置顶] Linq表达式树解析1_第2张图片

原文下面提出了对此方法的扩展说明:

Add more operations

To extend the parser: You may easily add more to the expressions, especially new operators is a simple thing:

  • to add + and - binary operators, add them to the _binOp dictionary (similar to ==, e.g. , ("+":Expression.Add(...)"-": Expression.Subtract(...)) create ParseSum() as a copy ofParseRelation, pass "+", "-" as ops, pass ParseSum to ParseRelation (in place of the ParseUnary), pass ParseUnary to ParseSum. That's it.
  • likewise for "*", "/", "%": make ParseMul as copy of the above mentioned ParseSum, pass the rightParseXXX actions, add the respective Expression factories to the _binOps dictionary. Done.
  • An unary "-" is to be added in the _unOps dictionary (no coercion needed). The parsing is done in theParseUnary() function, e.g.
......


你可能感兴趣的:(cast,expression,Expression.Call,GetEnumerator,Regex.Matches)