目次

Ver. 10.0
リリース時期 2021/11
同世代技術
  • .NET 6.0
  • Visual Studio 2022
要約・目玉機能

※一部、まだ記事化(めったに使わない機能や細かい修正の紹介)が完了していません:

執筆予定: C# 10.0 トラッキング issue

record struct

C# 9.0 (レコード型の最初のバージョン)では、レコード型は常に参照型(クラスと同系統の型)になります。 これに対して C# 10.0 では値型も選べるようにしました。 そのため、以下のように、record classrecord struct というキーワードで書き分けができるようになりました。

record class Reference(int X, int Y); // record だけ書いた場合こちらと同じ意味
record struct Value(int X, int Y);

詳しくは 「レコード型」のページ内に色々と追記しました。

構造体の引数なしコンストラクター

構造体に引数なしコンストラクターとかフィールド初期化子を書けるようになりました。

struct A
{
    public int X;
    public A() => X = 1;
}

これで、new A()X が1になります。 詳しくは「引数なしコンストラクター」で説明します。

文字列補間

文字列補間に2点ほど改善が入りました。

パフォーマンス改善

string.Format を使った実装ではどうしてもパフォーマンス上の改善が難しく、 別の型を使って結構複雑なコードに変換する最適化が入りました。 条件を満たす場合、

var formatted = $"({x}, {y})";

このコードは string.Format ではなく、以下のようなコードに展開されます。

DefaultInterpolatedStringHandler handler = new DefaultInterpolatedStringHandler(4, 2);
handler.AppendLiteral("(");
handler.AppendFormatted(x);
handler.AppendLiteral(", ");
handler.AppendFormatted(y);
handler.AppendLiteral(")");
string s = handler.ToStringAndClear();

詳しくは「C# 10.0 の補間文字列の改善」で説明します。

const 文字列補間

文字列補間でも、{} の中身が const 文字列な場合に限り、補完結果も const にできます。 例えば以下のような const 文字列を作れます。

const string A = "Abc";
const string B = "Xyz";
const string C = $"{nameof(A)}: {A}, {nameof(B)}: {B}"; // "A: Abc, B: Xyz"

詳しくは「const 文字列補間」で説明します。

CallerArgumentExpression 属性

CallerArgumentExpression 属性を使って、メソッド呼び出し元でどの引数にどういう式を渡したかを文字列として取れるようになりました。

using System.Runtime.CompilerServices;

m(2 * 3 * 5);

static void m(
    int x,
    [CallerArgumentExpression("x")] string? expression = null)
{
    Console.WriteLine($"{expression} = {x}");
}
2 * 3 * 5 = 30

詳しくは「呼び出し元情報(caller info)」で説明します。

シンプル プログラム

C# 9.0 のトップ レベル ステートメントに続いて、シンプルなプログラムであればシンプルなソースコードで書けるようになる機能が増えています。

これらの機能によって、いわゆる Hello World プログラムを以下の1行で書けるようになりました。

Console.WriteLine("Hello, World!");

実際、 .NET 6 からはコンソール アプリのプロジェクト テンプレートがこの1ファイル、1行だけのものになっています。

また、Web アプリ用のテンプレートも以下のような1ファイルのコードになりました。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

参考: 「最初の C# プログラム

これらを実現するために、C# の文法にもいくつかの新機能が追加されました。

ファイル スコープ名前空間

C# 10.0 から {} なしの以下のような書き方で名前空間を指定できるようになりました。

namespace Namespace;

class A { }

これで以下のコードと同じ意味になります。

namespace Namespace
{
    class A { }
}

詳しくは「ファイル スコープ namespace」で説明します。

global using

using ディレクティブの前に global という修飾を付けることで、 プロジェクト内全域に対して影響を及ぼす using (名前空間の参照)ができるようになりました。

例えば、プロジェクト内のどこか1つのファイルに以下のようなコードを書いたとします。

global using System;

これで、このプロジェクト内のすべてのファイルで、ファイルの先頭に using System; を書いたのと同じ状態になります。

詳しくは「global using」で説明します。

ラムダ式の改善(自然な型決定、戻り値明示、属性指定)

Web アプリ用テンプレートの MapGet を実現するために、 ラムダ式とデリゲートに以下の3つの機能が追加されました。

  • 自然な型決定
  • ラムダ式の戻り値の明示
  • ラムダ式への属性

これらにより、ラムダ式やデリゲートを以下のように書けるようになりました。

var f =
    [A]
    [return: B]
    static int ([C] int x)
    => x;

詳しくは「デリゲートの自然な型」と「ラムダ式の戻り値の明示と属性」で説明します。

その他

プロパティ パターンの拡張(入れ子のメンバー参照)

入れ子のプロパティ・フィールド参照でプロパティ パターンを書けるようになりました。

    if (x is { Name.Length: 1 })
    {
        Console.WriteLine("single-char Name");
    }

詳しくは「プロパティ パターン」で説明します。

分解宣言と分解代入の混在

分解代入と分解宣言の混在もできるようになりました。

int x;
(x, var u) = (1, 2);

ただし、式の途中に分解宣言 (var 付きの宣言) が来るようなコードは C# 10.0 でも書けません。

int x, y;
(x, var u) = (var v, y) = (1, 2);

明確な初期化ルールの改善

明確な初期化ルール(未初期化のまま変数から値を読めないようにするフロー解析)に関する改善がありました。 これまでは ?.?? が絡んだ時の判定があまり賢くなかったんですが、C# 10 で改善しました。

例えば以下のコードは C# 10 以降でだけコンパイルできます。

// C# 10 から大丈夫な例: ?. == true。
void m(Dictionary<int, int>? d)
{
    if (d?.TryGetValue(123, out var x) == true)
    {
        // C# 10 から大丈夫になった。
        // (前までは ?. からの == true は判定漏れでエラー。)
        Console.WriteLine(x);
    }
}

[雑記] 明確な代入ルール」で説明しています。

更新履歴

ブログ