【发布时间】:2010-04-19 11:42:32
【问题描述】:
我需要存储一堆需要全局访问的变量,我想知道单例模式是否适用。从我看到的例子来看,单例模式只是一个不能被继承的静态类。但是我看到的例子对于我的需求来说过于复杂了。什么是最简单的单例类?我不能只创建一个包含一些变量的静态密封类吗?
【问题讨论】:
-
有关更现代的实现的讨论,请参阅:stackoverflow.com/questions/2117241/…
我需要存储一堆需要全局访问的变量,我想知道单例模式是否适用。从我看到的例子来看,单例模式只是一个不能被继承的静态类。但是我看到的例子对于我的需求来说过于复杂了。什么是最简单的单例类?我不能只创建一个包含一些变量的静态密封类吗?
【问题讨论】:
通常单例不是静态类 - 单例将为您提供一个类的单个实例。
我不知道你看过什么例子,但通常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; } }
}
这并不难。
单例相对于静态成员的优势在于类可以实现接口等。有时这很有用 - 但其他时候,静态成员确实也可以。此外,通常以后从单例移动到非单例更容易,例如将单例作为“配置”对象传递给依赖类,而不是那些进行直接静态调用的依赖类。
就我个人而言,我会尽量避免使用单例 - 除了其他任何事情之外,它们会使测试变得更加困难。不过它们有时会很有用。
【讨论】:
private static readonly ConcurrentDictionary _reflectionCache = new ConcurrentDictionary();
有几种模式可能适合你,单例是最糟糕的一种。
注册表
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。
您应该看看依赖注入,因为它通常被认为是最佳实践,但它的话题太大,无法在此处解释:
【讨论】:
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() { }
}
如果您打算按照自己的想法行事,那么静态密封类就可以正常工作。
【讨论】:
使用 C# 6 自动属性初始化器。
public sealed class Singleton
{
private Singleton() { }
public static Singleton Instance { get; } = new Singleton();
}
短而干净 - 我很乐意听到缺点。
【讨论】:
我知道这个问题很老,但这里有另一个使用 .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
【讨论】:
所以,就我而言,这是 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 的依赖关系,并且可以使用依赖注入或以下方法之一传入:
【讨论】:
使用您的语言功能。 大多数简单的线程安全实现是:
public sealed class Singleton
{
private static readonly Singleton _instance;
private Singleton() { }
static Singleton()
{
_instance = new Singleton();
}
public static Singleton Instance
{
get { return _instance; }
}
}
【讨论】:
只是为了添加另一种可能的解决方案。我能想到的最简单、最直接且易于使用的方法是这样的:
//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;
}
}
【讨论】: