CodeGym /Cursos /C# SELF /Trabajando con tipos: is

Trabajando con tipos: is, as y Pattern Matching

C# SELF
Nivel 34 , Lección 4
Disponible

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"
};
2
Tarea
C# SELF, nivel 34, lección 4
Bloqueada
Comprobación del tipo de objeto usando el operador `is`
Comprobación del tipo de objeto usando el operador `is`
1
Cuestionario/control
Ejecución diferida, nivel 34, lección 4
No disponible
Ejecución diferida
Optimización del trabajo con colecciones
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION