C# 在GetHashCode实现中处理集合

C# 在GetHashCode实现中处理集合,c#,generics,collections,gethashcode,iequatable,C#,Generics,Collections,Gethashcode,Iequatable,我正在基于这个答案中的HashCode结构实现GetHashCode()。由于我的均等方法将使用枚举.StangeSuffar()来考虑集合,所以我需要在GethAsHeDeF()实现中包含集合。 首先,我使用Jon Skeet的嵌入式GetHashCode()实现来测试HashCode结构实现的输出。使用下面的测试,这项工作与预期一样- private class MyObjectEmbeddedGetHashCode { public int x; public string

我正在基于这个答案中的HashCode结构实现GetHashCode()。由于我的均等方法将使用枚举.StangeSuffar()来考虑集合,所以我需要在GethAsHeDeF()实现中包含集合。 首先,我使用Jon Skeet的嵌入式GetHashCode()实现来测试HashCode结构实现的输出。使用下面的测试,这项工作与预期一样-

private class MyObjectEmbeddedGetHashCode
{
    public int x;
    public string y;
    public DateTimeOffset z;

    public List<string> collection;

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;

            hash = hash * 31 + x.GetHashCode();
            hash = hash * 31 + y.GetHashCode();
            hash = hash * 31 + z.GetHashCode();

            return hash;
        }
    }
}

private class MyObjectUsingHashCodeStruct
{
    public int x;
    public string y;
    public DateTimeOffset z;

    public List<string> collection;

    public override int GetHashCode()
    {
        return HashCode.Start
            .Hash(x)
            .Hash(y)
            .Hash(z);
    }
}

[Test]
public void GetHashCode_CollectionExcluded()
{
    DateTimeOffset now = DateTimeOffset.Now;

    MyObjectEmbeddedGetHashCode a = new MyObjectEmbeddedGetHashCode() 
    { 
        x = 1, 
        y = "Fizz",
        z = now,
        collection = new List<string>() 
        { 
            "Foo", 
            "Bar", 
            "Baz" 
        } 
    };

    MyObjectUsingHashCodeStruct b = new MyObjectUsingHashCodeStruct()
    {
        x = 1,
        y = "Fizz",
        z = now,
        collection = new List<string>() 
        { 
            "Foo", 
            "Bar", 
            "Baz" 
        }
    };

    Console.WriteLine("MyObject::GetHashCode(): {0}", a.GetHashCode());
    Console.WriteLine("MyObjectEx::GetHashCode(): {0}", b.GetHashCode());

    Assert.AreEqual(a.GetHashCode(), b.GetHashCode());
}
但是,在HashCode结构中,这有点困难。在本例中,当将List类型的集合传递到Hash方法中时,T是List,因此尝试将obj强制转换为ICollection或IEnumberable是不起作用的。我可以成功地强制转换为IEnumerable,但这会导致装箱,我发现我不得不担心排除实现IEnumerable的string之类的类型

在这种情况下,有没有可靠的方法将obj强制转换为ICollection或IEnumerable

public struct HashCode
{
    private readonly int hashCode;

    public HashCode(int hashCode)
    {
        this.hashCode = hashCode;
    }

    public static HashCode Start
    {
        get { return new HashCode(17); }
    }

    public static implicit operator int(HashCode hashCode)
    {
        return hashCode.GetHashCode();
    }

    public HashCode Hash<T>(T obj)
    {
        // I am able to detect if obj implements one of the lower level
        // collection interfaces. However, I am not able to cast obj to
        // one of them since T in this case is defined as List<string>,
        // so using as to cast obj to ICollection<T> or IEnumberable<T>
        // doesn't work.
        var isGenericICollection = obj.GetType().GetInterfaces().Any(
            x => x.IsGenericType && 
            x.GetGenericTypeDefinition() == typeof(ICollection<>));

        var c = EqualityComparer<T>.Default;

        // This works but using IEnumerable causes boxing.
        // var h = c.Equals(obj, default(T)) ? 0 : ( !(obj is string) && (obj is IEnumerable) ? GetCollectionHashCode(obj as IEnumerable) : obj.GetHashCode());

        var h = c.Equals(obj, default(T)) ? 0 : obj.GetHashCode();
        unchecked { h += this.hashCode * 31; }
        return new HashCode(h);
    }

    public override int GetHashCode()
    {
        return this.hashCode;
    }
}
public结构HashCode
{
私有只读int哈希代码;
公共哈希代码(int哈希代码)
{
this.hashCode=hashCode;
}
公共静态哈希代码启动
{
获取{returnnewhashcode(17);}
}
公共静态隐式运算符int(HashCode HashCode)
{
返回hashCode.GetHashCode();
}
公共哈希码哈希(T obj)
{
//我能够检测到obj是否实现了一个较低的级别
//集合接口。但是,我无法将obj强制转换为
//其中之一,因为在本例中T定义为列表,
//因此,使用as将obj强制转换为ICollection或IEnumberable
//不起作用。
var isGenericCollection=obj.GetType().GetInterfaces().Any(
x=>x.IsGenericType&&
x、 GetGenericTypeDefinition()==typeof(ICollection));
var c=均衡比较程序默认值;
//这是可行的,但使用IEnumerable会导致装箱。
//var h=c.Equals(obj,默认值(T))?0:(!(obj是字符串)和&(obj是IEnumerable)?GetCollectionHashCode(obj是IEnumerable):obj.GetHashCode();
var h=c.Equals(obj,默认值(T))?0:obj.GetHashCode();
未选中{h+=this.hashCode*31;}
返回新的HashCode(h);
}
公共覆盖int GetHashCode()
{
返回此.hashCode;
}
}

您可以通过以下两种方式解决收款问题:

  • 使用非通用接口,例如
    ICollection
    IEnumerable
  • Hash()
    方法添加重载,例如
    Hash(IEnumerable list){…}
  • 也就是说,我认为最好不要使用
    struct HashCode
    ,而是将特定于集合的代码放在实际的
    GetHashCode()
    方法中。例如:

    public override int GetHashCode()
    {
        HashCode hash = HashCode.Start
            .Hash(x)
            .Hash(y)
            .Hash(z);
    
        foreach (var item in collection)
        {
            hash = hash.Hash(item);
        }
    
        return hash;
    }
    
    如果您确实想要功能齐全的
    struct HashCode
    类型,那么在我看来,您引用的同一页面似乎有一个:


    成员的命名有所不同,但基本上与
    struct HashCode
    类型的想法相同,但对其他复杂类型具有重载(如我上面的建议#2)。您可以使用它,或者只是将那里的技术应用到
    struct HashCode
    的实现中,保留其中使用的命名约定。

    旁注:
    GetHashCode
    应该是快速的,对所有项的反射和迭代不是“快速”代码的最佳示例。您试图解决什么问题?您不必在散列中包含集合。如果XYZ给了你一个很好的散列分布,那么就到此为止。@AlexeiLevenkov我在调用GetHashCode时放了一个秒表,在这里有LINQ查询和没有LINQ查询,它分别运行5毫秒和3毫秒,因此需要考虑性能影响。我将此标记为答案,因为为哈希方法添加重载提供了所需的行为。然而,我遇到了一个无限递归问题,由于我的两个类之间的双向关联,导致了Equals()覆盖中的StackOverflowException。这导致我放弃在IEquatable实现中包含集合和引用类型,并最终使用了一种类似于@Blam在对我的问题的评论中提到的解决方案。
    public override int GetHashCode()
    {
        HashCode hash = HashCode.Start
            .Hash(x)
            .Hash(y)
            .Hash(z);
    
        foreach (var item in collection)
        {
            hash = hash.Hash(item);
        }
    
        return hash;
    }