2009-08-13 15 views
119

カードに検証エラーを取得する次の機能があります。私の質問はGetErrorsを扱うことに関連しています。どちらの方法も同じ戻り値タイプIEnumerable<ErrorInfo>です。IEnumerableを使用したネストされたyield return

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のすべてのエラーを返すことは可能ですか?

これはおそらく愚かな質問ですが、私は間違っていないことを確認したいと思います。

+0

より多くの利回り返品の質問が表示されることを嬉しく思っています。私はそれを理解していません。愚かな質問ではありません! – JoshJordan

+0

'GetCardProductionValidationErrorsFor'とは何ですか? –

+4

どうしたの?* GetMoreErrors(カード); *? –

答えて

107

これは間違いなく愚かな質問ではありません。これは、F#が全体のコレクションに対してyield!をサポートしているか、単一のアイテムに対してyieldです。 (これは末尾再帰の面で非常に便利です...)

残念ながら、C#ではサポートされていません。しかし2つの実装の間に1つの非常に重要な違いがあります

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

:この1つはすべてを呼び出しますあなたは、それぞれが IEnumerable<ErrorInfo>返すいくつかのメソッドを持っている場合

しかし、あなたはあなたのコードを簡単にするためにEnumerable.Concatを使用することができますメソッドはすぐにになりますが、返されるイテレータは一度に1つしか使用されません。あなたの既存のコードはGetMoreErrors()にあるすべてのものをループするまで待つでしょう。それ以前にもに次のエラーについて尋ねます。

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

+2

ウェス・ダイアーは、このパターンについて言及している興味深い記事を持っています。 http://blogs.msdn.com/wesdyer/archive/2007/03/23/all-about-iterators.aspx – JohannesH

+1

通行人のマイナーな修正 - System.Linq.Enumeration.Concat <>(最初、2番目) 。 IEnumerationではありません。Concat()。 – redcalx

+0

@ the-locster:あなたがどういう意味か分かりません。 EnumerationではなくEnumerableです。あなたのコメントを明確にできますか? –

6

私はあなたの機能に何か悪いことは見ません、私はそれがあなたが何をしていると言いたいと思います。

最終エニュメレーションの要素が呼び出されるたびにその要素を返すと考えると、そのようなforeachループ内にあるときは、呼び出されるたびに1要素が返されます。 foreachに条件文を入力して結果セットをフィルタリングすることができます。あなたはこの方法では、後に、後続の利回りを追加する場合

(単にあなたの除外基準に降伏しないことによって)、それは...それは可能のようなものをやって作る、列挙に1つの要素を追加していきます

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

はいすべてのエラーを一度に返すことは可能です。ちょうどList<T>またはReadOnlyCollection<T>を返してください。

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

コレクション

  • 呼び出し側は、コレクションが返されたときに収集し、すべての項目の両方が存在することを確認することができます。コールごとにコレクションを作成する必要がある場合は、コレクションを返すことは本当に悪い考えです。
  • 返されたとき、ほとんどのコレクションは変更できます。
  • コレクションは有限のサイズです。

シーケンス

  • を挙げることができる - それはほとんどすべて、我々は確かに言うことができます。
  • 返されるシーケンス自体は変更できません。
  • 各要素は、シーケンスを実行する際に作成することができます(つまり、IEnumerable<T>を返すと遅延評価が可能になり、返すのはList<T>になりません)。
  • シーケンスは無限になる可能性があり、呼び出し元に返す要素の数を決定するために呼び出し元に任せます。
+0

コレクションを返すと、すべての要素のデータ構造を事前に割り当てているので、すべてのクライアントが実際に必要とするものであれば、無理なオーバーヘッドになる可能性があります。また、シーケンスを返す別のメソッドに委譲した場合、それをコレクションとして取得するには余分なコピーが必要となり、潜在する可能性のあるアイテムの数(したがってオーバーヘッドの量)はわかりません。したがって、コレクションがすでに存在していてもコレクションを返すのは良い考えで、コピーせずに直接返すことができます(または読み取り専用としてラップする)。他のすべてのケースでは、シーケンスはより良い選択です。 –

+0

私は同意します。コレクションを返すと言った印象があるのなら、私はあなたの意見を忘れてしまいました。私はコレクションを返すこととシーケンスを返すことの間に違いがあるという事実を強調しようとしていました。私はそれをより明確にしようとします。 –

14

このようなすべてのエラーソース(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のメソッドの実行も遅延されます。

9

私は、迅速なyield_スニペットを思い付いた:私は誰もがIEnumerable<IEnumerable<T>>に簡単な拡張方法をお勧めすると考えられていない驚いて

<?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> 
1

:ここでは、スニペットのXMLです

yield_ snipped usage animation

このコードを遅延実行し続けるようにします。私は多くの理由で遅延実行のファンです。その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; 
      } 
     } 
    } 
} 

そして、あなたは同様に、あなたがDoGetErrors周りのラッパー関数を離れて行うことができますし、単に呼び出し場所にUnWrapを移動し、この

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 
} 

ようなあなたの場合にはそれを使用することができます。

+0

'DoGetErrors(card).SelectMany(x => x)'は同じことをして、延期された振る舞いを保持しているので、Extensionメソッドについては誰も考えなかったでしょう。それはまさにアダムが[彼の答え](http://stackoverflow.com/a/22912410/1300910)で示唆しているものです。 –

関連する問題