首页 > 解决方案 > System.Linq.Enumerable+WhereSelectArrayIterator 与 List 上的 IEnumerable.Except()

问题描述

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

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

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

获得匹配和不匹配的文件将是识别两个集合之一,然后在完整列表中添加 .Except()-ing 的问题,对吧?

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

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

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

好吧,除非我将这三个集合都转换为 List,否则最后一个不匹配的变量仍然会在匹配集合上的 I .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();
    }
}

标签: c#linqienumerableexcept

解决方案


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

您的FileComparer 实现GetHashCode不正确,因为它只是返回对象的基于引用的哈希码FileInfo(它本身不会覆盖GetHashCode)。

实现需要确保如果该Equals(T, T)方法返回true两个对象xy,则该方法返回的值GetHashCode(T)必须x等于返回的值y

解决方案是GetHashCode基于与以下相同的相等定义来实现Equals

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

推荐阅读