Search code examples
c#foreach

What is the difference between returning Select() and yield return?


Examples:

public IEnumerable<Class> ReturnClass()
{
    var list = ...;

    return list.Select(item =>
    {
        // if, else, etc

        return item;
    });
}

or

public IEnumerable<Class> ReturnClass()
{
    var list = ...;

    foreach (var item in list)
    {
        // if, else, etc

        yield return item;
    }
}

Are there differences in the generated code? Performance? Etc

I only found one question on stackoverflow about this subject, but it raised more questions than it answered questions


Solution

  • The main differences is that yield return creates state machine and thus executes lazily. Lazy execution can be problematic in case of validation, let me show some toy example:

    // State Machine, Lazy execution
    public IEnumerable<int> ReturnClassLazy(int count)
    {
        // Validation (will be called lazily)
        if (count < 0)
            throw new ArgumentOutOfRangeException(nameof(count));
    
        var list = Enumerable.Range(1, count);
    
        foreach (var item in list) 
            yield return item * item;
    }
    
    // Eager execution
    public IEnumerable<int> ReturnClassEager(int count)
    {
        // Validation (will be called eagerly)
        if (count < 0)
            throw new ArgumentOutOfRangeException(nameof(count));
    
        var list = Enumerable.Range(1, count);
    
        return list.Select(item => item * item);
    }
    

    In case of eager version (when we return Select(...)), the method will be executed immediately (note, that validation will be called):

    // ArgumentOutOfRangeException is thrown (invalid count argument, -1)
    var result = ReturnClassEager(-1);
    

    while in case of lazy call (yield return version)

    // Nothing happens (even if count argument -1 is invalid)
    var result = ReturnClassLazy(-1);
    
    ... /* imagine 10000 lines of code here */
    
    // Only here (when we materialize) 
    // the exception is thrown due to invalid count argument -1
    int sum = result.Sum();
    

    Usually, we want validation exception be thrown as early as possible, that's why eager version is easier to maintain.