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:
- Permita almacenar elementos de tipo
T
, obligando queT : IComparable<T>
. - Ofrezca métodos
Push(T item)
,T Pop()
yT Peek()
. - Incorpore un método
T Max()
que devuelva el elemento mayor actualmente almacenado en la pila. - Lance
InvalidOperationException
si se llama aPop
,Peek
oMax
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:
- Genere una lista de al menos 8 estudiantes con cursos “Matemáticas”, “Física” y “Literatura”.
- Obtenga los 3 estudiantes con mayor promedio, independientemente de su curso.
- 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:
- Exponga un evento
TemperatureChanged
de tipoEventHandler<TemperatureChangedEventArgs>
. - 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:
- Lea del usuario N URLs.
- Use
HttpClient
yasync/await
para descargarlas todas en paralelo. - Informe, para cada URL, cuántos bytes se han descargado y el tiempo que tardó.
- 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.
- 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.
- 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
Comments are closed