首页 > 解决方案 > Inheriting from generic class that also inherits from a generic singleton

问题描述

I'm having difficulty inheriting from a generic class that is itself inherited from a generic singleton.

I am trying to make an inventory base class that is a singleton and derive different inventory types from this with different derived items.

Code has been simplified for brevity.

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static bool m_ShuttingDown = false;
    private static object m_Lock = new object();
    private static T m_Instance;

    public static T Instance
    {
        get
        {
            if (m_ShuttingDown)
                return null;
            lock (m_Lock)
            {
                if (m_Instance == null)
                {
                    m_Instance = (T)FindObjectOfType(typeof(T));    
                    if (m_Instance == null)
                    {
                        var singletonObject = new GameObject();
                        m_Instance = singletonObject.AddComponent<T>();
                        singletonObject.name = typeof(T).ToString() + " (Singleton)";

                        DontDestroyOnLoad(singletonObject);
                    }
                }    
                return m_Instance;
            }
        }
    }
    private void OnApplicationQuit()
    {
        m_ShuttingDown = true;
    }
    private void OnDestroy()
    {
        m_ShuttingDown = true;
    }
}

public class Inventory<T> : Singleton<Inventory<T>> where T : Item
{   
    ...
}
public class EquipmentInventory : Inventory<Equipment>
{
    ...
}
public class Item : ScriptableObject
{
    public string Name = "Item";
}
public class Equipment : Item
{
    public string Name = "Equipment";
}

I can't access the Instance;

private EquipmentInventory equipmentInventory;
private Inventory<Item> inventory;

public void Run()
{
    var cachedInventory = Inventory<Item>.Instance;  //returns null
    var cachedEquipmentInventory = EquipmentInventory.Instance as EquipmentInventory;  //returns null
}

Both statements return null.

The purpose of this, is that each inventory type will be a singleton and each type of inventory will be implent different item types, so that the base inventory will use the Item type, while the Equipment inventory will be implemented using the Equipment item type.

Here is an alternate method, which seems to solve this

public abstract class Inventory<T, TClass> 
        : Singleton<TClass> where TClass 
        : MonoBehaviour where T : Item
    {

    }
public class EquipmentInventory : Inventory<Equipment, EquipmentInventory>
    {

    }

I haven't fully tested this yet with actual code, but will update when I have tested it more thoroughly

Please assist.

标签: c#unity3dgenericssingleton

解决方案


主要问题是由

public class Inventory<T> : Singleton<Inventory<T>> : where T : Item { }

在这里,您将泛型类型“传入”Iventory<T>到,Singleton即使稍后您显式地从Inventory<T>该类继承。


这是一种可能的解决方案,尽管起初它可能看起来有点奇怪:

使您的 Inventory 采用第二种通用​​类型,根据需要在类中使用第一个类型,并将第二个“转发”到Singleton<T>类似类型,例如

// Note how for the limitation via where you still can use the generic type
// which makes sure no other MonoBehaviour can be passed to TSelf by accident
public class Inventory<TItem, TSelf> : Singleton<TSelf> where TItem : Item where TSelf : Inventory<TItem,TSelf>
{
    public TItem reference;

    private void Awake()
    {
        if (!reference) reference = ScriptableObject.CreateInstance<TItem>();
    }
}

然后在实现中您的通行证另外在您自己的最终(非通用)类型中,以便它可以正确“转发”到Singleton<T>类似的东西,例如

public class EquipmentInventory : Inventory<Equipment, EquipmentInventory> { }

请注意,无论如何,此类必须位于一个名为的单独文件中,EquipmentInventory.cs否则它将无法作为 Unity 中的组件工作。


这现在有效,因为现在您显式传入类型EquipmentInventoryTSelf然后将其转发给,Signleton<T>所以返回类型Instance是显式的EquipmentInventory


通常习惯于为每个单独的类/类型拥有一个脚本文件。

另外我会稍微改变你的领域ItemEquipment比如

[CreateAssetMenu]
public class Item : ScriptableObject
{
    [SerializeField] private string _name;

    public string Name => _name;

    private void Awake()
    {
        _name = GetName();
    }

    protected virtual string GetName()
    {
       return nameof(Item);
    }
}

[CreateAssetMenu]
public class Equipment : Item
{
    protected override string GetName()
    {
        return nameof(Equipment);
    }
}

这就是它的样子,例如

public class Example : MonoBehaviour
{
    public EquipmentInventory equipmentInventory;

    [ContextMenu("Run")]
    public void Run()
    {
        equipmentInventory = EquipmentInventory.Instance;
    }
}

在此处输入图像描述


推荐阅读