如何使用复合键选择器对IEnumerable <t>进行分组?

时间:2016-09-04 09:10:23

标签: c# linq grouping

我正在寻找通过列表项目的IEnumerable属性对列表项目进行分组的最简单方法。 例如,列表包含产品,每个产品都包含升级列表。如果两个产品具有相同的ID,则它们应该被分组,并且它们的升级属性包含相同的ID。我设法使用自定义EqualityComparer来完成它,但我想知道是否有更简单的方法可以做到这一点?

我目前的工作代码:

public class Product
 {
    public int ProductId { get; set; }
    public string Name { get; set; }
    public IEnumerable<Upgrade> Upgrades { get; set; }
 }

 public class Upgrade
 {
    public int Id { get; set; }
    public string Name { get; set; }
 }

 public class ProductsEqualityComparer : IEqualityComparer<Product>
 {      
    public bool Equals(Product x, Product y)
    {
     bool areProductsIdsEqual = x.ProductId == y.ProductId;
     bool areUpgradesEqual = new HashSet<int>(x.Upgrades.Select(u => u.Id)).SetEquals(y.Upgrades.Select(u2 => u2.Id));
     return areProductsIdsEqual && areUpgradesEqual;
    }

    public int GetHashCode(Product obj)
    {
     int hash = obj.ProductId.GetHashCode();
     foreach (var upgrade in obj.Upgrades)
     {
        hash ^= upgrade.Id.GetHashCode();
     }
     return hash;
    }
 }

然后我就这样称呼它:

var grouped = productList.GroupBy(l => l, new ProductsEqualityComparer());

对于那里的所有Linq大师 - 有没有更简单/更好的方法来做到这一点?

1 个答案:

答案 0 :(得分:3)

您自己的解决方案可能是效率最高的解决方案。但是,如果您能负担得起在产品上创建额外的包装并将这些包装器分组,还有另一种方法,例如:

var grouped = productList.Select(p => new {
        GroupId = p.ProductId.ToString() + ";" + String.Join(",", p.Upgrades.Distinct().OrderBy(u => u)),
        Product = p
    }.GroupBy(
        p => p.GroupId, 
        p => p.Product
    );

但请注意,此解决方案会产生计算GroupId字符串并将所有产品包装到中间对象中的成本。为简单起见,上面的示例使用字符串GroupId,如果有必要,可能还有优化空间。