原文: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(...)
) createParseSum()
as a copy ofParseRelation
, pass"+", "-"
as ops, passParseSum
toParseRelation
(in place of theParseUnary
), passParseUnary
toParseSum
. That's it. - likewise for
"*", "/", "%"
: makeParseMul
as copy of the above mentionedParseSum
, 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.