支持重入的简明单例 impl

本文关键字:单例 impl 支持 | 更新日期: 2023-09-27 18:31:51

我想找到一个简洁的 C# 实现 Singleton 模式,我想知道它是否应该支持以下方案。 如果我的单一实例的私有构造函数在初始化实例的过程中调用了本身尝试访问当前正在初始化的单一实例的构造函数,该怎么办? 这就是我在这个问题标题中所说的重入的意思。 有人会说构造函数不应该做任何可能导致这种情况发生的事情。 但是,如果由于调用的构造函数中的某些未来代码更改,它确实发生了怎么办?

我浏览了这个问题的各种答案。 使用Lazy<T>的简洁性给我留下了深刻的印象。 在我正在寻找的用例中,它抛出了一个异常,这比构造两个单例实例要好得多。 但是,唉,在抛出异常时,它会使应用程序崩溃,这意味着它不支持目标场景。

class Apple
{
    static readonly Lazy<Apple> instance = new Lazy<Apple>( () => new Apple(), true );
    public static Apple Instance { get { return instance.Value; } }
    private Apple()
    {
        // Call other methods which might access Instance...
        var testGet = Instance;
    }
}

所以我有想法做以下事情。

class Banana
{
    static Banana instance;
    public static Banana Instance { get { return instance ?? new Banana(); } }
    private Banana()
    {
        instance = this;
        // Then call other methods which might access Instance...
        var testGet = Instance;
    }
}

它支持目标场景,但它有什么问题吗? 有没有更好的解决方案?

在发布答案之前,请注意以下事项。 像许多人一样,我仍然认为单例是一种模式。 许多 DI 爱好者喜欢称其为反模式。 在依赖于 DI/IoC 的项目上下文中,这是一个合理的断言。 但是,在该上下文之外,单例仍然是一种有效的设计模式。 我不使用 DI,也不想在这里讨论它的优点。 请不要在下面发布答案,如果它将Singleton称为"反模式",或者如果它将提供"依赖注入"作为解决方案。 提前谢谢。

支持重入的简明单例 impl

尽管 Singleton 可能不被视为反模式,但在完全构造实例之前访问该实例的成员是一种反模式。 无法保证正在初始化的类外部的代码不会尝试使用某些未初始化的状态。 因此,不应支持有问题的方案。 如果尝试访问单一实例的代码稍后添加到单一实例的构造函数调用的构造函数中,则应该会触发重新设计。

按照Apple演示的那样使用 Lazy<T> 是更好的方法,因为它会在重新进入时引发异常,而不是访问不完整的实例。 如果需要,Lazy<T>还支持从多个线程进行访问。