Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using GetEnumerator in PowerShell on IEnumerable implemented using C# yield return

I have an API that returns IEnumerable<T> (or IEnumerable), which is internally implemented in C# using yield return.

A trivial example:

public class Class1
{
    public IEnumerable<int> Enumerate()
    {
        yield return 1;
    }
}

In PowerShell, I cannot call IEnumerable<T>.GetEnumerator on the enumerable returned by the method Enumerate():

$cls = New-Object Class1

$enumerable = $cls.Enumerate()

Write-Host $enumerable.GetType()

$enumerator = $enumerable.GetEnumerator()

It fails with:

Cannot find an overload for "GetEnumerator" and the argument count: "0".

The $enumerable.GetType() returns expected:

Class1+d__0

An identical code in C# works as expected.


If the method is implemented using a plain return:

return new[] { 1 };

then there's no problem in PowerShell.


Why PowerShell does not see the GetEnumerator()?

I have found this somewhat related question: PowerShell/GetEnumerator, but it does not really give me the answer/solution (or I do not see it).


To explain, why I want to use the IEnumerator, instead of using PowerShell foreach: I want to process the enumerable using parallel threads started using Start-ThreadJob. And the enumeration is lazy and time intensive. So collecting the results to some container prior to the enumeration would have a performance penalty.

like image 871
Martin Prikryl Avatar asked Nov 06 '22 02:11

Martin Prikryl


1 Answers

The problem appears to be that the compiler-generated IEnumerable<T> type implements IEnumerable and IEnumerator<T> explicitly:

private sealed class <M>d__0 : IEnumerable<int>, IEnumerable, ...
{
    [DebuggerHidden]
    IEnumerator IEnumerable.GetEnumerator()
    {
        ...
    }

    [DebuggerHidden]
    IEnumerator<string> IEnumerable<int>.GetEnumerator()
    {
        ...
    }

    ...
}

Late-bound languages typically have problems with explicitly implemented interfaces in .NET: after all your enumerator is of type <M>d__0, and that type implements two different GetEnumerator() methods which return different things.

(It doesn't matter that it got that instance of <M>d__0 by calling a method which returned an IEnumerable<int>: late-bound languages forget all about that bit of type information).

The language needs to provide special syntax to let you say which interface's version of GetEnumerator() you want to call. IronPython for example would let you say something like:

System.Collections.Generic.IEnumerable[int].GetEnumerator(enumerator)

I'm not aware of any PowerShell syntax to do the same (and my searching didn't find anything), instead people seem to be using reflection:

[System.Collections.Generic.IEnumerable[int]].GetMethod("GetEnumerator").Invoke($enumerable, $Null)
like image 137
canton7 Avatar answered Nov 12 '22 18:11

canton7