アウト変数
out変数の使用は、現在のように簡単ではありません。 out引数を使用してメソッドを呼び出す前に、このメソッドに渡される変数を宣言する必要があります。 通常、これらの値は宣言中にこれらの変数に割り当てられないため(論理的です-メソッドによって引き続き上書きされます)、 varキーワードは使用できません。 タイプを示す変数を宣言する必要があります。
public void PrintCoordinates(Point p) { int x, y; // p.GetCoordinates(out x, out y); WriteLine($"({x}, {y})"); }
C#7では、メソッド呼び出しで変数をすぐに宣言できるout変数が追加されています。
public void PrintCoordinates(Point p) { p.GetCoordinates(out int x, out int y); WriteLine($"({x}, {y})"); }
このような変数のスコープは外部ブロックです。そのため、メソッド呼び出しに続く式で変数を使用できます。
注:プレビュー4では、スコープに対してより厳しい制限があります。out変数は、定義された式内でのみ使用できます。 したがって、上記の例が機能するためには、次のリリースを待つ必要があります。
out変数はメソッド引数としての転送と同じ式で宣言されているため、コンパイラはその型を推測できます(このメソッドに競合するオーバーロードがない場合)。したがって、型の代わりにvarキーワードを使用できます。
p.GetCoordinates(out var x, out var y);
出力引数は、 Try ...メソッドファミリで広く使用されています。戻り値のブール値は操作の成功を示し、出力引数には結果の値が含まれます。
public void PrintStars(string s) { if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); } else { WriteLine(" - !"); } }
注:この例では 、 iは定義されているifブロック内でのみ使用されるため、プレビュー4ではこの例も機能します。
可能な改善の1つ(必ずしもC#7に該当するわけではありません)は、後で使用されないoutパラメーターの代わりにワイルドカード(*)を使用することです。 例:
p.GetCoordinates(out int x, out *); // x
パターンマッチング
C#の7番目のバージョンでは、 パターンの概念が表示されます。これは、一般的な場合、変数が特定のパターンに一致するかどうかを確認し、一致する場合はそこから情報を抽出できる構文構造です。
C#7には次のテンプレートがあります。
- 定数パターン : c (cはC#の定数式); 変数がこの定数と等しいかどうかを確認してください。
- 型テンプレート : T x (Tは型、xは可変); 変数の型がTであるかどうかをチェックし、ある場合は、その値をT型の新しい変数xに抽出します。
- varパターン : var x (xは変数); このテンプレートは常にtrueと評価され、同じ型で同じ値を持つ新しい変数を作成するために使用されます。
これはほんの始まりに過ぎず、将来的にはC#で新しいテンプレートを確実に追加する予定です。 テンプレートをサポートするために、2つの既存の言語構成が変更されました。
- isは 、タイプだけでなく、テンプレートでも(正しい引数として)使用できるようになりました。
- switchステートメントの caseは、定数だけでなくパターンを使用できるようになりました。
将来的には、テンプレートを使用するためのオプションをさらに追加する予定です。
あるテンプレート
定数パターンと型パターンの両方を使用する簡単な例を考えてみましょう。
public void PrintStars(object o) { if (o is null) return; // "null" if (!(o is int i)) return; // "int i" WriteLine(new string('*', i)); }
例からわかるように、テンプレート変数(テンプレートで宣言された)はout変数と同じスコープを持っているため、外部の可視性ブロック内で使用できます。
注:プレビュー4では、テンプレート変数および出力変数に、より厳密な可視性ルールが適用されるため、この例は次のリリースでのみ機能します。
テンプレートとTryメソッドは一緒に使用できます。
if (o is int i || (o is string s && int.TryParse(s, out i)) { /* i int */ }
パターンと表現を切り替える
スイッチの使用例が拡張され、次のことができるようになりました。
- (プリミティブだけでなく)任意のタイプを使用します。
- ケース式でパターンを使用します。
- ケース式に条件を追加します( whenキーワードを使用)。
次に例を考えてみましょう。
switch(shape) { case Circle c: WriteLine($" {c.Radius}"); break; case Rectangle s when (s.Length == s.Height): WriteLine($"{s.Length} x {s.Height} "); break; case Rectangle r: WriteLine($"{r.Length} x {r.Height} "); break; default: WriteLine("< >"); break; case null: throw new ArgumentNullException(nameof(shape)); }
新しい高度なスイッチの次の機能に注意してください。
- ケース式の順序が重要になりました。 これで、一致ロジックはcatch式の場合と同じになります。条件を満たしている最初の順序式が選択されます。 したがって、この例では、四角形のより具体的な条件が四角形のより一般的な条件の前に来ることが重要です。それらを交換すると、四角形の条件は機能しなくなります。 そのような場合、コンパイラーが助けになり、明示的な到達不能条件にフラグを立てます( catchの場合と同じ)。 この変更は、既存の動作の変更ではありません。C#7より前では、 case式の実行順序は定義されていませんでした。
- デフォルトの条件は常に最後に評価されます。 ヌル条件はその後に来ますが、デフォルト条件はその後にチェックされます。 これは既存のロジックをサポートするために行われましたが、デフォルトの条件を最後にした方がいいでしょう。
- 最後のヌル条件に到達できます。 これは、型パターンがis演算子の現在のロジックに従い、 nullに対して機能しないためです。 この動作により、 nullは最初のタイプのテンプレートと一致しません。 テンプレートを明示的に指定するか、無音状態のロジックを残す必要があります。
caseで宣言されたテンプレート変数のスコープはswitchステートメントです。
タプル
メソッドから複数の値を返したい場合があります。 現在利用可能な方法はどれも最適に見えません:
- 出力パラメータ:構文はオーバーロードされているように見えます(上記のイノベーションを使用しても)、非同期メソッドには適用されません。
- System.Tuple <...>:繰り返しますが、冗長に見え、追加のオブジェクトの作成が必要です。
- そのような場合ごとに個別のクラス:一時的に複数の値をグループ化することが唯一の目的である型には、コードが多すぎます。
- 動的オブジェクト:パフォーマンスの低下とコンパイル時の型チェックの欠如。
このタスクを簡素化するために、タプルとタプルリテラルがC#7に追加されました。
(string, string, string) LookupName(long id) // - { ... // return (first, middle, last); // }
現在、このメソッドはタプルに結合された3つの行を返します。 呼び出しコードは次のようにそれらを使用できます。
var names = LookupName(id); WriteLine($" {names.Item1} {names.Item3}.");
フィールド名Item1、Item2、...は各タプルのデフォルト名ですが、タプルにマージされたデータをより適切な名前にすることができます。
(string first, string middle, string last) LookupName(long id) //
これで、タプル要素に次のようにアクセスできます。
var names = LookupName(id); WriteLine($" {names.first} {names.last}.");
また、要素名はタプルリテラルですぐに指定できます。
return (first: first, middle: middle, last: last); //
要素の名前が一致しない場合、タプルを互いに割り当てることができます。主なことは、要素自体を互いに割り当てることができるということです。 将来的には、主にタプルリテラルに対して制限が追加され、要素名が誤ってスワップされるなどのエラーを通知します。 Preview 4にはまだそのような制限はありません。
タプルは意味のあるタイプであり、その要素は可変のオープンフィールドです。 タプルは等しいかどうかを比較できます。すべての構成要素がペアで互いに等しい(および同じハッシュコードを持つ)場合、2つのタプルは等しい(および同じハッシュコードを持つ)。 この動作により、メソッドから複数の値を返すだけでなく、タプルが便利になります。 たとえば、複合キーを持つ辞書が必要な場合は、タプルをキーとして使用します。 各位置に複数の値があるはずのリストが必要な場合、タプルのリストも使用します。 (翻訳者から:100%の状況でタプルを使用するためのガイドとしてこれを受け取らないでください。時には、いくつかのプロパティを持つ単純なクラスが意図をよりよく表現し、将来維持しやすくなります)。
注:作業中のタプルは、まだプレビュー4にないタイプに依存していますが、NuGetを使用してプロジェクトに追加できます(「プレリリースを含める」を選択し、「パッケージソース」として「nuget.org」を指定することを忘れないでください)、パッケージSystem.ValueTupleと呼ばれます 。
タプルの展開
タプルを使用する別の方法は、タプルをアンパックすることです。これは、要素を新しい変数に割り当てることで構成されます。
(string first, string middle, string last) = LookupName(id1); // deconstructing declaration WriteLine($" {first} {last}.");
各変数のタイプの代わりにvarキーワードを使用することもできます。
(var first, var middle, var last) = LookupName(id1); // var
または、括弧の前にvarを入れます:
var (first, middle, last) = LookupName(id1); // var
タプルを既に宣言されている変数にアンパックすることもできます。
(first, middle, last) = LookupName(id2); //
タプルだけでなく、どのタイプでもアンパックできます。 これを行うには、次の形式のメソッドが必要です。
public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }
出力パラメーターは、アンパックの結果として割り当てられる値に対応します。 タプルの代わりに出力パラメーターが使用されるのはなぜですか? そのため、パラメーターの数が異なる複数のメソッドオーバーロードを使用できます。
class Point { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } public void Deconstruct(out int x, out int y) { x = X; y = Y; } } (var myX, var myY) = GetPoint(); // Deconstruct(out myX, out myY);
このアプローチにより、「対称」コンストラクターと展開メソッドを作成できます。
out変数と同様に、返されたパラメーターの一部を無視するワイルドカードを追加する予定です。
(var myX, *) = GetPoint(); // myX
注: C#7でワイルドカードが追加されるかどうかはまだ不明です。
ローカル機能
補助関数は、呼び出される1つのメソッド内でのみ意味をなす場合があります。 これで、そのような関数をメソッド内で宣言できます。
public int Fibonacci(int x) { if (x < 0) throw new ArgumentException(" !", nameof(x)); return Fib(x).current; (int current, int previous) Fib(int i) { if (i == 0) return (1, 0); var (p, pp) = Fib(i - 1); return (p + pp, p); } }
外部メソッドの引数とそのローカル変数は、ラムダ式だけでなくローカル関数でも使用できます。
別の例として、反復子として実装されたメソッドを考えます。 この場合、そのようなメソッドは通常、引数をチェックするために非反復ラッパーメソッドを必要とします(MoveNextメソッドが呼び出されるまでイテレータ自体は呼び出されないため)。
ローカル関数を使用すると、この問題は通常よりもエレガントに解決されます。
public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter) { if (source == null) throw new ArgumentNullException(nameof(source)); if (filter == null) throw new ArgumentNullException(nameof(filter)); return Iterator(); IEnumerable<T> Iterator() { foreach (var element in source) { if (filter(element)) { yield return element; } } } }
Iteratorメソッドが通常のプライベートメソッドである場合、引数をチェックせずに偶然に呼び出される可能性があります。 さらに、彼はFilterメソッドと同じ引数を渡す必要があります。
注:プレビュー4では、呼び出す前にローカル関数を宣言する必要があります。 将来、この制限は緩和されます。ローカル関数は、使用されるすべてのローカル変数に値が割り当てられた後に呼び出すことができます。
リテラルの機能強化
C#7では、数値リテラルの区切り文字として_を追加する機能が追加されました。
var d = 123_456; var x = 0xAB_CD_EF;
区切り記号は数値間の任意の場所に追加できますが、値には影響しません。
バイナリリテラルもC#7に登場しました。
var b = 0b1010_1011_1100_1101_1110_1111;
参照によるローカル変数と戻り値
参照によってメソッドにパラメーターを渡す( refキーワードを使用)だけでなく、参照によってメソッドからデータを返すことができ、ローカル変数へのリンクを保存することもできます。
public ref int Find(int number, int[] numbers) { for (int i = 0; i < numbers.Length; i++) { if (numbers[i] == number) { return ref numbers[i]; // , } } throw new IndexOutOfRangeException($"{nameof(number)} "); } int[] array = { 1, 15, -39, 0, 7, 14, -12 }; ref int place = ref Find(7, array); // , 7 place = 9; // 7 9 WriteLine(array[4]); // 9
これで、大きなデータ構造の特定の場所にリンクを転送するのが便利になります。 たとえば、ゲームでは、情報は構造の事前に割り当てられた大きな配列に含まれています(ガベージコレクションの一時停止を避けるため)。 これで、メソッドはこれらの構造の1つへのリンクを返すことができ、その助けを借りて、呼び出し元のコードはこの構造を読み取り、変更できます。
リンクを安全に処理するために、次の制限が導入されています。
- 安全に返されるリンクのみが返されます。メソッドに渡されるオブジェクトへのリンクとオブジェクトのフィールドへのリンクです。
- 変数は特定の参照で初期化され、将来変更されません。
非同期メソッドによって返される型のリストの拡張
今日まで、 非同期メソッドはvoid 、 TaskまたはTask <T>のみを返すことができました。 C#7では、非同期で返すこともできる型を作成する機能が導入されました。 たとえば、 ValueTask <T>構造を作成して、非同期操作の結果が既に利用可能な場合にTask <T>オブジェクトを作成しないようにすることができます。 たとえば、バッファリングが使用される多くの非同期シナリオでは、このアプローチによりメモリ割り当ての数が大幅に削減され、パフォーマンスが向上します。
もちろん、タスクのようなオブジェクトが役立つ他の状況を思いつくことができます。 これらの型を正しく作成するのは簡単な作業ではないため、多数の開発者がそれらを作成することは期待していませんが、さまざまなフレームワークで有用であり、呼び出しコードはTaskのようにawaitを単純に使用できると考えています。
注:プレビュー4では、これらのタイプはまだ使用できません。
式としてのより多くのクラスメンバー
C#6に登場する式(式のボディメンバ)の形式のメソッドとプロパティがよく使用されましたが、すべてのタイプのクラスメンバがそのように宣言できるわけではありません。 C#7では、セッター、ゲッター、コンストラクター、およびデストラクターのサポートが追加されました。
class Person { private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>(); private int id = GetId(); public Person(string name) => names.TryAdd(id, name); // ~Person() => names.TryRemove(id, out *); // public string Name { get => names[id]; // set => names[id] = value; // } }
これは、コンパイラ開発チームではなく、コミュニティによって追加された新しい機能の例です! やったー
注:プレビュー4では、これらのクラスメンバーのサポートは利用できません。
式を投げる
式の途中で例外をスローすることはそれほど難しくありません。それを行うメソッドを呼び出すだけです。 ただし、C#7では、式の一部としてthrowを使用できるようになりました。
class Person { public string Name { get; } public Person(string name) => Name = name ?? throw new ArgumentNullException(name); public string GetFirstName() { var parts = Name.Split(" "); return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!"); } public string GetLastName() => throw new NotImplementedException(); }
注:プレビュー4では、このような式はまだ使用できません。
おわりに
C#7のリリースはまだまだ先ですが、今ではほとんどの新機能を試して、C#と.Netが一般的にどこに移動しているかを理解できます(私にとっては、C#は関数型言語からチップの一部を取得し、これによりコードが読みやすくなり、しかし、もちろん、測定値を知る必要があるすべての場所で)。