by WebSurfer
17. January 2024 14:54
C# 8.0 以降で、interface のメンバーに「既定の実装 (default implementation)」を設定できるようになり、それに関連してアクセス修飾子に private, protected, internal などを設定することが可能になりました。
ちなみに、C# 8.0 より前 (.NET Core 3 より前、.NET Framework はすべて) では interface のメンバーのアクセス修飾子は public しか許されておらず、継承する class 側でメンバーを実装する際にもアクセス修飾子に public と明示的に指定する必要がありました。
public 以外が許されなかった理由は、自分が調べたことの要約ですが、以下のようなことと理解しています。 (理由が明確に書いてある Microsoft のドキュメントは見つかりませんでした)
-
class が interface を継承すると、その class が必ず interface に定義されているメンバーを実装して公開するという外部との契約となる。(Interface の仕様に "An interface defines a contract. A class or struct that implements an interface shall adhere to its contract." と書いてあります)
-
別の言い方をすると、そもそも interface に指定されるメンバーを実装して class を利用する外部に公開するのが目的なのに、private とか protected で隠ぺいするのは理にかなってない。
上記にもかかわらず、C# 8.0 以降で interface のメンバーに private や protected などを設定できるようになったのは何故か、その理由に興味があったので調べてみました。以下に調べたことを備忘録として書いておきます。
調べたことを簡単に書くと、C# 8.0 で interface に「既定の実装」という機能を追加する際に、ついでに public 以外のあらゆるアクセス修飾子を設定できるようにし、「既定の実装」に対するアクセスコントロールを可能にするというのが目的らしいです。
そのあたりのことが書いてあったドキュメントと、関係する部分の抜粋を以下に書いておきます。
-
アクセス修飾子 (C# プログラミング ガイド) の「その他の型」
"インターフェイス メンバー宣言には、あらゆるアクセス修飾子を含めることができます。 そのことは、クラスを実装するあらゆるもので必要になる共通実装を静的メソッドから与えるときに最も役に立ちます。Interface member declarations may include any access modifier. This is most useful for static methods to provide common implementations needed by all implementors of a class."
-
interface (C# リファレンス)
"インターフェイスによってメンバーの既定の実装を定義できます。 共通の機能を 1 回で実装する目的で static メンバーも定義できます。 An interface may define a default implementation for members. It may also define static members in order to provide a single implementation for common functionality."
-
default interface methods
"Add support for virtual extension methods - methods in interfaces with concrete implementations. A class or struct that implements such an interface is required to have a single most specific implementation for the interface method, either implemented by the class or struct, or inherited from its base classes or interfaces."
-
インターフェイスのデフォルト実装
"メソッド、プロパティ、インデクサー、イベントのアクセサーの実装を持てるようになった。アクセシビリティを明示的に指定できるようになった。静的メンバーを持てるようになった・・・中略・・・狭義にはこの1番目の機能こそが「デフォルト実装」です。 ただ、これのついでに実装されたものなので2番目、3番目には具体的な名前がついていません"
以下に、interface のメンバーを「既定の実装」とし、アクセス修飾子に public, private, protected, internal を使ったサンプルを載せておきます。説明はコード中にコメントとして書きましたのでそちらを見てください。
namespace ConsoleAppInterface
{
internal class Program
{
static void Main(string[] args)
{
Sample sample = new();
((ISampleDerived)sample).PublicMethod();
sample.PublicMethod2();
sample.Protected3();
//((ISampleDerived)sample).Protected2(); // アクセス不可
}
}
// C# 8 以降で、interface のメンバーに「既定の実装 (default
// implementation)」を設定できるようになり、関連してアクセス修飾子に
// private, protected, internal などを設定することが可能になった
interface ISample
{
// デフォルトで public なのは以前と同じ。なので、アクセス修飾子を付
// けない場合は public になる
void Public()
{
Console.WriteLine("ISample.Public");
Private(); // private メンバーにアクセス
}
internal void Internal()
{
Console.WriteLine("ISample.Internal");
}
protected void Protected()
{
Console.WriteLine("ISample.Protected");
}
// private の場合「既定の実装」は必須。無いと以下のエラー:
// エラー CS0501 'ISample.Private()' は abstract、extern、または
// partial に指定されていないため、本体を宣言する必要があります
private void Private()
{
Console.WriteLine("ISample.Private");
}
}
interface ISampleDerived : ISample
{
void PublicMethod()
{
// interface で interface を継承する場合、継承元の public,
// internal, protected メソッドを呼べる。「既定の実装」の有無
// も関係なく呼べる
Public();
Internal();
Protected();
// Private(); private はもちろんダメ
}
void Default()
{
Console.WriteLine("ISampleDerived.Defualt");
}
// 派生先から protected メンバーにアクセスできるのは interface
// だけ。class から呼ぶことはできない。呼ぶとエラーになる。下の
// Sample の実装の PublicMethod2 メソッド内の説明を参照
protected void Protected2()
{
Console.WriteLine("ISampleDerived.Protected2");
}
// 以下のような「既定の実装」がない場合、継承する class 側で実装が
// 必要。ただし、継承する class 側では public にしないとエラー
protected void Protected3();
}
public class Sample : ISampleDerived
{
// 継承元の ISampleDerived 内で「既定の実装」がされているメソッド
// (この例では PublicMethod, Default, Protected2)は継承するクラ
// スでの実装が無くてもエラーにならない
public void PublicMethod2()
{
// Default を呼ぶには 1 段キャストが必要。単に Default(); とし
// たのではエラー
((ISampleDerived)this).Default();
// interface と違って class では protected なものは呼べない。
//((ISampleDerived)this).Protected2();
// ・・・とすると以下のエラーとなる:
// エラー CS1540 'ISampleDerived' 型の修飾子をとおしてプロ
// テクト メ���バー 'ISampleDerived.Protected2()' にアクセスす
// ることはできません。修飾子は 'Sample' 型、またはそれから派
// 生したものでなければなりません
}
// ISampleDerived に「既定の実装」がない Protected3() があるので
// class側で実装が必要。
public void Protected3()
{
Console.WriteLine("Sample.Protected3");
}
// ただし、アクセス修飾子を public にしないと以下のエラー:
// エラー CS0737 'Sample' は、インターフェイス メンバー
// 'ISampleDerived.Protected3()' を実装していません。
// 'Sample.Protected3()' は public ではないため、インターフェイス
// メンバーを実装できません。
// と言って class 側で Protected3 を実装しないと以下のエラー:
// エラー CS0535 'Sample' はインターフェイス メンバー
// 'ISampleDerived.Protected3()' を実装しません
}
}
// 結果は:
// ISample.Public
// ISample.Private
// ISample.Internal
// ISample.Protected
// ISampleDerived.Defualt
// Sample.Protected3