CSharp
Tutorial CSharp: Polimorfismo
Aprende el polimorfismo en C# y cómo usar casting seguro con operadores is y as para un código orientado a objetos robusto y flexible.
Aprende CSharp y certifícatePolimorfismo en acción
El polimorfismo es uno de los pilares fundamentales de la programación orientada a objetos que permite tratar objetos de diferentes clases a través de una interfaz común. En C#, el polimorfismo nos permite trabajar con objetos de clases derivadas a través de referencias a sus clases base.
Para entender el polimorfismo en acción, imaginemos un escenario práctico. Supongamos que estamos desarrollando un sistema para una tienda de mascotas que maneja diferentes tipos de animales. Cada animal tiene comportamientos comunes, pero los implementa de manera diferente.
Primero, definamos una clase base Animal
con un método virtual:
public class Animal
{
public string Nombre { get; set; }
public Animal(string nombre)
{
Nombre = nombre;
}
// Método virtual que puede ser sobrescrito por clases derivadas
public virtual string HacerSonido()
{
return "...";
}
public virtual void Alimentar()
{
Console.WriteLine($"Alimentando a {Nombre}");
}
}
Ahora, creemos algunas clases derivadas que sobrescriban estos métodos:
public class Perro : Animal
{
public Perro(string nombre) : base(nombre) { }
// Sobrescribimos el método de la clase base
public override string HacerSonido()
{
return "¡Guau!";
}
public override void Alimentar()
{
Console.WriteLine($"Dando croquetas a {Nombre}");
}
// Método específico de la clase Perro
public void Pasear()
{
Console.WriteLine($"Paseando a {Nombre}");
}
}
public class Gato : Animal
{
public Gato(string nombre) : base(nombre) { }
public override string HacerSonido()
{
return "¡Miau!";
}
public override void Alimentar()
{
Console.WriteLine($"Sirviendo atún a {Nombre}");
}
// Método específico de la clase Gato
public void Acicalar()
{
Console.WriteLine($"Acicalando a {Nombre}");
}
}
Ahora veamos el polimorfismo en acción. Podemos crear una lista de animales que contenga tanto perros como gatos, y tratarlos de manera uniforme a través de la interfaz común de la clase Animal
:
static void Main(string[] args)
{
// Creamos una lista de tipo Animal
List<Animal> animales = new List<Animal>
{
new Perro("Bobby"),
new Gato("Whiskers"),
new Perro("Rex"),
new Gato("Mittens")
};
// Iteramos sobre la lista y llamamos a los métodos polimórficos
foreach (Animal animal in animales)
{
Console.WriteLine($"{animal.Nombre} dice: {animal.HacerSonido()}");
animal.Alimentar();
Console.WriteLine();
}
}
La salida del programa sería:
Bobby dice: ¡Guau!
Dando croquetas a Bobby
Whiskers dice: ¡Miau!
Sirviendo atún a Whiskers
Rex dice: ¡Guau!
Dando croquetas a Rex
Mittens dice: ¡Miau!
Sirviendo atún a Mittens
Esto demuestra el polimorfismo en acción: aunque estamos tratando todos los objetos como Animal
, cada uno ejecuta su propia implementación de los métodos HacerSonido()
y Alimentar()
. C# determina en tiempo de ejecución qué método debe invocar basándose en el tipo real del objeto, no en el tipo de la referencia.
Veamos otro ejemplo práctico con una jerarquía de formas geométricas:
public abstract class Forma
{
// Método abstracto que debe ser implementado por todas las clases derivadas
public abstract double CalcularArea();
// Método virtual con implementación por defecto
public virtual void Dibujar()
{
Console.WriteLine("Dibujando una forma");
}
}
public class Circulo : Forma
{
public double Radio { get; set; }
public Circulo(double radio)
{
Radio = radio;
}
public override double CalcularArea()
{
return Math.PI * Radio * Radio;
}
public override void Dibujar()
{
Console.WriteLine($"Dibujando un círculo con radio {Radio}");
}
}
public class Rectangulo : Forma
{
public double Ancho { get; set; }
public double Alto { get; set; }
public Rectangulo(double ancho, double alto)
{
Ancho = ancho;
Alto = alto;
}
public override double CalcularArea()
{
return Ancho * Alto;
}
public override void Dibujar()
{
Console.WriteLine($"Dibujando un rectángulo de {Ancho}x{Alto}");
}
}
Ahora podemos usar el polimorfismo para procesar diferentes formas de manera uniforme:
static void ProcesarFormas()
{
Forma[] formas = new Forma[]
{
new Circulo(5),
new Rectangulo(4, 6),
new Circulo(3)
};
foreach (Forma forma in formas)
{
// Llamamos a métodos polimórficos
forma.Dibujar();
Console.WriteLine($"Área: {forma.CalcularArea():F2}");
Console.WriteLine();
}
}
La salida sería:
Dibujando un círculo con radio 5
Área: 78.54
Dibujando un rectángulo de 4x6
Área: 24.00
Dibujando un círculo con radio 3
Área: 28.27
El polimorfismo también se aplica cuando pasamos objetos como parámetros a métodos. Por ejemplo:
static void MostrarInformacion(Forma forma)
{
Console.WriteLine($"Tipo: {forma.GetType().Name}");
Console.WriteLine($"Área: {forma.CalcularArea():F2}");
forma.Dibujar();
}
// Uso:
MostrarInformacion(new Circulo(10)); // Pasamos un Circulo como Forma
MostrarInformacion(new Rectangulo(5, 8)); // Pasamos un Rectangulo como Forma
El polimorfismo en acción nos permite escribir código más flexible y extensible. Podemos agregar nuevas clases derivadas sin modificar el código existente que trabaja con la clase base. Por ejemplo, si añadimos una nueva clase Triangulo
que herede de Forma
, todo nuestro código que procesa formas funcionará automáticamente con triángulos.
public class Triangulo : Forma
{
public double Base { get; set; }
public double Altura { get; set; }
public Triangulo(double baseTriangulo, double altura)
{
Base = baseTriangulo;
Altura = altura;
}
public override double CalcularArea()
{
return (Base * Altura) / 2;
}
public override void Dibujar()
{
Console.WriteLine($"Dibujando un triángulo con base {Base} y altura {Altura}");
}
}
Ahora podemos incluir triángulos en nuestro array de formas sin cambiar el código que procesa las formas:
Forma[] formas = new Forma[]
{
new Circulo(5),
new Rectangulo(4, 6),
new Triangulo(8, 3)
};
// El mismo código funciona con la nueva clase
foreach (Forma forma in formas)
{
forma.Dibujar();
Console.WriteLine($"Área: {forma.CalcularArea():F2}");
}
Esta capacidad de tratar objetos de diferentes clases a través de una interfaz común es la esencia del polimorfismo en acción y es una de las características más potentes de la programación orientada a objetos en C#.
Casting seguro
Cuando trabajamos con polimorfismo en C#, a menudo necesitamos convertir objetos entre tipos relacionados en la jerarquía de clases. Aunque podemos usar referencias de tipo base para manipular objetos de tipos derivados, en ocasiones necesitamos acceder a miembros específicos de la clase derivada. Para esto utilizamos el casting o conversión de tipos.
El casting tradicional en C# se realiza mediante paréntesis, pero puede generar excepciones si la conversión no es válida. Por ejemplo:
Animal miAnimal = new Perro("Fido");
// Casting tradicional - peligroso si miAnimal no es realmente un Perro
Perro miPerro = (Perro)miAnimal; // Funciona porque miAnimal realmente es un Perro
Sin embargo, si intentamos hacer un casting incorrecto, obtendremos una excepción:
Animal miAnimal = new Gato("Felix");
// Esto lanzará una InvalidCastException en tiempo de ejecución
Perro miPerro = (Perro)miAnimal; // Error: no se puede convertir un Gato en Perro
Para evitar estas excepciones, C# proporciona mecanismos de casting seguro que nos permiten verificar la compatibilidad de tipos antes de realizar la conversión.
Operador de conversión segura: as
El operador as
intenta realizar una conversión entre tipos compatibles. Si la conversión no es posible, devuelve null
en lugar de lanzar una excepción:
Animal miAnimal = new Gato("Felix");
// Intenta convertir a Perro, pero devuelve null si no es posible
Perro miPerro = miAnimal as Perro; // miPerro será null
// Verificamos antes de usar
if (miPerro != null)
{
miPerro.Pasear(); // Solo se ejecuta si la conversión fue exitosa
}
else
{
Console.WriteLine("El animal no es un perro");
}
Este enfoque es especialmente útil cuando no estamos seguros del tipo exacto del objeto y queremos evitar excepciones:
void ProcesarAnimal(Animal animal)
{
// Intentamos convertir de forma segura
Perro perro = animal as Perro;
if (perro != null)
{
// Podemos usar métodos específicos de Perro
perro.Pasear();
return;
}
Gato gato = animal as Gato;
if (gato != null)
{
// Podemos usar métodos específicos de Gato
gato.Acicalar();
return;
}
// Si llegamos aquí, el animal no es ni perro ni gato
Console.WriteLine("Animal de tipo desconocido");
}
Es importante recordar que el operador as
solo funciona con tipos de referencia y tipos anulables. No puede usarse con tipos de valor a menos que sean anulables.
Comprobación de tipos con pattern matching
C# moderno ofrece una sintaxis más elegante para el casting seguro mediante pattern matching:
void ProcesarAnimalModerno(Animal animal)
{
// Usando pattern matching para casting y comprobación en una sola operación
if (animal is Perro perro)
{
// La variable 'perro' está disponible en este ámbito
Console.WriteLine($"{perro.Nombre} es un perro");
perro.Pasear();
}
else if (animal is Gato gato)
{
Console.WriteLine($"{gato.Nombre} es un gato");
gato.Acicalar();
}
else
{
Console.WriteLine("Tipo de animal desconocido");
}
}
Esta sintaxis combina la comprobación de tipo y la conversión en una sola operación, lo que hace que el código sea más limpio y menos propenso a errores.
Casting seguro con switch expressions
Para casos más complejos, podemos utilizar las expresiones switch
con pattern matching:
string DescribirAnimal(Animal animal)
{
return animal switch
{
Perro perro => $"{perro.Nombre} es un perro que hace {perro.HacerSonido()}",
Gato gato => $"{gato.Nombre} es un gato que hace {gato.HacerSonido()}",
_ => $"Animal desconocido llamado {animal.Nombre}"
};
}
Este enfoque es particularmente útil cuando necesitamos realizar diferentes acciones basadas en el tipo del objeto.
Ejemplo práctico de casting seguro
Veamos un ejemplo completo que demuestra varias técnicas de casting seguro:
public class GestionAnimales
{
public void ProcesarListaAnimales(List<Animal> animales)
{
foreach (var animal in animales)
{
// Método 1: Usando 'as' con verificación null
Perro perroConAs = animal as Perro;
if (perroConAs != null)
{
Console.WriteLine($"Método 1: {perroConAs.Nombre} es un perro");
continue; // Pasamos al siguiente animal
}
// Método 2: Usando pattern matching con 'is'
if (animal is Gato gatoConIs)
{
Console.WriteLine($"Método 2: {gatoConIs.Nombre} es un gato");
continue;
}
// Método 3: Usando switch con pattern matching
switch (animal)
{
case Perro p when p.Nombre.StartsWith("R"):
Console.WriteLine($"Método 3: {p.Nombre} es un perro cuyo nombre empieza con R");
break;
case Gato g when g.Nombre.Length > 5:
Console.WriteLine($"Método 3: {g.Nombre} es un gato con nombre largo");
break;
default:
Console.WriteLine($"Método 3: {animal.Nombre} es un animal de tipo {animal.GetType().Name}");
break;
}
}
}
}
Consideraciones de rendimiento
Al elegir entre diferentes métodos de casting seguro, es importante considerar el rendimiento:
- El operador
as
es generalmente más rápido que una combinación deis
seguido de un casting tradicional, ya que solo realiza la comprobación de tipo una vez. - El pattern matching con
is
es más legible y seguro, aunque puede ser ligeramente menos eficiente en algunos casos. - Para múltiples comprobaciones de tipo en el mismo objeto, el
switch
con pattern matching suele ser la opción más eficiente y legible.
Casting seguro con tipos genéricos
También podemos implementar métodos genéricos para realizar casting seguro:
public T CastSeguro<T>(Animal animal) where T : Animal
{
if (animal is T resultado)
{
return resultado;
}
return null; // O lanzar una excepción, según el caso de uso
}
// Uso:
Perro perro = CastSeguro<Perro>(miAnimal);
if (perro != null)
{
perro.Pasear();
}
Este enfoque es útil cuando necesitamos realizar el mismo tipo de casting en múltiples lugares del código.
El casting seguro es una herramienta fundamental cuando trabajamos con jerarquías de clases y polimorfismo en C#, permitiéndonos aprovechar la flexibilidad del polimorfismo mientras mantenemos la seguridad de tipos y evitamos excepciones en tiempo de ejecución.
Operadores is y as
En C#, los operadores is
y as
son herramientas fundamentales para trabajar con el polimorfismo y realizar comprobaciones y conversiones de tipos de manera segura. Estos operadores nos permiten interactuar con la jerarquía de clases sin arriesgar excepciones en tiempo de ejecución.
El operador is
El operador is
evalúa si un objeto es compatible con un tipo específico, devolviendo un valor booleano (true
o false
) como resultado. Este operador no realiza ninguna conversión, simplemente verifica la compatibilidad de tipos.
La sintaxis básica es:
if (objeto is TipoDestino)
{
// El objeto es compatible con TipoDestino
}
Por ejemplo, podemos verificar si un objeto de tipo Animal
es realmente un Perro
:
Animal animal = new Perro("Rex");
if (animal is Perro)
{
Console.WriteLine("El animal es un perro");
}
El operador is
es especialmente útil cuando necesitamos realizar diferentes acciones según el tipo real del objeto:
void RealizarAccionEspecifica(Animal animal)
{
if (animal is Perro)
{
Console.WriteLine("Procesando un perro");
// Aquí necesitaríamos hacer un casting para acceder a miembros específicos
}
else if (animal is Gato)
{
Console.WriteLine("Procesando un gato");
}
else
{
Console.WriteLine("Animal genérico");
}
}
Pattern matching con is
A partir de C# 7.0, el operador is
se mejoró con pattern matching, permitiendo declarar una variable del tipo destino en la misma expresión:
if (animal is Perro perro)
{
// La variable 'perro' está disponible aquí y es del tipo Perro
Console.WriteLine($"El perro se llama {perro.Nombre}");
perro.Pasear(); // Podemos llamar a métodos específicos de Perro
}
Esta sintaxis combina la comprobación de tipo y la asignación en una sola operación, lo que hace el código más conciso y legible. La variable declarada (perro
en este caso) solo está disponible dentro del bloque if
y solo se asigna si la comprobación es exitosa.
También podemos usar condiciones adicionales con pattern matching:
if (animal is Perro perro && perro.Nombre.StartsWith("R"))
{
Console.WriteLine($"Es un perro cuyo nombre empieza por R: {perro.Nombre}");
}
El operador as
El operador as
intenta convertir un objeto a un tipo específico, pero a diferencia del casting tradicional, devuelve null
si la conversión no es posible en lugar de lanzar una excepción.
La sintaxis básica es:
TipoDestino variable = objeto as TipoDestino;
Por ejemplo:
Animal animal = new Gato("Whiskers");
// Intenta convertir a Perro
Perro perro = animal as Perro; // perro será null porque animal es un Gato
// Verificamos antes de usar
if (perro != null)
{
perro.Pasear();
}
else
{
Console.WriteLine("No es un perro");
}
El operador as
solo funciona con:
- Tipos de referencia (clases)
- Tipos anulables (como
int?
,bool?
, etc.)
No puede usarse con tipos de valor no anulables:
// Esto NO compila
int numero = objeto as int; // Error
// Esto SÍ compila
int? numeroNulable = objeto as int?; // Correcto
Comparación entre is y as
Aunque ambos operadores se utilizan para trabajar con tipos de manera segura, tienen propósitos diferentes:
- is: Verifica si un objeto es compatible con un tipo (devuelve
bool
) - as: Intenta convertir un objeto a un tipo (devuelve el objeto convertido o
null
)
Veamos un ejemplo comparativo:
// Enfoque con 'is' (requiere casting adicional)
if (animal is Perro)
{
Perro perro = (Perro)animal; // Casting tradicional
perro.Pasear();
}
// Enfoque con 'is' y pattern matching (más moderno)
if (animal is Perro perro)
{
perro.Pasear();
}
// Enfoque con 'as'
Perro perro = animal as Perro;
if (perro != null)
{
perro.Pasear();
}
Escenarios de uso
Cuando usar is:
- Cuando solo necesitas verificar el tipo sin necesariamente acceder a sus miembros específicos
- Cuando quieres combinar la verificación de tipo con otras condiciones
- Con pattern matching, cuando necesitas verificar y convertir en una sola operación
// Verificación simple
bool esPerro = animal is Perro;
// Con pattern matching y condiciones adicionales
if (animal is Perro p && p.Edad > 5)
{
Console.WriteLine("Es un perro adulto");
}
Cuando usar as:
- Cuando necesitas convertir un objeto para acceder a sus miembros específicos
- Cuando la conversión podría fallar y prefieres manejar el caso
null
en lugar de una excepción - Cuando planeas usar el objeto convertido en múltiples lugares
Perro perro = animal as Perro;
if (perro != null)
{
// Usamos perro en múltiples operaciones
perro.Pasear();
perro.Alimentar();
Console.WriteLine(perro.HacerSonido());
}
Ejemplo práctico: Sistema de notificaciones
Veamos un ejemplo práctico donde usamos ambos operadores en un sistema de notificaciones:
public abstract class Notificacion
{
public string Mensaje { get; set; }
public DateTime Fecha { get; set; }
public abstract void Mostrar();
}
public class NotificacionEmail : Notificacion
{
public string DireccionEmail { get; set; }
public override void Mostrar()
{
Console.WriteLine($"Email a {DireccionEmail}: {Mensaje}");
}
public void Reenviar(string nuevaDireccion)
{
Console.WriteLine($"Reenviando email a {nuevaDireccion}");
}
}
public class NotificacionSMS : Notificacion
{
public string NumeroTelefono { get; set; }
public override void Mostrar()
{
Console.WriteLine($"SMS a {NumeroTelefono}: {Mensaje}");
}
public void EnviarRecordatorio()
{
Console.WriteLine($"Enviando recordatorio a {NumeroTelefono}");
}
}
Ahora, implementemos un procesador de notificaciones que utilice los operadores is
y as
:
public class ProcesadorNotificaciones
{
public void Procesar(List<Notificacion> notificaciones)
{
foreach (var notificacion in notificaciones)
{
// Primero mostramos todas las notificaciones (polimorfismo)
notificacion.Mostrar();
// Usando 'is' con pattern matching
if (notificacion is NotificacionEmail email && email.DireccionEmail.EndsWith("@empresa.com"))
{
Console.WriteLine("Notificación interna de la empresa");
email.Reenviar("archivos@empresa.com");
}
// Usando 'as'
NotificacionSMS sms = notificacion as NotificacionSMS;
if (sms != null && sms.NumeroTelefono.StartsWith("+34"))
{
Console.WriteLine("Notificación a número español");
sms.EnviarRecordatorio();
}
}
}
}
Uso con interfaces
Los operadores is
y as
también funcionan con interfaces, lo que los hace muy útiles para trabajar con sistemas basados en interfaces:
public interface IImprimible
{
void Imprimir();
}
public class Documento : IImprimible
{
public string Titulo { get; set; }
public void Imprimir()
{
Console.WriteLine($"Imprimiendo documento: {Titulo}");
}
public void GuardarPDF()
{
Console.WriteLine("Guardando como PDF");
}
}
// Uso:
object obj = new Documento { Titulo = "Informe Anual" };
// Verificando si implementa la interfaz
if (obj is IImprimible imprimible)
{
imprimible.Imprimir();
}
// Convertir a la clase concreta
Documento doc = obj as Documento;
if (doc != null)
{
doc.GuardarPDF();
}
Rendimiento y buenas prácticas
Al trabajar con los operadores is
y as
, considera estas recomendaciones:
- Evita verificaciones redundantes: Si ya has usado
is
para verificar un tipo, no usesas
inmediatamente después para la misma conversión.
// Mal (redundante)
if (animal is Perro)
{
Perro perro = animal as Perro; // Innecesario, ya sabemos que es un Perro
perro.Pasear();
}
// Bien (con pattern matching)
if (animal is Perro perro)
{
perro.Pasear();
}
Prefiere pattern matching: En código moderno, el pattern matching con
is
suele ser más legible que usaras
seguido de una verificación de nulidad.Considera el rendimiento: Para múltiples verificaciones de tipo en el mismo objeto, considera usar
switch
con pattern matching:
switch (animal)
{
case Perro perro:
perro.Pasear();
break;
case Gato gato:
gato.Acicalar();
break;
default:
Console.WriteLine("Animal desconocido");
break;
}
Los operadores is
y as
son herramientas esenciales para trabajar con jerarquías de clases en C#, permitiéndonos aprovechar el polimorfismo mientras mantenemos un código seguro y robusto.
Otros ejercicios de programación de CSharp
Evalúa tus conocimientos de esta lección Polimorfismo con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
CRUD en C# de modelo Customer sobre una lista
Arrays y listas
Objetos
Excepciones
Eventos
Lambdas
Diccionarios en C#
Variables y constantes
Tipos de datos
Herencia
Operadores
Uso de consultas LINQ
Clases y encapsulación
Uso de consultas LINQ
Excepciones
Control de flujo
Eventos
Diccionarios
Tipos de datos
Conjuntos, colas y pilas
Lambdas
Conjuntos, colas y pilas
Uso de async y await
Tareas
Constructores y destructores
Operadores
Arrays y listas
Polimorfismo
Polimorfismo
Variables y constantes
Proyecto colecciones y LINQ en C#
Clases y encapsulación
Creación de proyecto C#
Uso de async y await
Funciones
Delegados
Delegados
Constructores y destructores
Objetos
Control de flujo
Funciones
Tareas
Proyecto sintaxis en C#
Herencia C Sharp
OOP en C Sharp
Diccionarios
Introducción a C#
Todas las lecciones de CSharp
Accede a todas las lecciones de CSharp y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A C#
Introducción Y Entorno
Creación De Proyecto C#
Introducción Y Entorno
Variables Y Constantes
Sintaxis
Tipos De Datos
Sintaxis
Operadores
Sintaxis
Control De Flujo
Sintaxis
Funciones
Sintaxis
Estructuras De Control Iterativo
Sintaxis
Interpolación De Strings
Sintaxis
Estructuras De Control Condicional
Sintaxis
Manejo De Valores Nulos
Sintaxis
Clases Y Encapsulación
Programación Orientada A Objetos
Objetos
Programación Orientada A Objetos
Constructores Y Destructores
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Genéricos
Programación Orientada A Objetos
Métodos Virtuales Y Sobrecarga
Programación Orientada A Objetos
Clases Abstractas
Programación Orientada A Objetos
Interfaces
Programación Orientada A Objetos
Propiedades Y Encapsulación
Programación Orientada A Objetos
Métodos De Extensión
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Clases Parciales
Programación Orientada A Objetos
Miembros Estáticos
Programación Orientada A Objetos
Tuplas Y Tipos Anónimos
Programación Orientada A Objetos
Arrays Y Listas
Colecciones Y Linq
Diccionarios
Colecciones Y Linq
Conjuntos, Colas Y Pilas
Colecciones Y Linq
Uso De Consultas Linq
Colecciones Y Linq
Linq Avanzado
Colecciones Y Linq
Colas Y Pilas
Colecciones Y Linq
Conjuntos
Colecciones Y Linq
Linq Básico
Colecciones Y Linq
Delegados Funcionales
Programación Funcional
Records
Programación Funcional
Expresiones Lambda
Programación Funcional
Linq Funcional
Programación Funcional
Fundamentos De La Programación Funcional
Programación Funcional
Pattern Matching
Programación Funcional
Testing Unitario Con Xunit
Testing
Excepciones
Excepciones
Delegados
Programación Asíncrona
Eventos
Programación Asíncrona
Lambdas
Programación Asíncrona
Uso De Async Y Await
Programación Asíncrona
Tareas
Programación Asíncrona
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender el concepto de polimorfismo y su implementación en C# mediante métodos virtuales y abstractos.
- Aprender a utilizar el polimorfismo para tratar objetos de diferentes clases derivadas a través de una interfaz común.
- Conocer las técnicas de casting seguro en C#, incluyendo el uso de los operadores as y is.
- Aplicar pattern matching para realizar conversiones y comprobaciones de tipo de forma más legible y segura.
- Evaluar buenas prácticas y consideraciones de rendimiento al trabajar con casting y polimorfismo en C#.