You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否基于[Key]特性动态分组任意类的IEnumerable集合?

基于[Key]特性的动态LINQ分组实现方案

当然可以实现!针对你给出的这些带有[Key]特性的类,我们可以通过反射+LINQ的组合来完成动态分组,核心就是先识别每个类的主键属性,再基于这些属性的值构建分组键,最后用GroupBy完成分组。

先把你给出的类定义贴出来方便参考:

public class Potato 
{ 
    [Key] public string Farm { get; set; } 
    [Key] public int Size { get; set; } 
    public string Trademark { get; set; } 
}

public class Haybell 
{ 
    [Key] public string Color { get; set; } 
    public int StrawCount { get; set; } 
}

public class Frog 
{ 
    [Key] public bool IsAlive { get; set; } 
    [Key] public bool IsVirulent { get; set; } 
    public byte LimbCount { get; set; } = 4; 
    public ConsoleColor Color { get; set; } 
}

实现步骤与代码

1. 编写工具方法识别主键属性

首先我们需要一个通用方法,用来获取任意类型上标记了[Key]特性的属性。这里要注意复合主键的顺序,我们按属性在类中的定义顺序来排序,避免因为顺序问题导致分组键不一致:

using System.ComponentModel.DataAnnotations;
using System.Reflection;

public static class KeyPropertyHelper
{
    public static IEnumerable<PropertyInfo> GetKeyProperties<T>()
    {
        return typeof(T)
            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.GetCustomAttribute<KeyAttribute>() != null)
            .OrderBy(p => p.MetadataToken); // 按类中定义的顺序排序
    }
}

2. 编写通用的LINQ分组扩展方法

接下来我们写一个泛型扩展方法,对任意IEnumerable<T>集合,基于其主键属性进行分组。这里我们用不同的方式处理不同数量的主键,保证分组键能正确进行相等性比较:

public static class EnumerableGroupingExtensions
{
    public static IEnumerable<IGrouping<object, T>> GroupByKey<T>(this IEnumerable<T> source)
    {
        if (source == null)
            throw new ArgumentNullException(nameof(source));

        var keyProperties = KeyPropertyHelper.GetKeyProperties<T>().ToList();
        if (!keyProperties.Any())
            throw new InvalidOperationException($"类型 {typeof(T).Name} 没有标记[Key]特性的属性");

        // 为每个元素构建分组键
        return source.GroupBy(item =>
        {
            var keyValues = keyProperties.Select(p => p.GetValue(item)).ToArray();
            // 根据主键数量选择合适的键类型,保证相等性判断正确
            return keyValues.Length switch
            {
                1 => keyValues[0],
                2 => Tuple.Create(keyValues[0], keyValues[1]),
                3 => Tuple.Create(keyValues[0], keyValues[1], keyValues[2]),
                // 更多主键的情况用自定义不可变类型处理
                _ => new CompositeKey(keyValues)
            };
        });
    }

    // 自定义复合键类型,处理多主键场景,实现正确的相等性和哈希码逻辑
    private class CompositeKey : IEquatable<CompositeKey>
    {
        private readonly object[] _values;

        public CompositeKey(object[] values)
        {
            _values = values ?? throw new ArgumentNullException(nameof(values));
        }

        public bool Equals(CompositeKey? 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 CompositeKey);

        public override int GetHashCode()
        {
            int hash = 17;
            foreach (var value in _values)
            {
                hash = hash * 31 + (value?.GetHashCode() ?? 0);
            }
            return hash;
        }
    }
}

3. 测试示例

现在我们可以用你的类来测试这个分组方法:

// 测试Potato分组
var potatoes = new List<Potato>
{
    new Potato { Farm = "FarmA", Size = 10, Trademark = "SpudCo" },
    new Potato { Farm = "FarmA", Size = 10, Trademark = "RootFarm" },
    new Potato { Farm = "FarmB", Size = 15, Trademark = "SpudCo" }
};

var potatoGroups = potatoes.GroupByKey();
foreach (var group in potatoGroups)
{
    Console.WriteLine($"分组键: {group.Key},包含{group.Count()}个土豆");
    foreach (var potato in group)
    {
        Console.WriteLine($"  商标: {potato.Trademark}");
    }
}

// 测试Frog分组
var frogs = new List<Frog>
{
    new Frog { IsAlive = true, IsVirulent = false, Color = ConsoleColor.Green },
    new Frog { IsAlive = true, IsVirulent = false, Color = ConsoleColor.Brown },
    new Frog { IsAlive = false, IsVirulent = true, Color = ConsoleColor.Green }
};

var frogGroups = frogs.GroupByKey();
foreach (var group in frogGroups)
{
    Console.WriteLine($"分组键: {group.Key},包含{group.Count()}只青蛙");
    foreach (var frog in group)
    {
        Console.WriteLine($"  颜色: {frog.Color}");
    }
}

关键细节说明

  • 主键顺序稳定性:通过MetadataToken排序属性,确保复合主键的顺序和类定义一致,避免反射返回属性顺序不确定导致的分组错误。
  • 分组键可靠性:针对不同数量的主键选择合适的键类型,保证分组键能正确进行相等性比较——这是GroupBy方法正常工作的核心。
  • 通用性:这个扩展方法适用于任何带有[Key]特性的类,不需要为每个类单独编写分组逻辑,完全满足你“任意上述类”的需求。

内容的提问来源于stack exchange,提问作者Nick Farsi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 22:52:27