2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-10-11

概要

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

説明

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

コード

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

まずは下記のようにinterfaceを定義します。

IParent.cs
    public interface IParent { }

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

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);

            // その他の処理
        }
    }
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クラスのコンストラクタでインターフェース型の引数を受け取り、プロパティに保持します。

Child.cs
    public class Child
    {
        /// <summary>
        /// 親
        /// </summary>
        public IParent Parent;

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

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

使用方法

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

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

IParent.cs
    public interface IParent
    {
        /// <summary>
        /// 子のプロパティが変わったときに呼ばれる
        /// </summary>
        /// <param name="child"></param>
        void ChildChanged(Child child);
    }

Parent1Parent2にインターフェースで定義したChildChangedを実装します。

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)
        {
            // 処理
        }
    }
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クラスの処理を実行することができます。

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)
            {
                //エラー処理
            }
        }     
    }

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

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

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

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);

            // その他の処理
        }
    }
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);

            // その他の処理
        }
    }

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

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:

2
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?