如何将Expression<Func<T1,U>>转为Expression<Func<T2,U>>?泛型表达式转换异常解决
Let's break down your problem and fix it step by step:
The Root Cause of the Exception
Your original code uses Expression.Invoke to call the input expression, which creates a nested scope where the original parameter (like t in t => t.Foo) is still referenced but not defined in the new lambda's scope. That's exactly why you're seeing the InvalidOperationException about the variable not being defined.
The Solution: Replace Parameters Instead of Invoking
Instead of invoking the input expression, we'll use an ExpressionVisitor to replace its original parameter with our converted IBase parameter. We'll also add type checking logic to handle cases where the input IBase instance doesn't implement the target interface (returning a default value in those cases).
Updated Code Implementation
Here's the revised version of your FooImpl class with fixed Test and Test2 methods, plus the required type checking:
namespace ExpressionTest { interface IBase { string Name { get; set; } } interface IFoo : IBase { string Foo { get; set; } } interface IBar : IBase { string Bar { get; set; } } class FooImpl : IFoo { public FooImpl() { Name = "Name"; Foo = "Foo"; var e1 = Test<IBase, string>(t => t.Name); var e2 = Test<IFoo, string>(t => t.Foo); var e3 = Test<IBar, string>(t => t.Bar); var e4 = Test2<IBase, string>((b, v) => Stuff(b, v)); e4.Compile().Invoke(this, "new"); var name = e1.Compile().Invoke(this); // Returns "new" var foo = e2.Compile().Invoke(this); // Returns "Foo" var bar = e3.Compile().Invoke(this); // Returns null (since FooImpl doesn't implement IBar) } public void Stuff(IBase b, string v) { b.Name = v; } public string Name { get; set; } public string Foo { get; set; } private Expression<Func<IBase, TProperty>> Test<T, TProperty>(Expression<Func<T, TProperty>> expr) where T : IBase { // Create the new parameter of type IBase var baseParam = Expression.Parameter(typeof(IBase), "b"); // Check if the IBase instance is of type T var isTypeCheck = Expression.TypeIs(baseParam, typeof(T)); // Convert the IBase parameter to T var convertedParam = Expression.Convert(baseParam, typeof(T)); // Replace the original parameter in the input expression with our converted parameter var parameterReplacer = new ParameterReplacer(expr.Parameters[0], convertedParam); var body = parameterReplacer.Visit(expr.Body); // Create a conditional: if it's T, execute the body; else return default(TProperty) var conditional = Expression.Condition( isTypeCheck, body, Expression.Default(typeof(TProperty)) ); // Build the final lambda return Expression.Lambda<Func<IBase, TProperty>>(conditional, baseParam); } private Expression<Action<IBase, TProperty>> Test2<T, TProperty>(Expression<Action<T, TProperty>> expr) where T : IBase { // Get the original parameters (T instance and TProperty value) var originalInstanceParam = expr.Parameters[0]; var originalValueParam = expr.Parameters[1]; // Create the new IBase parameter var baseParam = Expression.Parameter(typeof(IBase), "b"); // Check if the IBase instance is of type T var isTypeCheck = Expression.TypeIs(baseParam, typeof(T)); // Convert the IBase parameter to T var convertedParam = Expression.Convert(baseParam, typeof(T)); // Replace the original instance parameter with our converted parameter var parameterReplacer = new ParameterReplacer(originalInstanceParam, convertedParam); var body = parameterReplacer.Visit(expr.Body); // Create a conditional: only execute the action if the instance is of type T var conditional = Expression.Condition( isTypeCheck, body, Expression.Empty() // Do nothing if type doesn't match ); // Build the final lambda (use the new IBase param and original value param) return Expression.Lambda<Action<IBase, TProperty>>(conditional, baseParam, originalValueParam); } // Helper class to replace parameters in an expression private class ParameterReplacer : ExpressionVisitor { private readonly ParameterExpression _oldParam; private readonly Expression _newParam; public ParameterReplacer(ParameterExpression oldParam, Expression newParam) { _oldParam = oldParam; _newParam = newParam; } protected override Expression VisitParameter(ParameterExpression node) { return node == _oldParam ? _newParam : base.VisitParameter(node); } } } class Program { static void Main(string[] args) { var foo = new FooImpl(); } } }
Key Improvements Explained
- Parameter Replacement: The
ParameterReplacervisitor swaps out the original expression's parameter (e.g.,tint => t.Foo) with our convertedIBaseparameter. This eliminates the nested scope issue that caused the original exception. - Type Checking: We use
Expression.TypeIsto mimic theisoperator, andExpression.Conditionto build a branch that returns the default value if the instance doesn't implement the target interface. - Action Handling: For the
Test2method, we add a conditional that only executes the action if the instance matches the target type—otherwise, it does nothing (you can adjust this to throw an error or handle it differently if needed).
Testing the Fix
When you run the updated code:
e1.Compile().Invoke(this)will return the updated Name value ("new")e2.Compile().Invoke(this)will return "Foo"e3.Compile().Invoke(this)will returnnull(sinceFooImpldoesn't implementIBar)e4.Compile().Invoke(this, "new")will successfully update the Name property
内容的提问来源于stack exchange,提问作者l33t

