调用无参数构造函数后,具有引用类型成员的c#结构的Equality和gethashcode重写

调用无参数构造函数后,具有引用类型成员的c#结构的Equality和gethashcode重写,c#,.net,struct,clr,value-type,C#,.net,Struct,Clr,Value Type,我正在设计一个结构来比较来自两个不同来源的方法签名(目前使用System.Reflection直接从程序集获取它们)。因为我只关心唯一性,所以我选择了HashSet来存储我的结构,并使用subset方法比较它们 public struct MethodSignature : IEquatable<MethodSignature> { #region Immutable fields public readonly string AssemblyName; pu

我正在设计一个结构来比较来自两个不同来源的方法签名(目前使用
System.Reflection
直接从程序集获取它们)。因为我只关心唯一性,所以我选择了
HashSet
来存储我的结构,并使用subset方法比较它们

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
}
编译器没有抱怨,因为ms1ms2被认为已初始化。我知道这是因为C#中的所有值类型默认都有一个无参数构造函数,默认为其所有成员。如果我将此行为与Microsoft提供的值类型进行比较

        int a = new int();
        int b = new int();

        // Returns true
        Console.WriteLine(a.Equals(b));
它们肯定是相等的,比较
GetHashCode()
的两个返回值也会返回true

我已经检查过了,但是,我不知道如何为此结构创建符合GetHashCode概念的每个引用类型的默认值(两个相等的对象返回相等的哈希代码)

最后,我的问题是:


当使用默认无参数构造函数时,结构中存在引用类型时,如何重写符合IEquatable实现的GetHashCode(),由于所有字段均为
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);

    }
此实现将使用字段的
null
值,并为字段中具有完全相同值的不同实例返回相同的结果


注意:一旦使用此hashcode(例如,在
字典中存储
MethodSignature
实例),您就不应该更改基础
参数字典
,因为这将影响
GetHashCode
计算。

这个问题相当混乱;为什么不简单地使用
=
来确定两个字符串是否相等?或者,如果您喜欢使用一个方法,静态
String.Equals(s1,s2)
method?@EricLippert我知道,对于字符串,运算符==的重载考虑空实例,我也使用我的单元测试系统进行了检查。Type和Dictionary,==运算符正确处理空值!引用类型的运算符==也总是检查空引用吗?@monsiergutix:Yes
==
对引用类型正确地检查空引用。也就是说,如果您在引用类型上重载了
=
,那么您有责任为空操作数正确实现它。@EricLippert谢谢!因此,只需使用运算符==的IEquatable接口更改所有等式,就可以实现NullPointerException。要解决这个问题,为什么struct MethodSignature的GetHashCode()不会抱怨其中的引用类型默认为null?据我所知,GetHashCode没有检查空值…@monsiergutix:显然你的观点是错误的。为什么您认为默认的
GetHashCode
不知道空值?如果((parameters==null)和&(parameters==null))返回true,我建议替换
if(ReferenceEquals(参数,参数))的code>返回true。这不仅处理“均为null”的情况,还处理“非null reference equals”的情况。如果引用是相等的,那么每一个其他的相等测试都会成功,所以这样做没有意义;请尽早离开。@JohanDonne非常感谢建议的GetHashCode实现!
    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);

    }
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;
        }
    }