197 votes

Retourner tous les énumérables avec yield return en une seule fois ; sans passer par la boucle.

J'ai la fonction suivante pour obtenir les erreurs de validation pour une carte. Ma question porte sur le traitement de la méthode GetErrors. Les deux méthodes ont le même type de retour IEnumerable<ErrorInfo> .

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    var errors = GetMoreErrors(card);
    foreach (var e in errors)
        yield return e;

    // further yield returns for more validation errors
}

Est-il possible de renvoyer toutes les erreurs dans GetMoreErrors sans avoir à les énumérer ?

0 votes

Je suis heureux (et curieux !) de voir que d'autres questions sur le rendement sont soulevées - je ne comprends pas très bien moi-même. Ce n'est pas une question stupide !

0 votes

Qu'est-ce que GetCardProductionValidationErrorsFor ?

4 votes

Quel est le problème avec retourner GetMoreErrors(carte) ; ?

172voto

Jon Skeet Points 692016

Ce n'est absolument pas une question stupide, et c'est quelque chose que F# prend en charge grâce à la fonction yield! pour une collection complète vs yield pour un seul élément. (Cela peut être très utile en termes de récursion de queue...)

Malheureusement, il n'est pas supporté en C#.

Toutefois, si vous avez plusieurs méthodes qui renvoient chacune un fichier IEnumerable<ErrorInfo> vous pouvez utiliser Enumerable.Concat pour rendre votre code plus simple :

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return GetMoreErrors(card).Concat(GetOtherErrors())
                              .Concat(GetValidationErrors())
                              .Concat(AnyMoreErrors())
                              .Concat(ICantBelieveHowManyErrorsYouHave());
}

Il y a cependant une différence très importante entre les deux implémentations : celle-ci appelle toutes les méthodes immédiatement même si elle n'utilise les itérateurs retournés qu'un par un. Votre code existant attendra jusqu'à ce qu'il ait parcouru en boucle tout ce qui se trouve dans le fichier GetMoreErrors() avant même qu'il ne soit demande sur les prochaines erreurs.

En général, ce n'est pas important, mais il est bon de comprendre ce qui va se passer quand.

3 votes

Wes Dyer a publié un article intéressant sur ce modèle. blogs.msdn.com/wesdyer/archive/2007/03/23/

1 votes

Petite correction pour les passants - c'est System.Linq.Enumeration.Concat<>(first,second). Pas IEnumeration.Concat().

0 votes

@the-locster : Je ne suis pas sûr de ce que vous voulez dire. C'est définitivement Enumerable plutôt que Enumeration. Pourriez-vous clarifier votre commentaire ?

36voto

Adam Boddington Points 3438

Vous pourriez configurer toutes les sources d'erreur comme ceci (noms de méthodes empruntés à la réponse de Jon Skeet).

private static IEnumerable<IEnumerable<ErrorInfo>> GetErrorSources(Card card)
{
    yield return GetMoreErrors(card);
    yield return GetOtherErrors();
    yield return GetValidationErrors();
    yield return AnyMoreErrors();
    yield return ICantBelieveHowManyErrorsYouHave();
}

Vous pouvez alors itérer sur eux en même temps.

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    foreach (var errorSource in GetErrorSources(card))
        foreach (var error in errorSource)
            yield return error;
}

Vous pouvez également aplatir les sources d'erreur avec SelectMany .

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return GetErrorSources(card).SelectMany(e => e);
}

L'exécution des méthodes dans GetErrorSources sera également retardé.

8voto

Tim Jarvis Points 12024

Je ne vois rien de mal dans votre fonction, je dirais qu'elle fait ce que vous voulez.

Considérez que le rendement renvoie un élément de l'énumération finale chaque fois qu'il est invoqué. Ainsi, lorsque vous l'avez dans la boucle foreach, chaque fois qu'il est invoqué, il renvoie un élément. Vous avez la possibilité d'insérer des instructions conditionnelles dans votre foreach pour filtrer le jeu de résultats. (simplement en ne retournant pas vos critères d'exclusion)

Si vous ajoutez des rendements ultérieurs plus tard dans la méthode, celle-ci continuera à ajouter 1 élément à l'énumération, ce qui permet de faire des choses comme...

public IEnumerable<string> ConcatLists(params IEnumerable<string>[] lists)
{
  foreach (IEnumerable<string> list in lists)
  {
    foreach (string s in list)
    {
      yield return s;
    }
  }
}

3voto

Brian Rasmussen Points 68853

Oui, il est possible de renvoyer toutes les erreurs en même temps. Il suffit de renvoyer une List<T> o ReadOnlyCollection<T> .

En renvoyant un IEnumerable<T> vous retournez une séquence de quelque chose. À première vue, cela peut sembler identique au renvoi de la collection, mais il y a un certain nombre de différences que vous devez garder à l'esprit.

Collections

  • L'appelant peut être sûr que la collection et tous les éléments existeront lorsque la collection sera retournée. Si la collection doit être créée à chaque appel, retourner une collection est une très mauvaise idée.
  • La plupart des collections peuvent être modifiées au retour.
  • La collection est de taille finie.

Séquences

  • On peut les énumérer - et c'est à peu près tout ce que nous pouvons dire avec certitude.
  • Une séquence retournée ne peut pas être modifiée.
  • Chaque élément peut être créé dans le cadre de l'exécution de la séquence (c'est-à-dire en retournant les données de la séquence). IEnumerable<T> permet une évaluation paresseuse, en retournant List<T> ne le fait pas).
  • Une séquence peut être infinie et donc laisser à l'appelant le soin de décider du nombre d'éléments à renvoyer.

0 votes

Le renvoi d'une collection peut entraîner une surcharge déraisonnable si le client n'a besoin que de l'énumérer, puisque vous allouez à l'avance les structures de données pour tous les éléments. De plus, si vous déléguez à une autre méthode qui renvoie une séquence, la capturer en tant que collection implique une copie supplémentaire, et vous ne savez pas combien d'éléments (et donc combien de frais généraux) cela peut potentiellement impliquer. Ainsi, c'est seulement une bonne idée de retourner une collection quand elle est déjà là et peut être retournée directement sans copie (ou enveloppée comme readonly). Dans tous les autres cas, la séquence est un meilleur choix.

0 votes

Je suis d'accord, et si vous avez eu l'impression que j'ai dit que retourner une collection est toujours une bonne idée, vous n'avez rien compris. J'essayais de souligner le fait qu'il existe des différences entre le retour d'une collection et le retour d'une séquence. Je vais essayer de le rendre plus clair.

2voto

Robert Cartaino Points 12173

El yield est spécifiquement conçu pour renvoyer un élément d'une liste d'éléments. IEnumerable à chaque fois que vous appelez la méthode.

Si vous souhaitez obtenir toute la collection en une seule fois, vous pouvez simplement modifier le type de retour de la méthode et return l'ensemble de la collection directement.

Quelque chose comme ceci (en supposant que GetMoreErrors() renvoie un List<> ) :

private static List<ErrorInfo> GetErrors(Card card)
{
    return GetMoreErrors(card);
}

Yield est juste un moyen pratique pour qu'une méthode s'occupe de l'itération à votre place... une pièce récupérée à chaque fois que vous appelez GetErrors() . Si vous retournez la collection entière, vous finirez probablement par itérer à travers la collection d'erreurs de toute façon, donc c'est juste une question de savoir où vous voulez voir l'icône de l'erreur. foreach() code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X