Creación de Tipos de Datos Personalizados en Programación Avanzada

Clase 30 de 35Curso de C# con .Net Core 2.1

Resumen

Domina el cálculo de promedios por asignatura en C# con un enfoque claro: usar LINQ para agrupar evaluaciones, añadir resultados a un diccionario y evitar tipos anónimos creando una clase específica. Verás por qué depender de object y casting complica el trabajo y cómo una estructura tipada hace el código más legible, seguro y mantenible.

¿Cómo agregar promedios de alumnos al diccionario de respuesta?

Para organizar la salida se renombra la lista como promediosAlumnos y se adiciona al diccionario de respuesta usando como key el nombre de la asignatura. La clave representa la asignatura, y el valor es la lista de promedios de sus alumnos.

  • Usar un diccionario con key string para la asignatura.
  • Guardar listas de promedios de alumnos como value.
  • Validar que la key sea el nombre de la asignatura.
// Ejemplo de estructura de carga
var promediosAlumnos = new List<AlumnoPromedio>();
respuesta.Add(asignaturaNombre, promediosAlumnos);

¿Qué clave usar en el diccionario por asignatura?

  • La key es el nombre de la asignatura.
  • Evita claves ambiguas: usa el nombre exacto de la asignatura.
  • Asegura unicidad por asignatura para consultar sin errores.

¿Cómo iterar los pares key-value con foreach?

  • Al recorrer la estructura, usa item.Key para la asignatura e item.Value para la lista de alumnos con promedio.
  • Diferencia claramente asignatura, ID de alumno y promedio.
foreach (var item in listaPromediosPorAsignatura)
{
    var asignatura = item.Key;             // nombre de la asignatura
    var alumnosConPromedio = item.Value;   // List<AlumnoPromedio>
}

¿Por qué evitar tipos anónimos y usar una clase AlumnoPromedio?

Cuando la lista interna usa tipos anónimos, el sistema la trata como object. Al intentar acceder a propiedades, no hay ayuda del compilador y terminas haciendo casting, lo que es frágil y propenso a fallos si la implementación cambia. En su lugar, define una clase concreta.

  • Los tipos anónimos son locales al método.
  • Fuera del método, solo quedan como object.
  • El casting con as requiere comprobaciones de nulos y añade complejidad.

¿Qué problema causa object y casting en tiempo de ejecución?

  • No hay acceso directo a propiedades.
  • Alto riesgo de errores si cambia la forma de los datos.
foreach (var alum in item.Value)
{
    var tmp = alum as Alumno; // puede ser null
    if (tmp != null)
    {
        // Acceso a propiedades, pero con riesgo y ruido.
    }
}

¿Cómo definir AlumnoPromedio y mapear propiedades?

Se crea un tipo fuerte y simple. Se discutió el encapsulamiento, pero para el ejercicio se dejó como campos públicos para reducir sobrecarga.

public class AlumnoPromedio
{
    public float promedio;
    public string alumnoId;
    public string alumnoNombre; // se llena al enriquecer el grupo
}
  • promedio: promedio final del alumno en la asignatura.
  • alumnoId: identifica al alumno de forma estable.
  • alumnoNombre: se agrega para lectura humana.
var ap = new AlumnoPromedio {
    alumnoId = keyAlumno,         // obtenido del agrupamiento
    alumnoNombre = nombreAlumno,  // enriquecido en el grupo
    promedio = promedioCalculado
};

¿Cómo usar LINQ para agrupar por asignatura y calcular promedios?

La clave está en agrupar por asignatura y, dentro del grupo, generar una clave compuesta con ID de alumno y nombre para poder poblar ambos valores. Así se evita depender de datos fuera del grupo y se completa AlumnoPromedio correctamente.

  • Agrupar por asignatura y por alumno con clave compuesta.
  • Calcular el promedio con las notas del grupo.
  • Mapear a AlumnoPromedio y adicionar al diccionario.
var consulta = evaluaciones
    .GroupBy(eval => new {
        asignatura = eval.Asignatura.Nombre,
        uniqueId = eval.Alumno.Id,
        nombre = eval.Alumno.Nombre
    })
    .Select(g => new {
        g.Key.asignatura,
        alumno = new AlumnoPromedio {
            alumnoId = g.Key.uniqueId,
            alumnoNombre = g.Key.nombre,
            promedio = g.Average(e => e.Nota)
        }
    });

// Ensamblar el diccionario final por asignatura
var respuesta = new Dictionary<string, List<AlumnoPromedio>>();
foreach (var item in consulta)
{
    if (!respuesta.TryGetValue(item.asignatura, out var lista))
    {
        lista = new List<AlumnoPromedio>();
        respuesta[item.asignatura] = lista;
    }
    lista.Add(item.alumno);
}

¿Qué consultas LINQ se aplicaron con keywords?

  • from, select, where: para proyectar y filtrar datos.
  • group by: para agrupar por asignatura y alumno.
  • distinct: para eliminar duplicados donde correspondía.
  • order by: ya utilizado en ejercicios previos para ordenar listas.

¿Qué se validó en el resultado final?

  • Asignaturas como Matemáticas y Castellano con su lista de alumnos.
  • Cada entrada con alumnoNombre, alumnoId y promedio.
  • Comprobación visual de nombres y promedios cargados correctamente.

¿Te gustaría que preparemos una variante que incluya ordenamiento por promedio o filtros por asignatura específica? Deja tus dudas o ideas en los comentarios y armamos la siguiente mejora juntos.

      Creación de Tipos de Datos Personalizados en Programación Avanzada