[英]Equality and gethashcode override for c# structs with reference type members after calling parameterless constructor
I am designing a struct to compare method signatures from two different sources (currently taking them directly from assemblies using System.Reflection
). 我正在设计一个结构来比较来自两个不同来源的方法签名(目前使用
System.Reflection
直接从程序System.Reflection
获取它们)。 Since I only care about uniqueness, I chose HashSet< MethodSignature>
to store my structs and compare them using the subset method. 由于我只关心唯一性,因此我选择了
HashSet< MethodSignature>
来存储我的结构并使用子集方法对它们进行比较。
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. 我已经在
System.Reflection
检查了类似类型的一些实现,但是,我更喜欢使用自定义结构,因为重写了Equality,并且还因为ValueTypes
的默认比较将通过引用比较字典(因为它应该用于引用类型),这不是我的目的所希望的。
The full Equality implementation is ready and works flawlessly (Implemented IEquatable< MethodSignature>
, overrode Object.Equals
, overloaded ==
and !=
) 完整的Equality实现已准备就绪并且完美无缺(已实现
IEquatable< MethodSignature>
, IEquatable< MethodSignature>
Object.Equals
,overloaded ==
和!=
)
But, now I stumped upon an all-zeroed instance of MethodSignature, and its behavior when using equality... Let's take a look 但是,现在我难以理解一个全零的MethodSignature实例,以及它在使用相等时的行为...让我们来看看
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. 编译器不会抱怨,因为ms1和ms2被认为是初始化的。 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.
我知道这可以归结为这样一个事实,即C#中的所有值类型默认情况下都是缺少所有成员的参数less构造函数。 If I compare this behavior to a Microsoft provided Value Type
如果我将此行为与Microsoft提供的值类型进行比较
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. 当然它们是相同的,比较
GetHashCode()
返回值也会返回true 。
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 ) 我已经检查这个和这个太 ,但我无法弄清楚如何创建为每个引用类型的这个结构,与GetHashCode的概念相符的默认(两个对象是相等相等的回报哈希码。 从微软采取 )
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? 在使用默认的无参数构造函数时,如果在结构中存在引用类型时如何覆盖符合IEquatable实现的GetHashCode()的任何想法?
First of when checking equality of MethodSignature
instances created with the default constructor, you will get exceptions due to all the fields being null
(they are all reference types). 首先,在检查使用默认构造函数创建的
MethodSignature
实例的相等性时,由于所有字段都为null
(它们都是引用类型),您将获得异常。 If you want the two instances of 如果你想要两个实例
MethodSignature ms1 = new MethodSignature();
MethodSignature ms2 = new MethodSignature();
to be considered equal, you should adjust your code as follows: 要被视为相等,您应该按如下方式调整代码:
private bool AreParametersEquals(Dictionary<string, Type> parameters)
{
if((parameters == null) && (Parameters == null)) return true;
if((parameters == null) || (Parameters == null)) return false;
if(parameters.Count != Parameters.Count) return false;
var paramArray1 = parameters.OrderBy(p => p.Key).ToArray();
var paramArray2 = Parameters.OrderBy(p => p.Key).ToArray();
for(int i = 0; i < paramArray1.Length; i++)
{
if(!string.Equals(paramArray1[i].Key, paramArray2[i].Key)) return false;
if(!string.Equals(paramArray1[i].Key, paramArray2[i].Key)) return false;
}
return true;
}
private bool AreReturnTypeEquals(Type returnType)
{
if((returnType == null) && (ReturnType == null)) return true;
return (returnType != null) && returnType.Equals(ReturnType);
}
private bool AreMethodNameEquals(string methodName)
{
// Ensuring case sensitive using IEquatable<string>
return string.Equals(methodName, MethodName);
}
private bool AreClassNameEquals(string className)
{
// Ensuring case sensitive using IEquatable<string>
return string.Equals(className, ClassName);
}
private bool AreAssemblyNamesEqual(string assemblyName)
{
// Ensuring case sensitive using IEquatable<string>
return string.Equals(assemblyName, AssemblyName);
}
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? ): 此外,
GetHashCode
的实现以您想要的方式运行(基于Jon Skeet的建议,在什么是重写的System.Object.GetHashCode的最佳算法? ):
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = (int)2166136261;
// Suitable nullity checks etc, of course :)
hash = (hash * 16777619) ^ AssemblyName?.GetHashCode()??0;
hash = (hash * 16777619) ^ ClassName?.GetHashCode()??0;
hash = (hash * 16777619) ^ MethodName?.GetHashCode()??0;
hash = (hash * 16777619) ^ ReturnType?.GetHashCode()??0;
if(Parameters == null) return hash;
var paramArray = Parameters.OrderBy(p => p.Key).ToArray();
for(int i = 0; i < Parameters.Count; i++)
{
hash = (hash * 16777619) ^ paramArray[i].Key?.GetHashCode()??0;
hash = (hash * 16777619) ^ paramArray[i].Value?.GetHashCode()??0;
}
return hash;
}
}
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. 此实现将使用字段的
null
值,并为字段中具有完全相同值的不同实例返回相同的结果。
Mind: once this hashcode is used (eg to store MethodSignature
instances in a Dictionary
) you should never change the underlying Parameter Dictionary
as this will impact the GetHashCode
calculation. 记住:一旦使用了这个哈希码(例如,在
Dictionary
存储MethodSignature
实例),你就不应该更改基础Parameter Dictionary
因为这会影响GetHashCode
计算。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.