Open VitaliyMF opened 7 years ago
Hi, i was just going to add an issue for that, but what you described is quite close The idea i had was to use NReco lambda parser to dynamically build lambda expressions for Linq queries
assuming I have IQueryable
** edit I see how the parsing works in nreco - all parameters identified in the expression are then translated to lambda parameters - so you have to supply all of them to invoke. But i'd like something different - could the parser treat all parameters as fields on the input object (the single parameter to the lambda)? This way we could easily make lambdas for linq operations on collections. Example
this expression
"\"Hello, \" + Name + \", your birth year is \" + BirthDate.Year"
will be parsed as such lambda:
(Name, BirthDate) => "Hello, " + Name + ", your birth year is " + BirthDate.Year
what i'd like to get is
x => "Hello, " + x.Name + ", your birth year is " + x.BirthDate.Year
where x is an object of some type T (T can be known at the time of parsing)
possible?
Thanks RG
But i'd like something different - could the parser treat all parameters as fields on the input object (the single parameter to the lambda)?
yes - this is possible. In fact you already can do that with some piece of extra code smth like:
Expression expr = new LambdaParser().Parse("a>5");
var exprParams = LambdaParser.GetExpressionParameters(expr);
// then add code that maps Func<,> arguments to exprParams
// and compile it to Func<,bool> delegate that can be used in IQueryable
Also you can check how object Eval(string expr, Func<string,object> getVarValue)
is implemented in LambdaParser.cs, I believe its code can answer on most of your possible questions.
I guess it is possible to add overloads like Expression<Func<T1,T2>> LambdaParser.Parse<T>(string expr, string t1VarNameInExpr)
that will make all these things inside LambdaParser library; feel free to add new issue for this.
BTW, this issue is about quote another thing - evaluation performance optimization. Linq expressions itself are strongly typed, but in most cases this is badly usable for user-defined formulas or expressions, especially if variables are not strongly typed. LambdaParser supports evaluation that is similar to C# 'dynamic' type (thats why internally LambdaParameterWrapper is used) but this has some implication on execution performance - it starts to be noticeable when expression is evaluated > 100,000 times (say, for dataset filtering). It is still a place to get better execution time by replacing reflection methods invocation with cache of delegates.
So, I am using this for some configurable formula evaluations. I can always make a class that inherits from LambdaParser but it would be nice if this was already included. I want two things out of this; 1) To have a cached compiled delegate returned and 2) to have a dictionary of Func
I might have gotten confused about the reason for this issue but here is a sample idea of how this could be done. A cached of value getter results may be useful too if something is static but that would take some thought as it kind of conflicts with this. This is also not very useful if caching is turned off.
public Func<object> CreateDelegate(string expr, IDictionary<string, Func<object>> valueGetters)
{
return () => Eval(expr, valueGetters);
}
public object Eval(string expr, IDictionary<string, Func<object>> vars) {
return Eval(expr, (varName) => {
Func<object> val = null;
vars.TryGetValue(varName, out val);
return val?.Invoke();
});
}
@some1one this issue is actually about reflection-based invocations made with InvokeMethod.cs: they may be even faster when compiled to delegate, this is possible in some cases (when no method overloads for the same number of arguments).
As I understand, you mean caching of variables; I've no idea why you need one more "Eval" overload as you can easily cache variables on your side with public object Eval(string expr, Func<string,object> getVarValue)
overload, for example:
Func<string,object> realGetValueDelegate = ... ;
var varCache = new Dictionary<string,object>();
var exprResult = lambdaParser.Eval(exprStr, (varName) => {
if (varCache.TryGetValue(varName, out var v))
return v;
var val = realGetValueDelegate(varName);
varCache[varName] = val;
return val;
});
No need to inherit from LambdaParser; if you like you can add extension method that adds this 'variables caching' behavior and use it in your project.
However, caching of variables may cause various side-effects and usually this is very project-specific thing. For now I'm not sure that it is better to include this code into library.
Okay, I understand now. I don't need that kind of performance for this project, but the custom filtering use case has already come up on another project that I am considering this for now. I'll have to do some benchmark s to see if will actually be a problem though.
And for the variable caching, yes, that makes a lot more sense when I think about it that way.
Right now all invocations are performed through reflection (InvokeMethod / InvokeDelegate / InvokeIndexer / InvokePropertyOrField). At the same time, evaluation performance is acceptable for most applications: 10,000 evals take about 20-30ms (depending on CPU).
It is possible to improve evaluation performance in cases if invocation is not ambiguous (only one method overload is available according to number of parameters); this might be useful if LambdaParser is used for in-memory filtering of large dataset (say, >100k rows) by user-defined expression.
If someone needs this improvement please leave a comment or vote for this issue.