在使用 C# yield return 实现的 IEnumerable 上的 PowerShell 中使用 GetEnumerator

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

我有一个 API returns IEnumerable<T>(或 IEnumerable),它是在 C# 中使用 yield return.[=28= 内部实现的]

一个简单的例子:

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

在 PowerShell 中,我无法对方法 Enumerate():

返回的可枚举调用 IEnumerable<T>.GetEnumerator
$cls = New-Object Class1

$enumerable = $cls.Enumerate()

Write-Host $enumerable.GetType()

$enumerator = $enumerable.GetEnumerator()

它失败了:

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

预期的$enumerable.GetType()returns:

Class1+d__0

C# 中的相同代码按预期工作。


如果该方法是使用普通 return:

return new[] { 1 };

那么PowerShell就没有问题了。


为什么 PowerShell 看不到 GetEnumerator()

我发现了这个有点相关的问题:PowerShell/GetEnumerator,但它并没有真正给我 answer/solution(或者我没有看到它)。


解释一下,为什么我要使用 IEnumerator,而不是使用 PowerShell foreach:我想使用开始使用 Start-ThreadJob 的并行线程来处理可枚举。并且枚举是惰性的并且是时间密集型的。因此,在枚举之前将结果收集到某个容器中会降低性能。

问题似乎是编译器生成的 IEnumerable<T> 类型实现了 IEnumerableIEnumerator<T> explicitly:

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

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

    ...
}

后期绑定语言通常在 .NET 中显式实现接口方面存在问题:毕竟您的枚举器是 <M>d__0 类型,并且该类型实现了两种不同的 GetEnumerator() 方法,其中 return 不同的东西。

(它通过调用一个 returned 一个 IEnumerable<int> 的方法获得了 <M>d__0 的实例并不重要:后期绑定语言完全忘记了那一点类型信息)。

该语言需要提供特殊的语法来让你说出你想调用哪个接口的 GetEnumerator() 版本。例如 IronPython 会 let you say something like:

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

我不知道有任何 PowerShell 语法可以做同样的事情(我的搜索没有找到任何东西),而是人们 seem to be using reflection:

[System.Collections.Generic.IEnumerable[int]].GetMethod("GetEnumerator").Invoke($enumerable, $Null)

补充@canton7 的答案:您可能希望修改 C# 代码以使用隐式实现,而不是在 PowerShell 中使用反射。如果您想继续使用 yield return,您可以添加一个包装器 class,它在生成的显式实现之上隐式实现该接口。

您需要选择要隐式实现的接口,IEnumerable<T>IEnumerable。对于在 PowerShell 中使用,它可能无关紧要。下面的代码隐式实现了 IEnumerable<T>

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();
    }
}

并像这样使用它:

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

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

不幸的是,反射方法对我不起作用。

但是我设法通过使用 LINQ 和 ToArray() 方法枚举了 IEnumerable,如下所示:

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

我认为反射不起作用的原因是因为在我的例子中 IEnumerable 的成员项是对象(自定义 class),而不是像 class 这样的基础 classes int] 或 [string]