首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >自动IEqualityComparer<T>

自动IEqualityComparer<T>
EN

Code Review用户
提问于 2016-11-21 15:46:48
回答 2查看 3.5K关注 0票数 4

有些API(如Except扩展)需要IEqualityComparer<T>才能工作。我发现对于这样一个简单的任务来说,实现一个接口的工作量太大了,所以我想为什么不自动实现它呢?

我以可重用的方式实现了这个接口,这样我就可以将它与任何值和任意数量的属性一起使用。

代码语言:javascript
运行
复制
internal class AutoEqualityComparer<T> : IEqualityComparer<T>
{
    public AutoEqualityComparer(IEnumerable<Func<T, object>> selectors)
    {
        Selectors = selectors;
    }

    private IEnumerable<Func<T, object>> Selectors { get; }

    public bool Equals(T left, T right)
    {
        return
            !ReferenceEquals(left, null) &&
            !ReferenceEquals(right, null) &&
            Selectors.All(selector => selector(left).Equals(selector(right)));
    }

    public int GetHashCode(T obj)
    {
        unchecked
        {
            return Selectors
                .Select(selector => selector(obj).GetHashCode())
                .Aggregate(17, (hashCode, subHashCode) => hashCode * 31 + subHashCode);
        }
    }
}

然后我构建了一个新的扩展,它也可以接受任意数量的选择器来进行比较:

代码语言:javascript
运行
复制
internal static class Enumerable
{
    public static IEnumerable<T> Except<T>(
        this IEnumerable<T> first, 
        IEnumerable<T> second, 
        params Func<T, object>[] compare)
    {
        var mec = new AutoEqualityComparer<T>(compare);
        return first.Except(second, mec);
    }
}

示例:

选择不在基本异常中的异常的所有属性:

代码语言:javascript
运行
复制
var exceptionProperties =
    typeof(ArgumentException)
    .GetProperties()
    .Except(typeof(Exception).GetProperties(), x => x.Name);

// result: ParamName is the only property
EN

回答 2

Code Review用户

回答已采纳

发布于 2016-11-21 16:32:02

我认为,如果您在类中创建了一个投影并将其传递给您,那么对于任何跟随您的人来说,使用起来都会更容易,也更容易阅读。

而不是IEnumerable<Func<T, object>>,我会将其更改为Func<T, K>投影,并丢失IEnumerable。,这将需要使类现在接受两个泛型。

代码语言:javascript
运行
复制
public class AutoEqualityComparer<T, K> : IEqualityComparer<T>
{
    private readonly Func<T, K> _projection;

    public AutoEqualityComparer(Func<T, K> projection)
    {
        _projection = projection;
    }

因为我们现在拥有了类的强类型,所以我们可以对等于方法和GetHashCode方法使用EqualityComparer类。

代码语言:javascript
运行
复制
    public virtual bool Equals(T x, T y)
    {
        if (x == null && y == null)
        {
            return true;
        }
        if (x == null)
        {
            return false;
        }
        if (y == null)
        {
            return false;
        }

        var xData = _projection(x);
        var yData = _projection(y);

        return EqualityComparer<K>.Default.Equals(xData, yData);
    }

    public virtual int GetHashCode(T obj)
    {
        if (obj == null)
        {
            return 0;
        }

        var objData = _projection(obj);

        return EqualityComparer<K>.Default.GetHashCode(objData);
    }

我会创建一个类来创建AutoEqualityComparer以使它更容易使用。

代码语言:javascript
运行
复制
public class EqualityProjectionComparer<T>
{
    public static AutoEqualityComparer<T, K> Create<K>(Func<T, K> projection)
    {
        return new AutoEqualityComparer<T, K>(projection);
    }
}

现在,使用扩展方法,您可以为IEqualityComparer创建元组或匿名类

例子可能是

代码语言:javascript
运行
复制
var comparer = EqualityProjectionComparer<ArgumentException>.Create(arg => new
{
    arg.ParamName,
    arg.Message
});

对我来说,这更清楚我们在比较什么,并且匿名类型,比较属性,看看它们是否相等,而不是引用。

票数 4
EN

Code Review用户

发布于 2016-11-21 16:00:54

一句简短的评论让你开始。

您的Equals实现不太正确。考虑:

代码语言:javascript
运行
复制
var first = new string[] {null};
var emptyEnumerable = first.Except(first, s => s.Length);

emptyEnumerable真的空了吗?不,不是的。它包含一个元素null

我认为这是一个危险的尝试,尤其是因为比较器往往被称为紧循环,这意味着您希望它们简单和快速-这看起来不会。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/147697

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档