C# 7 早わかりリファレンス(前編)

C# 7 早わかりリファレンス(前編)

C# 基礎文法 最速再入門【7.0対応】

2017年4月25日 改訂(初版[6.0対応]: 2016/04/14)

「あれ、どう書くんだっけ?」を素早く解決するための、C# 7.0主要文法がコンパクトにまとまったリファレンス(全3回)。前編では、C#の歴史/開発ツール/プログラムの実行と制御/型と変数/演算子/ステートメントを説明する。

  • このエントリーをはてなブックマークに追加

 本連載(全3回)は、執筆時点で最新のC# 7.0の主要な文法をサンプルコードと一緒に一覧できるようにするために書いたものである。文法の解説などは特に注意するべきことなどに絞っているため、初学者の学習用というよりはある程度C#を知っている方の参考用として書いている。

 今回の記事を書くに当たって、C#言語仕様などを確認していたが、筆者自身初めて知った文法ルールもあった。この内容は全てを覚えるというよりも、C#コードを書いていて気になったときに参考にしてもらえると幸いである。

 ちなみに、古いC# 6.0対応版が読みたい場合は、こちらを参照してほしい。また、C# 7.0の新機能をまだ知らない場合は、事前に「C# 7.0で知っておくべき10の新機能」を読んで言語機能を理解しておいてほしい(本連載は簡潔さを優先して説明をはしょっているため)。

 本連載のサンプルコードに関しては、初出時に公開したLINQPad形式に加えて、クロスプラットフォームで動作するように.NET Core向けのコンソールプロジェクト形式のものも用意した。両形式を含むコードを下記のリンク先で配布しているので、まずは2からZipファイルをダウンロードしてほしい。

 LINQPadを利用する場合は、無償で利用できるStandard Editionで動作可能である。ぜひLINQPad 5の最新版をインストールした上でLINQPad用サンプルコード(MITライセンス)を活用していただきたい。

 また、コンソールプロジェクト形式のものは、コードの主な部分はLINQPad用と同じだが、Mainメソッドから全てのサンプルコードが実行できるようにクラスやメソッドの構造に手を入れている。Visual StudioもしくはVisual Studio Codeなど各種開発ツールで実行してほしい。

【参考】LINQPad用のサンプルコードの基本的な使い方と注意点

 LINQPadをインストールすれば、.linqファイルをダブルクリックするだけで、そのファイルがLINQPadで開かれる。その状態で、[Language]欄で「C# Program」を選択して、F5キー(もしくは[Execute]ボタン)を押すだけで実行できる。

 またLINQPadには、ウィンドウ左下にある[Samples]タブの[Download/import more samples]のメニューから直接サンプルコードを取り込む機能がある。上記の3のURLを指定すると、この機能を使ってサンプルコードをまとめてLINQPadに取り込むことが可能である(お薦め)。

 参照するアセンブリやusingでインポートする名前空間は、F4キーを押して(もしくはメニューバーの[Query]-[References and Properties]を選択して)[Query Properties]ダイアログを表示し、そのダイアログ上の[Additional References](アセンブリ参照の追加)タブや[Additional Namespace Imports](名前空間インポートの追加)タブで指定できる。

 LINQPadでは、あらゆるオブジェクトの内容を確認するためのDump()メソッドが提供されており、LINQPad用サンプルコード内では用いているところが多数ある(本稿に掲載しているコード例ではDump()メソッドは省略しており、LINQPad用サンプルコードと少しコードが異なる部分があるので注意してほしい)。

<目次>

 文法の説明に入る前に、C#の最新動向に詳しくない方に向けて、C# 7.0までの成り立ちや最新の開発ツール事情を簡単に紹介しておこう。

▲目次に戻る

C#の歴史

 C#の正式版は、2002年にVisual Studio .NET 2002と一緒に、C# 1.0としてリリースされた。この.NETというのは.NET Frameworkを意味しており、実際に.NET FrameworkもC# 1.0と同時にリリースされている。

 .NET Frameworkは、基本クラスライブラリBCL)と、実行環境となる共通言語ランタイムCLR)で構成される。CLRは、共通言語基盤CLI)という共通的な仕様に基づいて実装されているので、C#以外の言語でもこの仕様にのっとった中間言語IL)コードを出力すれば、CLR上で実行可能である。実際、VB.NET(Visual Basice .NET)やF#がCLR上で実行可能であり、しかもコードレベルで異なる言語で書かれたたクラスを参照するといったことも可能になっている。

 C# 2.0以降は、C#と.NET FrameworkとVisual Studioは歩みをそろえてバージョンアップしていく。C# 2.0ではジェネリクスの機能が追加されたが、これは.NET Framework側に機能追加された結果であり、C#ではランタイムレベルでジェネリクスの機能が利用できることになった。C# 3.0では拡張メソッドとラムダ式が追加され、LINQが利用できるようになった。C# 4.0ではdynamic型が、C# 5.0ではasyncawaitキーワードによる非同期メソッドが、といったように着実に機能が追加されていった。

 C#の言語機能が追加されていく中、時代はGitHubを中心としたオープンソースなプログラムを利用する流れが強まっていった。そんな中、マイクロソフト自身もライブラリをオープンソースにする流れに変わっていったが、「C#ソースコードからILコードにコンパイルするコンパイラーをサービスとして利用できるようにし、それ自体をオープンソースにしよう」というプロジェクトが生まれた。“Roslyn”というコードネームで呼ばれたプロジェクトである。既存のコンパイラーから、オープンソースのRoslynコンパイラーに変えつつ、同時にC#の言語機能も追加してリリースされたのがC# 6.0である。

 C# 6.0以降は言語機能の追加に関する議論自体がオープンな場所で進められている。追加される機能内容が最初からオープンな場所で議論された最初のバージョンアップがC# 7.0であり、この流れは次期8.0以降にも続いている。

 一方、C# 6.0~7.0の間で、C#が動作する環境にも大きな変更があった。当初、C#および.NET Frameworkの動作がサポートされるOSはWindowsのみだった。Monoプロジェクトと呼ばれるLinuxへの移植を行うプロジェクトはあったが(ちなみに、Monoプロジェクトの流れは、今のXamarinに続いている)、マイクロソフトが公式でサポートするのはWindowsのみであった。ところが、.NET Coreとよばれる新しいフレームワークが発表された。この.NET Coreは、ランタイムと、クラスライブラリなどのフレームワーク一式がGitHub上で公開されており、Windowsに加えて、LinuxやmacOS(Mac)の各プラットフォームをサポートするように開発が進められている。加えて、Windows向けの商用サポートはマイクロソフトが、Linuxのディストリビューションの一つであるRed Hat Enterprise Linux向けの商用サポートはレッドハット(Red Hat)が提供している。

 現在のC#は、言語機能としてたゆまぬ進化が続いている中で、Windows以外のプラットフォームでも商用としても十分に利用に耐えられる、クロスプラットフォームで利用可能な言語として歩みを始めた状態である。

▲目次に戻る

開発ツール

 C# 1.0のリリース当初より同時にリリースされてきたという歴史の長さから分かるように、Visual StudioがC#サポート機能を最も豊富に搭載した開発ツールといえるだろう。最新のVisual Studio 2017では、従来の.NET Frameworkに加え、クロスプラットフォーム対応の.NET Coreもサポートしている。基本的には有償の商用ツールではあるが、無償版のCommunityエディションなども用意されている(無償版には、この他にExpressエディションもあるが、執筆時点ではVisual Studio 2017向けのものがまだ提供されていない。どちらの無償版を利用できるかなど利用用途やライセンスについては、公式サイトの説明を参照してほしい)。また、Visual Studioの拡張機能を開発することもでき、無償・有償を問わず多数の便利な拡張が公開されていることもメリットの一つだ。

 ただし、Visual StudioはWindowsのみのサポートであるため、.NET Core向けの開発をLinuxやmacOS上で行う場合は利用できない。そのためLinuxやmacOSでの.NET Core開発環境としては、GUIツールの代わりに、.NET Core SDKで提供されるCUIツールを利用するのが一般的だ。こちらのリンク先の手順(英語)に従って.NET Core SDKをダウンロードすると、.NET Core CLI(コマンドライン・インターフェース)がインストールされ、ターミナルなどでのコマンドライン実行のみで開発が開始できるようになる。それに加えて、Windows/Linux/macOSで動作するコードエディターのVisual Studio Codeを使うと便利だ。Visual Studio Codeに対しても(Visual Studioと同様に)拡張機能を開発することができ、C#開発用の機能も拡張機能として提供されている。どうしてもVisual StudioのようなGUIツール(いわゆるIDE)を使いたい場合は、サードパーティから提供されているJetBrainsのRiderEclipseプロジェクトのEclipse Cheを利用することもできる。また、macOSのみサポートされるが、マイクロソフトが提供するVisual Studio for Mac(執筆時点ではプレビュー版で、開発が進行中)も利用可能である。

▲目次に戻る

プログラムの実行と制御

 この章のサンプルコードは、LINQPad上だと標準出力などの挙動が異なるため、LINQPadのサンプルには載せていない。サンプルを試すには、C#のコンソールアプリとしてプロジェクトを作成されたい。

▲目次に戻る

Mainメソッド標準出力・標準エラー出力

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    // 標準出力。Console.Out.WriteLineも同じ
    Console.WriteLine("こんにちは、C#!");
    // 標準エラー出力
    Console.Error.WriteLine("こんにちは、C#!");
    // Visual Studioからデバッグ実行するときなどは、コンソールウインドウがすぐに閉じるのを防ぐために入力を待つとよい
    Console.ReadLine();
  }
}
リスト1-1 Mainメソッドと標準出力、標準エラー出力

 C#ではエントリポイントと呼ばれるMainメソッドを実行環境が呼び出すことにより、アプリケーションの起動が開始される。エントリポイントとなるMainメソッドは、staticメソッドであり、返り値はvoidもしくはint型、仮パラメーターはなしもしくはstring[]型でなければならない。

 またプロセスからの出力ストリームとして、標準出力標準エラー出力が用意されている。

▲目次に戻る

コマンドライン引数

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    foreach (var arg in args)
    {
      Console.WriteLine(arg);
    }
    Console.ReadLine();
  }
}
リスト1-2 コマンドライン引数

 アプリケーションの実行時に指定されるコマンドライン引数を利用するためには、Mainメソッドにstring[]型の仮パラメーターを指定する。コマンドライン引数が指定されていない場合は空の配列となり、nullにはならない。

 また、Visual Studioでデバッグ実行する場合は、プロジェクトのプロパティを開き、デバッグの開始オプションのコマンドライン引数を指定する(図1)。

図1 Visual Studioのオプションで、デバッグ時のコマンドライン引数を指定する

本連載のサンプルは、C# 7.0に対応しているVisual Studio 2017を使ってビルドして検証しているので注意してほしい。

 Visual Studio Codeの場合は、デフォルトではプロジェクトフォルダー直下に作成される.vscodeフォルダー内にあるlaunch.jsonファイルを編集することで、コマンドライン引数を編集できる。configurationsキー以下に複数のデバッグ設定をオブジェクトの配列として定義し、コマンドライン引数を追加する場合は、該当するデバッグ設定オブジェクトのargsキーの値に配列として指定する。

JSON
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": ".NET Core Launch (console)",
      "type": "coreclr",
      "request": "launch",
      "preLaunchTask": "build",
      "program": "${workspaceRoot}\\CheatSheetConsoleApp\\bin\\Debug\\netcoreapp1.1\\CheatSheetConsoleApp.dll",
      "args": ["Hello", "C#"],
      "cwd": "${workspaceRoot}\\CheatSheetConsoleApp",
      "console": "internalConsole",
      "stopAtEntry": false,
      "internalConsoleOptions": "openOnSessionStart"
    }
    //,
    //{
    //  ……このようにして別のデバッグ設定のオブジェクトを複数設定可能……
    //}
  ]
}
リスト1-2b Visual Studio Codeでのlaunch.jsonファイルの編集によるデバッグ時のコマンドライン引数の編集

プログラムのファイルパスの一部がnetcoreapp1.1となっているように、本連載のサンプルは.NET Core 1.1でビルドして検証している。1.0では本連載で示す一部のサンプルコードがビルドできないので、注意してほしい。

▲目次に戻る

標準入力の読み取り

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var input = Console.ReadLine();
    Console.WriteLine(input);
    Console.ReadLine();
  }
}
リスト1-3 標準入力の読み取り

 標準入力から入力された文字列を読み取ることができる。

▲目次に戻る

プログラムの終了とステータスコード

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    Environment.Exit(1);
  }
}
リスト1-4 Environment.Exitメソッドによるステータスコードを指定した終了
C#
class Program
{
  static int Main(string[] args)
  {
    return 1;
  }
}
リスト1-5 Mainメソッドの返り値でステータスコードを指定

 実行しているプロセスを終了した際のステータスコードを指定できる。指定せずに終了した場合のステータスコードは0であり、これは正常に完了したことを意味する。明示的に指定する場合はEnvironment.Exitメソッドの引数として指定するか、Mainメソッドの返り値として指定できる。なお、Environment.Exitメソッドを実行すると、その時点で実行中のプロセスは終了する。

▲目次に戻る

型と変数

▲目次に戻る

組み込み型リテラル表記

C#
class Program
{
  static void Main(string[] args)
  {
    // bool型
    var flg1 = true;
    var flg2 = false;

    // int、uint、long、ulongのうち表現できる最初の型。この場合はint型
    var int1 = 1;
    // uまたはUサフィックスを付けた場合はuintもしくはulongのうち表現できる最初の型。この場合はuint型
    var int2 = 1U;
    // lまたはLサフィックスを付けた場合はlongもしくはulongのうち表現できる最初の型。この場合はuint型
    var int3 = 1L;
    // ulもしくはULサフィックスを付けた場合はulong型
    var int4 = 1UL;

    // 単精度浮動小数点
    var number1 = 1f;
    // 指数表記
    var number2 = 1.2e-3f;
    // 倍精度浮動小数点
    var number3 = 1d;
    // 指数表記
    var number4 = 1.2e-3d;
    // 高精度小数(decimal型)
    var number5 = 0.1m;

    // char型
    var char1 = '1';
    var char2 = '\'';
    var char3 = '\xFFFF';

    // C# 7.0で導入された桁区切り
    var int5 = 1_000_000;
    // _は先頭と最後以外であれば任意の場所に任意の数だけ使える
    var int6 = 1_000__00000_0UL;
    // 小数部にも使える
    var number6 = 0.00_00__01f;
    var numbder7 = 100__00_0.0__1m;

    // 16進数表記は従来から利用可能
    var int7 = 0xdeadbeaf;
    // C# 7.0より2進表記が利用可能に
    var int8 = 0b10011100;
    // C# 7.0より16進表記や2進表記と桁区切りも併せて利用可能
    var int9 = 0xdead_beaf;
    var int10 = 0b1001_1000_1111;
  }
}
リスト2-1 値型のリテラル表記

 C#では値型参照型という2種類の型をサポートしている。そのうち、値型のいくつかはリテラル(=ソースコード上に直接記述した数値や文字列)で値を表現できる。

 整数型の場合、指定した数値が表現できる型(int型であれば–2147483648から2147483647まで、long型であれば–9223372036854775808から9223372036854775807まで、など)によってどの型のリテラル表現になるかが決まる。

 浮動小数点表記の場合、指定したリテラルが表現可能な精度より高い精度を指定している場合は四捨五入される。decimal型は28~29桁の有効精度を持っており、10の累乗でスケールされた整数として表現している。そのため、float型やdouble型では無限小数となるような0.1といった値を正確に表現できる。

 char型は整数型と同じ分類になるが、Unicode文字セットに属する文字を表現するのに利用する。\xでエスケープすることで、Unicodeのコード値でリテラル表記できる。

 また、C# 7.0より桁区切りとしてアンダースコア_が利用可能となった。リテラル表記の数値の先頭と末尾以外であれば、任意の場所に任意の数だけ指定することができる。

 C# 6.0以前より先頭に0xを付けることでリテラル数値を16進表記できていたが、C# 7.0より0bを付けることで2進表記もできるようになった。16進表記や2進表記は桁区切りと併せて利用することもできる。

▲目次に戻る

文字列リテラル逐語的リテラル文字列

C#
class Program
{
  static void Main(string[] args)
  {
    var str01 = "Hello World";   // 文字列リテラル
    var str02 = @"Hello World";  // 逐語的リテラル文字列

    var str03 = "Hello \t World";   // Hello  World
    var str04 = @"Hello \t World";  // Hello \t World

    var str05 = "\"Hello\" World";  // "Hello" World
    var str06 = @"""Hello"" World"; // "Hello" World

    var str07 = "C:\\tmp\\log.txt"; // C:\tmp\log.txt
    var str08 = @"C:\tmp\log.txt";  // C:\tmp\log.txt

    var str09 = "Hello\r\nWorld";
    var str10 = @"Hello
World";
  }
}
リスト2-2 文字列リテラルと逐語的リテラル文字列

 C#では文字列リテラルを二重引用符"でくくって表現する。二重引用符そのものやタブや改行といった一部の制御文字は円記号\でエスケープして表現する。

 一方、二重引用符の前に@を付加した逐語的リテラル文字列では改行や空白などの二重引用符以外の全ての文字を逐語的に(=一語一語、忠実に)解釈する。

▲目次に戻る

文字列補間

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var name = "テスト";
    var s1 = $@"名前は
{name}";

    var s2 = $"Now: {DateTime.Now:f}";
  }
}
リスト2-3 文字列補間

 C# 6.0で文字列補間という機能が導入された。今までstring.Formatメソッドで記述していたフォーマット文字列をリテラルに近い記法で記述できるようになった。逐語的文字列と組み合わせたり、書式を指定したりすることも可能だ。

▲目次に戻る

配列

C#
using System.Collections.Generic;

class Program
{
  static void Main(string[] args)
  {
    // 要素数を指定して配列を初期化
    var array1 = new int[2];
    // 要素を指定して配列を初期化
    var array2 = new[] { 1, 2, 3 };
    // プロパティ、フィールドなど左辺に型を記述する場合、new[]は不要
    double[] array3 = { 1d, 2.3d };
    // 配列はIList<T>を実装している
    IList<string> list3 = new[] { "A", "B" };
    // 2次元配列
    var array5 = new[,] { { 1, 2 }, { 3, 4 } };
    // 配列の配列
    var array6 = new[] { new[] { 1, 3, 5 }, new[] { 2, 4, 6, 8 } };
  }
}
リスト2-4 配列

 配列は、配列の長さを指定する方法と、要素自体を指定する方法で生成できる。要素数を指定した場合、各要素は配列要素に指定した型のデフォルト値で初期化される。また、配列T[]IList<T>インターフェースを実装している。

 C#では多次元配列と、配列の配列(=ジャグ配列とも呼ばれる)は別のものだ。配列の配列の各要素は次元や長さが異なっていても構わない。

▲目次に戻る

演算子

▲目次に戻る

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

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
    }

    // C# 7.0で導入されたis演算子の拡張
    int? i = 1;
    if (i is null) return;  // iがnullのときのみtrueとなる。constant pattern "null"
    if (i is int.MaxValue) return;  // iがint.MaxValueと等しいときのみtrueとなる。constant pattern
    if (i is 3) return;
    if (!(i is int j))  // type pattern "int j"
    {
      //j.Dump();  // is演算子の評価結果がtrueのときのみ変数が割り当てられるので、ここでjを参照するとコンパイルエラー
      return;
    }

    new string('*', j).Dump();  // 上のif文でis演算子の評価結果がfalseのときにreturnもしくは例外をスローせずにiを参照すると、確実に代入される保証がないのでコンパイルエラーになる

    string str = null;
    if (str is var k)  // var patternはnullのときも含め、常にtrueとなり割り当てられる
    {
      k.Dump();
    }
  }
}

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# 7.0からis演算子が拡張されて、constant patterntype patternvar patternの3通りの書き方が追加された。これらの書き方は後に紹介するswitch文の拡張と同じである。

  • constant pattern: is演算子の後ろに定数を記述し、定数と等しいときのみis演算子の結果はtrueとなる
  • type pattern: is演算子の後ろに型と変数を記述する。型に対するis演算子の評価結果がtrueのときのみ、その型にキャストした値が変数に代入される。この変数はtrueのときのみ代入されるため、falseのときにこの変数を参照しようとすると未定義変数への参照となりコンパイルエラーとなる
  • var pattern: nullの場合も含めて常にtrueとなり、変数に値が代入される

▲目次に戻る

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

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で指定した定数と一致したステートメントを実行する制御ステートメントだ。C# 7.0の新機能により拡張されたswitchステートメントと従来のswitchステートメントでは挙動が異なる点がある。そこで、従来の挙動とC# 7.0新機能を分けて順番に説明しよう。

 まず従来switchステートメント機能について説明すると、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拡張といったコンパイラーの拡張機能などで実現することは可能である。

 caseラベルとdefaultラベルは、任意の順番で記述できる。「どの変数の値のときに、どのラベルが実行されるか」という実行順序は記述する順番によらない。このような挙動になる理由は、defaultラベルは必ずどのcaseの値にも一致しないときに実行されることと、caseには定数しか指定できないからだ。詳細な挙動内容はコンパイラーの実装依存ではあるが、基本的には、従来のswitchステートメントとifelseステートメントの両方で同等の処理を記述した場合、switchステートメントによる分岐処理の方が(全てを上から順番に実行しない分だけ)高速になる。

 次にC# 7.0で拡張されたswitchステートメント機能について説明すると、switchには、従来のものに加えて、任意のオブジェクトを返す式も指定できるようになった(従来形式のラベルと併用できる)。また、caseラベルには、従来の定数(いわばis演算子でいうconstant pattern)に加えて、変数宣言(いわばis演算子でいうtype patternvar pattern)や各patternに対してガード節と呼ばれる条件指定も指定できるようになっている。

 caseラベルにtype patternやvar patternによる変数宣言を指定した場合、そのpatternに一致したときのみ、宣言した変数に値が代入され、ラベルのステートメント内で参照できる。

 また、各patternに対してガード節による条件を指定した場合、その条件がtrueのときのみ、そのラベルのステートメントが実行される。ガード節の評価結果がfalseの場合は、次のラベルの評価に移る。

 では、どういったときに、従来とC# 7.0、どちらの機能で挙動することになるのだろうか? これについては、従来のswitchに指定できないオブジェクトが指定されている場合のみ、そのswitchステートメントは拡張されたswitchステートメントとして振る舞うことになる。拡張されたswitchステートメントの挙動で注意すべき点は、前述した従来の挙動とは異なり、caseラベルは上から順番に評価されるということだ。イメージとしては、上から順番にifステートメントが並んでいるのと同等と考えればいいだろう。そのため、分岐処理の速度もifステートメントの連続と同程度になり得る。

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    object shape = new Circle(4);
    switch (shape)
    {
      // C# 6.0以前のcase節
      case 0:
        Console.WriteLine("should not be '0'");
        break;

      // C# 6.0以前のcaseにガード節追加
      case 1 when IsDebug(shape):
        Console.WriteLine("should not be '1' if debug is enabled");
        break;

      // type pattern
      // case Circle: のようにプリミティブでない型をC# 6.0以前のように記述するとコンパイルエラー。変数宣言として記述しないといけない
      case Circle c:
        Console.WriteLine($"circle with radius {c.Radius}");
        break;

      // type patternにガード節
      case Rectangle s when (s.Length == s.Height):
        Console.WriteLine($"{s.Length} x {s.Height} square");
        break;

      // 上のガード節に一致しない場合のtype pattern
      case Rectangle r:
        Console.WriteLine($"{r.Length} x {r.Height} rectangle");
        break;

      // var pattternも利用可能
      case var i when IsDebug(i):
        Console.WriteLine("debug is enabled.");
        break;

      default:
        Console.WriteLine("<unknown shape>");
        break;
    }
  }

  private static bool IsDebug(object i) => i is 1;
}

class Circle
{
  public int Radius { get; set; }
  public Circle(int r)
  {
    Radius = r;
  }
}

class Point
{
  public int X { get; }
  public int Y { get; }
  public Point(int x, int y) { X = x; Y = y; }
}

class Rectangle
{
  public int Height { get; }
  public int Length { get; }
  public Rectangle(int h, int l)
  {
    Height = h;
    Length = l;
  }
}
リスト4-2b C# 7.0で導入されたswitchステートメントの拡張

▲目次に戻る

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ステートメント内部で利用し、すぐ外側のステートメントの新たな繰り返しに制御を移し、処理を継続する。

▲目次に戻る

 以上、今回前編ではC#言語の文法の中でも最もベーシックな「型」「変数」「演算子」「ステートメント(文)」の基本機能をできるだけコンパクトに説明した。次回中編では、オブジェクト指向言語であるC#の要となる「クラス」関連の機能として「名前空間」「クラス(メソッド/プロパティ/イベント/インデクサー/演算子オーバーロード/コンストラクターとデストラクター)」について説明する。

1. C# 基礎文法【6.0対応】 ― 1回完結の最速再入門!

項目を羅列するだけでもかなり長くなってしまうC# 6.0の主要な文法を、実利用者目線でできるだけコンパクトにまとめた。日々のコーディングの「あれ、どう書くんだっけ?」を素早く解決するためのリファレンス。

2. 【現在、表示中】≫ C# 基礎文法 最速再入門【7.0対応】

「あれ、どう書くんだっけ?」を素早く解決するための、C# 7.0主要文法がコンパクトにまとまったリファレンス(全3回)。前編では、C#の歴史/開発ツール/プログラムの実行と制御/型と変数/演算子/ステートメントを説明する。

イベント情報(メディアスポンサーです)

Twitterでつぶやこう!


Build Insider賛同企業・団体

Build Insiderは、以下の企業・団体の支援を受けて活動しています(募集概要)。

ゴールドレベル

  • 日本マイクロソフト株式会社
  • グレープシティ株式会社