删除表达式树中的转换操作
Remove cast operations in expression tree
我想删除表达式树中的任何转换表达式。我们可以假设转换是多余的。
例如,这两个表达式:
IFoo t => ((t as Foo).Bar as IExtraBar).Baz;
IFoo t => ((IExtraBar)(t as Foo).Bar).Baz;
变成这样:
IFoo t => t.Bar.Baz
你怎么做到的?
示例代码
下面的示例说明了一个非常简单的场景。但是,我的编码失败并出现异常:
Unhandled Exception: System.ArgumentException: Property 'IBar Bar' is not defined for type 'ExpressionTest.Program+IFoo'
using System;
using System.Linq.Expressions;
namespace ExpressionTest
{
class Program
{
public interface IBar
{
string Baz { get; }
}
public interface IExtraBar : IBar
{
}
public interface IFoo
{
IBar Bar { get; }
}
public class Foo : IFoo
{
public IBar Bar { get; }
}
static void Main(string[] args)
{
Expression<Func<IFoo, string>> expr = t => ((t as Foo).Bar as IExtraBar).Baz;
Expression<Func<IFoo, string>> expr2 = t => ((IExtraBar)(t as Foo).Bar).Baz;
// Wanted: IFoo t => t.Bar.Baz
var visitor = new CastRemoverVisitor();
visitor.Visit(expr);
Console.WriteLine(visitor.Expression.ToString());
}
public class CastRemoverVisitor : ExpressionVisitor
{
public Expression Expression { get; private set; }
public override Expression Visit(Expression node)
{
Expression ??= node;
return base.Visit(node);
}
protected override Expression VisitUnary(UnaryExpression node)
{
Expression = node.Operand;
return Visit(node.Operand);
}
}
}
}
最终解决方案
接受的答案指出了 Expression.MakeMemberAccess 的使用和一些界面技巧。我们可以稍微“改进”代码,使用接口 PropertyInfo
而不是通过接口 getter。我最终得到以下结果:
public class CastRemoverVisitor : ExpressionVisitor
{
protected override Expression VisitUnary(UnaryExpression node)
{
return node.IsCastExpression() ? Visit(node.Operand) : base.VisitUnary(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression is UnaryExpression unaryExpression &&
unaryExpression.IsCastExpression())
{
var propertyInfo = node.Member.ToInterfacedProperty();
if (propertyInfo != null)
{
return base.Visit(
Expression.MakeMemberAccess(
unaryExpression.Operand,
propertyInfo
));
}
}
return base.VisitMember(node);
}
}
// And some useful extension methods...
public static class MemberInfoExtensions
{
public static MemberInfo ToInterfacedProperty(this MemberInfo member)
{
var interfaces = member.DeclaringType!.GetInterfaces();
var mi = interfaces.Select(i => i.GetProperty(member.Name))
.FirstOrDefault(p => p != null);
return mi;
}
}
public static class ExpressionExtensions
{
public static bool IsCastExpression(this Expression expression) =>
expression.NodeType == ExpressionType.TypeAs ||
expression.NodeType == ExpressionType.Convert;
}
然后我们这样使用它:
var visitor = new CastRemoverVisitor();
var cleanExpr = visitor.Visit(expr);
Console.WriteLine(cleanExpr.ToString());
首先,复制得很好。谢谢。
问题是 属性 访问 (t as Foo).Bar
正在调用 Foo.Bar
的 getter,而不是 IFoo.Bar
的 getter (是的,不同的 MethodInfos 是不同的东西)。
您可以通过覆盖 VisitMember
看到这一点,并看到 MethodInfo
被传递。
不过,这样的方法似乎可行。我们必须在成员访问点解包,因为我们只有在找到一个等效成员来访问未转换类型时才能继续:
public class CastRemoverVisitor : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression is UnaryExpression { NodeType: ExpressionType.TypeAs or ExpressionType.Convert, Operand: var operand } &&
node.Member is PropertyInfo propertyInfo &&
operand.Type.IsInterface)
{
// Is this just inheriting a type from a base interface?
// Get rid of the cast, and just call the property on the uncasted member
if (propertyInfo.DeclaringType == operand.Type)
{
return base.Visit(Expression.MakeMemberAccess(operand, propertyInfo));
}
// Is node.Expression a concrete type, which implements this interface method?
var methodInfo = GetInterfaceMethodInfo(operand.Type, node.Expression.Type, propertyInfo.GetMethod);
if (methodInfo != null)
{
return base.Visit(Expression.Call(operand, methodInfo));
}
}
return base.VisitMember(node);
}
private static MethodInfo GetInterfaceMethodInfo(Type interfaceType, Type implementationType, MethodInfo implementationMethodInfo)
{
if (!implementationType.IsClass)
return null;
var map = implementationType.GetInterfaceMap(interfaceType);
for (int i = 0; i < map.InterfaceMethods.Length; i++)
{
if (map.TargetMethods[i] == implementationMethodInfo)
{
return map.InterfaceMethods[i];
}
}
return null;
}
}
我敢肯定有些情况会打破这一点(想到字段,我知道 GetInterfaceMap
在某些情况下不能很好地使用泛型),但这是一个起点。
我想删除表达式树中的任何转换表达式。我们可以假设转换是多余的。
例如,这两个表达式:
IFoo t => ((t as Foo).Bar as IExtraBar).Baz;
IFoo t => ((IExtraBar)(t as Foo).Bar).Baz;
变成这样:
IFoo t => t.Bar.Baz
你怎么做到的?
示例代码
下面的示例说明了一个非常简单的场景。但是,我的编码失败并出现异常:
Unhandled Exception: System.ArgumentException: Property 'IBar Bar' is not defined for type 'ExpressionTest.Program+IFoo'
using System;
using System.Linq.Expressions;
namespace ExpressionTest
{
class Program
{
public interface IBar
{
string Baz { get; }
}
public interface IExtraBar : IBar
{
}
public interface IFoo
{
IBar Bar { get; }
}
public class Foo : IFoo
{
public IBar Bar { get; }
}
static void Main(string[] args)
{
Expression<Func<IFoo, string>> expr = t => ((t as Foo).Bar as IExtraBar).Baz;
Expression<Func<IFoo, string>> expr2 = t => ((IExtraBar)(t as Foo).Bar).Baz;
// Wanted: IFoo t => t.Bar.Baz
var visitor = new CastRemoverVisitor();
visitor.Visit(expr);
Console.WriteLine(visitor.Expression.ToString());
}
public class CastRemoverVisitor : ExpressionVisitor
{
public Expression Expression { get; private set; }
public override Expression Visit(Expression node)
{
Expression ??= node;
return base.Visit(node);
}
protected override Expression VisitUnary(UnaryExpression node)
{
Expression = node.Operand;
return Visit(node.Operand);
}
}
}
}
最终解决方案
接受的答案指出了 Expression.MakeMemberAccess 的使用和一些界面技巧。我们可以稍微“改进”代码,使用接口 PropertyInfo
而不是通过接口 getter。我最终得到以下结果:
public class CastRemoverVisitor : ExpressionVisitor
{
protected override Expression VisitUnary(UnaryExpression node)
{
return node.IsCastExpression() ? Visit(node.Operand) : base.VisitUnary(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression is UnaryExpression unaryExpression &&
unaryExpression.IsCastExpression())
{
var propertyInfo = node.Member.ToInterfacedProperty();
if (propertyInfo != null)
{
return base.Visit(
Expression.MakeMemberAccess(
unaryExpression.Operand,
propertyInfo
));
}
}
return base.VisitMember(node);
}
}
// And some useful extension methods...
public static class MemberInfoExtensions
{
public static MemberInfo ToInterfacedProperty(this MemberInfo member)
{
var interfaces = member.DeclaringType!.GetInterfaces();
var mi = interfaces.Select(i => i.GetProperty(member.Name))
.FirstOrDefault(p => p != null);
return mi;
}
}
public static class ExpressionExtensions
{
public static bool IsCastExpression(this Expression expression) =>
expression.NodeType == ExpressionType.TypeAs ||
expression.NodeType == ExpressionType.Convert;
}
然后我们这样使用它:
var visitor = new CastRemoverVisitor();
var cleanExpr = visitor.Visit(expr);
Console.WriteLine(cleanExpr.ToString());
首先,复制得很好。谢谢。
问题是 属性 访问 (t as Foo).Bar
正在调用 Foo.Bar
的 getter,而不是 IFoo.Bar
的 getter (是的,不同的 MethodInfos 是不同的东西)。
您可以通过覆盖 VisitMember
看到这一点,并看到 MethodInfo
被传递。
不过,这样的方法似乎可行。我们必须在成员访问点解包,因为我们只有在找到一个等效成员来访问未转换类型时才能继续:
public class CastRemoverVisitor : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression is UnaryExpression { NodeType: ExpressionType.TypeAs or ExpressionType.Convert, Operand: var operand } &&
node.Member is PropertyInfo propertyInfo &&
operand.Type.IsInterface)
{
// Is this just inheriting a type from a base interface?
// Get rid of the cast, and just call the property on the uncasted member
if (propertyInfo.DeclaringType == operand.Type)
{
return base.Visit(Expression.MakeMemberAccess(operand, propertyInfo));
}
// Is node.Expression a concrete type, which implements this interface method?
var methodInfo = GetInterfaceMethodInfo(operand.Type, node.Expression.Type, propertyInfo.GetMethod);
if (methodInfo != null)
{
return base.Visit(Expression.Call(operand, methodInfo));
}
}
return base.VisitMember(node);
}
private static MethodInfo GetInterfaceMethodInfo(Type interfaceType, Type implementationType, MethodInfo implementationMethodInfo)
{
if (!implementationType.IsClass)
return null;
var map = implementationType.GetInterfaceMap(interfaceType);
for (int i = 0; i < map.InterfaceMethods.Length; i++)
{
if (map.TargetMethods[i] == implementationMethodInfo)
{
return map.InterfaceMethods[i];
}
}
return null;
}
}
我敢肯定有些情况会打破这一点(想到字段,我知道 GetInterfaceMap
在某些情况下不能很好地使用泛型),但这是一个起点。