C#LINQ,通过[Key]属性进行动态分组

问题描述

请考虑以下课程:

this.choosenEmployee

每个类的属性都具有[Key]属性。是否可以通过它们各自的[Key]属性将这些类的IEnumerable动态分组?

解决方法

我会为每种类型添加扩展方法,例如

选项1:

static class Extensions 
{
    public static IEnumerable<IGrouping<Tuple<string,int>,Potato>>
       GroupByPrimaryKey(this IEnumerable<Potato> e)
    {
        return e.GroupBy(p => Tuple.Create(p.Farm,p.Size));
    }

    public static IEnumerable<IGrouping<Tuple<bool,bool>,Frog>>
       GroupByPrimaryKey(this IEnumerable<Frog> e)
    {
        return e.GroupBy(p => Tuple.Create(p.IsAlive,p.IsVirulent));
    }
}

如果类型很多,则可以使用t4生成代码。

用法:.GroupByPrimaryKey()

选项2:

一个更简单的变体:

static class Extensions 
{
    public static Tuple<string,int> GetPrimaryKey(this Potato p)
    {
        return Tuple.Create(p.Farm,p.Size);
    }
    public static Tuple<bool,bool> GetPrimaryKey(this Frog p)
    {
        return Tuple.Create(p.IsAlive,p.IsVirulent);
    }

}

用法:.GroupBy(p => p.GetPrimaryKey())

选项3:

有反射的解决方案是可能的,但是会很慢。草图(远未投入生产!)

class CombinedKey : IEquatable<CombinedKey>
{
    object[] _keys;
    CombinedKey(object[] keys)
    {
        _keys = keys;
    }
    
    public bool Equals(CombinedKey other)
    {
        return _keys.SequenceEqual(other._keys);
    }
    
    public override bool Equals(object obj)
    {
        return obj is CombinedKey && Equals((CombinedKey)obj);
    }
    
    public override int GetHashCode()
    {
        return 0;
    }

    public static CombinedKey GetKey<T>(T instance)
    {
        return new CombinedKey(GetKeyAttributes(typeof(T)).Select(p => p.GetValue(instance,null)).ToArray());
    }

    private static PropertyInfo[] GetKeyAttributes(Type type)
    {
        // you definitely want to cache this
        return type.GetProperties()
            .Where(p => Attribute.GetCustomAttribute(p,typeof(KeyAttribute)) != null)
            .ToArray();
    }
}   

用法:GroupBy(p => CombinedKey.GetKey(p))

,

这里的挑战是,您需要构建一个匿名类型才能拥有一个GroupBy表达式,该表达式可以转换为SQL或任何其他LINQ提供程序。

我不确定您可以使用反射来做到这一点(不是没有一些真正复杂的代码来在运行时创建匿名类型)。但是,如果您愿意提供匿名类型的示例作为种子,则可以创建分组表达式。

public static Expression<Func<TSource,TAnon>> GetAnonymous<TSource,TAnon>(TSource dummy,TAnon example)
{
  var ctor = typeof(TAnon).GetConstructors().First();
  var paramExpr = Expression.Parameter(typeof(TSource));
  return Expression.Lambda<Func<TSource,TAnon>>
  (
      Expression.New
      (
          ctor,ctor.GetParameters().Select
          (
              (x,i) => Expression.Convert
              (
                  Expression.Property(paramExpr,x.Name),// fetch same named property
                  x.ParameterType
              )
          )
      ),paramExpr);
}

这是您将如何使用它(注意:传递给该方法的伪匿名类型是为了使该匿名类型成为编译时类型,该方法不在乎您传入的值是什么为此。):

static void Main()
{
    
    var groupByExpression = GetAnonymous(new Frog(),new {IsAlive = true,IsVirulent = true});
    
    Console.WriteLine(groupByExpression);
    
    var frogs = new []{ new Frog{ IsAlive = true,IsVirulent = false},new Frog{ IsAlive = false,IsVirulent = true},new Frog{ IsAlive = true,IsVirulent = true}};
    
    var grouped = frogs.AsQueryable().GroupBy(groupByExpression);
    
    foreach (var group in grouped)
    {
       Console.WriteLine(group.Key);    
    }
    
}   

哪个会产生:

Param_0 => new <>f__AnonymousType0`2(Convert(Param_0.IsAlive,Boolean),Convert(Param_0.IsVirulent,Boolean))
{ IsAlive = True,IsVirulent = False }
{ IsAlive = False,IsVirulent = True }
{ IsAlive = True,IsVirulent = True }
,

有人发布了有效答案,并出于某种原因以后将其删除。在这里:

组合键类:

$dir1 = "/path/1/";
$dir2 = "/path/2/";

$commaSeparatedDirectories = implode(',',[$dir1,$dir2]);

$count = count(glob("{{$commaSeparatedDirectories}}*.gz",GLOB_BRACE));

呼叫者函数及其实际用法:

class CombinedKey<T> : IEquatable<CombinedKey<T>>
{
    readonly object[] _keys;

    public bool Equals(CombinedKey<T> other)
    {
        return _keys.SequenceEqual(other._keys);
    }

    public override bool Equals(object obj)
    {
        return obj is CombinedKey<T> key && Equals(key);
    }

    public override int GetHashCode()
    {
        int hash = _keys.Length;
        foreach (object o in _keys)
        {
            if (o != null)
            {
                hash = hash * 13 + o.GetHashCode();
            }
        }
        return hash;
    }

    readonly Lazy<Func<T,object[]>> lambdaFunc = new Lazy<Func<T,object[]>>(() =>
    {
        Type type = typeof(T);
        var paramExpr = Expression.Parameter(type);
        var arrayExpr = Expression.NewArrayInit(
            typeof(object),type.GetProperties()
                .Where(p => (Attribute.GetCustomAttribute(p,typeof(KeyAttribute)) != null))
                .Select(p => Expression.Convert(Expression.Property(paramExpr,p),typeof(object)))
                .ToArray()
            );

        return Expression.Lambda<Func<T,object[]>>(arrayExpr,paramExpr).Compile();
    },System.Threading.LazyThreadSafetyMode.PublicationOnly);

    public CombinedKey(T instance)
    {
        _keys = lambdaFunc.Value(instance);
    }
}

是的,它相对较慢,因此,如果有问题,那么KlausGütter的解决方案就是可行的方法。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...