1. はじめに
昔はインターフェースって、まるで私立学校の校則みたいに厳しかったんだよね:シグネチャだけ、フィールドなし、実装なし、静的メンバーもなし!でも.NETは進化してるし、プログラミング言語も生き物みたいなもんで、新しい課題に応えるために進化し続けてる。
新しいC#バージョンが来て、インターフェースも新しい技を覚えた。その中でも目立つのが静的メンバー。今やインターフェースにも静的メソッド、プロパティ、イベントが書けるようになった。C# 11以降では、静的抽象メソッド(static abstract)も宣言できて、インターフェースを実装する型に実装を要求できるようになったんだ。
これはパラダイムの大きな転換で、ジェネリックやオブジェクト指向プログラミングのやり方を変えるものだよ。
ざっくり言うと:インターフェースの静的メンバーってのは「共通」のメンバーで、オブジェクトのインスタンスじゃなくて、インターフェース型自体(または実装型)からアクセスするものなんだ。
ちょっと前まではクラス、構造体、enumだけが静的メソッドやプロパティを持てたけど、今はインターフェースもOKになった。
どんな感じ?シンタックス例
public interface IMyMath
{
static int Add(int a, int b) => a + b; // 静的メソッド(デフォルト実装)
static abstract int Multiply(int a, int b); // 実装型で実装必須
}
- static — インスタンスじゃなくて型レベルで使えるメンバー。
- static abstract — コントラクトが「実装型で静的メソッドを実装しろ」と要求する。
- インターフェース(クラスと同じく)でも静的メソッド、プロパティ、イベントが宣言できるようになった。定数も作れる。ただし、インスタンスフィールドや静的フィールド(定数以外)は今でもダメ。
インターフェースの静的メンバーの役割は、汎用的な「操作」を宣言できるようにすること。たとえば、オブジェクトのコレクションがあって「比較」したいけど、どの型がインターフェースを実装してるか知らなくても、静的抽象メンバーを使えばできるんだ。
2. インターフェース内の実装付き静的メソッド
これって何のため?
よくある悩み:インスタンスメソッド(オブジェクトで何かする)だけじゃなくて、静的メソッド(たとえば文字列から新しいオブジェクトを作るとか、2つのオブジェクトを静的に比較する)も書きたい。でも昔はパターン(Factory、Comparer、Helper)で頑張るしかなかった。今はインターフェースで直接書けるようになった!
特にGenericsや、いろんな型で動くアルゴリズムには超重要:
- 汎用オペレーター(たとえば加算、比較)の実装。
- ジェネリックコードの制約:「静的メソッドやオペレーターがある型だけOK」みたいな。
- シリアライズ/デシリアライズ:型を知らなくても文字列からオブジェクトを作りたいとき。
実装付き静的メソッド
C# 8から、インターフェースで実装付き静的メソッドがOKになった。クラスの静的メソッドと似てるよ。
public interface IUtility
{
static void PrintHello()
{
Console.WriteLine("インターフェースからこんにちは!");
}
}
こういうメソッドは IUtility.PrintHello(); みたいに呼べる。
インターフェースに論理的に属するけど、特定の実装には関係ないヘルパー関数に便利。たとえば型全体の統計、ファクトリーメソッド(CreateDefault)、共通のバリデーション(値のチェックとか)。
ポイント:インターフェースの静的メンバーはクラスで「オーバーライド」されない
インターフェースでstatic void Method() { ... }って宣言しても、実装クラスで同じシグネチャの静的メソッドを書いてもオーバーライドじゃない!ただの別メソッドで、名前が同じでも「仮想静的メソッド」にはならないよ。
3. 静的抽象メンバー
C# 11から、インターフェースでstatic abstractメソッドが宣言できるようになった。つまり「このインターフェースを実装するクラスや構造体は、同じシグネチャの静的メンバーを必ず実装しろ」ってこと。
例:
public interface IParsable<T>
{
static abstract T Parse(string s);
}
このインターフェースを実装する型は、静的メソッド Parse(string s) を必ず書かないといけない。
クラスでの実装例
public class Temperature : IParsable<Temperature>
{
public int Value { get; set; }
// 静的実装!
public static Temperature Parse(string s)
{
var temp = new Temperature();
temp.Value = int.Parse(s);
return temp;
}
}
どう動くの?
これはジェネリックコード(Generics)で特に面白い:
public static T ParseFromString<T>(string s) where T : IParsable<T>
{
return T.Parse(s);
}
// 使い方:
var temp = ParseFromString<Temperature>("42");
これで「静的」な振る舞いを持つ型なら何でも動く、ほんとに汎用的なコードが書ける!
4. インターフェースの静的メンバー vs. クラスの普通の静的メンバー
| 特徴 | クラスの静的メンバー | インターフェースの静的メンバー |
|---|---|---|
| 継承されるか | いいえ | いいえ、でもインターフェース契約として実装される |
| 実装必須か | いいえ | static abstract の場合だけ |
| Genericsで使えるか | いいえ(C# 11まで) | はい(static abstract から) |
| デフォルト実装できるか | はい | はい |
| オーバーライドできるか | いいえ | いいえ、必ず実装するだけ |
| 呼び出し時の見え方 | 型名で呼ぶ | インターフェース型か実装型で呼ぶ |
7. 実際の例
新しい機能で、ちょっとした学習用アプリをどう良くできるか見てみよう。
たとえば「IPrintable」インターフェース:
public interface IPrintable
{
void Print();
static void PrintAll(IEnumerable<IPrintable> items)
{
foreach (var item in items)
{
item.Print();
}
}
}
これで便利に呼べる:
var documents = new List<IPrintable>
{
new Invoice { Number = "INV-001" },
new Receipt { Number = "RC-007" }
};
IPrintable.PrintAll(documents); // インターフェースの静的メソッド!
こういうアーキテクチャは、インターフェースの全実装に対して「グループ操作」したいときにピッタリ。
もっと進んだ例:数値型の汎用加算
たとえばこんなインターフェース:
public interface IAddable<T>
{
static abstract T Add(T left, T right);
}
整数用の実装(ラッパークラス):
public struct MyInt : IAddable<MyInt>
{
public int Value { get; }
public MyInt(int val) => Value = val;
public static MyInt Add(MyInt left, MyInt right) => new MyInt(left.Value + right.Value);
}
そして、T型の2つの数を加算する汎用関数:
public static T Sum<T>(T a, T b) where T : IAddable<T>
{
return T.Add(a, b);
}
// 使い方:
var x = new MyInt(5);
var y = new MyInt(6);
var z = Sum(x, y); // z.Value == 11
こういう汎用性のために、インターフェースの静的メンバーがサポートされたんだよ!
8. よくあるミスや注意点
新機能って、例だけ見ると簡単そうだけど、実際はそうでもない。初心者がハマりやすいポイントをいくつか紹介するね:
インターフェースの静的メソッドは、実装クラスに「継承」されない。 インターフェースでstatic void Foo()って書いても、MyClass.Foo()とIMyInterface.Foo()は全く別物。
Static abstractは実装必須。 忘れると、コンパイラが「インターフェースを完全に実装してないよ」って怒る。
ジェネリック制約: 静的抽象メンバーを使うには、ジェネリックパラメータでインターフェース制約(where T : IMyInterface)が必要。
まだ全部のツールが新機能に対応してるわけじゃない。 たとえばRiderやVS Code、古いRoslynアナライザーは、.NETのバージョンがC# 11+じゃないとstatic abstractメンバーをちゃんと表示できないことがある。
インターフェースのextensionメソッドと混同しないで: extensionメソッドは別で実装されてて、静的メンバーとは違うよ。
GO TO FULL VERSION