我正在设计一个结构来比较来自两个不同来源的方法签名(目前直接从使用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
的默认比较将通过引用进行比较字典(因为它应该是一个参考类型),这不是我的目的所希望的。
完整的平等实施准备就绪并且运作完美(已实施IEquatable< MethodSignature>
,覆盖Object.Equals
,已超载==
和!=
)
但是,现在我难以理解一个全零的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 。
我检查了this和this too,但是,我无法弄清楚如何为符合GetHashCode概念的两个对象创建一个默认值(两个对象返回哈希值相等)代码相等。taken from Microsoft)
所以最后我的问题是:
在使用默认无参数构造函数时,如果在结构中存在引用类型时如何覆盖符合IEquatable实现的GetHashCode()的任何想法?
答案 0 :(得分:2)
首先检查使用默认构造函数创建的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 {{}}中的Jon Skeet建议:
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
值,并为字段中具有完全相同值的不同实例返回相同的结果。
介意:一旦使用此哈希码(例如,在MethodSignature
中存储Dictionary
个实例),您就不应该更改基础Parameter Dictionary
,因为这会影响GetHashCode
计算