2012-03-09 8 views
7

Przy następujących klas:Dlaczego ta grupa LINQ ma liczbę 3 zamiast 2?

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); 
    } 
} 

Ten test nie powiedzie się (niespodziewanie):

[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 
} 

i to przechodzi testy (expectedly):

[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)); 
} 

Dlaczego pierwszy wynik testu w liczba 3?

Odpowiedz

10

Pierwsza część kontroli równości odbywa się za pomocą kodu hash; ty musi podać poprawną implementację kodu hash (dlaczego, zobacz Why is it important to override GetHashCode when Equals method is overridden?). Twoja porównywarka może to zrobić, ale odracza do obiektu:

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

a obiekt nie podać prawidłowy kod hash. Odpowiednia realizacja wewnątrz WeekOfYear byłoby coś jak:

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(); 
} 

Stwierdzając Ja również dostarczył override równości zbyt.

W rzeczywistości, ponieważ obiekt udostępnia tutaj cały kod, nie ma żadnej korzyści w niestandardowym porównywalniku; można usunąć WeekOfYearComparer całkowicie, jako domyślne zachowanie jest poszukiwanie odpowiednich operacji równość/porównawczych typu bazowego:

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

Dzięki, Marc. Myślałem, że to może mieć coś wspólnego z kodem mieszania, ale nie był pewien. To zdecydowanie rozwiązało problem. Twoje zdrowie. –