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

问题描述

我有一个返回 IEnumerable<T>(或 IEnumerable)的 API,它是在 C# 中使用 yield return 内部实现的。

一个简单的例子:

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

在 PowerShell 中,我无法对 IEnumerable<T>.GetEnumerator 方法返回的枚举调用 Enumerate()

$cls = New-Object Class1

$enumerable = $cls.Enumerate()

Write-Host $enumerable.GetType()

$enumerator = $enumerable.GetEnumerator()

它失败了:

找不到“GetEnumerator”的重载和参数计数:“0”。

$enumerable.GetType() 预期返回:

Class1+d__0

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


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

return new[] { 1 };

那么在 PowerShell 中就没有问题了。


为什么 PowerShell 看不到 GetEnumerator()

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


解释一下,为什么我要使用 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() 方法,它们返回不同的东西。>

(通过调用返回 <M>d__0 的方法获得 IEnumerable<int> 的实例并不重要:后期绑定语言忘记了所有关于该位类型信息的信息)。

该语言需要提供特殊的语法,让您说出要调用的 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,您可以在生成的显式实现之上添加一个隐式实现接口的包装类。

您需要选择要隐式实现的接口,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;
    }
}