C# 使用单行为构造函数的Unity3D单例

C# 使用单行为构造函数的Unity3D单例,c#,unity3d,singleton,C#,Unity3d,Singleton,我有几个monobhavior子类需要成为Singleton,但是在Awake()中分配实例属性对于某些类来说太晚了,并且会导致竞争条件,所以我想知道在私有c-tor中是否有任何东西反对分配实例,像这样: public class Foo: MonoBehaviour { public static Foo Instance { get; private set; } private Foo() { Instance = this; } } p

我有几个
monobhavior
子类需要成为
Singleton
,但是在
Awake()
中分配
实例
属性对于某些类来说太晚了,并且会导致竞争条件,所以我想知道在私有c-tor中是否有任何东西反对分配
实例
,像这样:

public class Foo: MonoBehaviour
{
    public static Foo Instance { get; private set; }

    private Foo()
    {
        Instance = this;
    }
}
private static readonly Foo instance = new Foo();
public static Foo Instance
{
    get
    {
        return instance;
    }
}
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
            instance = new Foo();
        return instance;
    }
}
private static readonly object singletonSection = new object();
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
        {
            lock(singletonSection)
            {
                if (null == instance)
                    instance = new Foo();
            } 
        }
        return instance;
    }
}

或者这种方法是否有任何我需要注意的负面影响?

您使用的技术允许多次设置实例属性,即使只是由同一类的其他成员设置。这是不可能的。我会这样做:

public class Foo: MonoBehaviour
{
    public static Foo Instance { get; private set; }

    private Foo()
    {
        Instance = this;
    }
}
private static readonly Foo instance = new Foo();
public static Foo Instance
{
    get
    {
        return instance;
    }
}
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
            instance = new Foo();
        return instance;
    }
}
private static readonly object singletonSection = new object();
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
        {
            lock(singletonSection)
            {
                if (null == instance)
                    instance = new Foo();
            } 
        }
        return instance;
    }
}
这是一种确保singleton变量只设置一次的简单方法。如果需要惰性实例化,可以执行以下操作:

public class Foo: MonoBehaviour
{
    public static Foo Instance { get; private set; }

    private Foo()
    {
        Instance = this;
    }
}
private static readonly Foo instance = new Foo();
public static Foo Instance
{
    get
    {
        return instance;
    }
}
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
            instance = new Foo();
        return instance;
    }
}
private static readonly object singletonSection = new object();
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
        {
            lock(singletonSection)
            {
                if (null == instance)
                    instance = new Foo();
            } 
        }
        return instance;
    }
}
但是使用这种技术,您仍然可以多次写入实例变量。因此,您需要确保始终引用属性而不是变量。如果要从多个线程访问此属性,则需要使用以下关键部分来防止争用情况:

public class Foo: MonoBehaviour
{
    public static Foo Instance { get; private set; }

    private Foo()
    {
        Instance = this;
    }
}
private static readonly Foo instance = new Foo();
public static Foo Instance
{
    get
    {
        return instance;
    }
}
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
            instance = new Foo();
        return instance;
    }
}
private static readonly object singletonSection = new object();
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
        {
            lock(singletonSection)
            {
                if (null == instance)
                    instance = new Foo();
            } 
        }
        return instance;
    }
}

这是双重检查锁定模式。如果代码访问不多和/或性能不是问题,则可以使用常规锁定。

您使用的技术允许多次设置实例属性,即使只有同一类的其他成员可以设置实例属性。这是不可能的。我会这样做:

public class Foo: MonoBehaviour
{
    public static Foo Instance { get; private set; }

    private Foo()
    {
        Instance = this;
    }
}
private static readonly Foo instance = new Foo();
public static Foo Instance
{
    get
    {
        return instance;
    }
}
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
            instance = new Foo();
        return instance;
    }
}
private static readonly object singletonSection = new object();
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
        {
            lock(singletonSection)
            {
                if (null == instance)
                    instance = new Foo();
            } 
        }
        return instance;
    }
}
这是一种确保singleton变量只设置一次的简单方法。如果需要惰性实例化,可以执行以下操作:

public class Foo: MonoBehaviour
{
    public static Foo Instance { get; private set; }

    private Foo()
    {
        Instance = this;
    }
}
private static readonly Foo instance = new Foo();
public static Foo Instance
{
    get
    {
        return instance;
    }
}
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
            instance = new Foo();
        return instance;
    }
}
private static readonly object singletonSection = new object();
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
        {
            lock(singletonSection)
            {
                if (null == instance)
                    instance = new Foo();
            } 
        }
        return instance;
    }
}
但是使用这种技术,您仍然可以多次写入实例变量。因此,您需要确保始终引用属性而不是变量。如果要从多个线程访问此属性,则需要使用以下关键部分来防止争用情况:

public class Foo: MonoBehaviour
{
    public static Foo Instance { get; private set; }

    private Foo()
    {
        Instance = this;
    }
}
private static readonly Foo instance = new Foo();
public static Foo Instance
{
    get
    {
        return instance;
    }
}
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
            instance = new Foo();
        return instance;
    }
}
private static readonly object singletonSection = new object();
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
        {
            lock(singletonSection)
            {
                if (null == instance)
                    instance = new Foo();
            } 
        }
        return instance;
    }
}

这是双重检查锁定模式。如果代码访问不多和/或性能不是问题,可以使用常规锁定。

我同意Lorek的答案,但有一个问题

您不应该使用MonoBehavior的构造函数,因为它本身有不需要的行为。因为它不是特定游戏对象的一部分。因此,您必须将其添加到该行为的InitAwakeStart方法中;或者创建一个新类来包含要共享的逻辑。(新类不应由MonoBehavior类扩展)

然后按照上面Lorek的描述创建一个单例

您还可以更改脚本执行顺序,以确保需要作为“单例”运行的MonoBehavior在所有其他脚本之前执行。 但是,必须已经将此MonoBehavior附加到场景中的现有游戏对象,而不是自动/通过代码添加


我同意洛雷克的回答,但有一个问题

您不应该使用MonoBehavior的构造函数,因为它本身有不需要的行为。因为它不是特定游戏对象的一部分。因此,您必须将其添加到该行为的InitAwakeStart方法中;或者创建一个新类来包含要共享的逻辑。(新类不应由MonoBehavior类扩展)

然后按照上面Lorek的描述创建一个单例

您还可以更改脚本执行顺序,以确保需要作为“单例”运行的MonoBehavior在所有其他脚本之前执行。 但是,必须已经将此MonoBehavior附加到场景中的现有游戏对象,而不是自动/通过代码添加


谢谢大家的支持!我最终提出了以下方法,因为在我的例子中,这些单例是通过编辑器(从菜单)创建的,并且单例应该是容器游戏对象上的组件

public static T GetInstance<T>(string containerName) where T : Component
{
    /* Find container or create if it doesn't exist. */
    var container = GameObject.Find(containerName);
    if (container == null) container = new GameObject(containerName);
    /* Get existing instance or create new one if not found. */
    return container.GetComponent<T>() ?? container.AddComponent<T>();
}
publicstatict GetInstance(stringcontainerName),其中T:Component
{
/*查找容器或创建(如果不存在)*/
var container=GameObject.Find(containerName);
如果(container==null)container=newgameobject(containerName);
/*获取现有实例,如果找不到,则创建新实例*/
返回container.GetComponent()??container.AddComponent();
}

当然,它也不是完美的,因为它只依赖于对象名。但是它对我很有用。

谢谢大家的意见!我最终提出了以下方法,因为在我的例子中,这些单例是通过编辑器(从菜单)创建的,并且单例应该是容器游戏对象上的组件

public static T GetInstance<T>(string containerName) where T : Component
{
    /* Find container or create if it doesn't exist. */
    var container = GameObject.Find(containerName);
    if (container == null) container = new GameObject(containerName);
    /* Get existing instance or create new one if not found. */
    return container.GetComponent<T>() ?? container.AddComponent<T>();
}
publicstatict GetInstance(stringcontainerName),其中T:Component
{
/*查找容器或创建(如果不存在)*/
var container=GameObject.Find(containerName);
如果(container==null)container=newgameobject(containerName);
/*获取现有实例,如果找不到,则创建新实例*/
返回container.GetComponent()??container.AddComponent();
}

当然,它也不是完美的,因为它只依赖于对象名。但是它对我很有用。

如果您需要从任何地方访问单个全局MonoBehavior脚本,MonoSingleton类非常有用。以下是MonoSingleton类:

using UnityEngine;
public class MonoSingleton<T> where T : MonoBehaviour
{
    private static T _instance;
    private static bool isFound;
    private bool createMissingInstance;
    static MonoSingleton()
    {
        isFound = false;
        _instance = null;
    }
    public MonoSingleton(bool createNewInstanceIfNeeded = true)
    {
        this.createMissingInstance = createNewInstanceIfNeeded;
    }
    public T Instance
    {
        get
        {
            if (isFound && _instance)
            {
                return _instance;
            }
            else
            {
                UnityEngine.Object[] objects = GameObject.FindObjectsOfType(typeof(T));
                if (objects.Length > 0)
                {
                    if (objects.Length > 1)
                        Debug.LogWarning(objects.Length + " " + typeof(T).Name + "s were found! Make sure to have only one at a time!");
                    isFound = true;
                    _instance = (T) System.Convert.ChangeType(objects[0], typeof(T));
                    return _instance;
                }
                else
                {
                    Debug.LogError(typeof(T).Name + " script cannot be found in the scene!!!");
                     if (createMissingInstance)
                    {
                        GameObject newInstance = new GameObject(typeof(T).Name);
                        isFound = true;
                        _instance = newInstance.AddComponent<T>();
                        Debug.Log(typeof(T).Name + " was added to the root of the scene");
                        return _instance;
                    }
                    else
                    {
                        isFound = false;
                        return null; // or default(T)
                    }
                }
            }
        }
    }
}

如果您需要从任何地方访问单个全局MonoBehavior脚本,则MonoSingleton类非常有用。以下是MonoSingleton类:

using UnityEngine;
public class MonoSingleton<T> where T : MonoBehaviour
{
    private static T _instance;
    private static bool isFound;
    private bool createMissingInstance;
    static MonoSingleton()
    {
        isFound = false;
        _instance = null;
    }
    public MonoSingleton(bool createNewInstanceIfNeeded = true)
    {
        this.createMissingInstance = createNewInstanceIfNeeded;
    }
    public T Instance
    {
        get
        {
            if (isFound && _instance)
            {
                return _instance;
            }
            else
            {
                UnityEngine.Object[] objects = GameObject.FindObjectsOfType(typeof(T));
                if (objects.Length > 0)
                {
                    if (objects.Length > 1)
                        Debug.LogWarning(objects.Length + " " + typeof(T).Name + "s were found! Make sure to have only one at a time!");
                    isFound = true;
                    _instance = (T) System.Convert.ChangeType(objects[0], typeof(T));
                    return _instance;
                }
                else
                {
                    Debug.LogError(typeof(T).Name + " script cannot be found in the scene!!!");
                     if (createMissingInstance)
                    {
                        GameObject newInstance = new GameObject(typeof(T).Name);
                        isFound = true;
                        _instance = newInstance.AddComponent<T>();
                        Debug.Log(typeof(T).Name + " was added to the root of the scene");
                        return _instance;
                    }
                    else
                    {
                        isFound = false;
                        return null; // or default(T)
                    }
                }
            }
        }
    }
}

好的,到目前为止我基本上就是这样做的。但我不确定自己是否完全理解Unity如何实例化组件。它们不是由AddComponent()实例化的吗?至少看起来是这样。在这种情况下,“手动”实例化会导致另一个实例,不是吗?因为GetComponent()肯定不使用实例getter。我明白你现在的问题了。我去拿b