5

I really love the GroupBy LINQ method in C#.

One thing that I don't like though is that the Key is always called Key.

When I loop through the groups, Key doesn't say me anything. So I have to look at the rest of the context to understand what Key actually is.

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

Of course you could argue it's a small thing, because you only have to look at the outer loop.

In my opinion it still eats 'braincycles' when you're reading it though. So if there are good alternatives that need little boilerplate I would like to use it.

There are some alternatives, but they all add boilerplate code or depend on comments - both I try to omit.

Option 1 - Extra variable inside loop

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

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

This solution adds unnecessary boilerplate code.

Option 2 - Extra Select

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

This solution adds unnecessary boilerplate code.

Option 3 - Add comment close to the use of Key

I rather have expressive code then comments that get outdated.

Summary

Is it theoretically possible to design an extension method that somehow works similar like the anonymous class functionaliy?

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

So we get:

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

foreach ( var group in grouped_by_competition )
{
    [...]
    group.Competition.DoSomething();
}
Dirk Boer
  • 8,522
  • 13
  • 63
  • 111
  • Do you care if the `NamedGroupBy` implements a select or do you want to re-implement `GroupBy`? Is this for EF or SQL or just LINQ to Objects? – NetMage Jul 19 '17 at 22:18
  • The biggest gain I get for LINQ to Objects (of course if it would natively work with EF it would be a plus). – Dirk Boer Jul 19 '17 at 22:19
  • I don't understand yet what you mean with your first question. In theory I don't care that much for the implementation itself, I'm mainly looking to make the GroupBy 'Useage' cleaner. I write/read GroupBy way more often then I would read/write the implementation of this Extension method :) – Dirk Boer Jul 19 '17 at 22:21
  • 1
    Dear downvoters, how is this question opinion based? There is a clear undisputable answer and Peter Duhino gave a very insightful explanation. – Dirk Boer Jul 20 '17 at 08:37
  • I was trying to work out a way to get `NamedGroupBy` to just be (essentially) a macro for `GroupBy().Select()` but it isn't possible at compile time. And don't worry about downvoters that don't comment. – NetMage Jul 20 '17 at 15:51

3 Answers3

2

Is it theoretically possible to design an extension method that somehow works similar like the anonymous class functionaliy?

No, because the name has to be present at compile time, and your proposed extension method would rely on an Expression object being examined at run-time.

The only way for you to be able to refer to the member by name, would be if the object being referenced actually has a member of that name. There has to be an actual compile-time name for the compiler to use.

(I'm lying. When you use the dynamic keyword, you tell the compiler to defer compile-time operations to run-time. This allows you to get away with tricks that don't resolve types with specific member names until run-time. It is in fact one way to meet your stated goal. However, running the compiler at run-time is potentially expensive, and abandons the compile-time type-safety that is a hallmark feature of C# and similar statically-typed languages. The dynamic keyword is an important feature in some scenarios, but I would not say that this particular use case justifies the cost.)

Personally, I don't find the name Key a problem. In one very important respect, it's very expressive: that is, it tells me that I am dealing with the key of a grouping. If I want to know more about it, its type is usually sufficient to elaborate further, and that's immediately available by hovering over it.

But, different strokes for different folks. If the goal is to have a named member different from Key, your anonymous type projection is IMHO the best you're going to get. Note that you can improve the conciseness by using the GroupBy() overload that allows you to project each group directly, instead of having the Select() method:

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

So, one other thing: when I wrote that "The only way for you to be able to refer to the member by name, would be if the object being referenced actually has a member of that name", there's a bit of hand-waving there. That is, what's really important is that the compiler knows about the name at compile time.

Usually, this means that there is really a type with a named member having that name. But there is an exception to that rule, recently added to C#, which is the new tuple syntax using ValueTuple. With that, you can implicitly give a name without introducing an actual new type. The compiler just keeps track of it in the local method, and treats it as if it's an actual type.

The tuple syntax doesn't really improve over the anonymous type syntax, in this particular scenario. It's a little shorter, but not by much:

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

It does have the potential performance advantage of using the ValueTuple class; being a value type, there can be a significant reduction in GC overhead using it. But not all scenarios would benefit from that.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
1

If you are okay with stepping into the painful world of dynamic, then you can implement it. I wouldn't recommend it.

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));
    }
}
NetMage
  • 26,163
  • 3
  • 34
  • 55
  • Thanks for your answer. Dynamic is indeed a bit too much - I still prefer static typing safety :) interesting and learnfull approach though – Dirk Boer Jul 20 '17 at 20:02
0

I'm not sure what kind of codes is clean for you, but how about use dynamic type in Select method?

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();
}
Barry
  • 9
  • 3