C#的单例模式[关闭]

问题描述 投票:14回答:8

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

c# singleton
8个回答
38
投票

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

我不知道你见过的例子,但通常singleton pattern在C#中非常简单:

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; } }
}

这并不困难。

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

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


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];
  }
}

好处

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

缺点

  • 比Singleton或全局变量略慢

静态类

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

好处

  • 简单
  • 快速

缺点

  • 很难动态切换到模拟对象
  • 如果需求发生变化,很难切换到另一种对象类型

简单的单身人士

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


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() { }
}

如果你要沿着你想要的道路走下去,一个静态密封的课程就可以了。


1
投票

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

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

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


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,它知道如何使用它的私有构造函数实例化一个类。

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

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

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

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字段的值,并且我们要声明将从此属性(Value of Lazy)返回的两个实例都相同。

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


-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


-1
投票

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

public sealed class Singleton
{
    private static readonly Singleton _instance;

    private Singleton() { }

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

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

-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;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.