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

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

やりたいこと

仕事で読むコードに「interfaceの明示的実装」が出てきたが、 以前理解したはずなのに、またアレ?となった。

もう忘れないようにまとめておきたい。

前提

  • VisualStudio2022
  • .NET6

コードと資料

コード

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

実験①

using System.Diagnostics;

namespace InterfaceJikken
{
    internal class Program
    {
        static void Main(string[] args)
        {
            {
                MyClass1 mc1 = new MyClass1();
                MyInterface1 mi1 = new MyClass1();

                //mc1.Method1_1();
                //mc1.Method1_2();
                //((MyInterface1)mc1).Method1_1();
                //((MyInterface1)mc1).Method1_2();
            }

            {
                MyClass2 mc2 = new MyClass2();
                MyInterface2 mi2 = new MyClass2();

                Debug.WriteLine("mc2");
                mc2.Method1_1();
                mc2.Method1_2();

                Debug.WriteLine("mc2 class");
                ((MyClass1)mc2).Method1_1();
                ((MyClass1)mc2).Method1_2();
                ((MyClass2)mc2).Method1_1();//不要なキャスト
                ((MyClass2)mc2).Method1_2();//不要なキャスト

                Debug.WriteLine("mc2 if");
                ((MyInterface1)mc2).Method1_1();
                ((MyInterface1)mc2).Method1_2();
                ((MyInterface2)mc2).Method1_1();
                ((MyInterface2)mc2).Method1_2();

                Debug.WriteLine("mi2");
                mi2.Method1_1();
                mi2.Method1_2();

                Debug.WriteLine("mi2 class");
                ((MyClass1)mi2).Method1_1();
                ((MyClass1)mi2).Method1_2();
                ((MyClass2)mi2).Method1_1();
                ((MyClass2)mi2).Method1_2();

                Debug.WriteLine("mi2 if");
                ((MyInterface1)mi2).Method1_1();//不要なキャスト
                ((MyInterface1)mi2).Method1_2();//不要なキャスト
                ((MyInterface2)mi2).Method1_1();//不要なキャスト
                ((MyInterface2)mi2).Method1_2();//不要なキャスト
            }
        }
    }

    internal interface MyInterface1
    {
        void Method1_1();
        void Method1_2();
    }
    internal interface MyInterface2 : MyInterface1
    {
        void Method2_1();
    }

    internal class MyClass1 : MyInterface1
    {
        public void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass1");
        public void Method1_2()=> Debug.WriteLine(" Method1_2 of MyClass1");
    }

    internal class MyClass2 : MyClass1, MyInterface2
    {
        public new void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2");
        public void Method1_2() => Debug.WriteLine(" Method1_2 of MyClass2");
        public void Method2_1() => Debug.WriteLine(" Method2_1 of MyClass2");
    }
}

結果

mc2
 Method1_1 of MyClass2
 Method1_2 of MyClass2
mc2 class
 Method1_1 of MyClass1
 Method1_2 of MyClass1
 Method1_1 of MyClass2
 Method1_2 of MyClass2
mc2 if
 Method1_1 of MyClass2
 Method1_2 of MyClass2
 Method1_1 of MyClass2
 Method1_2 of MyClass2
mi2
 Method1_1 of MyClass2
 Method1_2 of MyClass2
mi2 class
 Method1_1 of MyClass1
 Method1_2 of MyClass1
 Method1_1 of MyClass2
 Method1_2 of MyClass2
mi2 if
 Method1_1 of MyClass2
 Method1_2 of MyClass2
 Method1_1 of MyClass2
 Method1_2 of MyClass2

※newを付けてくださいという警告(CS0108)が出ている状態と、newをつけて警告が出なくなった状態は、実行結果は同じ。(当たり前か)

実験② MyClass2の中のMethod1_1の実装を、MyInterface1の明示的な実装にしてみる

コード

using System.Diagnostics;

namespace InterfaceJikken
{
    internal class Program
    {
        static void Main(string[] args)
        {
            {
                MyClass1 mc1 = new MyClass1();
                MyInterface1 mi1 = new MyClass1();

                //mc1.Method1_1();
                //mc1.Method1_2();
                //((MyInterface1)mc1).Method1_1();
                //((MyInterface1)mc1).Method1_2();
            }

            {
                MyClass2 mc2 = new MyClass2();
                MyInterface2 mi2 = new MyClass2();

                Debug.WriteLine("mc2");
                mc2.Method1_1();
                mc2.Method1_2();

                Debug.WriteLine("mc2 class");
                ((MyClass1)mc2).Method1_1();
                ((MyClass1)mc2).Method1_2();
                ((MyClass2)mc2).Method1_1();//不要なキャスト
                ((MyClass2)mc2).Method1_2();//不要なキャスト

                Debug.WriteLine("mc2 if");
                ((MyInterface1)mc2).Method1_1();
                ((MyInterface1)mc2).Method1_2();
                ((MyInterface2)mc2).Method1_1();
                ((MyInterface2)mc2).Method1_2();

                Debug.WriteLine("mi2");
                mi2.Method1_1();
                mi2.Method1_2();

                Debug.WriteLine("mi2 class");
                ((MyClass1)mi2).Method1_1();
                ((MyClass1)mi2).Method1_2();
                ((MyClass2)mi2).Method1_1();
                ((MyClass2)mi2).Method1_2();

                Debug.WriteLine("mi2 if");
                ((MyInterface1)mi2).Method1_1();//不要なキャスト
                ((MyInterface1)mi2).Method1_2();//不要なキャスト
                ((MyInterface2)mi2).Method1_1();//不要なキャスト
                ((MyInterface2)mi2).Method1_2();//不要なキャスト
            }
        }
    }

    internal interface MyInterface1
    {
        void Method1_1();
        void Method1_2();
    }
    internal interface MyInterface2 : MyInterface1
    {
        void Method2_1();
    }

    internal class MyClass1 : MyInterface1
    {
        public void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass1");
        public void Method1_2()=> Debug.WriteLine(" Method1_2 of MyClass1");
    }

    internal class MyClass2 : MyClass1, MyInterface2
    {
        void MyInterface1.Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2"); // ★ココが①と違う
        public void Method1_2() => Debug.WriteLine(" Method1_2 of MyClass2");
        public void Method2_1() => Debug.WriteLine(" Method2_1 of MyClass2");
    }
}

結果

mc2
 Method1_1 of MyClass1 //★ココが①と違う → MyClass2の中で、MyInterface1のMethod1_1の実装をもう一回やってる感じということか
 Method1_2 of MyClass2
mc2 class
 Method1_1 of MyClass1
 Method1_2 of MyClass1
 Method1_1 of MyClass1 //★ココが①と違う
 Method1_2 of MyClass2
mc2 if
 Method1_1 of MyClass2
 Method1_2 of MyClass2
 Method1_1 of MyClass2
 Method1_2 of MyClass2
mi2
 Method1_1 of MyClass2
 Method1_2 of MyClass2
mi2 class
 Method1_1 of MyClass1
 Method1_2 of MyClass1
 Method1_1 of MyClass1 //★ココが①と違う
 Method1_2 of MyClass2
mi2 if
 Method1_1 of MyClass2
 Method1_2 of MyClass2
 Method1_1 of MyClass2
 Method1_2 of MyClass2

実験②では、mc2をMyClass1にキャストしてから、MyClass2の中で明示的にMyInterface1のメソッドとして実装したMyMethod1_1を呼ぼうとすると、不要なキャストとしてMsgがでる。

↓薄い色になって、不要なキャストと言われる

キャストはいらないということは、つまり、

「明示的にMyInterface1のメソッドとしてMyMethod1_1を実装することは、MyClass1に書いたMyMethod1_1の実装を、MyClass2でやり直しているだけ。」

だから、実質、

「MyClass2には、MyMethod1_1の実装はない!」

ということか。

実験③ Method1_1を、MyClass1ではvirtualにし、MyClass2でoverrideする

コード

using System.Diagnostics;

namespace InterfaceJikken
{
    internal class Program
    {
        static void Main(string[] args)
        {
            {
                MyClass1 mc1 = new MyClass1();
                MyInterface1 mi1 = new MyClass1();

                //mc1.Method1_1();
                //mc1.Method1_2();
                //((MyInterface1)mc1).Method1_1();
                //((MyInterface1)mc1).Method1_2();
            }

            {
                MyClass2 mc2 = new MyClass2();
                MyInterface2 mi2 = new MyClass2();

                Debug.WriteLine("mc2");
                mc2.Method1_1();
                mc2.Method1_2();

                Debug.WriteLine("mc2 class");
                ((MyClass1)mc2).Method1_1();
                ((MyClass1)mc2).Method1_2();
                ((MyClass2)mc2).Method1_1();//不要なキャスト
                ((MyClass2)mc2).Method1_2();//不要なキャスト

                Debug.WriteLine("mc2 if");
                ((MyInterface1)mc2).Method1_1();
                ((MyInterface1)mc2).Method1_2();
                ((MyInterface2)mc2).Method1_1();
                ((MyInterface2)mc2).Method1_2();

                Debug.WriteLine("mi2");
                mi2.Method1_1();
                mi2.Method1_2();

                Debug.WriteLine("mi2 class");
                ((MyClass1)mi2).Method1_1();
                ((MyClass1)mi2).Method1_2();
                ((MyClass2)mi2).Method1_1();
                ((MyClass2)mi2).Method1_2();

                Debug.WriteLine("mi2 if");
                ((MyInterface1)mi2).Method1_1();//不要なキャスト
                ((MyInterface1)mi2).Method1_2();//不要なキャスト
                ((MyInterface2)mi2).Method1_1();//不要なキャスト
                ((MyInterface2)mi2).Method1_2();//不要なキャスト
            }
        }
    }

    internal interface MyInterface1
    {
        void Method1_1();
        void Method1_2();
    }
    internal interface MyInterface2 : MyInterface1
    {
        void Method2_1();
    }

    internal class MyClass1 : MyInterface1
    {
        public virtual void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass1");  // ★ココが②と違う
        public void Method1_2()=> Debug.WriteLine(" Method1_2 of MyClass1");
    }

    internal class MyClass2 : MyClass1, MyInterface2
    {
        public override void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2");  // ★ココが②と違う
        public void Method1_2() => Debug.WriteLine(" Method1_2 of MyClass2");
        public void Method2_1() => Debug.WriteLine(" Method2_1 of MyClass2");
    }
}

結果

mc2
 Method1_1 of MyClass2 // ★ココが②と違う ①とは同じ
 Method1_2 of MyClass2
mc2 class
 Method1_1 of MyClass2 // ★ココが②と違う ①とも違う!
 Method1_2 of MyClass1
 Method1_1 of MyClass2 // ★ココが②と違う ①とは同じ
 Method1_2 of MyClass2
mc2 if
 Method1_1 of MyClass2
 Method1_2 of MyClass2
 Method1_1 of MyClass2
 Method1_2 of MyClass2
mi2
 Method1_1 of MyClass2
 Method1_2 of MyClass2
mi2 class
 Method1_1 of MyClass2 // ★ココが②と違う ①とは同じ
 Method1_2 of MyClass1
 Method1_1 of MyClass2 // ★ココが②と違う ①とは同じ
 Method1_2 of MyClass2
mi2 if
 Method1_1 of MyClass2
 Method1_2 of MyClass2
 Method1_1 of MyClass2
 Method1_2 of MyClass2

親のクラスでvirtualとして実装されたメソッドをoverrideすると、

呼ぶときに親(MyClass1)として呼ばれようが子(MyClass2)として呼ばれようが、overrideしたほうの子のメソッドが呼ばれる様子。

まとめ

そして、一番気になる、newとvirtual/overrideのやり方の違い(なんで、似たような機能がC#にあるのか?)が、下記ページに書かれていた。

https://atmarkit.itmedia.co.jp/fdotnet/csharp_abc/csharp_abc_004/csharp_abc02.html

こまかいこと

子クラスに親クラスと同じメソッドを定義したときに出てくる下記コメントの「非表示」は、英語だと「Hide」。

非表示というとよくわからないが、Hideだと「親のメソッドを隠す」ということで分かりやすい気がした。

参考

第4回 継承とインターフェイス

わかりやすい!

https://atmarkit.itmedia.co.jp/fdotnet/csharp_abc/csharp_abc_004/csharp_abc02.html

第5回に、インターフェースの話が書かれいてる。

これも、ずっと前からのギモン(抽象クラスとインターフェースがどう違うのか?似てるものが2個あるのはなぜ?)に触れてるようなので、見てみる。

https://atmarkit.itmedia.co.jp/fdotnet/csharp_abc/csharp_abc_004/csharp_abc03.html