Проблема с обработкой исключений для IEnumerable <T>, это зависит от ленивости - PullRequest
7 голосов
/ 16 ноября 2009

Я использовал для создания интерфейсов с IEnumerable<T> в качестве возвращаемого типа, когда я хочу указать, что определенный вывод доступен только для чтения. Мне нравится, так как он минималистичный, скрывает детали реализации и отделяет вызываемого от вызывающего.

Но недавно мой коллега утверждал, что IEnumerable<T> следует сохранить для сценариев, которые включают только ленивую оценку, так как в противном случае неясно для вызывающего метода, где обработка исключения должна иметь место - вокруг вызова метода или вокруг итерация. Тогда для активных случаев оценки с выходом только для чтения я должен использовать ReadOnlyCollection.

Звучит вполне разумно для меня, но что бы вы посоветовали? Согласитесь ли вы с этим соглашением для IEnumerable? Или есть лучшие методы обработки исключений вокруг IEnumerable?

На всякий случай, если мой вопрос неясен, я сделал примерный класс, иллюстрирующий проблему. Два метода здесь имеют абсолютно одинаковую сигнатуру, но требуют разной обработки исключений:

public class EvilEnumerable
{
    IEnumerable<int> Throw()
    {
        throw new ArgumentException();
    }

    IEnumerable<int> LazyThrow()
    {
        foreach (var item in Throw())
        {
            yield return item;
        }
    }

    public void Run()
    {
        try
        {
            Throw();
        }
        catch (ArgumentException)
        {
            Console.WriteLine("immediate throw");
        }

        try
        {
            LazyThrow();
        }
        catch (ArgumentException)
        {
            Console.WriteLine("No exception is thrown.");
        }

        try
        {
            foreach (var item in LazyThrow())
            {
                //do smth
            }
        }
        catch (ArgumentException)
        {
            Console.WriteLine("lazy throw");
        }
    }
}

Обновление 1. Вопрос не ограничивается только ArgumentException. Речь идет о передовых методах создания дружественных интерфейсов классов, которые сообщают вам, возвращают ли они ленивый оцененный результат или нет, поскольку это влияет на подход к обработке исключений.

Ответы [ 4 ]

10 голосов
/ 16 ноября 2009

Настоящая проблема здесь - отложенное выполнение. В случае проверки аргументов вы можете сделать это, добавив второй метод:

IEnumerable<int> LazyThrow() {
     // TODO: check args, throwing exception
     return LazyThrowImpl();
}
IEnumerable<int> LazyThrowImpl() {
    // TODO: lazy code using yield
}

Исключения случаются; даже для неотложенного результата (например, List<T>) вы можете получить ошибки (возможно, если другой поток корректирует список во время итерации). Вышеуказанный подход позволяет вам сделать как можно раньше, чтобы уменьшить неожиданные побочные эффекты yield и отложенного выполнения.

3 голосов
/ 16 ноября 2009

Теоретически я согласен с вашим коллегой. Если есть некоторая «работа» для создания результатов, и эта работа может вызвать исключение, и вам не нужна ленивая оценка, то действительно, если сделать результат ленивым, это только усложнит ситуацию.

Однако на практике нет способа посмотреть на тип возвращаемого значения и сделать что-то слишком полезное. Даже если вы сделали тип возврата ReadonlyCollection, он все равно может задержать оценку и выбросить, например (ReadonlyCollection не запечатан, поэтому подкласс мог бы явно реализовать интерфейс IEnumerable и делать дурацкие вещи).

В конце концов, система типов .Net не поможет вам рассуждать об исключительном поведении большинства объектов.

На самом деле, я бы по-прежнему решил возвращать коллекцию ReadonlyCollection, а не IEnumerable, но потому, что он предоставляет более полезные методы (например, O (1) доступ к n-му элементу) по сравнению с IEnumerable. Если вы собираетесь создать коллекцию манифеста, подкрепленную массивом, вы также можете предоставить потребителю полезные аспекты этого. (Вы также можете просто вернуть «свежий» массив, который вызывающий получает в свое распоряжение.)

2 голосов
/ 16 ноября 2009

Я не согласен с вашим коллегой. Документация метода должна следовать стандартному документу, какие исключения он может выдавать. Объявление возвращаемого типа как IEnumerable не делает его доступным только для чтения. Будет ли он доступен только для чтения, зависит от фактической реализации возвращаемого интерфейса. Вызывающая сторона не должна полагаться на то, что она доступна для записи, но это сильно отличается от коллекции, доступной только для чтения

0 голосов
/ 16 ноября 2009

Подобная проблема обсуждалась в постах Эрика Липперта:

http://blogs.msdn.com/ericlippert/archive/2007/09/05/psychic-debugging-part-one.aspx

http://blogs.msdn.com/ericlippert/archive/2007/09/06/psychic-debugging-part-two.aspx

Однако я не буду рассматривать это как аргумент против IEnumerable в целом. Честно говоря, в большинстве случаев, когда перечисление, которое я перечисляю, выдает исключение, я не знаю, что с ним делать, и в основном это относится к какому-то глобальному обработчику ошибок. Конечно, я согласен с тем, что момент выдачи исключения может сбить с толку.

...