删除表达式树中的强制转换操作

问题描述

我想删除表达式树中的任何强制转换表达式。我们可以假设演员表是多余的。

例如,这两个表达式:

IFoo t => ((t as Foo).Bar as IExtraBar).Baz;
IFoo t => ((IExtraBar)(t as Foo).Bar).Baz;

变成这样:

IFoo t => t.Bar.Baz

你是如何做到这一点的?

示例代码

下面的示例说明了一个非常简单的场景。但是,我的编码失败并出现异常:

未处理的异常:System.ArgumentException:未为类型“ExpressionTest.Program+IFoo”定义属性“IBar Bar”

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(是的,这些是具有不同 MethodInfo 的不同事物)。>

您可以通过覆盖 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 在某些情况下与泛型不能很好地配合),但这是一个起点。