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

问题描述 投票:1回答:1

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

好吧,除非我将三个集合都转换为List,否则最后一个不匹配的变量仍然会在匹配集合上的.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# linq ienumerable except
1个回答
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);
}
© www.soinside.com 2019 - 2024. All rights reserved.