is演算子とas演算子を理解しよう!

[C#] is演算子とas演算子を理解しよう!

※ 当サイトは広告を含みます。

ここではC#のis演算子as演算子について学びます。
この話はキャストが深く関係するため、以下を理解してる必要があります。

危険なキャスト

C#でキャストする最も簡単な方法は暗黙的キャストまたは明示的キャストです。
そして暗黙的キャストは問題ないとしても、明示的キャストには危険が伴います。

特に型変換ができない場合は例外が発生するため、結果としてプログラムが死にます。
以下は一般的なダウンキャストのサンプルコードです。これは明示的キャストのためビルドエラーにはなりません。


namespace Sample
{
  public class Parent
  {
  }

  public class Child : Parent
  {
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Parent p = new Parent();

      // 明示的キャストでダウンキャスト
      Child c = (Child)p; // ここで例外が発生する
    }
  }
}

こういった問題はtry-catchで捕捉してもいいのですが、処理を実行するかを判断したほうが速度的に有利です。
そんな時の条件分岐で利用するのが、今回のis演算子as演算子になります。

is演算子

is演算子を利用すると、指定した型に変換が可能かどうかを判断できます。
結果は真偽値で返るので、この値をif文とかで判断すると条件分岐ができます。


namespace Sample
{
  public class Parent
  {
  }

  public class Child : Parent
  {
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Parent p = new Parent();

      Console.WriteLine(p is Parent);
      Console.WriteLine(p is Child);

      Child c = new Child();
      if (c is Child)
      {
        Console.WriteLine("型変換が可能です");
      }
    }
  }
}

構文を抜き出すと以下です。


変数 is 型

型チェックと同時に代入

is演算子を利用する時に変数名を記述すると、型チェックと同時に代入ができます。


変換前の変数 is 型 変換後の変数

これとif文を併用することで、型変換が可能な場合だけ処理するような記述ができます。


namespace Sample
{
  public class Parent
  {
  }

  public class Child : Parent
  {
    public void Method()
    {
      Console.WriteLine("Child.Method()");
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Parent p = new Child();

      // 型変換が可能な場合は変数に代入される
      if (p is Child c)
      {
        // 代入された変数をそのまま使える
        c.Method();
      }
    }
  }
}

これの注意点として、作成する変数のスコープはif文と同じランクになります。
つまり、見た目はfor文とかの変数宣言に似てますが、以下のような記述は同名のビルドエラーになります。


namespace Sample
{
  public class Parent
  {
  }

  public class Child : Parent
  {
    public void Method()
    {
      Console.WriteLine("Child.Method()");
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Parent p = new Child();

      if (p is Child c)
      {
        c.Method();
      }

      // 同スコープなので無理
      var c = null;
    }
  }
}

否定形

一緒にnotを記述すると否定形の判断になります。つまり、変換できない場合の処理を記述できます。


namespace Sample
{
  public class Parent
  {
  }

  public class Child : Parent
  {
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Parent p = new Parent();

      // notで否定形
      if (p is not Child)
      {
        Console.WriteLine("変換できません");
      }
    }
  }
}

これは普通にis演算子の結果を!演算子で反転した場合と等価です。


namespace Sample
{
  public class Parent
  {
  }

  public class Child : Parent
  {
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Parent p = new Parent();

      if (!(p is Child))
      {
        Console.WriteLine("変換できません");
      }
    }
  }
}
管理人
管理人

なお、見た目は天と地くらいの差があります。

りさ
りさ

notを使ったほうが無駄な括弧が無くて綺麗だね。

nullを判断する

is演算子nullを判断することもできます。


namespace Sample
{
  public class Parent
  {
  }

  public class Child : Parent
  {
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Parent p = null;
      if (p is null)
      {
        Console.WriteLine("p is null");
      }

      Child c = new Child();
      if (c is not null)
      {
        Console.WriteLine("c is not null");
      }
    }
  }
}
りさ
りさ

これif文!=で書くのと何が違うんですか?

管理人
管理人

超大雑把に言うとis演算子のほうが早い。

C#は演算子をオーバーロードすることで==とか!=の機能を書き換えできます。

つまり、==!=は処理を行った結果で両者が等しいか異なるかを判断してます。
対してis演算子null判断は、nullかどうかを判断してるので単純に早いです。

as演算子

as演算子は必ずキャストするけど駄目ならnullにするって演算子です。
つまり、とりあえずキャストしてみて型的にNGならnullを返します。


namespace Sample
{
  public class Parent
  {
  }

  public class Child : Parent
  {
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Parent p = new Parent();

      Console.WriteLine(p as Parent); // p = not null
      Console.WriteLine(p as Child);  // p = null
    }
  }
}

構文を抜き出すと以下です。is演算子と変わりません。


変数 as 型

その特性から基本的に変数の代入と併用します。


namespace Sample
{
  public class Parent
  {
  }

  public class Child : Parent
  {
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Parent p = new Parent();

      // 無理な型変換はnullになる
      Child c = p as Child;

      // nullの受け入れが可能な仕組みと併用すると記述が統一できる
      Console.WriteLine(c);
    }
  }
}

これのメリットは処理の統一です。上記はConsole.WriteLine()に引数として値を渡しました。
このように受け入れ側でnullが許容される場合、例外処理などを使わずとも同一の記述ができます。

なお、as演算子は結果がnullの可能性があるため、参照型でしか利用できません。
よって、値型に対してas演算子を利用するとビルドエラーになります。

あとがき

真面目にコーディングしてると明示的キャストはほぼ使いません。仕方なく利用することもありますが、基本的に不要と思ってください。
ただ、Error処理を例外で統一して、あえて明示的キャストで変換。駄目なら例外に飛ばすような方法とかは普通にあります。

管理人
管理人

理解して使うなら何してもヨシ!

◆ C#に関する学習コンテンツ

この記事は参考になりましたか?

関連記事

コメント

この記事へのコメントはありません。