C# 6 新機能の概要
C# 言語バージョン 6 は進化し続けることで、定型コードを減らし、わかりやすさと一貫性を高めています。 よりわかりやすい初期化構文、catch/finally ブロックで await を使用する機能、および null 条件 ? 演算子は特に便利です。
Note
最新バージョンの C# 言語 (バージョン 7) については、C# 7.0 の新機能に関する記事を参照してください
このドキュメントでは、C# 6 の新機能について説明します。 これは Mono コンパイラで完全にサポートされており、開発者はすべての Xamarin ターゲット プラットフォームで新機能を使い始めることができます。
C# 6 の新機能のビデオ
C# 6 の使用
C# 6 コンパイラは、最近リリースされたすべての Visual Studio for Mac バージョンで使用されています。
コマンド ライン コンパイラを使用している場合は、mcs --version
によって 4.0 以降が返されることを確認する必要があります。
Visual Studio for Mac ユーザーの場合、Mono 4 (またはそれ以降) がインストールされているかどうかをチェックするには、[Visual Studio for Mac について] > [Visual Studio for Mac] > [詳細の表示] を確認します。
定形コードの削減
using static
列挙体と、System.Math
などの特定のクラスが、静的な値と関数の主要な所有者です。 C# 6 では、1 つの using static
ステートメントを使用して、型のすべての静的メンバーをインポートできます。 C# 5 と C# 6 の一般的な三角関数を比較してみましょう。
// Classic C#
class MyClass
{
public static Tuple<double,double> SolarAngleOld(double latitude, double declination, double hourAngle)
{
var tmp = Math.Sin (latitude) * Math.Sin (declination) + Math.Cos (latitude) * Math.Cos (declination) * Math.Cos (hourAngle);
return Tuple.Create (Math.Asin (tmp), Math.Acos (tmp));
}
}
// C# 6
using static System.Math;
class MyClass
{
public static Tuple<double, double> SolarAngleNew(double latitude, double declination, double hourAngle)
{
var tmp = Asin (latitude) * Sin (declination) + Cos (latitude) * Cos (declination) * Cos (hourAngle);
return Tuple.Create (Asin (tmp), Acos (tmp));
}
}
using static
では、Math.PI
や Math.E
などのパブリック const
フィールドに直接アクセスできません。
for (var angle = 0.0; angle <= Math.PI * 2.0; angle += Math.PI / 8) ...
//PI is const, not static, so requires Math.PI
拡張メソッドを使用した using static
using static
機能の動作は、拡張メソッドを使用すると少し変わってきます。 拡張メソッドは static
を使用して記述されますが、操作するインスタンスがないと意味をなしません。 そのため、拡張メソッドは、その拡張メソッドを定義する型で using static
が使用されている場合に、ターゲット型 (メソッドの this
型) で使用できるようになります。 たとえば、using static System.Linq.Enumerable
を使用すると、すべての LINQ 型を取り込むことなく、IEnumerable<T>
オブジェクトの API を拡張できます。
using static System.Linq.Enumerable;
using static System.String;
class Program
{
static void Main()
{
var values = new int[] { 1, 2, 3, 4 };
var evenValues = values.Where (i => i % 2 == 0);
System.Console.WriteLine (Join(",", evenValues));
}
}
前の例は動作の違いを示しています。つまり、拡張メソッド Enumerable.Where
は配列に関連付けられていますが、静的メソッド String.Join
は、String
型を参照せずに呼び出すことができます。
nameof 式
変数やフィールドに指定した名前を参照したい場合があります。 C# 6 では、nameof(someVariableOrFieldOrType)
は文字列 "someVariableOrFieldOrType"
を返します。 たとえば、ArgumentException
をスローするときは、ほとんどの場合、どの引数が無効であるかを指定する必要があります。
throw new ArgumentException ("Problem with " + nameof(myInvalidArgument))
nameof
式の主な利点は、型チェックされ、ツールによるリファクタリングと互換性があることです。 nameof
式の型チェックは、string
を使って型を動的に関連付ける状況で特に歓迎されます。 たとえば、iOS では、string
を使用して、UITableView
で UITableViewCell
オブジェクトのプロトタイプを作成するときに使用される型を指定します。 nameof
により、スペルミスやずさんなリファクタリングによってこの関連付けが失敗しないことが保証されます。
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell (nameof(CellTypeA), indexPath);
cell.TextLabel.Text = objects [indexPath.Row].ToString ();
return cell;
}
nameof
には修飾名を渡すことができますが、返されるのは最後の要素 (最後の .
の後) のみです。 たとえば、Xamarin.Forms でデータ バインディングを追加できます。
var myReactiveInstance = new ReactiveType ();
var myLabelOld.BindingContext = myReactiveInstance;
var myLabelNew.BindingContext = myReactiveInstance;
var myLabelOld.SetBinding (Label.TextProperty, "StringField");
var myLabelNew.SetBinding (Label.TextProperty, nameof(ReactiveType.StringField));
SetBinding
への 2 つの呼び出しは同じ値を渡しています。nameof(ReactiveType.StringField)
は "StringField"
であり、最初に想定した "ReactiveType.StringField"
ではありません。
null 条件演算子
C# に対する以前の更新プログラムで、null 許容型の概念と null 合体演算子 ??
が導入され、null 許容値を処理するときの定型コードの量が減りました。 C# 6 では、このテーマを引き継ぎ、"null 条件演算子" ?.
を導入しました。 null 条件演算子は、式の右辺のオブジェクトで使用されているときは、オブジェクトが null
でない場合はメンバー値を返し、それ以外の場合は null
を返します。
var ss = new string[] { "Foo", null };
var length0 = ss [0]?.Length; // 3
var length1 = ss [1]?.Length; // null
var lengths = ss.Select (s => s?.Length ?? 0); //[3, 0]
(length0
と length1
は両方とも型 int?
であると推定されます)
前の例の最後の行は、??
null 合体演算子と組み合わせた ?
null 条件演算子を示しています。 新しい C# 6 null 条件演算子は、配列内の 2 番目の要素で null
を返します。その時点で、null 合体演算子が開始され、lengths
配列に 0 を提供します (それが適切かどうかは、もちろん、問題によって異なります)。
null 条件演算子によって、多くのアプリケーションで必要な定型 null チェックの量が大幅に削減されるはずです。
null 条件演算子には、あいまいさによる制限がいくつかあります。 デリゲートを使って行うように、?
の直後に、かっこで囲まれた引数リストを続けることはできません。
SomeDelegate?("Some Argument") // Not allowed
それでも、Invoke
を使えば、引数リストから ?
を切り離すことができ、定型 null
ブロック チェックに比べれば格段に進歩しています。
public event EventHandler HandoffOccurred;
public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
HandoffOccurred?.Invoke (this, userActivity.UserInfo);
return true;
}
文字列補間
String.Format
関数は、従来、書式設定文字列のプレースホルダーとしてインデックスを使用してきました (例: String.Format("Expected: {0} Received: {1}.", expected, received
)。 もちろん、新しい値を追加するときは常に、煩わしい小さなタスクが伴います。たとえば、引数をカウントし、プレースホルダーの番号を変更し、引数リストの正しいシーケンスに新しい引数を挿入しなければなりません。
C# 6 の新しい文字列補間機能により、String.Format
が大幅に向上しました。 プレフィックスが $
の文字列で、変数を直接指定できるようになりました。 次に例を示します。
$"Expected: {expected} Received: {received}."
もちろん、変数はチェックされ、スペルミスや使用できない変数があると、コンパイラ エラーが発生します。
プレースホルダーは単純変数である必要はなく、任意の式を使用できます。 これらのプレースホルダー内では、引用符を、エスケープ "しないで" 使用できます。 たとえば、次の "s"
に注意してください。
var s = $"Timestamp: {DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}"
文字列補間では、String.Format
の配置と書式設定構文がサポートされます。 前に {index, alignment:format}
と記述していたように、C# 6 では {placeholder, alignment:format}
と記述します。
using static System.Linq.Enumerable;
using System;
class Program
{
static void Main ()
{
var values = new int[] { 1, 2, 3, 4, 12, 123456 };
foreach (var s in values.Select (i => $"The value is { i,10:N2}.")) {
Console.WriteLine (s);
}
Console.WriteLine ($"Minimum is { values.Min(i => i):N2}.");
}
}
結果は次のようになります。
The value is 1.00.
The value is 2.00.
The value is 3.00.
The value is 4.00.
The value is 12.00.
The value is 123,456.00.
Minimum is 1.00.
文字列補間は、String.Format
用の簡略された構文です。プレースホルダーが使用されていない場合でも、@""
文字列リテラルでは使用できず、const
とは互換性がありません。
const string s = $"Foo"; //Error : const requires value
文字列補間を使用して関数引数を構築する一般的なユース ケースでは、引き続きエスケープ、エンコード、カルチャの問題に注意する必要があります。 もちろん、SQL クエリと URL クエリのサニタイズは非常に重要です。 String.Format
と同様、文字列補間では CultureInfo.CurrentCulture
が使用されます。 CultureInfo.InvariantCulture
を使用すると、少し冗長になります。
Thread.CurrentThread.CurrentCulture = new CultureInfo ("de");
Console.WriteLine ($"Today is: {DateTime.Now}"); //"21.05.2015 13:52:51"
Console.WriteLine ($"Today is: {DateTime.Now.ToString(CultureInfo.InvariantCulture)}"); //"05/21/2015 13:52:51"
初期化
C# 6 には、プロパティ、フィールド、およびメンバーを指定するための簡潔な方法が多数用意されています。
自動プロパティ初期化
自動プロパティを、フィールドと同じ簡潔な方法で初期化できるようになりました。 不変自動プロパティは、ゲッターのみを使用して記述することができます。
class ToDo
{
public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
public DateTime Created { get; } = DateTime.Now;
コンストラクターでは、ゲッターのみの自動プロパティの値を設定できます。
class ToDo
{
public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
public DateTime Created { get; } = DateTime.Now;
public string Description { get; }
public ToDo (string description)
{
this.Description = description; //Can assign (only in constructor!)
}
この自動プロパティの初期化は、一般的な省スペース機能であると同時に、オブジェクトの不変性を重視したい開発者にとっては朗報です。
インデックス初期化子
C# 6 にはインデックス初期化子が導入されています。これにより、インデクサーを持つ型でキーと値の両方を設定できます。 通常、これは Dictionary
スタイルのデータ構造を対象としています。
partial void ActivateHandoffClicked (WatchKit.WKInterfaceButton sender)
{
var userInfo = new NSMutableDictionary {
["Created"] = NSDate.Now,
["Due"] = NSDate.Now.AddSeconds(60 * 60 * 24),
["Task"] = Description
};
UpdateUserActivity ("com.xamarin.ToDo.edit", userInfo, null);
statusLabel.SetText ("Check phone");
}
式形式の関数メンバー
ラムダ関数にはメリットがいくつかあり、そのうちの 1 つが、単純にスペースを節約できることです。 同様に、式形式のクラス メンバーを使用すると、以前のバージョンの C# 6 よりも少し簡潔に小さな関数を表現することができます。
式形式の関数メンバーでは、従来のブロック構文ではなくラムダ矢印構文が使用されます。
public override string ToString () => $"{FirstName} {LastName}";
ラムダ矢印構文では、明示的な return
が使用されていないことに注意してください。 void
を返す関数の場合は、式もステートメントである必要があります。
public void Log(string message) => System.Console.WriteLine($"{DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}: {message}");
式形式のメンバーは、メソッドでは async
がサポートされるが、プロパティではサポートされない、というルールの対象となります。
//A method, so async is valid
public async Task DelayInSeconds(int seconds) => await Task.Delay(seconds * 1000);
//The following property will not compile
public async Task<int> LeisureHours => await Task.FromResult<char> (DateTime.Now.DayOfWeek.ToString().First()) == 'S' ? 16 : 5;
例外
議論の余地はありません。例外処理を正しく行うのは困難です。 C# 6 の新機能により、例外処理の柔軟性と一貫性が高まります。
例外フィルター
定義上、例外は通常とは異なる状況で発生するため、特定の型の例外が発生する可能性がある "すべて" の状況を論理的に考え、コーディングすることは非常に困難な場合があります。 C# 6 には、ランタイム評価フィルターを使用して実行ハンドラーをガードする機能が導入されています。 これを行うには、通常の catch(ExceptionType)
宣言の後に when (bool)
パターンを追加します。 次の例では、フィルターによって、date
パラメーターに関連する解析エラーが、他の解析エラーと区別されています。
public void ExceptionFilters(string aFloat, string date, string anInt)
{
try
{
var f = Double.Parse(aFloat);
var d = DateTime.Parse(date);
var n = Int32.Parse(anInt);
} catch (FormatException e) when (e.Message.IndexOf("DateTime") > -1) {
Console.WriteLine ($"Problem parsing \"{nameof(date)}\" argument");
} catch (FormatException x) {
Console.WriteLine ("Problem parsing some other argument");
}
}
catch...finally... の await
C# 5 で導入された async
機能によって、この言語が大きく変わりました。 C# 5 では、await
は、catch
ブロックや finally
ブロックでは許可されず、async/await
機能の価値を考えると、頭痛の種となっていました。 C# 6 ではこの制限がなくなり、次のスニペットに示すように、非同期の結果を、プログラムを通して一貫して待つことができます。
async void SomeMethod()
{
try {
//...etc...
} catch (Exception x) {
var diagnosticData = await GenerateDiagnosticsAsync (x);
Logger.log (diagnosticData);
} finally {
await someObject.FinalizeAsync ();
}
}
まとめ
C# 言語は、開発者の生産性を高め、優れたプラクティスとサポート ツールを推進するために進化し続けています。 このドキュメントでは、C# 6 の新しい言語機能の概要と、その使用方法について簡単に説明しました。