it-swarm.com.de

Geschachtelte Ertragsrendite mit IEnumerable

Ich habe die folgende Funktion, um Validierungsfehler für eine Karte zu erhalten. Meine Frage bezieht sich auf den Umgang mit GetErrors. Beide Methoden haben den gleichen Rückgabetyp 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
}

Ist es möglich, alle Fehler in GetMoreErrors zurückzugeben, ohne sie aufzählen zu müssen?

Darüber nachzudenken ist wahrscheinlich eine dumme Frage, aber ich möchte sicherstellen, dass ich nichts falsch mache.

154
John Oxley

Das ist definitiv keine blöde Frage und wird von F # mit yield! Für eine ganze Sammlung und mit yield für ein einzelnes Element unterstützt. (Das kann in Bezug auf die Schwanzrekursion sehr nützlich sein ...)

Leider wird es in C # nicht unterstützt.

Wenn Sie jedoch mehrere Methoden haben, die jeweils einen IEnumerable<ErrorInfo> Zurückgeben, können Sie Ihren Code mit Enumerable.Concat Vereinfachen:

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

Es gibt jedoch einen sehr wichtigen Unterschied zwischen den beiden Implementierungen: Diese ruft alle Methoden auf sofort, obwohl sie jeweils nur die zurückgegebenen Iteratoren verwenden. Ihr vorhandener Code wird warten, bis er durch alles in GetMoreErrors() geschleift ist, bevor er überhaupt fragt nach den nächsten Fehlern.

Normalerweise ist das nicht wichtig, aber es lohnt sich zu verstehen, was wann passieren wird.

134
Jon Skeet

Sie könnten alle Fehlerquellen wie diese einrichten (Methodennamen, die aus Jon Skeets Antwort entlehnt wurden).

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

Sie können sie dann gleichzeitig durchlaufen.

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

Alternativ können Sie die Fehlerquellen mit SelectMany reduzieren.

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

Die Ausführung der Methoden in GetErrorSources wird ebenfalls verzögert.

21
Adam Boddington

Ich kam mit einem schnellen yield_ Schnipsel:

yield_ snipped usage animation

Hier ist das XML-Snippet:

<?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>
15
John Gietzen

Ich sehe nichts falsch mit Ihrer Funktion, ich würde sagen, dass es tut, was Sie wollen.

Stellen Sie sich Yield vor, Sie geben bei jedem Aufruf ein Element in der letzten Aufzählung zurück. Wenn Sie es also in der foreach-Schleife haben, wird bei jedem Aufruf 1 Element zurückgegeben. Sie haben die Möglichkeit, bedingte Anweisungen in foreach einzufügen, um die Ergebnismenge zu filtern. (einfach, indem Sie Ihre Ausschlusskriterien nicht einhalten)

Wenn Sie später in der Methode nachfolgende Ausbeuten hinzufügen, wird der Aufzählung weiterhin 1 Element hinzugefügt, sodass folgende Aktionen möglich sind:.

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

Ich bin überrascht, dass niemand daran gedacht hat, eine einfache Erweiterungsmethode für IEnumerable<IEnumerable<T>> Zu empfehlen, damit dieser Code seine verzögerte Ausführung beibehält. Ich bin aus vielen Gründen ein Fan von verzögerter Ausführung. Einer davon ist, dass der Speicherbedarf selbst für enorme Zahlenmengen gering ist.

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

Und Sie könnten es in Ihrem Fall so verwenden

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
}

Auf ähnliche Weise können Sie die Wrapper-Funktion um DoGetErrors aufheben und UnWrap einfach auf die Anrufseite verschieben.

3
Frank Bryce

Ja, es ist möglich, alle Fehler auf einmal zurückzugeben. Geben Sie einfach einen List<T> Oder ReadOnlyCollection<T> Zurück.

Wenn Sie einen IEnumerable<T> Zurückgeben, geben Sie eine Sequenz von etwas zurück. An der Oberfläche, die mit der Rücksendung der Sammlung identisch zu sein scheint, gibt es jedoch eine Reihe von Unterschieden, die Sie berücksichtigen sollten.

Sammlungen

  • Der Anrufer kann sicher sein, dass sowohl die Sammlung als auch alle Elemente vorhanden sind, wenn die Sammlung zurückgegeben wird. Wenn die Sammlung per Anruf erstellt werden muss, ist die Rückgabe einer Sammlung eine wirklich schlechte Idee.
  • Die meisten Sammlungen können bei der Rückgabe geändert werden.
  • Die Sammlung ist endlich groß.

Sequenzen

  • Kann aufgezählt werden - und das ist so ziemlich alles, was wir mit Sicherheit sagen können.
  • Eine zurückgegebene Sequenz selbst kann nicht geändert werden.
  • Jedes Element kann als Teil des Durchlaufens der Sequenz erstellt werden (d. H. Das Zurückgeben von IEnumerable<T> Ermöglicht eine verzögerte Auswertung, das Zurückgeben von List<T> Nicht).
  • Eine Sequenz kann unendlich sein und so dem Aufrufer überlassen, wie viele Elemente zurückgegeben werden sollen.
3
Brian Rasmussen