【C#】Interfaceの使いどころは配列ぶん回しのポリモーフィズムで理解出来る!

みなさんこんにちは!ひろぽんです!

オブジェクト指向の3大要素の一つであるポリモーフィズムについて書いていきたいと思います!

オブジェクト指向を学び始めた初心者が必ず躓くといって良いほど結構難しい概念なのですが、オブジェクト指向を学ぶ上で避けては通れません!

またinterfaceの存在は知っていても、どのように使うのがいいのか?おいしさは何なのか?というのが分からないという人も多いと思います。

私はC#でのinterfaceおいしさはこのポリモーフィズムに全て集約されているといっても過言ではないと思っていますので、今回はinterfaceとポリモーフィズムの使いどころの例に配列ぶん回しをして解説していきたいと思います。

Interface ポリモーフィズム classDiagram (IShape 継承 + 配列ぶん回し)
目次

まずはInterfaceを定義

💡 Interface の継承判定 (is / as / 暗黙キャスト) でハマったら別記事 C# Interface の継承判定と暗黙キャストの定石 で整理してます。

namespace InterfaceExample
{
    public interface ISayHello
    {
        void Say();
    }
}

めっちゃシンプルな感じで、Interfaceを宣言しました。

このInterfaceを継承するクラスは絶対にSay()という関数を実装しないといけません。

Interfaceを継承したクラスを定義

using System;

namespace InterfaceExample { public class CSharpHello : ISayHello { public void Say() { Console.WriteLine("Hello CSharp"); } } }

クラス一つ目はHelloCSharpとコンソールに出すだけのクラスです。

using System;

namespace InterfaceExample { public class VBnetHello : ISayHello { public void Say() { Console.WriteLine("-----------------------"); Console.WriteLine("Hello Vb.net"); } } }

二つ目のクラスはHello VB.netとコンソールに出しますが、その一行上に—-で線を引きます。

Interfaceの配列を定義

で上記二つのクラスを配列に入れてぶん回したいのですが、ISayHelloというインターフェースを実装しているので、ISayHelloの型の配列を宣言します。

namespace InterfaceExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var helloList = new ISayHello[]
            {
                new CSharpHello(), new VBnetHello(),
            };
        }
    }
}

配列をぶん回して実行

で上記の関数をぶん回します。

    class Program
    {
        static void Main(string[] args)
        {
            var helloList = new ISayHello[]
            {
                new CSharpHello(), new VBnetHello(),
            };
        foreach (var sayHello in helloList)
        {
            sayHello.Say();
        }

        Console.ReadLine();
    }
}</code></pre></div>

Interfaceの関数を実行しているのに結果が違う

するとISayHelloのInterfaceのSayを実行しているのに、違う結果に出ました。

これはnewしたインスタンスの型が違うから、interfaceを通じてnewしたインスタンスの中身を見ているのですね!

例えばこんな使い方もできます。

自由にHelloというクラスを作ってみる

using System;

namespace InterfaceExample { public class FreeHello : ISayHello { private string _helloItem; public FreeHello(string helloItem) { _helloItem = helloItem; }

    public void Say()
    {
        Console.WriteLine($@&quot;Hello {_helloItem}&quot;);
    }
}

}

例えばこんな感じでコンストラクタの引数にHello ○○の○○の文言を取るものを追加したとします。

しかしこのFreeHelloはISayHelloを継承しているので、問題なく配列に追加できます。

namespace InterfaceExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var helloList = new ISayHello[]
            {
                new CSharpHello()
                , new VBnetHello()
                , new FreeHello("Python")
                , new FreeHello("JavaScript"),  
            };
        foreach (var sayHello in helloList)
        {
            sayHello.Say();
        }

        Console.ReadLine();
    }
}

}

こんな感じでね!

で実行すると!

こんな感じで文言が追加されています!

ISayHelloのInterfaceを引数に取る関数を作ってみる

でこれだけだとおいしさが半減すると思うのでInterfaceを下記のように修正しましょう。

namespace InterfaceExample
{
    public interface IGetHello
    {
        string GetHello();
    }
}

さっきのHello CSharpとかの文言を戻り値として返すようにします。

で上記で宣言したクラスを下記のように修正します。

namespace InterfaceExample
{
    public class CSharpHello : IGetHello
    {
        public string GetHello()
        {
            return "Hello CSharp";
        }
    }
}
namespace InterfaceExample
{
    public class VBnetHello : IGetHello
    {
        public string GetHello()
        {
            return "Hello Vb.net";
        }
    }
}
namespace InterfaceExample
{
    public class FreeHello : IGetHello
    {
        private string _helloItem;
        public FreeHello(string helloItem)
        {
            _helloItem = helloItem;
        }
    public string GetHello()
    {
        return $@&quot;Hello {_helloItem}&quot;;
    }
}

}

で最後にIGetHelloを引数にとってConsoleに表示する関数を作ります。

namespace InterfaceExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var helloList = new IGetHello[]
            {
                new CSharpHello()
                , new VBnetHello()
                , new FreeHello("Python")
                , new FreeHello("JavaScript"),  
            };
        helloList.ToList().ForEach(ShowConsole);
        Console.ReadLine();
    }

    static void ShowConsole(IGetHello sayHello)
    {
        Console.WriteLine(sayHello.GetHello());

    }
}

}

このようにすると結果は下記になります。

ポリモーフィズムとはInterfaceありき

で最後にまとめですが、Interfaceは上記のように実行する関数は同じだけれども、中身の実装を変えたいときに効果を発揮します。

今回のは一例ですが、下記のような使い方もできます。

Sqlを実行する関数を作ったとして引数はISqlQueryBuilderというGetquery()という関数を持ったインターフェースを用意。

上記インターフェースを継承した下記のクラスを用意する。

  • DeleteQueryBuilder
  • InsertQueryBuilder
  • UpdateQueryBuilder
  • SelectQueryBuilder

ここまですれば中身の実装をそれぞれ別々のクエリを発行するようにして、Sqlを実行する関数に投げれば中で勝手に処理をしてくれます。

これがポリモーフィズムです!!!

今回のコードはサンプルでGithubに上げています!

下記GITHUBリポジトリに今回のソースコードを上げています!

GitHub
GitHub - HayashiyamaHiroshi/blog-source-demo Contribute to HayashiyamaHiroshi/blog-source-demo development by creating an account on GitHub.

💡 補足: 業務系の現場でよくハマるパターン

俺も Interface、業務でハマってきたところを3つ並べておきます。

① 抽象クラスと Interface の使い分けで悩む

共通実装を持ちたいなら抽象クラス、契約だけ揃えたいなら Interface。 多重継承相当が必要なら Interface。 .NET 8 の default interface members も使えるが業務系は使わない方が安全。

② is / as の使い分けで NRE

obj as IFoo はキャスト失敗で null を返す。 (IFoo)obj は InvalidCastException。 obj is IFoo foo でパターンマッチが現代的。詳しくは 継承判定の定石 で書いてます。

③ Interface の変更で全実装クラスが壊れる

Interface にメソッド追加すると、実装してる全クラスがコンパイルエラー。共有ライブラリで Interface を切る時は将来変更を見越して default member 検討。本番運用中のシステムでは絶対やらない。

❓ よくある質問

Q1. Interface と Abstract Class、結局どっち使う?

A. デフォルトは Interface。共通実装が複数クラスで重複するなら Abstract Class。 「is-a 関係」が強いなら継承、「can-do 関係」なら Interface(IDisposable, IEnumerable 等)。

Q2. プロパティだけの Interface ってあり?

A. ありです。 IUserContext { string UserId { get; } } 等は典型例。 DTO 系の契約に使う。

Q3. 明示的 Interface 実装って何?

A. void IDisposable.Dispose() のような書き方。同名メソッドが複数 Interface にある時の衝突回避や、外部に公開したくない場合に使う。普段の業務ではあまり使わない。

Q4. Interface を Mock 化するのは?

A. Moq / NSubstitute 等で簡単に Mock 化できる。テスタブルなコードを書きたいなら依存を Interface 経由にする(依存性注入)。業務系でも単体テスト書くなら必須。

Q5. List<IFoo> に複数の実装クラスを入れて回す典型例は?

A. ストラテジパターン。 List<IPaymentMethod> に CreditCard / BankTransfer / PayPay を入れて foreach (var m in methods) m.Process();。 ポリモーフィズムの典型。

📚 関連記事

この記事が気に入ったら
いいねしてね!

どんどんシェア待ってるぜ!!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次