COMPARTE ESTE ARTÍCULO

En este examen encontrarás cinco ejercicios de nivel avanzado que cubren conceptos como genéricos con restricciones, LINQ, eventos, programación asíncrona y reflexión. Cada enunciado va seguido de su solución completa en C# para que puedas comparar tu implementación.


1. Pila genérica con restricción y método Máximo

Enunciado:
Implementa una clase genérica BoundedStack<T> que:

  1. Permita almacenar elementos de tipo T, obligando que T : IComparable<T>.
  2. Ofrezca métodos Push(T item), T Pop() y T Peek().
  3. Incorpore un método T Max() que devuelva el elemento mayor actualmente almacenado en la pila.
  4. Lance InvalidOperationException si se llama a Pop, Peek o Max cuando la pila está vacía.

Solución:

using System;
using System.Collections.Generic;
using System.Linq;

public class BoundedStack<T> where T : IComparable<T>
{
    private readonly List<T> _items = new List<T>();

    public void Push(T item) => _items.Add(item);

    public T Pop()
    {
        if (!_items.Any())
            throw new InvalidOperationException("Pila vacía.");
        var top = _items.Last();
        _items.RemoveAt(_items.Count - 1);
        return top;
    }

    public T Peek()
    {
        if (!_items.Any())
            throw new InvalidOperationException("Pila vacía.");
        return _items.Last();
    }

    public T Max()
    {
        if (!_items.Any())
            throw new InvalidOperationException("Pila vacía.");
        // LINQ para encontrar el mayor
        return _items.Max();
    }
}

// Ejemplo de uso
class Programa
{
    static void Main()
    {
        var stack = new BoundedStack<int>();
        stack.Push(5);
        stack.Push(2);
        stack.Push(8);
        Console.WriteLine($"Tope: {stack.Peek()}");     // 8
        Console.WriteLine($"Máximo: {stack.Max()}");    // 8
        Console.WriteLine($"Pop: {stack.Pop()}");       // 8
        Console.WriteLine($"Nuevo Máximo: {stack.Max()}"); // 5
    }
}

2. Consultas LINQ avanzadas sobre objetos

Enunciado:
Dada la clase Estudiante { string Nombre; string Curso; double[] Notas; }, escribe un programa que:

  1. Genere una lista de al menos 8 estudiantes con cursos “Matemáticas”, “Física” y “Literatura”.
  2. Obtenga los 3 estudiantes con mayor promedio, independientemente de su curso.
  3. Agrupe a todos los estudiantes por curso y muestre, para cada curso, el promedio general de sus alumnos.

Solución:

using System;
using System.Collections.Generic;
using System.Linq;

public class Estudiante
{
    public string Nombre { get; set; }
    public string Curso { get; set; }
    public double[] Notas { get; set; }
    public double Promedio => Notas.Average();
}

class ProgramaLINQ
{
    static void Main()
    {
        var alumnos = new List<Estudiante>
        {
            new Estudiante { Nombre="Ana",   Curso="Matemáticas", Notas=new[]{8.5, 9.0, 7.5} },
            new Estudiante { Nombre="Luis",  Curso="Física",       Notas=new[]{6.0, 7.0, 5.5} },
            new Estudiante { Nombre="María", Curso="Literatura",   Notas=new[]{9.5, 8.5, 9.0} },
            new Estudiante { Nombre="Jorge", Curso="Matemáticas",  Notas=new[]{7.0, 7.5, 8.0} },
            new Estudiante { Nombre="Elena", Curso="Física",       Notas=new[]{8.0, 8.5, 8.0} },
            new Estudiante { Nombre="Pablo", Curso="Literatura",   Notas=new[]{6.5, 7.0, 6.0} },
            new Estudiante { Nombre="Sofía", Curso="Matemáticas",  Notas=new[]{9.0, 9.5, 9.0} },
            new Estudiante { Nombre="Raúl",  Curso="Física",       Notas=new[]{7.5, 7.0, 8.0} }
        };

        // 1. Top 3 por promedio
        var top3 = alumnos
            .OrderByDescending(e => e.Promedio)
            .Take(3);

        Console.WriteLine("Top 3 estudiantes por promedio:");
        foreach (var e in top3)
            Console.WriteLine($"{e.Nombre} ({e.Curso}) – Promedio: {e.Promedio:F2}");

        // 2. Agrupar por curso y calcular promedio general
        var porCurso = alumnos
            .GroupBy(e => e.Curso)
            .Select(g => new 
            {
                Curso = g.Key,
                PromedioCurso = g.Average(e => e.Promedio)
            });

        Console.WriteLine("\nPromedio por curso:");
        foreach (var grupo in porCurso)
            Console.WriteLine($"{grupo.Curso}: {grupo.PromedioCurso:F2}");
    }
}

3. Implementación del patrón Observer con eventos

Enunciado:
Crea una clase Thermometer que:

  1. Exponga un evento TemperatureChanged de tipo EventHandler<TemperatureChangedEventArgs>.
  2. Tenga propiedad double Temperature. Al cambiarla, dispare el evento con el valor antiguo y el nuevo.

Define TemperatureChangedEventArgs con OldTemperature y NewTemperature. Luego, en Main, crea un termómetro y suscribe dos manejadores:

  • Uno que muestre en consola el cambio.
  • Otro que escriba en un log (simulado con List<string>).

Solución:

using System;
using System.Collections.Generic;

// Argumentos del evento
public class TemperatureChangedEventArgs : EventArgs
{
    public double OldTemperature { get; }
    public double NewTemperature { get; }

    public TemperatureChangedEventArgs(double oldTemp, double newTemp)
    {
        OldTemperature = oldTemp;
        NewTemperature = newTemp;
    }
}

// Publicador
public class Thermometer
{
    private double _temperature;
    public event EventHandler<TemperatureChangedEventArgs> TemperatureChanged;

    public double Temperature
    {
        get => _temperature;
        set
        {
            if (Math.Abs(value - _temperature) > 0.001)
            {
                var args = new TemperatureChangedEventArgs(_temperature, value);
                _temperature = value;
                OnTemperatureChanged(args);
            }
        }
    }

    protected virtual void OnTemperatureChanged(TemperatureChangedEventArgs e)
        => TemperatureChanged?.Invoke(this, e);
}

// Suscriptores
class ProgramaObserver
{
    static void Main()
    {
        var thermometer = new Thermometer();
        var log = new List<string>();

        // Suscriptor consola
        thermometer.TemperatureChanged += (s, e) =>
        {
            Console.WriteLine($"Temperatura cambió de {e.OldTemperature:F1}°C a {e.NewTemperature:F1}°C");
        };

        // Suscriptor log
        thermometer.TemperatureChanged += (s, e) =>
        {
            log.Add($"[{DateTime.Now}] {e.OldTemperature:F1}°C → {e.NewTemperature:F1}°C");
        };

        // Simulación de cambios
        thermometer.Temperature = 20.0;
        thermometer.Temperature = 21.5;
        thermometer.Temperature = 19.8;

        // Mostrar log
        Console.WriteLine("\nRegistro de cambios:");
        log.ForEach(Console.WriteLine);
    }
}

4. Descarga asíncrona de múltiples URLs

Enunciado:
Escribe un programa que:

  1. Lea del usuario N URLs.
  2. Use HttpClient y async/await para descargarlas todas en paralelo.
  3. Informe, para cada URL, cuántos bytes se han descargado y el tiempo que tardó.
  4. Maneje excepciones individuales sin abortar las demás descargas.

Solución:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;

class ProgramaAsync
{
    static async Task Main()
    {
        Console.Write("¿Cuántas URLs quieres descargar? ");
        if (!int.TryParse(Console.ReadLine(), out int n) || n < 1) return;

        var urls = new List<string>();
        for (int i = 0; i < n; i++)
        {
            Console.Write($"URL #{i + 1}: ");
            urls.Add(Console.ReadLine());
        }

        var client = new HttpClient();
        var tasks = new List<Task>();

        foreach (var url in urls)
        {
            tasks.Add(DownloadAsync(client, url));
        }

        await Task.WhenAll(tasks);
    }

    static async Task DownloadAsync(HttpClient client, string url)
    {
        try
        {
            var sw = Stopwatch.StartNew();
            var data = await client.GetByteArrayAsync(url);
            sw.Stop();
            Console.WriteLine($"[{url}] {data.Length} bytes en {sw.ElapsedMilliseconds} ms");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error descargando {url}: {ex.Message}");
        }
    }
}

5. Reflexión para invocar métodos con atributo custom

Enunciado:
Define un atributo RunMeAttribute : Attribute. Luego, en un ensamblado cualquiera, habrá varias clases con métodos estáticos marcados con [RunMe] y sin parámetros.

  1. Usa reflexión para:
    • Cargar el ensamblado actual.
    • Buscar todos los métodos públicos estáticos sin parámetros que tengan el atributo RunMeAttribute.
    • Invócalos en tiempo de ejecución.
  2. Muestra el nombre de cada método invocado.

Solución:

using System;
using System.Linq;
using System.Reflection;

// Definición del atributo
[AttributeUsage(AttributeTargets.Method)]
public class RunMeAttribute : Attribute { }

// Ejemplo de métodos decorados
public class Tareas
{
    [RunMe]
    public static void Tarea1() => Console.WriteLine("Ejecutando Tarea1");

    [RunMe]
    public static void Tarea2() => Console.WriteLine("Ejecutando Tarea2");
}

class ProgramaReflection
{
    static void Main()
    {
        var asm = Assembly.GetExecutingAssembly();
        var tipos = asm.GetTypes();

        foreach (var tipo in tipos)
        {
            var metodos = tipo.GetMethods(BindingFlags.Public | BindingFlags.Static)
                              .Where(m => m.GetCustomAttributes(typeof(RunMeAttribute), false).Any()
                                          && m.GetParameters().Length == 0);

            foreach (var metodo in metodos)
            {
                Console.WriteLine($"Invocando {tipo.FullName}.{metodo.Name}()");
                metodo.Invoke(null, null);
            }
        }
    }
}

Contenido restringido

Acceso de usuarios existentes
   
Registro de un nuevo usuario
*Campo necesario

Categories:

Tags:

Comments are closed

Estado de acceso
ESTADO DE ACCESO
TRADUCTORES
COMPARTENOS
error: CONTENIDO PROTEGIDO