もくじ
https://tera1707.com/entry/2022/02/06/144447
やりたいこと
コードと資料
コード
https://github.com/tera1707/InterfaceJikken
資料
前提
- VisualStudio2022
- .NET6
やること
このようなコードがある。
using System.Diagnostics; namespace InterfaceJikken { internal class Program { static void Main(string[] args) { { MyClass2 asClassInstance = new MyClass2(); IMyInterface2 asInterface = new MyClass2(); Debug.WriteLine("mc2"); asClassInstance.Method1_1(); // 1 Debug.WriteLine("mc2 class"); ((MyClass1)asClassInstance).Method1_1(); // 2 ((MyClass2)asClassInstance).Method1_1(); // 3 Debug.WriteLine("mc2 if"); ((IMyInterface1)asClassInstance).Method1_1(); // 4 ((IMyInterface2)asClassInstance).Method1_1(); // 5 Debug.WriteLine("mi2"); asInterface.Method1_1(); // 6 Debug.WriteLine("mi2 class"); ((MyClass1)asInterface).Method1_1(); // 7 ((MyClass2)asInterface).Method1_1(); // 8 Debug.WriteLine("mi2 if"); ((IMyInterface1)asInterface).Method1_1(); // 9 ((IMyInterface2)asInterface).Method1_1(); // 10 } } } internal interface IMyInterface1 { void Method1_1(); } internal interface IMyInterface2 : IMyInterface1 { // 実質、MyInterface1と同じものを持ったinterface } #if true // パターン1 親クラスのメソッドをnewする internal class MyClass1 : IMyInterface1 { public void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass1"); // ① } internal class MyClass2 : MyClass1, IMyInterface2 { public new void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2"); // ② } #endif #if false // パターン2 親interfaceのメソッドとして明示的実装する internal class MyClass1 : IMyInterface1 { public void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass1"); // ① } internal class MyClass2 : MyClass1, IMyInterface2 { void IMyInterface1.Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2"); // ② } #endif #if false // パターン3 overrideする internal class MyClass1 : IMyInterface1 { public virtual void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass1"); // ① } internal class MyClass2 : MyClass1, IMyInterface2 { public override void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2"); // ② } #endif }
ポイントは、
- interfaceが2つある。(
IMyInterface1
とIMyInterface2
) IMyInterface2
はIMyInterface1
を継承している。MyClass1
は、IMyInterface1
を実装する。MyClass2
は、MyClass1
を継承し、かつIMyInterface2
を実装する。
このコードには、#if
で分けた3つのパターンがあるが、それぞれそのMyClassとMyInterfaceに対して、
- パターン1:親クラスのメソッドをnewする
- パターン2:親interfaceのメソッドとして明示的実装する
- パターン3:overrideする
ということをする。
実行すると、それぞれこういう出力になる。 (親(MyClass1)の方を通ったのを①、子(MyClass2)の方を通ったのを②としている)
この結果で気になったところ
以下で「※1」とか「※2」と言っているのは、上の表中のセルの中の※1や※2を指す。
そもそもMyClass2で明示的実装ができるのはIMyInterface2がIMyInterface1を継承しているから
上のコードでは、
internal class MyClass2 : MyClass1, IMyInterface2 { void IMyInterface1.Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2"); // ② }
となっていて、Method1_1()
を、IMyInterface1
のメソッドとして明示的に実装できてるが、
これを
internal class MyClass2 : MyClass1
とすると、下記のように怒られる。
つまり、
「interfaceを実装したクラス」を継承したクラスは、そのままでは親クラスが実装してるinterfaceを使って明示的にメソッドを実装できない。
明示的に実装できるのは、「今このクラスで実装すると書いたinterface」のメソッドだけだと思われる。
↑ここにあるinterfaceだけ、「明示的実装」に使える。
ただ、今回のコードだと、
下記で「今このクラスで実装すると書いたinterface」じゃないinterfaceを明示的実装に使ってるじゃないか、となるが、
これはIMyInterface2
がIMyInterface1
を継承しているからだと思われる。
(次の項目を参照)
※1 IMyInterface1として明示的に実装したメソッドがIMyInterface2として呼べちゃうのはなぜ?
上のコードにおいては、
IMyInterface2
は、IMyInterface1
を継承しているので、実質、
internal interface IMyInterface2 : IMyInterface1 { // 実質、MyInterface1と同じものを持ったinterface }
は、
internal interface IMyInterface2 { void Method1_1(); }
と同じだから、
((IMyInterface2)asInterface).Method1_1();
という呼び方もできるのだと思われる。
※「同じだから」と書いたが、いろいろ試していると、同じでもなさそう。
interfaceの明示的実装の意味合い
interfaceの明示的な実装には、2つの意味があるように感じた。
■意味①
複数のインターフェースで同じメソッドを実装したときに、指定したほうのインターフェースとしてのそのメソッドを実装する、という意味。
■意味②
明示的に実装したそのメソッドは、クラスのインスタンス経由では呼び出せなくして、インターフェース経由でしか呼べなくする、という意味。
つまりは、下記ページにも書かれているように、
https://ufcpp.net/study/csharp/oo_interface.html?p=3#explicit-impl
ということか。
※2 明示的に実装すると、クラスインスタンスからは呼べなくなり「隠れる」はずが、※2で呼べるのはなぜ?
掲題の通り、なぜ呼べるのか?と、なぜ両方親クラスのメソッドが呼ばれるのか?
これがよくわからない。
internal class MyClass2 : MyClass1, IMyInterface2 { void IMyInterface1.Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2"); // ② }
MyClass2
で明示的実装をしたからMyClass2
のメソッドは「隠れ」て、それをMyClass2
クラスインスタンスから呼ぶと、隠れてないMyClass1
のメソッドが呼ばれるということか??
※3 親interfaceとして明示的に実装してるメソッドを、子のinterfaceから呼べるのはなぜ?
なぜ呼べるのか、どういう理屈なのか、さっぱりわからない。
パターン2の明示的実装で、
こう書くと、エラーなくビルドできる。
そのときに、cls2.Method1_1();
のMethod1_1()でF12を押すと、MyClass1
のほうのMethod1_1に飛ぶ。
→これは、パターン②の1と同じか,,,
また、if2.Method1_1();
のほうののMethod1_1()でF12を押すと、
IMyInterface1`の中ののMethod1_1に飛ぶ。
→パターン②の6と同じか...
→そうか、
パターン2の1は、
- MyClass2の中で明示的に実装されたIMyInterface1のメソッドは、IMyInterface1のインスタンスから呼ばれるときには隠れない。
- つまり、下図のように、今実装すると書いたinterfaceのメソッドを明示的に実装しないと、隠すことはできない。
- 継承したクラスが持ってるメソッドを、子のクラスの中で明示的に実装しても、隠すことができない?
ということか?
パターン2の2,3は、
- MyClass1インスタンスとして呼ばれたメソッドは、普通に
MyClass1
のメソッドが呼ばれるだけ - MyClass2インスタンスとして呼ばれたメソッドは、MyClass2としてのメソッドは「隠れてる」けど、継承したMyClass1のメソッドとして呼ばれてる
ってことか?
で、パターン2の6は、
インターフェースとして呼ばれるときは「隠れない」から、
- MyClass2が持ってる、継承したMyClass1のメソッドが呼ばれてる
で、パターン2の7,8は、パターン2の2,3と同じか、、
「隠す」の発動条件
隠す、について、あってる自信はないが、
こういうことじゃないのか。
interfaceを実装したクラスを継承したクラスの中で、 親のメソッドを明示的に実装しても、隠すことはできない、ということか??
実際、この図のようにかけば、クラスのインスタンスからは呼べなくなる。
まとめ
本当に、わからないから試した、の記事になり、全然まとまらなかった。
書いたことが正しいかどうかもわからない。
今回、最初に書いたように、
- interfaceが2つある。(
IMyInterface1
とIMyInterface2
) IMyInterface2
はIMyInterface1
を継承している。MyClass1
は、IMyInterface1
を実装する。MyClass2
は、MyClass1
を継承し、かつIMyInterface2
を実装する。
みたいなややこしいことをするから、複雑になってしまった。
interfaceの明示的実装は、参考に上げさせていただいたこの記事のように、
同じ名前のメンバーを持った複数のinterfaceを実装する、というケースに絞って使ってあげた方がよいのかもしれない。 (直感的ではなさ過ぎて、私には理解が難しかった。。。)
参考
ufcpp
インターフェイスの明示的実装の用途
https://ufcpp.net/study/csharp/oo_interface.html?p=4
こちらで書かれている「メソッドを隠す」ということについて、隠したい!と思う場面にまだであったことがないので、その使い方が役に立つ実感はないが、今回の実験で実際に「隠れる」ということは分かった。
隠したい場面に出会ったらやってみる。