22

In the C# code below, I found the usage of _() strange. Can anyone explain what this means?

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
            Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));

    return _(); IEnumerable<TSource> _()
    {
        var knownKeys = new HashSet<TKey>(comparer);
        foreach (var element in source)
        {
            if (knownKeys.Add(keySelector(element)))
                yield return element;
        }
    }
}
Michael Liu
  • 52,147
  • 13
  • 117
  • 150
Anh Lai
  • 367
  • 2
  • 9

1 Answers1

26

The code might be more easily understood by inserting a line break after the return statement:

return _();

IEnumerable<TSource> _()
{
    var knownKeys = new HashSet<TKey>(comparer);
    foreach (var element in source)
    {
        if (knownKeys.Add(keySelector(element)))
            yield return element;
    }
}

In this context, the underscore is just an arbitrary name for a local function (which is a new feature introduced in C# 7.0). If you prefer, you could replace the underscore by a more descriptive name:

return DistinctByHelper();

IEnumerable<TSource> DistinctByHelper()
{
    var knownKeys = new HashSet<TKey>(comparer);
    foreach (var element in source)
    {
        if (knownKeys.Add(keySelector(element)))
            yield return element;
    }
}

As a local function, the _ (or DistinctByHelper) method can access all the variables of the DistinctBy method.

By the way, the reason for having two methods here is so that, in case any argument is null, ArgumentNullException will be thrown immediately when DistinctBy is called instead of when the result is enumerated (due to the presence of the yield return statement).

Michael Liu
  • 52,147
  • 13
  • 117
  • 150
  • 8
    I *really* hope this pattern doesn't catch on. That's just ugly. – Bradley Uffner Aug 28 '17 at 02:39
  • 1
    I never liked how local functions lack any kind of identifying keyword - they're easily confused for a local delegate or object initialiser list, for example. A simple `func` keyword or mandatory lambda syntax `=>` would help). – Dai Aug 28 '17 at 02:43
  • 1
    The local function seems unnecessary here anyway. – Antonín Lejsek Aug 28 '17 at 02:45
  • 2
    The [docs](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7#local-functions) give two examples for why a local function like this might be used. Mainly so exceptions are thrown when created instead of when iterated/awaited. – Chris Aug 28 '17 at 02:47
  • Yeah, I can see the use for it; Making it local saved some keystrokes by making use of closures, and protects the function within its scope. My main complaint is naming it `_`. – Bradley Uffner Aug 28 '17 at 02:49
  • 2
    Agreed. The underscore name + starting the definition on the same line doesn't sit well with me. – Chris Aug 28 '17 at 02:52