原文:http://www.codeproject.com/Articles/355513/Invent-your-own-Dynamic-LINQ-parser
LINQ Expression Tree
Dynamic LINQ
核心代码:
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
/// tokenizer pattern: Optional-SpaceS...Token...Optional-Spaces
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*";
/// get 1st char of current token (or a Space if no 1st char is obtained)
private char Ch { get { return string.IsNullOrEmpty(Curr) ? ' ' : Curr[0]; } }
/// move one token ahead true = moved ahead, false = end of stream
private bool Move() { return _tokens.MoveNext(); }
/// the token stream implemented as IEnumerator
private IEnumerator _tokens;
/// constructs the scanner for the given input string
protected PredicateParser(string s)
{
_tokens = Regex.Matches(s, _pattern, RegexOptions.Compiled).Cast()
.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 == '_'; } }
/// throw an argument exception
protected void Abort(string msg) { throw new ArgumentException("Error: " + (msg ?? "unknown error")); }
/// get the current item of the stream or an empty string after the end
protected string Curr { get { return _tokens.Current ?? string.Empty; } }
/// get current and move to the next token (error if at end of stream)
protected string CurrAndNext { get { string s = Curr; if (!Move()) Abort("data expected"); return s; } }
/// get current and move to the next token if available
protected string CurrOptNext { get { string s = Curr; Move(); return s; } }
/// moves forward if current token matches and returns that (next token must exist)
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 : 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)
};
/// enforce the type on the expression (by a cast) if not already of that type
private static Expression Coerce(Expression expr, Type type)
{
return expr.Type == type ? expr : Expression.Convert(expr, type);
}
/// casts if needed the expr to the "largest" type of both arguments
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;
}
/// returns the first if both are same, or the largest type of both (or the first)
private static Type MaxType(Type a, Type b)
{
return a == b ? a : (_prom.FirstOrDefault(t => t == a || t == b) ?? a);
}
///
/// Code generation of binary and unary epressions, utilizing type coercion where needed
///
private static readonly Dictionary> _binOp =
new Dictionary>()
{
{ "||", (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> _unOp =
new Dictionary>()
{
{ "!", a=>Expression.Not(Coerce(a, _bool)) },
};
/// create a constant of a value
private static ConstantExpression Const(object v) { return Expression.Constant(v); }
/// create lambda parameter field or property access
private MemberExpression ParameterMember(string s) { return Expression.PropertyOrField(_param, s); }
/// create lambda expression
private Expression> Lambda(Expression expr) { return Expression.Lambda>(expr, _param); }
/// the lambda's parameter (all names are members of this)
private readonly ParameterExpression _param = Expression.Parameter(typeof(TData), "_p_");
#endregion
#region parser
/// initialize the parser (and thus, the scanner)
private PredicateParser(string s) : base(s) { }
/// main entry point
public static Expression> Parse(string s) { return new PredicateParser(s).Parse(); }
private Expression> 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;
}
/// generic parsing of binary expressions
private Expression ParseBinary(Func 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
/// tokenizer pattern: Optional-SpaceS...Token...Optional-Spaces
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*";
/// get 1st char of current token (or a Space if no 1st char is obtained)
private char Ch { get { return string.IsNullOrEmpty(Curr) ? ' ' : Curr[0]; } }
/// move one token ahead true = moved ahead, false = end of stream
private bool Move() { return _tokens.MoveNext(); }
/// the token stream implemented as IEnumerator
private IEnumerator _tokens;
/// constructs the scanner for the given input string
protected PredicateParser(string s)
{
_tokens = Regex.Matches(s, _pattern, RegexOptions.Compiled)
.Cast()
.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 == '_'; } }
/// throw an argument exception
protected void Abort(string msg) { throw new ArgumentException("Error: " + (msg ?? "unknown error")); }
/// get the current item of the stream or an empty string after the end
protected string Curr { get { return _tokens.Current ?? string.Empty; } }
/// get current and move to the next token (error if at end of stream)
protected string CurrAndNext { get { string s = Curr; if (!Move()) Abort("data expected"); return s; } }
/// get current and move to the next token if available
protected string CurrOptNext { get { string s = Curr; Move(); return s; } }
/// moves forward if current token matches and returns that (next token must exist)
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 : 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)
};
/// enforce the type on the expression (by a cast) if not already of that type
private static Expression Coerce(Expression expr, Type type)
{
return expr.Type == type ? expr : Expression.Convert(expr, type);
}
/// casts if needed the expr to the "largest" type of both arguments
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;
}
/// returns the first if both are same, or the largest type of both (or the first)
private static Type MaxType(Type a, Type b)
{
return a == b ? a : (_prom.FirstOrDefault(t => t == a || t == b) ?? a);
}
/// produce comparison based on IComparable types
private static Expression CompareToExpression(Expression lhs, Expression rhs, Func 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);
}
///
/// Code generation of binary and unary epressions, utilizing type coercion where needed
///
private static readonly Dictionary> _binOp =
new Dictionary>()
{
{ "||", (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> _unOp =
new Dictionary>()
{
{ "!", a=>Expression.Not(Coerce(a, _bool)) },
//To extend the parser=>
{ "-", a=>Expression.Negate(a) },
};
/// create a constant of a value
private static ConstantExpression Const(object v) { return Expression.Constant(v); }
/// create lambda parameter field or property access
private MemberExpression ParameterMember(string s) { return Expression.PropertyOrField(_param, s); }
/// create lambda expression
private Expression> Lambda(Expression expr) { return Expression.Lambda>(expr, _param); }
/// the lambda's parameter (all names are members of this)
private readonly ParameterExpression _param = Expression.Parameter(typeof(TData), "_p_");
#endregion
#region parser
/// initialize the parser (and thus, the scanner)
private PredicateParser(string s) : base(s) { }
/// main entry point
public static Expression> Parse(string s) { return new PredicateParser(s).Parse(); }
private Expression> 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;
}
/// generic parsing of binary expressions
private Expression ParseBinary(Func 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
{
///
/// Linq 表达式树 测试
///
class Program
{
static void Main(string[] args)
{
var items = new List()
{
#region
//new Element("a", 1000,new List{new CC{email="fdsf@fd",age=12}}),
//new Element("b", 900,new List{new CC{email="fdsf@fd",age=12}}),
//new Element("c", 800,new List{new CC{email="fdsf@fd",age=12}}),
//new Element("d", 700,new List{new CC{email="fdsf@fd",age=12}}),
//new Element("e", 600,new List{new CC{email="fdsf@fd",age=12}}),
//new Element("x", 500,new List{new CC{email="fdsf@fd",age=12}}),
//new Element("y", 400,new List{new CC{email="fdsf@fd",age=12}}),
//new Element("z", 300,new List{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.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 _listcc;
//public Element(string name, int number, List 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 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:
+
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."*", "/", "%"
: make ParseMul
as copy of the above mentioned ParseSum
, pass the rightParseXXX
actions, add the respective Expression factories to the _binOps
dictionary. Done."-"
is to be added in the _unOps
dictionary (no coercion needed). The parsing is done in theParseUnary()
function, e.g.