How do I determine if a property was overridden?
Asked Answered
S

3

16

I am doing a project that where I need to register all the properties, because of the system being so huge it would require a lot of work to register all the properties that i want to be dependent for the purpose of Xaml.

The goal is to find all properties that are on the top of the tree.

so basically

public class A{
    public int Property1 { get; set; }
}

public class B : A{
    public int Property2 { get; set; }
    public virtual int Property3 { get; set; }
}

public class C : B{
    public override int Property3 { get; set; }
    public int Property4 { get; set; }
    public int Property5 { get; set; }
}

The end result would be something like this

A.Property1  
B.Property2  
B.Property3  
C.Property4  
C.Property5  

If you notice I don't want to accept overridden properties because of the way I search for the properties if I do something like this

C.Property3 for example and it cannot find it it will check C's basetype and there it will find it.

This is what I have so far.

public static void RegisterType( Type type )
{
    PropertyInfo[] properties = type.GetProperties( BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.GetProperty | BindingFlags.SetProperty );

    if ( properties != null && properties.Length > 0 )
    {
        foreach ( PropertyInfo property in properties )
        {
            // if the property is an indexers then we ignore them
            if ( property.Name == "Item" && property.GetIndexParameters().Length > 0 )
                continue;

            // We don't want Arrays or Generic Property Types
            if ( (property.PropertyType.IsArray || property.PropertyType.IsGenericType) )
                continue;

            // Register Property
        }
    }
}

What I want are the following:

  • Public properties, that are not overridden, not static, not private
  • Either get and set properties are allowed
  • They are not an array or a generic type
  • They are the top of the tree ie C class in the example is the highest (The property list example is exactly what I am looking for)
  • They are not an indexer property ( this[index] )
Stpeter answered 22/12, 2010 at 0:33 Comment(0)
W
29

In order to ignore inherited members, you can use the BindingFlags.DeclaredOnly flag, which you're already doing.

But when properties are overridden, they are re-declared by the derived class. The trick is to then look at their accessor methods to determine if they are in fact overridden.

Type type = typeof(Foo);

foreach ( var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) {
    var getMethod = property.GetGetMethod(false);
    if (getMethod.GetBaseDefinition() == getMethod) {
        Console.WriteLine(getMethod);
    }
}

If the property is overridden, its 'getter' MethodInfo will return a different MethodInfo from GetBaseDefinition.

Widgeon answered 22/12, 2010 at 0:49 Comment(3)
so the getMethod.Dump(); if they match then register? if they don't then ignore?Stpeter
Sorry that was an artifact left over from me testing that in LinqPad. I changed it to Console.WriteLine.Widgeon
if getMethod.GetBaseDefinition() == getMethod then you know it's the original property, not an overridden property.Widgeon
K
2

None of these solutions worked well in my case. I ended up using DeclaringType to determine difference in definitions (I've provided the full function to give some context):

static public String GetExpandedInfo(Exception e)
{
    StringBuilder info = new StringBuilder();
    Type exceptionType = e.GetType();

    // only get properties declared in this type (i.e. not inherited from System.Exception)
    PropertyInfo[] propertyInfo = exceptionType.GetProperties(System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
    if (propertyInfo.Length > 0)
    {
        // add the exception class name at the top
        info.AppendFormat("[{0}]\n", exceptionType.Name);

        foreach (PropertyInfo prop in propertyInfo)
        {
            // check the property isn't overriding a System.Exception property (i.e. Message)
            // as that is a default property accessible via the generic Exception class handlers
            var getMethod = prop.GetGetMethod(false);
            if (getMethod.GetBaseDefinition().DeclaringType == getMethod.DeclaringType)
            {
                // add the property name and it's value
                info.AppendFormat("{0}: {1}\n", prop.Name, prop.GetValue(e, null));
            }
        }
    }
Karyotype answered 2/7, 2014 at 15:42 Comment(0)
V
0

Based on poking around the property info field, it was pretty clear to me that the virtual and override keywords (as well as the new keyword) have more to do with how the code is compiled than how the compiled code is run. There is no flag on property info for virtual properties, nor a binding flag for such properties you can call with methods like Type.GetProperties(BindingFlags).

You can try to back into it with by getting properties with BindingFlags.DeclaredOnly on the derived and base types and comparing the sets, but this is both clunky and the difference between the new keyword for hiding a property vs the override keyword for virtual properties makes this approach less reliable.

I started with @Josh's answer, which tests if the getter is different from it's base definition, but found this method would show a base class property marked as virtual but not overridden as overridden. It still covered other cases, so after some poking around, I found I could compare the declaring type of the getters, which is more semantic anyway.

That led to the following method

IsPropertyOverriden Method

/// <summary>
/// Tests if the property overrides a base class property with the override keyword
/// </summary>
public static bool IsPropertyOverriden(this PropertyInfo prop)
{
  var getMethodBaseDeclaringType = prop
    .GetGetMethod(false)
    .GetBaseDefinition()
    .DeclaringType;
  return prop.DeclaringType != getMethodBaseDeclaringType;
}

This assumes the property has a getter implemented and also works with overridden getters. This will return false for virtual properties that are not overridden, and false for virtual and non-virtual properties that are hidden with the new keyword.

The method does not work on methods or fields since it's based on the properties getter.

This method tests from the prop.DeclaringType down the inheritance hierarchy from that class towards its base classes. This does not tell you if a class that inherits from DeclaringType later overrides the property, only if the property overrides something from a base class in the current context

Testing Methodology

I'm including my tests since the reflection APIs in this area are so opaque.

First, I started with the following class structure:

public class Base
{
    public string PropBaseNotOverriden { get; set; }

    public virtual string PropVirtualOverridenChildmost { get; set; }

    public virtual string PropVirtualOverrideIntermediate { get; set; }

    public virtual string PropVirtualOverrideBoth { get; set; }

    public virtual string PropVirtualHiddenDerived { get; set; }

    public virtual string PropVirtualHiddenIntermediate { get; set; }
    
    public virtual string PropVirtualHiddenBoth { get; set; }

    public virtual string PropVirtualNotOverriden { get; set; }

    public virtual string PropVirtualIntermediateManualGetterSetter { get; set; }

    public virtual string PropVirtualChildmostManualGetterSetter { get; set; }

    public string PropHidden { get; set; }
}

public class Intermediate : Base
{
    public override string PropVirtualOverrideIntermediate { get; set; }

    public override string PropVirtualOverrideBoth { get; set; }

    public new string PropVirtualHiddenBoth { get; set; }

    public new string PropVirtualHiddenIntermediate { get; set; }

    public override string PropVirtualIntermediateManualGetterSetter
    {
        get => base.PropVirtualIntermediateManualGetterSetter + " ";
        set => base.PropVirtualIntermediateManualGetterSetter = value + " ";
    }
}

public class Derived : Intermediate
{
    public override string PropVirtualOverridenChildmost { get; set; }

    public override string PropVirtualOverrideBoth { get; set; }

    public new string PropVirtualHiddenBoth { get; set; }

    public new string PropVirtualHiddenDerived { get; set; }

    public override string PropVirtualChildmostManualGetterSetter
    {
        get => base.PropVirtualChildmostManualGetterSetter + " ";
        set => base.PropVirtualChildmostManualGetterSetter = value + " ";
    }

    public string PropDerivedNotOverriden { get; set; }
    
    public string PropDerivedVirtualNotOverriden { get; set; }

    public new string PropHidden { get; set; }
}

I used the following test method

typeof(Derived)
    .GetProperties(BindingFlags.Public | BindingFlags.Instance)
    .Select(x => new
    {
        x.Name,
        IsOverriden = x.IsPropertyOverriden()
    })
    .Dump(); // LinqPad method to write output

which yielded the following (tested over all levels of the above hierarchy)

Name OverridenInDerived OverridenInIntermediate OverridenInBase
PropBaseNotOverriden False False False
PropDerivedNotOverriden False
PropDerivedVirtualNotOverriden False
PropHidden False False False
PropVirtualChildmostManualGetterSetter True False False
PropVirtualHiddenBoth False False False
PropVirtualHiddenDerived False False False
PropVirtualHiddenIntermediate False False False
PropVirtualIntermediateManualGetterSetter True True False
PropVirtualNotOverriden False False False
PropVirtualOverrideBoth True True False
PropVirtualOverrideIntermediate True True False
PropVirtualOverridenChildmost True False False
Viipuri answered 10/6 at 16:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.