C# 中的单例模式(最新版)

2019/07/26

47

单例模式是软件工程中最著名的模式之一。从本质上讲,单例是一个类的实例,这个类型只允许被创建成单个实例。最常见的情况是,单例不允许在创建实例时指定任何参数,否则实例的第二个请求(但具有不同参数)可能会有问题!如果需要访问具有相同参数的所有请求的同一实例,则工厂模式更适合。本文讨论的是不需要参数的情况。通常,单例被首次创建之前不会被创建实例。

C# 中实现单例模式有各种不同的方法。我们将在这里用从旧往新的方式编写单例模式,呈现C#的优雅。从最常见的、线程不安全的单例模式到一个完全懒惰加载、线程安全、简单、高性能的版本。

但是,所有的实现都具有以下四个共同特征:

第一个版本 - 线程不安全的

// Bad code! Do not use!
public sealed class Singleton
{
    private Singleton() { }

    static Singleton _instance;

    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }
    }
}

上述代码是线程不安全的。上述代码在多线程并发获取Instance时会创建出多个实例,这违背了单例模式。

第二个版本 - 简单的线程安全

// Bad code! Do not use!
public sealed class Singleton
{
    private Singleton() { }

    static Singleton _instance;
    static readonly object _padlock = new object();

    public static Singleton Instance
    {
        get
        {
            lock (_padlock)
            {
                if (_instance == null)
                {
                    _instance = new Singleton();
                }
            }
            return _instance;
        }
    }
}

上述代码是线程安全的。线程在创建实例之前获取共享对象的🔒,然后检查实例是否已创建。因为🔒可以确保只有一个线程可以创建实例。但遗憾的是,每次请求实例时都会获取🔒,因此性能会受到影响。

请注意,我锁定的静态变量的值是类的私有字段,而不是像此实现的某些版本那样锁定。锁定其他类可以访问并锁定的对象(如类型)可能会面临性能问题甚至死锁。

第三个版本 - 双检查机制➕🔒

// Bad code! Do not use!
public sealed class Singleton
{
    private Singleton() { }

    static Singleton _instance;
    static readonly object _padlock = new object();

    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
            {
                lock (_padlock)
                {
                    _instance = new Singleton();
                }
            }
            return _instance;
        }
    }
}

此实现是线程安全的,而不必每次都要取锁。遗憾的是,上述代码有四个缺点

第四个版本 - 不太惰性但线程安全且不用🔒

public sealed class Singleton
{
    // 显式指定静态构造函数,告诉 C# 编译器不将类型标记为 BeforeFieldInit
    static Singleton() { }
    private Singleton() { }

    static readonly Singleton _instance = new Singleton();

    public static Singleton Instance => _instance;
}

正如我们看到的,这真的非常简单,但为什么它是线程安全的、惰性的?C#中的静态构造函数被指定为将在创建类的实例或引用静态成员时执行,并且每个 AppDomain 只执行一次。鉴于新构造的类型需要执行此检查,无论发生什么情况,它的速度都比添加前面的示例中的额外检查要快。然而,有几个缺点

第五个版本 - 完全惰性实例化

public sealed class Singleton
{
    private Singleton() { }

    public static Singleton Instance { get { return Nested.instance; } }

    private class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested() { }

        internal static readonly Singleton instance = new Singleton();
    }
}

第六个版本 - 使用 .NET 4 的 Lazy 类型

如果您使用的是 .NET 4(或更高版本),则可以使用它。您只需将一个委托传递给调用 Singleton 构造函数的构造函数 - 这是使用 lambda 表达式最容易完成的。

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =  new Lazy<Singleton> (() => new Singleton());

    public static Singleton Instance { get { return lazy.Value; } }

    private Singleton() { }
}

它是简单的、性能良好的。如果还需要,它还允许您检查是否已使用 IsValueCreated 属性创建实例

来自深入理解C#第四版,这本书是我非常期待的书籍,中文版正在翻译中。csharpindepth

评论