【问题标题】:HashSet constructor with custom IEqualityCompare defined by lambda?具有自定义 IEqualityCompare 的 HashSet 构造函数由 lambda 定义?
【发布时间】:2009-05-19 07:44:35
【问题描述】:

目前允许您自己定义相等比较的HashSet<T> 构造函数是HashSet<T>(IEqualityComparer<T> comparer) 构造函数。 我想将此 EqualityComparer 定义为 lambda。

我发现this blog post 已经创建了一个类,该类允许您通过 lambda 生成比较器,然后使用扩展方法隐藏该类的构造以执行例如 except()。

现在我想做同样的事情,但使用构造函数。是否可以通过扩展方法创建构造函数?或者有没有其他方法可以创建一个HashSet<T>(Func<T,T,int> comparer)

--更新--
为清楚起见,这是我正在尝试完成的(一个 sn-p 的)手绘版本:

HashSet<FileInfo> resultFiles = new HashSet<FileInfo>(
    srcPath.GetFiles(),
    new LambdaComparer<FileInfo>(
        (f1, f2) => f1.Name.SubString(10).Equals(f2.Name.SubString(10))));

或者更理想的

HashSet<FileInfo> resultFiles = new HashSet<FileInfo>(
    srcPath.GetFiles(),
    (f1, f2) => f1.Name.SubString(10).Equals(f2.Name.SubString(10)));

【问题讨论】:

  • 那篇博文被 IMO 破坏了——它只通过调用原始哈希码来生成哈希码,这将导致哈希与相等函数不兼容。我已经在博客上发表了评论。

标签: c# .net lambda iequalitycomparer


【解决方案1】:

不,您不能添加构造函数(即使使用扩展方法)。

假设你有一些神奇的方法可以从 Func&lt;T,T,int&gt;IEqualityComparer&lt;T&gt;(如果你能引用它,我很想阅读那篇博文)——那么你能做的最接近的可能是:

public static class HashSet {
    public static HashSet<T> Create<T>(Func<T, T, int> func) {
        IEqualityComparer<T> comparer = YourMagicFunction(func);
        return new HashSet<T>(comparer);
    }
}

但是;我怀疑你可以用 lambda 做什么来表示平等......你有两个概念要表达:散列和真正的平等。你的 lambda 会是什么样子?如果您试图推迟到子属性,那么可能是 Func&lt;T,TValue&gt; 来选择属性,并在内部使用 EqualityComparer&lt;TValue&gt;.Default ......类似于:

class Person {
    public string Name { get; set; }
    static void Main() {
        HashSet<Person> people = HashSetHelper<Person>.Create(p => p.Name);
        people.Add(new Person { Name = "Fred" });
        people.Add(new Person { Name = "Jo" });
        people.Add(new Person { Name = "Fred" });
        Console.WriteLine(people.Count);
    }
}
public static class HashSetHelper<T> {
    class Wrapper<TValue> : IEqualityComparer<T> {
        private readonly Func<T, TValue> func;
        private readonly IEqualityComparer<TValue> comparer;
        public Wrapper(Func<T, TValue> func,
            IEqualityComparer<TValue> comparer) {
            this.func = func;
            this.comparer = comparer ?? EqualityComparer<TValue>.Default;
        }
        public bool Equals(T x, T y) {
            return comparer.Equals(func(x), func(y));
        }

        public int GetHashCode(T obj) {
            return comparer.GetHashCode(func(obj));
        }
    }
    public static HashSet<T> Create<TValue>(Func<T, TValue> func) {
        return new HashSet<T>(new Wrapper<TValue>(func, null));
    }
    public static HashSet<T> Create<TValue>(Func<T, TValue> func,
        IEqualityComparer<TValue> comparer)
    {
        return new HashSet<T>(new Wrapper<TValue>(func, comparer));
    }
}

【讨论】:

  • 抱歉,忘记添加指向博客文章的链接。更新了 OP。我也会将我的真正目标广告到 OP 来说明。
  • 博客文章已链接,但在我看来它已损坏。对于任何使用散列的情况,即使是最简单的投影比较也会破坏它。
  • 好吧,那差不多就解决了。 “不,没有”似乎是答案。
【解决方案2】:

马克是对的。单个 lambda 没有简单的方法来表达 Equals 和 GetHashCode 所需的信息。如果您提供的 GetHashCode 为“相等”元素返回不同的哈希值,则会导致不正确的行为。

这是我的妥协实现。它将允许任何通用 Func(如 Marc,我忽略了 int,因为您没有解释它),这将给出正确的(因为它符合合同),但非常低效的行为。

我建议您坚持使用真正满足您需求的 IEqualityComparer。可惜 C# 不支持匿名内部类。

public static class HashSetDelegate
{
    public static HashSet<T> Create<T>(Func<T, T, bool> func)
    {
    return new HashSet<T>(new FuncIEqualityComparerAdapter<T>(func));
    }

    private class FuncIEqualityComparerAdapter<U> : IEqualityComparer<U>
    {
    private Func<U, U, bool> func;
    public FuncIEqualityComparerAdapter(Func<U, U, bool> func)
    {
        this.func = func;
    }

    public bool Equals(U a, U b)
    {
        return func(a, b);
    }

    public int GetHashCode(U obj)
    {
        return 0;
    }  

    }
}

public class HashSetTest
{
    public static void Main()
    {
    HashSet<string> s = HashSetDelegate.Create((string a, string b) => string.Compare(a, b, true) == 0);
    }
}

【讨论】:

  • 嗯,是的,现在我已经用一个自定义的 Comparer 类解决了这个问题,但我认为寻找更好的解决方案是一个很好的练习。不过似乎没有真正好的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多