I am designing a struct to compare method signatures from two different sources (currently taking them directly from assemblies using System.Reflection
). Since I only care about uniqueness, I chose HashSet< MethodSignature>
to store my structs and compare them using the subset method.
public struct MethodSignature : IEquatable<MethodSignature>
{
#region Immutable fields
public readonly string AssemblyName;
public readonly string ClassName;
public readonly string MethodName;
public readonly System.Type ReturnType;
public readonly Dictionary<string, System.Type> Parameters;
#endregion
#region Constructors
public MethodSignature(string assemblyName, string className, string methodName, Type returnType, Dictionary<string, System.Type> parameters)
{
AssemblyName = assemblyName;
ClassName = className;
MethodName = methodName;
ReturnType = returnType;
Parameters = parameters;
}
#endregion
#region public Methods
public override string ToString()
{
string paramts = GetParametersAsString();
return string.Format("{0} {1}::{2}.{3}({4})", ReturnType.ToString(), AssemblyName, ClassName, MethodName, paramts);
}
public static bool operator ==(MethodSignature signature1, MethodSignature signature2)
{
// No nasty null checking thanks to value types :D :D :D
return signature1.Equals(signature2);
}
public static bool operator !=(MethodSignature signature1, MethodSignature signature2)
{
// No nasty null checking thanks to value types :D :D :D
return !signature1.Equals(signature2);
}
public bool Equals(MethodSignature signature)
{
return AreMethodSignatureEquals(signature);
}
public override bool Equals(object obj)
{
if (obj is MethodSignature)
return Equals((MethodSignature)obj);
else
return false;
}
#endregion
#region private Members
private string GetParametersAsString()
{
StringBuilder sb = new StringBuilder();
foreach (KeyValuePair<string, System.Type> param in Parameters)
{
sb.Append(string.Format("{0} {1},", param.Value.ToString(), param.Key.ToString()));
}
//Remove trailing comma
sb.Length--;
return sb.ToString();
}
private bool AreMethodSignatureEquals(MethodSignature signature)
{
return (AreAssemblyNamesEqual(signature.AssemblyName)
&& AreClassNameEquals(signature.ClassName)
&& AreMethodNameEquals(signature.MethodName)
&& AreReturnTypeEquals(signature.ReturnType)
&& AreParametersEquals(signature.Parameters));
}
private bool AreParametersEquals(Dictionary<string, Type> parameters)
{
return parameters.Count == Parameters.Count
&& AreSameSizeDictionariesKeyValuePairsEqual(parameters);
}
private bool AreSameSizeDictionariesKeyValuePairsEqual(Dictionary<string, Type> parameters)
{
foreach (KeyValuePair<string, Type> param in Parameters)
{
Type paramType;
//TryGetValue returns true if finds the keyValuePair
if (parameters.TryGetValue(param.Key, out paramType))
{
if (AreParameterTypesDifferent(param.Value, paramType))
{
return false;
}
}
else
{
return false;
}
}
return true;
}
private static bool AreParameterTypesDifferent(Type typeParameter1, Type typeParameter2)
{
return !typeParameter2.Equals(typeParameter1);
}
private bool AreReturnTypeEquals(Type returnType)
{
return returnType.Equals(ReturnType);
}
private bool AreMethodNameEquals(string methodName)
{
// Ensuring case sensitive using IEquatable<string>
return methodName.Equals(MethodName);
}
private bool AreClassNameEquals(string className)
{
// Ensuring case sensitive using IEquatable<string>
return className.Equals(ClassName);
}
private bool AreAssemblyNamesEqual(string assemblyName)
{
// Ensuring case sensitive using IEquatable<string>
return assemblyName.Equals(AssemblyName);
}
#endregion
}
I have checked some implementations for similar types in System.Reflection
, however, I do prefer using a custom struct since the Equality is overridden, and also because the default comparison for ValueTypes
will compare by reference the dictionary (as it should be for a reference type), this is not desired for my purposes.
The full Equality implementation is ready and works flawlessly (Implemented IEquatable< MethodSignature>
, overrode Object.Equals
, overloaded ==
and !=
)
But, now I stumped upon an all-zeroed instance of MethodSignature, and its behavior when using equality... Let's take a look
MethodSignature ms1 = new MethodSignature();
MethodSignature ms2 = new MethodSignature();
// This will throw null reference exception
bool areEqual = ms1.Equals(ms2);
The compiler does not complain because the ms1 and ms2 are considered initialized. I do know that this goes down to the fact that all Value Types in C# have by default the parameter less constructor that defaults all its members. If I compare this behavior to a Microsoft provided Value Type
int a = new int();
int b = new int();
// Returns true
Console.WriteLine(a.Equals(b));
Surely they are equal and comparing both returns of GetHashCode()
returns true as well.
I have checked this and this too, however, I cannot figure out how to create a default for every reference type for this struct that complies with the GetHashCode concept (Two objects that are equal return hash codes that are equal. taken from Microsoft )
So finally my question is:
Any idea on how to override GetHashCode() that complies with the IEquatable implementation when there are reference types within a struct while using the default parameterless constructor?
First of when checking equality of
MethodSignature
instances created with the default constructor, you will get exceptions due to all the fields beingnull
(they are all reference types). If you want the two instances ofto be considered equal, you should adjust your code as follows:
Furthermore an implementation of
GetHashCode
that acts the way you want (based on the suggestion of Jon Skeet in What is the best algorithm for an overridden System.Object.GetHashCode?):This implementation will work with
null
values for the fields and return the same result for different instances that have the exact same values in the fields.Mind: once this hashcode is used (e.g. to store
MethodSignature
instances in aDictionary
) you should never change the underlyingParameter Dictionary
as this will impact theGetHashCode
calculation.