什么是C#中的单例?


181

什么是Singleton,什么时候应该使用它?



4
同样,Singleton是OO编程中使用最广泛和滥用最广泛的设计模式之一。
ChaosPandion 2010年

3
@Fabiano:因为它有一种创建没有意义的耦合的方式(我该如何与之X交谈Y?只需做Y一个单例!),这反过来会导致测试/调试和编程程序风格的困难。有时单身人士是必要的;大多数时候,不是。
Aaronaught

3
这是我的标准电话面试问题之一。正确的答案是:从不。
jonnii

3
@jonnii很好,它可以警告潜在的开发人员老板的样子!
男孩先生

Answers:


144

单例是仅允许创建其自身的一个实例的类,并提供对该实例的简单,便捷的访问。单例前提是整个软件开发的一种模式。

有一个C#实现“在C#中实现单例模式”,涵盖了您需要了解的大多数知识-包括有关线程安全的一些好的建议。

老实说,您很少需要实现一个单例-在我看来,即使不经常使用它,它也应该是您应该意识到的事情之一。


2
不错的教程,但是
天哪,

这是我认为是2020年理想实现的更直接链接。即“ 使用.NET 4的Lazy <T>类型 ”,以及指向的Microsoft Doc的链接Lazy<T> Class
Chiramisu

52

您要求提供C#。琐碎的例子:


public class Singleton
{
    private Singleton()
    {
        // Prevent outside instantiation
    }

    private static readonly Singleton _singleton = new Singleton();

    public static Singleton GetSingleton()
    {
        return _singleton;
    }
}

14
不是线程安全的。两个线程可以同时调用并可以创建两个单独的对象。
Alagesan Palani 2015年

5
@Alagesan Palani,的确是正确的。我不擅长于类级别初始化的低级细节,但是我认为所做的更改解决了线程安全问题。
克里斯·西蒙斯

3
当然,我并不是说你错了。我向读者暗示了线程安全性,以便他们在处理线程安全性时要格外小心。
Alagesan Palani 2015年

9
不,我认为您的评论很重要。考虑到一个单例应该传递一个(且只有一个)实例,因此此处的竞争条件带来了传递多个实例的可能性。现在查看带有静态字段初始化的版本。我相信,如果我阅读文档并正确回答了该问题,就可以解决线程安全问题。
克里斯·西蒙斯

1
@AlagesanPalani,我看到您已经指出其他几个答案也不是线程安全的。您是否愿意提供线程安全的解决方案?
Bonez024 '18

39

它是什么:在应用程序的整个生命周期中,只有一个持久性实例的类。请参阅单例模式

何时使用:尽可能少。仅在绝对确定需要时。我不太愿意说“从不”,但是通常有更好的选择,例如依赖注入或仅是静态类。


16
我不确定静态类是否比单例更好……它确实取决于情况和语言。
marcgg 2010年

5
静态类的行为不同于单例,单例可以作为参数传递给方法,而静态类则不能。
TabbyCool 2010年

4
同意marcgg-我认为静态类不能替代单例,因为您仍然有提供替代品的问题,例如在测试依赖于此类的组件期间。但是我也看到了不同的用法,静态类通常用于独立于状态的独立实用程序功能,其中单例是实际的类实例,并且通常会存储状态。我完全同意使用DI,然后告诉您的DI容器您只希望使用该类的单个实例。
皮特2010年

9
我否决了这个答案,因为它没有提供有关何时使用它的信息。“只有在需要时”才真正为单身人士提供任何信息。
塞尔吉奥·塔皮亚

9
@Adkins:DI表示依赖注入,这是指通过(通常)构造函数或公共属性传入任何类依赖的情况。DI本身并不能解决“距离”问题,但通常与控制反转(IoC)容器一起实现,该容器知道如何自动初始化任何依赖项。因此,如果您要创建一个Singleton来解决“ X不知道如何查找/与Y对话”的问题,则DI和IoC的组合可以解决松耦合问题。
Aaronaught

27

在c#中实现单例的另一种方法,我个人更喜欢这种方法,因为您可以将singeton类的实例作为属性而不是方法来访问。

public class Singleton
    {
        private static Singleton instance;

        private Singleton() { }

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

        //instance methods
    }

但是据我所知,两种方式都被认为是“正确”的,所以这只是个人喜好。


11
不是线程安全的。两个线程可以同时调用并可以创建两个单独的对象。
Alagesan Palani 2015年

11
using System;
using System.Collections.Generic;
class MainApp
{
    static void Main()
    {
        LoadBalancer oldbalancer = null;
        for (int i = 0; i < 15; i++)
        {
            LoadBalancer balancerNew = LoadBalancer.GetLoadBalancer();

            if (oldbalancer == balancerNew && oldbalancer != null)
            {
                Console.WriteLine("{0} SameInstance {1}", oldbalancer.Server, balancerNew.Server);
            }
            oldbalancer = balancerNew;
        }
        Console.ReadKey();
    }
}

class LoadBalancer
{
    private static LoadBalancer _instance;
    private List<string> _servers = new List<string>();
    private Random _random = new Random();

    private static object syncLock = new object();

    private LoadBalancer()
    {
        _servers.Add("ServerI");
        _servers.Add("ServerII");
        _servers.Add("ServerIII");
        _servers.Add("ServerIV");
        _servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
        if (_instance == null)
        {
            lock (syncLock)
            {
                if (_instance == null)
                {
                    _instance = new LoadBalancer();
                }
            }
        }

        return _instance;
    }

    public string Server
    {
        get
        {
            int r = _random.Next(_servers.Count);
            return _servers[r].ToString();
        }
    }
}

我从dofactory.com上获取了代码,没什么花哨的,但是我发现这比Foo和Bar的示例要好得多,另外Judith Bishop撰写的有关C#3.0 Design Patterns的书也提供了有关Mac Dock中活跃应用程序的示例。

如果看一下代码,我们实际上是在for循环上构建新对象,以便创建新对象,但重用实例,结果oldbalancer和newbalancer具有相同的实例,如何?其由于静态上功能中使用的关键字GetLoadBalancer() ,尽管有不同的服务器值是随机的列表中,静态的GetLoadBalancer()所属的类型本身,而不是一个特定对象。

此外,还有双重检查锁定在这里

if (_instance == null)
            {
                lock (syncLock)
                {
                    if (_instance == null)

从MSDN开始

lock关键字可确保一个线程不会输入代码的关键部分,而另一个线程位于关键部分。如果另一个线程试图输入锁定的代码,它将等待阻塞,直到对象被释放为止。

因此,即使不需要,每次都会发出互斥锁,所以我们进行了null检查。

希望它有助于清除更多。

如果我的理解指向错误的方向,请发表评论。


6

当您只想在整个应用程序中创建一个类的一个实例时,就可以使用Singleton(并且不与C#绑定,这是一种OO设计模式)。使用通常包括全球资源,尽管我会从个人经验中说,它们经常是痛苦的根源。


5

尽管只能有一个单例实例,但它与静态类并不相同。静态类只能包含静态方法,并且不能实例化,而单例实例可以与任何其他对象相同的方式使用。


2

这是一种设计模式,并非特定于c#。可以在Internet和SO上获得更多有关此方面的信息,例如Wikipedia上的文章

在软件工程中,单例模式是一种设计模式,用于将类的实例化限制为一个对象。当仅需要一个对象来协调整个系统中的动作时,这很有用。有时将该概念推广到在只有一个对象存在的情况下运行效率更高的系统,或者将实例化限制为一定数量的对象(例如五个)的系统。有人认为它是一种反模式,认为它过度使用,在实际上不需要类的唯一实例的情况下引入了不必要的限制,并将全局状态引入了应用程序。

如果您想要一个只能实例化一次的类,则应使用它。


2

我用它来查找数据。从数据库加载一次。

public sealed class APILookup
    {
        private static readonly APILookup _instance = new APILookup();
        private Dictionary<string, int> _lookup;

        private APILookup()
        {
            try
            {
                _lookup = Utility.GetLookup();
            }
            catch { }
        }

        static APILookup()
        {            
        }

        public static APILookup Instance
        {
            get
            {
                return _instance;
            }
        }
        public Dictionary<string, int> GetLookup()
        {
            return _lookup;
        }

    }

2

什么是单例:
它是一个类,它仅允许创建其自身的一个实例,并且通常提供对该实例的简单访问。

何时使用:
取决于具体情况。

注意:请不要在数据库连接上使用,有关详细答案,请参阅 @Chad Grant的答案

这是一个简单的例子Singleton

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

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

    private Singleton()
    {
    }

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

您也可以使用Lazy<T>来创建自己的Singleton

请参阅此处以获取更详细的示例Lazy<T>



1

Singleton类用于为整个应用程序域创建单个实例。

public class Singleton
{
    private static Singleton singletonInstance = CreateSingleton();

    private Singleton()
    {
    }

    private static Singleton CreateSingleton()
    {
        if (singletonInstance == null)
        {
            singletonInstance = new Singleton();
        }

        return singletonInstance;
    }

    public static Singleton Instance
    {
        get { return singletonInstance; }            
    }
}

本文中,我们描述了如何使用只读变量创建线程安全的单例类,以及如何在应用程序中实际使用它们。


1

我知道回答这个问题已经很晚了,但是使用自动属性,您可以执行以下操作:

public static Singleton Instance { get; } = new Singleton();

Singleton您在哪里上课,可以在哪里(在本例中为readonly属性)通过Instance


0

EX您可以将Singleton用于需要注入的全局信息。

就我而言,我将Logged用户的详细信息(用户名,权限等)保留在Global Static Class中。当我尝试实现单元测试时,我无法将依赖项注入Controller类。因此,我将我的静态类更改为单例模式。

public class SysManager
{
    private static readonly SysManager_instance = new SysManager();

    static SysManager() {}

    private SysManager(){}

    public static SysManager Instance
    {
        get {return _instance;}
    }
}

http://csharpindepth.com/Articles/General/Singleton.aspx#cctor


0

当我们需要确保仅创建特定类的一个实例,然后为整个应用程序提供对该实例的简单全局访问时,我们需要在C#中使用Singleton设计模式。

可以使用Singleton设计模式的实时场景:服务代理:众所周知,调用Service API是应用程序中的一项广泛操作。花费大部分时间的过程是创建服务客户端以调用服务API。如果将服务代理创建为Singleton,则它将提高应用程序的性能。

外墙:您还可以将数据库连接创建为Singleton,以提高应用程序的性能。

日志:在应用程序中,对文件执行I / O操作是一项昂贵的操作。如果将Logger创建为Singleton,则它将提高I / O操作的性能。

数据共享:如果您有任何常量值或配置值,则可以将这些值保留在Singleton中,以便应用程序的其他组件可以读取它们。

缓存:众所周知,从数据库中获取数据是一个耗时的过程。在您的应用程序中,您可以将主服务器和配置缓存在内存中,这将避免DB调用。在这种情况下,可以使用Singleton类以有效的方式通过线程同步处理缓存,从而大大提高应用程序的性能。

C#中单例设计模式的缺点在C#中使用单例设计模式的缺点如下:

单元测试非常困难,因为它会将全局状态引入到应用程序中。因为要在多线程环境中访问单例实例,您需要使用锁定来序列化对象,所以它减少了程序内并行的可能性。

我从以下文章中摘录了此内容。

https://dotnettutorials.net/lesson/singleton-design-pattern/


0

线程安全单例,无需使用锁,也无需延迟实例化。

此实现具有静态构造函数,因此每个应用程序域仅执行一次。

public sealed class Singleton
{

    static Singleton(){}

    private Singleton(){}

    public static Singleton Instance { get; } = new Singleton();

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.