【问题标题】:Inheriting from generic class that also inherits from a generic singleton从也继承自泛型单例的泛型类继承
【发布时间】:2019-12-20 11:05:04
【问题描述】:

我在从一个泛型类继承时遇到了困难,而该泛型类本身是从一个泛型单例继承而来的。

我正在尝试创建一个作为单例的库存基类,并使用不同的派生项目从中派生不同的库存类型。

为简洁起见,代码已被简化。

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";
}

我无法访问实例;

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
}

两个语句都返回 null。

这样做的目的是,每种库存类型将是一个单件,并且每种类型的库存将实现不同的物品类型,因此基础库存将使用 Item 类型,而 Equipment 库存将使用 Equipment 实现项目类型。

这是另一种方法,似乎可以解决这个问题

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

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

    }

我还没有用实际代码完全测试过这个,但是当我更彻底地测试它时会更新

请帮忙。

【问题讨论】:

  • 在您提供的代码中,您从未分配给任何 m_Instance 字段,因此默认情况下它们都为空。
  • 在您未分配给m_Instance 的问题代码中,在您的真实代码中是否相同?如果是,那就是你的问题
  • 这可能是由于我的简化,我会调查一下。单例类取自这里 wiki.unity3d.com/index.php/Singleton 我将添加完整的定义。
  • 您的代码无法编译,因为没有非泛型Inventory 类,所以Inventory.Instance 无效。
  • var cachedEquipmentInventory = EquipmentInventory.Instance as EquipmentInventory; 始终为 null,因为该类的 Instance 的类型是 Inventory&lt;Equipment&gt; 而不是 EquipmentInventory(无效转换导致 null)。

标签: c# unity3d generics singleton


【解决方案1】:

主要问题是由

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

在这里,您将泛型类型Iventory&lt;T&gt;“传递”到Singleton,即使稍后您显式地从该Inventory&lt;T&gt; 类继承。


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

让您的 Inventory 采用第二种通用​​类型,根据需要在类中使用第一种类型,并将第二种类型“转发”到 Singleton&lt;T&gt;,例如

// 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&lt;T&gt;,例如

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

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


这现在有效,因为现在您显式传入 EquipmentInventory 类型为 TSelf,然后将其转发到 Signleton&lt;T&gt;,因此 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;
    }
}

【讨论】:

  • 我会接受这个答案,您已经确认传入第二个参数效果很好。谢谢你的视频,真的很有帮助。
  • 哦,我实际上并没有看到,当我写我的帖子时,你也有了同样的想法^^
  • 是的,但是您给出了很好的解释,这非常有帮助。
猜你喜欢
  • 2019-07-11
  • 2018-09-27
  • 2015-09-15
  • 2018-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-11
相关资源
最近更新 更多