简体   繁体   中英

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.

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)

To complement the @canton7's answer: Instead of using the reflection in PowerShell, you might want modify the C# code to use an implicit implementation. If you want to keep using the yield return , you can add a wrapper class that implements the interface implicitly on top of the generated explicit implementation.

You need to choose what interface you want to implement implicitly, the IEnumerable<T> or IEnumerable . For a use in PowerShell, it probably does not matter. The code below implements IEnumerable<T> implicitly.

internal class ImplicitEnumerable<T> : IEnumerable<T>
{
    private readonly IEnumerable<T> _enumerable;

    public ImplicitEnumerable(IEnumerable<T> enumerable)
    {
        _enumerable = enumerable;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _enumerable.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

And use it like this:

public class Class1
{
    public IEnumerable<int> Enumerate()
    {
        return new ImplicitEnumerable<int>(DoEnumerate());
    }

    private IEnumerable<int> DoEnumerate()
    {
        yield return 1;
    }
}

Unfortunately the reflection method didn't work for me.

But I managed to enumerate through the IEnumerable by using LINQ and the method ToArray(), like so:

$array = [Linq.Enumerable]::ToArray($enumerable)

I think the reason why reflection did not work is because the member items of the IEnumerable in my case, were objects (of a custom class), and not base classes like [int] or [string]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM