lunes, 18 de agosto de 2014

Sintaxis y Semántica de un Iterador en C#

Índice

0. Introducción
1. ¿Qué es un Iterador?
2. Implementación Básica de un Iterador
3. Semántica Iteradores
3.1 Uso múltiple de sentencias yield return
3.2 Uso de yield return
3.2 Bloques try/catch/finally en iteradores
4. Ejemplo de un iterador
5. Artefactos
6. Conclusiones
7. Glosario
8. Literatura & Enlaces

0. Introducción

En el artículos anterior estudiamos los enumeradores: construcciones (enumerable y enumeradores) útiles para la iteración de estructuras de datos creados por los propios programadores o las existentes en la biblioteca base de clases de .NET. En esta oportunidad vamos a tratar otro tema relacionado: se trata de los iteradores. Este tipo de construcción facilita el recorrido por cada uno de los elementos de una colección de objetos. En esta apartado trataremos los siguientes temas: consumidor y productor de un enumerador, semántica de los iteradores, manejo de excepciones en un iterador, y el uso de la palabra clave yield en combinación con la instrucción de retorno de valor de método return.

1. ¿Qué es un Iterador?

Podríamos definir en términos simples y directos a un iterador como un objeto que recorre los elementos almacenados en una estructura de datos o una colección (e.g., un arreglo o una lista). Gráficamente lo podríamos ver de la siguiente manera:
Analogía de un iterador
Figura 1. Analogía de un iterador.
En la Figura 1 se muestra a un invididuo identificado como Iterador. Este individuo se encarga de explorar cada elemento (de izquiera a derecha) de la colección que aparece en la parte superior.

2. Implementación Básica de un Iterador

De acuerdo con [1] un identificamos dos elementos básicos para iterar una estructura de datos o colección:
  • Consumdor (consumer): A través de la sentencia foreach se recorre o se consume los elementos de la estructura o colección.
  • Productor (producer): Generador o productor del enumador (cfr. Enumeradores).
Para comprender mejor estos conceptos, adaptemos el ejemplo hallado en [1]:

En las líneas 22-37 declaramos el método Fibonacci que se encarga de generar la serie hasta la cantidad especificada por su parámetro numero. Por cada iteración del ciclo for (líneas 27-36) se genera un valor de la serie. Este valor se retorna al ciclo foreach (líneas 12-15). En la siguiente iteración el control de ejecución del método Fibonacci continua en la línea 32. Este ciclo termina hasta que se hayan generado, como en este ejemplo, los 7 primeros números de la serie Fibonacci.

Para extendernos un poco debemos resaltar dos elementos:
  • yield return: Una vez en un método de iteración de elementos se alcance la línea que contiene esta instrucción compuesta, la ubicación es recordada, y el valor de retorno es devuelto al iterador o consumidor.

    En cuanto a la instrucción
    return simple, la diferencia radica en que una vez se alcanza esta el flujo de control pasa al método que hizo la llamada del método. Sería algo así como [1]:

    "Aquí está el valor que ha solicitado desde este método."

    Mientras que
    yield return:

    "Aquí está el siguiente valor que ha solicitado desde este enumerador."
  • IEnumerable<int>: Como el método Fibonacci se trata de un método iterador era necesario especificar como tipo de retorno la interfaz IEnumerable<T>.
A este último punto vale agregar que el tipo de retorno ya sea para un método o método de acceso debe ser cualquier de estas interfaces con un tipo paramétrico específico (en el caso de las interfaces genéricas):
  • Interfaces Enumerable:
    • System.Collections.IEnumerable
    • System.Collections.Generic.IEnumerable<T>
  • Interfaces Enumerator:
    • System.Collections.IEnumerator
    • System.Collections.Generic.IEnumerator<T>

Internamente el compilador realiza una serie de manipulaciones para crear una clase privada que implementa los métodos MoveNext, Reset y la propiedad Current. La nota completa acerca de este tipo de inversión es explicado con mejor detalle en la Figura 2 [1]:
Tarea del compilador con los enumeradores
Figura 2. Tarea del compilador con los enumeradores.
Para finalizar con el ejemplo del archivo C# IteradorSerieFibonacci.cs realizamos estas tareas:

Compilación:

  1. csc /target:exe IteradorSerieFibonacci.cs

Ejecución assembly:

  1. .\IteradorSerieFibonacci.exe


> Prueba de ejecución (local):
Ejecución assembly IteradorSerieFibonacci.exe
Figura 3. Ejecución assembly IteradorSerieFibonacci.exe.

3. Semántica Iteradores

3.1 Uso múltiple de sentencias yield return

En la lógica de implementación de un método iterador podemos especificar múltiples instrucciones yield return:

A resaltar las líneas del método ListaNumeros por cada iteración realizada por el consumidor de la iteración (líneas 13-16), este método retorna valor por valor (1, 2, y 3) de cada setencia yield return.

Compilación:


  1. csc /target:exe Multiplesyield.cs

Ejecución assembly:


  1. .\Multiplesyield.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly Multiplesyieldi.exe
Figura 4. Ejecución assembly Multiplesyieldi.exe.

3.2 Uso de yield break

Para finalizar arbitrariamente un método iterador o un propiedad de acceso de un iterador, podemos utilizar la sentencia compuesta yield break. Esto evita que las sentencias yield return de líneas posteriores no serán parte de la secuencia generada por el consumidor de la iteración. Así:

static IEnumerable<String> MetodoIterador(bool continuar)
{
yield return "Primero";
yield return "Segundo";

if (!continuar)
{
yield break;
}

yield return "Tercero";
}

Asumiendo que el valor pasado como argumento para el parámetro bool sea false la sentencia if se evaluará como true y el método iterador MetodoIterador finaliza por completo sin alcanzar la última sentencia yield return.

3.3 Bloques try/catch/finally en iteradores

De acuerdo con [1] una sentencia compuesta yield return no puede ser parte de un bloque try-catch; como en este ejemplo:

static IEnumerable> MetodoIterador()
{
try
{
yield return 1;
}
catch
{
//...
}

yield return 2;
}

Al intentar compilar un archivo de código fuente C# que contenga esta implementación, se generará el error CS1626 [4]. Esta restricción se debe a que el compilador debe generar clases privadas que implementen los miembros MoveNext, Current, Reset, y Dispose. Sin embargo, sí que está permitido la combinación try/finally:

static IEnumerable> MetodoIterador()
{
try
{
yield return 1;
}
finally
{
//...
}
}

Para este caso, cuando el consumidor del iterador haya finalizado, el bloque de instrucciones en el bloque finally se ejecutarán. Para aquellos enumeradores que son finalizados antes del recorrido completo (como en la sección 3.1), el consumidor del iterador implícitamente se encarga de liberar los recursos utilizados.

4. Ejemplo de un Iterador

Creemos un ejemplo refleje la creación de un iterador simple para generar números pares a partir de un rango de números enteros.

En las líneas 22-32 el método iterador GeneradorNumerosPares genera un número par a partir del rango especificado por los valores pasados a los argumentos. Por cada iteración retorna un número par (la validación se realiza con la sentencia if de la línea 27).

Compilación:

  1. csc /target:exe IteradorNumerosEnteros.cs

Ejecución assembly:


  1. .\IteradorNumerosEnteros.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly IteradorNumerosPares.exe
Figura 5. Ejecución assembly IteradorNumerosPares.exe.

5. Artefactos

Lista de enlaces de los artefactos producidos a lo largo del desarrollo de este artículo:

6. Conclusiones

Hemos comprendido una nueva construcción para la iteración de los elementos de una colección a partir del uso de las interfaces IEnumerable (genérica y no genérica) y IEnumerator (génerica y no genérica). La sentencia compuesta yield return es la encargada de retornar el valor de un elemento de una estructura o una colección por cada elemento explorado o iterado. En el próximo artículo nos centraremos la composición de secuencias; un tema muy similar al que que acabamos de estudiar, y por supuesto, de bastante utilidad.

7. Glosario

  • Bloque
  • Colección
  • Elemento
  • Estructura de datos
  • Excepción
  • Iterador
  • Semántica
  • Sintaxis

8. Literatura & Enlaces

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: Iterators (C# and Visual Basic) - http://msdn.microsoft.com/en-us/library/dscyy5s0.aspx
[3]: Enumeradores en C# | OrtizOL - Experiencias Construcción Software (xCSw)- http://ortizol.blogspot.com/2014/08/enumeradores-en-csharp.html
[4]: Compiler Error CS1626 - http://msdn.microsoft.com/en-us/library/cs1x15az(v=vs.90).aspx


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.