C# 6 の新機能What's New in C# 6

C# の 6.0 リリースには、開発者の生産性を向上させる多くの機能が含まれています。The 6.0 release of C# contained many features that improve productivity for developers. このリリースの新機能には、次の機能が含まれます。Features in this release include:

これらの機能がもたらす全体的な効果として、より読みやすく簡潔なコードを記述できるようになりました。The overall effect of these features is that you write more concise code that is also more readable. 一般的なベスト プラクティスを多数反映することで、構文に使用される形式的な記述が減っています。The syntax contains less ceremony for many common practices. 形式的な記述が減ったことで、設計の意図が理解しやすくなっています。It's easier to see the design intent with less ceremony. これらの機能を十分に学習すれば、生産性が高まり、より読みやすいコードを記述できるようになります。また、言語の構造がわかりやすくなる分、コア機能の作業に専念できるようになります。Learn these features well, and you'll be more productive, write more readable code, and concentrate more on your core features than on the constructs of the language.

このトピックでは、これらの各機能について詳しく説明していきます。The remainder of this topic provides details on each of these features.

自動プロパティの機能強化Auto-Property enhancements

自動的に実装されるプロパティ (通常、"自動プロパティ" と呼ばれる) の構文は、get と set のシンプルなアクセサーを使ったプロパティの作成を簡単にしました。The syntax for automatically implemented properties (usually referred to as 'auto-properties') made it very easy to create properties that had simple get and set accessors:

public string FirstName { get; set; }
public string LastName { get; set; }

ただし、このシンプルな構文では、自動プロパティを使ってサポートできる設計の種類に制限がありました。However, this simple syntax limited the kinds of designs you could support using auto-properties. C# 6 では、自動プロパティの機能が強化され、より多くのシナリオで自動プロパティを使用できるようなりました。C# 6 improves the auto-properties capabilities so that you can use them in more scenarios. これにより、冗長な構文を宣言したり、バッキング フィールドを手動で操作する労力が軽減されます。You won't need to fall back on the more verbose syntax of declaring and manipulating the backing field by hand so often.

新しい構文は、読み取り専用プロパティ、および自動プロパティの背後にある変数のストレージを初期化するためのシナリオに対処します。The new syntax addresses scenarios for read-only properties, and for initializing the variable storage behind an auto-property.

読み取り専用の自動プロパティRead-only auto-properties

読み取り専用の自動プロパティを使用すると、より簡潔な構文で不変型を作成できます。Read-only auto-properties provide a more concise syntax to create immutable types. 以前のバージョンの C# では、不変型を作成するための最も簡単な方法は、プライベート セッターを宣言する方法でした。The closest you could get to immutable types in earlier versions of C# was to declare private setters:

public string FirstName { get; private set; }
public string LastName { get; private set; }

この構文を使用した場合、コンパイラは型が本当に変更不可であることを保証できません。Using this syntax, the compiler doesn't ensure that the type really is immutable. FirstName プロパティと LastName プロパティが、クラス外部のコードから変更されないようにするだけです。It only enforces that the FirstName and LastName properties are not modified from any code outside the class.

読み取り専用の自動プロパティを使用すれば、真の読み取り専用動作を実現できます。Read-only auto-properties enable true read-only behavior. 自動プロパティは、get アクセサーのみを使用して宣言します。You declare the auto-property with only a get accessor:

public string FirstName { get; }
public string LastName { get;  }

FirstName プロパティと LastName プロパティは、コンス トラクターの本体でのみ設定できます。The FirstName and LastName properties can be set only in the body of a constructor:

public Student(string firstName, string lastName)
{
    if (IsNullOrWhiteSpace(lastName))
        throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
    FirstName = firstName;
    LastName = lastName;
}

別のメソッドで LastName を設定しようとすると、コンパイル エラー CS0200 が生成されます。Trying to set LastName in another method generates a CS0200 compilation error:

public class Student
{
    public string LastName { get;  }

    public void ChangeName(string newLastName)
    {
        // Generates CS0200: Property or indexer cannot be assigned to -- it is read only
        LastName = newLastName;
    }
}

この機能を使用すれば、不変型を作成したり、より簡潔で便利な自動プロパティ構文を使用するための、本格的な言語サポートを実現できます。This feature enables true language support for creating immutable types and using the more concise and convenient auto-property syntax.

自動プロパティ初期化子Auto-Property Initializers

自動プロパティ初期化子を使用すると、プロパティ宣言の一部として自動プロパティの初期値を宣言できます。Auto-Property Initializers let you declare the initial value for an auto-property as part of the property declaration. 以前のバージョンでは、これらのプロパティにはセッターが必要でした。そのセッターを使用して、バッキング フィールドによって使用されるデータ ストレージを初期化する必要がありました。In earlier versions, these properties would need to have setters and you would need to use that setter to initialize the data storage used by the backing field. たとえば、生徒の名前と成績を含んだ、生徒用のクラスがあるとします。Consider this class for a student that contains the name and a list of the student's grades:

public Student(string firstName, string lastName)
{
    FirstName = firstName;
    LastName = lastName;
}

このクラスが大きくなってくると、他のコンス トラクターが追加されることもあるでしょう。As this class grows, you may include other constructors. 各コンス トラクターでは、このフィールドを初期化する必要があります。そうしないとエラーが発生します。Each constructor needs to initialize this field, or you'll introduce errors.

C# 6 では、自動プロパティ宣言で自動プロパティによって使用されるストレージの初期値を割り当てることができます。C# 6 enables you to assign an initial value for the storage used by an auto-property in the auto-property declaration:

public ICollection<double> Grades { get; } = new List<double>();

Grades メンバーは宣言された場所で初期化されます。The Grades member is initialized where it is declared. これにより、初期化を厳密に 1 回だけ実行しやすくなります。That makes it easier to perform the initialization exactly once. 初期化がプロパティ宣言の一部になることで、ストレージ割り当てが Student オブジェクトのパブリック インターフェイスと同じであることを示しやすくなっています。The initialization is part of the property declaration, making it easier to equate the storage allocation with public interface for Student objects.

プロパティ初期化子は、ここで示すように、読み取り専用のプロパティと同様に読み取り/書き込みプロパティを使用できます。Property Initializers can be used with read/write properties as well as read-only properties, as shown here.

public Standing YearInSchool { get; set; } = Standing.Freshman;

式形式の関数メンバーExpression-bodied function members

開発者が記述する多くのメンバーの本文は、式として表すことができる 1 つのステートメントだけで構成されます。The body of a lot of members that we write consist of only one statement that can be represented as an expression. これに代えて式形式のメンバーを記述すれば、その構文を減らすことができます。You can reduce that syntax by writing an expression-bodied member instead. メソッドおよび読み取り専用のプロパティに対して動作します。It works for methods and read-only properties. たとえば、ToString() のオーバーライドはその好例です。For example, an override of ToString() is often a great candidate:

public override string ToString() => $"{LastName}, {FirstName}";

読み取り専用プロパティで式の本文メンバーを使用することもできます。You can also use expression-bodied members in read-only properties as well:

public string FullName => $"{FirstName} {LastName}";

using staticusing static

using static 拡張機能を使用すると、1 つのクラスの静的メソッドをインポートすることができます。The using static enhancement enables you to import the static methods of a single class. これまで、using ステートメントは名前空間内のすべての型をインポートしていました。Previously, the using statement imported all types in a namespace.

開発者はクラスの静的メソッドをコード内の随所で使用することがよくありますが、Often we use a class' static methods throughout our code. クラス名を繰り返し入力すると、コードの意味がわかりにくくなることもあります。Repeatedly typing the class name can obscure the meaning of your code. 一般的な例としては、多数の数値計算を実行するクラスが挙げられます。A common example is when you write classes that perform many numeric calculations. このようなクラスでは、Math クラス内の異なるメソッドに対する SinSqrt、およびその他の呼び出しがコード内に散在することになります。Your code will be littered with Sin, Sqrt and other calls to different methods in the Math class. 新しい using static 構文では、これらのクラスを大幅に簡潔化し、読みやすくすることができます。The new using static syntax can make these classes much cleaner to read. 具体的には、使用するクラスを次のように指定します。You specify the class you're using:

using static System.Math;

これで、Math クラスを修飾しなくても、Math クラス内の任意の静的メソッドを使用できます。And now, you can use any static method in the Math class without qualifying the Math class. Math クラスにはインスタンス メソッドが含まれていないので、この機能が特に役に立ちます。The Math class is a great use case for this feature because it does not contain any instance methods. また using static は、静的メソッドとインスタンス メソッドの両方を持つクラスの静的クラスをインポートするためにも使用できます。You can also use using static to import a class' static methods for a class that has both static and instance methods. 最も役に立つの例では、いずれかがString:One of the most useful examples is String:

using static System.String;

注意

static using ステートメントでは、完全修飾クラス名 (System.String) 使用する必要があります。You must use the fully qualified class name, System.String in a static using statement. 代わりに string キーワードを使用することはできません。You cannot use the string keyword instead.

これで、String クラス内で定義された静的メソッドを、そのクラスのメンバーとして修飾しなくても呼び出せるようになります。You can now call static methods defined in the String class without qualifying those methods as members of that class:

if (IsNullOrWhiteSpace(lastName))
    throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));

static using 機能と拡張メソッドは興味深い方法で相互作用し、言語設計には、それらの相互作用に具体的に対応するいくつかのルールが含まれています。The static using feature and extension methods interact in interesting ways, and the language design included some rules that specifically address those interactions. その目的は、既存のコードベース (ユーザーのコードを含む) に重大な変更が生じる可能性を最小限に減らすことです。The goal is to minimize any chances of breaking changes in existing codebases, including yours.

拡張メソッドは、拡張メソッドの呼び出し構文を使用して呼び出された場合にのみ、スコープ内に含められます。静的メソッドとして呼び出された場合には含められません。Extension methods are only in scope when called using the extension method invocation syntax, not when called as a static method. これは、LINQ クエリで多く見受けられることになるでしょう。You'll often see this in LINQ queries. LINQ パターンは、Enumerable をインポートすることによってインポートできます。You can import the LINQ pattern by importing Enumerable.

using static System.Linq.Enumerable;

このコードは、Enumerable クラス内のすべてのメソッドをインポートします。This imports all the methods in the Enumerable class. ただし拡張メソッドは、拡張メソッドとして呼び出された場合にしかスコープ内に含められません。However, the extension methods are only in scope when called as extension methods. 静的メソッドの構文を使用して呼び出された場合には、スコープに含められません。They are not in scope if they are called using the static method syntax:

public bool MakesDeansList()
{
    return Grades.All(g => g > 3.5) && Grades.Any();
    // Code below generates CS0103: 
    // The name 'All' does not exist in the current context.
    //return All(Grades, g => g > 3.5) && Grades.Any();
}

このような仕様になったのは、拡張メソッドが通常、拡張メソッドの呼び出し式を使用して呼び出されるためです。This decision is because extension methods are typically called using extension method invocation expressions. まれに、静的メソッドの呼び出し構文を使用して呼び出される場合もありますが、それはあいまいさを解決するためです。In the rare case where they are called using the static method call syntax it is to resolve ambiguity. 呼び出しには必ずクラス名を含めるようにするのが賢明と言えるでしょう。Requiring the class name as part of the invocation seems wise.

最後に、static using の機能がもう1 つあります。There's one last feature of static using. static using ディレクティブは、入れ子にされた型もインポートします。The static using directive also imports any nested types. これにより、入れ子になった型を修飾なしで参照できるようになっています。That enables you to reference any nested types without qualification.

Null 条件演算子Null-conditional operators

Null 値はコードを複雑にします。Null values complicate code. 開発者は変数のアクセスをすべてチェックして、null を逆参照していないことを確認する必要があります。You need to check every access of variables to ensure you are not dereferencing null. Null 条件演算子を使用すれば、これらのチェックをより簡単で円滑なものにすることができます。The null conditional operator makes those checks much easier and fluid.

やり方は、メンバー アクセス .?. 置き換えるだけです。Simply replace the member access . with ?.:

var first = person?.FirstName; 

上記の例では、person オブジェクトが null の場合に、変数 firstnull が割り当てられます。In the preceding example, the variable first is assigned null if the person object is null. その他の場合には、FirstName プロパティの値が割り当てられます。Otherwise, it gets assigned the value of the FirstName property. 特に重要なのは、?. を使用した場合、変数 personnull であっても、このコード行では NullReferenceException が生成されないということです。Most importantly, the ?. means that this line of code does not generate a NullReferenceException when the person variable is null. 代わりに、処理がショートサーキットされ、null が生成されます。Instead, it short-circuits and produces null.

また、この式は string の値に関係なく、person を返します。Also, note that this expression returns a string, regardless of the value of person. 処理がショート サーキットされた場合は、返された null 値が式全体に一致するように入力されます。In the case of short circuiting, the null value returned is typed to match the full expression.

null 結合演算子を使用したこのコンストラクトは、いずれかのプロパティが null の場合に既定値を割り当てる目的で使用できます。You can often use this construct with the null coalescing operator to assign default values when one of the properties are null:

first = person?.FirstName ?? "Unspecified";

?. 演算子の右側のオペランドは、プロパティやフィールドに制限されません。The right hand side operand of the ?. operator is not limited to properties or fields. メソッドを条件付きで呼び出すためにも使用できます。You can also use it to conditionally invoke methods. Null 条件演算子を使用したメンバー関数の最も一般的な用途は、null である可能性があるデリゲート (またはイベント ハンドラー) を安全に呼び出すことです。The most common use of member functions with the null conditional operator is to safely invoke delegates (or event handlers) that may be null. これを行うには、?. 演算子を使用してデリゲートの Invoke メソッドを呼び出し、メンバーにアクセスします。You'll do this by calling the delegate's Invoke method using the ?. operator to access the member. この例は、You can see an example in the
デリゲート パターンに関するトピックに記載されています。delegate patterns topic.

?. 演算子のルールでは、、演算子の左側が 1 回だけ評価されることが保証されています。The rules of the ?. operator ensure that the left-hand side of the operator is evaluated only once. これは重要なことであり、これによって多くの表現方法が可能になります (イベント ハンドラーを使用した例を含む)。This is important and enables many idioms, including the example using event handlers. まずは、イベント ハンドラーを使用した例から見ていきましょう。Let's start with the event handler usage. 以前のバージョンの C# では、コードを次のように記述することが推奨されていました。In previous versions of C#, you were encouraged to write code like this:

var handler = this.SomethingHappened;
if (handler != null)
    handler(this, eventArgs);

これは、次のような単純な構文よりも推奨されていました。This was preferred over a simpler syntax:

// Not recommended
if (this.SomethingHappened != null)
    this.SomethingHappened(this, eventArgs);

重要

上記の例では、競合状態が発生しています。The preceding example introduces a race condition. SomethingHappened イベントは、null に対してチェックされた時点ではサブスクライバーが存在する可能性がありますが、それらのサブスクライバーは、イベントが生成する前に削除される可能性もあります。The SomethingHappened event may have subscribers when checked against null, and those subscribers may have been removed before the event is raised. その場合、NullReferenceException がスローされます。That would cause a NullReferenceException to be thrown.

この 2 つ目のバージョンでは、SomethingHappened イベント ハンドラーがテスト時に Null 以外である可能性もありますが、その他のコードによってハンドラーが削除された場合には、イベント ハンドラーの呼び出し時に Null になっている可能性もあります。In this second version, the SomethingHappened event handler might be non-null when tested, but if other code removes a handler, it could still be null when the event handler was called.

?. 演算子に対してコンパイラが生成するコードでは、?. 式の左側 (this.SomethingHappened) が 1 回だけ評価され、その結果がキャッシュされます。The compiler generates code for the ?. operator that ensures the left side (this.SomethingHappened) of the ?. expression is evaluated once, and the result is cached:

// preferred in C# 6:
this.SomethingHappened?.Invoke(this, eventArgs);

左側が 1 回しか評価されないことが確実であるということは、?. の左側で任意の式 (メソッド呼び出しを含む) を使用できるということでもあります。それらに副作用があったとしても、1 回しか評価されないので、副作用も 1 回しか発生しません。Ensuring that the left side is evaluated only once also enables you to use any expression, including method calls, on the left side of the ?. Even if these have side-effects, they are evaluated once, so the side effects occur only once. この例は、イベントに関するコンテンツで参照できます。You can see an example in our content on events.

文字列補間String Interpolation

C# 6 で導入された新しい構文では、書式文字列と、その他の文字列値を生成するために評価できる式から、文字列を作成することができます。C# 6 contains new syntax for composing strings from a format string and expressions that can be evaluated to produce other string values.

これまでは、string.Format などのメソッドで位置指定パラメーターを使用する必要がありました。Traditionally, you needed to use positional parameters in a method like string.Format:

public string FullName
{
    get
    {
        return string.Format("{0} {1}", FirstName, LastName);
    }
}

C# 6 では、新しい文字列補間機能を使用して、書式文字列に式を埋め込むことができます。With C# 6, the new string interpolation feature enables you to embed the expressions in the format string. 方法は、文字列の前に $ を付けるだけです。Simple preface the string with $:

public string FullName => $"{FirstName} {LastName}";

この最初の例では、置換される式に変数の式を使用しています。This initial example used variable expressions for the substituted expressions. この構文を拡張することで、任意の式を使用することができます。You can expand on this syntax to use any expression. たとえば、補間の一部として生徒の評価点の平均を計算することもできます。For example, you could compute a student's grade point average as part of the interpolation:

public string GetFormattedGradePoint() =>
    $"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average()}";

上記の例を実行すると、Grades.Average() の出力に含まれる小数点以下の桁数が、開発者の意図よりも多くなる場合があることに気付くでしょう。Running the preceding example, you would find that the output for Grades.Average() might have more decimal places than you would like. 文字列補間の構文では、これまでの書式設定メソッドを通じて利用できるすべての書式がサポートされます。The string interpolation syntax supports all the format strings available using earlier formatting methods. 中かっこ内に書式文字列を追加し、You add the format strings inside the braces. 書式設定する式 の後に、: を追加します。Add a : following the expression to format:

public string GetGradePointPercentage() =>
    $"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average():F2}";

上記のコード行では、Grades.Average() の値が、小数点以下 2 桁の浮動小数点数として書式設定されます。The preceding line of code will format the value for Grades.Average() as a floating-point number with two decimal places.

: は常に、書式設定される式と書式文字列との間の区切り記号として解釈されます。The : is always interpreted as the separator between the expression being formatted and the format string. そのため、: が式内で別の目的 (条件演算子など) に使用されている場合には、問題が生じる可能性があります。This can introduce problems when your expression uses a : in another way, such as a conditional operator:

public string GetGradePointPercentages() =>
    $"Name: {LastName}, {FirstName}. G.P.A: {Grades.Any() ? Grades.Average() : double.NaN:F2}";

上記の例では、: は条件演算子の一部ではなく、書式文字列の開始点として解析されます。In the preceding example, the : is parsed as the beginning of the format string, not part of the conditional operator. このような場合には、式を中かっこで囲むことで、コンパイラに正しく式を解釈させることができます。In all cases where this happens, you can surround the expression with parentheses to force the compiler to interpret the expression as you intend:

public string GetGradePointPercentages() =>
    $"Name: {LastName}, {FirstName}. G.P.A: {(Grades.Any() ? Grades.Average() : double.NaN):F2}";

中かっこの間に配置できる式に制限はありません。There aren't any limitations on the expressions you can place between the braces. 補間文字列内で複雑な LINQ クエリを実行して計算を実行し、結果を表示することもできます。You can execute a complex LINQ query inside an interpolated string to perform computations and display the result:

public string GetAllGrades() =>
    $@"All Grades: {Grades.OrderByDescending(g => g)
    .Select(s => s.ToString("F2")).Aggregate((partial, element) => $"{partial}, {element}")}";

この例のように、文字列補間式の内部で、別の文字列補間式を入れ子にすることもできます。You can see from this sample that you can even nest a string interpolation expression inside another string interpolation expression. この例は開発者が運用コードで使用するものよりも複雑である可能性が高いですが、This example is very likely more complex than you would want in production code. この機能の対応範囲を説明するためにあえて示しました。Rather, it is illustrative of the breadth of the feature. 補間文字列の中かっこの間には、任意の C# 式を配置できます。Any C# expression can be placed between the curly braces of an interpolated string.

文字列補間と特定のカルチャString interpolation and specific cultures

前のセクションで示した例はいずれも、コードが実行されるマシンの現在のカルチャと言語を使用して文字列を書式設定するものでした。All the examples shown in the preceding section will format the strings using the current culture and language on the machine where the code executes. しかし場合によっては、生成された文字列を特定のカルチャで書式設定する必要が生じる場合もあるでしょう。Often you may need to format the string produced using a specific culture. 文字列補間によって生成されるオブジェクトの型には、String または FormattableString への暗黙的な変換があります。The object produced from a string interpolation is a type that has an implicit conversion to either String or FormattableString.

FormattableString 型には、書式文字列と、それらを文字列に変換する前の引数評価の結果が含まれます。The FormattableString type contains the format string, and the results of evaluating the arguments before converting them to strings. FormattableString のパブリック メソッドを使用すれば、文字列の書式設定時にカルチャを指定することができます。You can use public methods of FormattableString to specify the culture when formatting a string. たとえば、次のコードでは言語とカルチャにドイツ語を使用して文字列が生成されます。For example, the following will produce a string using German as the language and culture. (小数点区切り文字に ',' 文字が使用され、桁区切り記号に '.' 文字が使用されます。)(It will use the ',' character for the decimal separator, and the '.' character as the thousands separator.)

FormattableString str = $"Average grade is {s.Grades.Average()}";
var gradeStr = string.Format(null, 
    System.Globalization.CultureInfo.CreateSpecificCulture("de-de"),
    str.GetFormat(), str.GetArguments());

注意

上記の例は、.NET Core バージョン 1.0.1 ではサポートされません。The preceding example is not supported in .NET Core version 1.0.1. .NET Framework でのみサポートされます。It is only supported in the .NET Framework.

一般に、文字列補間式は文字列を出力として生成します。In general, string interpolation expressions produce strings as their output. ただし、文字列の書式設定に使用されるカルチャをより細かく制御する必要がある場合は、特定の出力を指定することもできます。However, when you want greater control over the culture used to format the string, you can specify a specific output. この機能を頻繁にする必要がある場合は、用途に合ったメソッドを (拡張メソッドとして) 作成して、特定のカルチャでの書式設定を簡単にすることができます。If this is a capability you often need, you can create convenience methods, as extension methods, to enable easy formatting with specific cultures.

例外フィルターException Filters

C# 6 では、例外フィルターという新機能も追加されています。Another new feature in C# 6 is exception filters. 例外フィルターは、特定の catch 句がいつ適用されるのかを決定する句です。Exception Filters are clauses that determine when a given catch clause should be applied. 例外フィルターに使用されている式が true と評価された場合、catch 句は例外に対して通常の処理を実行します。If the expression used for an exception filter evaluates to true, the catch clause performs its normal processing on an exception. 式が false と評価された場合、catch 句はスキップされます。If the expression evaluates to false, then the catch clause is skipped.

用途としては、例外に関する情報を調べて、catch 句で例外を処理できるかどうかを確認するという使い方があります。One use is to examine information about an exception to determine if a catch clause can process the exception:

public static async Task<string> MakeRequest()
{ 
    var client = new System.Net.Http.HttpClient();
    var streamTask = client.GetStringAsync("https://localHost:10000");
    try {
        var responseText = await streamTask;
        return responseText;
    } catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
    {
        return "Site Moved";
    }
}

例外フィルターによって生成されたコードには、スローされ、処理されていない例外についての詳細な情報が含まれます。The code generated by exception filters provides better information about an exception that is thrown and not processed. 例外フィルターがこの言語に追加される以前は、次のようなコードを作成する必要がありました。Before exception filters were added to the language, you would need to create code like the following:

public static async Task<string> MakeRequest()
{ 
    var client = new System.Net.Http.HttpClient();
    var streamTask = client.GetStringAsync("https://localHost:10000");
    try {
        var responseText = await streamTask;
        return responseText;
    } catch (System.Net.Http.HttpRequestException e)
    {
        if (e.Message.Contains("301"))
            return "Site Moved";
        else
            throw;
    }
}

これら 2 つの例では、例外がスローされるポイントが異なります。The point where the exception is thrown changes between these two examples. 旧来のコード (throw 句を使用したコード) では、スタック トレースの分析やクラッシュ ダンプの調査を実行した場合、catch 句内の throw ステートメントから例外がスローされたと示されます。In the previous code, where a throw clause is used, any stack trace analysis or examination of crash dumps will show that the exception was thrown from the throw statement in your catch clause. 実際の例外オブジェクトには元の呼び出し履歴が含まれますが、このスロー ポイントと元のスロー ポイントとの間での呼び出し履歴内にあるその他の変数情報はすべて失われます。The actual exception object will contain the original call stack, but all other information about any variables in the call stack between this throw point and the location of the original throw point has been lost.

これに対し、例外フィルターを使用したコードはどのように処理されるかというと、例外フィルター式が false と評価されます。Contrast that with how the code using an exception filter is processed: the exception filter expression evaluates to false. そのため、catch 句が実行されることはありません。Therefore, execution never enters the catch clause. catch 句が実行されないので、スタックのアンワインドは行われません。Because the catch clause does not execute, no stack unwinding takes place. つまり、以降に発生するいずれのデバッグ活動においても、元のスロー場所が保持されます。That means the original throw location is preserved for any debugging activities that would take place later.

例外の種類のみを使用するのではなく、例外のフィールドやプロパティも評価する必要がある場合には、必ず例外フィルターを使用して、デバッグの詳細を保持するようにしてください。Whenever you need to evaluate fields or properties of an exception, instead of relying solely on the exception type, use an exception filter to preserve more debugging information.

例外フィルターを使用するもう 1 つの推奨パターンとして、ルーチンのロギングがあります。Another recommended pattern with exception filters is to use them for logging routines. 例外フィルターが false と評価された場合に例外のスロー ポイントが保持されるという動作は、この用途でも役に立ちます。This usage also leverages the manner in which the exception throw point is preserved when an exception filter evaluates to false.

次の例では、無条件に false を返す例外を、ロギング メソッドの引数にしています。A logging method would be a method whose argument is the exception that unconditionally returns false:

public static bool LogException(this Exception e)
{
    Console.Error.WriteLine($"Exceptions happen: {e}");
    return false;
} 

例外をログに記録する必要がある場合には、catch 句を追加し、このメソッドを例外フィルターとして使用できます。Whenever you want to log an exception, you can add a catch clause, and use this method as the exception filter:

public void MethodThatFailsSometimes()
{
    try {
        PerformFailingOperation();
    } catch (Exception e) when (e.LogException())
    {
        // This is never reached!
    }
} 

LogException メソッドは常に false を返するため、例外はキャッチされません。The exceptions are never caught, because the LogException method always returns false. 常に false となるこの例外フィルターを使用すれば、このロギング ハンドラーを、他のどの例外ハンドラーよりも前に配置することができます。That always false exception filter means that you can place this logging handler before any other exception handlers:

public void MethodThatFailsButHasRecoveryPath()
{
    try {
        PerformFailingOperation();
    } catch (Exception e) when (e.LogException())
    {
        // This is never reached!
    }
    catch (RecoverableException ex)
    {
        Console.WriteLine(ex.ToString());
        // This can still catch the more specific
        // exception because the exception filter
        // above always returns false.
        // Perform recovery here 
    }
}

上記の例は、例外フィルターの非常に重要な特徴を示しています。The preceding example highlights a very important facet of exception filters. それは、例外フィルターを使用すれば、詳細な例外 catch 句よりも前に、より一般的な例外 catch 句が配置されるシナリオにも対応できるということです。The exception filters enable scenarios where a more general exception catch clause may appear before a more specific one. また、複数の catch 句に同じ種類の例外を配置することもできます。It's also possible to have the same exception type appear in multiple catch clauses:

public static async Task<string> MakeRequestWithNotModifiedSupport()
{ 
    var client = new System.Net.Http.HttpClient();
    var streamTask = client.GetStringAsync("https://localHost:10000");
    try {
        var responseText = await streamTask;
        return responseText;
    } catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
    {
        return "Site Moved";
    } catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("304"))
    {
        return "Use the Cache";
    }
}

もう 1 つの推奨パターンとして、デバッガーがアタッチされている場合には catch 句で例外を処理しないようにする用途もあります。Another recommended pattern helps prevent catch clauses from processing exceptions when a debugger is attached. この手法を使用すれば、デバッガーを使ってアプリケーションを実行し、例外がスローされた場合には実行を停止することができます。This technique enables you to run an application with the debugger, and stop execution when an exception is thrown.

具体的には、コードに例外フィルターを追加して、デバッガーがアタッチされていない場合にのみ、修復コードが実行されるようにします。In your code, add an exception filter so that any recovery code executes only when a debugger is not attached:

public void MethodThatFailsWhenDebuggerIsNotAttached()
{
    try {
        PerformFailingOperation();
    } catch (Exception e) when (e.LogException())
    {
        // This is never reached!
    }
    catch (RecoverableException ex) when (!System.Diagnostics.Debugger.IsAttached)
    {
        Console.WriteLine(ex.ToString());
        // Only catch exceptions when a debugger is not attached.
        // Otherwise, this should stop in the debugger. 
    }
}

この記述をコードに追加した後、すべての未処理例外に対して処理を中断するようにデバッガーを設定します。After adding this in code, you set your debugger to break on all unhandled exceptions. その後デバッガーを使用してプログラムを実行すると、デバッガーは PerformFailingOperation()RecoverableException をスローするたびに処理を中断します。Run the program under the debugger, and the debugger breaks whenever PerformFailingOperation() throws a RecoverableException. false を返す例外フィルターがあるため catch 句は実行されず、デバッガーがプログラムを中断します。The debugger breaks your program, because the catch clause won't be executed due to the false-returning exception filter.

nameofnameof Expressions

nameof 式はシンボルの名前を評価します。The nameof expression evaluates to the name of a symbol. この式は、変数、プロパティ、またはメンバー フィールドの名前が必要なときに便利です。It's a great way to get tools working whenever you need the name of a variable, a property, or a member field.

nameof の用途として特に一般的なものの 1 つは、例外の原因となったシンボルの名前を取得することです。One of the most common uses for nameof is to provide the name of a symbol that caused an exception:

if (IsNullOrWhiteSpace(lastName))
    throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));

また、INotifyPropertyChanged インターフェイスを実装する XAML ベースのアプリケーションでも使用されます。Another use is with XAML based applications that implement the INotifyPropertyChanged interface:

public string LastName
{
    get { return lastName; }
    set
    {
        if (value != lastName)
        {
            lastName = value;
            PropertyChanged?.Invoke(this, 
                new PropertyChangedEventArgs(nameof(LastName)));
        }
    }
}
private string lastName;

nameof 演算子が定数文字列よりも便利な点は、ツールがシンボルを把握できるという点です。The advantage of using the nameof operator over a constant string is that tools can understand the symbol. リファクタリング ツールを使用してシンボルの名前を変更すると、その名前が nameof 式で変更されます。If you use refactoring tools to rename the symbol, it will rename it in the nameof expression. これは、定数文字列にはない利点です。Constant strings don't have that advantage. 好みのエディターを使用して変数の名前を変更すれば、nameof 式も更新されます。Try it yourself in your favorite editor: rename a variable, and any nameof expressions will update as well.

nameof 式は、開発者が引数の完全修飾名を使用した場合でも、引数の非修飾名 (上記の例では LastName) を生成します。The nameof expression produces the unqualified name of its argument (LastName in the previous examples) even if you use the fully qualified name for the argument:

public string FirstName
{
    get { return firstName; }
    set
    {
        if (value != firstName)
        {
            firstName = value;
            PropertyChanged?.Invoke(this, 
                new PropertyChangedEventArgs(nameof(UXComponents.ViewModel.FirstName)));
        }
    }
}
private string firstName;

この nameof 式では、UXComponents.ViewModel.FirstName ではなく、FirstName が生成されます。This nameof expression produces FirstName, not UXComponents.ViewModel.FirstName.

Catch ブロックと Finally ブロックでの AwaitAwait in Catch and Finally blocks

C# 5 では、await 式を配置できる位置について、いくつかの制限がありました。C# 5 had several limitations around where you could place await expressions. C# 6 では、そのうちの 1 つが解消され、One of those has been removed in C# 6. awaitcatch 式や finally 式で使用できるようになりました。You can now use await in catch or finally expressions.

catch や finally のブロックに await を追加すると、それらの処理方法が複雑になるように思えるかもしれないので、The addition of await expressions in catch and finally blocks may appear to complicate how those are processed. コード例で説明していきましょう。Let's add an example to discuss how this appears. 非同期メソッドでは、finally 句で await 式を使用できます。In any async method, you can use an await expression in a finally clause.

C# 6 では、catch 式でも await を使用することができます。With C# 6, you can also await in catch expressions. 次に示すのは、ロギングのシナリオで特によく使用されるコードです。This is most often used with logging scenarios:

public static async Task<string> MakeRequestAndLogFailures()
{ 
    await logMethodEntrance();
    var client = new System.Net.Http.HttpClient();
    var streamTask = client.GetStringAsync("https://localHost:10000");
    try {
        var responseText = await streamTask;
        return responseText;
    } catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
    {
        await logError("Recovered from redirect", e);
        return "Site Moved";
    }
    finally
    {
        await logMethodExit();
        client.Dispose();
    }
}

catch 句と finally 句の内部に await のサポートを追加するための実装詳細では、その動作と、同期コードの動作との整合性が保証されています。The implementation details for adding await support inside catch and finally clauses ensures that the behavior is consistent with the behavior for synchronous code. catch または finally 句で実行されたコードがエラーをスローした場合、プログラムは次の包含ブロック内から適切な catch 句を検索します。When code executed in a catch or finally clause throws, execution looks for a suitable catch clause in the next surrounding block. 現行の例外があった場合、その例外は失われます。If there was a current exception, that exception is lost. catch 句や finally 句で待機中の式についても、これと同じことが起こります。つまり、適切な catch が検索され、現行の例外がある場合は、その例外が失われます。The same happens with awaited expressions in catch and finally clauses: a suitable catch is searched for, and the current exception, if any, is lost.

注意

この動作を理由に、catch 句と finally 句は慎重に記述することが推奨されています (新しい例外が導入されないようにしてください)。This behavior is the reason it's recommended to write catch and finally clauses carefully, to avoid introducing new exceptions.

インデックス初期化子Index Initializers

インデックス初期化子は、コレクション初期化子の一貫性を高める 2 つの機能のうちの 1 つです。Index Initializers is one of two features that make collection initializers more consistent. C# の以前のリリースでは、コレクション初期化子はシーケンス スタイルのコレクションでのみ使用できました。In earlier releases of C#, you could use collection initializers only with sequence style collections:

private List<string> messages = new List<string> 
{
    "Page not Found",
    "Page moved, but left a forwarding address.",
    "The web server can't come out to play today."
};

C# 6 では、Dictionary<TKey,TValue> コレクションや、それと同様の型でも使用できるようになりました。Now, you can also use them with Dictionary<TKey,TValue> collections and similar types:

private Dictionary<int, string> webErrors = new Dictionary<int, string>
{
    [404] = "Page not Found",
    [302] = "Page moved, but left a forwarding address.",
    [500] = "The web server can't come out to play today."
};

この機能が加わったことにより、いくつかのバージョンでシーケンス コンテナーに使用されていたものと同様の構文を使用して、連想コンテナーを初期化できるようになりました。This feature means that associative containers can be initialized using syntax similar to what's been in place for sequence containers for several versions.

コレクション初期化子内の拡張 Add メソッドExtension Add methods in collection initializers

コレクション初期化を使いやすくするもう 1 つの機能として、 メソッドの拡張メソッドAddを使用できるようになりました。Another feature that makes collection initialization easier is the ability to use an extension method for the Add method. この機能は、Visual Basic との同等性を確保するために追加されました。This feature was added for parity with Visual Basic.

この機能は、新しい項目を意味的に追加するために、カスタム コレクション クラスで別の名前のメソッドを使用している場合に特に便利です。The feature is most useful when you have a custom collection class that has a method with a different name to semantically add new items.

たとえば、次のような生徒のコレクションがあるとします。For example, consider a collection of students like this:

public class Enrollment : IEnumerable<Student>
{
    private List<Student> allStudents = new List<Student>();

    public void Enroll(Student s)
    {
        allStudents.Add(s);
    }

    public IEnumerator<Student> GetEnumerator()
    {
        return ((IEnumerable<Student>)allStudents).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<Student>)allStudents).GetEnumerator();
    }
}

Enroll メソッドは生徒を追加しますが、The Enroll method adds a student. Add パターンには従いません。But it doesn't follow the Add pattern. C# の以前のバージョンでは、コレクション初期化子は Enrollment オブジェクトには使用できませんした。In previous versions of C#, you could not use collection initializers with an Enrollment object:

var classList = new Enrollment()
{
    new Student("Lessie", "Crosby"),
    new Student("Vicki", "Petty"),
    new Student("Ofelia", "Hobbs"),
    new Student("Leah", "Kinney"),
    new Student("Alton", "Stoker"),
    new Student("Luella", "Ferrell"),
    new Student("Marcy", "Riggs"),
    new Student("Ida", "Bean"),
    new Student("Ollie", "Cottle"),
    new Student("Tommy", "Broadnax"),
    new Student("Jody", "Yates"),
    new Student("Marguerite", "Dawson"),
    new Student("Francisca", "Barnett"),
    new Student("Arlene", "Velasquez"),
    new Student("Jodi", "Green"),
    new Student("Fran", "Mosley"),
    new Student("Taylor", "Nesmith"),
    new Student("Ernesto", "Greathouse"),
    new Student("Margret", "Albert"),
    new Student("Pansy", "House"),
    new Student("Sharon", "Byrd"),
    new Student("Keith", "Roldan"),
    new Student("Martha", "Miranda"),
    new Student("Kari", "Campos"),
    new Student("Muriel", "Middleton"),
    new Student("Georgette", "Jarvis"),
    new Student("Pam", "Boyle"),
    new Student("Deena", "Travis"),
    new Student("Cary", "Totten"),
    new Student("Althea", "Goodwin")
};

C# 6 ではこれが可能になりましたが、AddEnroll にマップする拡張メソッドを作成した場合に限られます。Now you can, but only if you create an extension method that maps Add to Enroll:

public static class StudentExtensions
{
    public static void Add(this Enrollment e, Student s) => e.Enroll(s);
}

この機能を使用してできることは、拡張メソッドを作成することによって、コレクションに項目を追加する任意のメソッドを、Add という名前のメソッドにマップするということです。What you are doing with this feature is to map whatever method adds items to a collection to a method named Add by creating an extension method:

public class Enrollment : IEnumerable<Student>
{
    private List<Student> allStudents = new List<Student>();

    public void Enroll(Student s)
    {
        allStudents.Add(s);
    }

    public IEnumerator<Student> GetEnumerator()
    {
        return ((IEnumerable<Student>)allStudents).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<Student>)allStudents).GetEnumerator();
    }
}
public class ClassList
{
    public Enrollment CreateEnrollment()
    {
        var classList = new Enrollment()
        {
            new Student("Lessie", "Crosby"),
            new Student("Vicki", "Petty"),
            new Student("Ofelia", "Hobbs"),
            new Student("Leah", "Kinney"),
            new Student("Alton", "Stoker"),
            new Student("Luella", "Ferrell"),
            new Student("Marcy", "Riggs"),
            new Student("Ida", "Bean"),
            new Student("Ollie", "Cottle"),
            new Student("Tommy", "Broadnax"),
            new Student("Jody", "Yates"),
            new Student("Marguerite", "Dawson"),
            new Student("Francisca", "Barnett"),
            new Student("Arlene", "Velasquez"),
            new Student("Jodi", "Green"),
            new Student("Fran", "Mosley"),
            new Student("Taylor", "Nesmith"),
            new Student("Ernesto", "Greathouse"),
            new Student("Margret", "Albert"),
            new Student("Pansy", "House"),
            new Student("Sharon", "Byrd"),
            new Student("Keith", "Roldan"),
            new Student("Martha", "Miranda"),
            new Student("Kari", "Campos"),
            new Student("Muriel", "Middleton"),
            new Student("Georgette", "Jarvis"),
            new Student("Pam", "Boyle"),
            new Student("Deena", "Travis"),
            new Student("Cary", "Totten"),
            new Student("Althea", "Goodwin")
        };
        return classList;
    }           
}

public static class StudentExtensions
{
    public static void Add(this Enrollment e, Student s) => e.Enroll(s);
}

オーバーロード解決の改善Improved overload resolution

最後に説明するのは、言われなければ気付かないかもしれない機能です。This last feature is one you probably won't notice. 以前のバージョンの C# コンパイラでは、ラムダ式を使用した一部のメソッド呼び出しがあいまいと判断されるコンストラクトがありました。There were constructs where the previous version of the C# compiler may have found some method calls involving lambda expressions ambiguous. たとえば、次のメソッドがあったとします。Consider this method:

static Task DoThings() 
{
     return Task.FromResult(0); 
}

以前のバージョンの C# では、メソッド グループ構文を使用してこのメソッドを呼び出すと、エラーが発生していました。In earlier versions of C#, calling that method using the method group syntax would fail:

Task.Run(DoThings); 

以前のコンパイラでは、Task.Run(Action)Task.Run(Func<Task>()) を正しく区別することができませんでした。The earlier compiler could not distinguish correctly between Task.Run(Action) and Task.Run(Func<Task>()). 以前のバージョンでは、引数としてラムダ式を使用する必要がありました。In previous versions, you'd need to use a lambda expression as an argument:

Task.Run(() => DoThings());

C# 6 の コンパイラは、Task.Run(Func<Task>()) のほうが適切であるということを正しく判断できます。The C# 6 compiler correctly determines that Task.Run(Func<Task>()) is a better choice.