关于c#:处理GetHashCode实现中的集合

Handling collections in GetHashCode implementation

我正在基于这个答案中的hashcode结构来实现gethashcode()。因为我的equals方法将考虑使用Enumerable.SequenceEqual()的集合,所以我需要在getHashCode()实现中包含这些集合。

作为起点,我使用jon skeet的嵌入式gethashcode()实现来测试hashcode结构实现的输出。使用下面的测试,可以按预期工作-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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());
}

下一步是在getHashCode()计算中考虑集合。这需要在myObjecteEmbeddedGetHashCode中对getHashCode()实现进行少量添加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public override int GetHashCode()
{
    unchecked
    {
        int hash = 17;

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

        int collectionHash = 17;

        foreach (var item in collection)
        {
            collectionHash = collectionHash * 31 + item.GetHashCode();
        }

        hash = hash * 31 + collectionHash;

        return hash;
    }
}

然而,这在hashcode结构中有点困难。在本例中,当类型列表的集合传递到hash方法时,t是list,因此尝试将obj强制转换为ICollection或IEnumerable不起作用。我可以成功地强制转换为IEnumerable,但这会导致装箱,我发现我必须担心排除诸如实现IEnumerable的字符串之类的类型。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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;
    }
}


您可以通过以下几种方式解决收集问题:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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类型,在我看来,你引用的同一个页面有一个:https://stackoverflow.com/a/2575444/3538012

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