皆さんこんにちは!
エンジニアの星加です。
今回はC# 14から追加されたExtension Membersについてご紹介します。
面白そうな機能が追加されたなと思い実際に触ってみたので、使い方や利用できそうなケースについてまとめてみました。
目次
拡張メソッドとは
本題に入る前に、まずは拡張メソッドについておさらいしましょう。
C#3.0で導入された拡張メソッドは、既存のクラス(例えばstringやint、あるいは外部ライブラリのクラスなど)に対して、後付けでメソッドを追加できる機能です。
拡張メソッドは、静的クラス中にthisキーワードをつけた静的メソッドを実装することで定義できます。
public class User(string firstName, string lastName, string email) { public string FirstName { get; set; } = firstName; public string LastName { get; set; } = lastName; public string Email { get; set; } = email; } public static class UserExtensions { // 拡張メソッド public static bool IsValidEmail(this User user) { return user.Email.Contains("@example.com"); } }
定義した拡張メソッドは、あたかもそのクラスのメソッドのように呼び出すことが可能です。
User user = new("Taro", "Yamada", "yamada.taro@example.com"); if (user.IsValidEmail()) { Console.WriteLine("有効なメールアドレスです。"); }
主に外部ライブラリのクラスの拡張や、プリミティブ型にユーティリティメソッドを追加する際などに活用されています。
また、.NETのLINQもこの仕組みで動作しています。
しかし、拡張メソッドは名前の通りメソッドしか拡張できず、プロパティの追加などはできませんでした。
Extension Members
今回C# 14から追加されたExtension Membersは、拡張メソッドを進化させたような機能です。
Extension Membersでは、以下を拡張することができます。
- メソッド
- プロパティ
- 演算子(本記事では割愛します)
- 静的メンバー
Extension Membersは、extensionという新しい構文を使用して定義します。
従来の this キーワードを使う方法と異なり、extension(型 仮引数名) というブロック内に複数のメンバーをまとめて記述できます。
public class User(string firstName, string lastName, string email) { public string FirstName { get; set; } = firstName; public string LastName { get; set; } = lastName; public string Email { get; set; } = email; } public static class UserExtensions { extension(User user) { public bool IsValidEmail() => user.Email.Contains("@example.com"); } }
このブロック内では、this の代わりに定義した仮引数名(ここでは user)を使ってインスタンスにアクセスします。
プロパティの拡張
プロパティの拡張では、これまでメソッドとして定義するしかなかったものをプロパティとして表現できます。
例えば、GetFullName() のようなメソッドを FullName プロパティとして定義できます。
public class User(string firstName, string lastName, string email) { public string FirstName { get; set; } = firstName; public string LastName { get; set; } = lastName; public string Email { get; set; } = email; } public static class UserExtensions { extension(User user) { public string FullName => $"{user.FirstName} {user.LastName}"; } }
User user = new("Taro", "Yamada", "yamada.taro@example.com"); Console.WriteLine(user.FullName); // 出力: Taro Yamada
プロパティとして定義することで、メソッド呼び出しではなくデータの参照として表現することができます。
静的メンバーの拡張
静的メンバーの拡張では、インスタンスではなく型そのものに対して静的メソッドやプロパティを追加できます。
例えば、DateTime のようなフレームワークの型に、標準では提供されていない変換メソッドを追加することができます。
public static class DateTimeExtensions { extension(DateTime) { public static DateTime FromUnixTime(long unixSeconds) => DateTimeOffset.FromUnixTimeSeconds(unixSeconds).UtcDateTime; } } // 型に対して直接呼び出せる DateTime dt = DateTime.FromUnixTime(1711123200);
従来であれば DateTimeHelper.FromUnixTime() のような別クラスを用意する必要がありましたが、Extension Membersを使うことで DateTime.FromUnixTime() として型に直接紐付けて呼び出せます。
利用例
では実際に、Extension Membersを活用できそうなケースを考えてみたいと思います。
例として、架空のCSVパースライブラリが提供する CsvRow クラスがあり、直接編集できない状況を想定します。
// 変更できない外部ライブラリのクラス(架空のCSVパースライブラリが提供する行のクラス) public class CsvRow { public string[] Values { get; set; } public int LineNumber { get; set; } }
このクラスに、「空行かどうかを判定するプロパティ」「フィールド数を検証するメソッド」「エラー時に使う空行の静的プロパティ」を追加してみます。
public static class CsvRowExtensions { // インスタンスに対する拡張 extension(CsvRow row) { // 全フィールドが空の行かどうか public bool IsEmpty => row.Values == null || row.Values.All(v => string.IsNullOrWhiteSpace(v)); // フィールド数が期待値と一致するかどうか public bool HasExpectedFields(int count) => row.Values?.Length == count; } // 型に対する拡張(静的メンバー) extension(CsvRow) { // エラー時などに使用する空行のデフォルト値 public static CsvRow Empty => new CsvRow { Values = [], LineNumber = -1 }; } }
例として、次のような商品データのCSVファイルを処理する場面を想定します。
| 商品名 | 価格 | 在庫数 |
|---|---|---|
| りんご | 150 | 100 |
| バナナ | 80 | 0 |
| (空行) | ||
| みかん | 120 | 50 |
このライブラリは、CSVを読み込んで各行を CsvRow として返します。拡張メンバーを追加したことで、次のように処理できます。
var csvRows = CsvParser.Parse("products.csv"); foreach (CsvRow row in csvRows) { // 空行をスキップ if (row.IsEmpty) continue; // フィールド数の検証(商品名・価格・在庫の3列を想定) if (!row.HasExpectedFields(3)) { Console.WriteLine($"行 {row.LineNumber}: フィールド数が不正です"); continue; } Console.WriteLine($"商品名: {row.Values[0]}, 価格: {row.Values[1]}, 在庫: {row.Values[2]}"); } // パース失敗時のフォールバック CsvRow result = TryGetFirstRow("data.csv") ?? CsvRow.Empty;
Extension Membersを使うことで、変更できないクラスに対しても外部からアプリケーション固有のロジックを追加し、ライブラリのクラス自身のメンバーと区別なく呼び出せます。
おわりに
今回はC# 14の新機能Extension Membersについて紹介しました!
開発において、外部ライブラリを利用する機会は多くあるため、ユーティリティ機能などをより自然に追加できるようになったのは嬉しいアップデートでした。
表現の幅が広がることで、プロジェクトに合わせた柔軟なコード設計がやりやすくなりそうです。
他にも、C# 14で気になる新機能があれば、ぜひ深掘りしてみたいと思います!
サービス一覧 www.alterbooth.com cloudpointer.tech www.alterbooth.com