そーなのかーを読んで、自分でも曖昧になっていた部分があるので、「C#のインターフェースの明示的な実装」について、自分用の簡単なまとめを書こうと思ったところ、予想以上の混迷を見つけてしまいました。
インターフェースの明示的な実装とは何か §
インターフェースIFooのメソッド、プロパティ、イベント、インデクサを実装する際に、"その名前"で実装する方法と、"IFoo.その名前"で実装する方法があります。後者が明示的な実装です。
明示的な実装を行うと何が変るのか §
- クラスに異なるインターフェースを実装する際、それらの間に同名のメソッド、プロパティ、イベント、インデクサがあって安全に区別して実装できる
- "IFoo.その名前"を経由して外部からアクセスすることはできない。インテリセンスを発動しても表示されない
- つまり、あたかもprivateであるかのように見える
- しかし、IFooにキャストしてやると"その名前"経由でアクセス可能になる
- つまり、あたかもpublicであるかのように見える
疑問 §
さてここで素朴な疑問です。
キャストするとprivateがpublicに変化したように見えるとは、いったいどういう意味?
C#言語仕様では以下のように書かれています。
インターフェイス メンバの明示的実装には、他のメンバとは異なるアクセシビリティ特性があります。インターフェイス メンバの明示的実装は、メソッドの呼び出しやプロパティ アクセスにおいて、完全限定名ではアクセスできません。このため、アクセスはある意味ではプライベートです。ただし、インターフェイス インスタンスからはアクセスできるため、アクセスはある意味でパブリックでもあります。
ここでは、「完全限定名ではアクセスできない」ことがprivateに見える根拠になっているようにも読めます。ということは、本当はpublicなのに、アクセスできる名前が無いためにprivateであるかのように見えるだけ?
こういう時は論よりソース。MSのC# 3.0コンパイラの実装はどうなっているのでしょうか? リフレクションで強引にメソッドの情報を読み取れば分かるはずです。
サンプルソース §
using System;
using System.Reflection;
class MyClass : IDisposable
{
void IDisposable.Dispose() { }
}
class Program
{
private static void dumpType(Type t)
{
MethodInfo[] infos = t.GetMethods(BindingFlags.Public
| BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine("Type Name:{0}", t.Name);
foreach (var info in infos)
{
if (info.Name.Contains("Dispose"))
Console.WriteLine("{0} is {1}",
info.Name,
info.IsPrivate ? "private" : "non-private");
}
}
static void Main(string[] args)
{
dumpType(typeof(MyClass));
dumpType(typeof(IDisposable));
MyClass a = new MyClass();
IDisposable ia = a;
dumpType(a.GetType());
dumpType(ia.GetType());
ia.Dispose();
}
}
実行結果 §
Type Name:MyClass
System.IDisposable.Dispose is private
Type Name:IDisposable
Dispose is non-private
Type Name:MyClass
System.IDisposable.Dispose is private
Type Name:MyClass
System.IDisposable.Dispose is private
結論と混迷 §
というわけで、結論が出ました。
インターフェース本体のメンバはpublicですが、明示的に実装されたインターフェースメンバはprivateです。従って、インターフェース本体の型を経由したメソッドの呼び出しは成立しますが、インターフェースを実装した型を経由した外部からのメソッドの呼び出しは成立しません。
……という話で終われば良かったのですが。
よく見ると上記のサンプルソースの結果にはねじれた状況があります。
IDisposable ia = a;として宣言されているiaに対して、dumpType(ia.GetType());を実行した結果は以下の通りだからです。
- "System.IDisposable.Dispose is private"となり、このメソッドはprivateだと主張している
- "Dispose"というメンバは発見できていない
それにも関わらず、次行の"ia.Dispose();"というメソッド呼び出しは成立してしまいます。
コンパイル時に静的に解決される型情報と、実行時に動的に参照される型情報が食い違っているわけで、なかなか悩ましい混迷です。
あるいは別の結論 §
dumpType(ia.GetType().GetInterface("System.IDisposable"));と書けば、コンパイル時に認識されて呼び出される(と思われる)メソッドの情報を実行時に取得できます。つまり、「インターフェースの明示的な実装」を行った型に対して、呼び出せる可能性のある全てのメソッドを列挙するには、GetMethodsでメソッドの列挙を行うだけでは不十分であり、GetInterfacesメソッドでインターフェースを列挙した上でそれらに対してもGetMethodsでメソッドの列挙を行う必要がある?