web-dev-qa-db-ja.com

using(){}ブロック内のyieldreturnステートメント実行前に破棄します

特定のファイルに永続化する独自のカスタムデータレイヤーを作成し、カスタムDataContextパターンで抽象化しました。

これはすべて.NET2.0 Framework(ターゲットサーバーに与えられた制約)に基づいているため、一部はLINQ-to-SQLのように見えるかもしれませんが、そうではありません。同様のデータパターンを実装しました。

私がまだ説明できない状況の例については、以下の例を参照してください。

動物のすべてのインスタンスを取得するには-私はこれを行い、それはうまく機能します

public static IEnumerable<Animal> GetAllAnimals() {
        AnimalDataContext dataContext = new AnimalDataContext();
            return dataContext.GetAllAnimals();
}

そして、以下のAnimalDataContext()でのGetAllAnimals()メソッドの実装

public IEnumerable<Animal> GetAllAnimals() {
        foreach (var animalName in AnimalXmlReader.GetNames())
        {
            yield return GetAnimal(animalName);
        }
}

AnimalDataContext()はIDisposableを実装しています。これは、XmlTextReaderがあり、すぐにクリーンアップされるようにしたいためです。

ここで、最初の呼び出しを次のようなusingステートメントでラップするとします。

public static IEnumerable<Animal> GetAllAnimals() {
        using(AnimalDataContext dataContext = new AnimalDataContext()) {
            return dataContext.GetAllAnimals();
        }
}

そして、AnimalDataContext.GetAllAnimals()メソッドの最初の行にブレークポイントを置き、AnimalDataContext.Dispose()メソッドの最初の行に別のブレークポイントを置き、実行します...

Dispose()メソッドがFIRSTで呼び出されるため、Dispose()でAnimalXmlReaderがnullに設定されているため、AnimalXmlReader.GetNames()は「オブジェクト参照がオブジェクトのインスタンスに設定されていません」という例外を発生させます???

何か案は? yield return try-catchブロック内での呼び出しが許可されていないことに関連しているという予感があります。これは、コンパイルされると、singが効果的に表されます。

49
Neil Fenwick

GetAllAnimalsを呼び出すと、返されたIEnumerableをforeachループで列挙するまで、実際にはコードは実行されません。

DataContextは、IEnumerableを列挙する前に、ラッパーメソッドが戻るとすぐに破棄されます。

最も簡単な解決策は、次のように、ラッパーメソッドもイテレータにすることです。

public static IEnumerable<Animal> GetAllAnimals() {
    using (AnimalDataContext dataContext = new AnimalDataContext()) {
        foreach (var animalName in dataContext.GetAllAnimals()) {
            yield return GetAnimal(animalName);
        }
    }
}

このように、usingステートメントは外部イテレーターでコンパイルされ、外部イテレーターが破棄されたときにのみ破棄されます。

別の解決策は、ラッパーでIEnumerableを列挙することです。これを行う最も簡単な方法は、次のようにList<Animal>を返すことです。

public static IEnumerable<Animal> GetAllAnimals() {
    using (AnimalDataContext dataContext = new AnimalDataContext()) {
        return new List<Animal>(dataContext.GetAllAnimals());
    }
}

これにより、実行の延期のメリットが失われるため、必要がない場合でもすべての動物を取得できることに注意してください。

56
SLaks

これは、GetAllAnimalsメソッドが動物のコレクションを返さないためです。一度に動物を返すことができる列挙子を返します。

Usingブロック内のGetAllAnimals呼び出しから結果を返す場合は、列挙子を返すだけです。 usingブロックは、メソッドが終了する前にデータコンテキストを破棄します。その時点で、列挙子はまだ動物をまったく読み取っていません。その後、列挙子を使用しようとすると、データコンテキストから動物を取得できません。

回避策は、GetAllAnimalsメソッドが列挙子も作成するようにすることです。そうすれば、その列挙子の使用を停止するまで、usingブロックは閉じられません。

public static IEnumerable<Animal> GetAllAnimals() {
   using(AnimalDataContext dataContext = new AnimalDataContext()) {
      foreach (Animal animal in dataContext.GetAllAnimals()) {
         yield return animal;
      }
   }
}
11
Guffa