パターン マッチング、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 ステートメントでは、int?Nullable<int> など、null 許容値型が許可されませんが、他の値の型についてはテストできます。 前の例の is パターンは null 許容値型に限定されません。 また、これらのパターンを使用して、参照型の変数に値があるか、null であるかをテストすることもできます。

上の例では、変数をさまざまな型の 1 つにすることができる switch ステートメントで型パターンを使用する方法も示されています。

変数が特定の型かどうかのテストだけを行い、それを新しい変数に代入しない場合は、参照型と null 許容値型に対して is 演算子と as 演算子を使用できます。 次のコードでは、パターン マッチングが導入される前に C# 言語に含まれていた is ステートメントと as ステートメントを使用し、変数が指定の型かどうかをテストする方法が示されています。

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

ご覧のとおり、このコードとパターン マッチング コードを比較することで、テストと割り当てが 1 回のステートメントで組み合わされ、パターン マッチング構文がより強固になります。 可能な限り、パターン マッチングを使用してください。