Оператор yield return внутри блока using () {} Удаляет перед выполнением - PullRequest
49 голосов
/ 08 октября 2009

Я написал свой собственный слой данных для сохранения в определенном файле и абстрагировал его с помощью пользовательского шаблона DataContext.

Все это основано на .NET 2.0 Framework (с учетом ограничений для целевого сервера), поэтому, хотя некоторые из них могут выглядеть как LINQ-to-SQL, это не так! Я только что реализовал подобный шаблон данных.

См. Пример ниже для примера ситуации, которую я пока не могу объяснить.

Чтобы получить все экземпляры Animal - я делаю это, и он отлично работает

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

И реализация метода GetAllAnimals () в AnimalDataContext () ниже

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, так что AnimalXmlReader.GetNames () выдает исключение «ссылка на объект не установлена ​​для экземпляра объекта», поскольку AnimalXmlReader был установлен в null в Dispose () ???

Есть идеи? У меня есть догадка, что это связано с тем, что возвращаемая доходность не разрешается вызывать внутри блока try-catch, который с использованием эффективно представляет после компиляции ...

Ответы [ 2 ]

56 голосов
/ 08 октября 2009

Когда вы вызываете 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());
    }
}

Обратите внимание, что это теряет преимущество отложенного выполнения, поэтому оно получит всех животных, даже если они вам не нужны.

11 голосов
/ 08 октября 2009

Причина этого в том, что метод GetAllAnimals не возвращает коллекцию животных. Возвращает перечислитель, способный вернуть животное за раз.

Когда вы возвращаете результат вызова GetAllAnimals внутри блока using, вы просто возвращаете перечислитель. Блок using удаляет контекст данных до выхода из метода, и в этот момент счетчик еще не прочитал никаких животных. Когда вы попытаетесь использовать перечислитель, он не сможет получить животных из контекста данных.

Обходной путь - заставить метод GetAllAnimals также создать перечислитель. Таким образом, блок using не будет закрыт, пока вы не прекратите использовать этот перечислитель:

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