Использование HashSet<> с объектами класса DirectoryInfo C# .NET

Использование HashSet<> с объектами класса DirectoryInfo C# .NET


На вход поступает некий список файлов List<> files. Необходимо вернуть список всех директорий List<>, которые содержат файлы с расширением .mp3 и .wav. Каждую директорию нужно вернуть только один раз, порядок значения не имеет.

Сразу подумал, что HashSet<> здесь прекрасно подойдет для отсечения дубликатов, после просто конвертировать ToList и готово.

public static List<DirectoryInfo> GetAlbums(List<FileInfo> files)
{
    HashSet<DirectoryInfo> albums = new HashSet<DirectoryInfo>();
    foreach (var file in files)
       if (file.Extension == ".mp3" || file.Extension == ".wav")
          albums.Add(file.Directory);
    return albums.ToList;
}

Но повторяющиеся объекты все равно добавляются. Насколько я понял, это потому, что они типа DirectoryInfo, и они не переопределяют метод hashCode() который использует HashSet<> для сортировки хэшем, и соответственно проверки на повторяющиеся объекты.

Конвертировать к string, чтобы дубликаты отсеивались и потом перебирать весь HashSet string, добавляя в List DirectoryInfo, как-то слишком примитивно и затратно.

          albums.Add(file.Directory.ToString());
foreach (var album in albums)
    list.Add(new DirectoryInfo(album));
return list;

Если добавлять сразу в List DirectoryInfo и для отсечения повторов добавить в if дополнительное условие:

(!albums.Contains(file.Directory)

Так тоже не работает, if пропускает дубликаты, потому что Метод Contains() со ссылочными типами работает по другому (как я понял, может и не из-за этого).

В общем, объясните, почему происходят такие ситуации? Как их можно исправить, работая именно с HashSet<> или List<>, не прибегая к описанным мной действиям и LINQ?




Чтобы HashSet<T> работал, тип данных должен реализовать интерфейс IEquitable<T>, то есть должно быть возможность сравнения. DirectoryInfo не реализует, а значит сравнение происходит только ссылке как у обычного объекта.

Чтобы это обойти, нужно создать свой компарер, например сравнивающий пути к каталогам.

public class DirectoryInfoComparer : IEqualityComparer<DirectoryInfo>
{
    public bool Equals(DirectoryInfo left, DirectoryInfo right)
    {
        return left.FullName == right.FullName;
    }

    public int GetHashCode(DirectoryInfo di)
    {
        return di.FullName.GetHashCode();
    }
}

И передать его в конструктор хэшсета

DirectoryInfoComparer comparer = new();
HashSet<DirectoryInfo> albums = new(comparer);

Теперь будет работать как надо.


Report Page