Search code examples
c#reflectionexpression-trees

ExpressionVisitor not visiting overridden property of a derived class


I'm trying to use the ExpressionVisitor to get an overridden member of an expression but it's giving me the base one. What am I missing here?

The following example reproduces this behaviour:

Simple base and derived types:

class Base
{
    public virtual string Property { get; set; }
}

class Derived : Base
{
    public override string Property { get; set; }
}

I use this expression visitor:

internal class DemoVisitor : ExpressionVisitor
{
    private MemberInfo _member;

    public static MemberInfo GetMemberInfo(LambdaExpression expression)
    {
        var visitor = new DemoVisitor();
        visitor.Visit(expression);

        return visitor._member;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        // invalid member here
        //node.Member.DeclaringType.Name.Dump();
        _member = _member ?? node.Member;

        return base.VisitMember(node);
    }
}

Calling it like that

void Main()
{
    var derived = new Derived();
    var expression = (Expression<Func<string>>)(() => derived.Property);
    DemoVisitor.GetMemberInfo(expression).DeclaringType.Name.Dump();
}

This returns Base instead of Derived. What do I have to do to get to the overridden member?

I need it because I'm reading its attributes later and it's currently giving me the attributes of the property on the base class instead of the derived one.


Solution

  • If you want to know the type of the class/struct of your member you need to look at the MemberExpression's Expression property which is the expression for the object containing the member - in your case the derived variable - and get its type.

    Therefore your visitor needs to return both (here I used a ValueTuple):

    internal class DemoVisitor : ExpressionVisitor
    {
        private Type type;
        private MemberInfo _member;
    
        public static (Type, MemberInfo) GetMemberInfo(LambdaExpression expression)
        {
            var visitor = new DemoVisitor();
            visitor.Visit(expression);
    
            return (visitor.type, visitor._member);
        }
    
        protected override Expression VisitMember(MemberExpression node)
        {
            if (_member == null)
            {
              _member = node.Member;
              type = node.Expression.Type;
            }
    
            return base.VisitMember(node);
        }
    }
    

    Regarding your other question about which member gets visited: all used members in your expression get visited, i.e. all methods, properties and fields, whereas node.Member returns either a MethodInfo, PropertyInfo or FieldInfo object, which all derive from MemberInfo.