Edited at

C# 7.0 の新機能

More than 1 year has passed since last update.


はじめに

この記事は、MSDN .NET Blog に出ていた内容について簡単にまとめたものです。今回の機能強化は主に便利機能が中心です。サンプルは独自に作り動作確認を行ってみました。

New Features in C# 7.0

このブログによると、新機能は以下の通りです。


  • Out variables

  • Is-expressions with patterns

  • Switch statements with patterns

  • Tuples

  • Deconstruction

  • Local functions

  • Literal improvements

  • Ref returns and locals

  • Generalized async return types

  • More expression bodied members

  • Throw expressions


Out 変数

out パラメータを持つメソッドを呼び出すときに、同時に変数宣言ができるという優れものです。従来は、別に変数宣言を行う必要がありました。

static void testOutVariables(string str)

{
// 変数 n をメソッド呼び出し時に宣言できる。
if (Int32.TryParse(str, out int n))
{
Console.WriteLine(n);
}
else
{
Console.WriteLine("整数に変換できません。");
}
}


is 式の強化

これも便利機能の一種です。is が成立した場合、宣言した変数に同時に代入することができます。

/// <summary>

/// is 演算子の強化
/// </summary>
static void testIsExpression()
{
object o = 101;

// o が整数型であったら int i に整数として格納
if (o is int i)
{
i++;
Console.WriteLine("結果 {0}\n", i);
}
else
{
Console.WriteLine("整数に変換できません。\n");
}
}


Switch 文の強化

case のところに型判別と変数宣言ができるようになりました。

/// <summary>

/// switch 文の強化
/// </summary>
static void testSwitch()
{
// ここの代入値を変更してみる。
object o = "3";

switch (o)
{
case int i:
Console.WriteLine("整数 {0}", i);
break;

case char c:
Console.WriteLine("文字 {0}", c);
break;

case float f:
Console.WriteLine("単精度浮動小数点数 {0}", f);
break;

case string s when (s.Length > 0):
Console.WriteLine("空でない文字列 {0}", s);
break;

default:
Console.WriteLine("サポートしていないです。");
break;
}
}


タプル (Tuples)

今回の強化点で最も大きい項目がタプル関連機能の強化だそうです。従来のバージョンでも Tuple クラスを使ってタプル機能は利用できました。ただ、これだと使い勝手が悪く、あえてタプルを使わなくても他の方法で目的の機能を実現できたのであまり使われていなかったようです。

今回のタプル機能強化で簡単な文法でタプルを使えるようになったので、これまで以上に使われるようになるかもしれませんね。

(注意) この機能を使うためには、NuGet で System.ValueTuple をインストールする必要があり、これを怠ると文法エラーになります。

/// <summary>

/// タプル
/// </summary>
/// <remarks>NuGet で "System.ValueTuple" をインストールすること。</remarks>
static void testTuple()
{
Tuple<string, float> tower1 = getHeight("通天閣");
Console.WriteLine("名前:{0} 高さ:{1}", tower1.Item1, tower1.Item2);

(string, float) tower = lookupHeight("東京タワー");
Console.WriteLine("名前:{0} 高さ:{1}", tower.Item1, tower.Item2);

(string name, float height) tower2 = lookupHeight("東京スカイツリー");
Console.WriteLine("名前:{0} 高さ:{1}", tower2.name, tower2.height);
}

/// <summary>
/// 従来の Tuple 型の使用例
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
static Tuple<string, float> getHeight(string name)
{
Tuple<string, float> tower;

switch (name)
{
case "東京タワー":
tower = new Tuple<string, float>(name, 333.0f);
break;

case "東京スカイツリー":
tower = new Tuple<string, float>(name, 634.0f);
break;

case "通天閣":
tower = new Tuple<string, float>(name, 108.0f);
break;

default:
tower = new Tuple<string, float>("unknown", 0.0f);
break;
}

return tower;
}

/// <summary>
/// 新しい Tuple の使用例
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
static (string, float) lookupHeight(string name)
{
(string, float) tower;

switch (name)
{
case "東京タワー":
tower = (name, 333.0f);
break;

case "東京スカイツリー":
tower = (name, 634.0f);
break;

case "通天閣":
tower = (name, 108.0f);
break;

default:
tower = ("unknown", 0.0f);
break;
}

return tower;
}


Deconstruction (タプル要素の使用)

前のタプルのサンプルと似ていますが、タプル変数を宣言しなくても、タプルのメンバーに直接代入できます。

/// <summary>

/// タプル要素の使用
/// </summary>
static void testDeconst()
{
(string name, float height) = lookupHeight("東京スカイツリー");
Console.WriteLine($"name: {name}, height: {height}");
}


ローカル関数

従来の C# でも、匿名メソッドをデリゲート変数に代入することにより、ローカル関数を実現できましたが、今回の強化でずばりメソッド内に関数を記述できるようになりました。そのメソッド内だけでしか使わない関数は、ローカル関数にした方がわかりやすいですね。

delegate double FuncHypo(double x, double y);

/// <summary>
/// ローカル関数
/// </summary>
static void testLocalFunc()
{
// ラムダ式で定義したローカル関数
FuncHypo Hypo = (double x, double y) => Math.Sqrt(x * x + y * y);

// ローカル関数(新機能)
double hypotenuse(double x, double y)
{
return Math.Sqrt(x * x + y * y);
}

double Y = hypotenuse(1.0, 0.5);
Console.WriteLine($"1 斜辺の長さ {Y}");
Y = Hypo(1.0, 0.5);
Console.WriteLine($"2 斜辺の長さ {Y}");
}


リテラルの改善

リテラル内に _ を記述して見やすくすることができるようになりました。長い2進数などは、これがないと非常にわかりづらいですね。

/// <summary>

/// リテラルの改善
/// </summary>
static void testImproveLiteral()
{
var b = 0b1010_1011_1100_1101_1110_1111;
Console.WriteLine("{0:x}", b);
}


参照を返す関数

関数の戻り値として「値」でなく「参照」を返せるようになりました。ただ、あまり使う機会はないような気がしますが。

/// <summary>

/// Ref の戻り値
/// </summary>
static void testRefReturn()
{
int[] arry = { 5, 3, 4, 0 };

// 3 の参照が返される。
ref int p = ref findIt(3, arry);
Console.WriteLine(p);

// 参照なので p を変更すれば配列の要素が変更される。
p = -1;
Console.WriteLine(arry[1]);
}

/// <summary>
/// 見つかった配列の要素でなく、その参照を返す関数
/// </summary>
/// <param name="n">探す値</param>
/// <param name="arry">探す対象の配列</param>
/// <returns><参照/returns>
static ref int findIt(int n, int[] arry)
{
var i = 0;
while (i != arry.Length)
{
if (arry[i] == n)
{
return ref arry[i];
}
i++;
}

throw new Exception("見つかりませんでした。");
}


async 関数の戻り値の制限撤廃

async が付いた関数の戻り値は void, Task, Task&ltT&gt: のどれかである必要があります。この制限をなくすような強化点のようですが、実際にはまだ実装されていない(?)ようで、.NET Blog でもサンプルが出ていません。現在のバージョンでも下のサンプルのように整数を返すような書き方ができるので(Task<int> を返している)、実装されていなくても関係ないと言えば関係ないですが。

(注意)コメント欄参照。

/// <summary>

/// async 関数の改善 (実装されていないようなので従来の記述例)
/// </summary>
static async void testAsyncFunc()
{
var sr = new StreamReader(@"C:\temp\Hcg.sql");
char[] buffer = new char[0x1000];
int n = await sr.ReadAsync(buffer, 0, buffer.Length);
System.Threading.Thread.Sleep(10);
Console.WriteLine($"\n n = {n}");
//return n;
}


クラスの宣言で => が使えるようになった

プロパティの宣言などで => を使って式を書けるようになりました。

/// <summary>

/// クラスメンバーなどの書き方改善
/// </summary>
static void testBodiedMember()
{
var o = new Class1();
o.Name = "CLASS";
Console.WriteLine(o.Name);
Console.WriteLine(o.Length);
}


Class1.cs

using System;

namespace NewFeatures
{
class Class1
{
private string name;

// => を使ってメソッド本体を書けるようになった。
public string Name
{
get => name == null ? "NULL" : name;
set => name = value;
}

// Read Only の場合はこういう書き方ができる。
public int Length => 0;

// ラムダ式のように throw を書ける。
public int getValue() => throw new NotImplementedException();
}
}



Throw 式

前のサンプル (Class1) でラムダ式のように throw を書いていますが、それが正しく動作するかのサンプルが次のコードです。

/// <summary>

/// throw の改善
/// </summary>
static void testThrow()
{
var o = new Class1();
try
{
o.getValue();
}
catch (NotImplementedException )
{
Console.WriteLine("NotImplementedException");
}
}


完全なサンプル

前のサンプルの全体は以下のようになっています。


Program.cs

using System;

using System.IO;

namespace NewFeatures
{
/// <summary>
/// C# 7.0 New Features
/// </summary>
/// <remarks>NuGet で System.ValueTuple をインストールすること。</remarks>
class Program
{
/// <summary>
/// メイン
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
int n;
string[] menu = { " 1. out 変数", " 2. is 式の強化", " 3. switch 文の強化", " 4. タプル", " 5. タプル要素の使用",
" 6. ローカル関数", " 7. リテラルの改善", " 8. ref の戻り値", " 9. async 関数の戻り値 (未実装?)", "10. クラスメンバーなどの書き方改善",
"11. throw の改善" };

while (true)
{
Console.WriteLine("\tC# 7.0 の新機能\n");
foreach (var mi in menu)
{
Console.WriteLine(mi);
}

// 番号を入力。
Console.Write("\n> ");
var ns = Console.ReadLine();

if (Int32.TryParse(ns, out n))
{
switch (n)
{
case 1:
// out 変数
testOutVariables();
break;

case 2:
// is 式の強化
testIsExpression();
break;

case 3:
// switch 文の強化
testSwitch();
break;

case 4:
// タプル
testTuple();
break;

case 5:
// タプル要素の使用
testDeconst();
break;

case 6: // ローカル関数
testLocalFunc();
break;

case 7: // リテラルの改善
testImproveLiteral();
break;

case 8: // Ref の戻り値
testRefReturn();
break;

case 9: // async 関数の戻り値
testAsyncFunc();
break;

case 10: // クラスメンバーなどの書き方改善
testBodiedMember();
break;

case 11: // throw の改善
testThrow();
break;

default:
break;
}
}
else
{
Console.WriteLine("番号が正しくありません。");
break;
}
}
}

/// <summary>
/// out 変数
/// </summary>
static void testOutVariables()
{
Console.Write("= ");
string str = Console.ReadLine();

if (Int32.TryParse(str, out int n))
{
Console.WriteLine($"n = {n}");
}
else
{
Console.WriteLine("整数に変換できません。\n");
}
}

/// <summary>
/// is 演算子の強化
/// </summary>
static void testIsExpression()
{
object o = 101;

if (o is int i)
{
i++;
Console.WriteLine("結果 {0}\n", i);
}
else
{
Console.WriteLine("整数に変換できません。\n");
}
}

/// <summary>
/// switch 文の強化
/// </summary>
static void testSwitch()
{
// ここの代入値を変更してみる。
object o = "3";

switch (o)
{
case int i:
Console.WriteLine("整数 {0}", i);
break;

case char c:
Console.WriteLine("文字 {0}", c);
break;

case float f:
Console.WriteLine("単精度浮動小数点数 {0}", f);
break;

case string s when (s.Length > 0):
Console.WriteLine("空でない文字列 {0}", s);
break;

default:
Console.WriteLine("サポートしていないです。");
break;
}
}

/// <summary>
/// タプル
/// </summary>
/// <remarks>NuGet で "System.ValueTuple" をインストールすること。</remarks>
static void testTuple()
{
Tuple<string, float> tower1 = getHeight("通天閣");
Console.WriteLine("名前:{0} 高さ:{1}", tower1.Item1, tower1.Item2);

(string, float) tower = lookupHeight("東京タワー");
Console.WriteLine("名前:{0} 高さ:{1}", tower.Item1, tower.Item2);

(string name, float height) tower2 = lookupHeight("東京スカイツリー");
Console.WriteLine("名前:{0} 高さ:{1}", tower2.name, tower2.height);
}

/// <summary>
/// 従来の Tuple 型の使用例
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
static Tuple<string, float> getHeight(string name)
{
Tuple<string, float> tower;

switch (name)
{
case "東京タワー":
tower = new Tuple<string, float>(name, 333.0f);
break;

case "東京スカイツリー":
tower = new Tuple<string, float>(name, 634.0f);
break;

case "通天閣":
tower = new Tuple<string, float>(name, 108.0f);
break;

default:
tower = new Tuple<string, float>("unknown", 0.0f);
break;
}

return tower;
}

/// <summary>
/// 新しい Tuple の使用例
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
static (string, float) lookupHeight(string name)
{
(string, float) tower;

switch (name)
{
case "東京タワー":
tower = (name, 333.0f);
break;

case "東京スカイツリー":
tower = (name, 634.0f);
break;

case "通天閣":
tower = (name, 108.0f);
break;

default:
tower = ("unknown", 0.0f);
break;
}

return tower;
}

/// <summary>
/// タプル要素の使用
/// </summary>
static void testDeconst()
{
(string name, float height) = lookupHeight("東京スカイツリー");
Console.WriteLine($"name: {name}, height: {height}");
}

delegate double FuncHypo(double x, double y);

/// <summary>
/// ローカル関数
/// </summary>
static void testLocalFunc()
{
// ラムダ式で定義したローカル関数
FuncHypo Hypo = (double x, double y) => Math.Sqrt(x * x + y * y);

// ローカル関数(新機能)
double hypotenuse(double x, double y)
{
return Math.Sqrt(x * x + y * y);
}

double Y = hypotenuse(1.0, 0.5);
Console.WriteLine($"1 斜辺の長さ {Y}");
Y = Hypo(1.0, 0.5);
Console.WriteLine($"2 斜辺の長さ {Y}");
}

/// <summary>
/// リテラルの改善
/// </summary>
static void testImproveLiteral()
{
var b = 0b1010_1011_1100_1101_1110_1111;
Console.WriteLine("{0:x}", b);
}

/// <summary>
/// Ref の戻り値
/// </summary>
static void testRefReturn()
{
int[] arry = { 5, 3, 4, 0 };

// 3 の参照が返される。
ref int p = ref findIt(3, arry);
Console.WriteLine(p);

// 参照なので p を変更すれば配列の要素が変更される。
p = -1;
Console.WriteLine(arry[1]);
}

/// <summary>
/// 見つかった配列の要素でなく、その参照を返す関数
/// </summary>
/// <param name="n">探す値</param>
/// <param name="arry">探す対象の配列</param>
/// <returns></returns>
static ref int findIt(int n, int[] arry)
{
var i = 0;
while (i != arry.Length)
{
if (arry[i] == n)
{
return ref arry[i];
}
i++;
}

throw new Exception("見つかりませんでした。");
}

/// <summary>
/// async 関数の改善
/// </summary>
static async void testAsyncFunc()
{
var sr = new StreamReader(@"C:\temp\Hcg.sql");
char[] buffer = new char[0x1000];
int n = await sr.ReadAsync(buffer, 0, buffer.Length);
System.Threading.Thread.Sleep(10);
Console.WriteLine($"\n n = {n}");
//return n;
}

/// <summary>
/// クラスメンバーなどの書き方改善
/// </summary>
static void testBodiedMember()
{
var o = new Class1();
o.Name = "CLASS";
Console.WriteLine(o.Name);
Console.WriteLine(o.Length);
}

/// <summary>
/// throw の改善
/// </summary>
static void testThrow()
{
var o = new Class1();
try
{
o.getValue();
}
catch (NotImplementedException )
{
Console.WriteLine("NotImplementedException");
}
}
}
}



Class1.cs

using System;

namespace NewFeatures
{
class Class1
{
private string name;

// => を使ってメソッド本体を書けるようになった。
public string Name
{
get => name == null ? "NULL" : name;
set => name = value;
}

// Read Only の場合はこういう書き方ができる。
public int Length => 0;

// ラムダ式のように throw を書ける。
public int getValue() => throw new NotImplementedException();
}
}


実行例

Running.png