隐藏

怎样在C#中list实现分组group方法

发布:2022/12/20 14:35:13作者:管理员 来源:本站 浏览次数:699



在C#/.NET应用程序编程开发中,如何对一个泛型集合进行LINQ动态分组(GroupBy)?


当前有一个泛型集合,一般情况下,使用LINQ进行分组都是按固定属性名称,比如有一个Customer集合,现要按固定的属性Grade进行分组统计数量,如下:


using System;

using System.Collections.Generic;

using System.Linq;


namespace ConsoleApp1

{

   public class Customer

   {

       public int Id { get; set; }

       public string FirstName { get; set; }

       public string LastName { get; set; }

       public int Grade { get; set; }

   }

   class Program

   {

       static void Main(string[] args)

       {

           var customers = new List<Customer>

           {

               new Customer{ Id = 1, FirstName="Rector" ,LastName = "Liu",Grade = 1},

               new Customer{ Id = 2, FirstName="James" ,LastName = "Liu",Grade = 2},

               new Customer{ Id = 3, FirstName="Stephen" ,LastName = "Liu",Grade = 3},

               new Customer{ Id = 4, FirstName="Loin" ,LastName = "Liu",Grade = 2},

               new Customer{ Id = 5, FirstName="Stephen" ,LastName = "Curry",Grade = 1},

           };

           var groupByFirstName = customers.GroupBy(x=>x.Grade)

               .Select(x=>new {

                   x.Key,

                   Count = x.Count()

               }).ToList();

           groupByFirstName.ForEach(x => Console.WriteLine("Key:{0},Count:{1}",x.Key, x.Count));

           Console.ReadLine();

       }

   }

}


输出结果:


Key:1,Count:2

Key:2,Count:2

Key:3,Count:1



那么,如果要在程序中依据不同情况动态对集合进行分组和统计,应该如何实现呢?

方案一


创建一个获取属性的方法,如下:


private static object GetPropertyValue(object obj, string propertyName)

{

   return obj.GetType().GetProperty(propertyName).GetValue(obj, null);

}



using System;

using System.Collections.Generic;

using System.Linq;


namespace ConsoleApp1

{

   public class Customer

   {

       public int Id { get; set; }

       public string FirstName { get; set; }

       public string LastName { get; set; }

       public int Grade { get; set; }

   }

   class Program

   {

       static void Main(string[] args)

       {

           var customers = new List<Customer>

           {

               new Customer{ Id = 1, FirstName="Rector" ,LastName = "Liu",Grade = 1},

               new Customer{ Id = 2, FirstName="James" ,LastName = "Liu",Grade = 2},

               new Customer{ Id = 3, FirstName="Stephen" ,LastName = "Liu",Grade = 3},

               new Customer{ Id = 4, FirstName="Loin" ,LastName = "Liu",Grade = 2},

               new Customer{ Id = 5, FirstName="Stephen" ,LastName = "Curry",Grade = 1},

           };

           var groupByFirstName = customers.GroupBy(x=> GetPropertyValue(x,"LastName"))

               .Select(x=>new {

                   x.Key,

                   Count = x.Count()

               }).ToList();

           groupByFirstName.ForEach(x => Console.WriteLine("Key:{0},Count:{1}",x.Key, x.Count));

           Console.ReadLine();

       }


       private static object GetPropertyValue(object obj, string propertyName)

       {

           return obj.GetType().GetProperty(propertyName).GetValue(obj, null);

       }

   }

}


输出结果:


Key:Liu,Count:4

Key:Curry,Count:1



方案二


创建一个基于表达式树的静态方法,如下:


private static Expression<Func<Menu, string>> GetGroupKey(string property)

{

   var parameter = Expression.Parameter(typeof(Menu));

   var body = Expression.Property(parameter, property);

   return Expression.Lambda<Func<Menu, string>>(body, parameter);

}


调用示例:


customers.GroupBy(GetGroupKey("LastName").Compile())


完整示例:


using System;

using System.Collections.Generic;

using System.Linq;

using System.Linq.Expressions;


namespace ConsoleApp1

{

   public class Customer

   {

       public int Id { get; set; }

       public string FirstName { get; set; }

       public string LastName { get; set; }

       public int Grade { get; set; }

   }

   class Program

   {

       static void Main(string[] args)

       {

           var customers = new List<Customer>

           {

               new Customer{ Id = 1, FirstName="Rector" ,LastName = "Liu",Grade = 1},

               new Customer{ Id = 2, FirstName="James" ,LastName = "Liu",Grade = 2},

               new Customer{ Id = 3, FirstName="Stephen" ,LastName = "Liu",Grade = 3},

               new Customer{ Id = 4, FirstName="Loin" ,LastName = "Liu",Grade = 2},

               new Customer{ Id = 5, FirstName="Stephen" ,LastName = "Curry",Grade = 1},

           };

           var groupByFirstName = customers.GroupBy(GetGroupKey("LastName").Compile())

               .Select(x=>new {

                   x.Key,

                   Count = x.Count()

               }).ToList();

           groupByFirstName.ForEach(x => Console.WriteLine("Key:{0},Count:{1}",x.Key, x.Count));

           Console.ReadLine();

       }


       private static Expression<Func<Customer, string>> GetGroupKey(string property)

       {

           var parameter = Expression.Parameter(typeof(Customer));

           var body = Expression.Property(parameter, property);

           return Expression.Lambda<Func<Customer, string>>(body, parameter);

       }

   }

}




方案三


创建一个支持LINQ动态分组的静态扩展方法,如下:


public static IEnumerable<IGrouping<object, TElement>> GroupByMany<TElement>(this IEnumerable<TElement> elements, params string[] groupSelectors)

{

   var selectors = new List<Func<TElement, object>>(groupSelectors.Length);

   selectors.AddRange(groupSelectors.Select(selector => DynamicExpression.ParseLambda(typeof (TElement), typeof (object), selector)).Select(l => (Func<TElement, object>) l.Compile()));

   return elements.GroupByMany(selectors.ToArray());

}


public static IEnumerable<IGrouping<object, TElement>> GroupByMany<TElement>(this IEnumerable<TElement> elements, params Func<TElement, object>[] groupSelectors)

{

   if (groupSelectors.Length > 0)

   {

       Func<TElement, object> selector = groupSelectors.First();

       return elements.GroupBy(selector);

   }

   return null;

}


 


 


 


static class LinqExt

{

public class DGroupBy<T> : IGrouping<object[], T>

{

private List<T> _innerlist = new List<T>();


private object[] _key;


public DGroupBy(object[] key) { _key = key; }


public object[] Key

{

get { return _key; }

}


public void Add(T value)

{

_innerlist.Add(value);

}


public IEnumerator<T> GetEnumerator()

{

return this._innerlist.GetEnumerator();

}


System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

{

return this._innerlist.GetEnumerator();

}

}


public static IEnumerable<IGrouping<object[], T>> DynamicGroupBy<T>(this IEnumerable<T> data, string[] keys)

{

List<DGroupBy<T>> list = new List<DGroupBy<T>>();

foreach (var item in data.Select(x => new {

k = keys.Select(y => x.GetType().GetProperty(y).GetValue(x, null)).ToArray(),

v = x

}))

{

DGroupBy<T> existing = list.SingleOrDefault(x => x.Key.Zip(item.k, (a, b) => a.Equals(b)).All(y => y));

if (existing == null)

{

existing = new DGroupBy<T>(item.k);

list.Add(existing);

}

existing.Add(item.v);

}

return list;

}

}