Практическое руководство. Безопасное приведение с помощью сопоставления шаблонов, а также операторов is и as

В связи с полиморфизмом объектов переменная типа базового класса может содержать производный тип. Для доступа к членам экземпляра производного типа необходимо привести значение обратно к производному типу. Однако приведение создает риск возникновения исключения InvalidCastException. C# предоставляет операторы сопоставления шаблонов, которые выполняют приведение условно только в случае успеха. C# также предоставляет операторы is и as для проверки определенного типа значения.

В следующем примере показано, как использовать сопоставление шаблонов 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);

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

В предыдущем примере показано несколько возможностей синтаксиса сопоставления шаблонов. Оператор if (a is Mammal m) объединяет проверку с назначением инициализации. Назначение происходит, только если проверка пройдет успешно. Переменная m действует только во внедренном операторе if, где она назначена. Вы не можете получить доступ m позже в том же методе. В предыдущем примере также показано, как использовать оператораas для преобразования объекта в указанный тип.

Вы можете использовать тот же синтаксис для проверки наличия значения у типа значений, допускающего значение NULL, как показано в следующем примере:

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

В предыдущем примере показаны другие возможности сопоставления шаблонов для использования с преобразованиями. Вы можете проверить переменную для шаблона со значением NULL путем проверки конкретно значения null. Если значение переменной в среде выполнения равно null, оператор is, проверяющий тип, всегда возвращает false. Оператор сопоставления шаблонов is не принимает тип, допускающий значение NULL, например int? или Nullable<int>, но вы можете выполнить проверку для любого другого типа значения. is Шаблоны из предыдущего примера не ограничиваются типами значений, допускающих значение NULL. Эти шаблоны можно также использовать для проверки того, имеет ли переменная ссылочного типа значение или она равна null.

Предыдущий пример также показывает, как использовать шаблон типа в операторе switch, где переменная может принимать один из множества различных типов.

Если вы хотите проверить, принадлежит ли переменная определенному типу, но не хотите назначать новую переменную, используйте операторы is и as для ссылочных типов и значений типов, допускающих значение NULL. Ниже показано, как использовать операторы is и as, которые были частью языка C# до того, как было введено сопоставление шаблонов для проверки принадлежности переменной к определенному типу.

// 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 { }

Как видно при сравнении этого кода с кодом сопоставления шаблонов, синтаксис сопоставления шаблонов предоставляет более надежные функции, поскольку сочетает в себе проверку и назначение в одном операторе. Используйте синтаксис сопоставления шаблонов во всех подходящих случаях.