C#中单例对象构造的问题

时间:2012-10-17 05:44:32

标签: c# constructor singleton

我遵循一段实现单例类(Double-Check Locking)的代码

public sealed class Plugin
{
    #region Private Fields

    private static volatile Plugin _instance;
    private static object syncRoot = new Object();

    private Dictionary<int, string> myMap;

    #endregion

    private Plugin()
    {
        myMap = MapInit(GetMainModuleName());
    }

    static Plugin()
    { }

    public static Plugin Instance
    {
        get 
        {
            if (_instance == null) 
            {
                lock (syncRoot) 
                {
                   if (_instance == null)
                       _instance = new Plugin();
                }
            }

            return _instance;
        }
    }
}

单例实例在调试模式下正确构建,一切似乎都正常工作。但是在发布模式下,实例在正确构造之前返回,即myMap未初始化。

另外需要注意的是,以下代码需要大约10-15秒才能在调试模式下完全执行

myMap = MapInit(GetMainModuleName());

这是一些编译器优化的问题吗?请帮忙

2 个答案:

答案 0 :(得分:1)

你不需要Singleton,实际上你不做Singleton。为什么这些天人们在做单身人士?

看,这很简单:

public sealed class Plugin
{
    private static readonly Plugin _instance;
    private /*readonly?*/ Dictionary<int, string> myMap;

    private Plugin()
    {
        myMap = MapInit(GetMainModuleName());
    }

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

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

静态构造函数保证每个应用程序域只运行一次,这是C#语言规范的一部分。


要解决您的问题,双重检查模式存在问题,因为您已经证明当机器在硬件中有多个线程时,它不适用于编译器优化。原因是......

[来自http://blogs.msdn.com/b/brada/archive/2004/05/12/130935.aspx]

  

内存模型允许重新排序非易失性读/写   只要从a的角度看不能发现这种变化   单线程。

即使使用volatilevolatile关键字告诉编译器必须在读取字段_instance后写入字段_instance。但是,在首先读取Plugin的值之前,没有什么可以阻止它初始化新的_instance对象。

除此之外,你说你正面临另一个问题:

  

在构造正确之前返回实例

然后你需要等待初始化完成,而不仅仅是检查它是否已经启动。显然字段_instance已经在类Plugin的构造函数结束之前设置了,如果是这种情况,则意味着你需要等到它完成。此外,如果有一些异步调用,你可能需要添加一个“就绪”属性或其他一些等待方式[允许一个对象处于无效状态是你的错误]。

*:这通常是通过引入时间变量来解决的,您可以在其中设置新实例以及将该变量分配给您的字段。该技术还允许通过添加内存屏障使字段非易失性......然而,它增加了使构造函数运行多次的风险。所以,我已经跳过了这一切。

要解决这两个问题,你可以使用Interlocked和ManualResetEvent的这种组合[不知道构造函数的内部我怀疑我可以做更多]:

public sealed class Plugin
{
    private static readonly Plugin _instance;
    private static int _initializing;
    private static ManualReserEvent _done;
    private Dictionary<int, string> myMap;

    private Plugin()
    {
        myMap = MapInit(GetMainModuleName());
    }

    static Plugin()
    {
        _done = new ManualResetEvent(false);
    }

    public static Plugin Instance
    {
        get
        {
            if (Interlocked.CompareExchance(ref _initializing, 1, 0) == 0)
            {
                _instance = new Plugin();
                _done.Set();
            }
            else
            {
                _done.WaitOne();
            }
            return _instance;
        }
    }
}

即使......只使用静态构造函数。

答案 1 :(得分:0)

好的,这是可能听起来很幼稚的实际问题。带有上述代码的dll被加载到主应用程序中,该应用程序的exe.config无效。由于我的dll分离了dll.config(这是有效的),应用程序在运行调试器时工作正常,但是当在部署环境中运行(没有附带调试器)时,它遇到了无效的配置文件异常。

我已将主exe.config作为有效的配置文件,现在可以正常工作。

所以基本上,解决方案与检查构造过程中是否存在异常一样天真。