C#7の新機能とVisual Studio“ 15” Preview 4で既にサポートされている機能

Visual Studio“ 15” Preview 4のリリースにより多くの新しいC#7機能を試すことができます。 C#7の主な革新は、データの操作を容易にし、コードを簡素化し、パフォーマンスを向上させるように設計されています。 私自身は、C#は関数型言語に移行しており、タプルやパターンマッチングなどを追加していると言います。 すべての新機能がプレビュー4で意図したとおりに機能するわけではありません。これらの場合、現在何を使用できるか、および今後どのように機能するかが示されます。 さあ、始めましょう。



アウト変数



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#で新しいテンプレートを確実に追加する予定です。 テンプレートをサポートするために、2つの既存の言語構成が変更されました。





将来的には、テンプレートを使用するためのオプションをさらに追加する予定です。



あるテンプレート



定数パターンと型パターンの両方を使用する簡単な例を考えてみましょう。



 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 */ }
      
      





パターンと表現を切り替える



スイッチの使用が拡張され、次のことができるようになりました。





次に例を考えてみましょう。



 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)); }
      
      





新しい高度なスイッチの次の機能に注意してください。





caseで宣言されたテンプレート変数のスコープはswitchステートメントです。



タプル



メソッドから複数の値を返したい場合があります。 現在利用可能な方法はどれも最適に見えません:





このタスクを簡素化するために、タプルとタプルリテラルが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つへのリンクを返すことができ、その助けを借りて、呼び出し元のコードはこの構造を読み取り、変更できます。



リンクを安全に処理するために、次の制限が導入されています。





非同期メソッドによって返される型のリストの拡張



今日まで、 非同期メソッドはvoidTaskまたは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#は関数型言語からチップの一部を取得し、これによりコードが読みやすくなり、しかし、もちろん、測定値を知る必要があるすべての場所で)。



All Articles