Thursday, October 06, 2005

Logic for determining method inheritance type

Topics:
.NET Framework 1.x, 1.1
Reflection
MethodInfo
IsHideBySig
GetBaseDefinition()
Type.GetMethod Method (String, Type[])
overrides keyword
new keyword

*****

Using reflection I can easily determine all kinds of member information, such as whether a method is abstract, static, virtual, final. (MethodInfo.IsVirtual and so on)

However, how to easily determine a method's inheritance type? (corollary: and the inheritance type of a property's accessor methods?)

In other words, how does one determine where a method has originated:

in the current type (i.e. MethodInfo.ReflectedType),
inherited from a base class,
as overridden in the current type (from a virtual method in a base class)
as 'new' keyword in the current type (to hide this method in the current type from a method in the base class of the same name--whether or not the base class method has been declared as virtual.)

To do this, I created a method to parse these four possibilities. To store these values, I created an enumeration, InheritanceTypeEnum:


public enum InheritanceTypeEnum
{
DeclaredLocally,
Inherited,
OverridesKeyword,
NewKeyword
}


I then created two helper methods to parse each inheritnace type.


Note: I encountered two unexpected behaviours with Reflection API that required workarounds:

1. MethodInfo.IsHideBySig returned true in many scenarios other than a method with a 'new' keyword.
Workaround: I created my own method, IsMethodOfSameSignatureFoundInBaseClass(), to determine this.)

2. Type.GetMethod Method (String, Type[]) returned non-null in scenarios with the parameters in the method matched with false positives (when a parameter type was compared against a parameter of type System.Object).
Workaround: I wrote verification code on the returned method.

Here's the code:

/// 2005Oct06 David Gadd
/// new logic to determine the member's inheritance type
/// (local, inherited, overridden or new keyword)
private InheritanceTypeEnum GetInheritanceType(MethodInfo currentMethod)
{
// 1. If the method's current type != method's declaring type then the method is inherited.
// (It does not exist locally at all, it is entirely owned by the base/declaring class.
if(currentMethod.ReflectedType.FullName != currentMethod.DeclaringType.FullName)
{
return InheritanceTypeEnum.Inherited;
}
else
{
// 2. If the class that owns the base(first) definition of this method != the class
// that owns the current definition of this method, then the method is overridden
if(currentMethod.GetBaseDefinition().ReflectedType.FullName != currentMethod.ReflectedType.FullName)
{
return InheritanceTypeEnum.OverridesKeyword;
}
// else the method is defined locally within the current type
else
{
// 3. If the local method has a method of the same name in the base class
// of the current type, then this method must be qualified with the "new" keyword.
// (Originally I tried to do this with MethodInfo.IsHideBySig, but I found that this
// property returned true in many cases other than the 'new' keyword...)
bool hasSameNamedBaseMethod = IsMethodOfSameSignatureFoundInBaseClass(currentMethod);

if(hasSameNamedBaseMethod)
{
return InheritanceTypeEnum.NewKeyword;
}
else
{
// 4. If the local method's signature does not use the 'new' keyword,
// then the method is completely local in origin.
return InheritanceTypeEnum.DeclaredLocally;
}
}
}
}

/// This is meant to be a more reliable alternative to the MethodInfo.IsHideBySig property
/// which seems to render true for many situations other than the application of the new keyword
/// to a method
private bool IsMethodOfSameSignatureFoundInBaseClass(MethodInfo currentMethod)
{
bool matchFound = false;

try
{
MethodInfo baseMethod;

ParameterInfo[] currentMethodParameters = currentMethod.GetParameters();
Type[] currentMethodParameterTypes = new Type[currentMethodParameters.Length];
for(int i = 0; i < currentMethodParameters.Length; i++)
{
currentMethodParameterTypes[i] = currentMethodParameters[i].ParameterType;
}

baseMethod = currentMethod.ReflectedType.BaseType.GetMethod(currentMethod.Name, currentMethodParameterTypes);

if(baseMethod != null)
{
// At this point, if the baseMethod is not null, a succesful match SHOULD have been found.
// However, it appears that Type.GetMethod(name, parameterTypes) may have a bug:
// in testing I have found that in a 1-parameter method where the base class has a
// type of System.Object, it will be perceived as a match to an overload method in the derived
// class of 1-parameter whose type is NOT System.Object. Therefore, the following workaround:
// retrieve the parameter from baseMethod and do an explicit Type comparison.
ParameterInfo[] baseMethodParameters = baseMethod.GetParameters();

for(int i = 0; i < baseMethodParameters.Length; i++)
{
if(currentMethodParameterTypes[i] == baseMethodParameters[i].ParameterType)
{
matchFound = true;
}
else
{
matchFound = false;
// as soon as any false matches are found, exit the loop.
break;
}
}

}
}
catch(AmbiguousMatchException aex)
{
// the code above thoroughly handles method signatures
// so an ambiguous match should never be found.
throw new AmbiguousMatchException(aex.Message);
}
catch(ArgumentNullException)
{
matchFound = false;
}
catch(Exception)
{
matchFound = false;
}

return matchFound;
}

No comments: