C# 7.1:Dev Basics/Keyword
C# 7.1は、C# 7に非同期Mainメソッド、defaultリテラル、タプルの要素名の推測など、若干の機能が追加された「ポイントリリース」。
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# の最新のマイナー バージョン(最新)]を選択すればよい。
あるいはVS Code+.NET Core SDK 2.0などを利用している場合には、プロジェクトファイル(.csprojファイル)を開いて、<PropertyGroup>要素の子要素として<LangVersion>要素を追加して、その値を「7.1」とするか(C# 7.1決め打ち)、「latest」にする(最新のマイナーバージョン)。
以上で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();
}
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メソッドに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>);
…… 省略 ……
}
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
…… 省略 ……
}
ただし、型推測が不可能な場合には省略はできない。上の例では変数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() };
…… 省略 ……
}
上のコードでは、構造体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では、変数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.1では最初のコードのままで、要素名の推測がうまく行われ、「x.x」などとしてタプルの要素にアクセスできるようになっている。ちなみにVS 2017では、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();
}
IfTestメソッドとSwitchTestメソッドは共にパターンマッチを使用して、パラメーターsrcの実際の型に応じた分岐処理を行っている。C# 7ではこうしたコードはコンパイル時にエラーとなっていたが、C# 7.1では問題なくコンパイルできるようになっている。
C# 7.1は、C# 7に対して少々の機能が追加された「ポイントリリース」だ。既にC# 7.2やC# 8.0に関する議論も進められており、今後はC#の進化のペースが速まっていくものと思われる。
参考資料
- What's new in C# 7.1: C# 7.1の新機能について述べられている
- C# 7.1: C# 7.1で追加された各機能のプロポーザルへのリンク
- C# 7の新機能詳説:明瞭なコーディングのために: C# 7で追加された新機能の概要とローカル関数などについて説明した記事
- C# 7の新機能詳説:簡潔なコーディングのために: C# 7で追加されたタプルなどについて説明した記事
- C# 7の新機能詳説:型による分岐の改良: C# 7で追加されたパターンマッチについて説明した記事
Copyright© Digital Advantage Corp. All Rights Reserved.