Problem with exception handling for IEnumerable <T>, it depends on laziness

I used to create interfaces with IEnumerable<T> as the return type, whenever I want to indicate that a particular output is read-only. I like how minimalistic it is, hiding implementation details and separating the caller from the caller.

But recently, my colleague argued that IEnumerable<T> should be stored for scripts that only include lazy evaluation, because otherwise it is not clear to the caller method where exception handling will take its place - around the method call or around the iteration. Then for greedy evaluation cases with read-only output, I have to use ReadOnlyCollection .

Sounds pretty reasonable to me, but what would you recommend? Do you agree with this convention for IEnumerable? Or are there more efficient exception handling methods around IEnumerable?

Just in case, if my question is not clear, I made a class sample illustrating the problem. Two methods here have exactly the same signature, but require different exception handling:

 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"); } } } 

Update 1. The question is not limited to the ArgumentException argument. This is about the best methods for creating friendly class interfaces that tell you whether they believe the lazy result of the evaluation or not, because it affects the exception handling approach.

+7
c # exception-handling ienumerable
source share
4 answers

The real problem here is delayed execution. In case of checking the arguments, you can do this by adding a second method:

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

Exceptions occur; even for a non-delayed result (for example, List<T> ) you can get errors (possibly if another thread adjusts the list during iteration). The above approach allows you to take as much time as possible to reduce unexpected side effects of yield and delayed execution.

+9
source share

In theory, I agree with your colleague. If there is some kind of β€œwork” for creating results, and this work may cause an exception, and you do not need a lazy assessment, then, indeed, a lazy result will simply complicate the situation.

However, in practice, you cannot look at the type of return and conclude that it is too useful. Even if you made the return type ReadonlyCollection, it can still delay evaluation and throw, for example (ReadonlyCollection is not sealed, so a subclass can explicitly implement the IEnumerable interface and do crazy things).

At the end of the day, a system such as .Net will not help you talk much about the exceptional behavior of most objects.

I would still decide to return ReadonlyCollection, not IEnumerable, but because it provides more useful methods (like O (1) access to the nth element) compared to IEnumerable. If you are going to create a manifest collection supported by an array, you can also give consumers useful useful aspects of this. (You can also just return the β€œfresh” array that the caller receives.)

+3
source share

I do not agree with your colleague. The documentation for the shoul method conforms to the standard of the document whose exceptions it can throw. Declaring a return type because IEnumerable does not make it read-only. Regardless of whether it reads, it depends only on the actual implementation of the returned interface. The caller should not rely on being writable, but very different from the collection just read

+2
source share

A similar issue was discussed in Eric Lippert's posts:

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

However, I will not consider it as an argument against IEnumerable in general. Honestly, in most cases when the collection I am listing throws an exception, I don’t know what to do with it, and basically it refers to some global error handler. Of course, I agree that the moment the exception is thrown can be confusing.

0
source share

All Articles