【问题标题】:Singleton Pattern for C# [closed]C# 的单例模式 [关闭]
【发布时间】:2010-04-19 11:42:32
【问题描述】:

我需要存储一堆需要全局访问的变量,我想知道单例模式是否适用。从我看到的例子来看,单例模式只是一个不能被继承的静态类。但是我看到的例子对于我的需求来说过于复杂了。什么是最简单的单例类?我不能只创建一个包含一些变量的静态密封类吗?

【问题讨论】:

标签: c# singleton


【解决方案1】:

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

我不知道你看过什么例子,但通常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; } }
}

这并不难。

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

就我个人而言,我会尽量避免使用单例 - 除了其他任何事情之外,它们会使测试变得更加困难。不过它们有时会很有用。

【讨论】:

  • 真棒解释....你能告诉我们一个实例(实时)你会发现单例模式有用吗? +1
  • @Raja:我现在想不出任何真实的情况,尽管这并不意味着我没有使用任何情况。
  • @Raja 这是我将在 .NET 4.0 中大量使用的东西,在我的类中进行任何反射private static readonly ConcurrentDictionary _reflectionCache = new ConcurrentDictionary();
  • @bufferz:这往往会使测试变得更加困难,IMO - 您将所有内容都连接到一个类,而不是为每个对象呈现其依赖关系。
  • @JonSkeet 我不敢相信 Jon Skeet 会回复我 :)。谢谢,我去看看
【解决方案2】:

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

注册表

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

优势

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

缺点

  • 比单例或全局变量稍慢

静态类

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

【讨论】:

  • 我认为你的单例模式是错误的,如果检查 getter 内部不是线程安全的。你想实现一个由@Jon Skeet 描述的单例。理论上 2 个线程可以同时进入该 getter,两者都看到 instance == null,然后都执行第二行,导致单例通过最后一个输出线程发生变异。
  • @Chris:我在“缺点”部分提到了这个实现不是线程安全的。代码是用来解释这个概念的,而不是给出一个完整的例子。
  • @dbemerlin 请修复 FooRegistry 的代码,公共静态 Data Get(String key) 而不是 void 。非常感谢您的精彩概述。
【解决方案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() { }
}

如果您打算按照自己的想法行事,那么静态密封类就可以正常工作。

【讨论】:

  • 我认为您需要稍微修改该代码 - 除非您在修改时非常小心,否则它不会是线程安全的。但是,使其成为线程安全的确实非常容易。
  • @Jon 我不打算在这里使用线程安全版本。快速示例。我知道一种使它成为线程安全的方法,但我很想知道其他版本(只是出于好奇)。
【解决方案4】:

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

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

短而干净 - 我很乐意听到缺点。

【讨论】:

  • 对于初学者来说,它不是线程安全的
  • @JerryGoyal 怎么样?在多线程环境中使用此实现可能会遇到什么问题?
【解决方案5】:

我知道这个问题很老,但这里有另一个使用 .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 构造函数的 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,我们要断言将从该属性(Lazy 的值)返回的两个实例相同。

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

【讨论】:

    【解决方案6】:

    所以,就我而言,这是 C# 中单例模式的最简洁和简单的实现。

    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

    【讨论】:

      【解决方案7】:

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

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

      【讨论】:

        【解决方案8】:

        ...什么是最简单的单例类?

        只是为了添加另一种可能的解决方案。我能想到的最简单、最直接且易于使用的方法是这样的:

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

        【讨论】:

        • 那些类不是单例的。由于有公共构造函数,因此可以创建任意数量的实例。
        • 好吧。任何具有除私有构造函数之外的公共构造函数的 Singleton 子类都不是单例。上述方法不强制使用单例。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-01
        • 2014-06-05
        • 1970-01-01
        • 2013-03-11
        • 1970-01-01
        相关资源
        最近更新 更多