C# list group分組擴充套件,方法來源網路記錄備忘
在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;
}
}