186

カードの検証エラーを取得するための次の関数があります。私の質問は、GetErrorsの処理に関するものです。どちらのメソッドも同じ戻り型を持ってい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
}

GetMoreErrorsそれらを列挙することなく、すべてのエラーを返すことは可能ですか?

4

6 に答える 6

160

これは間違いなくばかげた質問ではなく、F#がyield!コレクション全体yieldと単一のアイテムでサポートしているものです。(これは末尾再帰の観点から非常に便利です...)

残念ながら、C#ではサポートされていません。

ただし、それぞれがを返す複数のメソッドがある場合は、コードを単純化するためにIEnumerable<ErrorInfo>使用できます。Enumerable.Concat

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

ただし、2つの実装には非常に重要な違いが1つあります。これは、返されたイテレータを一度に1つしか使用しない場合でも、すべてのメソッドをすぐに呼び出します。既存のコードは、次のエラーについて尋ねるGetMoreErrors()前に、すべてがループされるまで待機します。

通常、これは重要ではありませんが、いつ何が起こるかを理解することは価値があります。

于 2009-08-13T05:30:06.510 に答える
33

このようにすべてのエラーソースを設定できます(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();
}

その後、それらを同時に繰り返すことができます。

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

または、。を使用してエラーソースをフラット化することもできますSelectMany

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

のメソッドの実行GetErrorSourcesも遅れます。

于 2014-04-07T12:27:32.453 に答える
20

yield_私は簡単なスニペットを思いついた:

yield_切り取られた使用法のアニメーション

スニペット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>
于 2015-06-14T18:16:17.893 に答える
9

IEnumerable<IEnumerable<T>>このコードの実行を延期し続けるために、単純な拡張メソッドを推奨する人が誰もいないことに驚いています。私は多くの理由で遅延実行のファンですが、その1つは、膨大な数の列挙型に対してもメモリフットプリントが小さいことです。

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

そして、あなたはこのようにあなたのケースでそれを使うことができます

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
}

同様に、ラッパー関数を廃止して、コールサイトDoGetErrorsに移動UnWrapすることもできます。

于 2016-09-22T14:20:59.360 に答える
8

私はあなたの機能に何も悪いことは見ていません、私はそれがあなたが望むことをしていると言うでしょう。

Yieldは、呼び出されるたびに最終的な列挙型の要素を返すと考えてください。そのため、このようにforeachループにある場合、呼び出されるたびに1つの要素が返されます。結果セットをフィルタリングするために、foreachに条件ステートメントを配置することができます。(単にあなたの除外基準に屈しないことによって)

メソッドの後半で後続のyieldを追加すると、列挙に1つの要素が追加され続け、次のようなことが可能になります...

public IEnumerable<string> ConcatLists(params IEnumerable<string>[] lists)
{
  foreach (IEnumerable<string> list in lists)
  {
    foreach (string s in list)
    {
      yield return s;
    }
  }
}
于 2009-08-13T05:00:20.627 に答える
3

はい、すべてのエラーを一度に返すことができます。List<T>またはを返すだけReadOnlyCollection<T>です。

を返すことによりIEnumerable<T>、何かのシーケンスを返します。一見、コレクションを返すのと同じように見えるかもしれませんが、いくつかの違いがあるので、覚えておく必要があります。

コレクション

  • 呼び出し元は、コレクションが返されるときに、コレクションとすべてのアイテムの両方が存在することを確認できます。呼び出しごとにコレクションを作成する必要がある場合、コレクションを返すことは非常に悪い考えです。
  • ほとんどのコレクションは、返されるときに変更できます。
  • コレクションは有限サイズです。

シーケンス

  • 列挙することができます-そしてそれは私たちが確かに言うことができるほとんどすべてです。
  • 返されたシーケンス自体は変更できません。
  • 各要素は、シーケンスの実行の一部として作成できます(つまりIEnumerable<T>、戻ると遅延評価が可能になりますが、戻るとできList<T>ません)。
  • シーケンスは無限である可能性があるため、返される要素の数を決定するのは呼び出し元に任せます。
于 2009-08-13T05:38:23.030 に答える