IEnumerable.Except () для System.Linq.Enumerable + WhereSelectArrayIterator против List <T> - PullRequest
1 голос
/ 15 марта 2019

Может быть, я упускаю детали здесь, но я ожидаю, что 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 () - это некоторый класс, который сравнивает полный путь двух файлов.

Что ж, , если я не приведу обе из трех коллекций к списку, последняя переменная не совпадает с полным списком файлов после 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();
    }
}

1 Ответ

4 голосов
/ 15 марта 2019

Разница между ними заключается в том, что без ToList отложенный запрос allfiles выполняется дважды, создавая различные экземпляры FileInfo, которые не будут проходить через равенство ссылок.

Ваш FileComparer неправильно реализует GetHashCode , поскольку он просто возвращает основанный на ссылках хэш-код объектов 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);
}
...