IEnumerable.Except() 在 System.Linq.Enumerable+WhereSelectArrayIterator 与 List<T> 上

IEnumerable.Except() on System.Linq.Enumerable+WhereSelectArrayIterator vs. List<T>

也许我在这里遗漏了细节,但我希望 IEnumerable.Except() 可以在 Enumerables 上工作,而不是具体地转换为集合。

让我用一个简单的例子来解释:我有一个目录中的文件列表,我想排除以某个字符串开头的文件。

var allfiles = Directory.GetFiles(@"C:\test\").Select(f => new FileInfo(f));

同时获得匹配和不匹配的文件将是识别两个集合之一然后在完整列表中进行 .Except() 处理的问题,对吗?

var matching = allfiles.Where(f => f.Name.StartsWith("TOKEN"));

var notmatching = allfiles.Except(matching, new FileComparer());

其中 FileComparer() 是一些 class 比较两个文件的完整路径。

好吧,除非我将这三个集合都转换为一个列表,否则最后一个不匹配的变量仍然会在我对匹配集合执行 .Except() 之后为我提供完整的文件列表.要明确:

var allfiles = Directory.GetFiles(@"C:\test\").Select(f => new FileInfo(f));
var matching = allfiles.Where(f => f.Name.StartsWith("TOKEN"));
var notmatching = allfiles.Except(matching, new FileComparer());

不排除,而

var allfiles = Directory.GetFiles(@"C:\test\").Select(f => new FileInfo(f)).ToList();
var matching = allfiles.Where(f => f.Name.StartsWith("TOKEN")).ToList();
var notmatching = allfiles.Except(matching, new FileComparer()).ToList();

实际上做到了罐头上所说的。 我在这里错过了什么?我不明白为什么 LINQ 不操作当前未转换为列表的集合。

例如,在第一种情况下甚至不会调用 FileComparer。

internal class FileComparer : IEqualityComparer<FileInfo>
{
    public bool Equals(FileInfo x, FileInfo y)
    {
        return x == null ? y == null : (x.Name.Equals(y.Name, StringComparison.OrdinalIgnoreCase) && x.Length == y.Length);
    }

    public int GetHashCode(FileInfo obj)
    {
        return obj.GetHashCode();
    }
}

两者之间的区别在于,如果没有 ToList,延迟的 allfiles 查询将执行两次,从而生成不会通过引用相等性的 FileInfo 的不同实例。

你的 FileComparer implements GetHashCode incorrectly,因为它只是 returns FileInfo 对象的基于引用的哈希码(它本身不会覆盖 GetHashCode ).

Implementations are required to ensure that if the Equals(T, T) method returns true for two objects x and y, then the value returned by the GetHashCode(T) method for x must equal the value returned for y.

解决方案是根据与 Equals:

相同的相等定义来实现 GetHashCode
public int GetHashCode(FileInfo obj)
{
    return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name);
}