也许我错过了这里的细节,但我希望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();
}
}
两者之间的区别在于,如果没有ToList
,延迟的allfiles
查询将执行两次,产生不会传递引用相等性的FileInfo
的不同实例。
你的FileComparer
implements GetHashCode
incorrectly,因为它只返回FileInfo
对象的基于引用的哈希码(它本身不会覆盖GetHashCode
)。
需要实现以确保如果
Equals(T, T)
方法为两个对象true
和x
返回y
,那么GetHashCode(T)
方法为x
返回的值必须等于为y
返回的值。
解决方案是基于与GetHashCode
相同的相等定义来实现Equals
:
public int GetHashCode(FileInfo obj)
{
return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name);
}