C# 10は、.NET 6とともにリリースされたC#言語の重要なマイルストーンです。

このバージョンでは、開発者の生産性向上ボイラープレート(定型コード)の削減、そしてランタイムのパフォーマンス改善に主眼が置かれました。

特に、現代のソフトウェア開発において求められる「簡潔で読みやすいコード」を実現するための構文が数多く導入されています。

従来のC#では、単純なコンソールアプリケーションを作成する際にも、多くの using ディレクティブや入れ子になった名前空間の記述が必要でした。

C# 10は、こうした「儀式」とも呼べるコードを徹底的に削ぎ落とし、ロジックそのものに集中できる環境を提供します。

本記事では、C# 10で導入された主要な新機能から、知っておくと便利な細かい改善点まで、具体的なソースコードを交えて詳しく解説します。

グローバルな using ディレクティブ(Global Using Directives)

C# 10において最も目に見える変化の一つが、グローバルな using ディレクティブの導入です。

これまでは、すべてのソースファイル(.cs)の先頭に、同じような using System;using System.Collections.Generic; といった記述を繰り返す必要がありました。

グローバル using の基本構文

global 修飾子を using の前に付けることで、その名前空間はプロジェクト内のすべてのファイルに適用されます。

通常は、GlobalUsings.cs といった名前のファイルを一つ作成し、そこにプロジェクト全体で共有したい名前空間をまとめて記述するのが一般的なプラクティスです。

C#
// GlobalUsings.cs
// プロジェクト全体で利用する名前空間を一括定義
global using System;
global using System.IO;
global using System.Net.Http;
global using System.Threading.Tasks;
global using Microsoft.Extensions.Logging;

このように定義しておけば、他のファイルではこれらの名前空間を記述することなく、直接クラスやメソッドを使用できます。

これにより、各ファイルの冒頭がスッキリとし、本質的なコードの可読性が大幅に向上します。

暗黙的な using (Implicit Usings)

グローバル using をさらに一歩進めた機能が、SDK レベルで提供される「暗黙的な using 」です。

プロジェクトファイル(.csproj)の中で <ImplicitUsings>enable</ImplicitUsings> が設定されている場合、プロジェクトの種類(コンソール、Web、クラスライブラリなど)に応じて、標準的な名前空間が自動的にグローバル using として追加されます。

例えば、ASP.NET Core プロジェクトであれば、Microsoft.AspNetCore.Http などが自動的にインポートされます。

これにより、開発者は意識することなく、最小限の記述でコーディングを開始できるようになりました。

ファイルスコープの名前空間宣言(File-scoped Namespaces)

C# 10では、名前空間の宣言をより簡潔にするためのファイルスコープの名前空間宣言が導入されました。

従来の書き方と新しい書き方の比較

これまでの C# では、名前空間を宣言する際に中括弧 {} を使用し、その中にあるすべてのコードを一段階インデントさせる必要がありました。

C#
// 従来の書き方
namespace MyProject.Services
{
    public class DataService
    {
        public void Process() { }
    }
}

ファイル内に一つの名前空間しか存在しない場合、このインデントは画面の横幅を無駄に占有してしまいます。

C# 10からは、セミコロン ; を使用した新しい構文が利用可能です。

C#
// C# 10 のファイルスコープ名前空間
namespace MyProject.Services;

public class DataService
{
    public void Process() { }
}

この構文を採用することで、不要なインデントが解消され、特にネストが深くなりがちな大規模なクラスにおいて、コードの見通しが劇的に改善されます。

現在の C# 開発において、最も推奨される記述スタイルの一つです。

レコード構造体(Record Structs)

C# 9 で導入された「レコード(Record)」は、不変(イミュータブル)なデータモデルを定義するのに非常に強力な機能でした。

しかし、C# 9 のレコードは参照型(クラス)に限定されていました。

C# 10 では、値型としてのレコードである record struct が追加されました。

レコード構造体の特徴

レコード構造体は、従来の struct のメリット(スタック割り当てによるメモリ効率)と、record のメリット(値ベースの比較、簡潔な構文)を兼ね備えています。

C#
// 定義例
public record struct Point(double X, double Y);

// 使用例
var p1 = new Point(10, 20);
var p2 = new Point(10, 20);

// 値ベースの比較(プロパティの値が同じなら true)
Console.WriteLine(p1 == p2); // Output: True

// 非破壊的変更(with 式)
var p3 = p1 with { X = 30 };
Console.WriteLine(p3); // Output: Point { X = 30, Y = 20 }

record class と record struct の使い分け

C# 10 以降、従来のレコードは暗黙的に record class とみなされます。

  • record class:大きなデータ構造や、継承を利用したい場合に適しています。
  • record struct:座標、色、複素数など、小さく、頻繁に生成・破棄されるデータに適しており、ガベージコレクション(GC)の負荷を軽減できます。

また、readonly record struct と宣言することで、完全な不変性を持たせることも可能です。

ラムダ式の改善

C# 10 では、ラムダ式の記述がより自然で強力なものになりました。

コンパイラが型を推論できる範囲が広がり、明示的な型指定を省略できるケースが増えています。

自然な型(Natural Types)の推論

以前は、ラムダ式を変数に代入する際、Func<...>Action<...> を明示的に記述する必要がありました。

C# 10 では var を使用できるようになりました。

C#
// C# 10 から可能になった書き方
var parse = (string s) => int.Parse(s);

// 戻り値を明示することも可能
var choose = object (bool b) => b ? 1 : "two";

var を使った宣言により、コードが簡潔になるだけでなく、複雑なシグネチャを持つラムダ式の扱いが容易になります。

属性の適用と戻り値の型指定

ラムダ式に対して、メソッドと同じように属性(Attribute)を付与したり、戻り値の型を明示的に指定したりできるようになりました。

これは、Minimal APIs などのモダンなフレームワークで、エンドポイントのメタデータを記述する際に非常に役立ちます。

C#
// 戻り値の型を明示し、属性を付与する例
var handler = [Authorize] string (int id) => $"User {id}";

拡張されたプロパティパターン(Extended Property Patterns)

パターンマッチング機能も、C# 10 でさらに洗練されました。

ネストされたプロパティへのアクセスが、より直感的に記述できるようになっています。

ドット記法によるネストアクセス

従来の C# では、ネストされたオブジェクトのプロパティをチェックする場合、中括弧を入れ子にする必要がありました。

C#
// 従来の書き方
if (user is { Address: { City: "Tokyo" } })
{
    // 処理
}

C# 10 では、ドット記法を使用してフラットに記述できます。

C#
// C# 10 の書き方
if (user is { Address.City: "Tokyo" })
{
    // 処理
}

この改善は小さく見えますが、複雑なデータ構造を扱う条件分岐において、コードの可読性を劇的に向上させます。

定数補完文字列(Constant Interpolated Strings)

文字列補完(Interpolated Strings)は非常に便利な機能ですが、これまでは「定数(const)」の定義には使用できませんでした。

C# 10 では、補完される要素自体が定数文字列である場合に限り、文字列補完を const として宣言できるようになりました。

C#
public class ApiRoutes
{
    private const string BaseUrl = "https://api.example.com";
    
    // C# 10 ではこれが可能
    public const string GetUsers = $"{BaseUrl}/users";
    public const string GetOrders = $"{BaseUrl}/orders";
}

以前は + 演算子による結合が必要でしたが、この機能により URL のパス定義やログのフォーマット定義などがより安全かつ分かりやすく記述できます。

構造体の改善:引数なしコンストラクタとフィールド初期化子

C# 10 では、struct における長年の制限がいくつか撤廃されました。

引数なしコンストラクタの定義

これまでの構造体では、引数を持たないコンストラクタを独自に定義することができませんでしたが、C# 10 からは可能になりました。

また、フィールドの宣言時に直接初期値を代入する「フィールド初期化子」も利用可能です。

C#
public struct Measurement
{
    // フィールド初期化子
    public double Value = 1.0;
    public string Unit = "gram";

    // 引数なしコンストラクタ
    public Measurement()
    {
        // 独自の初期化ロジック
    }
}

これにより、構造体を利用する際の「デフォルト状態」をより柔軟に制御できるようになります。

ただし、default キーワードや配列の確保時には、依然としてこれらの初期化子は実行されない(ゼロクリアされる)点には注意が必要です。

CallerArgumentExpression 属性

デバッグや引数チェックに便利な新機能として、CallerArgumentExpression 属性が追加されました。

この属性を使用すると、メソッドに渡された引数の式の文字列を取得できます。

活用例:ガード句の簡略化

バリデーションライブラリなどで、どの変数がエラーを引き起こしたかを動的に取得する際に威力を発揮します。

C#
using System.Runtime.CompilerServices;

public static class Guard
{
    public static void IsNotNull(object? obj, [CallerArgumentExpression("obj")] string? expression = null)
    {
        if (obj is null)
        {
            throw new ArgumentNullException(expression, "値が null です");
        }
    }
}

// 使用時
string? myName = null;
Guard.IsNotNull(myName);

上記コードを実行すると、例外メッセージには自動的に "myName" という変数名が含まれます。

従来のように文字列を手動で渡す必要がなくなり、リファクタリング耐性の高い安全なコードが書けるようになります。

補完文字列ハンドラー(Interpolated String Handlers)

これは主にライブラリ作者やパフォーマンスを極限まで追求する開発者向けの機能ですが、実行時の文字列処理を最適化するための仕組みが導入されました。

従来の文字列補完は、内部的に string.Format が呼ばれたり、ボクシングが発生したりすることがありました。

C# 10 では、補完文字列ハンドラーをカスタマイズすることで、不必要なメモリ割り当てを避け、非常に高速な文字列処理を実現できます。

たとえば、ログレベルが無効な場合に文字列構築そのものをスキップするといった、高度な最適化が可能になっています。

その他の細かな改善点

C# 10 には、上記以外にも開発をスムーズにする多くの小さな改善が含まれています。

  • 分解(Deconstruction)の改善:変数の宣言と既存の変数への代入を、一つの分解式の中で混在させることができるようになりました。
  • record の ToString() に sealed を指定可能:派生レコードで ToString の挙動を上書きさせないように制限できます。
  • ソースジェネレータの強化:コンパイル時のコード生成機能がより安定し、パフォーマンスが向上しました。

分解の混合の例

C#
int x = 0;
// x は既存の変数、y は新しい変数の宣言
(x, int y) = (10, 20); 

Console.WriteLine($"x: {x}, y: {y}");
機能概要主なメリット
Global Usingsプロジェクト全体で using を共有ボイラープレートの削減
File-scoped Namespaces名前空間の {} を省略インデントの解消、可読性向上
Record Structs値型のレコードメモリ効率と不変性の両立
Lambda Improvements型推論の強化、属性付与ラムダ式の柔軟性向上
Extended Property Patternsドット記法でのパターンマッチ条件分岐の簡略化

まとめ

C# 10は、従来のバージョンに比べて劇的なパラダイムシフトを起こすものではありませんが、「日々のコーディングにおけるストレスを軽減する」という点において、非常に完成度の高いアップデートとなっています。

グローバル using やファイルスコープ名前空間によってソースファイルは驚くほどクリーンになり、レコード構造体によってデータモデルの設計がより柔軟になりました。

また、ラムダ式やパターンマッチングの進化は、より直感的でバグの少ない実装をサポートします。

これらの新機能を活用することで、開発者はインフラストラクチャ的なコードの記述から解放され、ドメインロジックの構築に専念できるようになります。

これから新規プロジェクトを開始する場合や、既存の .NET プロジェクトをアップグレードする際は、ぜひ C# 10 の新しい構文を積極的に取り入れ、モダンで効率的な C# 開発を体験してください。