C#基于计算值分组失效:分组结果不符合预期
分组结果不符合预期的原因及解决办法
问题根源
你用数组作为GroupBy的分组键是导致结果不符合预期的核心原因。数组属于引用类型,GroupBy默认使用引用相等性判断键是否相同——哪怕两个数组的元素完全一致,只要它们是不同的数组实例(内存地址不同),就会被判定为不同的分组键。
看你的数据:两个DateTime(2023, 7, 19, 7, 0, 0)生成的分组键数组内容完全一样,但因为是两次ToArray()创建的不同数组实例,所以被分成了两个独立分组,最终总分组数变成4个,而非预期的3个。
解决方案
方案1:使用值元组作为分组键(推荐)
值元组是值类型,会自动按元素内容比较相等性,代码简洁且性能较好:
query.GroupBy(datapoint => { var timeDimensionValue = (DateTime)datapoint.Dimensions[TimeDimension]; var keyValues = groupingFunctions.Select(func => func(timeDimensionValue)).ToList(); // 按分组维度的数量定义元组,示例为2个维度的情况 return (DayOfWeek: keyValues[0], DateComponent: keyValues[1]); });
如果分组维度数量不固定,可以用ValueTuple.Create动态创建元组。
方案2:使用匿名类型作为分组键
匿名类型在C#中会自动重写Equals和GetHashCode方法,按属性内容比较相等性,适合维度数量固定的场景:
query.GroupBy(datapoint => { var timeDimensionValue = (DateTime)datapoint.Dimensions[TimeDimension]; var keyValues = groupingFunctions.Select(func => func(timeDimensionValue)).ToList(); return new { Day = keyValues[0], Date = keyValues[1] }; });
方案3:自定义实现相等性判断的键类
如果分组维度数量不固定,或者需要更灵活的逻辑,可以自定义一个实现IEquatable<T>的类,手动实现内容相等性判断:
public class GroupKey : IEquatable<GroupKey> { public object[] Values { get; } public GroupKey(object[] values) { Values = values ?? throw new ArgumentNullException(nameof(values)); } public bool Equals(GroupKey other) { if (other == null || Values.Length != other.Values.Length) return false; for (int i = 0; i < Values.Length; i++) { if (!Equals(Values[i], other.Values[i])) return false; } return true; } public override bool Equals(object obj) => Equals(obj as GroupKey); public override int GetHashCode() { int hash = 17; foreach (var value in Values) { hash = hash * 31 + (value?.GetHashCode() ?? 0); } return hash; } }
然后在GroupBy中使用这个类:
query.GroupBy(datapoint => { var timeDimensionValue = (DateTime)datapoint.Dimensions[TimeDimension]; var keyValues = groupingFunctions.Select(func => func(timeDimensionValue)).ToArray(); return new GroupKey(keyValues); });
内容的提问来源于stack exchange,提问作者Steve Andrews
相关产品推荐
相关产品推荐

