Linq ToDictionary 匿名与具体类型 [重复]

Posted

技术标签:

【中文标题】Linq ToDictionary 匿名与具体类型 [重复]【英文标题】:Linq ToDictionary anonymous versus concrete types [duplicate] 【发布时间】:2017-03-26 09:56:23 【问题描述】:

我正在尝试使用 Linq 将列表转换为字典。我已经能够使用匿名类型作为键获得正确的结果,但不能使用具体类型。请参阅下面的代码:

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

我尝试通过在具体类型中实现 IComparer 和/或 IComparable 来解决问题;无济于事

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;

我了解 Lookup 可能更适合该词典;完成任何匿名类型后,我将查找 Lookup。

【问题讨论】:

匿名类型使用结构相等,通过反射检查类型的所有属性,具体类默认使用对象/引用相等。创建一个实现IEqualityComparer&lt;MonthYear&gt; 类型的自定义类。也就是说,不是 IComparer 而是作为一个单独的类。即class MonthYearEqualityComparer : IEqualityComparer&lt;MonthYear&gt;... 并将其作为最后一个参数传递给ToDictionary 方法 或者,如果您可以将 MonthYear 设置为不可变的 struct,则无需您进行任何工作(同样,默认情况下使用反射完成),您将获得结构平等,尽管这可能不是一个可行的选择 【参考方案1】:

Dictionary 默认使用 Object.Equals 和 Object.GetHashCode 来检查键。您不想要 IComparable(IComparable 是关于 ordering 事物,而不是检查它们是否相等。从技术上讲,仅仅因为 CompareTo 返回零,并不意味着 2 个对象是相同的)。您需要覆盖 Object.GetHashCode()Object.Equals(object otherObject) ,这些匿名类型会自动执行,这就是它们在这里为您工作的原因。

相等应该很容易,只要确保对象是正确的类型,然后检查两个字段是否相等。对于 GetHashCode,在 *** 的其他地方有一些很好的答案(让它变得好可能有点棘手),比如这里:What is the best algorithm for an overridden System.Object.GetHashCode?

【讨论】:

没错。容器对象,即键,将是不同的对象。它们不会是对象相等的;但我想要价值相等的东西;对象可能是不同的,但它们持有的值是相等的。不会覆盖 .Equal 来给出值等价,而不是对象等价,会导致其他地方出现问题吗? 我实现了 .Equals;仍然没有为我解决问题 我实现了你所指的功能,问题就解决了。但我怀疑这是否是正确的做法:用对象值等价替换对象等价。 它们不是对象等价的(你说“新的 MonthDate”)。即使您说 new MonthDate(1,1) 和 new MonthDate(1,1),它们也是两个不同的对象,它们不是对象等价的。我提出的解决方案正是匿名类型已经在做的事情(创建覆盖 Equals 和 GetHashCode 的不同对象以使其值等价)。如果您想要对象等价,您将需要一个更复杂的系统,其中您有一个“MonthDate”对象池,每个组合只分发一个单例。这几乎不值得。 我只是在回答“为什么具体类型和匿名类型以这种方式不同”,这实际上就是您提出的问题。答案是因为匿名类型实现了 Equals/GetHashCode,而您的类型没有。如果您想回答不同的问题,请提出不同的问题。

以上是关于Linq ToDictionary 匿名与具体类型 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

C# - 避免 LINQ ToDictionary 中的空指针

linQ 结果 匿名类型 用作 binding source的方法

Linq专题之提高编码效率—— 第二篇 神一样的匿名类型

具有匿名类型和用户定义类型的 LINQ 选择查询

Enumerable.ToDictionary 是不是只检索它需要的内容?

如何使用 Lambda 或 Linq 将匿名类型转换为原始类型成员