Использование 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);
Теперь будет работать как надо.