C# 7 の新機能What's new in C# 7

C# 7 では、C# 言語に多くの新機能が追加されます。C# 7 adds a number of new features to the C# language:

  • out 変数out variables
    • out の値は、それが使用されるメソッドの引数としてインラインで宣言できます。You can declare out values inline as arguments to the method where they are used.
  • タプルTuples
    • 複数のパブリック フィールドを含む、軽量で名前のない型を作成できます。You can create lightweight, unnamed types that contain multiple public fields. コンパイラおよび IDE ツールでは、このような型のセマンティクスが認識されます。Compilers and IDE tools understand the semantics of these types.
  • 破棄Discards
    • 破棄は、割り当てられた値を考慮しない場合に割り当てで使用された、一時的な書き込み専用の値です。Discards are temporary, write-only variables used in assignments when you don't care about the value assigned. タプルおよびユーザー定義の型を分解する場合や、メソッドを out パラメーターを使用して呼び出す場合に特に便利です。They are particularly useful when deconstructing tuples and user-defined types, as well as when calling methods with out parameters.
  • パターン一致Pattern Matching
    • これらの型のメンバーの任意の型と値に基づいて、分岐ロジックを作成できます。You can create branching logic based on arbitrary types and values of the members of those types.
  • ref ローカル変数と戻り値ref locals and returns
    • メソッド引数とローカル変数は、他のストレージへの参照になります。Method arguments and local variables can be references to other storage.
  • ローカル関数Local Functions
    • 関数を他の関数の中に入れ子にして、関数のスコープと可視性を制限することができます。You can nest functions inside other functions to limit their scope and visibility.
  • 式形式のメンバーの追加More expression-bodied members
    • 式を使用して作成できるメンバーが増加しました。The list of members that can be authored using expressions has grown.
  • throwthrow Expressions
    • throw がステートメントだったためにこれまで許可されなかったコード コンストラクトで例外をスローできるようになりました。You can throw exceptions in code constructs that previously were not allowed because throw was a statement.
  • 一般化された async の戻り値の型Generalized async return types
    • async 修飾子を使って宣言したメソッドは、TaskTask<T> に加えて他の型を返すことができます。Methods declared with the async modifier can return other types in addition to Task and Task<T>.
  • 数値リテラルの構文の改善Numeric literal syntax improvements
    • 新しいトークンにより、数値定数の読みやすさが向上します。New tokens improve readability for numeric constants.

このトピックの残りの部分では、各機能について説明します。The remainder of this topic discusses each of the features. 機能ごとに、その背後にある論拠のほか、For each feature, you'll learn the reasoning behind it. 構文についても説明します。You'll learn the syntax. また、新機能の使用により、開発者としての生産性が向上するサンプル シナリオもいくつか紹介します。You'll see some sample scenarios where using the new feature will make you more productive as a developer.

out 変数out variables

out パラメーターをサポートする既存の構文は、このバージョンで改良されました。The existing syntax that supports out parameters has been improved in this version.

以前は、out 変数の宣言とその初期化を 2 つの異なるステートメントに分離する必要がありました。Previously, you would need to separate the declaration of the out variable and its initialization into two different statements:

int numericResult;
if (int.TryParse(input, out numericResult))
    WriteLine(numericResult);
else
    WriteLine("Could not parse input");

現在は、別の宣言ステートメントを記述するのではなく、メソッド呼び出しの引数リストで out 変数を宣言できるようになりました。You can now declare out variables in the argument list of a method call, rather than writing a separate declaration statement:

if (int.TryParse(input, out int result))
    WriteLine(result);
else
    WriteLine("Could not parse input");

上記のように、わかりやすくするために out 変数の型を指定することができます。You may want to specify the type of the out variable for clarity, as shown above. ただし、この言語では、次のように暗黙的に型指定されたローカル変数を使用できます。However, the language does support using an implicitly typed local variable:

if (int.TryParse(input, out var answer))
    WriteLine(answer);
else
    WriteLine("Could not parse input");
  • コードが読みやすくなる。The code is easier to read.
    • out 変数は、使用する場所で宣言します。その場所より上にある別の行で宣言しません。You declare the out variable where you use it, not on another line above.
  • 初期値を割り当てる必要がない。No need to assign an initial value.
    • out 変数は、メソッド呼び出し内の使用場所で宣言することにより、割り当てる前に誤って使用することがなくなります。By declaring the out variable where it is used in a method call, you can't accidentally use it before it is assigned.

この機能の最も一般的な用途は Try パターンになります。The most common use for this feature will be the Try pattern. このパターンでは、メソッドは、成功または失敗を示す bool と、メソッドが成功した場合に結果を提供する out 変数を返します。In this pattern, a method returns a bool indicating success or failure and an out variable that provides the result if the method succeeds.

out 変数の宣言を使用すると、宣言された変数は if ステートメントの外側のスコープに "リーク" されます。When using the out variable declaration, the declared variable "leaks" into the outer scope of the if statement. これにより、その後はこの変数を使用することができます。This allows you to use the variable afterwards:

if (!int.TryParse(input, out int result))
{    
    return null;
}

return result;

タプルTuples

注意

新しいタプル機能を使用するには、ValueTuple 型が必要です。The new tuples features require the ValueTuple types. 型が含まれていないプラットフォームで使用する場合は、NuGet パッケージ System.ValueTuple を追加する必要があります。You must add the NuGet package System.ValueTuple in order to use it on platforms that do not include the types.

これは、フレームワークで提供される型に依存するその他の言語機能に似ています。This is similar to other language features that rely on types delivered in the framework. たとえば、INotifyCompletion インターフェイスに依存する asyncawaitIEnumerable<T> に依存する LINQ などがあります。Example include async and await relying on the INotifyCompletion interface, and LINQ relying on IEnumerable<T>. ただし、.NET がプラットフォームにさらに依存しなくなりつつあるため、配信メカニズムもそれに応じて変わりつつあります。However, the delivery mechanism is changing as .NET is becoming more platform independent. .NET Framework が、言語コンパイラと同じ周期で配布されるとは限りません。The .NET Framework may not always ship on the same cadence as the language compiler. 新しい言語機能が新しい型に依存する場合、それらの型は、言語機能の配布時に NuGet パッケージとして入手できます。When new language features rely on new types, those types will be available as NuGet packages when the language features ship. これらの新しい型は .NET 標準 API に追加され、フレームワークの一部として配信されるため、NuGet パッケージは必要なくなります。As these new types get added to the .NET Standard API and delivered as part of the framework, the NuGet package requirement will be removed.

C# には、設計の意図を説明するために使用される、クラスと構造体の豊富な構文が用意されています。C# provides a rich syntax for classes and structs that is used to explain your design intent. ところが、場合によっては、その豊富な構文を使用するために、余分な作業が必要になることがあります。この場合、メリットはごくわずかです。But sometimes that rich syntax requires extra work with minimal benefit. 複数のデータ要素を含む単純な構造を必要とするメソッドを記述することはよくあります。You may often write methods that need a simple structure containing more than one data element. このようなシナリオをサポートするために、C# には "タプル" が追加されました。To support these scenarios tuples were added to C#. タプルとは、データ メンバーを表す複数のフィールドを含む軽量なデータ構造です。Tuples are lightweight data structures that contain multiple fields to represent the data members. フィールドは検証されず、独自のメソッドを定義することはできません。The fields are not validated, and you cannot define your own methods

注意

タプルは C# 7 より前で使用できましたが、効率的でなく、言語サポートがありませんでした。Tuples were available before C# 7, but they were inefficient and had no language support. これは、タプル要素が Item1Item2 などとしてのみ参照できることを意味しました。This meant that tuple elements could only be referenced as Item1, Item2 and so on. C# 7 では、タプルの言語サポートが導入されたことで、新しい、より効率的なタプル型を使用するフィールドのセマンティック名が有効になります。C# 7 introduces language support for tuples, which enables semantic names for the fields of a tuple using new, more efficient tuple types.

タプルを作成するには、各メンバーを値に割り当てます。You can create a tuple by assigning each member to a value:

var letters = ("a", "b");

その割り当てによってメンバーが Item1Item2 のタプルが作成され、Tuple と同じようにして使用できるようになります。構文を変更してタプルの各メンバーにセマンティック名を提供するタプルを作成することができます。That assignment creates a tuple whose members are Item1 and Item2, which you can use in the same way as Tuple You can change the syntax to create a tuple that provides semantic names to each of the members of the tuple:

(string Alpha, string Beta) namedLetters = ("a", "b");

namedLetters タプルには、AlphaBeta と呼ばれるフィールドが含まれています。The namedLetters tuple contains fields referred to as Alpha and Beta. これらの名前は、コンパイル時にのみ存在し、実行時にリフレクションを使用してタプルを検査するときなどには保持されません。Those names exist only at compile time and are not preserved for example when inspecting the tuple using reflection at runtime.

タプルの割り当てでは、代入の右辺でフィールドの名前を指定することもできます。In a tuple assignment, you can also specify the names of the fields on the right-hand side of the assignment:

var alphabetStart = (Alpha: "a", Beta: "b");

代入の左辺と右辺の両方でフィールドの名前を指定することができます。You can specify names for the fields on both the left and right-hand side of the assignment:

(string First, string Second) firstLetters = (Alpha: "a", Beta: "b");

上記の行では、警告 CS8123 が生成されます。これは、代入の右辺にある名前 (AlphaBeta) が左辺にある名前 (FirstSecond) と競合するために無視されることを示しています。The line above generates a warning, CS8123, telling you that the names on the right side of the assignment, Alpha and Beta are ignored because they conflict with the names on the left side, First and Second.

上の例では、タプルを宣言する基本的な構文を示しています。The examples above show the basic syntax to declare tuples. タプルは、private メソッドと internal メソッドの戻り値の型として最も有用です。Tuples are most useful as return types for private and internal methods. タプルには、これらのメソッドが複数の不連続値を返すための簡単な構文が用意されています。そのため、返される型を定義する class または struct を作成するという手間を省くことができます。Tuples provide a simple syntax for those methods to return multiple discrete values: You save the work of authoring a class or a struct that defines the type returned. 新しい型を作成する必要はありません。There is no need for creating a new type.

タプルの作成は、より効率的かつ生産的です。Creating a tuple is more efficient and more productive. タプルは、複数の値を保持するデータ構造を定義するための、よりシンプルで軽量な構文です。It is a simpler, lightweight syntax to define a data structure that carries more than one value. 次の例のメソッドは、整数のシーケンス内で見つかった最小値と最大値を返します。The example method below returns the minimum and maximum values found in a sequence of integers:

private static (int Max, int Min) Range(IEnumerable<int> numbers)
{
    int min = int.MaxValue;
    int max = int.MinValue;
    foreach(var n in numbers)
    {
        min = (n < min) ? n : min;
        max = (n > max) ? n : max;
    }
    return (max, min);
}

このようにタプルを使用すると、いくつかの利点があります。Using tuples in this way offers several advantages:

  • 返される型を定義する class または struct を作成する手間がかからない。You save the work of authoring a class or a struct that defines the type returned.
  • 新しい型を作成する必要がない。You do not need to create new type.
  • 言語の拡張により、Create<T1>(T1) メソッドを呼び出す必要がなくなる。The language enhancements removes the need to call the Create<T1>(T1) methods.

メソッドの宣言では、返されるタプルのフィールドに名前を指定します。The declaration for the method provides the names for the fields of the tuple that is returned. このメソッドを呼び出した場合の戻り値は、MaxMin というフィールドを含むタプルです。When you call the method, the return value is a tuple whose fields are Max and Min:

var range = Range(numbers);

状況によっては、メソッドから返されたタプルのメンバーをばらすことが必要になる場合もあります。There may be times when you want to unpackage the members of a tuple that were returned from a method. そのためには、タプル内のそれぞれの値に対して別個の変数を宣言します。You can do that by declaring separate variables for each of the values in the tuple. この操作は、タプルの "分解" と呼ばれます。This is called deconstructing the tuple:

(int max, int min) = Range(numbers);

.NET でも任意の型に同様の分解を指定することができます。You can also provide a similar deconstruction for any type in .NET. そのためには、Deconstruct メソッドをクラスのメンバーとして記述します。This is done by writing a Deconstruct method as a member of the class. その Deconstruct メソッドは、抽出する各プロパティ用に一連の out 引数を提供します。That Deconstruct method provides a set of out arguments for each of the properties you want to extract. 次の Point クラスを考えてみましょう。このクラスは、X 座標と Y 座標を抽出するデコンストラクター メソッドを指定しています。Consider this Point class that provides a deconstructor method that extracts the X and Y coordinates:

public class Point
{
    public Point(double x, double y)
    {
        this.X = x;
        this.Y = y;
    }

    public double X { get; }
    public double Y { get; }

    public void Deconstruct(out double x, out double y)
    {
        x = this.X;
        y = this.Y;
    }
}

Point にタプルを割り当てることで、個々のフィールドを抽出できます。You can extract the individual fields by assigning a tuple to a Point:

var p = new Point(3.14, 2.71);
(double X, double Y) = p;

Deconstruct メソッドで定義された名前に制約されません。You are not bound by the names defined in the Deconstruct method. 割り当ての一環として、抽出変数の名前を変更してもかまいません。You can rename the extract variables as part of the assignment:

(double horizontalDistance, double verticalDistance) = p;

タプルの詳細については、タプルのトピックを参照してください。You can learn more in depth about tuples in the tuples topic.

破棄Discards

out パラメーターを使用してタプルを分解したりメソッドを呼び出したりする場合に、使用する予定がなく、考慮にも入れない値の変数の定義を強制されることが多くあります。Often when deconstructing a tuple or calling a method with out parameters, you're forced to define a variable whose value you don't care about and don't intend to use. C# ではこのシナリオを処理するために破棄のサポートを追加しています。C# adds support for discards to handle this scenario. 破棄は名前が _ (アンダースコア () 文字) の書き込み専用の変数で、破棄するすべての値をこの 1 つの変数に割り当てることができます。A discard is a write-only variable whose name is `` (the underscore character); you can assign all of the values that you intend to discard to the single variable. 破棄は未割り当ての変数に似ています。代入ステートメントとは異なり、破棄はコードで使用できません。A discard is like an unassigned variable; apart from the assignment statement, the discard can't be used in code.

次のシナリオでは破棄はサポートされません。Discards are supported in the following scenarios:

  • タプルまたはユーザー定義の型を分解する場合。When deconstructing tuples or user-defined types.

  • out パラメーターを使用してメソッドを呼び出す場合。When calling methods with out parameters.

  • is および switch ステートメントによるパターン マッチング操作。In a pattern matching operation with the is and switch statements.

  • 割り当ての値を破棄として明示的に識別する必要がある場合の、スタンドアロン識別子。As a standalone identifier when you want to explicitly identify the value of an assignment as a discard.

次の例は、ある都市の 2 つの異なる年度のデータを含む 6 つのタプルを戻す QueryCityDataForYears メソッドを定義しています。The following example defines a QueryCityDataForYears method that returns a 6-tuple that contains a data for a city for two different years. この例のメソッド呼び出しでは、メソッドによって戻された 2 つの人口の値のみが考慮されているため、タプルの残りの値はタプルの分解時に破棄として扱われます。The method call in the example is concerned only with the two population values returned by the method and so treats the remaining values in the tuple as discards when it deconstructs the tuple.

using System;
using System.Collections.Generic;

public class Example
{
   public static void Main()
   {
       var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

       Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
   }
   
   private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
   {
      int population1 = 0, population2 = 0;
      double area = 0;
      
      if (name == "New York City") {
         area = 468.48; 
         if (year1 == 1960) {
            population1 = 7781984;
         }
         if (year2 == 2010) {
            population2 = 8175133;
         }
      return (name, area, year1, population1, year2, population2);
      }

      return ("", 0, 0, 0, 0, 0);
   }
}
// The example displays the following output:
//      Population change, 1960 to 2010: 393,149

詳細については、破棄に関するページを参照してください。For more information, see Discards.

パターン マッチングPattern matching

"パターン マッチング" は、オブジェクトの型以外のプロパティでメソッドのディスパッチを実装できるようにする機能です。Pattern matching is a feature that allows you to implement method dispatch on properties other than the type of an object. オブジェクトの型に基づいたメソッドのディスパッチに慣れている方も多いはずです。You're probably already familiar with method dispatch based on the type of an object. オブジェクト指向プログラミングでは、仮想メソッドとオーバーライド メソッドには、オブジェクトの型に基づくメソッドのディスパッチを実装するための言語構文が用意されています。In Object Oriented programming, virtual and override methods provide language syntax to implement method dispatching based on an object's type. 基底クラスと派生クラスは異なる実装を提供します。Base and Derived classes provide different implementations. パターン マッチング式により、この概念が拡張されたため、継承階層を介して関連していない型やデータ要素に同様のディスパッチ パターンを簡単に実装できるようになります。Pattern matching expressions extend this concept so that you can easily implement similar dispatch patterns for types and data elements that are not related through an inheritance hierarchy.

パターン マッチングでは、is 式と switch 式がサポートされています。Pattern matching supports is expressions and switch expressions. どちらの式でも、オブジェクトとそのプロパティを検査して、そのオブジェクトが必要なパターンを満たしているかどうかを判定できます。Each enables inspecting an object and its properties to determine if that object satisfies the sought pattern. パターンに追加の規則を指定するには、when キーワードを使用します。You use the when keyword to specify additional rules to the pattern.

isis expression

is パターン式を使用すると、使い慣れた is 演算子を拡張して、その型を超えてオブジェクトを照会できます。The is pattern expression extends the familiar is operator to query an object beyond its type.

では、簡単なシナリオから始めましょう。Let's start with a simple scenario. ここでは、関連付けられていない型を操作するアルゴリズムをパターン マッチング式で簡単にする方法を示すために、いくつかの機能をこのシナリオに追加します。We'll add capabilities to this scenario that demonstrate how pattern matching expressions make algorithms that work with unrelated types easy. まず、サイコロの出目の合計を計算するメソッドを追加します。We'll start with a method that computes the sum of a number of die rolls:

public static int DiceSum(IEnumerable<int> values)
{
    return values.Sum();
}

複数のサイコロを使った場合はそれぞれの出目の合計を求める必要があることがすぐにわかります。You might quickly find that you need to find the sum of die rolls where some of the rolls are made with multiple dice (dice is the plural of die). 入力シーケンスの一部は、単一の数字ではなく複数の結果になる場合があります。Part of the input sequence may be multiple results instead of a single number:

public static int DiceSum2(IEnumerable<object> values)
{
    var sum = 0;
    foreach(var item in values)
    {
        if (item is int val)
            sum += val;
        else if (item is IEnumerable<object> subList)
            sum += DiceSum2(subList);
    }
    return sum;
}

このシナリオでは、is パターン式が非常にうまく機能します。The is pattern expression works quite well in this scenario. 型のチェックの一環として、変数の初期化を記述します。As part of checking the type, you write a variable initialization. これにより、検証されたランタイム型の新しい変数が作成されます。This creates a new variable of the validated runtime type.

これらのシナリオをさらに拡張していくと、さらに多くの if ステートメントと else if ステートメントを作成することになります。As you keep extending these scenarios, you may find that you build more if and else if statements. それが扱いにくくなったら、switch パターン式に切り替えることをお勧めします。Once that becomes unwieldy, you'll likely want to switch to switch pattern expressions.

switch ステートメントの更新switch statement updates

"一致式" には、既に C# 言語に含まれている switch ステートメントに基づいた、使い慣れた構文があります。The match expression has a familiar syntax, based on the switch statement already part of the C# language. ここで、新しいケースを追加する前に、一致式を使用するように既存のコードを変換してみましょう。Let's translate the existing code to use a match expression before adding new cases:

public static int DiceSum3(IEnumerable<object> values)
{
    var sum = 0;
    foreach (var item in values)
    {
        switch (item)
        {
            case int val:
                sum += val;
                break;
            case IEnumerable<object> subList:
                sum += DiceSum3(subList);
                break;
        }
    }
    return sum;
}

一致式の構文は is 式の構文とは若干異なり、型と変数を case 式の先頭で宣言します。The match expressions have a slightly different syntax than the is expressions, where you declare the type and variable at the beginning of the case expression.

一致式では定数もサポートされます。The match expressions also support constants. これにより、単純なケースが取り除かれるため、時間を節約できます。This can save time by factoring out simple cases:

public static int DiceSum4(IEnumerable<object> values)
{
    var sum = 0;
    foreach (var item in values)
    {
        switch (item)
        {
            case 0:
                break;
            case int val:
                sum += val;
                break;
            case IEnumerable<object> subList when subList.Any():
                sum += DiceSum4(subList);
                break;
            case IEnumerable<object> subList:
                break;
            case null:
                break;
            default:
                throw new InvalidOperationException("unknown item type");
        }
    }
    return sum;
}

上のコードでは、int の特殊なケースとして 0 のケース、入力がない場合の特殊なケースとして null のケースが追加されています。The code above adds cases for 0 as a special case of int, and null as a special case when there is no input. これは、switch パターン式の重要な新機能の 1 つを示しています。ここでは、case 式の順序が重要になります。This demonstrates one important new feature in switch pattern expressions: the order of the case expressions now matters. 0 のケースは、一般的な int のケースの前に記述する必要があります。The 0 case must appear before the general int case. そうしないと、一致する最初のパターンは、値が 0 であっても int のケースになります。Otherwise, the first pattern to match would be the int case, even when the value is 0. 後のケースが先に処理されるように一致式の順序を誤って指定すると、コンパイラはそこにフラグを設定し、エラーを生成します。If you accidentally order match expressions such that a later case has already been handled, the compiler will flag that and generate an error.

これと同じ動作により、空の入力シーケンスに対する特殊なケースにも対応できます。This same behavior enables the special case for an empty input sequence. このコードから、要素を持つ IEnumerable 項目のケースは一般的な IEnumerable ケースの前に配置する必要があることがわかります。You can see that the case for an IEnumerable item that has elements must appear before the general IEnumerable case.

このバージョンでは、default ケースも追加されています。This version has also added a default case. default ケースは、常に最後に評価されます。ソース内で記述される順序は関係ありません。The default case is always evaluated last, regardless of the order it appears in the source. そのため、慣習として、default ケースを最後に配置します。For that reason, convention is to put the default case last.

最後に、新しいスタイルのサイコロ用に最後の case を追加しましょう。Finally, let's add one last case for a new style of die. ゲームによっては、より広範な数字を表すためにパーセンタイル ダイスが使用される場合があります。Some games use percentile dice to represent larger ranges of numbers.

注意

10 面のパーセンタイル ダイスを 2 つ使用すると、0 から 99 までのすべての数字を表すことができます。Two 10-sided percentile dice can represent every number from 0 through 99. 1 つのサイコロの面には、00102090 のようなラベルが付けられています。One die has sides labelled 00, 10, 20, ... 90. もう 1 つのサイコロの面には、0129 のようなラベルが付けられています。The other die has sides labeled 0, 1, 2, ... 9. 2 つのサイコロの数字を加算すると、0 から 99 までのすべての数字を取得することができます。Add the two die values together and you can get every number from 0 through 99.

この種類のサイコロをコレクションに追加するには、まずパーセンタイル ダイスを表す型を定義します。To add this kind of die to your collection, first define a type to represent the percentile dice. TensDigit プロパティは値 01020 を、最大 90 まで格納します。The TensDigit property stores values 0, 10, 20, up to 90:

public struct PercentileDice
{
    public int OnesDigit { get; }
    public int TensDigit { get; }

    public PercentileDice(int tensDigit, int onesDigit)
    {
        this.OnesDigit = onesDigit;
        this.TensDigit = tensDigit;
    }
}

次に、新しい型の case 一致式を追加します。Then, add a case match expression for the new type:

public static int DiceSum5(IEnumerable<object> values)
{
    var sum = 0;
    foreach (var item in values)
    {
        switch (item)
        {
            case 0:
                break;
            case int val:
                sum += val;
                break;
            case PercentileDice dice:
                sum += dice.TensDigit + dice.OnesDigit;
                break;
            case IEnumerable<object> subList when subList.Any():
                sum += DiceSum5(subList);
                break;
            case IEnumerable<object> subList:
                break;
            case null:
                break;
            default:
                throw new InvalidOperationException("unknown item type");
        }
    }
    return sum;
}

パターン マッチング式の新しい構文を使用すると、オブジェクトの型やその他のプロパティに基づいたディスパッチ アルゴリズムを、明確かつ簡潔な構文を使用して簡単に作成できます。The new syntax for pattern matching expressions makes it easier to create dispatch algorithms based on an object's type, or other properties, using a clear and concise syntax. パターン マッチング式は、継承によって関連付けられていないデータ型に対してこれらのコンストラクトを有効にします。Pattern matching expressions enable these constructs on data types that are unrelated by inheritance.

パターン マッチングの詳細については、C# のパターン マッチングに特化したトピックを参照してください。You can learn more about pattern matching in the topic dedicated to pattern matching in C#.

ref ローカル変数と戻り値Ref locals and returns

この機能により、他の場所に定義されている変数への参照を使用したり返したりするアルゴリズムが実現します。This feature enables algorithms that use and return references to variables defined elsewhere. 1 つの例として、大規模なマトリックスを使用していて、特定の特性を持つ 1 つの場所を探します。One example is working with large matrices, and finding a single location with certain characteristics. 1 つのメソッドでは、マトリックス内の単一の場所に 2 つのインデックスが返されます。One method would return the two indices for a single location in the matrix:

public static (int i, int j) Find(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return (i, j);
    return (-1, -1); // Not found
}

このコードには多くの問題があります。There are many issues with this code. まず、これは、タプルを返すパブリック メソッドです。First of all, it's a public method that's returning a tuple. この言語ではこれがサポートされていますが、パブリック API にはユーザー定義型 (クラスまたは構造体) をお勧めします。The language supports this, but user defined types (either classes or structs) are preferred for public APIs.

2 つ目に、このメソッドは、マトリックスの項目のインデックスを返します。Second, this method is returning the indices to the item in the matrix. そのため、呼び出し元は、これらのインデックスを使用してマトリックスを逆参照し、1 つの要素を変更するコードを記述することになります。That leads callers to write code that uses those indices to dereference the matrix and modify a single element:

var indices = MatrixSearch.Find(matrix, (val) => val == 42);
Console.WriteLine(indices);
matrix[indices.i, indices.j] = 24;

それよりも、変更するマトリックス内の要素への "参照" を返すメソッドを記述することをお勧めします。You'd rather write a method that returns a reference to the element of the matrix that you want to change. これを実現するには、アンセーフ コードを使用し、前のバージョンの int へのポインターを返す方法しかありません。You could only accomplish this by using unsafe code and returning a pointer to an int in previous versions.

それでは、一連の変更を確認しながら、ref ローカル変数の機能と、内部ストレージへの参照を返すメソッドの作成方法を説明します。Let's walk through a series of changes to demonstrate the ref local feature and show how to create a method that returns a reference to internal storage. その過程で、ref 戻り値および ref ローカル変数の機能の誤用を防ぐための規則についても説明します。Along the way, you'll learn the rules of the ref return and ref local feature that protects you from accidentally misusing it.

まず、タプルの代わりに ref int を返すように Find メソッドの宣言を変更します。Start by modifying the Find method declaration so that it returns a ref int instead of a tuple. 次に、2 つのインデックスの代わりに、マトリックスに格納されている値を返すように、return ステートメントを変更します。Then, modify the return statement so it returns the value stored in the matrix instead of the two indices:

// Note that this won't compile. 
// Method declaration indicates ref return,
// but return statement specifies a value return.
public static ref int Find2(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return matrix[i, j];
    throw new InvalidOperationException("Not found");
}

メソッドが ref 変数を返すことを宣言する際に、それぞれの return ステートメントに ref キーワードも追加する必要があります。When you declare that a method returns a ref variable, you must also add the ref keyword to each return statement. これは、参照渡しを示します。これにより、後でコードを読む開発者にも、そのメソッドが参照渡しで返すことがわかります。That indicates return by reference, and helps developers reading the code later remember that the method returns by reference:

public static ref int Find3(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return ref matrix[i, j];
    throw new InvalidOperationException("Not found");
}

このメソッドはマトリックス内の整数値への参照を返すため、呼び出される場所を変更する必要があります。Now that the method returns a reference to the integer value in the matrix, you need to modify where it's called. var 宣言は、valItem がタプルではなく int であることを意味します。The var declaration means that valItem is now an int rather than a tuple:

var valItem = MatrixSearch.Find3(matrix, (val) => val == 42);
Console.WriteLine(valItem);
valItem = 24;
Console.WriteLine(matrix[4, 2]);

上記の例の 2 番目の WriteLine ステートメントで出力される値は 42 であり、24 ではありません。The second WriteLine statement in the example above prints out the value 42, not 24. 変数 valItem は、int であり、ref int ではありません。The variable valItem is an int, not a ref int. var キーワードを使用すると、コンパイラは、型を指定できますが、ref 修飾子を暗黙的に追加しません。The var keyword enables the compiler to specify the type, but will not implicitly add the ref modifier. 代わりに、ref return によって参照される値が代入の左辺にある変数に "コピー" されます。Instead, the value referred to by the ref return is copied to the variable on the left-hand side of the assignment. この変数は ref ローカル変数ではありません。The variable is not a ref local.

目的の結果を得るために、ref 修飾子をローカル変数の宣言に追加して、戻り値が参照の場合に変数が参照になるようにする必要があります。In order to get the result you want, you need to add the ref modifier to the local variable declaration to make the variable a reference when the return value is a reference:

ref var item = ref MatrixSearch.Find3(matrix, (val) => val == 42);
Console.WriteLine(item);
item = 24;
Console.WriteLine(matrix[4, 2]);

上記の例の 2 番目の WriteLine ステートメントは、値 24 を出力します。これは、マトリックスのストレージが変更されたことを示しています。Now, the second WriteLine statement in the example above will print out the value 24, indicating that the storage in the matrix has been modified. ローカル変数は ref 修飾子で宣言されており、ref 戻り値を受け取ります。The local variable has been declared with the ref modifier, and it will take a ref return. ref 変数は宣言時に初期化する必要があります。宣言と初期化を分けることはできません。You must initialize a ref variable when it is declared, you cannot split the declaration and the initialization.

C# 言語には、これ以外に、ref ローカル変数と戻り値の誤用を防ぐ規則が 3 つあります。The C# language has three other rules that protect you from misusing the ref locals and returns:

  • 標準的なメソッドの戻り値を ref ローカル変数に割り当てることはできません。You cannot assign a standard method return value to a ref local variable.
    • したがって、ref int i = sequence.Count(); のようなステートメントは使用できません。That disallows statements like ref int i = sequence.Count();
  • 有効期間がメソッドの実行期間を超えない変数に ref を返すことはできません。You cannot return a ref to a variable whose lifetime does not extend beyond the execution of the method.
    • つまり、ローカル変数または類似のスコープの変数への参照を返すことはできません。That means you cannot return a reference to a local variable or a variable with a similar scope.
  • ref ローカル変数と戻り値は、非同期メソッドと共に使用することはできません。ref locals and returns can't be used with async methods.
    • コンパイラは、非同期メソッドが戻るときに、参照先の変数が、最終的な値に設定されているかどうかを認識できません。The compiler can't know if the referenced variable has been set to its final value when the async method returns.

ref ローカル変数および ref 戻り値の追加により、値のコピーを回避したり、逆参照操作を複数回実行したりすることで、より効率的なアルゴリズムを実現できます。The addition of ref locals and ref returns enable algorithms that are more efficient by avoiding copying values, or performing dereferencing operations multiple times.

ローカル関数Local functions

クラスの多くの設計には、1 つの場所からのみ呼び出されるメソッドが含まれます。Many designs for classes include methods that are called from only one location. このような追加のプライベート メソッドを使用することで、各メソッドのサイズを小さくし、その焦点を絞ることができます。These additional private methods keep each method small and focused. ただし、このプライベート メソッドにより、初めてクラスを読むときに理解しにくくなる場合があります。However, they can make it harder to understand a class when reading it the first time. これらのメソッドは、単一の呼び出し元の場所のコンテキストの外部でも理解する必要があります。These methods must be understood outside of the context of the single calling location.

そのような設計では、"ローカル関数" を使用すると、別のメソッドのコンテキスト内でメソッドを宣言することができます。For those designs, local functions enable you to declare methods inside the context of another method. これにより、クラスを読むときに、ローカル メソッドが、それが宣言されているコンテキストからのみ呼び出されることを容易に理解できます。This makes it easier for readers of the class to see that the local method is only called from the context in which is it declared.

ローカル関数には、パブリック反復子メソッドとパブリック非同期メソッドという 2 つの非常に一般的なユース ケースがあります。There are two very common use cases for local functions: public iterator methods and public async methods. どちらの種類のメソッドも、プログラマーが期待するよりも遅くエラーを報告するコードを生成します。Both types of methods generate code that reports errors later than programmers might expect. 反復子メソッドの場合、例外が検出されるのは、返されたシーケンスを列挙するコードを呼び出した場合のみです。In the case of iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. 非同期メソッドの場合、例外が検出されるのは、返された Task が待機状態になったときのみです。In the case of async methods, any exceptions are only observed when the returned Task is awaited.

それでは、反復子メソッドから見ていきましょう。Let's start with an iterator method:

public static IEnumerable<char> AlphabetSubset(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
    for (var c = start; c < end; c++)
        yield return c;
}

下のコードでは、不適切な方法で反復子メソッドを呼び出しています。Examine the code below that calls the iterator method incorrectly:

var resultSet = Iterator.AlphabetSubset('f', 'a');
Console.WriteLine("iterator created");
foreach (var thing in resultSet)
    Console.Write($"{thing}, ");

例外は、resultSet が作成されたときではなく、resultSet が反復処理されたときにスローされます。The exception is thrown when resultSet is iterated, not when resultSet is created. ここに示した例では、ほとんどの開発者が問題を迅速に診断できるでしょう。In this contained example, most developers could quickly diagnose the problem. ところが、より大きなコードベースでは、多くの場合、反復子を作成するコードが結果を列挙するコードの近くにあるわけではありません。However, in larger codebases, the code that creates an iterator often isn't as close to the code that enumerates the result. パブリック メソッドですべての引数を検証し、プライベート メソッドで列挙型を生成するように、コードをリファクタリングすることができます。You can refactor the code so that the public method validates all arguments, and a private method generates the enumeration:

public static IEnumerable<char> AlphabetSubset2(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
    return alphabetSubsetImplementation(start, end);
}

private static IEnumerable<char> alphabetSubsetImplementation(char start, char end)
{ 
    for (var c = start; c < end; c++)
        yield return c;
}

このリファクタリングしたバージョンでは、例外が即座にスローされます。これは、パブリック メソッドが反復子メソッドではないためです。つまり、yield return 構文を使用するのは、プライベート メソッドのみです。This refactored version will throw exceptions immediately because the public method is not an iterator method; only the private method uses the yield return syntax. ただし、このリファクタリングには潜在的な問題があります。However, there are potential problems with this refactoring. プライベート メソッドは、パブリック インターフェイス メソッドからのみ呼び出す必要があります。そうしないと、すべての引数の検証がスキップされるためです。The private method should only be called from the public interface method, because otherwise all argument validation is skipped. クラスを読む開発者がこの事実を発見するには、クラス全体を読み、alphabetSubsetImplementation メソッドへの他の参照を検索する必要があります。Readers of the class must discover this fact by reading the entire class and searching for any other references to the alphabetSubsetImplementation method.

alphabetSubsetImplementation をパブリック API メソッド内のローカル関数として宣言することで、この設計の意図をより明確にすることができます。You can make that design intent more clear by declaring the alphabetSubsetImplementation as a local function inside the public API method:

public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

    return alphabetSubsetImplementation();

    IEnumerable<char> alphabetSubsetImplementation()
    {
        for (var c = start; c < end; c++)
            yield return c;
    }
}

上記のバージョンでは、ローカル メソッドが外部メソッドのコンテキストでのみ参照されることが明確になっています。The version above makes it clear that the local method is referenced only in the context of the outer method. また、ローカル関数の規則により、開発者が誤ってクラス内の別の場所からローカル関数を呼び出して、引数の検証をバイパスできないようになっています。The rules for local functions also ensure that a developer can't accidentally call the local function from another location in the class and bypass the argument validation.

同じ手法を async メソッドで使用すると、引数の検証で発生する例外が非同期操作の開始前にスローされることを保証できます。The same technique can be employed with async methods to ensure that exceptions arising from argument validation are thrown before the asynchronous work begins:

public Task<string> PerformLongRunningWork(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    return longRunningWorkImplementation();

    async Task<string> longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }
}

注意

ローカル関数によってサポートされる設計の中には、"ラムダ式" を使用して実現できるものもあります。Some of the designs that are supported by local functions could also be accomplished using lambda expressions. 興味のある方は、その違いの詳細を確認してくださいThose interested can read more about the differences

式形式のメンバーの追加More expression-bodied members

C# 6 では、メンバー関数の式形式のメンバーと読み取り専用プロパティが導入されました。C# 6 introduced expression-bodied members for member functions, and read-only properties. C# 7 では、式として実装できる許可されたメンバーが拡張されます。C# 7 expands the allowed members that can be implemented as expressions. C# 7 では、"コンストラクター"、"ファイナライザー"、get アクセサー、および set アクセサーを "プロパティ" と "インデクサー" に実装できます。In C# 7, you can implement constructors, finalizers, and get and set accessors on properties and indexers. それぞれの例を次のコードに示します。The following code shows examples of each:

// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;

// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");

private string label;

// Expression-bodied get / set accessors.
public string Label
{
    get => label;
    set => this.label = value ?? "Default label";
}

注意

この例ではファイナライザーは必要ありませんが、その構文を紹介するために示しています。This example does not need a finalizer, but it is shown to demonstrate the syntax. アンマネージ リソースを解放する必要がない限り、クラスにファイナライザーを実装しないでください。You should not implement a finalizer in your class unless it is necessary to release unmanaged resources. また、アンマネージ リソースを直接管理する代わりに、SafeHandle クラスの使用を検討する必要もあります。You should also consider using the SafeHandle class instead of managing unmanaged resources directly.

式形式のメンバーに関するこのような新しい場所は、C# 言語にとって重要なマイルストーンを表します。これらの機能は、オープンソースの Roslyn プロジェクトに従事しているコミュニティ メンバーによって実装されました。These new locations for expression-bodied members represent an important milestone for the C# language: These features were implemented by community members working on the open-source Roslyn project.

throw 式Throw expressions

C# では、throw は常にステートメントでした。In C#, throw has always been a statement. throw は式ではなくステートメントであるため、使用できない C# コンストラクトがありました。Because throw is a statement, not an expression, there were C# constructs where you could not use it. これには、条件式、null 結合式、および一部のラムダ式が含まれます。These included conditional expressions, null coalescing expressions, and some lambda expressions. 式形式のメンバーが追加されたことにより、さらに多くの場所で throw 式が役に立つようになりました。The addition of expression-bodied members adds more locations where throw expressions would be useful. C# 7 では、このようなコンストラクトを記述できるように、"throw 式" が導入されています。So that you can write any of these constructs, C# 7 introduces throw expressions.

その構文は、throw ステートメントに常に使用してきた構文と同じです。The syntax is the same as you've always used for throw statements. 唯一の違いは、条件式などの新しい場所に配置できるようになったことです。The only difference is that now you can place them in new locations, such as in a conditional expression:

public string Name
{
    get => name;
    set => name = value ?? 
        throw new ArgumentNullException(paramName: nameof(value), message: "New name must not be null");
}

この機能により、初期化式で throw 式を使用できるようになります。This features enables using throw expressions in initialization expressions:

private ConfigResource loadedConfig = LoadConfigResourceOrDefault() ?? 
    throw new InvalidOperationException("Could not load config");

以前は、これらの初期化は、コンストラクターの本体に throw ステートメントを使用して、コンストラクター内で行う必要がありました。Previously, those initializations would need to be in a constructor, with the throw statements in the body of the constructor:

public ApplicationOptions()
{
    loadedConfig = LoadConfigResourceOrDefault();
    if (loadedConfig == null)
        throw new InvalidOperationException("Could not load config");

}

注意

前述のコンストラクトのどちらにおいても、オブジェクトの構築中に例外がスローされます。Both of the preceding constructs will cause exceptions to be thrown during the construction of an object. 多くの場合、これらの例外から回復するのは困難です。Those are often difficult to recover from. そのため、構築中に例外をスローする設計は推奨しません。For that reason, designs that throw exceptions during construction are discouraged.

一般化された async の戻り値の型Generalized async return types

非同期メソッドから Task オブジェクトを返すと、特定のパスでパフォーマンスのボトルネックが発生する可能性があります。Returning a Task object from async methods can introduce performance bottlenecks in certain paths. Task は参照型です。したがって、これを使うことは、オブジェクトを割り当てることを意味します。Task is a reference type, so using it means allocating an object. async 修飾子で宣言されたメソッドがキャッシュされた結果を返すか、同期的に完了する場合、追加の割り当ては、コードのパフォーマンスが重要なセクションにおいて大きな時間コストにつながります。In cases where a method declared with the async modifier returns a cached result, or completes synchronously, the extra allocations can become a significant time cost in performance critical sections of code. 厳密なループ処理でこのような割り当てが発生した場合、非常にコストがかかる場合があります。It can become very costly if those allocations occur in tight loops.

新しい言語機能は、非同期メソッドが、TaskTask<T>void に加えて他の型を返す可能性があることを意味します。The new language feature means that async methods may return other types in addition to Task, Task<T> and void. 返される型は引き続き非同期パターンを満たす必要があります。つまり、GetAwaiter メソッドはアクセス可能である必要があります。The returned type must still satisfy the async pattern, meaning a GetAwaiter method must be accessible. 1 つの具体的な例として、この新しい言語機能を使用するために .NET Framework に ValueTask 型が追加されました。As one concrete example, the ValueTask type has been added to the .NET framework to make use of this new language feature:

public async ValueTask<int> Func()
{
    await Task.Delay(100);
    return 5;
}

注意

ValueTask<TResult> 型を使用するには、NuGet パッケージ System.Threading.Tasks.Extensions を追加する必要があります。You need to add the NuGet package System.Threading.Tasks.Extensions in order to use the ValueTask<TResult> type.

単純な最適化では、これまで Task が使用されていた場所に ValueTask を使用します。A simple optimization would be to use ValueTask in places where Task would be used before. ただし、手動で追加の最適化を実行する場合は、非同期処理の結果をキャッシュし、その結果を以降の呼び出しで再利用できます。However, if you want to perform extra optimizations by hand, you can cache results from async work and reuse the result in subsequent calls. ValueTask 構造体には Task パラメーターを持つコンストラクターがあるため、既存の非同期メソッドの戻り値から ValueTask を構築できます。The ValueTask struct has a constructor with a Task parameter so that you can construct a ValueTask from the return value of any existing async method:

public ValueTask<int> CachedFunc()
{
    return (cache) ? new ValueTask<int>(cacheResult) : new ValueTask<int>(LoadCache());
}
private bool cache = false;
private int cacheResult;
private async Task<int> LoadCache()
{
    // simulate async work:
    await Task.Delay(100);
    cacheResult = 100;
    cache = true;
    return cacheResult;
}

すべてのパフォーマンスに関する推奨事項と同様に、コードに大規模な変更を加える前に、両方のバージョンのベンチマークを計測する必要があります。As with all performance recommendations, you should benchmark both versions before making large scale changes to your code.

数値リテラルの構文の改善Numeric literal syntax improvements

数値定数を読み間違えると、コードを初めて読むときにコードを理解するのが難しくなる場合があります。Misreading numeric constants can make it harder to understand code when reading it for the first time. これは、数値がビット マスクや数値以外の記号として使用されている場合にありがちなことです。This often occurs when those numbers are used as bit masks or other symbolic rather than numeric values. C# 7 では、使用目的に応じて最も読みやすい形式で簡単に数値を記述しできるように、"バイナリ リテラル" と "桁区切り文字" という 2 つの新機能が導入されました。C# 7 includes two new features to make it easier to write numbers in the most readable fashion for the intended use: binary literals, and digit separators.

ビット マスクを作成しているときや、数値をバイナリで表現するとコードが最も読みやすくなる場合は、数字をバイナリで記述します。For those times when you are creating bit masks, or whenever a binary representation of a number makes the most readable code, write that number in binary:

public const int One =  0b0001;
public const int Two =  0b0010;
public const int Four = 0b0100;
public const int Eight = 0b1000;

定数の先頭にある 0b は、数値が 2 進数として記述されていることを示します。The 0b at the beginning of the constant indicates that the number is written as a binary number.

バイナリ数値は非常に長くなる場合があるため、ビット パターンが見やすくなるように、桁区切り記号として _ が導入されました。Binary numbers can get very long, so it's often easier to see the bit patterns by introducing the _ as a digit separator:

public const int Sixteen =   0b0001_0000;
public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight = 0b1000_0000;

桁区切り記号は定数のどこにでも置くことができます。The digit separator can appear anywhere in the constant. 10 進数の場合は、3 桁の区切り記号として使用するのが一般的です。For base 10 numbers, it would be common to use it as a thousands separator:

public const long BillionsAndBillions = 100_000_000_000;

桁区切り記号は、decimal 型、float 型、double 型でも使用できます。The digit separator can be used with decimal, float and double types as well:

public const double AvogadroConstant = 6.022_140_857_747_474e23;
public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;

これらをまとめると、数値定数をさらに見やすい状態で宣言できます。Taken together, you can declare numeric constants with much more readability.