Linq表达式树解析1

来源:互联网 发布:淘宝店怎么设置佣金 编辑:程序博客网 时间:2024/06/10 21:29

原文: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; }    }}


输出结果:

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


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

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.
......


0 0
原创粉丝点击