Home > OS >  Cannot yield even though the return type is IEnumerable<U>
Cannot yield even though the return type is IEnumerable<U>

Time:09-07

Consider the following trivial example:

class Filter<T, U>
    where T : IEnumerable<U>
    where U : IEquatable<U>
{
    private readonly U id;

    public Filter(U id) => this.id = id;

    public T Eval_A(T items)
    {
        foreach (var item in items)
            if (item.Equals(id))
                yield return item;
    }

    public IEnumerable<U> Eval_B(IEnumerable<U> items)
    {
        foreach (var item in items)
            if (item.Equals(id))
                yield return item;
    }
}

Even though T is of type IEnumerable<U>, an error still occurs for T Eval_A(T items) as follows.

The body of 'accessor' cannot be an iterator block because 'type' is not an iterator interface type

MSDN said:

This error occurs if an iterator accessor is used but the return type is not one of the iterator interface types: IEnumerable, IEnumerable<T>, IEnumerator, IEnumerator<T>. To avoid this error, use one of the iterator interface types as a return type.

I think the return type does conforms to the requirement.

What am I missing here?

CodePudding user response:

The return type of the method has to be exactly one of IEnumerable, IEnumerable<T>, IEnumerator or IEnumerator<T>, and not T, which is some type that implements IEnumerable<U>.

This is because the way that iterator methods work, is that the compiler generates a type implementing one of the above interfaces, with the MoveNext and Current correctly implemented so that it matches the behaviour of your method, and then it rewrites your method to return an instance of that type instead.

For example, for:

public IEnumerable<int> GetSingleDigitNumbersLoop()
{
    int index = 0;
    while (index < 10)
        yield return index  ;
}

The following is generated (seen using SharpLab.io):

[IteratorStateMachine(typeof(<GetSingleDigitNumbersLoop>d__0))]
public IEnumerable<int> GetSingleDigitNumbersLoop()
{
    return new <GetSingleDigitNumbersLoop>d__0(-2);
}

[CompilerGenerated]
private sealed class <GetSingleDigitNumbersLoop>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IDisposable, IEnumerator
{
    private int <>1__state;

    private int <>2__current;

    private int <>l__initialThreadId;

    private int <index>5__2;

    int IEnumerator<int>.Current
    {
        [DebuggerHidden]
        get
        {
            return <>2__current;
        }
    }

    object IEnumerator.Current
    {
        [DebuggerHidden]
        get
        {
            return <>2__current;
        }
    }

    [DebuggerHidden]
    public <GetSingleDigitNumbersLoop>d__0(int <>1__state)
    {
        this.<>1__state = <>1__state;
        <>l__initialThreadId = Environment.CurrentManagedThreadId;
    }

    [DebuggerHidden]
    void IDisposable.Dispose()
    {
    }

    private bool MoveNext()
    {
        int num = <>1__state;
        if (num != 0)
        {
            if (num != 1)
            {
                return false;
            }
            <>1__state = -1;
        }
        else
        {
            <>1__state = -1;
            <index>5__2 = 0;
        }
        if (<index>5__2 < 10)
        {
            <>2__current = <index>5__2  ;
            <>1__state = 1;
            return true;
        }
        return false;
    }

    bool IEnumerator.MoveNext()
    {
        //ILSpy generated this explicit interface implementation from .override directive in MoveNext
        return this.MoveNext();
    }

    [DebuggerHidden]
    void IEnumerator.Reset()
    {
        throw new NotSupportedException();
    }

    [DebuggerHidden]
    IEnumerator<int> IEnumerable<int>.GetEnumerator()
    {
        if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
        {
            <>1__state = 0;
            return this;
        }
        return new <GetSingleDigitNumbersLoop>d__0(0);
    }

    [DebuggerHidden]
    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<int>)this).GetEnumerator();
    }
}

<GetSingleDigitNumbersLoop>d__0 is the type that implements IEnumerable<int> that GetSingleDigitNumbersLoop is rewritten to return instead.

Now, if GetSingleDigitNumbersLoop were declared to return some other type, like a type parameter T for example, the following wouldn't work:

public T GetSingleDigitNumbersLoop()
{
    return new <GetSingleDigitNumbersLoop>d__0(-2);
}

Because <GetSingleDigitNumbersLoop>d__0 is not necessarily a T. In fact it almost never would be, because it is a compiler generated type!

See also: yield statement implementation

  • Related