Help us understand the problem. What is going on with this article?

【C#】Genericな基底クラスから派生したクラスをListで管理したい

More than 1 year has passed since last update.

はじめに

この記事は非常にニッチなケースだということを予めお伝えしておきます。

どういう状況か

例えばこのようなクラスがあります。

abstract class HogeBase<T> where T : HogeBase<T> {}

このクラスを継承したクラス一覧を配列などで管理しようとしたことはありませんか?
そこで特に意識せずタイピングするとこうしたくなります。

List<HogeBase<T>> list;

そして思います。
あれ?ちょっと待ってTは何?どこで決まるの?
変数でwhere使えない!

そうです、Generic型の制約を持つGenericクラスを配列などで管理しようとするとできません。
これはそんな状況をどうやって解決するかについての記事になります。

答えを最初に言ってしまうと、interfaceを使って解決します。
それでは実装を見てみましょう。

実装

Framework

何らかのFramework上のクラスです。
外部の何かかもしれませんし、自社製の何かかもしれません。
とりあえず自分では触れることができない前提です。

Framework.cs
namespace Framework
{
    public abstract class Framework<T> where T : Framework<T>
    {
        public void Function()
        {
            UnityEngine.Debug.Log(typeof(T).Name);
        }
    }
}

Interface

IHoge.cs
public interface IHoge
{
    void WrapFunction();
}

Base class

HogeBase.cs
using Framework;

public abstract class HogeBase<T> : Framework<T>, IHoge where T : HogeBase<T>
{
    void IHoge.WrapFunction()
    {
        Function();
    }
}

派生1

HogeEntity1.cs
public sealed class HogeEntity1 : HogeBase<HogeEntity1>
{
}

派生2

HogeEntity2.cs
public sealed class HogeEntity2 : HogeBase<HogeEntity2>
{
}

HogeBaseを継承しないクラス

HogeOther.cs
using Framework;

public class HogeOther : Framework<HogeOther>, IHoge
{
    void IHoge.WrapFunction()
    {
        Function();
    }
}

Manager

こちらが実際の管理クラスになります。
Frameworkではなく、IHogeをベースとして管理しています。
使用しているのはDictionaryですが、Listなどでも一緒です。

HogeManager.cs
using Framework;
using System;
using System.Collections.Generic;

public class HogeManager
{
    private Dictionary<Type, IHoge> _hogeEntities;

    public HogeManager()
    {
        _hogeEntities = new Dictionary<Type, IHoge>
        {
            {typeof(HogeEntity1), new HogeEntity1()},
            {typeof(HogeEntity2), new HogeEntity2()},
            {typeof(HogeOther),   new HogeOther()},
        };
    }

    public void ExecuteFrameworkFunction<THoge>() where THoge : Framework<THoge>, IHoge
    {
        Type type = typeof(THoge);
        var entity = _hogeEntities[type];
        entity.WrapFunction();
    }
}

実際に使う時の例

呼び出し側では、管理に扱っている何らかのパラメータを渡して制御する対象を変えます。
今回の例ではクラスの型を管理パラメータとして扱っています。

var hogeManager = new HogeManager();
hogeManager.ExecuteFrameworkFunction<HogeEntity1>();
hogeManager.ExecuteFrameworkFunction<HogeEntity2>();
hogeManager.ExecuteFrameworkFunction<HogeOther>();

補足

①全ての管理対象となるクラスは、特定の一つのクラスから派生していた方が良いのでは?

本当はFramework<THoge>, IHogeの両方を継承してる基底クラスを一つ作って起き、管理対象となるクラスはそれを継承する方が良さそうですが、そうじゃない(HogeOtherのような)ケースも実際にはあったのでこのような作りになっています。

②基底となるクラスをGenericじゃなくしちゃダメなの?

基底クラスを

abstract class HogeBase : Framework<HogeBase> {}

にすれば良くね?と思われるかもしれませんが、その場合さらに基底にあるFrameworkが参照するTが、すべてHogeBaseになってしまいます。
最上層の型を対象とした何らかの処理がある場合にはそれだと都合が悪い場合があります。

③ExecuteFrameworkFunctionメソッドの制約はinterfaceだけじゃダメなの?

ExecuteFrameworkFunctionの制約は where THoge : IHoge だけだとダメです。
利用側で hogeManager.ExecuteFrameworkFunction<IHoge>(); とすることができてしまいます。

実際にどういう時?

例えば複数のウィンドウを管理するクラスがこのような作りだったとして、各ウィンドウから管理中のいずれかのウィンドウを呼び出す、といった場合に使える可能性があります。
例えばウィンドウを生成したり、前面に出したり、値を受け渡したり、そんな感じです。

最後に

冒頭でもお伝えした通り、非常に稀なケースですが、やりたくなる場合があるかもしれません。
その時にこの記事を思い出して、それが少しでもお役に立てれば幸いです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away