假设有这个类:
public class Foo
{
public int Id { get; set; }
public int? NullableId { get; set; }
public Foo(int id, int? nullableId)
{
Id = id;
NullableId = nullableId;
}
}
我需要按照以下规则比较这些对象:
- 如果两个对象都具有 NullableId 值,那么我们比较两个 Id 和 NullableId
- 如果某些对象/它们都没有 NullableId 那么 忽略它,只比较 Id。
为了实现它,我像这样重写了 Equals 和 GetHashCode:
public override bool Equals(object obj)
{
var otherFoo = (Foo)obj;
var equalityCondition = Id == otherFoo.Id;
if (NullableId.HasValue && otherFoo.NullableId.HasValue)
equalityCondition &= (NullableId== otherFoo.NullableId);
return equalityCondition;
}
public override int GetHashCode()
{
var hashCode = 806340729;
hashCode = hashCode * -1521134295 + Id.GetHashCode();
return hashCode;
}
再往下我有两个 Foo 列表:
var first = new List<Foo> { new Foo(1, null) };
var second = new List<Foo> { new Foo(1, 1), new Foo(1, 2), new Foo(1, 3) };
接下来,我要加入这些列表。如果我这样做:
var result = second.Join(first, s => s, f => f, (f, s) => new {f, s}).ToList();
然后结果如我所料,我将得到 3 个项目。 但是,如果我改变顺序并首先加入第二个:
var result = first.Join(second, f => f, s => s, (f, s) => new {f, s}).ToList();
那么结果将只有 1 个项目 - new Foo(1, null) 和 new Foo(1 ,3)
我不明白我做错了什么。如果尝试在 Equals 方法中放置一个断点,那么我可以看到它尝试比较来自同一列表的项目(例如比较 new Foo(1, 1) 和 new Foo(1 ,2 ))。对我来说,这似乎是因为在 Join 方法中创建了 Lookup。
有人可以澄清那里发生了什么吗?我应该更改什么以实现所需的行为?
最佳答案
您的 Equals 方法是自反和对称的,但它不是可传递的。
您的实现不符合文档中指定的要求:
If (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true.
来自 https://learn.microsoft.com/en-us/dotnet/api/system.object.equals?view=netframework-4.8
例如,假设您有:
var x = new Foo(1, 100);
var y = new Foo(1, null);
var z = new Foo(1, 200);
你有 x.Equals(y)
和 y.Equals(z)
这意味着你也应该有 x.Equals(z)
,但您的实现不会这样做。由于您不符合规范,因此您不能指望任何依赖于您的 Equals 方法的算法都能正确运行。
你问的是你能做什么。这完全取决于您需要做什么。部分问题在于,如果它们确实可以出现,那么在极端案例中的意图并不十分清楚。如果一个 Id
在一个或两个列表中多次出现相同的 NullableId
会发生什么?举个简单的例子,如果 new Foo(1, 1)
在第一个列表中出现三次,在第二个列表中出现三次,那么输出中应该是什么?九个项目,每个配对一个?
这是解决您的问题的幼稚尝试。这仅加入 Id
,然后过滤掉任何具有不兼容的 NullableId
的配对。但是当一个 Id
在每个列表中出现多次时,您可能不会想到重复项,如示例输出中所示。
using System;
using System.Linq;
using System.Collections.Generic;
public class Foo
{
public int Id { get; set; }
public int? NullableId { get; set; }
public Foo(int id, int? nullableId)
{
Id = id;
NullableId = nullableId;
}
public override string ToString() => $"Foo({Id}, {NullableId?.ToString()??"null"})";
}
class MainClass {
public static IEnumerable<Foo> JoinFoos(IEnumerable<Foo> first, IEnumerable<Foo> second) {
return first
.Join(second, f=>f.Id, s=>s.Id, (f,s) => new {f,s})
.Where(fs =>
fs.f.NullableId == null ||
fs.s.NullableId == null ||
fs.f.NullableId == fs.s.NullableId)
.Select(fs => new Foo(fs.f.Id, fs.f.NullableId ?? fs.s.NullableId));
}
public static void Main (string[] args) {
var first = new List<Foo> { new Foo(1, null), new Foo(1, null), new Foo(1, 3) };
var second = new List<Foo> { new Foo(1, 1), new Foo(1, 2), new Foo(1, 3), new Foo(1, null) };
foreach (var f in JoinFoos(first, second)) {
Console.WriteLine(f);
}
}
}
输出:
Foo(1, 1)
Foo(1, 2)
Foo(1, 3)
Foo(1, null)
Foo(1, 1)
Foo(1, 2)
Foo(1, 3)
Foo(1, null)
Foo(1, 3)
Foo(1, 3)
如果您有数万个具有相同 Id
的项目,它对您来说也可能太慢了,因为它会在过滤之前构建所有可能的具有匹配 Id
的对他们出来。如果每个列表有 10,000 个带有 Id == 1
的项目,那么就有 100,000,000 对要挑选。
关于c# - 具有可为空字段的 EqualityComparer 的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60696070/