c# - ejemplo - ¿Por qué no puede rendir el retorno dentro de un bloque de prueba con una captura?
yield return list c# (5)
Acepté la respuesta de THE INVINCIBLE SKEET hasta que alguien de Microsoft viniera a echarle agua fría a la idea. Pero no estoy de acuerdo con la parte de la materia de opinión; por supuesto, un compilador correcto es más importante que uno completo, pero el compilador de C # ya es muy astuto al clasificar esta transformación para nosotros en la medida de lo posible. Un poco más de exhaustividad en este caso facilitaría el uso del lenguaje, enseñarlo, explicarlo, con menos casos extremos o errores. Entonces creo que valdría la pena el esfuerzo extra. Algunos chicos en Redmond se rascan la cabeza durante una quincena y, como resultado, millones de codificadores durante la próxima década pueden relajarse un poco más.
(También albergo un deseo sórdido de que exista una forma de hacer que el yield return
arroje una excepción que ha sido introducida en la máquina de estados "desde afuera", por el código que impulsa la iteración. Pero mis razones para querer esto son bastante oscuras .)
En realidad, una consulta que tengo sobre la respuesta de Jon tiene que ver con el lanzamiento de expresión de rendimiento de retorno.
Obviamente, el retorno de rendimiento 10 no es tan malo. Pero esto sería malo:
yield return File.ReadAllText("c://missing.txt").Length;
Entonces, ¿no tendría más sentido evaluar esto dentro del bloque Try / catch anterior?
case just_before_try_state:
try
{
Console.WriteLine("a");
__current = File.ReadAllText("c://missing.txt").Length;
}
catch (Something e)
{
CatchBlock();
goto case post;
}
return true;
El próximo problema sería anidar try / catch blocks y volver a lanzar excepciones:
try
{
Console.WriteLine("x");
try
{
Console.WriteLine("a");
yield return 10;
Console.WriteLine("b");
}
catch (Something e)
{
Console.WriteLine("y");
if ((DateTime.Now.Second % 2) == 0)
throw;
}
}
catch (Something e)
{
Console.WriteLine("Catch block");
}
Console.WriteLine("Post");
Pero estoy seguro de que es posible ...
Lo siguiente está bien:
try
{
Console.WriteLine("Before");
yield return 1;
Console.WriteLine("After");
}
finally
{
Console.WriteLine("Done");
}
El bloque finally
se ejecuta cuando todo ha terminado de ejecutarse ( IEnumerator<T>
admite IDisposable
para proporcionar una forma de garantizar esto incluso cuando la enumeración se abandona antes de que finalice).
Pero esto no está bien:
try
{
Console.WriteLine("Before");
yield return 1; // error CS1626: Cannot yield a value in the body of a try block with a catch clause
Console.WriteLine("After");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Supongamos (por razones de argumento) que una u otra de las llamadas WriteLine
arroja una excepción dentro del bloque try. ¿Cuál es el problema con continuar la ejecución en el bloque catch
?
Por supuesto, la parte devuelta de rendimiento es (actualmente) incapaz de tirar nada, pero ¿por qué eso nos impide tener un try
/ catch
de catch
para hacer frente a las excepciones lanzadas antes o después de un yield return
?
Actualización: ¡ Aquí hay un comentario interesante de Eric Lippert , parece que ya tienen suficientes problemas para implementar correctamente el comportamiento try / finally!
EDITAR: La página de MSDN en este error es: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx . Sin embargo, no explica por qué.
Me gustaría especular que debido a la forma en que la pila de llamadas se activa / desenrolla cuando se obtiene el rendimiento de un enumerador, resulta imposible que un bloque try / catch realmente "atrape" la excepción. (porque el bloque de retorno de rendimiento no está en la pila, a pesar de que originó el bloque de iteración)
Para obtener una idea de lo que estoy hablando, configure un bloque iterador y un foreach usando ese iterador. Comprueba cómo se ve la pila de llamadas dentro del bloque foreach y luego márcala dentro del bloque try / finally del iterador.
Sospecho que esto es una cuestión de practicidad en lugar de factibilidad. Sospecho que hay muy pocas veces donde esta restricción es realmente un problema que no se puede solucionar, pero la complejidad añadida en el compilador sería muy significativa.
Hay algunas cosas como esta que ya he encontrado:
- Atributos que no pueden ser genéricos
- Incapacidad para que X se derive de XY (una clase anidada en X)
- Bloques del iterador usando campos públicos en las clases generadas
En cada uno de estos casos, sería posible ganar un poco más de libertad, a costa de una complejidad adicional en el compilador. El equipo hizo la elección pragmática, por lo que los aplaudo. Prefiero tener un lenguaje un poco más restrictivo con un compilador preciso del 99.9% (sí, hay errores, encontré uno en SO el otro día) que un lenguaje flexible que no pudo compilar correctamente.
EDITAR: Aquí hay una pseudo-prueba de cómo es posible.
Considere eso:
- Puede asegurarse de que la parte devuelta de rendimiento en sí misma no arroje una excepción (precalcula el valor, y luego simplemente establece un campo y devuelve "verdadero")
- Está permitido try / catch, que no usa yield return en un bloque iterador.
- Todas las variables locales en el bloque de iteradores son variables de instancia en el tipo generado, por lo que puede mover libremente el código a nuevos métodos
Ahora transforma:
try
{
Console.WriteLine("a");
yield return 10;
Console.WriteLine("b");
}
catch (Something e)
{
Console.WriteLine("Catch block");
}
Console.WriteLine("Post");
en (una especie de pseudo-código):
case just_before_try_state:
try
{
Console.WriteLine("a");
}
catch (Something e)
{
CatchBlock();
goto case post;
}
__current = 10;
return true;
case just_after_yield_return:
try
{
Console.WriteLine("b");
}
catch (Something e)
{
CatchBlock();
}
goto case post;
case post;
Console.WriteLine("Post");
void CatchBlock()
{
Console.WriteLine("Catch block");
}
La única duplicación es en la configuración de bloques de prueba / captura, pero eso es algo que el compilador ciertamente puede hacer.
Puede que me haya perdido algo aquí, si es así, ¡por favor házmelo saber!
Todas las declaraciones de yield
en una definición de iterador se convierten a un estado en una máquina de estados que utiliza de manera efectiva una instrucción de switch
para avanzar estados. Si generara código para las declaraciones de yield
en un try / catch, tendría que duplicar todo en el bloque try
para cada sentencia de yield
, excluyendo cualquier otra declaración de yield
para ese bloque. Esto no siempre es posible, especialmente si una declaración de yield
depende de una anterior.
para aquellos que usan Unity:
yield return new WaitForSeconds(startWait);
while (numWaves < 4 && _myPauseState)
{
for (int i = 0; i < hazardCount;)
{
//spawn code
}
yield return new WaitForSeconds(waveWait);
numWaves++;
}
es realmente posible dentro de un ienumerator