Ungerade Rückgabesyntaxanweisung

106

Ich weiß, dass das seltsam klingen mag, aber ich weiß nicht einmal, wie ich diese Syntax im Internet durchsuchen soll, und ich bin mir auch nicht sicher, was genau das bedeutet.

Also habe ich mir MoreLINQ-Code angesehen und dann diese Methode bemerkt

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

Was ist diese seltsame Rückgabeanweisung? return _();?

Kuskmen
quelle
6
Oder meinst du : return _(); IEnumerable<TSource> _()?
Alex K.
6
@Steve, ich frage mich, ob sich das OP mehr auf das bezieht return _(); IEnumerable<TSource> _()als auf das yield return?
Rob
5
Ich denke, er meinte diese Linie return _(); IEnumerable<TSource> _(). Er könnte eher durch die Art und Weise verwirrt sein, wie es aussieht, als durch die tatsächliche Rückgabeerklärung.
Mateusz
5
@AkashKava Das OP sagte, dass es eine seltsame Return-Anweisung gab. Leider enthält der Code zwei return-Anweisungen. Es ist also verständlich, wenn die Leute verwirrt sind, worauf sie sich beziehen.
mjwills
5
Die Frage wurde bearbeitet und die Verwirrung noch einmal entschuldigt.
Kuskmen

Antworten:

106

Dies ist C # 7.0, das lokale Funktionen unterstützt ....

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

        // This is basically executing _LocalFunction()
        return _LocalFunction(); 

        // This is a new inline method, 
        // return within this is only within scope of
        // this method
        IEnumerable<TSource> _LocalFunction()
        {
            var knownKeys = new HashSet<TKey>(comparer);
            foreach (var element in source)
            {
                if (knownKeys.Add(keySelector(element)))
                    yield return element;
            }
        }
    }

Aktuelles C # mit Func<T>

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

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

        // This is basically executing func
        return func(); 

    }

Der Trick ist, dass _ () nach seiner Verwendung deklariert wird, was vollkommen in Ordnung ist.

Praktische Nutzung lokaler Funktionen

Das obige Beispiel ist nur eine Demonstration der Verwendung der Inline-Methode. Wenn Sie die Methode jedoch nur einmal aufrufen, ist dies höchstwahrscheinlich nicht von Nutzen.

Wie in den Kommentaren von Phoshi und Luaan erwähnt , bietet die Verwendung der lokalen Funktion im obigen Beispiel einen Vorteil. Da eine Funktion mit Ertragsrückgabe nur ausgeführt wird, wenn jemand sie wiederholt, wird in diesem Fall eine Methode außerhalb der lokalen Funktion ausgeführt und eine Parameterüberprüfung durchgeführt, selbst wenn der Wert von niemandem wiederholt wird.

Oft haben wir Code in der Methode wiederholt. Schauen wir uns dieses Beispiel an.

  public void ValidateCustomer(Customer customer){

      if( string.IsNullOrEmpty( customer.FirstName )){
           string error = "Firstname cannot be empty";
           customer.ValidationErrors.Add(error);
           ErrorLogger.Log(error);
           throw new ValidationError(error);
      }

      if( string.IsNullOrEmpty( customer.LastName )){
           string error = "Lastname cannot be empty";
           customer.ValidationErrors.Add(error);
           ErrorLogger.Log(error);
           throw new ValidationError(error);
      }

      ... on  and on... 
  }

Ich könnte dies optimieren mit ...

  public void ValidateCustomer(Customer customer){

      void _validate(string value, string error){
           if(!string.IsNullOrWhitespace(value)){

              // i can easily reference customer here
              customer.ValidationErrors.Add(error);

              ErrorLogger.Log(error);
              throw new ValidationError(error);                   
           }
      }

      _validate(customer.FirstName, "Firstname cannot be empty");
      _validate(customer.LastName, "Lastname cannot be empty");
      ... on  and on... 
  }
Akash Kava
quelle
4
@ZoharPeled Naja .. das gepostete Code tut zeigen eine Verwendung für die Funktion .. :)
Rob
2
@ColinM Einer der Vorteile ist, dass die anonyme Funktion von ihrem 'Host' aus leicht auf Variablen zugreifen kann.
mjwills
6
Sind Sie sicher, dass dies in C # -Sprache tatsächlich als anonyme Funktion bezeichnet wird? Es scheint einen Namen zu haben, nämlich _AnonymousFunctionoder nur _, während ich erwarten würde, dass eine echte anonyme Funktion so etwas wie ist (x,y) => x+y. Ich würde dies eine lokale Funktion nennen, bin aber nicht an die C # -Terminologie gewöhnt.
Chi
12
Um genau zu sein, wie niemand darauf hingewiesen zu haben scheint, verwendet dieses Code-Snippet die lokale Funktion, da es ein Iterator ist (beachten Sie die Ausbeute) und daher träge ausgeführt wird. Ohne die lokale Funktion müssten Sie entweder akzeptieren, dass die Eingabevalidierung bei der ersten Verwendung erfolgt, oder eine Methode haben, die nur von einer anderen Methode aufgerufen wird, die aus sehr geringen Gründen herumliegt.
Phoshi
6
@ColinM Das Beispiel, das kuksmen veröffentlicht hat, ist einer der Hauptgründe, warum dies schließlich implementiert wurde. Wenn Sie eine Funktion mit yield returnerstellen, wird kein Code ausgeführt, bis die Aufzählung tatsächlich aufgezählt ist. Dies ist unerwünscht, da Sie beispielsweise Argumente sofort überprüfen möchten. Die einzige Möglichkeit, dies in C # zu tun, besteht darin, die Methode in zwei Methoden zu trennen - eine mit yield returns und die andere ohne. Mit Inline-Methoden können Sie die yieldusing-Methode im Inneren deklarieren , um Unordnung und möglichen Missbrauch einer Methode zu vermeiden, die streng intern und nicht wiederverwendbar ist.
Luaan
24

Betrachten Sie das einfachere Beispiel

void Main()
{
    Console.WriteLine(Foo()); // Prints 5
}

public static int Foo()
{
    return _();

    // declare the body of _()
    int _()
    {
        return 5;
    }
}

_() ist eine lokale Funktion, die in der Methode deklariert ist, die die return-Anweisung enthält.

Stuart
quelle
3
Ja, ich weiß über lokale Funktionen Bescheid, es war die Formatierung, die mich getäuscht hat ... hoffe, dass dies nicht zum Standard wird.
Kuskmen
20
Meinen Sie die Funktionsdeklaration, die in derselben Zeile beginnt? Wenn ja, stimme ich zu, ist es schrecklich!
Stuart
3
Ja das meinte ich.
Kuskmen
9
Abgesehen von dieser Benennung ist der Unterstrich auch schrecklich
Icepickle
1
@AkashKava: Die Frage ist nicht, ob es sich um legales C # handelt, sondern ob der Code bei einer solchen Formatierung leicht zu verstehen (und daher leicht zu pflegen und angenehm zu lesen) ist. Persönliche Vorlieben spielen eine Rolle, aber ich stimme Stuart eher zu.
PJTraill