Upcasting y Downcasting
Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy — lección 29 de 70.
Al trabajar con jerarquías de herencia, a menudo es necesario realizar conversiones entre tipos base y derivados. Upcasting convierte un tipo derivado a su tipo base, mientras que downcasting hace lo contrario.
El upcasting es implícito y siempre seguro porque se garantiza que una clase derivada tiene todo lo que la clase base tiene:
Dog myDog = new Dog();
Animal animal = myDog; // Upcasting - implícito, siempre funcionaEl downcasting requiere una conversión explícita y puede fallar en tiempo de ejecución si el objeto no es realmente del tipo al que se está realizando la conversión:
Animal animal = new Dog();
Dog dog = (Dog)animal; // Downcasting - funciona porque realmente es un Dog
Animal cat = new Cat();
Dog wrong = (Dog)cat; // ¡Lanza InvalidCastException!Para realizar una conversión descendente de forma segura, use la palabra clave is para comprobar el tipo primero, o use as que devuelve null en lugar de lanzar una excepción:
if (animal is Dog d)
{
d.Bark(); // Seguro - d solo está disponible si la comprobación tiene éxito
}
Dog maybeDog = animal as Dog;
if (maybeDog != null)
{
maybeDog.Bark();
}El enfoque de coincidencia de patrones is es preferido en el C# moderno porque combina la comprobación de tipo y la conversión en un solo paso, haciendo que su código sea más seguro y limpio.
Desafío
FácilVamos a construir un sistema de inspección de vehículos que demuestre cómo el upcasting y el downcasting te permiten trabajar de forma flexible con objetos en una jerarquía de herencia. Crearás diferentes tipos de vehículos y escribirás código que identifique e interactúe de forma segura con tipos de vehículos específicos.
Organizarás tu código en cuatro archivos:
Vehicle.cs: Define una clase baseVehicleen el espacio de nombresInspection. Cada vehículo tiene una propiedadLicensePlate(string) establecida a través de un constructor. Incluye un método llamadoGetBasicInfo()que devuelva"Vehicle: {LicensePlate}".Car.cs: Define una claseCaren el espacio de nombresInspectionque herede deVehicle. Los coches tienen una propiedad adicionalNumberOfDoors(int). El constructor acepta tanto la matrícula como el número de puertas. Añade un método llamadoHonk()que devuelva"{LicensePlate} honks: Beep beep!".Motorcycle.cs: Define una claseMotorcycleen el espacio de nombresInspectionque herede deVehicle. Las motocicletas tienen una propiedadHasSidecar(bool). El constructor acepta la matrícula y el estado del sidecar. Añade un método llamadoRev()que devuelva"{LicensePlate} revs: Vroom!".Program.cs: En tu archivo principal, crearás vehículos y practicarás tanto el upcasting como el downcasting seguro. Crea unCary unaMotorcycleutilizando valores de entrada, luego almacena ambos en un array de referencias deVehicle(upcasting). Recorre el array y, para cada vehículo, imprime su información básica. Luego usa la palabra claveiscon coincidencia de patrones (pattern matching) para realizar un downcast seguro: si es unCar, llama aHonk(); si es unaMotorcycle, llama aRev().
Recibirás cuatro entradas:
- Matrícula del coche
- Número de puertas
- Matrícula de la motocicleta
- Tiene sidecar (
trueofalse)
Imprime la salida en este formato para cada vehículo del array:
{GetBasicInfo() result}
{Honk() or Rev() result based on actual type}Por ejemplo, si las entradas son ABC-123, 4, XYZ-789, y false, la salida debería ser:
Vehicle: ABC-123
ABC-123 honks: Beep beep!
Vehicle: XYZ-789
XYZ-789 revs: Vroom!¡Observa cómo todos los vehículos se almacenan como referencias de Vehicle (upcasting), pero puedes descubrir de forma segura sus tipos reales y acceder a métodos específicos del tipo utilizando la sintaxis de coincidencia de patrones is para el downcasting!
Hoja de referencia
Al trabajar con jerarquías de herencia, el upcasting convierte un tipo derivado en su tipo base, mientras que el downcasting convierte un tipo base en un tipo derivado.
El upcasting es implícito y siempre seguro:
Dog myDog = new Dog();
Animal animal = myDog; // Upcasting - implícito, siempre funcionaEl downcasting requiere una conversión explícita y puede fallar en tiempo de ejecución:
Animal animal = new Dog();
Dog dog = (Dog)animal; // Downcasting - funciona porque realmente es un Dog
Animal cat = new Cat();
Dog wrong = (Dog)cat; // ¡Lanza InvalidCastException!Downcasting seguro utilizando la palabra clave is con coincidencia de patrones (preferido):
if (animal is Dog d)
{
d.Bark(); // Seguro - d solo está disponible si la comprobación tiene éxito
}Downcasting seguro utilizando la palabra clave as (devuelve null si la conversión falla):
Dog maybeDog = animal as Dog;
if (maybeDog != null)
{
maybeDog.Bark();
}Pruébalo tú mismo
using System;
using Inspection;
class Program
{
public static void Main(string[] args)
{
// Leer las entradas
string carPlate = Console.ReadLine();
int numberOfDoors = Convert.ToInt32(Console.ReadLine());
string motorcyclePlate = Console.ReadLine();
bool hasSidecar = Convert.ToBoolean(Console.ReadLine());
// TODO: Crear un objeto Car usando carPlate y numberOfDoors
// TODO: Crear un objeto Motorcycle usando motorcyclePlate y hasSidecar
// TODO: Crear un arreglo de referencias de Vehicle que contenga ambos vehículos (upcasting)
// TODO: Recorrer el arreglo y para cada vehículo:
// - Imprimir el resultado de GetBasicInfo()
// - Usar la palabra clave 'is' con pattern matching para realizar un downcast de forma segura:
// - Si es un Car, llamar e imprimir Honk()
// - Si es un Motorcycle, llamar e imprimir Rev()
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantes