Return return внутри блока using() {} Устанавливает перед выполнением

Я написал свой собственный пользовательский слой данных, чтобы сохранить его в определенном файле, и я отрисовал его с помощью шаблона 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()???

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

Ответ 1

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

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

Ответ 2

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

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

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

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