インターフェース・継承周りの実験②

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

前回記事の続き。

コードと資料

コード

https://github.com/tera1707/InterfaceJikken

資料

https://github.com/tera1707/Siryou/blob/master/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E3%83%BB%E7%B6%99%E6%89%BF%E5%91%A8%E3%82%8A%E3%81%AE%E5%AE%9F%E9%A8%93.xlsx

前提

  • 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つある。(IMyInterface1IMyInterface2
  • IMyInterface2IMyInterface1を継承している。
  • 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を明示的実装に使ってるじゃないか、となるが、

これはIMyInterface2IMyInterface1を継承しているからだと思われる。
(次の項目を参照)

※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つある。(IMyInterface1IMyInterface2
  • IMyInterface2IMyInterface1を継承している。
  • MyClass1は、IMyInterface1を実装する。
  • MyClass2は、MyClass1を継承し、かつIMyInterface2を実装する。

みたいなややこしいことをするから、複雑になってしまった。

interfaceの明示的実装は、参考に上げさせていただいたこの記事のように、

同じ名前のメンバーを持った複数のinterfaceを実装する、というケースに絞って使ってあげた方がよいのかもしれない。 (直感的ではなさ過ぎて、私には理解が難しかった。。。)

参考

ufcpp

インターフェイスの明示的実装の用途

https://ufcpp.net/study/csharp/oo_interface.html?p=4

こちらで書かれている「メソッドを隠す」ということについて、隠したい!と思う場面にまだであったことがないので、その使い方が役に立つ実感はないが、今回の実験で実際に「隠れる」ということは分かった。

隠したい場面に出会ったらやってみる。