J'ai la fonction suivante pour obtenir des erreurs de validation pour une carte. Ma question concerne le traitement de 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?
En y réfléchissant, c'est probablement une question stupide, mais je veux m'assurer que je ne me trompe pas.
Ce n'est certainement pas une question stupide, et c'est quelque chose que F # prend en charge avec yield!
Pour une collection entière vs yield
pour un seul élément. (Cela peut être très utile en termes de récursivité de queue ...)
Malheureusement, il n'est pas pris en charge en C #.
Cependant, si vous disposez de plusieurs méthodes renvoyant chacune un IEnumerable<ErrorInfo>
, Vous pouvez utiliser Enumerable.Concat
Pour simplifier votre code:
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
return GetMoreErrors(card).Concat(GetOtherErrors())
.Concat(GetValidationErrors())
.Concat(AnyMoreErrors())
.Concat(ICantBelieveHowManyErrorsYouHave());
}
Il existe cependant une différence très importante entre les deux implémentations: celle-ci appellera toutes les méthodes immédiatement, même si elle n'utilisera que les itérateurs retournés un par un. Votre code existant attendra qu'il soit bouclé à travers tout dans GetMoreErrors()
avant même demande sur les prochaines erreurs.
Habituellement, ce n'est pas important, mais cela vaut la peine de comprendre ce qui se passera quand.
Vous pouvez 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 ensuite les parcourir 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ée.
Je suis venu avec un rapide yield_
extrait:
Voici l'extrait XML:
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.Microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Author>John Gietzen</Author>
<Description>yield! expansion for C#</Description>
<Shortcut>yield_</Shortcut>
<Title>Yield All</Title>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal Editable="true">
<Default>items</Default>
<ID>items</ID>
</Literal>
<Literal Editable="true">
<Default>i</Default>
<ID>i</ID>
</Literal>
</Declarations>
<Code Language="CSharp"><![CDATA[foreach (var $i$ in $items$) yield return $i$$end$;]]></Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
Je ne vois rien de mal à votre fonction, je dirais qu'elle fait ce que vous voulez.
Considérez le rendement comme renvoyant un élément dans l'énumération finale chaque fois qu'il est invoqué, donc quand vous l'avez dans la boucle foreach comme ça, chaque fois qu'il est invoqué, il renvoie 1 élément. Vous avez la possibilité de placer des instructions conditionnelles dans votre foreach pour filtrer l'ensemble de résultats. (simplement en ne cédant pas à vos critères d'exclusion)
Si vous ajoutez des rendements ultérieurs plus tard dans la méthode, il continuera d'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;
}
}
}
Je suis surpris que personne n'ait pensé à recommander une méthode d'extension simple sur IEnumerable<IEnumerable<T>>
pour que ce code conserve son exécution différée. Je suis un fan de l'exécution différée pour de nombreuses raisons, l'une d'entre elles est que l'empreinte mémoire est petite, même pour les énumérations énormes.
public static class EnumearbleExtensions
{
public static IEnumerable<T> UnWrap<T>(this IEnumerable<IEnumerable<T>> list)
{
foreach(var innerList in list)
{
foreach(T item in innerList)
{
yield return item;
}
}
}
}
Et vous pouvez l'utiliser dans votre cas comme ceci
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
return DoGetErrors(card).UnWrap();
}
private static IEnumerable<IEnumerable<ErrorInfo>> DoGetErrors(Card card)
{
yield return GetMoreErrors(card);
// further yield returns for more validation errors
}
De même, vous pouvez supprimer la fonction wrapper autour de DoGetErrors
et déplacer simplement UnWrap
vers le site d'appel.
Oui, il est possible de renvoyer toutes les erreurs à la fois. Renvoyez simplement un List<T>
ou ReadOnlyCollection<T>
.
En renvoyant un IEnumerable<T>
vous retournez une séquence de quelque chose. À première vue, cela peut sembler identique au retour de la collection, mais il y a un certain nombre de différences, vous devez garder à l'esprit.
Les collections
Les séquences
IEnumerable<T>
permet une évaluation paresseuse, renvoyant List<T>
ne fait pas).