Pregunta

Quiero saber todo sobre la declaración yield, en una forma fácil de entender.

He leído acerca de la declaración yield y su facilidad en la aplicación del iterador. Sin embargo, la mayor parte es muy seco. Me gustaría obtener bajo las sábanas y ver cómo Microsoft se encarga de producir una rentabilidad.

Además, cuando usted utiliza yield break?

¿Fue útil?

Solución

yield funciona mediante la construcción de una máquina de estados internos. Almacena el estado actual de la rutina cuando sale y se reanuda desde ese estado la próxima vez.

Puede utilizar el reflector para ver cómo se implementa por el compilador.

yield break se utiliza cuando se quiere dejar de devolver los resultados. Si usted no tiene un yield break, el compilador asumiría uno al final de la función (al igual que una declaración return; en una función normal)

Otros consejos

Como dice Mehrdad, se construye una máquina de estados.

Así como el uso del reflector (otra excelente sugerencia) que puede encontrar mi artículo sobre la aplicación de bloques iterador útil. Sería relativamente simple si no fuera por bloques finally - pero introducen toda una dimensión extra de complejidad

Vamos a retroceder un poco: la palabra clave yield se traduce como muchos otros dijeron que una máquina de estados.

En realidad esto no es exactamente igual que el uso de un sistema incorporado en la aplicación que se utiliza detrás de las escenas, sino más bien el compilador volver a escribir el código relacionado yield a una máquina de estado mediante la aplicación de una de las interfaces pertinentes (el tipo de retorno del método que contiene las palabras clave yield).

A (finito) máquina de estado es sólo una pieza de código que, dependiendo de en qué parte del código (dependiendo del estado previo, de entrada) va a otra acción del estado, y esto es más o menos lo que está sucediendo cuando se está utilizando y el rendimiento con el método de tipo de retorno de IEnumerator<T> / IEnumerator. La palabra clave yield es lo que va a crear otra acción para avanzar al siguiente estado de la anterior, por lo tanto, la administración de estado se crea en la aplicación MoveNext().

Esto es lo que exactamente el compilador de C # / Roslyn va a hacer: comprobar la presencia de una palabra clave yield más el tipo de tipo de retorno del método que contiene, si se trata de un IEnumerator<T>, IEnumerable<T>, IEnumerator o IEnumerable y luego crear una privada clase que refleja ese método, la integración de las variables y los estados necesarios.

Si está interesado en los detalles de cómo la máquina de estado y cómo las iteraciones se reescrito por el compilador, puede comprobar los enlaces a cabo en Github:

Trivia 1 : la AsyncRewriter (utilizado cuando se escribe código async / await también hereda de StateMachineRewriter ya que también aprovecha una máquina de estados atrás.

Como se ha mencionado, la máquina de estado se refleja fuertemente en el bool MoveNext() generado aplicación en la que hay un switch + a veces algunos de los antiguos goto moda basado en un campo de estado que representa las diferentes rutas de ejecución a diferentes estados de su método.

El código que se genera por el compilador del usuario de código que no se ve "buena", sobre todo que el compilador añade algunas extrañas prefijos y sufijos aquí y allá

Por ejemplo, el código:

public class TestClass 
{
    private int _iAmAHere = 0;

    public IEnumerator<int> DoSomething()
    {
        var start = 1;
        var stop = 42;
        var breakCondition = 34;
        var exceptionCondition = 41;
        var multiplier = 2;
        // Rest of the code... with some yield keywords somewhere below...

Las variables y los tipos relacionados con ese trozo de código anterior después de la compilación se ven como:

public class TestClass
{
    [CompilerGenerated]
    private sealed class <DoSomething>d__1 : IEnumerator<int>, IDisposable, IEnumerator
    {
        // Always present
        private int <>1__state;
        private int <>2__current;

        // Containing class
        public TestClass <>4__this;

        private int <start>5__1;
        private int <stop>5__2;
        private int <breakCondition>5__3;
        private int <exceptionCondition>5__4;
        private int <multiplier>5__5;

En cuanto a la máquina de estados, vamos a echar un vistazo a un ejemplo muy simple con un maniquí de ramificación para la obtención de algunos incluso / cosas raras.

public class Example
{
    public IEnumerator<string> DoSomething()
    {
        const int start = 1;
        const int stop = 42;

        for (var index = start; index < stop; index++)
        {
            yield return index % 2 == 0 ? "even" : "odd";
        }
    }
} 

se traducirán en el MoveNext como:

private bool MoveNext()
{
    switch (<>1__state)
    {
        default:
            return false;
        case 0:
            <>1__state = -1;
            <start>5__1 = 1;
            <stop>5__2 = 42;
            <index>5__3 = <start>5__1;
            break;
        case 1:
            <>1__state = -1;
            goto IL_0094;
        case 2:
            {
                <>1__state = -1;
                goto IL_0094;
            }
            IL_0094:
            <index>5__3++;
            break;
    }
    if (<index>5__3 < <stop>5__2)
    {
        if (<index>5__3 % 2 == 0)
        {
            <>2__current = "even";
            <>1__state = 1;
            return true;
        }
        <>2__current = "odd";
        <>1__state = 2;
        return true;
    }
    return false;
} 

Como se puede ver esta implementación está lejos de ser sencillo, pero hace el trabajo!

Trivia 2 :? ¿Qué ocurre con el IEnumerable / IEnumerable<T> tipo de retorno método
Así, en lugar de generar una clase que implementa la IEnumerator<T>, será, generar una clase que implementan tanto IEnumerable<T> así como la IEnumerator<T> por lo que la aplicación de IEnumerator<T> GetEnumerator() aprovechará la misma clase generada.

Recordatorio calientesobre las pocas interfaces implementadas de forma automática cuando se utiliza una palabra clave yield:

public interface IEnumerable<out T> : IEnumerable
{
    new IEnumerator<T> GetEnumerator();
}

public interface IEnumerator<out T> : IDisposable, IEnumerator
{
    T Current { get; }
}

public interface IEnumerator
{
    bool MoveNext();

    object Current { get; }

    void Reset();
}

También puede consultar este ejemplo con diferentes caminos / ramificación y la plena aplicación por la reescritura compilador.

Esto ha sido creado con SharpLab , se puede jugar con esa herramienta para probar diferentes rutas de ejecución relacionados yield y ver cómo el compilador reescribirlos como una máquina de estados en la implementación MoveNext.

Sobre la segunda parte de la pregunta, es decir, yield break, se ha contestado aquí

  

Se especifica que un iterador ha llegado a su fin. Tu puedes pensar en   yield break como una instrucción de retorno que no devuelve un valor.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top