LINQ.Distinct 在 IEquatable 对象上不起作用
LINQ.Distinct on IEquatable object doesn't work
我有一个继承自 IEquatable<> 的基础 class 的对象。到目前为止一切顺利,它适用于继承相同基础 class 的其他对象。但是我在 class "RoomType" 上使用 "Attrbiutes" 属性 时似乎有问题。下面你会看到 classes 和一个测试,我希望给出其他输出。
我通过 RoomType.GetHashCode() 将问题缩小到某些问题,当我评论 "SafeHashCode(Attributes)" 时返回了预期的结果。
测试:
private static void QuickTest()
{
RoomType[] rooms = new RoomType[] {
new RoomType {
Attributes = new [] { "a", "b,"c"},
},
new RoomType
{
Attributes = new [] { "a", "b","c"},
}
};
List<RoomType> result = rooms.Distinct().ToList();
//result contains 2 items, I was expecting 1
}
房间类型:
public class RoomType : EntityBase
{
public string OriginalRoomCode { get; set; }
public Enum.RoomType RoomCode { get; set; }
public IEnumerable<string> Attributes { get; set; }
public override bool Equals(object obj)
{
RoomType other = obj as RoomType;
if (other != null)
return Equals(other);
return false;
}
public override bool Equals(EntityBase obj)
{
RoomType y = (RoomType)obj;
return SafeEqual(OriginalRoomCode, y.OriginalRoomCode) &&
SafeEqual(RoomCode, y.RoomCode) &&
SafeEqual(Attributes,y.Attributes);
}
public override int GetHashCode()
{
unchecked
{
return SafeHashCode(OriginalRoomCode) ^
SafeHashCode(RoomCode) ^
SafeHashCode(Attributes);
}
}
public override object Clone()
{
return new RoomType
{
RoomCode = (Enum.RoomType)SafeClone(RoomCode),
OriginalRoomCode = (string)SafeClone(OriginalRoomCode),
Attributes = (IEnumerable<string>)SafeClone(Attributes)
};
}
}
实体库:
public abstract class EntityBase : IEquatable<EntityBase>, ICloneable
{
public bool SafeEqual<T>(T x, T y)
{
bool isXDefault = EqualityComparer<T>.Default.Equals(x, default(T));
bool isYDefault = EqualityComparer<T>.Default.Equals(y, default(T));
if (isXDefault && isYDefault)
return true;
if (isXDefault != isYDefault)
return false;
if (x is EntityBase)
return x.Equals(y);
IEnumerable<object> xEnumerable = x as IEnumerable<object>;
IEnumerable<object> yEnumerable = y as IEnumerable<object>;
if (xEnumerable != null && yEnumerable != null)
{
foreach (var yItem in yEnumerable)
{
bool match = false;
foreach (var xItem in xEnumerable)
{
if(SafeEqual(xItem, yItem))
{
match = true;
break;
}
}
if (!match)
return false;
}
return true;
}
return x.Equals(y);
}
public int SafeHashCode<T>(T x)
{
if (EqualityComparer<T>.Default.Equals(x, default(T)))
return 0;
return x.GetHashCode();
}
public object SafeClone<T>(T x)
{
//if x is null or default value
if (EqualityComparer<T>.Default.Equals(x, default(T)))
return default(T);
//if x is of type EntityBase call clone()
if (x is EntityBase)
return (x as EntityBase).Clone();
//else the type is a default type return the value
return x;
}
public abstract bool Equals(EntityBase other);
public override abstract int GetHashCode();
public abstract override bool Equals(object obj);
public abstract object Clone();
}
更新
我能够通过在 SafeHashCode(T x)
中添加以下代码来修复它
IEnumerable<object> xEnumerable = x as IEnumerable<object>;
if (xEnumerable != null)
return xEnumerable.Aggregate(17, (acc, item) => acc * 19 + SafeHashCode(item));
对于 SaveEqual
,您可以自定义检查 IEnumerable
类型比较 - 您可以检查 xEnumerable
中的每个项目是否包含在 yEnumerable
中。 注意 1:您这里也有一个错误 - yEnumerable
可以包含其他项目或者它可以有重复的项目。
但是对于 SaveHashCode
,您没有对 IEnumerable
类型的自定义处理。你只是 return 参数的哈希码。这将为不同的数组实例提供不同的结果,即使数组包含相同的值也是如此。要解决这个问题,您应该根据集合项计算哈希码:
public int SaveHashCode<T>(T x)
{
if (EqualityComparer<T>.Default.Equals(x, default(T)))
return 0;
IEnumerable<object> xEnumerable = x as IEnumerable<object>;
if (xEnumerable != null)
return xEnumerable.Aggregate(17, (acc, item) => acc * 19 + SaveHashCode(item));
return x.GetHashCode();
}
注 2 使用 XOR 进行 hashCode 计算会给您带来另一个问题 - 与零进行异或(您将其用作默认值,或者它可以是整数的哈希码 0
, or boolean false
) 不修改结果。结果也不会取决于订单。 IE。如果你有两个数组:[0, 1, 2]
和 [2,0,1,0]
。按照@christophano 的建议进行异或运算会给你……3
和3
。但是那些数组是完全不同的。所以我建议你使用 hashCode calcualtion based on prime numbers.
问题是 2 个数组的哈希码将不相同,即使内容可以被认为是相同的。
而不是将数组传递给 SafeHashCode
获取数组每个成员的哈希码。
public override int GetHashCode()
{
unchecked
{
return SafeHashCode(OriginalRoomCode) ^
SafeHashCode(RoomCode) ^
Attributes.Select(x => SafeGetHashCode(x)).Aggregate((seed, current) => seed ^ current);
}
}
我有一个继承自 IEquatable<> 的基础 class 的对象。到目前为止一切顺利,它适用于继承相同基础 class 的其他对象。但是我在 class "RoomType" 上使用 "Attrbiutes" 属性 时似乎有问题。下面你会看到 classes 和一个测试,我希望给出其他输出。
我通过 RoomType.GetHashCode() 将问题缩小到某些问题,当我评论 "SafeHashCode(Attributes)" 时返回了预期的结果。
测试:
private static void QuickTest()
{
RoomType[] rooms = new RoomType[] {
new RoomType {
Attributes = new [] { "a", "b,"c"},
},
new RoomType
{
Attributes = new [] { "a", "b","c"},
}
};
List<RoomType> result = rooms.Distinct().ToList();
//result contains 2 items, I was expecting 1
}
房间类型:
public class RoomType : EntityBase
{
public string OriginalRoomCode { get; set; }
public Enum.RoomType RoomCode { get; set; }
public IEnumerable<string> Attributes { get; set; }
public override bool Equals(object obj)
{
RoomType other = obj as RoomType;
if (other != null)
return Equals(other);
return false;
}
public override bool Equals(EntityBase obj)
{
RoomType y = (RoomType)obj;
return SafeEqual(OriginalRoomCode, y.OriginalRoomCode) &&
SafeEqual(RoomCode, y.RoomCode) &&
SafeEqual(Attributes,y.Attributes);
}
public override int GetHashCode()
{
unchecked
{
return SafeHashCode(OriginalRoomCode) ^
SafeHashCode(RoomCode) ^
SafeHashCode(Attributes);
}
}
public override object Clone()
{
return new RoomType
{
RoomCode = (Enum.RoomType)SafeClone(RoomCode),
OriginalRoomCode = (string)SafeClone(OriginalRoomCode),
Attributes = (IEnumerable<string>)SafeClone(Attributes)
};
}
}
实体库:
public abstract class EntityBase : IEquatable<EntityBase>, ICloneable
{
public bool SafeEqual<T>(T x, T y)
{
bool isXDefault = EqualityComparer<T>.Default.Equals(x, default(T));
bool isYDefault = EqualityComparer<T>.Default.Equals(y, default(T));
if (isXDefault && isYDefault)
return true;
if (isXDefault != isYDefault)
return false;
if (x is EntityBase)
return x.Equals(y);
IEnumerable<object> xEnumerable = x as IEnumerable<object>;
IEnumerable<object> yEnumerable = y as IEnumerable<object>;
if (xEnumerable != null && yEnumerable != null)
{
foreach (var yItem in yEnumerable)
{
bool match = false;
foreach (var xItem in xEnumerable)
{
if(SafeEqual(xItem, yItem))
{
match = true;
break;
}
}
if (!match)
return false;
}
return true;
}
return x.Equals(y);
}
public int SafeHashCode<T>(T x)
{
if (EqualityComparer<T>.Default.Equals(x, default(T)))
return 0;
return x.GetHashCode();
}
public object SafeClone<T>(T x)
{
//if x is null or default value
if (EqualityComparer<T>.Default.Equals(x, default(T)))
return default(T);
//if x is of type EntityBase call clone()
if (x is EntityBase)
return (x as EntityBase).Clone();
//else the type is a default type return the value
return x;
}
public abstract bool Equals(EntityBase other);
public override abstract int GetHashCode();
public abstract override bool Equals(object obj);
public abstract object Clone();
}
更新 我能够通过在 SafeHashCode(T x)
中添加以下代码来修复它 IEnumerable<object> xEnumerable = x as IEnumerable<object>;
if (xEnumerable != null)
return xEnumerable.Aggregate(17, (acc, item) => acc * 19 + SafeHashCode(item));
对于 SaveEqual
,您可以自定义检查 IEnumerable
类型比较 - 您可以检查 xEnumerable
中的每个项目是否包含在 yEnumerable
中。 注意 1:您这里也有一个错误 - yEnumerable
可以包含其他项目或者它可以有重复的项目。
但是对于 SaveHashCode
,您没有对 IEnumerable
类型的自定义处理。你只是 return 参数的哈希码。这将为不同的数组实例提供不同的结果,即使数组包含相同的值也是如此。要解决这个问题,您应该根据集合项计算哈希码:
public int SaveHashCode<T>(T x)
{
if (EqualityComparer<T>.Default.Equals(x, default(T)))
return 0;
IEnumerable<object> xEnumerable = x as IEnumerable<object>;
if (xEnumerable != null)
return xEnumerable.Aggregate(17, (acc, item) => acc * 19 + SaveHashCode(item));
return x.GetHashCode();
}
注 2 使用 XOR 进行 hashCode 计算会给您带来另一个问题 - 与零进行异或(您将其用作默认值,或者它可以是整数的哈希码 0
, or boolean false
) 不修改结果。结果也不会取决于订单。 IE。如果你有两个数组:[0, 1, 2]
和 [2,0,1,0]
。按照@christophano 的建议进行异或运算会给你……3
和3
。但是那些数组是完全不同的。所以我建议你使用 hashCode calcualtion based on prime numbers.
问题是 2 个数组的哈希码将不相同,即使内容可以被认为是相同的。
而不是将数组传递给 SafeHashCode
获取数组每个成员的哈希码。
public override int GetHashCode()
{
unchecked
{
return SafeHashCode(OriginalRoomCode) ^
SafeHashCode(RoomCode) ^
Attributes.Select(x => SafeGetHashCode(x)).Aggregate((seed, current) => seed ^ current);
}
}