IEqualityComparer не работает должным образом

У меня есть List путей файлов, хранящихся на моем компьютере. Моя цель - сначала отфильтровать файлы с таким же именем и затем отфильтровать те, которые имеют одинаковый размер.
Для этого я создал два класса, реализующих IEqualityComparer<string>, и реализовал методы Equals и GetHashCode.

var query = FilesList.Distinct(new CustomTextComparer())
                     .Distinct(new CustomSizeComparer()); 

Код для обоих классов приведен ниже: -

public class CustomTextComparer : IEqualityComparer<string>
{
    public bool Equals(string x, string y)
    {
        if (Path.GetFileName(x) == Path.GetFileName(y))
        {
            return true;
        }
        return false; 
    }
    public int GetHashCode(string obj)
    {
        return obj.GetHashCode();
    }
}
public class CustomSizeComparer : IEqualityComparer<string>
{
    public bool Equals(string x, string y)
    {
        if (new FileInfo(x).Length == new FileInfo(y).Length)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    public int GetHashCode(string obj)
    {
        return obj.GetHashCode();
    }
}

Но код не работает.

Он не бросает никаких исключений и не имеет ошибки компилятора, но проблема в том, что код не работает (не исключает дублирование файлов).

Итак, как я могу исправить эту проблему? Я могу что-то сделать, чтобы код работал правильно.

Ответ 1

Измените свой GetHashCode для работы с сравниваемым значением. То есть для вашего компаратора размера:

public int GetHashCode(string obj)
{
    return FileInfo(x).Length.GetHashCode();
}

А для другого:

public int GetHashCode(string obj)
{
    return Path.GetFileName(obj).GetHashCode();
}

В соответствии с этим ответом - Какова роль GetHashCode в IEqualityComparer <T> в .NET? сначала оценивается хеш-код. Equals вызывается в случае столкновения.

Очевидно, было бы разумно работать с FileInfo s, а не на строках.

Итак, возможно:

FileList.Select(x => new FileInfo(x))
        .Distinct(new CustomTextComparer())
        .Distinct(new CustomSizeComparer());

Конечно, тогда вам нужно изменить свои компаньоны для работы над правильным типом.

Ответ 2

Ваш GetHashCode должен возвращать одно и то же значение для любых одинаковых значений:

// Try this
public int GetHashCode(string obj)
{
    return Path.GetFileName(x).GetHashCode();
}

// And this
public int GetHashCode(string obj)
{
    return new FileInfo(x).Length.GetHashCode();
}

Но это намного проще для всей задачи без дополнительных классов:

var query = FilesList
                .GroupBy(f => Path.GetFileName(f)).Select(g => g.First())
                .GroupBy(f => new FileInfo(f).Length).Select(g => g.First())
                .ToList();

Ответ 3

Хэш-код используется до вызова Equals. Поскольку ваш код дает разные хеш-коды для элементов, которые равны, вы не получаете желаемого результата. Вместо этого вы должны убедиться, что возвращаемый хэш-код равен, когда элементы равны, например:

public class CustomTextComparer : IEqualityComparer<string>
{
    public bool Equals(string x, string y)
    {
        if (Path.GetFileName(x) == Path.GetFileName(y))
        {
            return true;
        }
        return false; 
    }
    public int GetHashCode(string obj)
    {
        return Path.GetFileName(obj).GetHashCode();
    }
}

Однако, как отметил Петр, это не совсем хороший способ достичь вашей цели, так как вы будете делать много Path.GetFileName и new FileInfo соответственно, что будет значительный успех, особенно потому, что вы имеете дело с файловой системой, которая не точно известна своей скоростью ответа.