C# 为什么my List.Intersect返回的结果与Where(List.Contains)返回的结果不同?

C# 为什么my List.Intersect返回的结果与Where(List.Contains)返回的结果不同?,c#,linq,C#,Linq,我正在进行一个Active Directory同步项目,我正在尝试将AD中的用户列表与存储在数据库中的用户列表进行比较。我已经实现了一个自定义UserPrincipal,我创造性地将其称为NetworkUserPrincipal,以公开我们在登船过程中需要设置的一些属性。NetworkUserPrincipal还实现了IEqualityComparer 我还致力于小样本的单元测试,我试图制定一个过程,跳过所有信息没有改变的人。这是我的测试: // a static list of two use

我正在进行一个Active Directory同步项目,我正在尝试将AD中的用户列表与存储在数据库中的用户列表进行比较。我已经实现了一个自定义UserPrincipal,我创造性地将其称为NetworkUserPrincipal,以公开我们在登船过程中需要设置的一些属性。NetworkUserPrincipal还实现了IEqualityComparer

我还致力于小样本的单元测试,我试图制定一个过程,跳过所有信息没有改变的人。这是我的测试:

// a static list of two users
List<NetworkUserPrincipal> expected = JobTestData.InternalActiveDirectoryData.SynchronizedUsers
// returns only one user.
List<NetworkUserPrincipal> actual = _activeDirectoryUsers.Intersect(_databaseUsers).ToList(); // returns only one user.

Assert.IsTrue(expected.All(_databaseUsers.Contains)); // true
Assert.IsTrue(expected.All(_activeDirectoryUsers.Contains)); // true
Assert.IsTrue(expected.SequenceEqual(actual)); // false
但是,如果我更改_activeDirectoryUsers.Intersect_databaseUsers.ToList;至_activeDirectoryUsers.Where_databaseUsers.Contains.ToList;,我的期末考试通过了

调试单元测试时,我在Equals override函数中放置了一个断点,在Intersect场景中只调用一次

发生了什么事?

SequenceEqual要求两个列表具有相同的内容和顺序。问题很可能是顺序不同。而是使用,它不关心集合的顺序

CollectionAssert.AreEquivalent(expected,actual);
此外,Intersect对结果执行隐式Distinct。如果您有两个相同的项目,则预期.All_databaseUsers.Contains将通过,但这两个列表可能具有不同的计数。

SequenceEqual要求两个列表具有相同的内容和相同的顺序。问题很可能是顺序不同。而是使用,它不关心集合的顺序

CollectionAssert.AreEquivalent(expected,actual);

此外,Intersect对结果执行隐式Distinct。如果您有两个相同的项目,则预期.All_databaseUsers.Contains将通过,但这两个列表的计数可能不同。

您需要正确的GetHashCode实现才能使用Intersect。不知道为什么,但没有它确实会有问题

似乎任何实现,即使是GetHashCode中不正确的实现,都会这样做,只是默认的对象实现不会


编辑:正如@ScottChamberlain在评论中解释的,没有任何不正确的实现可以工作,它确实需要一个正确的实现,其中每个相等的对象都返回一个相等的哈希代码。它不需要高效,只需要正确。

使用Intersect需要正确的GetHashCode实现。不知道为什么,但没有它确实会有问题

似乎任何实现,即使是GetHashCode中不正确的实现,都会这样做,只是默认的对象实现不会


编辑:正如@ScottChamberlain在评论中解释的,没有任何不正确的实现可以工作,它确实需要一个正确的实现,其中每个相等的对象都返回一个相等的哈希代码。它不需要高效,只要正确。

您确定要SequenceEqual吗?JobTestData.InternalActiveDirectoryData.SynchronizedUsers是一个只有一个预期用户的列表吗?除了Equal之外,您是否实现了GetHashCode?我记得Intersect在GetHashCode的默认对象实现中遇到了问题,我没有写答案,因为我不确定为什么会发生这种情况,需要进行全面测试,但请检查并确认。请确保在第一种情况下,Intersect列表为空。任何不正确的GetHashCode实现似乎都能完成任务。即使只是返回0,但没有实现,也就是说,它使用的对象1不起作用。@Jcl-bingo。你完全正确。如果您将其作为答案发布,我很高兴记下我的答案并接受您的答案。@jwiscarson NetworkUserPrincipal还实现了IEqualityComparer,这绝对不会给您任何帮助。请确保它改为实现IEquatable,或者在单独的类中实现IEqualityComparer,并将其作为第三个参数传递给Intersect。是否确实要SequenceEqual?JobTestData.InternalActiveDirectoryData.SynchronizedUsers是一个只有一个预期用户的列表吗?除了Equal之外,您是否实现了GetHashCode?我记得Intersect在GetHashCode的默认对象实现中遇到了问题,我没有写答案,因为我不确定为什么会发生这种情况,需要进行全面测试,但请检查并确认。请确保在第一种情况下,Intersect列表为空。任何不正确的GetHashCode实现似乎都能完成任务。即使只是返回0,但没有实现,也就是说,它使用的对象1不起作用。@Jcl-bingo。你完全正确。如果您将其作为答案发布,我很高兴记下我的答案并接受您的答案。@jwiscarson NetworkUserPrincipal还实现了IEqualityComparer,这绝对不会给您任何帮助。请确保它实现了IEquatable,或者在单独的类中实现IEqualityComparer,并将其作为第三个参数传递给Intersect。谢谢。我也总是实现GetHashCode。我习惯于编写代码来比较字符串并忽略大小写,我只是从来没有考虑过在GetHashCode函数中这样做。它出现问题的原因是Intersect通过创建一个HashSet来工作,实际上是一个集合,但这是一个私有类,然后加载
s第一个列表然后遍历第二个列表,试图找到集合中存在的任何列表。如果找到一个,则将其返回到IEnumerable。该散列集需要GetHashCode才能工作。@ScottChamberlain据我所知,它需要GetHashCode才能有效工作,但它应该适用于任何实现,因为如果对象在同一个散列桶上,它应该调用Equal。所以不知道为什么它不起作用,我在GetHashCode上直接返回0,但它仍然有效,因为Equals返回正确的结果。@jwiscarson经常在将类与字符串进行比较时,我保留一个私有StringComparer s=StringComparer.OrdnalIgnorseCase,然后在Equals中我可以执行s.Equalsx.StringProp,y.StringProp,在GetHashCode中我执行s.GetHashCodex.StringProp。这样,如果我想更改它的比较方式,我只需更改与该类一起创建的比较器。@Jcl如果不实现GetHashCode,默认实现会使用CLR提供给它的对象id使每个人都进入自己的bucket,它不会将每个人都放入同一个bucket。不过这不是OP的问题。谢谢。我也总是实现GetHashCode。我习惯于编写代码来比较字符串和忽略大小写,我只是从来没有考虑过在GetHashCode函数中这样做。它出现问题的原因是Intersect通过创建一个HashSet来工作,实际上是一个集合,但这是一个私有类,然后加载第一个列表,然后通过第二个列表尝试查找集合中存在的任何内容。如果找到一个,则将其返回到IEnumerable。该散列集需要GetHashCode才能工作。@ScottChamberlain据我所知,它需要GetHashCode才能有效工作,但它应该适用于任何实现,因为如果对象在同一个散列桶上,它应该调用Equal。所以不知道为什么它不起作用,我在GetHashCode上直接返回0,但它仍然有效,因为Equals返回正确的结果。@jwiscarson经常在将类与字符串进行比较时,我保留一个私有StringComparer s=StringComparer.OrdnalIgnorseCase,然后在Equals中我可以执行s.Equalsx.StringProp,y.StringProp,在GetHashCode中我执行s.GetHashCodex.StringProp。这样,如果我想更改它的比较方式,我只需更改与该类一起创建的比较器。@Jcl如果不实现GetHashCode,默认实现会使用CLR提供给它的对象id使每个人都进入自己的bucket,它不会将每个人都放入同一个bucket。然而,这不是OP的问题。你的回答实际上触及了为什么我最初使用如此小的一组数据。事实上,我已经忘记了收藏家,所以我也很感激你的回答。我使用SequenceEquals是因为我正在将数据从数据库中排序,但是我越是考虑你的答案,我就越意识到在单元测试中使用它是一个很糟糕的理由。你的答案实际上说明了我最初为什么使用这么小的一组数据。事实上,我已经忘记了收藏家,所以我也很感激你的回答。我使用SequenceEquals是因为我正在将数据从数据库中排序,但是我越是考虑你的答案,我就越意识到在单元测试中使用它是一个很糟糕的理由。