2012-03-09 25 views
17

¿Por qué no podemos usar el rendimiento y el rendimiento en el mismo método?¿Por qué no se puede usar "return" y "yield return" en el mismo método?

Por ejemplo, podemos tener GetIntegers1 y GetIntegers2 a continuación, pero no GetIntegers3.

public IEnumerable<int> GetIntegers1() 
{ 
    return new[] { 4, 5, 6 }; 
} 

public IEnumerable<int> GetIntegers2() 
{ 
    yield return 1; 
    yield return 2; 
    yield return 3; 
} 

public IEnumerable<int> GetIntegers3() 
{ 
    if (someCondition) 
    { 
    return new[] {4, 5, 6}; // compiler error 
    } 
    else 
    { 
    yield return 1; 
    yield return 2; 
    yield return 3; 
    } 
} 
+11

espere un segundo, jon skeet vendrá ahora. – Juvanis

+0

Agregaré que si realmente lo necesita, podría crear un GetIngegers4 que llame a GetIntegers1 O GetIntegers2 dependiendo de una condición. – xanatos

+0

Esto es probablemente obvio, pero en tales casos siempre podría desenrollar su colección y devolver los artículos: foreach (elemento var en new [] {4,5,6}) rendimiento devolver artículo; – Foo42

Respuesta

16

return está ansioso. Devuelve todo el conjunto de resultados a la vez. yield return construye un enumerador. Detrás de escena, el compilador de C# emite la clase necesaria para el enumerador cuando usa yield return. El compilador no busca condiciones de tiempo de ejecución como if (someCondition) al determinar si debe emitir el código para un enumerable o si tiene un método que devuelve una matriz simple. Detecta que en su método está usando ambos, lo que no es posible ya que no puede emitir el código para un enumerador y al mismo tiempo hacer que el método devuelva una matriz normal y todo esto para el mismo método.

+0

Cuando intento depurar el código, puedo ver que recorre todas las líneas de código en la iteración. Entonces, ¿cómo podemos decir que construye el enumerador internamente y lo devuelve solo una vez ... – Karan

7

El compilador vuelve a escribir cualquier método con una instrucción yield (retorno o corte). Actualmente no puede manejar métodos que pueden o no pueden ser yield.

Recomendaría tener una lectura del capítulo 6 de Jon Skeet's C# in Depth, del cual el capítulo 6 está disponible de forma gratuita - cubre bastante bien los bloques de iteradores.

No veo ninguna razón por la que esto no sea posible en futuras versiones del compilador de C# sin embargo. Otros lenguajes .Net son compatibles con algo similar en la forma de un operador 'yield from' (See F# yield!). Si existiera tal operador en C# que le permitirá escribir el código en la forma:

public IEnumerable<int> GetIntegers() 
{ 
    if (someCondition) 
    { 
    yield! return new[] {4, 5, 6}; 
    } 
    else 
    { 
    yield return 1; 
    yield return 2; 
    yield return 3; 
    } 
} 
10

No, no puedes hacer eso - un bloque de iterador (algo con un yield) no puede utilizar el regular (no-rendimiento) return. En su lugar, es necesario utilizar 2 métodos:

public IEnumerable<int> GetIntegers3() 
{ 
    if (someCondition) 
    { 
    return new[] {4, 5, 6}; // compiler error 
    } 
    else 
    { 
    return GetIntegers3Deferred(); 
    } 
} 
private IEnumerable<int> GetIntegers3Deferred() 
{ 
    yield return 1; 
    yield return 2; 
    yield return 3; 
} 

o desde en este caso específico el código para los dos que ya existe en los otros 2 métodos:

public IEnumerable<int> GetIntegers3() 
{ 
    return (someCondition) ? GetIntegers1() : GetIntegers2(); 
} 
3

En teoría me parece que no hay razón ¿por devolución y retorno de rendimiento no se pueden mezclar: sería un asunto fácil para que el compilador primero transformar sintácticamente cualquier frase en return (blabla());:

var myEnumerable = blabla(); 
foreach (var m in myEnumerable) 
    yield return m; 
yield break; 

y luego continuar (para transformar todo el método en ... lo que sea que lo transformen ahora; IEnumerator una clase anónima interna ?!)

Entonces, ¿por qué no elegir para ponerlo en práctica, aquí hay dos conjeturas:

  • que podrían haber decidido que sería confuso para los usuarios que tienen tanto la rentabilidad como retorno de rendimiento a la vez,

  • Devolver todo el enumerable es más rápido y más barato, pero también con ganas; construir a través del retorno de rendimiento es un poco más caro (especialmente si se llama recursivamente, vea la advertencia de Eric Lippert para el cruce en árboles binarios con declaraciones de rendimiento aquí: https://stackoverflow.com/a/3970171/671084 por ejemplo) pero vago. Por lo tanto, un usuario generalmente no quiere mezclar estos: si no necesita pereza (es decirusted conoce toda la secuencia) no sufre la penalización de eficiencia, solo use un método normal. Podrían haber querido obligar al usuario a pensar en esta línea.

Por otro lado, parece que hay situaciones en las que el usuario podría beneficiarse de algunas extensiones sintácticas; Es posible que desee leer esta pregunta y respuestas como un ejemplo (no es la misma pregunta, pero probablemente con un motivo similar): Yield Return Many?

+0

Esto es en realidad una respuesta a "¿Cuáles podrían haber sido las razones por las que Microsoft decidió no admitir ...?", Lo que hubiera sido mejor pregunta en primer lugar. –

0

Creo que la razón principal por la que no funciona es porque se diseña de una manera que no es demasiado complicado, pero es eficaz al mismo tiempo sería difícil, con relativamente poco beneficio.

¿Qué haría exactamente tu código? ¿Devolvería directamente la matriz, o se iteraría sobre ella?

Si devuelve directamente la matriz, entonces tendría que idear reglas complicadas bajo qué condiciones está permitido return, porque return después de no tiene sentido. Y probablemente necesite generar código complicado para decidir si el método devolverá el iterador personalizado o la matriz.

Si desea iterar la colección, probablemente desee una mejor palabra clave. Something like yield foreach. Eso fue realmente considerado, pero finalmente no fue implementado. Creo que recuerdo haber leído que la razón principal es que realmente es muy difícil hacerlo funcionar bien, si tienes varios iteradores anidados.

+1

Es difícil hacerlo funcionar bien con iteradores anidados, pero ese problema se puede resolver con cierta astucia; lo hicieron en C-Omega. Sin embargo, si lo hace, entonces comienza a encontrarse con problemas de corrección cuando esos iteradores anidados contienen manejo de excepciones. Es una idea encantadora y desearía poder hacerlo, pero la relación dolor-ganancia es demasiado alta. –

Cuestiones relacionadas