编译器编译机制解析及嵌套泛型Expression<Func<T,bool>>编译问询
Hey there! Let's unpack your questions about expression trees, dynamic compilation, and that manual expression-building code—this is such a cool part of .NET once you get the hang of how it all fits together.
Expression<Func<T, bool>> Basics First, let's clarify: Expression<Func<T, bool>> is itself an expression tree representing a delegate that takes a T and returns a bool. When you talk about "nested" versions (like Expression<Func<T, Expression<Func<U, bool>>>>), this just means an expression tree that produces another expression tree as its result. These are useful in scenarios where you need to dynamically generate filtering logic that depends on nested types, or build composable query conditions.
The key thing to remember is that an expression tree isn't executable code—it's a data structure that describes code. This is why LINQ providers like Entity Framework can translate it into SQL: they traverse the tree to understand what logic you want to run, then generate equivalent database queries.
Let's split this into two parts: static lambda-to-expression-tree compilation, and runtime compilation of expression trees.
Static Lambda Compilation
When you write code like this:
Expression<Func<Person, bool>> isAdult = p => p.Age >= 18;
The C# compiler doesn't generate IL to execute p.Age >= 18 directly. Instead, it generates code to build an expression tree structure identical to the manual code you shared. Here's what it does under the hood:
- Recognizes the parameter
pand creates aParameterExpressionfor it (typePerson, name "p"). - Resolves
p.Ageby looking up theAgeproperty'sPropertyInfovia reflection, then creates aMemberExpressionrepresenting that property access. - Creates a
ConstantExpressionfor the value18. - Creates a
BinaryExpressionof typeGreaterThanOrEqualto represent the comparison between the property access and the constant. - Wraps all these nodes into an
Expression.Lambda<Func<Person, bool>>instance, which is the final expression tree.
Runtime Compilation of Expression Trees
When you call .Compile() on an expression tree (like isAdult.Compile()), the .NET runtime's expression tree compiler takes that data structure and generates actual IL code for the delegate. This IL is then JIT-compiled into native code when you invoke the delegate. The result is a fully executable Func<Person, bool> that you can call directly.
Your code is essentially replicating what the compiler does automatically for static lambdas. Let's walk through each line to map it to the equivalent static lambda:
Suppose we have a static lambda p => p.[prop] == val (where prop is a property name string and val is the value to compare against). Here's how your code matches that:
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
Creates the parameter node forp—exactly what the compiler does when it parses the lambda's input parameter.PropertyInfo pi = typeof(T).GetProperty(prop);
Uses reflection to get the metadata for the property namedpropon typeT. The compiler does this same lookup at compile time when it seesp.Property.MemberExpression me = Expression.MakeMemberAccess(pe, pi);
Builds the node representingp.[prop]—the property access operation in the lambda.ConstantExpression ce = Expression.Constant(val);
Creates a node for the constant valueval, matching the right-hand side of the==in the lambda.BinaryExpression be = Expression.Equal(me, ce);
Builds the equality comparison nodep.[prop] == val—the core logic of the lambda.return Expression.Lambda<Func<T,bool>>(be, pe);
Wraps the comparison node and parameter into a full expression tree representing the lambda, just like the compiler does for static lambdas.
The big advantage of manual construction is that you can dynamically change parts of the expression (like which property to check, what comparison operator to use, or what value to compare against) at runtime—something you can't do with static lambdas.
内容的提问来源于stack exchange,提问作者user786

