1. はじめに
昔はクラスに追加できたのは Extensionメソッド だけだったんだ。でも、プロパティ(property)をクラス拡張として追加することはできなかった!つまり、どんな DateTime にも IsWeekend プロパティを持たせたかったら、メソッドでやるしかなかった。これってちょっと不便だよね ― 特に date.IsWeekend みたいなシンタックスが欲しいのに、date.IsWeekend() って書かないといけなかったから。
C# 14 の登場で夢が叶った!今はプロパティ拡張も、ちょっとメソッドっぽい書き方でできるようになったよ。既存の型のソースコードをいじらずに新しいプロパティを追加できて、しかも普通のプロパティみたいに使える!
どんな時に便利?
- .NETの標準型や外部ライブラリみたいに、コードをいじれない時。
- ViewモデルやUI、計算プロパティみたいな「仮想」プロパティを追加したい時。
- .SomeCalculatedProperty みたいなカッコいいシンタックスが欲しい時。
2. C# 14 の新しいシンタックス
C# 14 では、拡張用の this キーワードをやめて、代わりに extension(Type this) っていう特別なオペレーターが導入されたんだ。これでクラスに新しい仮想メンバーを追加できるようになったよ。
古いシンタックス
public static class StringExtensions
{
public static bool IsNullOrWhiteSpace(this string str)
=> string.IsNullOrWhiteSpace(str);
}
新しいシンタックス
public static class Enumerable
{
extension(TSource source)
{
// オブジェクト用の仮想メンバー
}
extension(TSource)
{
// 仮想staticメンバー
}
}
3. Extension Propertyのシンタックス
どんな感じ?
例えば DateTime 型に「この日付が週末かどうか」を返すプロパティを追加したいとするよ:
public static class DateTimeExtensions
{
public static bool IsWeekend(this DateTime date) =>
date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
}
でもこれは昔ながらのExtension Methodで、呼び出しは myDate.IsWeekend() になる。
新スタイル ― Extension Property
public static class DateTimeExtensions
{
extension(DateTime source)
{
public bool IsWeekend // extension property!
{
get => source.DayOfWeek == DayOfWeek.Saturday || source.DayOfWeek == DayOfWeek.Sunday;
}
}
}
これで、こんな風に書けるようになるよ:
DateTime today = DateTime.Now;
if (today.IsWeekend)
{
Console.WriteLine("もっと寝てていいよ!");
}
4. どんなextension propertiesがある?
read-onlyだけじゃなくて、read-writeプロパティも追加できるよ:
public static class DateTimeExtensions
{
extension(DateTime source)
{
public int YearFrom1900 // extension property!
{
get => source.Year - 1900;
set => source.Year = value + 1900;
}
}
}
5. staticプロパティ
新しいシンタックスのおかげで、仮想プロパティはオブジェクトだけじゃなくクラス自体にも追加できる!そう、つまり仮想staticプロパティも追加できるようになったんだ。シンタックスはほぼ同じ:
public static class DateTimeExtensions
{
// 新しいシンタックス: DateTime用のextension block (C# 14)
extension(DateTime)
{
// ユーザーの現在のタイムゾーンを保存するstaticプロパティ
private static TimeZoneInfo _currentTimeZone = TimeZoneInfo.Local;
// 現在のタイムゾーンにアクセスするextension property
public static TimeZoneInfo CurrentTimeZone
{
get => _currentTimeZone;
set => _currentTimeZone = value ?? TimeZoneInfo.Local;
}
// ユーザーのタイムゾーンでの時刻を返す
public DateTime InCurrentTimeZone
{
get => TimeZoneInfo.ConvertTime(this, CurrentTimeZone);
}
}
}
6. 例:Dogクラスを拡張しよう!
じゃあ、前のレクチャーから引き続き、犬アプリを進化させてみよう。
今度は犬だけじゃなくて、ワクチン接種リスト(List<DateTime>)もあるとする。でもDogクラスは外部ライブラリのやつで、コードをいじれない。そこで追加したいプロパティは:今年必要なワクチンは全部打った?
昔のやり方(extensionメソッド):
public static class DogExtensions
{
public static bool AllVaccinatedThisYear(this Dog dog)
{
return dog.Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
// 使い方:
if (myDog.AllVaccinatedThisYear())
Console.WriteLine("犬は元気だよ!");
新しいやり方(extension property):
public static class DogExtensions
{
extension(Dog)
{
public bool AllVaccinatedThisYear
{
get => Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
}
// 使い方:
if (myDog.AllVaccinatedThisYear)
Console.WriteLine("犬は元気だよ!");
フルサンプル
// Dogクラス ― 例えば外部ライブラリのやつ:
public class Dog
{
public string Name { get; set; }
public List<DateTime> Vaccinations { get; set; } = new List<DateTime>();
}
// extension block付きの拡張クラス:
public static class DogExtensions
{
extension(Dog)
{
public bool AllVaccinatedThisYear
{
get => Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
}
// プログラムで:
var myDog = new Dog { Name = "Druzhok" };
myDog.Vaccinations.Add(new DateTime(DateTime.Now.Year, 2, 16)); // 今年のワクチン
Console.WriteLine($"{myDog.Name}: ワクチン済み? {(myDog.AllVaccinatedThisYear ? "はい" : "いいえ")}");
7. 比較表: Extensionメソッド vs Extensionプロパティ
| Extensionメソッド | Extensionプロパティ | |
|---|---|---|
| 呼び出しシンタックス | obj.Method() | obj.Property |
| パラメータ渡せる? | はい | いいえ(this objだけ) |
| 書き込みできる? | 適用外 | 実装されていればOK |
| View/UIに便利? | たまに | はい(双方向バインディングやラムダ) |
| リフレクションで見える? | いいえ | いいえ |
8. ありがちな落とし穴とミス
extensionプロパティが役立たない時
- 本物のフィールドの代わりにはならない:オブジェクトのprivateメンバーにはアクセスできない。
- 自動でオブジェクトの状態を変えることはできない(元クラスの値変更を直接トラッキングできない)。
- extension propertyでインターフェースや抽象クラスの実装はできない。
名前の衝突ミス
もし元のクラスに同じ名前のプロパティが追加されたら、「本家」の方が優先されて、拡張プロパティは使われない。だから.NETの公開型を拡張する時は特に、名前選びは慎重に!
シリアライズ/リフレクションの問題
Extension property は「本物の」型メンバーじゃなくて、ただの便利なシンタックスシュガー。だからリフレクションやシリアライズで使うと、拡張プロパティは見えないことがあるよ。
GO TO FULL VERSION