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

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 在某些情况下不能很好地使用泛型),但这是一个起点。