検索
連載

C# 7.1Dev Basics/Keyword

C# 7.1は、C# 7に非同期Mainメソッド、defaultリテラル、タプルの要素名の推測など、若干の機能が追加された「ポイントリリース」。

Share
Tweet
LINE
Hatena
「Dev Basics/Keyword」のインデックス

連載目次

 C# 7.1はC# 7に対して若干の機能を追加した「ポイントリリース」である(マイナーバージョンを持ったC#のリリースは2003年のC# 1.1以来となる)。Visual Studio 2017 Update 3(以下、VS 2017)および.NET Core SDK 2.0以降でサポートされる。

C# 7.1の新機能

 C# 7.1では以下の機能が追加されている。

  • 非同期Mainメソッド
  • default(T)式の省略表記(defaultリテラル)
  • タプルの要素名の推測

 まず、VS 2017および.NET Core 2.0でC# 7.1を有効にする方法を見た後に、これらの新機能について簡単に見ていこう。

C# 7.1を有効にするには

 VS 2017でC# 7.1を有効にするには、プロジェクトのプロパティウィンドウを表示して、[ビルド]タブにある[詳細設定]ボタンをクリックする。すると、次のようなダイアログが表示されるので、[言語バージョン]ドロップダウンで[C# 7.1]か[C# の最新のマイナー バージョン(最新)]を選択すればよい。

C# 7.1を有効にする
C# 7.1を有効にする

 あるいはVS Code+.NET Core SDK 2.0などを利用している場合には、プロジェクトファイル(.csprojファイル)を開いて、<PropertyGroup>要素の子要素として<LangVersion>要素を追加して、その値を「7.1」とするか(C# 7.1決め打ち)、「latest」にする(最新のマイナーバージョン)。

VS Codeで.csprojファイルを編集し、C# 7.1を有効にしているところ(赤枠内)
VS Codeで.csprojファイルを編集し、C# 7.1を有効にしているところ(赤枠内)

 以上でC# 7.1で追加された機能を利用してコードを記述できるようになる。では、3つの新機能を見ていくことにしよう。

非同期Mainメソッド

 これまでは、Mainメソッド(プログラムのエントリポイント)を非同期メソッドにすることはできなかったが、C# 7.1ではこれをTask/Task<int>を戻り値型とするasyncメソッドとして記述できるようになった。

 コンソールアプリプロジェクトを作成して、非同期処理を実際に試してみようと思うと、これまでは例えば次のようなコードを記述しなければならなかった(他にも多くの記述方法はあるが、ここではシンプルなコード例を紹介している)。

static async Task DoSomethingAcync()
{
  Console.WriteLine("sleep");
  await Task.Delay(5000);
  Console.WriteLine("waken!");
}

static void Main(string[] args)
{
  Console.WriteLine("begin");
  Task t = DoSomethingAcync();
  t.Wait();
  Console.WriteLine("end");
  Console.ReadKey();
}

非同期メソッドをMainメソッドから呼び出す例

 async/awaitキーワードはMainメソッドでは使わずに、TaskクラスとそのWaitメソッドを利用して、非同期処理が完了するのを自前で待機するようにしている。これをC# 7.1の非同期Mainメソッドを使うように書き換えたものが以下だ。

static async Task DoSomethingAcync()
{
  …… 省略(上と同様) ……
}

static async Task Main(string[] args)
{
  Console.WriteLine("begin");
  await DoSomethingAcync();
  Console.WriteLine("end");
  Console.ReadKey();
}

非同期Mainメソッドの記述例

 このように、Mainメソッドにasyncキーワードを指定して、その戻り値をTask型としている。メソッド本体ではawaitキーワードを使って、非同期処理を待機できる。ここではMainメソッドは戻り値を戻さない(void)なのでTaskクラスを使用しているが、処理の成功/失敗を示す数値を返す場合にはTask<int>型を戻り値型とする。

 つまり、これまでのエントリポイントに加えて、C# 7.1では以下のエントリポイントが有効になったといえる。

  • static Task Main()
  • static Task Main(string[] args)
  • static Task<int> Main()
  • static Task<int> Main(string[] args)

 そして、Mainメソッドの戻り値型がTask/Task<int>のいずれかである場合には、asyncキーワードでMainメソッドを修飾してもよい。よって、上のコード例では「static async Task Main(string[] args)」というシグネチャのエントリポイントが使われている。

 詳細については「Main() とコマンドライン引数」を参照されたい。

default(T)式の省略表記(defaultリテラル)

 C#ではある型Tの既定値を得るのに、「default(T)」と記述できる。この式で得られるのは0初期化された値である(0、false、nullなど)。

static void Main()
{
  int i = default(int);
  IList<int> list = default(IList<int>);

  …… 省略 ……
}

default(T)式

 C# 7.1では、代入式の左辺で型指定が行われているなど、型推測が可能である場合には「default(T)」を「default」と省略して表記できるようになっている。これをC# 7.1では「defaultリテラル」などと呼んでいる。

 例えば、上のコードは次のように記述できる。

static void Main()
{
  int i = default;
  IList<int> list = default;
  var j = default// これは型が分からないのでエラーとなる
  var k = default(int);  // これはOK

  …… 省略 ……
}

default(T)式の省略表記

 ただし、型推測が不可能な場合には省略はできない。上の例では変数jはvarキーワードを使って宣言されているが、これだと型が推測できないので省略表記はできない(変数kはdefault(int)としているので、int型の既定値で初期化される)。

 defaultリテラルは変数初期化子、変数代入、オプション引数の既定値の指定、メソッド呼び出し時の引数、return文でのメソッドの戻り値などに記述できる。以下に例を示す。

struct LongLongNamedStruct
{
  …… 省略 ……
}

// オプション引数の既定値
static void SomeMethod(LongLongNamedStruct s = default) {
  …… 省略 ……
}

static LongLongNamedStruct GetLongLongNamedStruct()
{
  …… 省略 ……
  return default// return文で既定値を返送
}

static void Main()
{
  SomeMethod(default);  // メソッド呼び出しの引数として既定値を渡す
  // 初期化子としてdefaultリテラルを使用
  var array = new LongLongNamedStruct[] { default, new LongLongNamedStruct() };

  …… 省略 ……
}

defaultリテラルの使用例

 上のコードでは、構造体LongLongNamedStructを定義し、その型を利用したメソッドのパラメーター、戻り値、メソッド呼び出し時の引数、LongLongNamedStruct型の配列の初期化などでdefaultリテラルを使用している。defaultリテラルを使えないと、これは「default(LongLongNamedStruct)」と書かねばならない。defaultリテラル(と型推測)のおかげで、C# 7.1では簡潔な記述が可能になったということだ。

 詳細については「既定の値式」を参照されたい。

タプルの要素名の推測

 C# 7.1ではタプルの要素名をうまく推測してくれるようになった。例えば、以下に示すコードは、C# 7ではエラーとなる。

static void Main()
{
  var a = new { x = 1, y = 2 }; // anonymous type
  var b = new { m = 100, n = 200 };
  var x = (a.x, b.n);
  Console.WriteLine(x.x);

  …… 省略 ……
}

このコードはC# 7ではエラーとなる

 C# 7では、変数xの値であるタプルの要素にアクセスするには、x.Item1やx.Item2などと記述しなければならない。あるいは、タプルを変数xに代入する際に「(x: a.x, n: b.n)」のように要素名を明記することで、「x.x」のようにしてタプルの要素にアクセスできるようになる。そのように修正したコードを以下に示す。

static void Main()
{
  var a = new { x = 1, y = 2 };
  var b = new { m = 100, n = 200 };
  var x = (x: a.x, n: b.n);    // タプルの要素名を明記
  Console.WriteLine(x.x);
  var y = (a.y, b.m);
  Console.WriteLine(y.Item1);  // ItemXという要素名でアクセス

  …… 省略 ……
}

C# 7でエラーが出ないようにしたコード

 これに対して、C# 7.1では最初のコードのままで、要素名の推測がうまく行われ、「x.x」などとしてタプルの要素にアクセスできるようになっている。ちなみにVS 2017では、C# 7.1が有効になっていないと次のようなメッセージが表示される。

C# 7.1が有効でない場合、要素名を推測できるが、その名前でアクセスするにはC# 7.1以上が必要とのメッセージが表示される
C# 7.1が有効でない場合、要素名を推測できるが、その名前でアクセスするにはC# 7.1以上が必要とのメッセージが表示される

 詳細については「Inferred tuple element names」を参照されたい。

 この他にも、ジェネリクス型として与えられたオブジェクトに対するパターンマッチも可能になっている。簡単な例だけを示しておこう。

static void IfTest<T>(T src)
{
  if (src is string s)
    Console.WriteLine($"string: {s}");
  else if (src is int n)
    Console.WriteLine($"int: {n}");
  else
    Console.WriteLine($"other {src.ToString()}");
}

static void SwitchTest<T>(T src)
{
  switch (src)
  {
    case int n:
      Console.WriteLine($"int: {n}");
      break;
    case string s:
      Console.WriteLine($"string: {s}");
      break;
    default:
      Console.WriteLine($"other {src.ToString()}");
      break;
  }
}

static void Main()
{
  IfTest(new int[] { 1, 2, 3});
  SwitchTest("insider.net");
  Console.ReadKey();
}

C# 7.1ではジェネリクス型として渡されたオブジェクトについてもパターンマッチが可能

 IfTestメソッドとSwitchTestメソッドは共にパターンマッチを使用して、パラメーターsrcの実際の型に応じた分岐処理を行っている。C# 7ではこうしたコードはコンパイル時にエラーとなっていたが、C# 7.1では問題なくコンパイルできるようになっている。


 C# 7.1は、C# 7に対して少々の機能が追加された「ポイントリリース」だ。既にC# 7.2やC# 8.0に関する議論も進められており、今後はC#の進化のペースが速まっていくものと思われる。

参考資料


「Dev Basics/Keyword」のインデックス

Dev Basics/Keyword

Copyright© Digital Advantage Corp. All Rights Reserved.

ページトップに戻る