目次

概要

Ver. 7

C# 7で、is演算子switchステートメントcaseが拡張されて、以下のような機能が入りました。

  • caseでも、is演算子と同じように、インスタンスの型を見ての分岐ができるようになった
  • x is T tや、case T tというように、型を調べつつ、型が一致してたらキャスト結果を変数tで受け取れるようになった

この機能を型スイッチ(type switch)と呼びます。

この機能は、C# 7よりもさらに先のバージョンで入る予定の「パターン マッチング(pattern matching)」という機能のうち、一番初歩的な部分だけを先に実装したものです。C# 7の時点では、型を見ての分岐(型スイッチ)だけが実装されていますが、将来的にはもっと複雑な条件を書けるようになります。

is演算子の拡張

C# 7では、is演算子で以下のような書き方ができるようになりました(型の後ろに新しい変数を書けるようになりました) 。

型を調べたい変数 is  新しい変数

C# 6以前のis演算子は少し使い勝手が悪い面がありました。型の一致を判定するだけならいいんですが、 型変換も絡むといまいちです。

例えば、以下のように型を判定するだけならis演算子の出番です。

// 型判定のみなら、これまでの is 演算子でも十分
if (obj is string) Console.WriteLine("string");

ところが、型を判定したうえでダウンキャストしたいという場面では、以下のように、「2度手間」になって、コード量的にも実行効率的にもよくないです。

// 型変換もしたい
if (obj is string)
{
    var s = (string)obj;
    //↑ isとキャストで2つの別命令を使う。二重処理になってるだけで無駄
    Console.WriteLine("string #" + s.Length);
}

結局、以下のように、as演算子を使うことが推奨されます。

// 結局、as 演算子 + null チェックを使うことになる
var s = obj as string;
if (s != null)
{
    Console.WriteLine("string #" + s.Length);
}

これに対して、C# 7では、is演算子で以下のような書き方ができるようになりました。

// C# 7での新しい書き方
if (obj is string s)
{
    Console.WriteLine("string #" + s.Length);
}

挙動的には、先ほどのas演算子を使ったものとまったく同じ挙動になります。 is演算子で型を判定しつつ(boolの戻り値を返しつつ)、その型への変換結果を新しい変数で受け取れます。

is演算子で宣言された変数のスコープ

is演算子の拡張によって、式の中で変数宣言ができるようになりました。 そこで問題になるのはこの変数のスコープです。

概ね、「その式を含むブロック内」と考えていいんですが、ifwhileなどの中で使ったときなど、いくつか特殊な場合があります。 詳細については「式の中で変数宣言」を参照してください。

is演算子によるnullチェック

元々のis演算子の仕様でもあるんですが、nullには型がなくて常にisに失敗します(falseを返す)。

string x = null;

if (x is string)
{
    // x の変数の型は string なのに、is string は false
    // is 演算子は変数の実行時の中身を見る & null には型がない
    Console.WriteLine("ここは絶対通らない");
}

この仕様は、C# 7からの新しい構文でも引き継いでいて、nullじゃないときだけだけ何かの処理をしたいときに使えます。 と言っても、参照型の場合にはあまり使い道はありませんが、以下のような書き方ができます。

static void F(string nullable)
{
    if (nullable is string nonNull)
    {
        // nonNull には絶対に null が入らない
        // nullable をそのまま使っても、if の結果、null じゃない保証があるのであまり意味はないけども
        Console.WriteLine(nonNull.Length);
    }
}

この書き方が役に立つのは、値型とnull許容型を使う場合でしょう。 例えばC# 6以前だと、以下のような書き方になります。

static void F(int? x)
{
    // C# 6以前の書き方
    if (x.HasValue)
    {
        // この「.Value」が結構うっとおしい
        Console.WriteLine(x.Value * x.Value);
    }
}

これが、C# 7で以下のように書けるようになります。

static void F(int? x)
{
    if (x is int n)
    {
        Console.WriteLine(n * n);
    }
}

余談: 変数の意味を変えない

プログラミング言語によっては、以下のように、is演算子で型を判定した後には、自動的にその型扱いしてくれる言語もあります。

static void F(object obj)
{
    if (obj is string)
    {
        // この中では obj を string 扱いできる言語がある
        // C# ではコンパイル エラー
        Console.WriteLine("string #" + obj.Length);
    }
    else if (obj is int)
    {
        // 同上、int 扱いできる言語がある
        // C# ではコンパイル エラー
        Console.WriteLine("int " + (obj * obj));
    }
}

C# では、こういう、「objectだと思っていたものが一定範囲でだけ別の型になる」というようなことはやらない方針です。

また、以下のように、同名の別変数を導入できる言語もありますが、こちらもC#では認めていません。

static void F(object x)
{
    if (x is string x)
    {
        // 引数の x とは別に、is 演算子で別の「x」を導入できる言語もある
        // C# ではコンパイル エラー
        Console.WriteLine("string #" + x.Length);
    }
}

C#では、変数はスコープ内で意味不変(invariant meaning)であるべきという方針を持っています。 上記の2つの例では、objxが部分的に(ifの中でだけ)別の意味になるので、C#としては認めたくないものになります。

更新履歴

ブログ