-1

I am trying to use Linq to convert a list to Dictionary. I have been able to get proper results with anonymous types as keys, but not with concrete types. Please see code snipped below:

        // Works. Produces 329 entries in dictionary, as expected. 
        var groupByMonth =
            from e in list
            group e by new { e.chk.Month, e.chk.Year } into g
            select new { month = g.Key, rates = g.ToList() };
        var monnthlyRates = groupByMonth.ToList();
        var monnthlyRatesDict = groupByMonth.ToDictionary(t => t.month, t => t.rates);

        // IS NOT WORKING. Produces 30K entries in dictionary; same num as in the list.
        // i.e. the grouping does not happen
        var groupByMonth2 =
            from e in list
            group e by new MonthYear { Month = e.chk.Month, Year = e.chk.Year } into g
            select new MonthYearRates { MonthYear = g.Key, Rates = g.ToList()};
        var monnthlyRatesDict2 = groupByMonth2.ToDictionary(t => t.MonthYear, t => t.Rates);

        // Works. Dictionary has 329 entries
        var groupByMonth3 =
            from e in list
            group e by new DateTime(e.chk.Year, e.chk.Month, 1) into g
            select new MonthYearRates2 { MonthYear = g.Key, Rates = g.ToList() };
        var monnthlyRatesDict3 = groupByMonth3.ToDictionary(t => t.MonthYear, t => t.Rates);

I tried to fix the problem by implementing IComparer and/or IComparable in the concrete type; to no avail

class MonthYear : IComparer
// class MonthYear : IComparable, IComparer
{
    public MonthYear()
    {
    }
    public MonthYear(int month, int year)
    {
        Month = month;
        Year = year;
    }
    int IComparer.Compare(Object x, Object y)
    {
        MonthYear xo = (MonthYear)x;
        MonthYear yo = (MonthYear)y;

        if (yo.Year > xo.Year)
            return 1;
        else if (yo.Year < xo.Year)
            return -1;
        else
        {
            if (yo.Month > xo.Month)
                return 1;
            else if (yo.Month < xo.Month)
                return -1;
            else
                return 0;
        }
    }

    //int IComparable.CompareTo(object obj)
    //{
    //    MonthYear o = (MonthYear)obj;

    //    if (Year > o.Year)
    //        return 1;
    //    else if (Year < o.Year)
    //        return -1;
    //    else
    //    {
    //        if (Month > o.Month)
    //            return 1;
    //        else if (Month < o.Month)
    //            return -1;
    //        else
    //            return 0;
    //    }
    //}
    public int Month;
    public int Year;
}

I understand that Lookup might be a better fit that dictionary; I will lookup Lookup once I am done with anyonymous types.

Amit
  • 1,775
  • 12
  • 20
  • Anonymous types use structural equality which via reflection checks all properties of the type, concrete classes use object/reference equality by default. Create a custom class that implements `IEqualityComparer` type. that is , not IComparer and as a separate class. Ie `class MonthYearEqualityComparer : IEqualityComparer{...} ` and pass that to the `ToDictionary` method as the last parameter – pinkfloydx33 Nov 12 '16 at 09:28
  • Alternatively if you can make MonthYear an immutable `struct` instead, you'll get structural equality without any work on your part (again, done by default using reflection) though this may not be a viable option – pinkfloydx33 Nov 12 '16 at 09:31

1 Answers1

2

Dictionary uses Object.Equals and Object.GetHashCode by default for checking the keys. You don't want IComparable (IComparable is about ordering things, not checking for whether they are equal. Technically, just because CompareTo returns zero, doesn't mean 2 objects are the same). You need to override Object.GetHashCode() and Object.Equals(object otherObject) , which anonymous types do automatically, which is why they work for you here.

Equals should be easy, just make sure the object is the right type, then check the two fields for equality. For GetHashCode, there are some good answers elsewhere on StackOverflow (getting it good can be a bit tricky), like here: What is the best algorithm for an overridden System.Object.GetHashCode?

Community
  • 1
  • 1
  • Precisely. The container objects, the keys, would be distinct objects. They would NOT be object equal; but I want something that is value equal; the objects might be distinct, but the values that they hold are equal. Woudn't overriding .Equal to give out value equivalence, rather than object equivalence, lead to problems elsewhere? – Amit Nov 12 '16 at 07:19
  • And I implemented .Equals; that still did not fix the problem for me – Amit Nov 12 '16 at 07:28
  • The problem is solved when I implemented the functions pointed by you. But I doubt if that is the correct thing to do: replacing object equivalence with object value equivalence. – Amit Nov 12 '16 at 07:48
  • They are NOT object equivalent (you say "new MonthDate"). Even if you say new MonthDate(1,1) and new MonthDate(1,1), those are two distinct objects that are not object equivalent. The solution I proposed is exactly what anonymous types are already doing (creating distinct objects that override Equals and GetHashCode to be value equivalent). if you want object equivalence, you'll need a much more complicated system where you have a pool of "MonthDate" objects that you only ever hand a singleton out per combination. That's almost never worth it. –  Nov 12 '16 at 07:50
  • Right. My doubt is that by overriding Equals did I not make them equal? – Amit Nov 12 '16 at 07:51
  • What "problems elsewhere" are you concerned about? To make them be the same object, you can't use new (and your anonymous type solution has the same problem). You'll have to create a large pool of MonthDates, so that you never duplicate one, and managing their lifetime becomes extremely difficult. Like I said, almost never worth it unless you have a specific problem you are trying to avoid. –  Nov 12 '16 at 07:55
  • 1
    I'm just answering "why are concrete and anonymous types different in this way", which is literally the question you asked. The answer is because anonymous types implement Equals/GetHashCode, and your type does not. If you want a _different_ question answered, ask a different question. –  Nov 12 '16 at 07:56