System.Linq.Enumerable + WhereSelectArrayIterator与List <t>上的IEnumerable.Except()

时间:2019-03-15 09:39:07

标签: c# linq ienumerable except

也许我在这里错过了细节,但是我希望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()是用于比较两个文件的完整路径的类。

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

1 个答案:

答案 0 :(得分:4)

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

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

  

要求执行以确保如果Equals(T, T)方法为两个对象truex返回y,则GetHashCode(T)方法返回的值x的值必须等于y返回的值。

解决方案是根据与GetHashCode相同的相等性定义实施Equals

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