如何将Expression<Func<T1,bool>>转换为Expression<Func<T2,bool>>?T1与T2同接口
Expression<Func<T1, bool>> to Expression<Func<T2, bool>> (When T1 and T2 Implement the Same Interface) Great question—this is such a common scenario when working with generic repositories or shared query logic, especially since T1 and T2 share a common interface. The key here is to rewrite the expression tree to swap out references to T1 with T2, while ensuring all member accesses map correctly to the shared interface or T2's members. Here's a practical, step-by-step solution:
Step 1: Set Up Your Interface and Types
First, let's define a sample shared interface and concrete types to work with (adjust these to match your actual code):
public interface ISharedEntity { int Id { get; } string DisplayName { get; } } public class Customer : ISharedEntity { public int Id { get; set; } public string DisplayName { get; set; } public string CustomerOnlyField { get; set; } } public class Order : ISharedEntity { public int Id { get; set; } public string DisplayName { get; set; } public DateTime OrderDate { get; set; } }
Step 2: Build a Custom Expression Visitor
Expression trees are immutable, so we need a custom ExpressionVisitor to rewrite the tree—replacing the T1 parameter with T2 and fixing member accesses:
public class ExpressionTypeConverter<TFrom, TTo> : ExpressionVisitor { private readonly ParameterExpression _targetParam; public ExpressionTypeConverter(ParameterExpression targetParam) { _targetParam = targetParam; } // Replace the original TFrom parameter with the new TTo parameter protected override Expression VisitParameter(ParameterExpression node) { return node.Type == typeof(TFrom) ? _targetParam : base.VisitParameter(node); } // Ensure member accesses map to TTo or the shared interface protected override Expression VisitMember(MemberExpression node) { if (node.Member.DeclaringType == typeof(TFrom)) { // First check TTo for the member; fall back to the shared interface if needed var targetMember = typeof(TTo).GetMember(node.Member.Name).FirstOrDefault() ?? typeof(ISharedEntity).GetMember(node.Member.Name).FirstOrDefault(); if (targetMember == null) throw new InvalidOperationException( $"Member '{node.Member.Name}' doesn't exist on {typeof(TTo)} or {typeof(ISharedEntity)}"); // Rebuild the member access with the rewritten expression return Expression.MakeMemberAccess(Visit(node.Expression), targetMember); } return base.VisitMember(node); } }
Step 3: Create a Reusable Conversion Method
Wrap the visitor in a clean, generic method to handle the full conversion:
public static Expression<Func<TTo, bool>> ConvertFilter<TFrom, TTo>( Expression<Func<TFrom, bool>> originalExpression) where TFrom : ISharedEntity where TTo : ISharedEntity { // Create a new TTo parameter (keep the original name for clarity) var targetParam = Expression.Parameter(typeof(TTo), originalExpression.Parameters[0].Name); // Rewrite the original expression's body var converter = new ExpressionTypeConverter<TFrom, TTo>(targetParam); var rewrittenBody = converter.Visit(originalExpression.Body); // Build and return the new lambda expression return Expression.Lambda<Func<TTo, bool>>(rewrittenBody, targetParam); }
Step 4: Test the Conversion
Let's put this to work with a real example:
// Original filter targeting Customer Expression<Func<Customer, bool>> customerFilter = c => c.Id > 100 && c.DisplayName.Contains("VIP"); // Convert to an Order filter Expression<Func<Order, bool>> orderFilter = ConvertFilter<Customer, Order>(customerFilter); // Verify the result (outputs: "o => (o.Id > 100) && o.DisplayName.Contains("VIP")") Console.WriteLine(orderFilter.ToString());
Key Notes
- Avoid Type-Specific Members: If your original expression uses fields/methods unique to
T1(likeCustomerOnlyFieldhere), the conversion will fail—sinceT2doesn't have that member. Stick to members from the shared interface for compatibility. - Handles Complex Expressions: This visitor works with more than just simple member accesses—it supports method calls, conditional logic, and nested expressions, as long as all dependencies map to
T2or the shared interface. - Generic Interfaces: If your shared interface is generic (e.g.,
ISharedEntity<T>), just update the type constraints in the conversion method to match.
内容的提问来源于stack exchange,提问作者Ruhollah Delpak

