2009-12-28 13 views
13

Trabajando a través de un tutorial (Profesional ASP.NET MVC - Cena Nerd), me encontré con este fragmento de código:uso interesante de la palabra clave de rendimiento en C# empollón Cena tutorial

public IEnumerable<RuleViolation> GetRuleViolations() { 
    if (String.IsNullOrEmpty(Title)) 
     yield return new RuleViolation("Title required", "Title"); 
    if (String.IsNullOrEmpty(Description)) 
     yield return new RuleViolation("Description required","Description"); 
    if (String.IsNullOrEmpty(HostedBy)) 
     yield return new RuleViolation("HostedBy required", "HostedBy"); 
    if (String.IsNullOrEmpty(Address)) 
     yield return new RuleViolation("Address required", "Address"); 
    if (String.IsNullOrEmpty(Country)) 
     yield return new RuleViolation("Country required", "Country"); 
    if (String.IsNullOrEmpty(ContactPhone)) 
     yield return new RuleViolation("Phone# required", "ContactPhone"); 
    if (!PhoneValidator.IsValidNumber(ContactPhone, Country)) 
     yield return new RuleViolation("Phone# does not match country", "ContactPhone"); 
    yield break; 
} 

He leído en yield, pero creo que mi comprensión todavía es un poco confusa. Lo que parece que hacer es crear un objeto que permite bicicleta a través de los elementos de una colección sin tener que hacer la bicicleta a menos que y hasta que sea absolutamente necesario.

Este ejemplo es un poco extraño para mí, sin embargo. Lo que creo que está haciendo es retrasar la creación de cualquier instancia RuleViolation hasta que el programador realmente solicite un elemento específico en la colección usando for each o un método de extensión LINQ como .ElementAt(2).

Más allá de esto, sin embargo, tengo algunas preguntas:

  1. ¿Cuándo las partes condicionales de las declaraciones if se evalúan? ¿Cuándo se llama GetRuleViolations() o cuando el enumerable realmente se itera? En otras palabras, si el valor de Title cambia de null a Really Geeky Dinner entre el momento en que llamo al GetRuleViolations() y el momento en que intento iterar sobre él, ¿se creará RuleViolation("Title required", "Title") o no?

  2. ¿Por qué es necesario yield break;? ¿Qué está haciendo realmente aquí?

  3. Digamos Title es nulo o vacío. Si llamo al GetRuleViolations() y luego repito el número enumerable resultante dos veces seguidas, ¿cuántas veces se llamará a new RuleViolation("Title required", "Title")?

+3

El compilador .Net convierte este azúcar sintáctico en una forma más bastarda. Compile la muestra y luego cargue IL en el reflector. Debería poder entender exactamente qué está pasando allí. –

Respuesta

17

Una función que contiene yield comandos se trata de manera diferente a una función normal. Lo que ocurre detrás de escena cuando se llama a esa función, es que un tipo anónimo se construye del tipo específico IEnumerable de la función, la función crea un objeto de ese tipo y lo devuelve. La clase anónima contiene lógica que ejecuta el cuerpo de la función hasta el siguiente comando yield por cada vez que se llama al IEnumerable.MoveNext. Es un poco engañoso, el cuerpo de la función no se ejecuta en un lote como una función normal, sino que en pedazos, cada pieza se ejecuta cuando el enumerador avanza un paso adelante.

En lo que respecta a sus preguntas:

  1. Como ya he dicho, cada if es ejecutado cuando iterar al siguiente elemento.
  2. yield break no es necesario en el ejemplo anterior. Lo que hace es que termina la enumeración.
  3. Cada vez que itera sobre el enumerable, fuerza la ejecución del código nuevamente. Ponga un punto de interrupción en la línea relevante y pruébelo usted mismo.
+0

+1 respuesta realmente clara y útil, gracias. Solo una pequeña pregunta de seguimiento si no te importa: ¿Puedes pensar en un ejemplo donde 'yield break' * es * necesario? – devuxer

+1

Aclaración menor: quiere decir 'IEnumerator []' en algunos lugares donde dice 'IEnumerable []'. –

+0

Entonces, en el ejemplo, el 'salto de rendimiento' es necesario, porque si no se crea' RuleViolation', arrojaría una excepción, ¿no? – Ronald

6

1) Tomar este ejemplo sencillo:

public void Enumerate() 
{ 
    foreach (var item in EnumerateItems()) 
    { 
     Console.WriteLine(item); 
    } 
} 

public IEnumerable<string> EnumerateItems() 
{ 
    yield return "item1"; 
    yield return "item2"; 
    yield break; 
} 

Cada vez que llame MoveNext() del IEnumerator el código devuelve desde el punto y se mueve yield a la siguiente línea de código ejecutable.

2) yield break; le dirá al IEnumerator que no hay nada más que enumerar.

3) una vez por enumeración.

Uso de yield break;

public IEnumerable<string> EnumerateUntilEmpty() 
{ 
    foreach (var name in nameList) 
    { 
     if (String.IsNullOrEmpty(name)) yield break; 
     yield return name; 
    }  
} 
+0

¿Seguro sobre el "3) una vez"? Por lo que yo entiendo, esto se reevalúa siempre (ver las otras dos respuestas aquí). –

+0

Lo aclaré. – ChaosPandion

+0

Si hace una lista, ¿por qué no repetirla de todos modos? El método 'yield' le permite no construir explícitamente una lista. En su caso, por ejemplo: public IEnumerable Enumerate() { yield return "item1"; yield return "item2"; } –

2

versión corta:

1: El rendimiento es el "Parar y volver más tarde" la magia de palabras clave, por lo que el caso se han evaluado las declaraciones frente a la "activa" una.

2: vacaciones de rendimiento termina explícitamente la enumeración (piense "ruptura" en una caja de conmutación)

3: Cada vez. Puede almacenar en caché el resultado, por supuesto, convirtiéndolo en una lista, por ejemplo, e iterando sobre eso luego.

Cuestiones relacionadas