如果我考虑单例的基本实现,例如:
private static Foo instance;
private readonly static Object SyncRoot=new Object();
public static Foo Instance {
get {
if(instance!=null)
return instance;
lock(SyncRoot) {
if(instance!=null) {
return instance;
}
instance=new Foo();
return instance;
}
}
}
是否有任何情况下,我得到两个不同的单例在同一个应用程序?(动态DLL加载与反射,执行和同步上下文,appdomain类,或任何其他类型的"魔术"?)
您必须定义"同一应用程序"的含义。如果一个"应用程序"可以跨越多个AppDomain,那么是的——每个AppDomain将有效地拥有一个完全独立的Foo
类。同样,如果您有可信代码使用反射将instance
字段重置为null,那么您将非常容易地结束两个实例:
var field = typeof(Foo).GetField("instance",
BindingFlags.Static | BindingFlags.NonPublic);
var foo1 = Foo.Instance;
field.SetValue(null, null);
var foo2 = Foo.Instance;
foo1
和foo2
都是非空的,不同的引用。或者正如gdoron的回答所提到的,代码也可以通过反射调用构造函数(可能是私有的)。
在单个AppDomain中,没有任何故意引起问题,您应该没问题。
请注意,我并不推荐这种单例模式的实现。我通常只是使用静态初始化器来简化工作。要了解更多细节,请参阅我关于单例实现的文章。
是的,这是可能的反射,你的代码只适用于属性,反射可以创建没有属性的Foo
实例。
ConstructorInfo ctor = typeof(Foo).GetConstructors
(BindingFlags.Instance | BindingFlags.NonPublic)[0];
Foo foo = (Foo) ctor.Invoke(null);
当然,如果您使用不同的AppDomain,那么每个AppDomain将获得一个实例。我认为如果您在多线程环境中使用锁定机制也可能存在问题。与其在getter中创建实例,不如使用
private static Foo Instance = new Foo();
抗反射单例模式:
public sealed class Singleton
{
public static Singleton Instance => _lazy.Value;
private static Lazy<Singleton, Func<int>> _lazy { get; }
static Singleton()
{
var i = 0;
_lazy = new Lazy<Singleton, Func<int>>(() =>
{
i++;
return new Singleton();
}, ()=>i);
}
private Singleton()
{
if (_lazy.Metadata() == 0 || _lazy.IsValueCreated)
throw new Exception("Singleton creation exception");
}
public void Run()
{
Console.WriteLine("Singleton called");
}
}
然后试试:
static void Main(string[] args)
{
Singleton.Instance.Run();
((Singleton) Activator.CreateInstance(typeof(Singleton), true)).Run();
}