CodeGym /Corsi /C# SELF /Lavorare con i tipi: is

Lavorare con i tipi: is, as e Pattern Matching

C# SELF
Livello 34 , Lezione 4
Disponibile

1. Operatore is: controllo della compatibilità dei tipi

Nelle app reali capita spesso di avere un riferimento a un oggetto della classe base, ma sai o sospetti che in memoria ci sia in realtà un tipo derivato più specifico. In questi casi potresti voler accedere ai membri unici di quel tipo derivato. Per lavorare in modo sicuro e comodo con queste situazioni, in C# ci sono alcuni strumenti chiave: gli operatori is, as e, soprattutto nelle versioni moderne del linguaggio, le potenti feature di pattern matching.

is ti permette di controllare se un oggetto è un'istanza di un certo tipo, di un suo derivato o se implementa una certa interfaccia. Restituisce true se l'oggetto è compatibile con quel tipo, e false altrimenti.

Uso principale: capire se puoi convertire in sicurezza un oggetto a un certo tipo.

Esempio: Controllo del tipo base e dell'ereditarietà


class Animal { public string Species { get; set; } = "Sconosciuto"; }
class Dog : Animal { public void Bark() => Console.WriteLine("Bau!"); }
class Cat : Animal { public void Meow() => Console.WriteLine("Miao!"); }

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

In questo esempio myAnimal in realtà è un Dog. Quindi myAnimal is Animal e myAnimal is Dog restituiranno true.

Esempio: Controllo su null

L'operatore is si comporta in modo prevedibile anche con null.


Animal nullAnimal = null;
Dog specificDog = new Dog();

Console.WriteLine($"nullAnimal is Animal: {nullAnimal is Animal}"); // False (null non è istanza di nessun tipo)
Console.WriteLine($"specificDog is null: {specificDog is null}");   // False (l'oggetto non è null)

Nota che null non è istanza di nessun tipo, quindi null is MyType è sempre false. Però someObject is null è un modo comodo e sicuro per controllare se il riferimento null.

Esempio: Uso di is con le interfacce

L'operatore is funziona anche per controllare se un oggetto implementa un'interfaccia.


interface IFlyable { void Fly(); }
class Bird : Animal, IFlyable { public void Fly() => Console.WriteLine("Sbatti sbatti!"); }
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. Operatore as: cast sicuro dei tipi

L'operatore as serve per convertire in modo sicuro un oggetto a un certo tipo. Diversamente dal cast esplicito (Type)obj, che lancia InvalidCastException se fallisce, as restituisce null se la conversione non è possibile. È perfetto quando non sei sicuro del tipo reale dell'oggetto.

Uso principale: Provare a convertire il tipo, ottenendo null se non va.

Esempio: Uso base di 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 };

// Prova a convertire in Circle
Circle circle = myShape as Circle;
if (circle != null) // Controllo su null obbligatorio!
{
    Console.WriteLine($"È un cerchio con raggio: {circle.Radius}"); // Output: È un cerchio con raggio: 5
}

// Prova a convertire in Square
Square square = myShape as Square;
if (square == null) // Conversione fallita, square == null
{
    Console.WriteLine("Non è un quadrato."); // Output: Non è un quadrato.
}

Come vedi, as ti evita errori a runtime, restituendo null invece di un'eccezione.

Esempio: Limiti di as

Ricorda che l'operatore as funziona solo con tipi reference e tipi value nullable. Non puoi usarlo con tipi value normali, perché non possono essere null.


object someValue = 100;

int num = someValue as int; // ERRORE DI COMPILAZIONE: 'as' non si usa con tipi non-nullable
int? nullableNum = someValue as int?; // Ok, int nullable
Console.WriteLine($"Int nullable: {nullableNum}"); // Output: Int nullable: 100

string str = someValue as string; // Restituisce null, perché 100 non è una stringa
Console.WriteLine($"String da int: {str ?? "null"}"); // Output: String da int: null

Per tipi value non-nullable usa il cast esplicito ((int)someValue) se sei sicuro del tipo (e sei pronto a gestire l'eccezione), oppure meglio ancora il pattern matching.

3. Pattern Matching

Il pattern matching è una feature potente e in continua evoluzione che ti permette di controllare tipi e estrarre dati dagli oggetti in modo elegante e sicuro. Riduce un sacco di codice boilerplate e migliora la leggibilità.

Type Pattern con is

È la forma più usata di pattern matching: controlli il tipo di un oggetto e, se va bene, lo assegni subito a una nuova variabile di quel tipo.

Sintassi: espressione is Tipo variabile

Esempio: Sostituzione di is + cast


// Usiamo ancora le classi Shape, Circle, Square

Shape currentShape = new Circle { Radius = 7.5 };

// Vecchio modo, prolisso:
if (currentShape is Circle)
{
    Circle c = (Circle)currentShape;
    Console.WriteLine($"Vecchio modo: Cerchio con raggio {c.Radius}");
}

// Nuovo modo elegante con type pattern
if (currentShape is Circle c) // Controllo tipo e creazione variabile 'c'
{
    Console.WriteLine($"Nuovo modo: Cerchio con raggio {c.Radius}"); // 'c' è già di tipo Circle
}

Shape anotherShape = new Square { Side = 10.0 };
if (anotherShape is Square s)
{
    Console.WriteLine($"È un quadrato con lato {s.Side}");
}

La variabile c (o s) è visibile solo nel blocco if, così non rischi di usarla fuori dal contesto giusto.

Property Pattern

Da C# 8.0 puoi non solo controllare il tipo, ma anche verificare i valori di una o più proprietà dell'oggetto e estrarle in nuove variabili.

Sintassi: espressione is Tipo { Proprietà1: pattern_valore, Proprietà2: variabile_estratta }

Esempio: Controllo di una proprietà


Shape testShape = new Circle { Color = "Verde", Radius = 12.0 };

// Controlla se è un cerchio e se il colore è verde
if (testShape is  Circle { Color: "Verde" })
{
    Console.WriteLine("Trovato un cerchio verde.");
}

// Controlla se è un cerchio ed estrae il raggio
if (testShape is  Circle { Radius: var r })
{
    Console.WriteLine($"Raggio estratto: {r}");
}

// Combina controllo e estrazione:
if (testShape is  Circle { Color: "Verde", Radius: var radiusVal } circleObj)
{
    Console.WriteLine($"Estratto cerchio verde. Raggio: {radiusVal}, Oggetto: {circleObj.Radius}");
}

Il property pattern semplifica un sacco le condizioni complesse.

Esempio: Range e operatori logici nei Property Patterns

Ora puoi controllare che le proprietà dell'oggetto rispettino certe condizioni:


Circle bigCircle = new Circle { Radius = 25.0, Color = "Blu" };

// Controlla raggio > 20 E colore blu
if (bigCircle is Circle { Radius: > 20, Color: "Blu" })
{
    Console.WriteLine("Trovato un grande cerchio blu.");
}

// Controlla raggio nel range (5..15)
if (testShape is Circle { Radius: >= 5 and <= 15 })
{
    Console.WriteLine("Cerchio di dimensione media.");
}

Attenzione! Diversamente dal tipo bool, qui si usano le parole chiave: and, or e not.

4. Switch Expressions e Switch Statements con Pattern Matching

Il pattern matching dà il meglio di sé negli switch: puoi eseguire logiche diverse a seconda del pattern che corrisponde.

Esempio: Switch Statement

Ti permette di definire blocchi di codice per ogni pattern che corrisponde.


// Classi Animal, Dog, Cat come all'inizio della lezione
Animal currentCreature = new Dog { Species = "Barboncino" };

switch (currentCreature)
{
    case Dog d: // Pattern tipo: se è Dog, assegna a 'd'
        d.Bark();
        Console.WriteLine($"È un cane di razza {d.Species}.");
        break;
    case Cat c when c.Species == "Siamese": // Pattern tipo con condizione (when)
        c.Meow();
        Console.WriteLine($"È un gatto siamese.");
        break;
    case Animal a: // Se è solo Animal (non Dog/Cat)
        Console.WriteLine($"È solo un animale di specie {a.Species}.");
        break;
    case null: // Pattern null per gestire i null
        Console.WriteLine("L'oggetto è null.");
        break;
    default: // Se nessuno dei pattern sopra corrisponde
        Console.WriteLine("Creatura sconosciuta.");
        break;
}

L'ordine dei case nello switch conta: i pattern più specifici vanno prima di quelli più generici.

Esempio: Switch Expression

Sintassi più compatta per switch, che restituisce un valore. Perfetta per trasformare un oggetto in un altro valore in base al tipo o alle proprietà.

Sintassi: espressione switch { pattern1 => risultato1, pattern2 => risultato2, ... _ => risultato_default }


Shape processShape = new Rectangle { Width = 5, Height = 5, Color = "Rosso" };

string shapeInfo = processShape switch
{
    Circle { Radius: var r } when r > 10 => $"Grande cerchio (R={r})", // Pattern proprietà con condizione
    Circle { Color: "Blu" } c =>  $"Cerchio blu (R={c.Radius})",   // Pattern proprietà con estrazione
    Circle c =>  $"Cerchio normale (R={c.Radius})",                     // Pattern tipo semplice
    Rectangle { Width: var w, Height: var h } when w == h =>  $"Quadrato ({w}x{h})", // Quadrato
    Rectangle r =>  $"Rettangolo ({r.Width}x{r.Height})",         // Altri rettangoli
    null =>  "Figura non esistente (null)",                           // Pattern null
    _ =>  "Figura sconosciuta"                                     // Pattern discard (_), invece di 'default'
};

Console.WriteLine(shapeInfo); // Output: Quadrato (5x5)

switch expression è super comodo per trasformazioni brevi e chiare.

Var Pattern

Da C# 7.0 puoi usare var nel pattern matching. var corrisponde sempre a qualsiasi oggetto (tranne null) e lo estrae in una variabile del tipo giusto.

Esempio: Uso del var Pattern


object obj = "Hello World";

if (obj is var  result) // Sempre true, result sarà string
{
    Console.WriteLine($"Tipo: {result.GetType().Name}, Valore: {result}");
}

obj = 123;
string typeName = obj switch
{
    var x when x is int => "È un intero",
    var y when y is string =>  "È una stringa",
    _ => "Altro"
};
Console.WriteLine(typeName); // Output: È un intero

Il var pattern si usa raramente da solo per controllare il tipo, ma è utilissimo dentro altri pattern o negli switch per estrarre il valore.

5. is vs as vs Pattern Matching: quando usare cosa?

La scelta dello strumento dipende dal tuo caso specifico e dalla versione di C# che usi.

Operatore is (semplice):

Quando usarlo: Se ti serve solo controllare se un oggetto è di un certo tipo e non devi subito fare cast o accedere a membri specifici.


    if (myObject is SomeType)

Operatore as:

Quando usarlo: Se vuoi convertire un tipo reference (o value nullable) e vuoi gestire il fallimento senza eccezioni, usando il controllo su null.


    MyDerivedClass derived = myBaseObject as MyDerivedClass;
if (derived != null) { /* ... */ }

Pattern Matching (is Type variable):

Approccio moderno preferito: Combina controllo tipo e cast sicuro in una sola riga più leggibile. Funziona per tutti i tipi.

Quando usarlo: Se vuoi controllare il tipo e subito usare membri specifici di quel tipo.


    if (myObject is MyDerivedClass derived) { derived.SpecificMethod(); }

Pattern Matching (switch expressions / statements):

Approccio moderno preferito: Quando vuoi fare azioni diverse o ottenere valori diversi a seconda del tipo dell'oggetto, delle sue proprietà o di altre caratteristiche. Molto più pulito delle lunghe catene if-else if.

Quando usarlo: Per implementare comportamento polimorfico o logiche di scelta complesse basate sulle caratteristiche dell'oggetto.


string GetShapeInfo(Shape s) => s switch
{
    Circle c => $"Cerchio R={c.Radius}",
    Rectangle r => $"Rettangolo W={r.Width} H={r.Height}",
    _ => "Sconosciuto"
};
2
Compito
C# SELF, livello 34, lezione 4
Bloccato
Verifica del tipo di oggetto usando l'operatore `is`
Verifica del tipo di oggetto usando l'operatore `is`
1
Sondaggio/quiz
Esecuzione differita, livello 34, lezione 4
Non disponibile
Esecuzione differita
Ottimizzazione del lavoro con le collezioni
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION