也许我在这里错过了细节,但是我希望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();
}
}
答案 0 :(得分:4)
两者之间的区别在于,如果没有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);
}