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 |