C#的单例模式

时间:2010-04-19 11:42:32

标签: c# singleton

我需要存储一堆需要全局访问的变量,我想知道单例模式是否适用。从我看过的例子来看,单例模式只是一个无法继承的静态类。但我见过的例子对我的需求来说过于复杂。什么是最简单的单身人士课程?我不能只用一些变量制作一个静态的密封类吗?

8 个答案:

答案 0 :(得分:38)

通常单例不是一个静态类 - 单例将为您提供一个类的单个实例

我不知道你看过的是什么样的例子,但是在C#中singleton pattern通常很简单:

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();
    static Singleton() {} // Make sure it's truly lazy
    private Singleton() {} // Prevent instantiation outside

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

这并不难。

单例相对于静态成员的优点是类可以实现接口等。有时这很有用 - 但有时候,静态成员确实也会这样做。另外,以后通常更容易从单身人士转移到非单身人士,例如将单例作为“配置”对象传递给依赖类,而不是那些进行直接静态调用的依赖类。

就个人而言,我会尽量避免使用单身人士 - 除了其他任何事情之外,他们会更加努力地进行测试。它们偶尔会有用。

答案 1 :(得分:8)

有几种模式可能适合你,单身是最糟糕的模式之一。

<强>注册表

struct Data {
  public String ProgramName;
  public String Parameters;
}

class FooRegistry {
  private static Dictionary<String, Data> registry = new Dictionary<String, Data>();
  public static void Register(String key, Data data) {
     FooRegistry.registry[key] = data;
  }
  public static void Get(String key) {
     // Omitted: Check if key exists
     return FooRegistry.registry[key];
  }
}

优点

  • 轻松切换到模拟对象进行自动化测试
  • 您仍然可以存储多个实例,但如果需要,您只有一个实例。

缺点

  • 比Singleton或全局变量稍慢

静态类

class GlobalStuff {
  public static String ProgramName {get;set;}
  public static String Parameters {get;set;}
  private GlobalStuff() {}
}

优点

  • 简单
  • 快速

缺点

  • 很难动态切换到模拟对象
  • 如果需求更改
  • ,则很难切换到其他对象类型

Simple Singleton

class DataSingleton {
  private static DataSingleton instance = null;
  private DataSingleton() {}
  public static DataSingleton Instance {
     get {
         if (DataSingleton.instance == null) DataSingleton.instance = new DataSingleton();
         return DataSingleton;
     }
  }
}

优点

  • 没有,

缺点

  • 如果多个线程访问该实例,则难以创建线程安全单例,上述版本失败。
  • 很难切换模拟对象

我个人喜欢注册表模式但YMMV。

您应该查看依赖注入,因为它通常被认为是最佳实践,但这里的主题太大了解释:

Dependency Injection

答案 2 :(得分:3)

Singleton不仅仅是一个无法继承的静态类。它是一个常规类,只能实例化一次,每个人都共享该单个实例(并使其线程安全更加有效)。

Singleton的典型.NET代码如下所示。 这是一个快速示例,绝不是最佳实现或线程安全代码

public sealed class Singleton
{
    Singleton _instance = null;

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

            return _instance;
        }
    }

    // Default private constructor so only we can instanctiate
    private Singleton() { }

    // Default private static constructor
    private static Singleton() { }
}

如果你想沿着你正在思考的道路前进,那么静态密封课程就可以正常工作。

答案 3 :(得分:1)

使用C#6自动属性初始化器。

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

简短而干净 - 我会很高兴听到缺点。

答案 4 :(得分:0)

我知道这个问题已经过时了,但是这是另一个使用.Net 4.0或更高版本的解决方案(包括.Net Core和.Net Standard)。

首先,定义将转换为Singleton的类:

public class ClassThatWillBeASingleton
{
    private ClassThatWillBeASingleton()
    {
        Thread.Sleep(20);
        guid = Guid.NewGuid();
        Thread.Sleep(20);
    }

    public Guid guid { get; set; }
}

在这个示例类中,我定义了一个睡眠一段时间的构造函数,然后创建一个新的Guid并保存到它的公共属性。 (Sleep只用于并发测试)

请注意,构造函数是私有的,因此没有人可以创建此类的新实例。

现在,我们需要定义将此类转换为单例的包装器:

public abstract class SingletonBase<T> where T : class
{
    private static readonly Lazy<T> _Lazy = new Lazy<T>(() =>
    {
        // Get non-public constructors for T.
        var ctors = typeof(T).GetConstructors(System.Reflection.BindingFlags.Instance |
                                              System.Reflection.BindingFlags.NonPublic);
        if (!Array.Exists(ctors, (ci) => ci.GetParameters().Length == 0))
            throw new InvalidOperationException("Non-public ctor() was not found.");
        var ctor = Array.Find(ctors, (ci) => ci.GetParameters().Length == 0);
        // Invoke constructor and return resulting object.
        return ctor.Invoke(new object[] { }) as T;
    }, System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);

    public static T Instance
    {
        get { return _Lazy.Value; }
    }
}

请注意,它使用Lazy创建一个字段_Lazy,它知道如何使用私有构造函数实例化一个类。

它定义了一个Property Instance来访问Lazy字段的值。

注意传递给Lazy构造函数的LazyThreadSafetyMode枚举。它正在使用ExecutionAndPublication。因此,只允许一个线程初始化Lazy字段的值。

现在,我们所要做的就是定义将成为单例的包装类:

public class ExampleSingleton : SingletonBase<ClassThatWillBeASingleton>
{
    private ExampleSingleton () { }
}

以下是一个用法示例:

ExampleSingleton.Instance.guid;

一个测试断言两个线程将获得Singleton的相同实例:

[Fact()]
public void Instance_ParallelGuid_ExpectedReturnSameGuid()
{
    Guid firstGuid = Guid.Empty;
    Guid secondGuid = Guid.NewGuid();

    Parallel.Invoke(() =>
    {
        firstGuid = Singleton4Tests.Instance.guid;
    }, () =>
    {
        secondGuid = Singleton4Tests.Instance.guid;
    });

    Assert.Equal(firstGuid, secondGuid);
}

此测试同时调用Lazy字段的值,并且我们想断言将从此属性返回的两个实例(Lazy的值)都是相同的。

有关此主题的更多详细信息,请访问:C# in Depth

答案 5 :(得分:-1)

因此,就我而言,这是C#中Singleton模式最简洁,最简单的实现。

http://blueonionsoftware.com/blog.aspx?p=c6e72c38-2839-4696-990a-3fbf9b2b0ba4

然而,我会建议单身人士是非常丑陋的模式......我认为它们是一种反模式。

http://blogs.msdn.com/scottdensmore/archive/2004/05/25/140827.aspx

对我来说,我更喜欢拥有像Repository这样的东西来实现IRepository。您的类可以在构造函数中声明对IRepository的依赖,并且可以使用依赖注入或其中一种方法传递它:

http://houseofbilz.com/archive/2009/05/02.aspx

答案 6 :(得分:-1)

使用您的语言功能。 大多数简单的线程安全实现是:

public sealed class Singleton
{
    private static readonly Singleton _instance;

    private Singleton() { }

    static Singleton()
    {
        _instance = new Singleton();
    }

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

答案 7 :(得分:-1)

......最简单的单身人士课程是什么?

只需添加一个可能的解决方案。我能想到的最简单,最直接和易于使用的方法是这样的:

//The abstract singleton
public abstract class Singleton<T> where T : class
{
    private static readonly Lazy<T> instance = new Lazy<T>( CreateInstance, true );

    public static T Instance => instance.Value;

    private static T CreateInstance()
    {
        return (T)Activator.CreateInstance( typeof(T), true);
    }
}

//This is the usage for any class, that should be a singleton
public class MyClass : Singleton<MyClass>
{
    private MyClass()
    {
        //Code...
    }

    //Code...
}

//Example usage of the Singleton
class Program
{
    static void Main(string[] args)
    {
        MyClass clazz = MyClass.Instance;
    }
}