Ayende @ Rahien

Refunds available at head office

Method Equality

The CLR team deserve a truly great appreciation for making generics works at all. When you get down to it, it is amazingly complex. Most of the Rhino Mocks bugs stems from having to work at that level. Here is one example,  comparing method equality. Let us take this simple example:

[TestFixture]
public class WeirdStuff
{
	public class Test<T>
	{
		public void Compare()
		{
			Assert.AreEqual(GetType().GetMethod("Compare"),
				MethodInfo.GetCurrentMethod()
				);
		}
	}

	[Test]
	public void ThisIsWeird()
	{
		new Test<int>().Compare();
	}
}

This is one of those things that can really bites you. And it fails only if the type is a generic type, even though the comparison is made of the closed generic version of the type. Finding the root cause was fairly hard, and naturally the whole thing is internal, but eventually I managed to come up with a way to compare them safely:

private static bool AreMethodEquals(MethodInfo left, MethodInfo right)
{
	if (left.Equals(right))
		return true;
	// GetHashCode calls to RuntimeMethodHandle.StripMethodInstantiation()
	// which is needed to fix issues with method equality from generic types.
	if (left.GetHashCode() != right.GetHashCode())
		return false;
	if (left.DeclaringType != right.DeclaringType)
		return false;
	ParameterInfo[] leftParams = left.GetParameters();
	ParameterInfo[] rightParams = right.GetParameters();
	if (leftParams.Length != rightParams.Length)
		return false;
	for (int i = 0; i < leftParams.Length; i++)
	{
		if (leftParams[i].ParameterType != rightParams[i].ParameterType)
			return false;
	}
	if (left.ReturnType != right.ReturnType)
		return false;
	return true;
}

The secret here is with the call to GetHashCode, which remove the method instantiation code, which is fairly strange concept, because I wasn't aware that you can instantiate methods :-)

Comments

Mark Monster
07/27/2007 11:04 AM by
Mark Monster

What do you mean by method instantiation? Instantiation using delegates for named and anonymous method?

Ayende Rahien
07/27/2007 11:11 AM by
Ayende Rahien

I mean by the method object itself, no delegates involved.

This is probably mentioned in the docs, but I have no idea where

josh
07/27/2007 03:34 PM by
josh

Nice solution. What about method access privileges on each? What if one method is public and the other is internal or protected? Should they still be considered equal to each other?

Thomas Krause
07/27/2007 04:34 PM by
Thomas Krause

Shouldn't you also check the method name for equality?

Two methods could be declared on the same class with the same signature (parameters and return type) but different names. The Hash code will of course be different in most of the cases, but this isn't guaranteed... Am I missing something here?

Ayende Rahien
07/27/2007 07:36 PM by
Ayende Rahien

Yes, because if it has the same declaring type, and the same name & parameters, you can define another method with a different name

Ayende Rahien
07/27/2007 07:37 PM by
Ayende Rahien

Thomas ,

Great catch, I'll add that

Oran
07/28/2007 04:11 AM by
Oran

"the comparison is made of the closed generic version of the type" - actually, it's not. GetType().GetMethod("Compare").DeclaringType is closed. MethodInfo.GetCurrentMethod().DeclaringType is open. See the Community Content section for MethodBase.GetCurrentMethod for details:

http://msdn2.microsoft.com/en-us/library/system.reflection.methodbase.getcurrentmethod.aspx

Basically, MethodBase.GetCurrentMethod doesn't behave as one would expect.

Also notice that ContainsGenericParameters is false in the first MethodInfo while it is true in the second. According to the following link, this means the second one is an open constructed generic method:

http://blogs.msdn.com/parthopdas/archive/2005/10/21/483463.aspx

Comments have been closed on this topic.