Core源码(七)GroupBy

  如果大数据量的分组,而且是从数据库读取,那么分组尽可能在数据库端进行,因为大数据量的读取传输就会消耗一定量的带宽和时间,而在网页应用中,响应时间越短越好,时间越长,用户体验越糟。而在数据库中分组,读取分组后的统计数据,消耗的传输带宽会明显降低。

这里我们介绍的是C#集合的分组,关于数据库的分组可以参考下文

SQL夯实基础(三):聚合函数详解

GroupBy使用

基本使用如下

List<IGrouping<long,StatisEmployeeLog>> group=list.GroupBy(c => c.DefeatReasonId).ToList();
var groupList =callList.GroupBy(q => new { q.AgentId, q.CallStartTime.Date }).ToList() ;

分组后的查询

var thisGroup = group.FirstOrDefault(c => c.Key == item.DefeatReasonId);
var listThis = thisGroup.ToList();

基本原理

GroupBy在没有传comparer的时候,会创建一个基于当前TSource类型的默认的comparer。

但不管是默认的comparer还是我们自己传的comparer,都会调用Equals和GetHashCode两个方法,所以我们需要重载这两个方法。不论如何,一定要重载GetHashCode。匿名类型会自动生成对应的GetHashCode方法,而我们定义的类,要自己实现。

比如如下

class StudentKey : IEquatable<StudentKey>
{
    public int Age { get; set; }
    public string Class { get; set; }

    public override int GetHashCode()
    {
        return Age.GetHashCode() ^ Class.GetHashCode();
    }
    
    public bool Equals(StudentKey other)
    {
        return Age == other.Age && Class == other.Class;
    }
}

  IGrouping<TKey,TElement>对象序列,请注意,是对象序列,而不是单个对象。由于group查询产生的IGrouping<TKey,TElement>实质上是列表的列表。因此必须使用嵌套的foreach循环来访问每一组的各个子项。外部循环可以访问每个组的Key,内部循环可以访问每个组的子项。每个组的Key可以是任何类型,如字符串、用户自定义的对象或其他

程序通过Enumerable类中如下方法进行分组

public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);

内部调用的逻辑如下

public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) =>

            new GroupedEnumerable<TSource, TKey>(source, keySelector, null);

代码位置为corefxsrcSystem.Linq

GroupedEnumerable类

我们之前使用的tolist方法就是调用的这个类型下的实现

public List<IGrouping<TKey, TSource>> ToList()

{

    IIListProvider<IGrouping<TKey, TSource>> lookup = Lookup<TKey, TSource>.Create(_source, _keySelector, _comparer);

    return lookup.ToList();

}

Lookup类

  Lookup<TKey,TElement>也是一种字典,不过它是一对多,不像Dictionary<TKey,TElement>一样是一对一的。Lookup<int,int>和Dictionary<int,List<int>>是一样的。

  Lookup<TKey,TElement>的对象可以存储ToLookup方法的结果。代码如下

  ToDictionary 和ToLookUp 对对象集合的操作带来极大的方便,特别是对索引的提供。方便通过 key 来找到相应的键值,ToDictionary 转换成是键值对 关系是一 一 对应的关系且key 值是唯一的不能重复。ToLookUp 是ToDictionary 的扩展版本

[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(SystemLinq_LookupDebugView<,>))]
public class Lookup<TKey, TElement> : ILookup<TKey, TElement>, IIListProvider<IGrouping<TKey, TElement>>
{
    private readonly IEqualityComparer<TKey> _comparer;
    private Grouping<TKey, TElement>[] _groupings;
    private Grouping<TKey, TElement> _lastGrouping;
    private int _count;

    internal static Lookup<TKey, TElement> Create<TSource>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer)
    {
        Debug.Assert(source != null);
        Debug.Assert(keySelector != null);
        Debug.Assert(elementSelector != null);

        Lookup<TKey, TElement> lookup = new Lookup<TKey, TElement>(comparer);
        foreach (TSource item in source)
        {
            lookup.GetGrouping(keySelector(item), create: true).Add(elementSelector(item));
        }
    
        return lookup;
    }

    internal Grouping<TKey, TElement> GetGrouping(TKey key, bool create)
    {
        int hashCode = InternalGetHashCode(key);
        for (Grouping<TKey, TElement> g = _groupings[hashCode % _groupings.Length]; g != null; g = g._hashNext)
        {
            if (g._hashCode == hashCode && _comparer.Equals(g._key, key))
            {
                return g;
            }
        }

        if (create)
        {
            if (_count == _groupings.Length)
            {
                Resize();
            }

            int index = hashCode % _groupings.Length;
            Grouping<TKey, TElement> g = new Grouping<TKey, TElement>();
            g._key = key;
            g._hashCode = hashCode;
            g._elements = new TElement[1];
            g._hashNext = _groupings[index];
            _groupings[index] = g;
            if (_lastGrouping == null)
            {
                g._next = g;
            }
            else
            {
                g._next = _lastGrouping._next;
                _lastGrouping._next = g;
            }

            _lastGrouping = g;
            _count++;
            return g;
        }
        return null;
    }

    IGrouping<TKey, TElement>[] IIListProvider<IGrouping<TKey, TElement>>.ToArray()
    {
        IGrouping<TKey, TElement>[] array = new IGrouping<TKey, TElement>[_count];
        int index = 0;
        Grouping<TKey, TElement> g = _lastGrouping;
        if (g != null)
        {
            do
            {
                g = g._next;
                array[index] = g;
                ++index;
            }
            while (g != _lastGrouping);
        }

        return array;
    }
}
Lookup源码

延伸阅读

分组失效

原文地址:https://www.cnblogs.com/qixinbo/p/12703471.html