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<MonthYear>
类型的自定义类。也就是说,不是 IComparer 而是作为一个单独的类。即class MonthYearEqualityComparer : IEqualityComparer<MonthYear>...
并将其作为最后一个参数传递给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的方法