[英]Equality and gethashcode override for c# structs with reference type members after calling parameterless constructor
我正在設計一個結構來比較來自兩個不同來源的方法簽名(目前使用System.Reflection
直接從程序System.Reflection
獲取它們)。 由於我只關心唯一性,因此我選擇了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
}
我已經在System.Reflection
檢查了類似類型的一些實現,但是,我更喜歡使用自定義結構,因為重寫了Equality,並且還因為ValueTypes
的默認比較將通過引用比較字典(因為它應該用於引用類型),這不是我的目的所希望的。
完整的Equality實現已准備就緒並且完美無缺(已實現IEquatable< MethodSignature>
, IEquatable< MethodSignature>
Object.Equals
,overloaded ==
和!=
)
但是,現在我難以理解一個全零的MethodSignature實例,以及它在使用相等時的行為...讓我們來看看
MethodSignature ms1 = new MethodSignature();
MethodSignature ms2 = new MethodSignature();
// This will throw null reference exception
bool areEqual = ms1.Equals(ms2);
編譯器不會抱怨,因為ms1和ms2被認為是初始化的。 我知道這可以歸結為這樣一個事實,即C#中的所有值類型默認情況下都是缺少所有成員的參數less構造函數。 如果我將此行為與Microsoft提供的值類型進行比較
int a = new int();
int b = new int();
// Returns true
Console.WriteLine(a.Equals(b));
當然它們是相同的,比較GetHashCode()
返回值也會返回true 。
我已經檢查這個和這個太 ,但我無法弄清楚如何創建為每個引用類型的這個結構,與GetHashCode的概念相符的默認(兩個對象是相等相等的回報哈希碼。 從微軟采取 )
最后我的問題是:
在使用默認的無參數構造函數時,如果在結構中存在引用類型時如何覆蓋符合IEquatable實現的GetHashCode()的任何想法?
首先,在檢查使用默認構造函數創建的MethodSignature
實例的相等性時,由於所有字段都為null
(它們都是引用類型),您將獲得異常。 如果你想要兩個實例
MethodSignature ms1 = new MethodSignature();
MethodSignature ms2 = new MethodSignature();
要被視為相等,您應該按如下方式調整代碼:
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);
}
此外, 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;
}
}
此實現將使用字段的null
值,並為字段中具有完全相同值的不同實例返回相同的結果。
記住:一旦使用了這個哈希碼(例如,在Dictionary
存儲MethodSignature
實例),你就不應該更改基礎Parameter Dictionary
因為這會影響GetHashCode
計算。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.