HashSet<T>.CreateSetComparer的用法示例

5
我知道 HashSet<T>.SetEquals 方法,但是何时以及如何使用 CreateSetComparer文档 指出:"仅在一个级别上检查相等性;但是,您可以链接比较器到其他级别以执行更深层次的相等性测试"
那么简单的例子是什么?
特别地,如果我要比较的每个集合中还包含 HashSet,那么应该如何正确使用 CreateSetComparer
这是我的起点。我想知道 CreateSetComparer 方法是否适用以及如何正确使用它:
public class Foo : IEquatable<Foo>
{
    public string Label { get; set; }
    public string Value { get; set; }
    public override string ToString() {return String.Format("{0}:{1}", Label, Value); }

    // assume for this example that Label and Value are immutable once set;
    public override int GetHashCode(){ return ToString().GetHashCode(); }
    // simplified equality check; assume it meets my needs for this example;
    public bool Equals(Foo other){ return String.Equals(this.ToString(), other.ToString()); }
}

public class FooGroup : IEquatable<FooGroup>
{
    public int GroupIndex {get; set;}
    public HashSet<Foo> FooCollection {get; set;}

    // -----------------------------
    // Does HashSet.CreateSetComparer somehow eliminate or simplify the following code?
    // -----------------------------
    public override int GetHashCode()
    { 
        int hash = GroupIndex;
        foreach(Foo f in FooCollection)
          hash = hash ^ (f.GetHashCode() & 0x7FFFFFFF);
        return hash;
    }

    public bool Equals(FooGroup other)
    { 
        // ignore missing null checks for this example
        return this.GroupIndex == other.GroupIndex && this.FooCollection.SetEquals(other.FooCollection);
    }
}

public class GroupCollection : IEquatable<GroupCollection>
{
    public string CollectionLabel {get; set;}
    public HashSet<FooGroup> AllGroups {get; set;}

    // -----------------------------
    // Does HashSet.CreateSetComparer somehow eliminate or simplify the following code?
    // -----------------------------
    public override int GetHashCode()
    { 
        int hash = CollectionLabel.GetHashCode();
        foreach(FooGroup g in AllGroups)
          hash = hash ^ (g.GetHashCode() & 0x7FFFFFFF);
        return hash;
    }

    public bool Equals(GroupCollection other)
    { 
        // ignore missing null checks for this example
        return String.Equals(this.CollectionLabel, other.CollectionLabel) && this.AllGroups.SetEquals(other.AllGroups);
    }
}

忽略关于系统设计等方面的争议,一个简化的用例是:想象一下我已经获取了一个看起来像这样的复杂数据集:
var newSetA = new GroupCollection{ ... }
var oldSetA = new GroupCollection{ ... }

我只是想检查:

if (newSetA.Equals(oldSetA))
  Process(newSetA);

我很难想到一个通用的情况,这会有用。 - Jodrell
2个回答

3

我在使用它时,是为了提供一个“多个”键的字典,其中顺序并不重要:

var dict = new Dictionary<HashSet<int>, string>(HashSet<int>.CreateSetComparer());
dict[new HashSet<int> { 1, 2 }] = "foo";
dict[new HashSet<int> { 2, 1 }].Dump();

您可以通过使用params索引器对其进行包装,以提供更好的API:
public class MultiKeyDictionary<TKey, TValue> : IDictionary<HashSet<TKey>, TValue>
{
    private readonly IDictionary<HashSet<TKey>, TValue> _dict;

    public MultiKeyDictionary()
    {
        _dict = new Dictionary<HashSet<TKey>, TValue>(HashSet<TKey>.CreateSetComparer());
    }

    public TValue this[params TKey[] keys]
    {
        get { return _dict[new HashSet<TKey>(keys)]; }
        set { _dict[new HashSet<TKey>(keys)] = value; }
    }

    ...
}


var dict = new MultiKeyDictionary<int, string>();
dict[1, 2] = "foo";
dict[2, 1].Dump();

3

让我们先来探讨一下“何时使用CreateSetComparer”这个问题?你已经有一个相当的想法:

特别是,如果我比较的每个集合中的每个项也包含一个HashSet,那么使用CreateSetComparer的正确用法是什么?

好吧,例如,下一个示例演示了HashSet使用其默认比较器(仅按引用比较)时的默认行为:

var set1 = new HashSet<HashSet<int>>{
    new HashSet<int>{2,3,4},
    new HashSet<int>{7,8,9}
};
var set2 = new HashSet<HashSet<int>>{
    new HashSet<int>{2,3,4},
    new HashSet<int>{7,8,9},
};

set1.SetEquals(set2).Dump(); // false :-(    
set1.SequenceEqual(set2).Dump(); // false
set1.SequenceEqual(set2, HashSet<int>.CreateSetComparer()).Dump(); // true

你也可以使用CreateSetComparerSetEquals一起使用,像这样:

// the order of elements in the set has been change.
var set1 = new HashSet<HashSet<int>>(HashSet<int>.CreateSetComparer()){
    new HashSet<int>{2,3,4},
    new HashSet<int>{7,8,9}
};
var set2 = new HashSet<HashSet<int>>{
    new HashSet<int>{7,8,9},
    new HashSet<int>{2,3,4},
};

set1.SetEquals(set2).Dump(); // true :-)
set1.SequenceEqual(set2).Dump(); // false
set1.SequenceEqual(set2, HashSet<int>.CreateSetComparer()).Dump(); // false

通常情况下,这是常用的方式。不过,CreateSetComparer提供了GetHashCode方法,你可以利用它,虽然这并不一定更简短/干净,和你已经做的相同。

// -----------------------------
// Does HashSet.CreateSetComparer somehow eliminate or simplify the following code?
// -----------------------------
private IEqualityComparer<HashSet<FooGroup>> _ecomparer = 
        HashSet<FooGroup>.CreateSetComparer();
public override int GetHashCode()
{ 
    int hash = CollectionLabel.GetHashCode();
    hash ^= _ecomparer.GetHashCode(AllGroups);
    return hash;
}

太棒了!我无法从文档中推断出这种用法。只是提醒一下,当使用嵌套的值类型集时SetEquals会失败...我本以为HashSet使用默认比较器的行为会在这种情况下递归。 - mdisibio
1
@mdisibio,我还添加了一些关于GetHashCode的内容。顺便说一句,你可能会争论哪种功能更有用,但.NET的哲学是不要把事情复杂化,并且对于所有东西始终具有相同的默认行为,没有例外。 - Erti-Chris Eelmaa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接