有些API(如Except
扩展)需要IEqualityComparer<T>
才能工作。我发现对于这样一个简单的任务来说,实现一个接口的工作量太大了,所以我想为什么不自动实现它呢?
我以可重用的方式实现了这个接口,这样我就可以将它与任何值和任意数量的属性一起使用。
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);
}
}
}
然后我构建了一个新的扩展,它也可以接受任意数量的选择器来进行比较:
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);
}
}
示例:
选择不在基本异常中的异常的所有属性:
var exceptionProperties =
typeof(ArgumentException)
.GetProperties()
.Except(typeof(Exception).GetProperties(), x => x.Name);
// result: ParamName is the only property
发布于 2016-11-21 16:32:02
我认为,如果您在类中创建了一个投影并将其传递给您,那么对于任何跟随您的人来说,使用起来都会更容易,也更容易阅读。
而不是IEnumerable<Func<T, object>>
,我会将其更改为Func<T, K>
投影,并丢失IEnumerable
。,这将需要使类现在接受两个泛型。
public class AutoEqualityComparer<T, K> : IEqualityComparer<T>
{
private readonly Func<T, K> _projection;
public AutoEqualityComparer(Func<T, K> projection)
{
_projection = projection;
}
因为我们现在拥有了类的强类型,所以我们可以对等于方法和GetHashCode
方法使用EqualityComparer类。
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
以使它更容易使用。
public class EqualityProjectionComparer<T>
{
public static AutoEqualityComparer<T, K> Create<K>(Func<T, K> projection)
{
return new AutoEqualityComparer<T, K>(projection);
}
}
现在,使用扩展方法,您可以为IEqualityComparer
创建元组或匿名类
例子可能是
var comparer = EqualityProjectionComparer<ArgumentException>.Create(arg => new
{
arg.ParamName,
arg.Message
});
对我来说,这更清楚我们在比较什么,并且匿名类型,比较属性,看看它们是否相等,而不是引用。
发布于 2016-11-21 16:00:54
一句简短的评论让你开始。
您的Equals
实现不太正确。考虑:
var first = new string[] {null};
var emptyEnumerable = first.Except(first, s => s.Length);
emptyEnumerable
真的空了吗?不,不是的。它包含一个元素null
。
我认为这是一个危险的尝试,尤其是因为比较器往往被称为紧循环,这意味着您希望它们简单和快速-这看起来不会。
https://codereview.stackexchange.com/questions/147697
复制相似问题