Instruction de syntaxe de retour impair

106

Je sais que cela peut sembler étrange, mais je ne sais même pas comment rechercher cette syntaxe sur Internet et je ne suis pas sûr de ce que cela signifie exactement.

J'ai donc regardé du code MoreLINQ, puis j'ai remarqué cette méthode

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

Quelle est cette déclaration de retour étrange? return _();?

kuskmen
la source
6
Ou voulez-vous dire return _(); IEnumerable<TSource> _():?
Alex K.
6
@Steve, je me demande si l'OP fait plus référence au return _(); IEnumerable<TSource> _()que le yield return?
Rob
5
Je pense qu'il voulait dire cette ligne return _(); IEnumerable<TSource> _(). Il pourrait être confus par son apparence plutôt que par la déclaration de retour réelle.
Mateusz
5
@AkashKava L'OP a dit qu'il y avait une déclaration de retour étrange. Malheureusement, le code contient deux instructions de retour. Il est donc compréhensible que les gens ne comprennent pas à quoi il / elle fait référence.
mjwills
5
Modifié la question, et encore une fois désolé pour la confusion.
kuskmen

Réponses:

106

C'est C # 7.0 qui prend en charge les fonctions locales ...

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 # actuel avec 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(); 

    }

L'astuce est que _ () est déclaré après son utilisation, ce qui est parfaitement bien.

Utilisation pratique des fonctions locales

L'exemple ci-dessus est juste une démonstration de la façon dont la méthode en ligne peut être utilisée, mais très probablement si vous allez invoquer la méthode une seule fois, alors cela ne sert à rien.

Mais dans l'exemple ci-dessus, comme mentionné dans les commentaires de Phoshi et Luaan , il y a un avantage à utiliser la fonction locale. Puisque la fonction avec yield return ne sera exécutée que si quelqu'un l'itère, dans ce cas, la méthode en dehors de la fonction locale sera exécutée et la validation des paramètres sera effectuée même si personne n'itérera la valeur.

Plusieurs fois, nous avons répété du code dans la méthode, regardons cet exemple.

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

Je pourrais optimiser cela avec ...

  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
la source
4
@ZoharPeled Eh bien .. le code affiché ne montre une utilisation pour la fonction .. :)
Rob
2
@ColinM l'un des avantages est que la fonction anonyme peut facilement accéder aux variables depuis son 'hôte'.
mjwills
6
Etes-vous sûr qu'en C #, cela s'appelle en fait une fonction anonyme? Il semble avoir un nom, à savoir _AnonymousFunctionou juste _, alors que je m'attendrais à ce qu'une véritable fonction anonyme soit quelque chose comme (x,y) => x+y. J'appellerais cela une fonction locale, mais je ne suis pas habitué à la terminologie C #.
chi
12
Pour être explicite, comme personne ne semble l'avoir souligné, cet extrait de code utilise la fonction locale car c'est un itérateur (notez le rendement), et s'exécute donc paresseusement. Sans la fonction locale, vous devrez soit accepter que la validation d'entrée se produise lors de la première utilisation, soit avoir une méthode qui ne sera jamais appelée par une autre méthode que pour très peu de raisons.
Phoshi
6
@ColinM L'exemple de kuksmen publié est en fait l'une des principales raisons pour lesquelles cela a finalement été implémenté - lorsque vous créez une fonction avec yield return, aucun code n'est exécuté tant que l'énumérable n'est pas réellement énuméré. Ceci n'est pas souhaitable, car vous voulez par exemple vérifier les arguments tout de suite. La seule façon de faire cela en C # est de séparer la méthode en deux méthodes - l'une avec yield returns et l'autre sans. Les méthodes en ligne vous permettent de déclarer la yieldméthode using à l' intérieur , évitant ainsi l'encombrement et le mauvais usage potentiel d'une méthode strictement interne à son parent et non réutilisable.
Luaan
24

Prenons l'exemple le plus simple

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

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

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

_() est une fonction locale déclarée dans la méthode contenant l'instruction return.

Stuart
la source
3
Oui je connais les fonctions locales c'est le formatage qui m'a trompé ... j'espère que cela ne deviendra pas standard.
kuskmen
20
Voulez-vous dire la déclaration de fonction commençant sur la même ligne? Si oui, je suis d'accord, c'est horrible!
Stuart
3
Oui, c'est ce que je voulais dire.
kuskmen
9
Sauf pour le nommer, le soulignement est également horrible
Icepickle
1
@AkashKava: la question n'est pas de savoir si c'est du C # légal, mais si le code est facile à comprendre (et donc facile à maintenir et agréable à lire) lorsqu'il est formaté comme ça. Les préférences personnelles jouent un rôle, mais j'ai tendance à être d'accord avec Stuart.
PJTraill