是否可以组合GroupBy和Select以获得正确的命名密钥?

时间:2017-07-19 22:15:58

标签: c# linq extension-methods readability

我真的很喜欢C#中的GroupBy LINQ方法。

我不喜欢的一件事是Key总是被称为Key

当我遍历群组时,Key并没有告诉我任何事情。因此,我必须查看其余的上下文,以了解Key究竟是什么。

var grouped_by_competition = all_events
    .GroupBy(e => e.Competition);

foreach ( var group in grouped_by_competition )
{
    [...]
    // ok what is Key for kind of object? I have to look at the outer loop to understand
    group.Key.DoSomething();
}

当然你可以说它是一件小事,因为你只需要看外环。

在我看来,当你阅读它时,它仍会吃掉“大脑”。因此,如果有很好的替代品需要很少的样板,我想使用它。

有一些替代方案,但它们都添加了样板代码或依赖于评论 - 我都试图省略。

选项1 - 循环内部的额外变量

var grouped_by_competition = all_events
    .GroupBy(e => e.Competition)

foreach ( var group in grouped_by_competition )
{
    var competition = group.Key;
    [...]
    competition.DoSomething();
}

此解决方案添加了不必要的样板代码。

选项2 - 额外选择

var grouped_by_competition = all_events
    .GroupBy(e => e.Competition)
    .Select(x => new { Competition = x.Key, Items = x } );

foreach ( var group in grouped_by_competition )
{
    [...]
    group.Competition.DoSomething();
}

此解决方案添加了不必要的样板代码。

选项3 - 添加关于使用密钥

的评论

我宁愿使用富有表现力的代码,也要过时的评论。

摘要

理论上是否可以设计一种类似于匿名类功能的扩展方法?

var example = new { e.Competition };
example.Competition.DoSomething(); // property name deduced

所以我们得到:

var grouped_by_competition = all_events
    .NamedGroupBy(e => e.Competition);

foreach ( var group in grouped_by_competition )
{
    [...]
    group.Competition.DoSomething();
}

3 个答案:

答案 0 :(得分:2)

  

理论上是否可以设计一种类似于匿名类功能的扩展方法?

不,因为名称必须在编译时出现,并且您建议的扩展方法将依赖于在运行时检查的Expression对象。

方式,您可以通过名称引用成员,如果被引用的对象实际上具有该名称的成员。编译器必须有一个实际的编译时名称。

(我说谎。当您使用dynamic关键字时,您告诉编译器将编译时操作推迟到运行时。这样您就可以放弃使用#39; t解析具有特定成员名称的类型直到运行时。它实际上是一种满足你既定目标的方法。但是,在运行时运行编译器可能很昂贵,并放弃编译时类型安全性是C#和类似静态类型语言的标志性特征。dynamic关键字在某些情况下是一个重要特征,但我不会说这个特定用例证明了成本。)

就个人而言,我找不到名称Key的问题。在一个非常重要的方面,它的非常表达:也就是说,它告诉我我正在处理分组的关键。如果我想了解更多信息,它的类型通常足以进一步详细说明,并且可以通过将鼠标悬停在它上面立即获得。

但是,不同人的不同笔画。如果目标是让一个命名成员与Key不同,那么您的匿名类型投影就是恕我直言,这是您最好的。请注意,您可以使用GroupBy()重载来提高简洁性,该重载允许您直接投影每个组,而不是使用Select()方法:

var grouped_by_competition = all_events
    .GroupBy(e => e.Competition, (k, g) => new { Competition = k, Items = g });

所以,另外一件事:当我写了" only 方式让你能够通过名字引用成员时,如果被引用的对象实际上有一个那个名字的成员",那里有点挥手。也就是说,编译器在编译时知道名称是非常重要的。

通常,这意味着确实存在具有该名称的命名成员的类型。但该规则有一个例外,最近添加到C#,这是使用ValueTuple的新元组语法。有了它,您可以隐式地给出一个名称而不引入实际的新类型。编译器只是在本地方法中跟踪它,并将其视为实际类型。

在此特定方案中,元组语法并未真正改进匿名类型语法。它有点短,但不是很多:

var grouped_by_competition = all_events
    .GroupBy(e => e.Competition, (k, g) => (Competition: k, Items: g));

使用ValueTuple类确实具有潜在的性能优势;作为一种值类型,使用它可以显着降低GC开销。但并非所有场景都会从中受益。

答案 1 :(得分:1)

如果你可以进入dynamic的痛苦世界,那么你可以实现它。我不推荐它。

class DynamicGroup<TKey, TElement> : DynamicObject, IGrouping<TKey, TElement> {
    private string keyname;
    private IGrouping<TKey, TElement> items;

    public DynamicGroup(IGrouping<TKey, TElement> pItems, string pKeyname) {
        items = pItems;
        keyname = pKeyname;
    }

    public IEnumerator<TElement> GetEnumerator() => items.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    public TKey Key { get => items.Key; }

    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        if (binder.Name == keyname) {
            result = items.Key;
            return true;
        }
        else {
            result = null;
            return false;
        }
    }
}

static class Ext {
    public static IEnumerable<dynamic> NamedGroupBy<TElement, TKey>(this IEnumerable<TElement> src, Expression<Func<TElement, TKey>> keySelector) {
        var ksb = keySelector.Body as MemberExpression;

        return src.GroupBy(keySelector.Compile()).Select(g => new DynamicGroup<TKey, TElement>(g, ksb.Member.Name));
    }
}

答案 2 :(得分:0)

我不确定哪种代码适合您,但是如何在Select方法中使用动态类型?

var grouped_by_competition = all_events
.GroupBy(e => e.Competition)
.Select(x => {{
   dynamic ret=new System.Dynamic.ExpandoObject();
   ret.Competition=x.Key;
   ret.Items=x;
   return ret;
} );
foreach( var group in grouped_by_competition )
{
    group.Competition.DoSomething();
}