Following this question I would like to know if the reuse of lambda parameter expression instances should be considered good or bad?
I sometimes get a complete LINQ expression tree where the same lambda parameter instance is local correctly used in a second, nonnested lambda:
// class Person { public int AProp { get; set; } public bool BProp { get; set; }}
var lparam = Expression.Parameter(typeof(Person),"x");
var lambda1 = (Expression<Func<Person,int>>)Expression.Lambda(Expression.Property(lparam, "AProp"), lparam);
var lambda2 = (Expression<Func<Person,bool>>)Expression.Lambda(Expression.Property(lparam, "BProp"), lparam);
var source = (new Person[0]).AsQueryable();
var query = source.Where(lambda2).OrderBy(lambda1);
So the declaration of the same lambda parameter instance lparam is correct for both lambda1 and lambda2.
It is just that this shared lambda parameter instance forces IQueryProvider implementations to not associate additional global meaning based on the pure lambda parameter reference, as the same parameter could need to be interpreted differently during processing of a different lambda. Also, you will not get this kind of expression tree (or should I say graph?) from LINQ by using
Expression<Func<Person,int>> lambda3 = x => x.AProp;
Expression<Func<Person,bool>> lambda4 = x => x.BProp;
because there will be different parameter instances of (Person x) for both lambda expressions. The same goes for
var query = source.Where(x => x.BProp).OrderBy(x => x.AProp);
or
var query = from x in source where x.BProp order by x.AProp select x;
It also makes the expression tree to be rather a graph.
Is that style of reuse of parameter instances considered good or bad? So far, I have not found a clear answer to this from the authorities.
I would not share a parameter object between two disjoint lambdas.
First, let's not make false economies here. Objects are cheap and you are not going to be making a hundred thousand of these. (And if you are, you probably have larger problems to solve.)
Second, as you note, sharing referentially identical parameter objects across unrelated lambdas means that code which analyzes those lambda expression trees is required to understand that the parameter object has a different meaning in different contexts. That seems like a bug waiting to happen.
Third, one imagines that someday you might want to take two expression trees:
x => Foo(x);
y => Bar(y);
and from them build a third, say:
(x,y) => Foo(x) && Bar(y);
If x and y are actually both the same parameter object then you have a problem on your hands:
(x,x) => Foo(x) && Bar(x); // Huh?
On the other side, StriplingWarrior's answer points out that if you have
x => Foo(x);
x => Bar(x);
then it is easier to combine them as
x => Foo(x) && Bar(x);
because then you do not need to rewrite anything.
Basically, it just seems like a risky move with no truly compelling upside, so why do it?
You need to consider your use case. How might these lambdas be combined in the future?
For example, are you going to want to combine two lambdas using an OR operation?
Expression<Func<Person, bool>> lambda1 = p => !p.IsDeleted;
Expression<Func<Person, bool>> lambda2 = p => p.DomainId == 1;
// How do I get (p => !p.IsDeleted || p.DomainId == 1)?
If so, it's easier to join them like this:
Expression.Lambda<Func<Person, bool>>(
Expression.OrElse(lambda1.Body, lambda2.Body),
lambda1.Parameters[0]);
The above code works just fine if they both have the same parameter expression object. If they don't, suddenly you have to traverse the entire tree of lambda2
, creating a new expression tree that substitutes the parameter for the one that's in the first expression. It can be done, and I've written some utility methods to make it easy when I have to do stuff like this, but if you can identify that this is the type of use case you're going to run into you might as well make life simpler for yourself by using the same parameter in the first place.
On the other hand, if you're going to combine the lambdas in a way that the parameters need to play different roles in a larger lambda expression, as Eric pointed out, then you're going to need those parameters to be different.