简体   繁体   English

调用无参数构造函数后,使用引用类型成员的c#结构的等式和gethashcode覆盖

[英]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. 编译器不会抱怨,因为ms1ms2被认为是初始化的。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM