what unity does c# yield-return

c# - unity - enumeraciones de ''rendimiento'' que no son ''finalizadas'' por la persona que llama-lo que sucede



yield return c# stack overflow (4)

Me acabo de quedar con una conexión filtrada.

No tu no eres.

¿Cuándo se invoca finalmente?

Cuando se IEnumerator<T> el IEnumerator<T> , lo First que va a hacer después de obtener el primer elemento de la secuencia (como todos deberían estar haciendo cuando usan un IEnumerator<T> ).

Ahora si alguien escribió:

//note no `using` block on `iterator` var iterator = Foo().GetEnumerator(); iterator.MoveNext(); var first = iterator.Current; //note no disposal of iterator

luego se filtraría el recurso, pero allí el error está en el código de la persona que llama, no en el bloque del iterador.

supongo que tengo

IEnumerable<string> Foo() { try { /// open a network connection, start reading packets while(moredata) { yield return packet; } } finally { // close connection } }

(O tal vez hice un ''uso'' - lo mismo). ¿Qué pasa si mi interlocutor se va

var packet = Foo().First();

Me acabo de quedar con una conexión filtrada. ¿Cuándo se invoca finalmente? O lo correcto siempre sucede por arte de magia

editar con respuestas y pensamientos

Mi muestra y otros patrones de llamadas "normales" (por ejemplo, ...) funcionarán muy bien porque eliminan el IEnumerable (en realidad, el IEnumerator devuelto por GetEnumerator). Debo, por lo tanto, tener una persona que llama en algún lugar que está haciendo algo funky (obtener explícitamente un enumerador y no eliminarlo o algo similar). Los haré disparar

el código malo

Encontré un llamador haciendo

IEnumerator<T> enumerator = foo().GetEnumerator();

cambiado a

using(IEnumerator<T> enumerator = foo().GetEnumerator())


No hay magia especial. Si comprueba el documento en IEnumerator<T> , encontrará que hereda de IDisposable . La construcción foreach , como saben, es azúcar sintáctico descompuesto por el compilador en una secuencia de operaciones en un enumerador, y todo se envuelve en un bloque try / finally , llamando al objeto Dispose on enumerator.

Cuando el compilador convierte un método de iterador (es decir, un método que contiene declaraciones de yield ) en una implementación de IEnumerable<T> / IEnumerator<T> , maneja la lógica try / finally en el método Dispose de la clase generada.

Puede intentar usar ILDASM para analizar el código generado en su caso. Va a ser bastante complejo, pero te dará la idea.


No terminarías con una conexión filtrada. Los objetos IDisposable producidos por yield return son IDisposable , y las funciones LINQ son cuidadosas para asegurar una eliminación adecuada.

Por ejemplo, First() se implementa de la siguiente manera:

public static TSource First<TSource>(this IEnumerable<TSource> source) { if (source == null) throw Error.ArgumentNull("source"); IList<TSource> list = source as IList<TSource>; if (list != null) { if (list.Count > 0) return list[0]; } else { using (IEnumerator<TSource> e = source.GetEnumerator()) { if (e.MoveNext()) return e.Current; } } throw Error.NoElements(); }

Tenga en cuenta cómo se envuelve el resultado de source.GetEnumerator() . Esto asegura la llamada a Dispose , que a su vez asegura la llamada de su código en el bloque finally .

Lo mismo ocurre con las iteraciones por bucle foreach : el código garantiza la eliminación del enumerador independientemente de si la enumeración se completa o no.

El único caso en el que puede terminar con una conexión filtrada es cuando llama a GetEnumerator usted mismo y no lo elimina adecuadamente. Sin embargo, esto es un error en el código que usa IEnumerable , no en el IEnumerable .


Ok esta pregunta podría usar un poco de datos empíricos.

Utilizando VS2015 y un proyecto de borrador, escribí el siguiente código:

private IEnumerable<string> Test() { using (TestClass t = new TestClass()) { try { System.Diagnostics.Debug.Print("1"); yield return "1"; System.Diagnostics.Debug.Print("2"); yield return "2"; System.Diagnostics.Debug.Print("3"); yield return "3"; System.Diagnostics.Debug.Print("4"); yield return "4"; } finally { System.Diagnostics.Debug.Print("Finally"); } } } private class TestClass : IDisposable { public void Dispose() { System.Diagnostics.Debug.Print("Disposed"); } }

Y luego lo llamó de dos maneras:

foreach (string s in Test()) { System.Diagnostics.Debug.Print(s); if (s == "3") break; } string f = Test().First();

Que produce la siguiente salida de depuración

1 1 2 2 3 3 Finally Disposed 1 Finally Disposed

Como podemos ver, ejecuta tanto el bloque finally como el método Dispose .