【问题标题】:Is it possible to combine a GroupBy and Select to get a proper named Key?是否可以结合 GroupBy 和 Select 以获得正确的命名键?
【发布时间】:2017-07-19 22:15:58
【问题描述】:

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

我不喜欢的一件事是密钥总是被称为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();
}

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

在我看来,当您阅读它时,它仍然会消耗“大脑周期”。所以如果有好的替代品需要很少的样板,我想使用它。

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

选项 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 - 添加接近使用 Key 的注释

我宁愿拥有富有表现力的代码,而不是过时的 cmets。

总结

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

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();
}

【问题讨论】:

  • 您关心NamedGroupBy 是否实现了选择,还是您想重新实现GroupBy?这是 EF 还是 SQL 还是 LINQ to Objects?
  • 我从 LINQ to Objects 中获得的最大收获(当然,如果它本身可以与 EF 一起使用,那将是一个加分项)。
  • 我还不明白你的第一个问题是什么意思。从理论上讲,我不太关心实现本身,我主要是想让 GroupBy 'Useage' 更清洁。我更频繁地写/读 GroupBy,然后我会读/写这个扩展方法的实现:)
  • 亲爱的投票者,这个问题的意见是如何建立的?有一个明确的无可争议的答案,Peter Duhino 给出了非常有见地的解释。
  • 我试图想办法让NamedGroupBy (基本上)成为GroupBy().Select() 的宏,但在编译时这是不可能的。不要担心不发表评论的投票者。

标签: c# linq extension-methods readability


【解决方案1】:

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

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

唯一您能够通过名称引用成员的方法是,如果被引用的对象实际上具有该名称的成员。必须有一个实际的编译时名称供编译器使用。

(我在撒谎。当您使用 dynamic 关键字时,您告诉编译器将编译时操作推迟到运行时。这使您可以摆脱无法解析类型的技巧在运行时之前使用特定的成员名称。这实际上是满足您既定目标的一种方法。但是,在运行时运行编译器可能很昂贵,并且放弃了作为 C# 标志性特性的编译时类型安全和类似的静态类型语言。dynamic 关键字在某些场景中是一个重要的特性,但我不会说这个特殊的用例证明了成本的合理性。)

就我个人而言,我认为Key 这个名字没有问题。在一个非常重要的方面,它非常富有表现力:也就是说,它告诉我我正在处理一个分组的键。如果我想了解更多关于它的信息,它的 type 通常足以进一步阐述,并且可以通过将鼠标悬停在它上面立即获得。

但是,不同的人有不同的笔触。如果目标是拥有一个不同于Key 的命名成员,那么您的匿名类型投影是恕我直言,您将获得的最佳结果。请注意,您可以通过使用GroupBy() 重载来提高简洁性,该重载允许您直接投影每个组,而不是使用Select() 方法:

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

所以,另外一件事:当我写“唯一您能够通过名称引用成员的方式时,如果被引用的对象实际上具有该成员名字”,那里有点挥手。也就是说,真正重要的是编译器在编译时知道名称。

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

在这种特殊情况下,元组语法并没有真正改进匿名类型语法。它有点短,但不是很多:

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

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

【讨论】:

    【解决方案2】:

    如果您可以进入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));
        }
    }
    

    【讨论】:

    • 感谢您的回答。动态确实有点过分了——我还是更喜欢静态类型的安全性:)虽然很有趣,但学习方法也很丰富
    【解决方案3】:

    我不确定哪种代码对你来说是干净的,但是在 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();
    }
    

    【讨论】:

      猜你喜欢
      • 2012-06-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-25
      • 2018-07-19
      • 2013-03-04
      • 1970-01-01
      相关资源
      最近更新 更多