¿Qué significa "ruptura de rendimiento"? hacer en C #?


494

He visto esta sintaxis en MSDN: yield breakpero no sé qué hace. ¿Alguien sabe?


26
Sin embargo, la documentación de MSDN lo menciona pero no lo explica. Esa es una mala manera de provocar a un desarrollador hambriento de conocimiento.
Phil

3
El rendimiento de rendimiento elimina la necesidad de una lista de respaldo, es decir, no necesita codificar algo como MyList.Add(...)simplemente hacer yield return .... Si necesita salir del bucle prematuramente y devolver la lista de respaldo virtual que usayield break;
The Muffin Man el

Respuestas:


529

Especifica que un iterador ha llegado a su fin. Puede pensar yield breakcomo una returndeclaración que no devuelve un valor.

Por ejemplo, si define una función como un iterador, el cuerpo de la función puede verse así:

for (int i = 0; i < 5; i++)
{
    yield return i;
}

Console.Out.WriteLine("You will see me");

Tenga en cuenta que después de que el ciclo haya completado todos sus ciclos, se ejecuta la última línea y verá el mensaje en la aplicación de la consola.

O así con yield break:

int i = 0;
while (true)
{
    if (i < 5)
    {
        yield return i;
    }
    else
    {
        // note that i++ will not be executed after this
        yield break;
    }
    i++;
}

Console.Out.WriteLine("Won't see me");

En este caso, la última declaración nunca se ejecuta porque dejamos la función antes.


8
¿Podría ser simplemente en breaklugar de yield breaken su ejemplo anterior? El compilador no se queja de eso.
orad

85
@orad un simple breaken este caso detendría el ciclo, pero no abortaría la ejecución del método, por lo tanto, la última línea se ejecutaría y el "No me verá el texto" se vería realmente.
Damir Zekić

55
@Damir Zekić ¿Podría agregar también a su respuesta por qué preferiría el límite de rendimiento sobre el rendimiento y cuáles son las diferencias entre ambos?
Bruno Costa

11
@ DamirZekić regresando nulo y el límite de rendimiento no es lo mismo. Si devuelve nulo, puede NullReferenceExceptionobtener el enumerador de IEnumerable, mientras que con el límite de rendimiento no lo hace (hay una instancia sin elementos).
Bruno Costa

66
@BrunoCosta Intentar obtener retornos regulares en el mismo método da un error de compilación. Usando yield return xalertas el compilador quiere que este método sea azúcar sintáctico para crear un Enumeratorobjeto. Este enumerador tiene método MoveNext()y propiedad Current. MoveNext () ejecuta el método hasta una yield returndeclaración y convierte ese valor en Current. La próxima vez que se llama a MoveNext, la ejecución continúa desde allí. yield breakestablece Current en nulo, señalando el final de este enumerador, para que foreach (var x in myEnum())finalice a.
Wolfzoon

57

Finaliza un bloque iterador (por ejemplo, dice que no hay más elementos en IEnumerable).


2
con [+1], aunque académicamente no hay iteradores en .NET. solo enumeradores (una dirección, hacia adelante)
Shaun Wilson

@Shaun Wilson, pero con la yieldpalabra clave puede iterar la colección en ambas direcciones, además, puede tomar todos los elementos siguientes de la colección que no
estén

@monstr puede iterar la colección de cualquier manera y no necesita yieldhacerlo. No digo que te equivoques, veo lo que estás sugiriendo; pero, académicamente, no hay iteradores en .NET, solo enumeradores (una dirección, hacia adelante): a diferencia de stdc ++, no hay un "marco de iterador genérico" definido en el CTS / CLR. LINQ ayuda a cerrar la brecha con métodos de extensión que utilizan yield return y también métodos de devolución de llamada , pero son métodos de extensión de primera clase, no iteradores de primera clase. lo resultante IEnumerableno puede iterar en ninguna otra dirección que no sea hacia adelante con la persona que llama.
Shaun Wilson

29

Le dice al iterador que ha llegado al final.

Como ejemplo:

public interface INode
{
    IEnumerable<Node> GetChildren();
}

public class NodeWithTenChildren : INode
{
    private Node[] m_children = new Node[10];

    public IEnumerable<Node> GetChildren()
    {
        for( int n = 0; n < 10; ++n )
        {
            yield return m_children[ n ];
        }
    }
}

public class NodeWithNoChildren : INode
{
    public IEnumerable<Node> GetChildren()
    {
        yield break;
    }
}

21

yieldbásicamente hace que un IEnumerable<T>método se comporte de manera similar a un hilo programado de forma cooperativa (en lugar de preventivamente).

yield returnes como un hilo que llama a una función de "programación" o "suspensión" para ceder el control de la CPU. Al igual que un subproceso, el IEnumerable<T>método recupera los controles en el punto inmediatamente posterior, con todas las variables locales que tienen los mismos valores que tenían antes de ceder el control.

yield break es como un hilo que llega al final de su función y termina.

La gente habla de una "máquina de estado", pero una máquina de estado es todo un "hilo". Un subproceso tiene algún estado (es decir, valores de variables locales), y cada vez que se programa toma algunas acciones para alcanzar un nuevo estado. El punto clave yieldes que, a diferencia de los subprocesos del sistema operativo a los que estamos acostumbrados, el código que lo usa se congela en el tiempo hasta que la iteración se avanza o finaliza manualmente.



9

La yield breakdeclaración hace que la enumeración se detenga. En efecto, yield breakcompleta la enumeración sin devolver ningún elemento adicional.

Considere que en realidad hay dos formas en que un método iterador podría dejar de iterar. En un caso, la lógica del método podría salir naturalmente del método después de devolver todos los elementos. Aquí hay un ejemplo:

IEnumerable<uint> FindPrimes(uint startAt, uint maxCount)
{
    for (var i = 0UL; i < maxCount; i++)
    {
        startAt = NextPrime(startAt);
        yield return startAt;
    }

    Debug.WriteLine("All the primes were found.");
}

En el ejemplo anterior, el método iterador naturalmente dejará de ejecutarse una vez que maxCountse hayan encontrado los números primos.

La yield breakdeclaración es otra forma para que el iterador deje de enumerar. Es una forma de salir temprano de la enumeración. Aquí está el mismo método que el anterior. Esta vez, el método tiene un límite en la cantidad de tiempo que puede ejecutar el método.

IEnumerable<uint> FindPrimes(uint startAt, uint maxCount, int maxMinutes)
{
    var sw = System.Diagnostics.Stopwatch.StartNew();
    for (var i = 0UL; i < maxCount; i++)
    {
        startAt = NextPrime(startAt);
        yield return startAt;

        if (sw.Elapsed.TotalMinutes > maxMinutes)
            yield break;
    }

    Debug.WriteLine("All the primes were found.");
}

Observe la llamada a yield break. En efecto, está saliendo de la enumeración temprano.

Tenga en cuenta también que yield breakfunciona de manera diferente a un simple break. En el ejemplo anterior, yield breaksale del método sin realizar la llamada a Debug.WriteLine(..).


7

Aquí http://www.alteridem.net/2007/08/22/the-yield-statement-in-c/ es un muy buen ejemplo:

Public static IEnumerable <int> Range (int min, int max)
{
   mientras (cierto)
   {
      si (min> = max)
      {
         ruptura de rendimiento;
      }
      rendimiento de rendimiento min ++;
   }
}

y explicación, que si yield breakse golpea una declaración dentro de un método, la ejecución de ese método se detiene sin retorno. Hay algunas situaciones de tiempo, cuando no quieres dar ningún resultado, entonces puedes usar el límite de rendimiento.


5

el límite de rendimiento es solo una forma de decir retorno por última vez y no devolver ningún valor

p.ej

// returns 1,2,3,4,5
IEnumerable<int> CountToFive()
{
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
    yield return 5;
    yield break;
    yield return 6;
    yield return 7;
    yield return 8;
    yield return 9;
 }

4

Si lo que quiere decir con "lo que realmente hace la ruptura del rendimiento" es "cómo funciona", consulte el blog de Raymond Chen para obtener más información. https://devblogs.microsoft.com/oldnewthing/20080812-00/?p=21273

Los iteradores de C # generan un código muy complicado.


12
Bueno, solo son complicados si te importa el código en el que están compilados. El código que los usa es bastante simple.
Robert Rossney

@Robert, a eso me refería, así que actualicé la respuesta para incluir tu comentario
Tony Lee

-2

La palabra clave de rendimiento se usa junto con la palabra clave de retorno para proporcionar un valor al objeto enumerador. return return especifica el valor o valores devueltos. Cuando se alcanza la declaración de rendimiento, se almacena la ubicación actual. La ejecución se reinicia desde esta ubicación la próxima vez que se llama al iterador.

Para explicar el significado usando un ejemplo:

    public IEnumerable<int> SampleNumbers()
    {
        int counter = 0;
        yield return counter;

        counter = counter + 2;

        yield return counter;

        counter = counter + 3;

        yield return counter ;
    }

Los valores devueltos cuando esto se repite son: 0, 2, 5.

Es importante tener en cuenta que la variable de contador en este ejemplo es una variable local. Después de la segunda iteración que devuelve el valor de 2, la tercera iteración comienza desde donde dejó antes, mientras se conserva el valor anterior de la variable local denominada contador que era 2.


11
No explicaste lo que yield breakhace
Jay Sullivan

No creo que yield returnrealmente sea compatible con la devolución de múltiples valores. Tal vez eso no es lo que realmente quisiste decir, pero así es como lo leí.
Sam

Sam: el método SampleNumbers con múltiples declaraciones de rendimiento de rendimiento funciona, el valor del iterador se devuelve inmediatamente y la ejecución se reanuda cuando se solicita el siguiente valor. He visto a personas terminar un método como este con un "límite de rendimiento", pero es innecesario. Llegar al final del método también finaliza el iterador
Loren Paulsen

la razón por la que este es un mal ejemplo yield breakes que no contiene un enumerador de nivel de lenguaje como un foreach- cuando se usa un enumerador, yield breakproporciona un valor real. Este ejemplo parece un bucle desenrollado. casi nunca verá este código en el mundo real (todos podemos pensar en algunos casos extremos, claro) también, no hay un "iterador" aquí. el "bloque iterador" no puede extenderse más allá del método como una cuestión de especificación de lenguaje. lo que realmente se devuelve es un "enumerable", vea también: stackoverflow.com/questions/742497/…
Shaun Wilson
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.