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?