Dichiarazione di sintassi di ritorno dispari


106

So che può sembrare strano ma non so nemmeno come cercare questa sintassi in Internet e inoltre non sono sicuro di cosa significhi esattamente.

Quindi ho controllato un po 'di codice MoreLINQ e poi ho notato questo metodo

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

Cos'è questa strana dichiarazione di ritorno? return _();?


6
Oppure intendi return _(); IEnumerable<TSource> _():?
Alex K.

6
@Steve, mi chiedo se l'OP si riferisca più al return _(); IEnumerable<TSource> _()che al yield return?
Rob

5
Penso che intendesse questa linea return _(); IEnumerable<TSource> _(). Potrebbe essere confuso dal modo in cui sembra piuttosto che dall'effettiva dichiarazione di ritorno.
Mateusz

5
@ AkashKava L'OP ha detto che c'era una strana dichiarazione di ritorno. Sfortunatamente, il codice contiene due istruzioni di ritorno. Quindi è comprensibile se le persone sono confuse su ciò a cui si riferisce.
mjwills

5
Ho modificato la domanda e ancora una volta mi dispiace per la confusione.
kuskmen

Risposte:


106

Questo è C # 7.0 che supporta le funzioni locali ....

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

C # corrente con 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(); 

    }

Il trucco è che _ () viene dichiarato dopo essere stato utilizzato, il che va benissimo.

Uso pratico delle funzioni locali

L'esempio sopra è solo una dimostrazione di come il metodo inline può essere utilizzato, ma molto probabilmente se invocerai il metodo solo una volta, allora non sarà di alcuna utilità.

Ma nell'esempio sopra, come menzionato nei commenti di Phoshi e Luaan , c'è un vantaggio nell'usare la funzione locale. Poiché la funzione con yield return non verrà eseguita a meno che qualcuno non la iteri, in questo caso verrà eseguito un metodo esterno alla funzione locale e verrà eseguita la convalida dei parametri anche se nessuno itererà il valore.

Molte volte abbiamo ripetuto il codice nel metodo, guardiamo questo esempio ..

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

Potrei ottimizzarlo con ...

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

4
@ZoharPeled Beh .. il codice postato fa mostrare un uso per la funzione .. :)
Rob

2
@ColinM uno dei vantaggi è che la funzione anonima può accedere facilmente alle variabili dal suo "host".
mjwills

6
Sei sicuro che in C # -speak questa sia effettivamente chiamata funzione anonima? Sembra avere un nome, cioè _AnonymousFunctiono semplicemente _, mentre mi aspetto che una vera funzione anonima sia qualcosa di simile (x,y) => x+y. La chiamerei una funzione locale, ma non sono abituato alla terminologia C #.
chi

12
Per essere espliciti, come nessuno sembra averlo sottolineato, questo frammento di codice utilizza la funzione locale perché è un iteratore (nota il rendimento), e quindi viene eseguito pigramente. Senza la funzione locale dovresti accettare che la convalida dell'input avvenga al primo utilizzo o avere un metodo che verrà chiamato solo da un altro metodo in giro per pochissime ragioni.
Phoshi

6
@ColinM L'esempio kuksmen pubblicato è in realtà uno dei motivi principali per cui questo è stato finalmente implementato: quando si crea una funzione con yield return, non viene eseguito alcun codice finché l'enumerabile non viene effettivamente enumerato. Ciò è indesiderabile, poiché ad esempio si desidera verificare subito gli argomenti. L'unico modo per eseguire questa operazione in C # è separare il metodo in due metodi: uno con se yield returnl'altro senza. I metodi inline consentono di dichiarare il yieldmetodo using all'interno , evitando il disordine e il potenziale uso improprio di un metodo che è strettamente interno al suo genitore e non riutilizzabile.
Luaan

24

Considera l'esempio più semplice

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

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

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

_() è una funzione locale dichiarata all'interno del metodo contenente l'istruzione return.


3
Sì, so delle funzioni locali, è stata la formattazione che mi ha ingannato ... spero che questo non diventi standard.
kuskmen

20
Intendi la dichiarazione di funzione che inizia sulla stessa riga? Se è così, sono d'accordo, è orribile!
Stuart

3
Sì, è quello che volevo dire.
kuskmen

9
Fatta eccezione per il nome, anche la sottolineatura è orribile
Icepickle

1
@ AkashKava: la domanda non è se sia legale C #, ma se il codice sia facile da capire (e quindi facile da mantenere e piacevole da leggere) quando formattato in questo modo. Le preferenze personali giocano un ruolo, ma tendo ad essere d'accordo con Stuart.
PJTraill
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.