Ver. 9.0
リリース時期 | 未定(おそらく .NET 5 ど同じ 2020/11) |
---|---|
同世代技術 |
|
要約・目玉機能 |
|
.NET 5 Preview 2 (2020/3)頃から、C# 9.0 機能もちらほらプレビュー実装され始めました。
プレビュー状態なので言語バージョンに preview
の指定が必要です。
執筆予定: C# 9.0 トラッキング issue
トップ レベル ステートメント
トップ レベル(top-leve: クラスや名前空間よりも外側、ファイル直下)にステートメントを直接書けるようになりました。
例えばよくある「Hello World」であれば、単に以下のように書けるようになります。
using System;
Console.WriteLine("Hello World!");
詳しくは「トップ レベル ステートメント」で説明します。
パターンの追加
C# 7.0から脈々と改善されてきたパターン マッチングですが、 C# 9.0 でもいくつかのパターンが追加されています。
// not, and, or や、 <, <=, >, >= などのパターンが増えた。
int M(uint x) => x switch
{
0 or 2 or 4 or 6 or 8 => 0,
1 or 3 or 5 or 7 or 9 => 1,
>= 10 => -1,
};
3世代かけてようやく当初予定(C# に追加すること自体は最初から決めていた機能)が全て入りました。 当初から予定に入ってたのは、既存のいくつかのプログラミング言語が同様の文法を持っていて、 網羅性や重複の検査を含めて実装手段が既知で、検討コストが低いからです。 それでも、需要が高いものから少しずつ実装した結果、3世代に分かれました。 3世代目なことを指して「パターン v3」(patterns v3)という俗称があったりもします。
詳しくは「パターン マッチング」で説明します。 C# 9.0 で追加されているのは以下の3つです。
ターゲット型推論の強化
ターゲットからの new 型推論
ターゲット型からの推論が効く場合に、new T()
の T
の部分を省略できるようになりました。
(target-typed new とか呼ばれたりします。)
特に、var
が使えず、
型名が長い特に便利です。
using System.Collections.Generic;
class Sample
{
// フィールドに対しては var が使えない。
// 代わりに new 型推論を使うと便利なことがある(特に、型名が長い時)。
Dictionary<string, List<(int x, int y)>> _cache = new();
}
詳しくは「ターゲットからの new 型推論」で説明します。
条件演算子のターゲット型推論
条件演算子の第2・第3項がターゲット型からの型推論するようになりました。
void M(bool b)
{
// int? を明示するとコンパイルできる(var だとダメ)。
int? i = b ? 1 : null;
// Base を明示するとコンパイルできる(var だとダメ)
Base c = b ? new A() : new B();
}
class Base { }
class A : Base { }
class B : Base { }
詳しくは「条件演算子のターゲット型推論」で説明します。 「型の決定」も参考にしてください。
unsafe/ネイティブ相互運用向け機能
C# 7.2の辺りから、 言語の方向性として生産性や安全性を優先する C# でも、 パフォーマンス改善を目的とするような言語機能が結構増えてきました。
また、 クロスプラットフォーム化が進んだことで、ネイティブ相互運用関連の機能も増えています。
この手の機能は一般的な開発者が直接触れることは少ないですが、 .NET ランタイム自体や、大規模に使われているライブラリのパフォーマンス改善につながり、 間接的にすべての C# 開発者が恩恵を受けるものになります。
ローカル変数の0初期化抑止
/unsafe
オプション指定時限定ですが、ローカル変数の0初期化を抑止できるようになりました。
using System;
using System.Runtime.CompilerServices;
using System.Text.Unicode;
m("aあ😀");
// この属性を付けると stackalloc の要素の0初期化がなくなる。
[SkipLocalsInit]
static void m(string s)
{
// UTF-16 の文字数に大して、UTF-8 のバイト数は最大でも3倍以内。
Span<byte> buffer = stackalloc byte[s.Length * 3];
Utf8.FromUtf16(s, buffer, out _, out var bytesWritten);
// FromUtf16 の仕様上、bytesWritten バイト目までは必ず上書きされる。
// 上書きされた部分だけを使う分には0初期化は「余計なお世話」。
var written = buffer[..bytesWritten];
foreach (var b in written)
{
Console.WriteLine(b);
}
}
詳しくは「ローカル変数の0初期化抑止」で説明します。
その他
null 許容参照型の改善
C# 8.0 で入った null 許容参照型に対してちょっと改善が入っています。 主に以下の2点です。
- 制約なしジェネリック型引数に
?
を付けれるようになった - アノテーション属性に
MemberNotNull
とMemberNotNullWhen
が増えた
ラムダ式の引数を破棄
ラムダ式の引数で、_
を使った値の破棄ができるようになりました。
static void Subscribe(INotifyPropertyChanged source)
{
// _ を破棄扱いして、2個以上並べられる
source.PropertyChanged += (_, _) => { };
}
詳細は「値の破棄 - ラムダ式の引数」で説明します。
ローカル関数への属性適用
ローカル関数に属性を付けられるようになりました。
using System;
using System.Diagnostics.CodeAnalysis;
m("", "");
static void m(string? a, string? b)
{
// C# 9.0 からローカル関数に属性を付けれる。
// C# 8.0 の null 許容参照型がらみで特に有用。
[return: NotNullIfNotNull("s")]
string? toLower(string? s) => s?.ToLower();
if (a is not null && b is not null)
{
// a, b の null 許容性が、NotNullIfNotNull 属性のおかげで al, bl に伝搬。
string al = toLower(a);
string bl = toLower(b);
// a, b が非 null なので、al, bl は非 null で確定済み。改めてのチェック不要。
Console.WriteLine(al.GetHashCode());
Console.WriteLine(bl.GetHashCode());
}
}
拡張メソッドでの GetEnumerator 実装
パターン ベースな foreach
、await foreach
で、拡張メソッドによる実装ができるようになりました。