Secuencias infinitas con Yield return y creación de una única secuencia de entidades de todos los archivos de referencia

Aquí el vídeo relacionado con este post

En ocasiones nos interesará crear un enumerador que enumere las entidades de todos los archivos de referencia como un todo, es decir, como si todos los archivos de referencia fueran un único archivo, por ejemplo cuando realicemos un control de bordes: nos interesará analizar la continuidad de las entidades del archivo de dibujo activo con respecto a todos los archivos de dibujo de referencia cargados.

Para ello, vamos a aprovecharnos de una de las maravillas de C# que es la palabra clave yield. Esta palabra clave básicamente nos permite devolver un valor al llamador, pero almacenando el estado en el que hemos devuelvo el control para continuar por el mismo sitio y con los mismos valores si se vuelve a llamar al método que utiliza el yield return.

Por ejemplo, (te voy a explicar el ejemplo más común, pero es que viene de perlas) si queremos hacer un método que devuelva una secuencia de 10 números aleatorios, podríamos implementarlo de la siguiente manera:

       public IEnumerable<int> SecuenciaNúmerosAleatorios()
        {
            Random r = new Random();

            int[] array = new int[10];
            for (int i = 0; i < 10; i++)
                array[i] = r.Next();

            return array;
        }

Como un array de int es siempre un IEnumerable<int> pues tan solo tenemos que crear un array de 10 posiciones, rellenarlo y devolverlo, y estaremos devolviendo una secuencia de 10 números aleatorios. Perfecto.

Ahora, ¿que pasa si te digo que quiero que esa secuencia sea infinita?

No podemos hacer un array infinito porque nuestro ordenador no tiene una cantidad infinita de memoria.

Aquí es donde viene a ayudarnos la palabra clave yield. Mira cómo podríamos hacer ese método como un método que devuelve un número infinito de valores:

        public IEnumerable<int> SecuenciaNúmerosAleatorios()
        {
            Random r = new Random();

            while(true)
                yield return r.Next();
        }

¿no te parece precioso?

Yield lo que hace es implementar una secuencia perezosa, únicamente se generará un nuevo número aleatorio en el momento en el que ordenemos al iterador que se mueva a la siguiente posición, de modo que no hay consumo de memoria ni tiempo de espera para que se rellene el ¿array? ¿que array? de valores.

Bien, pues ahora que conoces la palabra clave yield, vamos a crear un método de extensión que se ejecute sobre una secuencia de ReadOnlyDrawingFile y que nos devuelva una secuencia única de entidades que devuelva una a una todas las entidades de todos los archivos de referencia cargados.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Entities;
using Digi21.DigiNG.IO;

namespace UtilidadesDigi
{
    public static class UtilidadesSecuenciaReadOnlyDrawingFile
    {
        public static IEnumerable<Entity> EnumeraEntidades(this IEnumerable<ReadOnlyDrawingFile> archivos)
        {
            foreach (var archivo in archivos)
                foreach (var entidad in archivo)
                    yield return entidad;
        }

    }
}

No se a tí, pero a mí me encanta C#, por cosas como esta.

A continuación vamos a hacer una orden que nos muestre en la ventana de resultados la suma de entidades de cada tipo (líneas, puntos, …) de todos los archivos de referencia cargados utilizando el método de extensión que acabamos de desarrollar:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Entities;
using Digi21.DigiNG.IO;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG;
using Digi21.Digi3D;
using UtilidadesDigi;

namespace Acme
{
    [Command(Name="suma_entidades_archivos_referencia")]
    public class SumaEntidadesArchivosReferencia
        : Command
    {
        public SumaEntidadesArchivosReferencia()
        {
            this.Initialize += new EventHandler(SumaEntidadesArchivosReferencia_Initialize);
        }

        void SumaEntidadesArchivosReferencia_Initialize(object sender, EventArgs e)
        {
            try
            {
                if (DigiNG.ReferenceFiles.Length == 0)
                {
                    Digi3D.Music(MusicType.Error);
                    Digi3D.ShowBallon(
                        "suma_entidades_archivos_referencia",
                        "No hay ningún archivo de referencia cargado",
                        2,
                        BallonIcon.Error);
                    return;
                }

                Digi3D.OutputWindow.WriteLine(
                    "Suma de todos los tipos de entidades de los archivos de referencia cargados:");


                Digi3D.OutputWindow.WriteLine(
                    "Número de líneas: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades().
                    CuantasEntidadesDeTipo<ReadOnlyLine>());

                Digi3D.OutputWindow.WriteLine(
                    "Número de puntos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades().
                    CuantasEntidadesDeTipo<ReadOnlyPoint>());

                Digi3D.OutputWindow.WriteLine(
                    "Número de textos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades().
                    CuantasEntidadesDeTipo<ReadOnlyText>());

                Digi3D.OutputWindow.WriteLine(
                    "Número de polígonos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades().
                    CuantasEntidadesDeTipo<ReadOnlyPolygon>());

                Digi3D.OutputWindow.WriteLine(
                    "Número de complejos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades().
                    CuantasEntidadesDeTipo<ReadOnlyComplex>());
            }
            finally
            {
                Dispose();
            }
        }

    }
}