views:

163

answers:

4

I used to create interfaces with IEnumerable<T> as return type, whenever I want to specify that a particular output is read-only. I like it as it's minimalistic, hides implementation details and decouples a callee from a caller.

But recently a colleague of mine argued that IEnumerable<T> should be kept for scenarios which involve lazy evaluation only, as otherwise it's unclear for a caller method, where the exception handling should take it's place -- around a method call or around an iteration. Then for the eager evaluation cases with a read-only output I should use a ReadOnlyCollection.

Sounds quite reasonable for me, but what would you recommend? Would you agree with that convention for IEnumerable? Or are there better methods for exception handling around IEnumerable?

Just in case if my question is unclear, I made a sample class illustrating the problem. Two methods in here have exactly the same signature, but they 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 ArgumentException only. It's about best practices for making friendly class interfaces, that tell you whether they return lazy evaluated result or not, because this influences the exception-handling approach.

+7  A: 

The real problem here is the deferred execution. In the case of argument-checking, 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 happen; even for non-deferred result (List<T>, for example) you can get errors (perhaps if another thread adjusts the list while you are iterating). The above approach lets you do as much as possible ahead of time to reduce the unexpected side-effects of yield and deferred execution.

Marc Gravell
Well, that's a useful snippet, but my question wasn't actually limited to checking args, any exception can happen during the lazy evaluation itself (in the LazyThrowImpl that is). So what I wonder is if it would be a reasonable convention to keep IEnumerable<T> only for lazy evaluation cases, thus helping callers to understand where the exception handling code must be.
Yacoder
My point is that any exception can also happen during *regular* evaluation, so it is a moot point. OK *more* generally happens in lazy evaluation, but I'd still say *in general* not to worry about it.
Marc Gravell
+2  A: 

I do not agree with your collegue. The documentation of the method shoul follow the standard an document which exceptions it might throw. Declaring the return type as IEnumerable does not make it read only. Whether or not it's read only dependts on the actual implementation of the interface being returned. The caller shouldnt rely on it being writable but that's very different from the collection being read only

Rune FS
I'm sorry, I understand what you are saying, but I don't see how it answers the question (
Yacoder
@yacoder my point is that no IEnumerable<T> had nothing to do with lazy evaluation. All BCL generic collections implement that interface and List<T> and the like is mit lazily evaluated. Furthermore declaring the return type doesn't say that it _is_ readonly. It just says that the only thing you Can count on is getting an enumerator
Rune FS
A: 

Similar problem 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 treat it as an argument against IEnumerable in general. To be honest, most times when collection I'm enumerating throws an exception I don't know what to do with it and basically it goes to some global error handler. Of course, I agree that the moment the exception is thrown may be confusing.

empi
My question is exactly about the moment the exception is thrown. It's impossible to understand just from the interface if the method is going to throw its exception when I call it, or when I iterate over its result.
Yacoder
That is, when you use IEnumerable<T> for everything.
Yacoder
+3  A: 

In theory, I agree with your colleague. If there is some 'work' to create the results, and that work may cause an exception, and you don't need lazy evaluation, then indeed making the result lazy will just complicate matters.

However in practice, there's no way you can look at the return type and infer anything too useful. Even if you made the return type ReadonlyCollection, it still might delay the evaluation and throw, for example (ReadonlyCollection is not sealed, so a subclass could explicitly implement the IEnumerable interface and do wacky things).

At the end of the day, the .Net type system is not going to help you reason much about the exception behavior of most objects.

I would, in fact, still choose to return a ReadonlyCollection rather than an IEnumerable, but because it exposes more useful methods (like O(1) access to the nth element) compared to IEnumerable. If you're going to produce a manifest collection backed by an array, you might as well give consumers back useful aspects of that. (You might also just return a 'fresh' array that the caller gets to own.)

Brian
Well, surely, one can do nasty things even with the ReadOnlyCollection, but it would require some effort. While throwing out a lazy evaluated IEnumerable versus eager evaluated is effortless. return myInternalCollection.Select(...) vs return myInternalCollection.Select().ToList()
Yacoder