2009-09-10 4 views
19

IEnumerable <が完了したことを知っているので、foreachから切り離す適切な方法があります。foreachからの歩留まり復帰と懸念について

次のコードを考えてみましょう:

private static IEnumerable<Person> getPeople() 
    { 
     using (SqlConnection sqlConnection = new SqlConnection("...")) 
     { 
      try 
      { 
       sqlConnection.Open(); 
       using (SqlCommand sqlCommand = new SqlCommand("select id, firstName, lastName from people", sqlConnection)) 
       { 

        using (SqlDataReader reader = sqlCommand.ExecuteReader()) 
        { 
         while (reader.Read()) 
          yield return new Person(reader.GetGuid(0), reader.GetString(1), reader.GetString(2)); 
        } 
       } 
      } 
      finally 
      { 
       Console.WriteLine("finally disposing of the connection"); 
       if (sqlConnection.State == System.Data.ConnectionState.Open) 
        sqlConnection.Close(); 
      } 
     } 
    } 

彼は、消費者は、その後everthingは罰金で、読者はfalseを返しますforeachのから壊れていない場合は、whileループのwillendと機能がデータベースコマンドとの接続をクリーンアップ。しかし、終了する前に発信者がforeachから壊れた場合はどうなりますか?

+1

もhttp://stackoverflow.com/questions/1400146/are-there-any-pitfalls-to-using-an-ienumerablet-return-type-for-sql-data/1400195#を参照してください1400195 –

答えて

30

優秀な質問です。あなたはこれについて心配する必要はありません。コンパイラはあなたのためにそれを世話します。基本的には、finallyブロックのクリーンアップコードを、生成されたイテレータの特別なクリーンアップメソッドに入れます。コントロールが呼び出し元のforeachブロックを離れると、コンパイラはイテレータのクリーンアップコードを呼び出すコードを生成します。

簡単な例:

static IEnumerable<int> GetInts() 
{ 
    try { yield return 1; yield return 2;} 
    finally { Cleanup(); } 
} 

あなたの質問は基本的に "このシナリオで呼ばれるクリーンアップ()ですか?" され

foreach(int i in GetInts()) { break; } 

はい。反復子ブロックはクリーンアップを呼び出すDisposeメソッドを持つクラスとして生成され、その後、foreachループは、のようなものとして生成されます。ブレークが発生したときに

{ 
    IEnumerator<int> enumtor = GetInts().GetEnumerator(); 
    try 
    { 
    while(enumtor.MoveNext()) 
    { 
     i = enumtor.Current; 
     break; 
    } 
    } 
    finally 
    { 
    enumtor.Dispose(); 
    } 
} 

だから、最終的に引き継ぎ、ディスポーザーが呼び出されます。

この機能の設計で考慮した奇妙なコーナーケースの詳細については、私の最近の記事をご覧ください。

http://blogs.msdn.com/ericlippert/archive/tags/Iterators/default.aspx

+0

これはすばらしい答えです! –

2

、あなたは

yield break; 

が早期降伏ループから抜け出すためのステートメントを使用することができますが、「使って」ステートメントを使用すると、あなたのコードは、私が思う誤解... を明らかに

using (SqlConnection sqlConnection = new SqlConnection("...")) 
{ 
    // other stuff 
} 

あなたは自動的にコンパイルされたILコードでtry finallyブロックを取得し、finnalyブロックはDisposeを呼び出し、Disposeコードで接続が閉じられます。

+0

マイナーな修正: 'yield break'は文です。 – jason

+0

編集して修正します –

2

あなたの質問があるかどうかを見てみましょう。

foreach(Person p in getPeople()) 
{ 
    // break here 
} 

foreachキーワードのため、列挙子は適切に配置されています。 Enumeratorの処分中、getPeople()の実行は終了します。したがって、接続は適切にクリーンアップされます。

関連する問題