演算子
算術演算子(*、/、%、+、-)
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
}
}
|
乗算*
、除算/
、剰余%
、加算+
、減算-
は算術演算子と呼ばれ、演算子の左右の値を算術演算する。
同じ型(例えばint
型)同士の演算であれば演算の結果もint
型になるが、例えばint
とdouble
を演算する場合はint
型の値がdouble
型に変換されてdouble
型の演算がオーバーロードにより実行され、double
型が返される。
また、unchecked
コンテキストの中では演算結果がオーバーフローしても処理を続行すする。checked
コンテキストの中ではオーバーフローする場合は、コンパイルエラーが発生するか、もしくは実行時にSystem.OverflowException
がスローされる。コンパイルオプションでプログラム全体のデフォルトをunchecked
もしくはchecked
に指定できるが、このコードのように適宜明示的に指定することもできる。
剰余はいわゆる割り算の「余り」を計算する。浮動小数点でも剰余演算が定義されている。.NETでは浮動小数点の剰余はで計算される(はx以下の最大の整数)。
シフト演算子(<<、>>)
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
}
}
|
参考:例えばint
型の1は2進数で表すと0001となる。
左シフト演算子<<
と右シフト演算子>>
はシフト演算を行う。左シフトの場合、左辺がint
値もしくはuint
値の場合は右辺の数値の下位5ビット(bit)がシフトカウントとして使われ、long
値もしくはulong
値の場合は右辺の数値の下位6bitがシフトカウントとして使われる。シフト演算の場合、オーバーフローは発生しない。
比較演算子(<、>)
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
}
}
|
比較演算子は数値型もしくは列挙型に対して左辺と右辺の値を比較した結果をbool
(ブール)値で返す。
等値演算子(==、!=)
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
}
}
|
等値演算子==
および非等値演算子!=
は左辺と右辺の値を比較する。string
以外の参照型の場合は、同一のインスタンスを比較した場合のみtrueが返される。string
型の場合は文字列の値を比較する。
is/as演算子とキャスト式
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 { }
|
is
演算子を使うと、「オブジェクトの実行時の型」が「指定した型」と互換性があるかどうかを動的にチェックできる。
as
演算子もしくはcast(キャスト)式を使うと、式を特定の型にキャストできる。cast式は、指定した型に式を明示的に変換し、その変換が存在しない場合は例外をスローする。as
演算子は、指定した参照型もしくはnull許容型に式を明示的に変換するが、キャストできないときは例外をスローせずnullを返す。
as
演算子は、is
演算子とcast式の組み合わせることでほぼ同値なコードに置き換え可能だが(式 as 型
= 式 is 型 ? (型)式 : (型)null
)、as
演算子を使えば、動的な型チェックが1回だけ実行されるようにコンパイラーによって最適化される。as
演算子で書けるときはas
演算子を優先的に使うのがよいだろう。
論理演算子(&&、||)
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;
}
}
|
論理演算子は、bool
値の論理演算結果を返す。
AND演算子&
および&&
は、左辺・右辺の値がともにTrue(真)のときのみtrueを返す。
OR演算子|
および||
は、左辺・右辺の値のどちらかがTrueのときにtrueを返す。
&&
および||
はショートサーキット(短絡評価)を行う点が、&
および|
と異なる。すなわち、&&
は左辺がFalse(偽)のときは右辺の評価を行わない。||
は左辺がTrueのときは右辺の評価を行わない。右辺の式が副作用を含むときは使い分けに注意が必要だ。一般的に多くのコードでは、より効率的に処理できるショートサーキット評価&&
および||
を使った上で、副作用を含む式を避けている。
条件演算子、null合算演算子
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"
}
}
|
?:
は条件演算子もしくは三項演算子と呼ばれる。b ? x : y
の形式で利用し、条件b
を評価し、trueならばx
が評価され、falseならばy
が評価される。
null合体演算子??
は左辺の値がnullでない場合に左辺の値を返し、nullのときには右辺の値を返す。nullのときにデフォルト値を設定したい場合などによく利用される。
null条件演算子
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?型
}
}
|
null条件演算子は、メンバーアクセス?.
もしくはインデックス・アクセス?[
する際にnullチェックの記述を簡略化できる。すなわち、nullの場合はメンバーアクセスやインデックス・アクセスを行わずにnullを返し、nullでないときのみメンバーアクセスやインデックス・アクセスを行う。
これはデリゲートの呼び出しの際にnullチェックを行うことにも利用できる。デリゲートの関数をnull条件演算子でnullチェックして呼び出すには、?.Invoke()
で呼び出せばよい。関数の返り値がnull非許容の値型の場合、null条件演算子の返り値はnull許容型になる。
nameof演算子
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
}
}
|
nameof
はC# 6.0で導入された演算子で、変数やクラスといった式の名前を取得するためのものである。変数名やクラス名などを文字列で指定する必要がある場所に利用することで、例えばソースコードの変数名を変更するときに、IDEのリファクタリング機能などによる追随が容易になるといったメリットがある。
ステートメント
if
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;
}
}
|
if
ステートメントはbool
型の式の値を評価し、trueならばif
節のステートメントを、falseならばelse
節のステートメントを実行する。複数の式を実行する場合はブロック{ }
内を利用するが、1つの式のみを記述することもできる。else if
により、複数のbool
型の式で処理を分岐させることもできる。
switch
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}";
}
}
|
DateTime.DayOfWeek
プロパティから日本語の曜日の省略名を取得するコードを記述しているが、最後にある通り、DateTime
型のインスタンスから曜日の省略名を取得するには書式指定文字列を指定することでも記述できる。
switch
ステートメントは、キーワードswitch
の後に指定した式を評価して、case
で指定した定数と一致したステートメントを実行する制御ステートメントだ。switch
に指定できる式は、数値型もしくはenum
/char
/string
型の式と、それらのnull許容型を返す式のみが指定でき、case
には定数のみが指定できる。
switch
ステートメントのcase
ラベルに記述するステートメントは、if
ステートメントなどと異なり複数のステートメントをブロック{ }
でまとめることなく記述できる。つまりcase
ラベルに続く複数のステートメントは、次のcase
もしくはdefault
ラベルまで実行される。しかし、ローカル変数のスコープを狭めるといった意図でブロック{ }
でまとめることもよく見かける。
あるcase
ラベルから後続のcase
ラベルに処理を続行するいわゆる「フォールスルー」はC#では禁止されているため、各case
ラベルの式はbreak
やreturn
で制御をswitch
ステートメントの外に移す必要がある。フォールスルーはgoto
ラベルステートメントで疑似的に記述することはできるが、goto
文は処理の制御を分かりづらくすることもあるので注意が必要だ。なお、複数のcase
ラベルを連続して併記している場合(=各case
ラベルの間にステートメントを含まない場合)に限り、フォールスルーのような形でそれら全てのcaseで後続のステートメントの処理が実行される(逆にいうと、「1つのswitchセクションは、複数のcase
ラベルを持つことが可能」というわけだ)。
default
ラベルを記述することもでき、switch
で評価した値がどのcase
ラベルにも一致しない場合に、default
ラベルに指定したステートメントが実行される。
enum
型を指定した場合であっても、そのenum
の取り得る全ての値をcase
に列挙しているか検査する仕組みは、言語仕様としては存在しない。が、Roslyn拡張といったコンパイラーの拡張機能などで実現することは可能である。
whileとdo
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);
}
}
|
while
ステートメントとdo
ステートメントは繰り返しのための制御構文だ。どちらもwhile
キーワードの後ろの条件を評価した結果がtrueの間、繰り返し処理を行う。while
が繰り返し処理の前に条件を評価するのに対し、do
は繰り返し処理をした後に条件を評価する。
forとforeach
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);
}
}
}
|
for
ステートメントとforeach
ステートメントも繰り返しを制御する。
for
ステートメントは、for (初期化式; 条件式; 反復式)
という構文で、
- 一番最初に1回だけ初期化式を評価した後、
- 条件式を評価して、
- 結果がtrueの場合、以下のb~dの処理を実行。falseの場合はループを終了して抜ける。
- まず
for
の後の埋め込みステートメントを実行し、 - 次に反復式を評価する。
- 再び、2の条件式の評価から順に繰り返す。
for
ステートメントは、配列の要素を列挙する場合など、繰り返し回数が最初に分かっている場合などに使われることが多い。
foreach
ステートメントは、コレクションの要素を列挙し、コレクションの要素ごとに埋め込みステートメントを実行する。ここで利用できるコレクションとは、配列やIEnumerable<T>
/IEnumerable
インターフェースを実装したコレクションクラスなどである。なお、言語仕様上はforeach
に指定するコレクションはIEnumerable
インターフェースを実装する必要はなく、GetEnumerator
メソッドなどが定義されていればよいことになっている。
また、foreach
ステートメントの要素は、var
ではなく明示的に型を指定することもできる。これは要素に特定の型しか格納されていないが、定義の上ではobject
型が返り値になっている場合などに利用できるが、サンプルコードにあるように実行時に格納されている要素の型が、foreach
の要素の型と違っておりキャストできない場合は、実行時例外がスローされる。
breakとcontinue
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」のみ
}
}
}
|
break
ステートメントは、すでに説明したswitch
/while
/do
/for
/foreach
ステートメントの内部で利用し、すぐ外側のステートメントを終了する。
continue
ステートメントは、while
/do
/for
/foreach
ステートメント内部で利用し、すぐ外側のステートメントの新たな繰り返しに制御を移し、処理を継続する。
- 前のページへ
- 次のページへ
- 【記事のページ一覧】
- 1 プログラムの実行と制御/型と変数
- 2 演算子/ステートメント
- 3 名前空間/クラス
- 4 メソッド/プロパティ/イベント/インデクサー/演算子オーバーロード/コンストラクターコンストラクタとデストラクターデストラクタ
- 5 構造体/継承とインターフェース/列挙型/イテレーター/例外処理/リソースの解放
- 6 ラムダ式/非同期処理(async/await)