Condividi tramite


Come eseguire il cast in modo sicuro usando il pattern matching e gli operatori is e as

Poiché gli oggetti sono polimorfici, è possibile che una variabile di un tipo di classe base contenga un tipo derivato. Per accedere ai membri dell'istanza del tipo derivato, è necessario effettuare il cast del valore al tipo derivato. Tuttavia, un cast crea il rischio di generare un oggetto InvalidCastException. C# fornisce istruzioni di corrispondenza di pattern che eseguono un cast in modo condizionale solo se avrà successo. C# fornisce anche gli operatori is e as per verificare se un valore è di un determinato tipo.

L'esempio seguente illustra come usare l'istruzione pattern matching is :

var g = new Giraffe();
var a = new Animal();
FeedMammals(g);
FeedMammals(a);
// Output:
// Eating.
// Animal is not a Mammal

SuperNova sn = new SuperNova();
TestForMammals(g);
TestForMammals(sn);
// Output:
// I am an animal.
// SuperNova is not a Mammal

static void FeedMammals(Animal a)
{
    if (a is Mammal m)
    {
        m.Eat();
    }
    else
    {
        // variable 'm' is not in scope here, and can't be used.
        Console.WriteLine($"{a.GetType().Name} is not a Mammal");
    }
}

static void TestForMammals(object o)
{
    // You also can use the as operator and test for null
    // before referencing the variable.
    var m = o as Mammal;
    if (m != null)
    {
        Console.WriteLine(m.ToString());
    }
    else
    {
        Console.WriteLine($"{o.GetType().Name} is not a Mammal");
    }
}

class Animal
{
    public void Eat() { Console.WriteLine("Eating."); }
    public override string ToString()
    {
        return "I am an animal.";
    }
}
class Mammal : Animal { }
class Giraffe : Mammal { }

class SuperNova { }

Nell'esempio precedente vengono illustrate alcune funzionalità della sintassi di corrispondenza di modelli. L'istruzione if (a is Mammal m) combina il test con un'assegnazione di inizializzazione. L'assegnazione viene eseguita solo quando il test ha esito positivo. La variabile m è visibile solo nell'istruzione incorporata if dove viene assegnata. Non è possibile accedere m in un secondo momento nello stesso metodo. Nell'esempio precedente viene inoltre illustrato come utilizzare l'operatoreas per convertire un oggetto in un tipo specificato.

È anche possibile usare la stessa sintassi per verificare se un tipo di valore nullable ha un valore, come illustrato nell'esempio seguente:

int i = 5;
PatternMatchingNullable(i);

int? j = null;
PatternMatchingNullable(j);

double d = 9.78654;
PatternMatchingNullable(d);

PatternMatchingSwitch(i);
PatternMatchingSwitch(j);
PatternMatchingSwitch(d);

static void PatternMatchingNullable(ValueType? val)
{
    if (val is int j) // Nullable types are not allowed in patterns
    {
        Console.WriteLine(j);
    }
    else if (val is null) // If val is a nullable type with no value, this expression is true
    {
        Console.WriteLine("val is a nullable type with the null value");
    }
    else
    {
        Console.WriteLine("Could not convert " + val.ToString());
    }
}

static void PatternMatchingSwitch(ValueType? val)
{
    switch (val)
    {
        case int number:
            Console.WriteLine(number);
            break;
        case long number:
            Console.WriteLine(number);
            break;
        case decimal number:
            Console.WriteLine(number);
            break;
        case float number:
            Console.WriteLine(number);
            break;
        case double number:
            Console.WriteLine(number);
            break;
        case null:
            Console.WriteLine("val is a nullable type with the null value");
            break;
        default:
            Console.WriteLine("Could not convert " + val.ToString());
            break;
    }
}

L'esempio precedente illustra altre caratteristiche del confronto dei modelli da usare con le conversioni. È possibile testare una variabile per il pattern Null controllando in modo specifico il valore null. Quando il valore di runtime della variabile è null, il controllo dell'istruzione di un tipo restituisce sempre is. L'istruzione pattern matching is non consente un tipo di valore nullable, ad esempio int? o Nullable<int>, ma è possibile eseguire il test per qualsiasi altro tipo di valore. I modelli is dell'esempio precedente non sono limitati ai tipi valore nullable. È anche possibile usare questi modelli per verificare se una variabile di un tipo riferimento ha un valore o è null.

Nell'esempio precedente viene inoltre illustrato come usare il modello di tipo in un'istruzione switch in cui la variabile può essere uno di molti tipi diversi.

Se si desidera testare se una variabile è di un tipo specificato, ma non assegnarla a una nuova variabile, è possibile usare gli operatori is e as per i tipi riferimento e i tipi valore nullable. Il codice seguente illustra come usare le istruzioni is e as che facevano parte del linguaggio C# prima dell'introduzione del pattern matching per verificare se una variabile è di un determinato tipo:

// Use the is operator to verify the type.
// before performing a cast.
Giraffe g = new();
UseIsOperator(g);

// Use the as operator and test for null
// before referencing the variable.
UseAsOperator(g);

// Use pattern matching to test for null
// before referencing the variable
UsePatternMatchingIs(g);

// Use the as operator to test
// an incompatible type.
SuperNova sn = new();
UseAsOperator(sn);

// Use the as operator with a value type.
// Note the implicit conversion to int? in
// the method body.
int i = 5;
UseAsWithNullable(i);

double d = 9.78654;
UseAsWithNullable(d);

static void UseIsOperator(Animal a)
{
    if (a is Mammal)
    {
        Mammal m = (Mammal)a;
        m.Eat();
    }
}

static void UsePatternMatchingIs(Animal a)
{
    if (a is Mammal m)
    {
        m.Eat();
    }
}

static void UseAsOperator(object o)
{
    Mammal? m = o as Mammal;
    if (m is not null)
    {
        Console.WriteLine(m.ToString());
    }
    else
    {
        Console.WriteLine($"{o.GetType().Name} is not a Mammal");
    }
}

static void UseAsWithNullable(System.ValueType val)
{
    int? j = val as int?;
    if (j is not null)
    {
        Console.WriteLine(j);
    }
    else
    {
        Console.WriteLine("Could not convert " + val.ToString());
    }
}
class Animal
{
    public void Eat() => Console.WriteLine("Eating.");
    public override string ToString() => "I am an animal.";
}
class Mammal : Animal { }
class Giraffe : Mammal { }

class SuperNova { }

Come si può notare confrontando questo codice con il codice di corrispondenza dei pattern, la sintassi del pattern matching offre funzionalità più avanzate combinando il controllo e l'assegnazione in una singola istruzione. Usare la sintassi di pattern matching quando possibile.