c# - Why does this LINQ grouping have a count of 3 instead of 2? -
given following classes:
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 { { return datetime.year; } } public int week { { 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); } }
this test fails (unexpectedly):
[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 3 }
and test passes (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)); }
why first test result in count of 3?
the first part of equality check done via hash-code; must provide valid hash-code implementation (for why, see why important override gethashcode when equals method overridden?). comparer this, defers object:
public int gethashcode(weekofyear weekofyear) { return weekofyear.gethashcode(); }
and object does not provide valid hash-code. suitable implementation inside weekofyear
like:
public bool equals(weekofyear other) { return other != null && year == other.year && week == other.week; } public override bool equals(object obj) { return equals(obj weekofyear); } public override int gethashcode() { // exploit number of weeks in year return (year.gethashcode()*52) + week.gethashcode(); }
noting i've provided override
equality too.
actually, since object provides code here, there no benefit in custom comparer; remove weekofyearcomparer
completely, default behaviour suitable equality/comparison operations on underlying type:
var groups = dates.groupby(date => new weekofyear(date));
Comments
Post a Comment