leo*_*ora 7 c# linq distinct iequalitycomparer
我有许多是从类派生的类BaseClass,其中BaseClass仅仅有一个'ID属性.
我现在需要对其中一些对象的集合进行区分.我为每个子类反复提供以下代码:
public class PositionComparer : IEqualityComparer<Position>
{
public bool Equals(Position x, Position y)
{
return (x.Id == y.Id);
}
public int GetHashCode(Position obj)
{
return obj.Id.GetHashCode();
}
}
Run Code Online (Sandbox Code Playgroud)
鉴于逻辑只是基于Id,我想创建一个单一的比较器来减少重复:
public class BaseClassComparer : IEqualityComparer<BaseClass>
{
public bool Equals(BaseClass x, BaseClass y)
{
return (x.Id == y.Id);
}
public int GetHashCode(BaseClass obj)
{
return obj.Id.GetHashCode();
}
}
Run Code Online (Sandbox Code Playgroud)
但这似乎没有编译:
IEnumerable<Position> positions = GetAllPositions();
positions = allPositions.Distinct(new BaseClassComparer())
Run Code Online (Sandbox Code Playgroud)
...因为它说它无法转换BaseClass为Position.为什么比较器会强制执行此Distinct()调用的返回值?
更新:这个问题是我2013年7月的博客主题.谢谢你这个好问题!
您在泛型方法类型推断算法中发现了一个不幸的边缘情况.我们有:
Distinct<X>(IEnumerable<X>, IEqualityComparer<X>)
Run Code Online (Sandbox Code Playgroud)
接口在哪里:
IEnumerable<out T> -- covariant
Run Code Online (Sandbox Code Playgroud)
和
IEqualityComparer<in T> -- contravariant
Run Code Online (Sandbox Code Playgroud)
当我们做推断时allPositions,IEnumerable<X>我们说" IEnumerable<T>在T中是协变的,所以我们可以接受Position 或任何更大的类型.(基类型比衍生类型"更大;世界上有比动物长颈鹿更多的动物.)
当我们从比较器进行推断时,我们说" IEqualityComparer<T>在T中是逆变的,所以我们可以接受BaseClass 或任何更小的类型."
那么当实际推断出类型参数时会发生什么呢?我们有两个候选人:Position和BaseClass.两者都满足规定的界限.Position满足第一个边界,因为它与第一个边界相同,并且满足第二个边界,因为它小于第二个边界.BaseClass满足第一个边界,因为它大于第一个边界,并且与第二个边界相同.
我们有两位获胜者.我们需要打破平局.在这种情况下我们该怎么做?
这是一个争论点,三方面都有争论:选择更具体的类型,选择更一般的类型,或者让类型推断失败.我不会重复整个论点,但足以说"选择更一般"的一方赢得了胜利.
(更糟糕的是,规范中有一个错字说"选择更具体"是正确的做法!这是设计过程中编辑错误的结果,从未得到纠正.编译器实现"选择更一般的".我已经提醒Mads这个错误,希望这将在C#5规范中得到修复.)
你去吧 在这种情况下,类型推断选择更一般的类型并推断出调用意味着什么Distinct<BaseClass>.类型推断永远不会考虑返回类型,并且它当然不会考虑将表达式赋予的内容,因此它选择与赋值变量不兼容的类型的事实不是它的业务.
我的建议是在这种情况下明确说明类型参数.
如果查看Distinct的定义,则只涉及一个泛型类型参数(而不是一个TCollection用于输入和输出集合,一个TComparison用于比较器).这意味着您的BaseClassComparer将结果类型约束为基类,并且无法进行赋值转换.
您可以使用泛型参数创建GenericComparer,该参数被约束为至少基类,这可能使您更接近您尝试执行的操作.这看起来像
public class GenericComparer<T> : IEqualityComparer<T> where T : BaseClass
{
public bool Equals(T x, T y)
{
return x.Id == y.Id;
}
public int GetHashCode(T obj)
{
return obj.Id.GetHashCode();
}
}
Run Code Online (Sandbox Code Playgroud)
因为您需要一个实例而不仅仅是方法调用,所以您不能让编译器推断泛型类型(请参阅此讨论),但在创建实例时必须这样做:
IEnumerable<Position> positions;
positions = allPositions.Distinct(new GenericComparer<Position>());
Run Code Online (Sandbox Code Playgroud)
Eric的答案解释了整个问题的根本原因(在协方差和逆变方面).
| 归档时间: |
|
| 查看次数: |
1080 次 |
| 最近记录: |