【问题标题】:Using GetEnumerator in PowerShell on IEnumerable implemented using C# yield return在 IEnumerable 上使用 C# 实现的 PowerShell 中的 GetEnumerator
【发布时间】:2021-06-21 01:37:09
【问题描述】:

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

一个简单的例子:

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

在 PowerShell 中,我无法在 Enumerate() 方法返回的枚举上调用 IEnumerable&lt;T&gt;.GetEnumerator

$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 启动的并行线程来处理可枚举。并且枚举是懒惰且耗时的。因此,在枚举之前将结果收集到某个容器中会降低性能。

【问题讨论】:

    标签: c# .net powershell ienumerable yield-return


    【解决方案1】:

    很遗憾,反射方法对我不起作用。

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

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

    我认为反射不起作用的原因是因为在我的情况下 IEnumerable 的成员项是(自定义类的)对象,而不是像 [int] 或 [string] 这样的基类

    【讨论】:

    • 嗯,这是一个明显的解决方案,它没有回答问题,它说:“并且枚举是惰性的并且是时间密集型的。所以在枚举之前将结果收集到某个容器会有性能损失。”
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

      问题似乎是编译器生成的IEnumerable&lt;T&gt; 类型实现了IEnumerableIEnumerator&lt;T&gt; explicitly

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

      后期绑定的语言通常在 .NET 中显式实现接口存在问题:毕竟您的枚举器属于 &lt;M&gt;d__0 类型,并且该类型实现了两个不同的 GetEnumerator() 方法,它们返回不同的东西。

      (通过调用返回 IEnumerable&lt;int&gt; 的方法获得 &lt;M&gt;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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-21
        • 1970-01-01
        相关资源
        最近更新 更多