Почему эта группа LINQ имеет счет 3 вместо 2? - PullRequest
7 голосов
/ 09 марта 2012

Даны следующие классы:

public class WeekOfYear : IEquatable<WeekOfYear>, IComparable<WeekOfYear>
{
    private readonly DateTime dateTime;
    private readonly DayOfWeek firstDayOfWeek;

    public WeekOfYear(DateTime dateTime)
        : this(dateTime, DayOfWeek.Sunday)
    {
    }

    public WeekOfYear(DateTime dateTime, DayOfWeek firstDayOfWeek)
    {
        this.dateTime = dateTime;
        this.firstDayOfWeek = firstDayOfWeek;
    }

    public int Year
    {
        get
        {
            return dateTime.Year;
        }
    }

    public int Week
    {
        get
        {
            return CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(dateTime, CalendarWeekRule.FirstDay, firstDayOfWeek);
        }
    }

    public bool Equals(WeekOfYear other)
    {
        return Year == other.Year && Week == other.Week;
    }

    public int CompareTo(WeekOfYear other)
    {
        if (Year > other.Year || Year == other.Year && Week > other.Week)
        {
            return 1;
        }
        if (Equals(other))
        {
            return 0;
        }
        return -1;
    }

    public override string ToString()
    {
        return String.Format("Week of {0}", dateTime.FirstDayOfWeek(firstDayOfWeek).ToString("MMMM dd, yyyy"));
    }
}

public class WeekOfYearComparer : IEqualityComparer<WeekOfYear>, IComparer<WeekOfYear>
{
    public bool Equals(WeekOfYear x, WeekOfYear y)
    {
        return x.Equals(y);
    }

    public int GetHashCode(WeekOfYear weekOfYear)
    {
        return weekOfYear.GetHashCode();
    }

    public int Compare(WeekOfYear x, WeekOfYear y)
    {
        return x.CompareTo(y);
    }
}

Этот тест не пройден (неожиданно):

[Test]
public void Fails()
{
    var dates = new List<DateTime>
                    {
                        new DateTime(2012, 1, 1),
                        new DateTime(2012, 2, 1),
                        new DateTime(2012, 1, 1)
                    };

    IEnumerable<IGrouping<WeekOfYear, DateTime>> groups = dates.GroupBy(date => new WeekOfYear(date), new WeekOfYearComparer());

    Assert.That(groups.Count(), Is.EqualTo(2)); // count is 3
}

И этот тест проходит (ожидаемо):

[Test]
public void Works()
{
    var dates = new List<DateTime>
                    {
                        new DateTime(2012, 1, 1),
                        new DateTime(2012, 2, 1),
                        new DateTime(2012, 1, 1)
                    };

    var groups = dates.GroupBy(
        date =>
            {
                var weekOfYear = new WeekOfYear(date);
                return new { weekOfYear.Year, weekOfYear.Week };
            });

    Assert.That(groups.Count(), Is.EqualTo(2));
}

Почему первый тест дает результат 3?

1 Ответ

10 голосов
/ 09 марта 2012

Первая часть проверки на равенство выполняется через хеш-код;Вы должны предоставить правильную реализацию хеш-кода (почему см. Почему важно переопределить GetHashCode, если переопределен метод Equals? ).Ваш компаратор мог бы сделать это, но он ссылается на объект:

public int GetHashCode(WeekOfYear weekOfYear)
{
    return weekOfYear.GetHashCode();
}

, а объект не предоставляет действительный хэш-код.Подходящая реализация внутри WeekOfYear будет выглядеть примерно так:

public bool Equals(WeekOfYear other)
{
    return other != null && Year == other.Year && Week == other.Week;
}
public override bool Equals(object obj)
{
    return Equals(obj as WeekOfYear);
}
public override int GetHashCode()
{ // exploit number of weeks in year
    return (Year.GetHashCode()*52) + Week.GetHashCode();
}

Отметив, что я также предоставил override для равенства.

На самом деле, поскольку ваш объект предоставляет весь кодздесь нет никакого преимущества в пользовательском компараторе;Вы можете полностью удалить WeekOfYearComparer, так как поведение по умолчанию - поиск подходящих операций равенства / сравнения для базового типа:

var groups = dates.GroupBy(date => new WeekOfYear(date));
...