Tutorial: Combinar técnicas de C# para generar etiquetas de distintivo

En Meetup, las etiquetas de distintivo deben estar ordenadas por nombre Cada asistente solicita una o varias copias, y cada etiqueta incluye un mensaje para el rol del asistente. En este tutorial, combinará técnicas pequeñas de C# para generar las etiquetas sin una colección independiente de cadenas con formato.

Este tutorial es para desarrolladores que conocen los métodos, bucles y colecciones básicos de C#. Usted elige dónde pertenecen las operaciones cortas, hace que las dependencias auxiliares sean explícitas y decide cuándo el programa genera resultados. Para conocer el razonamiento que subyace a estas opciones, consulte Building software.

Prerequisites

Comience con una etiqueta por asistente

  1. Cree una carpeta denominada BadgeLabels y un archivo denominado Program.cs dentro de él.

  2. Copie el origen completo de la sección siguiente en Program.cs. Use este mismo archivo para cada paso y experimento:

    #define BADGE_SORT
    #define BADGE_COPIES
    #define BADGE_FORMAT
    #define BADGE_COUNT
    // #define BADGE_DIAGNOSTICS
    // #define BADGE_REPEAT
    // #define BADGE_SNAPSHOT
    
    Attendee[] attendees =
    [
        new("Ben", "Host", 1),
        new("Ada", "Guest", 2)
    ];
    
    #if BADGE_SORT
    #if BADGE_DIAGNOSTICS
    Console.WriteLine("Before query definition.");
    #endif
    var orderedAttendees = attendees.OrderBy(
    #if BADGE_DIAGNOSTICS
        attendee =>
        {
            Console.WriteLine($"Sort key: {attendee.Name}");
            return attendee.Name;
        }, StringComparer.Ordinal);
    #else
        attendee => attendee.Name, StringComparer.Ordinal);
    #endif
    #if BADGE_DIAGNOSTICS
    Console.WriteLine("After query definition.");
    #endif
    #else
    var orderedAttendees = attendees;
    #endif
    
    #if BADGE_SNAPSHOT
    var attendeesToPrint = orderedAttendees.ToList();
    #else
    var attendeesToPrint = orderedAttendees;
    #endif
    
    int passes = 1;
    #if BADGE_REPEAT
    passes = 2;
    #endif
    
    for (int pass = 0; pass < passes; pass++)
    {
    #if BADGE_COUNT
        int expected = attendees.Sum(attendee => attendee.Copies);
        int printed = 0;
    #endif
    
        foreach (Attendee attendee in attendeesToPrint)
        {
    #if BADGE_COPIES
            var labels = CreateLabels(attendee);
    #if BADGE_DIAGNOSTICS
            Console.WriteLine($"Sequence created: {attendee.Name}");
    #endif
            foreach (string label in labels)
            {
                Console.WriteLine(label);
    #if BADGE_COUNT
                printed++;
    #endif
            }
    #else
            Console.WriteLine($"{attendee.Name} | {attendee.Role}");
    #endif
        }
    
    #if BADGE_COUNT
        if (printed != expected)
        {
            throw new InvalidOperationException("Label count doesn't match the request.");
        }
    
        Console.WriteLine($"Printed {printed} of {expected} requested labels.");
    #endif
    }
    
    #if BADGE_COPIES
    static IEnumerable<string> CreateLabels(Attendee attendee)
    {
    #if BADGE_DIAGNOSTICS
        Console.WriteLine($"Iterator started: {attendee.Name}");
    #endif
        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(attendee.Copies);
    
    #if BADGE_FORMAT
        string message = attendee.Role switch
        {
            "Guest" => "Welcome!",
            "Host" => "Ask me for help",
            _ => throw new ArgumentException(
                $"Unsupported role: {attendee.Role}.", nameof(attendee))
        };
    #endif
    
        for (int copy = 1; copy <= attendee.Copies; copy++)
        {
    #if BADGE_FORMAT
            yield return FormatLabel(
                attendee.Name, attendee.Role, message, copy, attendee.Copies);
    #else
            yield return $"{attendee.Name} | {attendee.Role} [{copy}/{attendee.Copies}]";
    #endif
        }
    
    #if BADGE_FORMAT
        static string FormatLabel(
            string name, string role, string message, int copy, int total) =>
            $"{name} | {role} | {message} [{copy}/{total}]";
    #endif
    }
    #endif
    
    record Attendee(string Name, string Role, int Copies);
    
  3. En la parte superior del archivo, marque como comentario las cuatro líneas activas de #define. Deje las tres líneas de experimento comentadas también.

  4. Desde esa carpeta, ejecute la aplicación basada en archivos:

    dotnet run --file Program.cs
    

El programa escribe:

Ben | Host
Ada | Guest

El archivo contiene cada fase. A partir de ahora, cambie solo las #define líneas de la parte superior, no los cuerpos de método ni las declaraciones. Una #if directiva incluye código solo cuando se define su símbolo; una #else rama proporciona la alternativa. Consulte Compilación condicional.

Descomente una línea de fase adicional en cada paso y mantenga habilitadas las líneas de fase anteriores:

Stage Símbolos de fase activa
Una etiqueta por asistente None
Ordenar asistentes BADGE_SORT
Creación de copias BADGE_SORT, BADGE_COPIES
Dar formato a los mensajes BADGE_SORT, , BADGE_COPIES, BADGE_FORMAT
Compruebe el recuento BADGE_SORT, BADGE_COPIES, , BADGE_FORMAT, BADGE_COUNT

El código fuente descargado habilita los cuatro símbolos de fase, por lo que ejecuta el programa final de forma predeterminada. Los otros tres símbolos controlan experimentos opcionales y permanecen desactivados durante la progresión normal. Estos símbolos usan un BADGE_ prefijo para evitar símbolos definidos por el SDK, como TRACE.

Los siguientes extractos resaltan el código ya en el archivo. No las copie en todo el programa.

Attendee[] attendees =
[
    new("Ben", "Host", 1),
    new("Ada", "Guest", 2)
];

La Attendee declaración define un registro, un tipo que agrupa los datos relacionados y proporciona igualdad basada en valores. Cada asistente tiene un Name, un Role y un número solicitado de Copies. Un registro se ajusta a este modelo de datos pequeño porque el programa pasa los datos de los asistentes entre operaciones sin cambiarlos. Para obtener más información sobre este tipo, vea Registros.

La entrada fija usa un array y una expresión de colección. En cada new(...) llamada, C# deduce Attendee del tipo de elemento de matriz; vea Target-typed new. Las instrucciones de nivel superior permiten ejecutar el ejemplo sin escribir un método Main.

El foreach bucle recorre cada asistente y una cadena interpolada inserta sus valores en la salida. Actualmente, el bucle omite Copies.

Mantenga esta entrada pequeña conforme desarrolle el programa: Ben solicita una copia y Ada solicita dos. Solo Guest y Host son roles admitidos, y los recuentos de copias deben ser positivos.

Ordenar con una expresión lambda

Para colocar a los asistentes en orden de nombres, descomente #define BADGE_SORT. Deje los demás símbolos comentados. La rama de ordenación define esta consulta:

var orderedAttendees = attendees.OrderBy(
#if BADGE_DIAGNOSTICS
    attendee =>
    {
        Console.WriteLine($"Sort key: {attendee.Name}");
        return attendee.Name;
    }, StringComparer.Ordinal);
#else
    attendee => attendee.Name, StringComparer.Ordinal);
#endif

Con el diagnóstico desactivado, la rama #else proporciona la expresión lambda corta. El bucle imprime los asistentes ordenados:

    foreach (Attendee attendee in attendeesToPrint)
    {
#if BADGE_COPIES
        var labels = CreateLabels(attendee);
#if BADGE_DIAGNOSTICS
        Console.WriteLine($"Sequence created: {attendee.Name}");
#endif
        foreach (string label in labels)
        {
            Console.WriteLine(label);
#if BADGE_COUNT
            printed++;
#endif
        }
#else
        Console.WriteLine($"{attendee.Name} | {attendee.Role}");
#endif
    }

Ejecute el mismo comando. La salida ahora coloca Ada primero:

Ada | Guest
Ben | Host

attendee => attendee.Name es una expresión lambda, una función sin nombre proporcionada donde el código necesita comportamiento. Aquí, la expresión lambda selecciona el nombre por el que se va a ordenar. La expresión lambda permanece en línea, directamente en la OrderBy llamada, porque es corta y su propósito está claro allí. Podría extraer la selección en un método auxiliar con nombre, pero un nombre de método no explicaría mejor esta selección de una propiedad. Consulte Expresiones lambda.

OrderBy forma parte de Language Integrated Query (LINQ), un conjunto de características de C# y API de .NET para consultas sobre datos. Consulte Consultas LINQ.

En este ejemplo se usa la sintaxis de método, que permite llamar directamente a métodos LINQ. En cambio, la sintaxis de consulta usa cláusulas de C# como from, orderbyy select:

var orderedAttendees =
    from attendee in attendees
    orderby attendee.Name
    select attendee;

El compilador traduce la sintaxis de consulta en llamadas de método. La sintaxis de consulta puede facilitar la lectura de una consulta con varias cláusulas; la sintaxis de método mantiene corta esta única ordenación y le permite pasar un comparador a OrderBy. En el ejemplo de sintaxis de consulta se usa el comparador de cadenas predeterminado, por lo que no garantiza el mismo orden entre distintas culturas. Mantenga la versión con sintaxis de método para este programa. Consulte el método LINQ y la sintaxis de consulta.

Para mantener la salida independiente de la cultura de la máquina, el ejemplo usa Ordinal para comparaciones de nombres. Para los nombres cortos aquí, ese orden es alfabético. Consulte Ordenar datos con LINQ.

La var declaración permite al compilador deducir el tipo de la variable de consulta. La variable orderedAttendees describe una consulta, no una colección ordenada completada. Enumerar una secuencia significa leer sus elementos uno a uno. OrderBy lee y ordena todos los asistentes cuando foreach solicita por primera vez un elemento.

Respeto del orden de ejecución

La ejecución diferida significa que el trabajo se inicia cuando el código enumera una secuencia en lugar de cuando el código define la consulta. Consulte Ejecución de una consulta. Use la salida de diagnóstico para ver la diferencia:

  1. Mantenga habilitado solo BADGE_SORT de entre los símbolos de etapa.

  2. Descomente #define BADGE_DIAGNOSTICS en la parte superior del mismo archivo. Las ramas de diagnóstico rodean la definición de consulta con mensajes y usan una expresión lambda que imprime cada clave de ordenación:

    #if BADGE_SORT
    #if BADGE_DIAGNOSTICS
    Console.WriteLine("Before query definition.");
    #endif
    var orderedAttendees = attendees.OrderBy(
    #if BADGE_DIAGNOSTICS
        attendee =>
        {
            Console.WriteLine($"Sort key: {attendee.Name}");
            return attendee.Name;
        }, StringComparer.Ordinal);
    #else
        attendee => attendee.Name, StringComparer.Ordinal);
    #endif
    #if BADGE_DIAGNOSTICS
    Console.WriteLine("After query definition.");
    #endif
    #else
    var orderedAttendees = attendees;
    #endif
    
  3. Vuelva a ejecutar dotnet run --file Program.cs . Antes de leer la salida, prediga si Sort key: aparece antes o después de After query definition..

La salida es la siguiente:

Before query definition.
After query definition.
Sort key: Ben
Sort key: Ada
Ada | Guest
Ben | Host

No aparecen claves de ordenación entre los dos mensajes de definición de consulta. La expresión lambda solo se ejecuta cuando foreach solicita el primer resultado. Ambos mensajes clave aparecen antes de la primera etiqueta debido a que OrderBy necesita cada clave para poder determinar qué asistente va primero. Las claves siguen el orden de entrada, Ben y luego Ada; los resultados siguen el orden ordenado, Ada y luego Ben.

La expresión lambda de diagnóstico necesita un mensaje y una clave devuelta, por lo que usa un cuerpo de instrucción de bloque con llaves y return, en lugar del cuerpo de una sola expresión original.

Con los diagnósticos activados, pruebe los dos conmutadores del experimento en el mismo archivo. No se necesitan cambios en los bucles o consultas:

  1. Quite el comentario #define BADGE_REPEAT y ejecútelo de nuevo:

    Before query definition.
    After query definition.
    Sort key: Ben
    Sort key: Ada
    Ada | Guest
    Ben | Host
    Sort key: Ben
    Sort key: Ada
    Ada | Guest
    Ben | Host
    

    El segundo paso repite la ordenación. La variable de consulta no almacena en caché los resultados.

  2. Mantenga BADGE_REPEAT habilitado, descomente #define BADGE_SNAPSHOT y vuelva a ejecutarlo:

    Before query definition.
    After query definition.
    Sort key: Ben
    Sort key: Ada
    Ada | Guest
    Ben | Host
    Ada | Guest
    Ben | Host
    

    Los dos mensajes clave ahora solo aparecen una vez, durante ToList, antes de que cualquiera de las dos pasadas imprima etiquetas. Ambos pases leen los datos de los asistentes almacenados.

Estos conmutadores seleccionan la secuencia para enumerar y el número de pases:

#if BADGE_SNAPSHOT
var attendeesToPrint = orderedAttendees.ToList();
#else
var attendeesToPrint = orderedAttendees;
#endif

int passes = 1;
#if BADGE_REPEAT
passes = 2;
#endif

La ejecución en modo eager significa que una operación se ejecuta inmediatamente en lugar de esperar a un recorrido posterior. ToList ejecuta la consulta inmediatamente y almacena sus resultados en una lista. Ambas iteraciones ahora leen los asistentes almacenados sin volver a ejecutar la ordenación. Por el contrario, la definición de la OrderBy consulta sigue aplazando la ordenación: lee y almacena en búfer toda la entrada solo cuando comienza la enumeración. El almacenamiento en búfer no significa que la consulta se ejecute al definirla. Utilice una instantánea cuando necesite reutilizar los resultados sin recalcularlos; mantenga la consulta diferida cuando desee que cada enumeración lea la entrada actual. Consulte Creación de consultas con resultados almacenados en caché.

Antes de continuar, comente BADGE_DIAGNOSTICS, BADGE_REPEAT y BADGE_SNAPSHOT de nuevo. Mantenga BADGE_SORT habilitado. Ejecute la aplicación para confirmar que solo aparecen las dos etiquetas ordenadas.

Generar las copias solicitadas con un iterador

Para expandir cada asistente al número solicitado de etiquetas, descomente #define BADGE_COPIES junto con BADGE_SORT. El bucle ahora selecciona la BADGE_COPIES rama mostrada anteriormente y llama al iterador ya en el archivo:

#if BADGE_COPIES
static IEnumerable<string> CreateLabels(Attendee attendee)
{
#if BADGE_DIAGNOSTICS
    Console.WriteLine($"Iterator started: {attendee.Name}");
#endif
    ArgumentOutOfRangeException.ThrowIfNegativeOrZero(attendee.Copies);

#if BADGE_FORMAT
    string message = attendee.Role switch
    {
        "Guest" => "Welcome!",
        "Host" => "Ask me for help",
        _ => throw new ArgumentException(
            $"Unsupported role: {attendee.Role}.", nameof(attendee))
    };
#endif

    for (int copy = 1; copy <= attendee.Copies; copy++)
    {
#if BADGE_FORMAT
        yield return FormatLabel(
            attendee.Name, attendee.Role, message, copy, attendee.Copies);
#else
        yield return $"{attendee.Name} | {attendee.Role} [{copy}/{attendee.Copies}]";
#endif
    }

#if BADGE_FORMAT
    static string FormatLabel(
        string name, string role, string message, int copy, int total) =>
        $"{name} | {role} | {message} [{copy}/{total}]";
#endif
}
#endif

Vuelva a ejecutar la aplicación:

Ada | Guest [1/2]
Ada | Guest [2/2]
Ben | Host [1/1]

CreateLabels es un iterador, una función que genera elementos de secuencia uno a uno. Su IEnumerable<T> tipo de valor devuelto, escrito como IEnumerable<string>, representa una secuencia de cadenas. Cada yield return proporciona una etiqueta al bucle interno foreach, que la imprime antes de solicitar la siguiente etiqueta. Vea Iteradores.

El for bucle cuenta copias de 1 a attendee.Copies. Un asistente genera tantas cadenas como solicitudes de Copies. Una colección también sería válida, pero el autor de la llamada solo necesita cada etiqueta una vez. El iterador evita una colección de etiquetas independiente y mantiene junto el bucle de número de copia. Los bucles externos e internos foreach separan la ordenación de asistentes de la producción de etiquetas; el autor de la llamada sigue siendo responsable de la impresión.

La llamada CreateLabels(attendee) no ejecuta su cuerpo. Al igual que la consulta LINQ, el iterador usa la ejecución diferida. El foreach interno inicia ese trabajo, incluida la comprobación de copia positiva. Una nueva enumeración vuelve a recorrer el iterador. A diferencia de OrderBy, que lee y ordena todos los asistentes antes de su primer resultado, este iterador proporciona una etiqueta sin crear las etiquetas restantes.

Para observar el orden de ejecución del iterador, descomente #define BADGE_DIAGNOSTICS de nuevo. Mantenga BADGE_REPEAT y BADGE_SNAPSHOT apagados. Los diagnósticos ahora muestran tanto la ordenación como el iterador:

Before query definition.
After query definition.
Sort key: Ben
Sort key: Ada
Sequence created: Ada
Iterator started: Ada
Ada | Guest [1/2]
Ada | Guest [2/2]
Sequence created: Ben
Iterator started: Ben
Ben | Host [1/1]

Para cada asistente, el mensaje creado por secuencia aparece antes del mensaje iniciado por iterador y las etiquetas. La creación de la secuencia no entra en el cuerpo del iterador; solicitar su primer elemento sí lo hace. Antes de continuar, comente BADGE_DIAGNOSTICS de nuevo.

Los mismos controles de experimento permanecen disponibles en las fases posteriores. BADGE_REPEAT enumera los asistentes dos veces y vuelve a crear la secuencia de etiquetas de cada asistente. BADGE_SNAPSHOT almacena en caché solo los asistentes ordenados, no las cadenas de etiqueta, por lo que evita otra ordenación, pero no impide que el iterador se ejecute de nuevo en cada pase.

La comprobación de ThrowIfNegativeOrZero notifica una entrada no válida en lugar de no generar ninguna etiqueta de forma silenciosa. Una excepción notifica un error que interrumpe la ejecución normal; consulte Crear y lanzar excepciones. Aquí, un recuento de copias no válido detiene el programa en lugar de omitir de forma silenciosa ese asistente.

Cálculo de un mensaje y asignación de nombre al paso de formato

Cada rol necesita un mensaje diferente. Descomente #define BADGE_FORMAT y deje BADGE_SORT y BADGE_COPIES habilitados. El mismo iterador ahora incluye el cálculo del mensaje:

#if BADGE_FORMAT
    string message = attendee.Role switch
    {
        "Guest" => "Welcome!",
        "Host" => "Ask me for help",
        _ => throw new ArgumentException(
            $"Unsupported role: {attendee.Role}.", nameof(attendee))
    };
#endif

También selecciona la yield return FormatLabel(...) rama e incluye este asistente local:

#if BADGE_FORMAT
    static string FormatLabel(
        string name, string role, string message, int copy, int total) =>
        $"{name} | {role} | {message} [{copy}/{total}]";
#endif

Vuelva a ejecutar la aplicación:

Ada | Guest | Welcome! [1/2]
Ada | Guest | Welcome! [2/2]
Ben | Host | Ask me for help [1/1]

La switch expresión calcula el mensaje una vez por asistente, antes del bucle de copia. Una expresión se ajusta porque cada rol selecciona un valor para asignar a message. Una cadena if/else o una instrucción switch también serían válidas, pero repetirían la asignación en cada rama.

La coincidencia de patrones compara una expresión con un patrón especificado. Aquí, attendee.Role es la expresión de entrada y los patrones constantes"Guest" y "Host" prueban su valor evaluado para seleccionar el mensaje coincidente. El patrón _de descarte coincide con cualquier otro rol y produce una excepción, por lo que los roles no admitidos no reciben un mensaje predeterminado engañoso. Consulte Coincidencia de patrones.

El nameof operador proporciona a la excepción el nombre del parámetro attendee. A diferencia de una cadena codificada de forma rígida, ese nombre permanece correcto si cambia el nombre del parámetro con una herramienta de refactorización.

FormatLabel es una función local, una función con nombre declarada dentro de otra función. Dado que solo CreateLabels necesita la operación de formato, su colocación dentro CreateLabels limita el asistente a la operación que la usa. Un método miembro privado en una clase haría que la función auxiliar estuviera disponible para otros miembros de esa clase. Elija ese ámbito más amplio si varias operaciones necesitan la misma operación de formato. Consulte Funciones locales.

El nombre FormatLabel le permite seguir el bucle de copia sin leer los detalles de formato. Mantener la cadena interpolada directamente en yield return también funcionaría, como funcionaba en el paso anterior. El nombre resulta más útil ahora que una etiqueta incluye un mensaje específico del rol. El cuerpo de expresión => del asistente devuelve la única cadena con formato.

Todas las entradas de formato se reciben mediante parámetros. Una función local no estática podría capturar o usar variables de su ámbito circundante. El modificador static impide que se produzca esa captura, por lo que sus dependencias permanecen explícitas. Consulte Parámetros y estado capturado.

Dado que CreateLabels sigue siendo un iterador, la comprobación de roles también se ejecuta solo cuando se inicia la enumeración. Para esta entrada controlada, esas comprobaciones corresponden a la producción de etiquetas. El programa no promete validar a todos los asistentes antes de que se imprima ninguna etiqueta.

Comprobar el total y ejecutar el programa completo

Para comparar la producción de etiquetas reales con el recuento solicitado, quite la marca de comentario #define BADGE_COUNT. Los cuatro símbolos de fase ahora están habilitados. Deje los tres símbolos del experimento comentados para la salida final normal.

La configuración de recuento se ejecuta antes del bucle de asistentes:

#if BADGE_COUNT
    int expected = attendees.Sum(attendee => attendee.Copies);
    int printed = 0;
#endif

Sum usa la ejecución inmediata para agregar los recuentos de copias de la matriz de entrada. A diferencia de OrderBy, devuelve un número, no una secuencia para enumerar más adelante. Su función lambda, attendee => attendee.Copies, selecciona el recuento de copias en línea. Al igual que con la selección de nombres, un método auxiliar independiente no aclararía esta expresión corta. Un bucle con un acumulador también funcionaría; Sum indica directamente que desea un total. Consulte Métodos LINQ comunes.

El printed++ incremento del bucle de etiquetas aumenta el contador solo cuando el bucle recibe una etiqueta. Después de ese bucle, la if instrucción compara ese recuento con el total solicitado y lanza una excepción si difieren:

#if BADGE_COUNT
    if (printed != expected)
    {
        throw new InvalidOperationException("Label count doesn't match the request.");
    }

    Console.WriteLine($"Printed {printed} of {expected} requested labels.");
#endif

Ahora está en la configuración predeterminada de todo el programa que copió al principio. No es necesario cambiar ningún método ni declaración:

#define BADGE_SORT
#define BADGE_COPIES
#define BADGE_FORMAT
#define BADGE_COUNT
// #define BADGE_DIAGNOSTICS
// #define BADGE_REPEAT
// #define BADGE_SNAPSHOT

Ejecútelo con dotnet run --file Program.cs. La salida es la siguiente:

Ada | Guest | Welcome! [1/2]
Ada | Guest | Welcome! [2/2]
Ben | Host | Ask me for help [1/1]
Printed 3 of 3 requested labels.

La matriz de entrada ya está en memoria. Sum lo lee inmediatamente, OrderBy lo ordena cuando comienza el bucle de asistentes y CreateLabels genera cadenas a medida que avanza el bucle interno. El programa nunca crea una colección independiente de etiquetas. Para obtener instrucciones más amplias sobre consultas, consulte LINQ y colecciones.

Para volver a consultar los diagnósticos con el programa final, habilite únicamente BADGE_DIAGNOSTICS de entre los símbolos de experimento. La salida es la siguiente:

Before query definition.
After query definition.
Sort key: Ben
Sort key: Ada
Sequence created: Ada
Iterator started: Ada
Ada | Guest | Welcome! [1/2]
Ada | Guest | Welcome! [2/2]
Sequence created: Ben
Iterator started: Ben
Ben | Host | Ask me for help [1/1]
Printed 3 of 3 requested labels.

Si también habilita BADGE_REPEAT, cada pasada comprueba e informa de su propio recuento. Con BADGE_SNAPSHOT habilitado, las claves de ordenación solo aparecen una vez, pero los mensajes de iterador, las etiquetas y el informe count siguen apareciendo en cada paso. Comente/desactive mediante comentarios los símbolos del experimento para restaurar la salida final normal.

Summary

Cada técnica cumple una función distinta dentro de la misma tarea:

  • Lambdas y LINQ seleccionan nombres y recuentos de copias en línea para la ordenación y el total.
  • Un iterador expresa la producción personalizada de uno a varios y deja el consumo de etiquetas al autor de la llamada.
  • Una switch expresión calcula un valor específico del rol y hace que los roles no admitidos se expliciten.
  • Una función local asigna un nombre al paso de formato, limita su ámbito y indica sus entradas.

Elija estos formularios cuando su ámbito, dependencias y orden de ejecución se ajusten a la operación. Un bucle directo, un método compartido o una colección completada pueden ajustarse mejor a otros llamadores.