2016-07-08 11 views
0

私はPythonの世界から来て、C#で "generator"メソッドを作成しようとしています。私は、特定のバッファサイズのチャンクでファイルを解析しており、一度に1つずつ次のチャンクを読み込んで保存し、それをループの中でforeachループに収めたいだけです。ここで私はこれまでのところ(概念実証を簡素化)しているものです:C# "Generator"メソッド

class Page 
{ 
    public uint StartOffset { get; set; } 
    private uint currentOffset = 0; 

    public Page(MyClass c, uint pageNumber) 
    { 
     uint StartOffset = pageNumber * c.myPageSize; 

     if (StartOffset < c.myLength) 
      currentOffset = StartOffset; 
     else 
      throw new ArgumentOutOfRangeException("Page offset exceeds end of file"); 

     while (currentOffset < c.myLength && currentOffset < (StartOffset + c.myPageSize)) 
      // read data from page and populate members (not shown for MWE purposes) 
      . . . 
    } 
} 

class MyClass 
{ 
    public uint myLength { get; set; } 
    public uint myPageSize { get; set; } 

    public IEnumerator<Page> GetEnumerator() 
    { 
     for (uint i = 1; i < this.myLength; i++) 
     { 
      // start count at 1 to skip first page 
      Page p = new Page(this, i); 
      try 
      { 
       yield return p; 
      } 
      catch (ArgumentOutOfRangeException) 
      { 
       // end of available pages, how to signal calling foreach loop? 
      } 
     } 
    } 
} 

それは(私はこれらのプロパティの多くは公に設定することができますが、ためていない最低限の実施例であるので、私は、これは完璧ではない知っていますこれを単純にしておくと、プライベートメンバーやプロパティを入力する必要はありません)。

しかし、私の主な質問どのように私は、ループするための項目が残っていることをforeachステートメントで知っているMyClassを介してループすることができますか?要素が残っていないことを示すためにスローする例外はありますか?

+0

Pythonの場合と同じように、アイテムの降伏を単に止めるだけです。つまり、IEnumerable を返すメソッドを作成する必要があります。列挙可能なものは簡単に消費できます。 – poke

+1

'IEnumerator .MoveNext'は、呼び出し元に反復処理を停止するよう指示するものです。これは 'yield return'を使うときに実装されます。明示的に停止したい場合は、 'yield break'を使うことができます。 –

+0

@この例では矛盾は私のせいです。ページはこの投稿のために作られたもので、BTreePageは本当に私が実際のコードで返すものです。一定。 – Dan

答えて

1

のようになります、あなたの代わりにIEnumerator<T>IEnumerable<T>を使用する必要があります。列挙子は、何かを列挙するために使用されている技術的オブジェクトです。それは多くの場合、数えられるものです。

C#には、列挙型を扱う特別な能力があります。最も顕著なのは、foreachループを列挙型(ただし列挙型ではなく、実際には列挙型の列挙子を使用しています)でも使用できます。また、列挙型ではLINQを使用すると、消費をさらに容易にすることができます。

ですから、このようなあなたのクラスを変更する必要があります。

class MyClass 
{ 
    public uint myLength { get; set; } 
    public uint myPageSize { get; set; } 

    # note the modified signature 
    public IEnumerable<Page> GetPages() 
    { 
     for (uint i = 1; i < this.myLength; i++) 
     { 
      Page p; 
      try 
      { 
       p = new Page(this, i); 
      } 
      catch (ArgumentOutOfRangeException) 
      { 
       yield break; 
      } 
      yield return p; 
     } 
    } 
} 

最後に、これはあなたがこのようにそれを使用することができます:もちろん

var obj = new MyClass(); 

foreach (var page in obj.GetPages()) 
{ 
    // do whatever 
} 

// or even using LINQ 
var pageOffsets = obj.GetPages().Select(p => p.currentOffset).ToList(); 

、あなたも名前を変更する必要がありますメソッドのは何か意味があります。ページを返す場合は、GetPagesは正しい方向への最初の一歩です。 GetEnumeratorという名前は、IEnumerableを実装するタイプに予約されています。GetEnumeratorメソッドは、オブジェクトが表すコレクションの列挙子を返すことになっています。

+0

もっと意味があります。ありがとうございました! – Dan

0

yield break;ステートメントを使用して、イテレータメソッドが生成しているシーケンスを終了します。

1

それを行うには、2つの方法は、コードの実行がGetEnumeratorメソッド関数の最後に到達させたり、コードにyield break;に置かれ、これはvoidを返される関数でreturn;と同じように動作します。

呼び出し元の認識では、GetEnumerator()から返された列挙子は、MoveNext()falseを返します。つまり、列挙子が完了したことを示します。


あなたのあなたは、コードの間違った部分の周りのtry/catchを入れて「catch節でtryブロックの本体内の値が得られないことができ、」修正するには、execptionは上のスローされますnewyield returnではありません。コメントで述べたように、あなたのコードは

public IEnumerator<Page> GetEnumerator() 
{ 
    for (uint i = 1; i < this.myLength; i++) 
    { 
     // start count at 1 to skip first page 
     Page p; 
     try 
     { 
      p = new Page(this, i); 
     } 
     catch (ArgumentOutOfRangeException) 
     { 
      yield break; 
     } 
     yield return p; 
    } 
} 
+0

これは正しい答えだと思う別の質問ですが、今私は2つの問題があります;) - 明らかに私はtryステートメント内の歩留まりを使用することはできません。 Arg – Dan

+0

Odd。私は "catch節でtryブロックの本体の中に値を与えることはできません" – Dan

+0

はい、[できません](http://stackoverflow.com/q/346365/216074)。変数の中の値を捕捉し、try/catchの後にそれを返します。 – poke