Using LINQ, can I give the "grouping" a name in VB.NET like I can in C#?

Viewed 103

My goal is to group and transform data, but have both the transformed and un-transformed "grouped data" available.

Consider the following minimal C# example:

var data = new[] { ("a", 1), ("a", 2), ("b", 1) };

var groupedAndTransformed =
    from d in data
    group d by d.Item1 into g
    select new
    {
        Untransformed = g,
        Transformed = g.Key + string.Join(",", g.Select(tuple => tuple.Item2))
    };

Now I try to do the same in VB.NET, but I can only achieve that with a two-step process, because VB.NET's Group By works somewhat differently than C#'s group by:

Dim data = {("a", 1), ("a", 2), ("b", 1)}

Dim step1 =
    From d In data
    Group d By d.Item1 Into Group

Dim groupedAndTransformed =
    From g In step1
    Select
        Untransformed = g,
        Transformed = g.Item1 + String.Join(",", g.Group.Select(Function(tuple) tuple.Item2))

The problem seems to be that VB.NET's Group By puts the result of the grouping into some kind of "global namespace": If I continue writing LINQ code directly after Group d By d.Item1 Into Group, I have both Item1 and Group in scope. However, I don't have a name for the IGrouping consisting of them (dubbed g in C#), so I need the second step.

Can I somehow tell VB's Group By statement to give me a name for the IGrouping? The only other workarounds I found were to use (a) functional syntax instead of declarative LINQ syntax (i.e., call Enumerable.GroupBy directly) or (b) nest step1 into the second step (i.e., From g In (...step1...)). Is there something I have missed?

3 Answers

It's too bad that the C# and VB LINQ syntax is so incompatible. I think the following VB LINQ is as close as you can get the to the original (with Option Infer On of course):

Dim data = { ("a", 1), ("a", 2), ("b", 1) }

Dim groupedAndTransformed = From d In data
    Group d By d.Item1 Into g = Group
    Select New With {
        Key .Untransformed = g,
        Key .Transformed = Item1 & String.Join(",", g.Select(Function(tuple) tuple.Item2))
    }

I've tested and it compiles and runs but it's hard to tell if it will function identically.

What Dave said, but I just wanted to note that overall it might be neater to use method syntax:


    Dim groupedAndTransformed = data.GroupBy(
        Function(d) d.Item1,
        Function(k, g) New With {
            Key .Untransformed = g
            Key .Transformed = g.Select(Function(tuple) tuple.Item2)
        }
    )

Or if you want to dump the Untransformed, there's an overload that will help reduce the subselect


    Dim groupedAndTransformed = data.GroupBy(
        Function(d) d.Item1,
        Function(d) d.Item2,
        Function(k, g) New With {
            Key .Transformed = k & String.Join(",", g)
        }
    )

I do wish they'd shorten Function to F

In the posted C# code, the variable g is of type System.Linq.Lookup.Grouping. This type is an internal class of the System.Linq.Lookup type. Since the intent of the LINQ appears to be to extract the Lookup groupings, perhaps it would be better to write the query to use the Enumerable.ToLookup Method to create an enumerable of the groupings.

var groupedAndTransformed = from grp in data.ToLookup((item) => item.Item1)
                            select new
                            {
                              Untransformed = grp,
                              Transformed = grp.Key + string.Join(",", grp.Select((tuple) => tuple.Item2))
                            };

This can be directly translated to VB code.

Dim groupedAndTransformed = From grp In data.ToLookup(Function(item) item.Item1)
                            Select New With
                             {
                               Key .Untransformed = grp,
                               Key .Transformed = grp.Key & String.Join(",", grp.Select(Function(tuple) tuple.Item2))
                             }

I realize this does not answer the question of how to force VB's "Group By" to use the same internal implementation as the C# compiler emits, but do you really want code that relies upon an implementation detail?

Related