Como moldar com segurança usando a correspondência de padrões e os operadores is e como operadores

Como os objetos são polimórficos, é possível que uma variável de um tipo de classe base contenha um tipo derivado. Para acessar os membros da instância do tipo derivado, é necessário converter o valor de volta para o tipo derivado. No entanto, um elenco cria o risco de lançar um InvalidCastException. O C# fornece instruções de correspondência de padrões que executam uma conversão condicionalmente somente quando ela for bem-sucedida. O C# também fornece os operadores is e as para testar se um valor é de um determinado tipo.

O exemplo a seguir mostra como usar a instrução de correspondência de is padrão:

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);

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");
    }
}
// Output:
// I am an animal.
// SuperNova 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 { }

O exemplo anterior demonstra alguns recursos da sintaxe de correspondência de padrões. A if (a is Mammal m) instrução combina o teste com uma atribuição de inicialização. A atribuição ocorre somente quando o teste é bem-sucedido. A variável m só está no escopo na instrução incorporada if onde foi atribuída. Não é possível acessar m posteriormente no mesmo método. O exemplo anterior também mostra como usar o as operador para converter um objeto em um tipo especificado.

Você também pode usar a mesma sintaxe para testar se um tipo de valor anulável tem um valor, conforme mostrado no exemplo a seguir:

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;
    }
}

O exemplo anterior demonstra outros recursos de correspondência de padrões para usar com conversões. Você pode testar uma variável para o padrão nulo verificando especificamente o null valor. Quando o valor de tempo de execução da variável é null, uma is instrução verificando um tipo sempre retorna false. A instrução de correspondência is de padrão não permite um tipo de valor anulável, como int? ou Nullable<int>, mas você pode testar qualquer outro tipo de valor. Os is padrões do exemplo anterior não estão limitados aos tipos de valor anulável. Você também pode usar esses padrões para testar se uma variável de um tipo de referência tem um valor ou é null.

O exemplo anterior também mostra como você usa o padrão de tipo em uma switch instrução onde a variável pode ser um dos muitos tipos diferentes.

Se você quiser testar se uma variável é um determinado tipo, mas não atribuí-la a uma nova variável, você pode usar os is operadores e as para tipos de referência e tipos de valor anulável. O código a seguir mostra como usar as is instruções e as que faziam parte da linguagem C# antes da correspondência de padrões ser introduzida para testar se uma variável é de um determinado 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 { }

Como você pode ver comparando esse código com o código de correspondência de padrão, a sintaxe de correspondência de padrão fornece recursos mais robustos combinando o teste e a atribuição em uma única instrução. Use a sintaxe de correspondência de padrão sempre que possível.