Tengo una API que devuelve IEnumerable<T> (o IEnumerable ), que se implementa internamente en C# mediante yield return .
Un ejemplo trivial:
public class Class1 { public IEnumerable<int> Enumerate() { yield return 1; } } En PowerShell, no puedo llamar a IEnumerable<T>.GetEnumerator en el enumerable devuelto por el método Enumerate() :
$cls = New-Object Class1 $enumerable = $cls.Enumerate() Write-Host $enumerable.GetType() $enumerator = $enumerable.GetEnumerator()Falla con:
No se puede encontrar una sobrecarga para "GetEnumerator" y el recuento de argumentos: "0".
El $enumerable.GetType() devuelve lo esperado:
Clase1+d__0
Un código idéntico en C# funciona como se esperaba.
Si el método se implementa usando un simple return :
return new[] { 1 };entonces no hay problema en PowerShell.
¿Por qué PowerShell no ve GetEnumerator() ?
Encontré esta pregunta algo relacionada: PowerShell/GetEnumerator , pero realmente no me da la respuesta/solución (o no la veo).
Para explicar, por qué quiero usar IEnumerator , en lugar de usar PowerShell foreach : quiero procesar el enumerable usando subprocesos paralelos que comenzaron con Start-ThreadJob . Y la enumeración es perezosa y requiere mucho tiempo. Por lo tanto, recopilar los resultados en algún contenedor antes de la enumeración tendría una penalización de rendimiento.
El problema parece ser que el tipo IEnumerable<T> generado por el compilador implementa IEnumerable e IEnumerator<T> explícitamente :
private sealed class <M>d__0 : IEnumerable<int>, IEnumerable, ... { [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { ... } [DebuggerHidden] IEnumerator<string> IEnumerable<int>.GetEnumerator() { ... } ... } Los lenguajes enlazados en tiempo de ejecución generalmente tienen problemas con las interfaces implementadas explícitamente en .NET: después de todo, su enumerador es del tipo <M>d__0 , y ese tipo implementa dos métodos GetEnumerator() diferentes que devuelven cosas diferentes.
(No importa que obtuvo esa instancia de <M>d__0 llamando a un método que devolvió un IEnumerable<int> : los lenguajes enlazados en tiempo de ejecución se olvidan de ese bit de información de tipo).
El lenguaje debe proporcionar una sintaxis especial que le permita decir a qué versión de interfaz de GetEnumerator() desea llamar. IronPython, por ejemplo, le permitiría decir algo como :
System.Collections.Generic.IEnumerable[int].GetEnumerator(enumerator)No conozco ninguna sintaxis de PowerShell para hacer lo mismo (y mi búsqueda no encontró nada), en cambio, las personas parecen estar usando la reflexión :
[System.Collections.Generic.IEnumerable[int]].GetMethod("GetEnumerator").Invoke($enumerable, $Null)Para complementar la respuesta de @canton7: en lugar de usar el reflejo en PowerShell, es posible que desee modificar el código C# para usar una implementación implícita. Si desea seguir usando el yield return , puede agregar una clase contenedora que implemente la interfaz implícitamente sobre la implementación explícita generada.
Debe elegir qué interfaz desea implementar implícitamente, IEnumerable<T> o IEnumerable . Para un uso en PowerShell, probablemente no importe. El siguiente código implementa IEnumerable<T> implícitamente.
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(); } }Y utilízalo así:
public class Class1 { public IEnumerable<int> Enumerate() { return new ImplicitEnumerable<int>(DoEnumerate()); } private IEnumerable<int> DoEnumerate() { yield return 1; } }Desafortunadamente, el método de reflexión no funcionó para mí.
Pero logré enumerar a través de IEnumerable usando LINQ y el método ToArray(), así:
$array = [Linq.Enumerable]::ToArray($enumerable)Creo que la razón por la que la reflexión no funcionó es porque los elementos miembros de IEnumerable en mi caso eran objetos (de una clase personalizada) y no clases base como [int] o [string]