1. Operador is: comprobación de compatibilidad de tipos
En aplicaciones reales, muchas veces tienes una referencia a un objeto de una clase base, pero sabes o sospechas que el objeto real en memoria es de un tipo derivado más específico. En estos casos, puede que necesites acceder a miembros únicos de ese tipo derivado. Para trabajar de forma segura y cómoda con estas situaciones en C#, tienes varias herramientas clave: los operadores is, as y, sobre todo en las versiones modernas del lenguaje, las potentes capacidades de pattern matching.
is te permite comprobar si un objeto es una instancia de un tipo dado, de un tipo derivado o si implementa una interfaz específica. Devuelve true si el objeto es compatible con ese tipo, y false en caso contrario.
Uso principal: determinar si se puede convertir un objeto de forma segura a un tipo concreto.
Ejemplo: Comprobación de tipo base y herencia
class Animal { public string Species { get; set; } = "Desconocido"; }
class Dog : Animal { public void Bark() => Console.WriteLine("¡Guau!"); }
class Cat : Animal { public void Meow() => Console.WriteLine("Miau!"); }
Animal myAnimal = new Dog { Species = "Golden Retriever" };
Console.WriteLine($"myAnimal is Animal: {myAnimal is Animal}"); // True
Console.WriteLine($"myAnimal is Dog: {myAnimal is Dog}"); // True
Console.WriteLine($"myAnimal is Cat: {myAnimal is Cat}"); // False
En este ejemplo, myAnimal en realidad es un Dog. Por eso, myAnimal is Animal y myAnimal is Dog devuelven true.
Ejemplo: Comprobación de null
El operador is también se comporta de forma predecible con null.
Animal nullAnimal = null;
Dog specificDog = new Dog();
Console.WriteLine($"nullAnimal is Animal: {nullAnimal is Animal}"); // False (null no es instancia de ningún tipo)
Console.WriteLine($"specificDog is null: {specificDog is null}"); // False (el objeto no es null)
Fíjate que null no es instancia de ningún tipo, así que null is MyType siempre es false. Sin embargo, someObject is null es una forma cómoda y segura de comprobar si la referencia null.
Ejemplo: Usando is con interfaces
El operador is también sirve para comprobar la implementación de interfaces.
interface IFlyable { void Fly(); }
class Bird : Animal, IFlyable { public void Fly() => Console.WriteLine("¡Aleteo!"); }
class Fish : Animal { }
Animal creature = new Bird();
Console.WriteLine($"creature is Bird: {creature is Bird}"); // True
Console.WriteLine($"creature is IFlyable: {creature is IFlyable}"); // True
creature = new Fish();
Console.WriteLine($"creature is IFlyable: {creature is IFlyable}"); // False
2. Operador as: conversión segura de tipos
El operador as está pensado para convertir un objeto a un tipo concreto de forma segura. A diferencia de la conversión explícita (Type)obj, que lanza InvalidCastException si falla, as devuelve null si la conversión no es posible. Esto lo hace ideal para situaciones en las que no estás seguro del tipo real del objeto.
Uso principal: Intentar convertir el tipo, obteniendo null si falla.
Ejemplo: Uso básico de as
class Shape { }
class Circle : Shape { public double Radius { get; set; } }
class Square : Shape { public double Side { get; set; } }
Shape myShape = new Circle { Radius = 5.0 };
// Intento de conversión a Circle
Circle circle = myShape as Circle;
if (circle != null) // ¡Siempre comprueba null!
{
Console.WriteLine($"Es un círculo con radio: {circle.Radius}"); // Salida: Es un círculo con radio: 5
}
// Intento de conversión a Square
Square square = myShape as Square;
if (square == null) // Conversión fallida, square == null
{
Console.WriteLine("No es un cuadrado."); // Salida: No es un cuadrado.
}
Como ves, as te permite evitar errores en tiempo de ejecución, devolviendo null en vez de lanzar una excepción.
Ejemplo: Limitaciones de as
Ojo, el operador as solo funciona con tipos de referencia y tipos valor nullable. No se puede usar con tipos valor normales, porque no pueden ser null.
object someValue = 100;
int num = someValue as int; // ERROR DE COMPILACIÓN: 'as' no se puede usar con tipos no-null
int? nullableNum = someValue as int?; // Ok, int nullable
Console.WriteLine($"Int nullable: {nullableNum}"); // Salida: Int nullable: 100
string str = someValue as string; // Devuelve null, porque 100 no es string
Console.WriteLine($"String desde int: {str ?? "null"}"); // Salida: String desde int: null
Para tipos valor no-nullable, usa conversión explícita ((int)someValue) si estás seguro del tipo (y aceptas la excepción), o mejor aún, pattern matching.
3. Pattern Matching (Sopado de patrones)
El pattern matching es una función potente y en constante evolución que te da formas más elegantes y seguras de comprobar tipos y extraer datos de objetos. Reduce mucho el código repetitivo y mejora la legibilidad.
Type Pattern (Patrón de tipo) con is
Esta es la forma más común de pattern matching, que te permite comprobar el tipo de un objeto y, si la comprobación es exitosa, asignarlo directamente a una nueva variable de ese tipo.
Sintaxis: expresión is Tipo variable
Ejemplo: Sustituyendo is + conversión
// Seguimos con las clases Shape, Circle, Square
Shape currentShape = new Circle { Radius = 7.5 };
// Forma antigua, más larga:
if (currentShape is Circle)
{
Circle c = (Circle)currentShape;
Console.WriteLine($"Forma antigua: Círculo con radio {c.Radius}");
}
// Forma moderna y elegante con type pattern
if (currentShape is Circle c) // Comprobación de tipo y creación de variable 'c'
{
Console.WriteLine($"Forma nueva: Círculo con radio {c.Radius}"); // 'c' ya es de tipo Circle
}
Shape anotherShape = new Square { Side = 10.0 };
if (anotherShape is Square s)
{
Console.WriteLine($"Es un cuadrado con lado {s.Side}");
}
La variable c (o s) solo existe dentro del bloque if, así evitas usarla fuera del contexto correcto.
Property Pattern (Patrón de propiedad)
Desde C# 8.0, puedes no solo comprobar el tipo, sino también comprobar valores de una o varias propiedades del objeto y extraerlas en nuevas variables.
Sintaxis: expresión is Tipo { Propiedad1: patrón_valor, Propiedad2: variable_extracción }
Ejemplo: Comprobando una propiedad
Shape testShape = new Circle { Color = "Verde", Radius = 12.0 };
// Comprobamos si el objeto es un círculo y su color es verde
if (testShape is Circle { Color: "Verde" })
{
Console.WriteLine("Círculo verde encontrado.");
}
// Comprobamos si el objeto es un círculo y extraemos su radio
if (testShape is Circle { Radius: var r })
{
Console.WriteLine($"Radio extraído: {r}");
}
// Combinamos comprobación y extracción:
if (testShape is Circle { Color: "Verde", Radius: var radiusVal } circleObj)
{
Console.WriteLine($"Círculo verde extraído. Radio: {radiusVal}, Objeto: {circleObj.Radius}");
}
El patrón de propiedad simplifica mucho las condiciones complejas.
Ejemplo: Rangos y operadores lógicos en Property Patterns
Ahora puedes comprobar que las propiedades del objeto cumplen ciertas condiciones:
Circle bigCircle = new Circle { Radius = 25.0, Color = "Azul" };
// Comprobamos radio mayor que 20 Y color azul
if (bigCircle is Circle { Radius: > 20, Color: "Azul" })
{
Console.WriteLine("Círculo azul grande detectado.");
}
// Comprobamos radio en rango (5..15)
if (testShape is Circle { Radius: >= 5 and <= 15 })
{
Console.WriteLine("Círculo de tamaño medio.");
}
¡Ojo! A diferencia del tipo bool, aquí se usan las palabras clave: and, or y not.
4. Switch Expressions y Switch Statements con Pattern Matching
El pattern matching muestra su mayor flexibilidad y legibilidad en los operadores switch. Te permiten ejecutar lógica diferente según el patrón que coincida.
Ejemplo: Switch Statement
Permite definir bloques de código para cada patrón que coincida.
// Clases Animal, Dog, Cat como al principio de la lección
Animal currentCreature = new Dog { Species = "Poodle" };
switch (currentCreature)
{
case Dog d: // Patrón de tipo: si es Dog, asigna a 'd'
d.Bark();
Console.WriteLine($"Es un perro de raza {d.Species}.");
break;
case Cat c when c.Species == "Siamese": // Patrón de tipo con condición (when clause)
c.Meow();
Console.WriteLine($"Es un gato siamés.");
break;
case Animal a: // Si es solo Animal (y no Dog/Cat)
Console.WriteLine($"Es solo un animal de especie {a.Species}.");
break;
case null: // Patrón null para manejar valores null
Console.WriteLine("El objeto es null.");
break;
default: // Si ningún patrón anterior coincide
Console.WriteLine("Criatura desconocida.");
break;
}
El orden de los casos en switch importa: los patrones más específicos deben ir antes que los más generales.
Ejemplo: Switch Expression
Es una sintaxis más compacta para switch, que devuelve un valor. Ideal para transformar un objeto en otro valor según su tipo o propiedades.
Sintaxis: expresión switch { patrón1 => resultado1, patrón2 => resultado2, ... _ => resultado_por_defecto }
Shape processShape = new Rectangle { Width = 5, Height = 5, Color = "Rojo" };
string shapeInfo = processShape switch
{
Circle { Radius: var r } when r > 10 => $"Círculo grande (R={r})", // Patrón de propiedad con condición
Circle { Color: "Azul" } c => $"Círculo azul (R={c.Radius})", // Patrón de propiedad con extracción
Circle c => $"Círculo normal (R={c.Radius})", // Patrón de tipo simple
Rectangle { Width: var w, Height: var h } when w == h => $"Cuadrado ({w}x{h})", // Cuadrado
Rectangle r => $"Rectángulo ({r.Width}x{r.Height})", // Cualquier otro rectángulo
null => "La figura no existe (null)", // Patrón null
_ => "Figura desconocida" // Patrón discard (_) en vez de 'default'
};
Console.WriteLine(shapeInfo); // Salida: Cuadrado (5x5)
switch expression es súper útil para transformaciones cortas y expresivas.
Var Pattern (Patrón var)
Desde C# 7.0, puedes usar var en pattern matching. var siempre coincide con cualquier objeto (excepto null) y lo extrae en una variable de su tipo real.
Ejemplo: Usando el patrón var
object obj = "Hola Mundo";
if (obj is var result) // Siempre true, result será string
{
Console.WriteLine($"Tipo: {result.GetType().Name}, Valor: {result}");
}
obj = 123;
string typeName = obj switch
{
var x when x is int => "Es un número entero",
var y when y is string => "Es una cadena",
_ => "Otra cosa"
};
Console.WriteLine(typeName); // Salida: Es un número entero
El patrón var rara vez se usa solo para comprobar el tipo, pero es muy útil dentro de otros patrones o switch para extraer el valor.
5. is vs as vs Pattern Matching: ¿cuándo usar cada uno?
La elección depende de tu caso concreto y la versión de C# que uses.
Operador is (simple):
Cuándo usar: Cuando solo necesitas comprobar si un objeto es de un tipo concreto y no necesitas convertirlo ni acceder a miembros específicos.
if (myObject is SomeType)
Operador as:
Cuándo usar: Cuando intentas convertir un tipo de referencia (o tipo valor nullable) y quieres manejar el fallo sin excepción, usando comprobación de null.
MyDerivedClass derived = myBaseObject as MyDerivedClass;
if (derived != null) { /* ... */ }
Pattern Matching (is Type variable):
Enfoque moderno preferido: Combina comprobación de tipo y conversión segura en una sola línea, más legible. Funciona para todos los tipos.
Cuándo usar: Cuando necesitas comprobar el tipo y usar directamente miembros específicos de ese tipo.
if (myObject is MyDerivedClass derived) { derived.SpecificMethod(); }
Pattern Matching (switch expressions / statements):
Enfoque moderno preferido: Cuando necesitas hacer acciones diferentes o devolver valores distintos según el tipo del objeto, sus propiedades u otras características. Es mucho más limpio que largas cadenas de if-else if.
Cuándo usar: Implementar comportamiento polimórfico o lógica de selección compleja basada en las características del objeto.
string GetShapeInfo(Shape s) => s switch
{
Circle c => $"Círculo R={c.Radius}",
Rectangle r => $"Rectángulo W={r.Width} H={r.Height}",
_ => "Desconocido"
};
GO TO FULL VERSION