【C#】Interfaceを使用して、1つのプロパティに2種類以上のクラスを保持する方法 (保持できる数は1つ)

# 概要
とあるクラスから、そのクラスのプロパティに保持しておいた別のクラスを呼び出す際に、プロパティに保持したいクラスが2種類以上ある場合で、少しだけ工夫した書き方をまとめてみました。

# 説明
とあるクラスのプロパティに別のクラスを保持したいとき、その「別のクラス」が2種類以上あり、また保持したいクラスはその候補のうちの1種類だけ、という場合に、インターフェースを使用して1つのプロパティのみで保持できる、という書き方になります。

# コード
`Child`という名前のクラスがあり、このクラスのプロパティに`Parent1`クラス、もしくは`Parent2`クラスのいずれかを保持したい場合、という形で実装していきます。

まずは下記のようにinterfaceを定義します。
```C#:IParent.cs
    public interface IParent { }
```

`Parent1`クラスと`Parent2`クラスに先ほどのインターフェースを実装させます。
この2つのクラスはプロパティに、`Child`クラスのリストを保持しています。

```C#:Parent1.cs
    public class Parent1 : IParent
    {
        public List<Child> Children { get; } = new List<Child>();

        public void CreateChildren()
        {
            // サンプルとして、子を一つ追加
            var child = new Child(this);
            Children.Add(child);

            // その他の処理
        }
    }
```

```C#:Parent2.cs
    public class Parent2 : IParent
    {
        public List<Child> Children { get; } = new List<Child>();

        public void CreateChildren()
        {
            // サンプルとして、子を一つ追加
            var child = new Child(this);
            Children.Add(child);

            // その他の処理
        }
    }
```

`Child`クラスのコンストラクタでインターフェース型の引数を受け取り、プロパティに保持します。

```C#:Child.cs
    public class Child
    {
        /// <summary>
        /// 親
        /// </summary>
        public IParent Parent;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="parent"></param>
        public Child(IParent parent)
        {
            Parent = parent;
        }
    }
```

大まかな実装は以上です。
以下から、使用方法について記載します。

# 使用方法
例えばインターフェースに、`Parent1`と`Parent2`のどちらのクラスにも実装しているメソッドを記載しておけば、`Child`クラスからプロパティに保持されたParentクラスの種類に応じて、そのクラスのメソッドを呼び出すことができます。

例えば、WPFになりますが私がよく使用していた方法だと、インターフェースを下記のように定義し、

```C#:IParent.cs
    public interface IParent
    {
        /// <summary>
        /// 子のプロパティが変わったときに呼ばれる
        /// </summary>
        /// <param name="child"></param>
        void ChildChanged(Child child);
    }
```
`Parent1`と`Parent2`にインターフェースで定義した`ChildChanged`を実装します。

```C#:Parent1.cs
    public class Parent1 : IParent
    {
        public List<Child> Children { get; } = new List<Child>();

        public void CreateChildren()
        {
            // サンプルとして、子を一つ追加
            var child = new Child(this);
            Children.Add(child);

            // その他の処理
        }

        /// <summary>
        /// 子の特定のプロパティが変わったときに呼ばれる
        /// </summary>
        /// <param name="child"></param>
        public void ChildChanged(Child child)
        {
            // 処理
        }
    }
```

```C#:Parent2.cs
    public class Parent2 : IParent
    {
        public List<Child> Children { get; } = new List<Child>();

        public void CreateChildren()
        {
            // サンプルとして、子を一つ追加
            var child = new Child(this);
            Children.Add(child);

            // その他の処理
        }

        /// <summary>
        /// 子の特定のプロパティが変わったときに呼ばれる
        /// </summary>
        /// <param name="child"></param>
        public void ChildChanged(Child child)
        {
            // 処理
        }
    }
```
`Child`クラスで、prismの`OnPropertyChanged()`を実装し、その中で親の`ChildChanged()`を呼び出します。
そうすることで、`Child`クラスのプロパティが変化した場合、自動で紐づく親クラス (ここでは`parent1`もしくは`parent2`) の`ChildChanged()`が呼び出され、`Child`クラスのプロパティの変化に自動で応対する形で`Parent`クラスの処理を実行することができます。

```C#:Child.cs
    public class Child : BindableBase
    {
        /// <summary>
        /// 親
        /// </summary>
        public IParent Parent;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="parent"></param>
        public Child(IParent parent)
        {
            Parent = parent;
        }

        public bool PropertyA { get => _propertyA; set => SetProperty(ref _propertyA, value); }
        private bool _propertyA;

        public bool PropertyB { get => _propertyB; set => SetProperty(ref _propertyB, value); }
        private bool _propertyB;

        public bool PropertyC { get => _propertyC; set => SetProperty(ref _propertyC, value); }
        private bool _propertyC;
        
        protected override void OnPropertyChanged(PropertyChangedEventArgs args)
        {
            try
            {
                if (string.IsNullOrEmpty(args.PropertyName)
                    || args.PropertyName == nameof(PropertyA)
                    || args.PropertyName == nameof(PropertyB))
                {
                    // PropertyAとPropertyBが変わった場合のみ、親に伝える
                    Parent.ChildChanged(this);
                }
            }
            catch (Exception ex)
            {
                //エラー処理
            }
        }     
    }
```

:::note
`Child`クラスが継承している`BindableBase`クラスや、`SetProperty`、`OnPropertyChanged`はWPFでよく使用されるライブラリ`prism`の機能です。
これらの機能を使用し上記のように書くと、プロパティが変更された際に、`OnPropertyChanged`メソッドが自動で走るようになります。
その処理内で、Parentクラスに通知している形になります。
:::

上記以外にも、以下のように`Child`クラス内でキャストすることで、キャストされた`Parent`クラスが使用できます。

例えば、`Parent1`クラスと`Parent2`クラスで別々のプロパティがあり、

```C#:Parent1.cs
    public class Parent1 : IParent
    {
        public List<Child> Children { get; } = new List<Child>();

        /// <summary>
        /// Parent1クラスのみにあるプロパティ
        /// </summary>
        public string Parent1Property {get; set; }

        public void CreateChildren()
        {
            // サンプルとして、子を一つ追加
            var child = new Child(this);
            Children.Add(child);

            // その他の処理
        }
    }
```

```C#:Parent2.cs
    public class Parent2 : IParent
    {
        public List<Child> Children { get; } = new List<Child>();

        /// <summary>
        /// Parent2クラスのみにあるプロパティ
        /// </summary>
        public bool Parent2Property {get; set; }
        
        public void CreateChildren()
        {
            // サンプルとして、子を一つ追加
            var child = new Child(this);
            Children.Add(child);

            // その他の処理
        }
    }
```

以下のように、それぞれのクラスにキャストをすると、キャスト後のクラスが使用できます。

```C#:Child.cs
    public class Child
    {
        /// <summary>
        /// 親
        /// </summary>
        public IParent Parent;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="parent"></param>
        public Child(IParent parent)
        {
            Parent = parent;
        }
    }

    public void SampleMethod()
    {
        if (Parent is Parent1 parent1)
        {
            // プロパティのクラスがParent1だった場合の処理
            parent1.Parent1Property = "ほげほげ";
        }
        
        if (Parent is Parent2 parent2)
        {
            // プロパティのクラスがParent2だった場合の処理
            parent2.Parent2Property = true;
        }
    }
```

# 終わりに
書きたかったことはコードに書いたとおりのことなのですが、記事のタイトルや概要、説明の文章ににすごく悩みました。
コードでやりたい思いやイメージを言葉で表現するのは難しい場合もあると感じました。:sweat_smile: