Jak bezpiecznie rzutować przy użyciu dopasowywania wzorca i właściwości i jako operatorów

Ponieważ obiekty są polimorficzne, zmienna typu klasy bazowej może przechowywać typ pochodny. Aby uzyskać dostęp do składowych wystąpienia typu pochodnego, należy rzutować wartość z powrotem do typu pochodnego. Jednak rzutowanie tworzy ryzyko rzutowania obiektu InvalidCastException. Język C# udostępnia instrukcje dopasowywania wzorców, które wykonują rzutowanie warunkowo tylko wtedy, gdy powiedzie się. Język C# udostępnia również operatory ijako operatory do testowania, czy wartość jest określonego typu.

W poniższym przykładzie pokazano, jak używać instrukcji dopasowywania is wzorca:

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

W poprzednim przykładzie pokazano kilka funkcji składni dopasowania wzorca. Instrukcja if (a is Mammal m) łączy test z przypisaniem inicjowania. Przypisanie odbywa się tylko wtedy, gdy test zakończy się pomyślnie. Zmienna m znajduje się tylko w zakresie w instrukcji osadzonej if , do której została przypisana. Nie można uzyskać dostępu m później w tej samej metodzie. W poprzednim przykładzie pokazano również, jak używać as operatora do konwertowania obiektu na określony typ.

Można również użyć tej samej składni do testowania, jeśli typ wartości dopuszczanej do wartości null ma wartość, jak pokazano w poniższym przykładzie:

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

W poprzednim przykładzie przedstawiono inne funkcje dopasowywania wzorców do użycia z konwersjami. Możesz przetestować zmienną dla wzorca wartości null, sprawdzając specjalnie pod kątem null wartości. Gdy wartość środowiska uruchomieniowego zmiennej to null, instrukcja sprawdzająca is typ zawsze zwraca wartość false. Instrukcja dopasowywania is wzorca nie zezwala na typ wartości dopuszczanej do wartości null, taki jak int? lub Nullable<int>, ale można przetestować pod kątem dowolnego innego typu wartości. Wzorce is z poprzedniego przykładu nie są ograniczone do typów wartości dopuszczających wartość null. Można również użyć tych wzorców, aby sprawdzić, czy zmienna typu odwołania ma wartość, czy jest to null.

W poprzednim przykładzie pokazano również, jak używać wzorca typu w switch instrukcji, w której zmienna może być jedną z wielu różnych typów.

Jeśli chcesz sprawdzić, czy zmienna jest danym typem, ale nie przypisze jej do nowej zmiennej, możesz użyć is operatorów i as dla typów odwołań i typów wartości dopuszczanych do wartości null. Poniższy kod pokazuje, jak używać is instrukcji i as , które były częścią języka C# przed wprowadzeniem dopasowania wzorca w celu przetestowania, czy zmienna jest danego typu:

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

Jak widać, porównując ten kod ze zgodnym wzorcem, składnia dopasowania wzorca zapewnia bardziej niezawodne funkcje, łącząc test i przypisanie w jednej instrukcji. Zawsze, gdy jest to możliwe, użyj składni dopasowania wzorca.