Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 

C# 早わかりリファレンス

― 2/6ページ: 演算子/ステートメント ―

2024年4月19日

演算子

算術演算子(*、/、%、+、-)

C#
class Program
{
  static void Main(string[] args)
  {
    // 加算
    var x0 = 1 + 4; // 5
    // int + double は double + double として加算。結果も double
    var x1 = 1 + 2.5d; // = 3.5
    // int - long は long - long
    var x2 = int.MinValue - 1L; // = -2147483649
    // 乗算
    var x3 = 2 * 4; // = 8
    // uncheckedコンテキストではオーバーフローしても処理が続行される
    // int.MaxValue * 2L だと 4294967294L
    unchecked
    {
      var x4 = int.MaxValue * 2; //  = -2
    }
    // 整数の除算は演算結果も整数
    var x5 = 7 / 2; // = 3
    // 割られる数がdoubleであれば演算結果もdoubleになる
    var x6 = 7f / 2; // = 3.5
    // 割る数がdoubleの場合もdouble型の除算が行われる
    var x7 = 7 / 2.0; // = 3.5
    // 剰余
    var x8 = 7 % 2; // = 1
    // 浮動小数点
    var x9 = 7f % 2.1; // = 0.7
  }
}
リスト3-1 算術演算子

 乗算*、除算/、剰余、加算+、減算-算術演算子と呼ばれ、演算子の左右の値を算術演算する。

 同じ型(例えばint型)同士の演算であれば演算の結果もint型になるが、例えばintdoubleを演算する場合はint型の値がdouble型に変換されてdouble型の演算がオーバーロードにより実行され、double型が返される。

 また、uncheckedコンテキストの中では演算結果がオーバーフローしても処理を続行すする。checkedコンテキストの中ではオーバーフローする場合は、コンパイルエラーが発生するか、もしくは実行時にSystem.OverflowExceptionがスローされる。コンパイルオプションでプログラム全体のデフォルトをuncheckedもしくはcheckedに指定できるが、このコードのように適宜明示的に指定することもできる。

 剰余はいわゆる割り算の「余り」を計算する。浮動小数点でも剰余演算が定義されている。.NETでは浮動小数点の剰余はx-[x/y]yで計算される([x]はx以下の最大の整数)。

シフト演算子(<<、>>)

C#
class Program
{
  static void Main(string[] args)
  {
    // 0001 << 00001(1bit左シフト) = 0010
    var x0 = 1 << 1; // 2
    // intまたuintのときは下位5bitをシフトカウントとして使う(要するに32bitの数値なので0~31bitのシフトが可能ということ)
    // 33 = 0010 0001 だが、下位5bitの 0 0001 でシフトすることになる
    // 0001 << 00001(1bit左シフト) = 0010
    var x1 = 1 << 33; // 2
    // longまたはulongのときは下位6bitでシフトする(要するに64bitの数値なので0~63bitのシフトが可能ということ)
    // 0001 << 100001(33bit左シフト) =‭‬‬‬‬‬‬ 0010 0000 0000 0000 0000 0000 0000 0000 0000‬‬
    var x2 = 1L << 33; // 8589934592
    // 97 = 0110 0001の下位6bitの 10 0001 でシフトする
    // 0001 << 100001(33bit左シフト) = 同上
    var x3 = 1L << 97; // 8589934592

    // 0011 1110 1000 >> 3(3bit右シフト) = 125 (0111 1101)
    var x4 = 1000 >> 3; // 125
  }
}
リスト3-2 シフト演算子

参考:例えばint型の1は2進数で表すと0001となる。

 左シフト演算子<<と右シフト演算子>>はシフト演算を行う。左シフトの場合、左辺がint値もしくはuint値の場合は右辺の数値の下位5ビット(bit)がシフトカウントとして使われ、long値もしくはulong値の場合は右辺の数値の下位6bitがシフトカウントとして使われる。シフト演算の場合、オーバーフローは発生しない。

比較演算子(<、>)

C#
class Program
{
  static void Main(string[] args)
  {
    var f1 = 1 > 1;  // False
    var f2 = 1 >= 1; // True
    var f3 = 1 < 2;  // True
    var f4 = 1 <= 2; // True
  }
}
リスト3-3 比較演算子

 比較演算子は数値型もしくは列挙型に対して左辺と右辺の値を比較した結果をbool(ブール)値で返す。

等値演算子(==、!=)

C#
class Program
{
  static void Main(string[] args)
  {
    var f1 = 1 == 2; // False
    var f2 = 1 != 1; // False
    var f3 = new object() == new object();     // False
    var f4 = "abc" == string.Copy("a") + "bc"; // True
  }
}
リスト3-4 等値演算子

 等値演算子==および非等値演算子!=は左辺と右辺の値を比較する。string以外の参照型の場合は、同一のインスタンスを比較した場合のみtrueが返される。string型の場合は文字列の値を比較する。

is/as演算子キャスト式

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var b = new B();
    var c = new C();

    var f0 = b is B; // True
    var f1 = b is A; // True
    var f2 = c is A; // False

    // A型の変数として宣言
    A a = new B();

    // isで型チェック後、cast式でキャストする
    if (a is B)
    {
      ((B)a).Test(); // I'm B
    }

    // 上記のis+キャストはas+nullチェックで代用可能
    var b2 = a as B;
    if (b2 != null)
    {
      b2.Test(); // I'm B
    }

    // nullチェックはnull条件演算子で置き換えることも可能
    (a as B)?.Test(); // I'm B
  }
}

class A { }
class B : A { public void Test() { Console.WriteLine("I'm B."); } }
class C { }
リスト3-5 is/as演算子およびcast式

 is演算子を使うと、「オブジェクトの実行時の型」が「指定した型」と互換性があるかどうかを動的にチェックできる。

 as演算子もしくはcast(キャスト)式を使うと、式を特定の型にキャストできる。cast式は、指定した型に式を明示的に変換し、その変換が存在しない場合は例外をスローする。as演算子は、指定した参照型もしくはnull許容型に式を明示的に変換するが、キャストできないときは例外をスローせずnullを返す。

 as演算子は、is演算子とcast式の組み合わせることでほぼ同値なコードに置き換え可能だが(式 as 型式 is 型 ? (型)式 : (型)null)、as演算子を使えば、動的な型チェックが1回だけ実行されるようにコンパイラーによって最適化される。as演算子で書けるときはas演算子を優先的に使うのがよいだろう。

論理演算子(&&、||)

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var f1 = True() || False(); // True is called: True
    var f2 = True() | False(); // True is called: False is called: True

    var f3 = False() && True(); // False is called: False
    var f4 = False() & True(); // False is called: True is called: False
  }

  static bool True()
  {
    Console.Write("True is called: ");
    return true;
  }

  static bool False()
  {
    Console.Write("False is called: ");
    return false;
  }
}
リスト3-6 論理演算子

 論理演算子は、bool値の論理演算結果を返す。

 AND演算子&および&&は、左辺・右辺の値がともにTrue(真)のときのみtrueを返す。

 OR演算子|および||は、左辺・右辺の値のどちらかがTrueのときにtrueを返す。

 &&および||はショートサーキット(短絡評価)を行う点が、&および|と異なる。すなわち、&&は左辺がFalse(偽)のときは右辺の評価を行わない。||は左辺がTrueのときは右辺の評価を行わない。右辺の式が副作用を含むときは使い分けに注意が必要だ。一般的に多くのコードでは、より効率的に処理できるショートサーキット評価&&および||を使った上で、副作用を含む式を避けている。

条件演算子null合算演算子

C#
class Program
{
  static void Main(string[] args)
  {
    var f1 = true;
    var t1 = f1 ? "true" : "false"; // "true"

    var f2 = false;
    var t2 = f2 ? "true" : "false"; // "false"

    string v1 = null;
    var t3 = v1 ?? "default";       // "default"

    string v2 = "text";
    var t4 = v2 ?? "default";       // "text"
  }
}
リスト3-7 条件演算子およびnull合体演算子

 ?:条件演算子もしくは三項演算子と呼ばれる。b ? x : yの形式で利用し、条件bを評価し、trueならばxが評価され、falseならばyが評価される。

 null合体演算子??は左辺の値がnullでない場合に左辺の値を返し、nullのときには右辺の値を返す。nullのときにデフォルト値を設定したい場合などによく利用される。

null条件演算子

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    int[] array = null;
    var length = array?.Length; // null
    var i1 = array?[1]; // null。i1はint?型

    Func<int> func = null;
    var i2 = func?.Invoke(); // null。i2はint?型
  }
}
リスト3-8 null条件演算子

 null条件演算子は、メンバーアクセス?.もしくはインデックス・アクセス?[する際にnullチェックの記述を簡略化できる。すなわち、nullの場合はメンバーアクセスやインデックス・アクセスを行わずにnullを返し、nullでないときのみメンバーアクセスやインデックス・アクセスを行う。

 これはデリゲートの呼び出しの際にnullチェックを行うことにも利用できる。デリゲートの関数をnull条件演算子でnullチェックして呼び出すには、?.Invoke()で呼び出せばよい。関数の返り値がnull許容の値型の場合、null条件演算子の返り値はnull許容型になる。

nameof演算子

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var s1 = nameof(args);         // args
    var s2 = nameof(DateTime.Now); // Now
    var s3 = nameof(System.Linq);  // Linq
  }
}
リスト3-9 nameof演算子

 nameofC# 6.0で導入された演算子で、変数やクラスといった式の名前を取得するためのものである。変数名やクラス名などを文字列で指定する必要がある場所に利用することで、例えばソースコードの変数名を変更するときに、IDEのリファクタリング機能などによる追随が容易になるといったメリットがある。

ステートメント

if

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var flg = true;
    // true
    if (flg)
      Console.WriteLine("true");

    // else if
    if (Check())
    {
      Console.WriteLine("if");
    }
    else if (flg)
    {
      Console.WriteLine("else if");
    }
    else
    {
      Console.WriteLine("else");
    }
  }

  static bool Check()
  {
    return false;
  }
}
リスト4-1 ifステートメント

 ifステートメントはbool型の式の値を評価し、trueならばif節のステートメントを、falseならばelse節のステートメントを実行する。複数の式を実行する場合はブロック{ }内を利用するが、1つの式のみを記述することもできる。else ifにより、複数のbool型の式で処理を分岐させることもできる。

switch

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var dayOfWeek = DayOfWeek.Thursday;
    string text;
    switch (dayOfWeek)
    {
      case DayOfWeek.Monday:
        text = "月";
        break;
      case DayOfWeek.Tuesday:
        text = "火";
        break;
      case DayOfWeek.Wednesday:
        text = "水";
        break;
      case DayOfWeek.Thursday:
        text = "木";
        break;
      case DayOfWeek.Friday:
        text = "金";
        break;
      case DayOfWeek.Saturday:
        text = "土";
        break;
      case DayOfWeek.Sunday:
        text = "日";
        break;
      default:
        throw new ArgumentOutOfRangeException();
    }
    // なお、日付を表すDateTime型のインスタンスからカレントカルチャの曜日の省略名を得る場合はこのように書ける
    var s1 = $"{DateTime.Now:ddd}";
  }
}
リスト4-2 switchステートメント

DateTime.DayOfWeekプロパティから日本語の曜日の省略名を取得するコードを記述しているが、最後にある通り、DateTime型のインスタンスから曜日の省略名を取得するには書式指定文字列を指定することでも記述できる。

 switchステートメントは、キーワードswitchの後に指定した式を評価して、caseで指定した定数と一致したステートメントを実行する制御ステートメントだ。switchに指定できる式は、数値型もしくはenumcharstring型の式と、それらのnull許容型を返す式のみが指定でき、caseには定数のみが指定できる。

 switchステートメントのcaseラベルに記述するステートメントは、ifステートメントなどと異なり複数のステートメントをブロック{ }でまとめることなく記述できる。つまりcaseラベルに続く複数のステートメントは、次のcaseもしくはdefaultラベルまで実行される。しかし、ローカル変数のスコープを狭めるといった意図でブロック{ }でまとめることもよく見かける。

 あるcaseラベルから後続のcaseラベルに処理を続行するいわゆる「フォールスルー」はC#では禁止されているため、各caseラベルの式はbreakreturnで制御をswitchステートメントの外に移す必要がある。フォールスルーはgotoラベルステートメントで疑似的に記述することはできるが、goto文は処理の制御を分かりづらくすることもあるので注意が必要だ。なお、複数のcaseラベルを連続して併記している場合(=各caseラベルの間にステートメントを含まない場合)に限り、フォールスルーのような形でそれら全てのcaseで後続のステートメントの処理が実行される(逆にいうと、「1つのswitchセクションは、複数のcaseラベルを持つことが可能」というわけだ)。

 defaultラベルを記述することもでき、switchで評価した値がどのcaseラベルにも一致しない場合に、defaultラベルに指定したステートメントが実行される。

 enum型を指定した場合であっても、そのenumの取り得る全ての値をcaseに列挙しているか検査する仕組みは、言語仕様としては存在しない。が、Roslyn拡張といったコンパイラーの拡張機能などで実現することは可能である。

whiledo

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    int term = 0;
    while (term <= 3)
    {
      Console.WriteLine(term);
      ++term;
    }

    term = 0;
    do
    {
      Console.WriteLine(term);
      ++term;
    } while (term == 0);
  }
}
リスト4-3 whileステートメントとdoステートメント

 whileステートメントとdoステートメントは繰り返しのための制御構文だ。どちらもwhileキーワードの後ろの条件を評価した結果がtrueの間、繰り返し処理を行う。whileが繰り返し処理の前に条件を評価するのに対し、doは繰り返し処理をした後に条件を評価する。

forforeach

C#
using System;
using System.Linq;

class Program
{
  static void Main(string[] args)
  {
    var array = new[] { 1, 3, 5 };

    Console.WriteLine("for文");
    var length = array.Length;
    for (int i = 0; i < length; i++)
    {
      Console.WriteLine(array[i]);
    }

    Console.WriteLine("foreach文:配列");
    foreach (var e in array)
    {
      Console.WriteLine(e);
    }

    Console.WriteLine("foreach文:Enumerator");
    var enumerable = Enumerable.Range(1, 5);
    foreach (var e in enumerable)
    {
      Console.WriteLine(e);
    }

    Console.WriteLine("foreach文:cast");
    // int型とdouble型を混ぜた配列をobject[]型として宣言
    var objects = new object[] { 1, 3, 4.5d };
    // 要素型はvarだとobject型になるが、以下のようにint型で宣言するとキャストされるため、
    // 要素がdouble型のときにキャストに失敗して実行時例外がスローされる
    foreach (int e in objects)
    {
      Console.WriteLine(e);
    }
  }
}
リスト4-4 forステートメントとforeachステートメント

 forステートメントとforeachステートメントも繰り返しを制御する。

 forステートメントは、for (初期化式; 条件式; 反復式)という構文で、

  1. 一番最初に1回だけ初期化式を評価した後、
  2. 条件式を評価して、
    1. 結果がtrueの場合、以下のb~dの処理を実行。falseの場合はループを終了して抜ける。
    2. まずforの後の埋め込みステートメントを実行し、
    3. 次に反復式を評価する。
    4. 再び、2の条件式の評価から順に繰り返す。

 forステートメントは、配列の要素を列挙する場合など、繰り返し回数が最初に分かっている場合などに使われることが多い。

 foreachステートメントは、コレクションの要素を列挙し、コレクションの要素ごとに埋め込みステートメントを実行する。ここで利用できるコレクションとは、配列やIEnumerable<T>IEnumerableインターフェースを実装したコレクションクラスなどである。なお、言語仕様上はforeachに指定するコレクションはIEnumerableインターフェースを実装する必要はなく、GetEnumeratorメソッドなどが定義されていればよいことになっている。

 また、foreachステートメントの要素は、varではなく明示的に型を指定することもできる。これは要素に特定の型しか格納されていないが、定義の上ではobject型が返り値になっている場合などに利用できるが、サンプルコードにあるように実行時に格納されている要素の型が、foreachの要素の型と違っておりキャストできない場合は、実行時例外がスローされる。

breakcontinue

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var array = new[] { 1, 2, 3, 4, 5 };

    foreach (var e in array)
    {
      if (e % 2 == 1)
        continue;  // 奇数のときは次の繰り返しへ制御を移す(=以下のコードはスキップされる)

      if (e == 4)
        break;     // 4のときにforeachを終了させる

      Console.WriteLine(e);  // 出力されるのは「2」のみ
    }
  }
}
リスト4-5 breakステートメントとcontinueステートメント

 breakステートメントは、すでに説明したswitchwhiledoforforeachステートメントの内部で利用し、すぐ外側のステートメントを終了する。

 continueステートメントは、whiledoforforeachステートメント内部で利用し、すぐ外側のステートメントの新たな繰り返しに制御を移し、処理を継続する。

サイトからのお知らせ

Twitterでつぶやこう!