.Net懒惰初始化Singleton属性

时间:2012-02-15 16:47:57

标签: c# c#-4.0 singleton lazy-loading lazy-initialization

我有一个类DataAdapter,它被我的站点实例化为单例对象。那个类有一个Person属性,我想成为一个懒惰的单例,完成这个的代码:

private readonly object _personLock = new object();
private volatile IPersonManager _person;
public IPersonManager Person
{
    get
    {
        if (_person == null)
        {
            lock (_personLock)
            {
                if (_person == null)
                {
                    _person = new PersonManager(_adUserName, _adPassword, client);
                }
            }
        }
        return _person;
    }
}

(PersonManager构造函数的三个参数是当前对象的属性/字段。) 此代码完美运行(它是double-lock check模式)。

但是,这是很多代码,我想在.Net 4.0中使用新的Lazy<> type来简化它。所以我将代码更改为:

    private static readonly Lazy<IPersonManager> _person = new Lazy<IPersonManager>(() => new PersonManager(_adUserName, _adPassword, client));
    public static IPersonManager Person { get { return _person.Value; } }

但这不起作用,因为这三个参数不是静态的(它们是当前方法的实例对象)。 write ups I've found无法解决此问题。我需要一些方法将这些值传递给lambda表达式?懒惰&lt;&gt;看起来它看起来像是一个空的签名。

2 个答案:

答案 0 :(得分:3)

那么,如果Lazy使用Instance属性来处理你的单例实例来为自己提供属性呢?这些字段仍然可以是私有的,因为我们正在从类内部(聪明)处理它们,并且在执行期间第一次引用Singleton.Instance之前,整个事情仍然是懒惰的。但是,在代码尝试获取Person属性之前,私有字段必须具有正确的值。如果在Singleton实例化时他们急切地加载,那很好。

借用C# In Depth,这里是一个准懒惰的Singleton,其中一个完全懒惰的Person成员使用引用Singleton的lambda初始化。

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static Singleton()
    {
    }

    private Singleton()
    {
       //I HIGHLY recommend you initialize _adusername, 
       //_adpassword and client here.
    }

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

    private static readonly Lazy<IPersonManager> _person = 
       new Lazy<IPersonManager>(() => new PersonManager(Instance._adUserName, Instance._adPassword, Instance.client));
    public static IPersonManager Person { get { return _person.Value; } }

    private object _adUserName;
    private object _adPassword;
    private object client;
}

public class PersonManager:IPersonManager {}

public interface IPersonManager{}

编辑:如果您有IoC,请使用IoC。您目前正在尝试混合模式;您正在使用IoC使用运行时规则将实例类“提升”为单例,然后尝试基于此faux-singleton的实例范围数据字段实例化编译器强制的惰性静态属性。这根本不起作用

一旦你进入IoC,就应该注册并注入依赖项。使用Ninject将PersonManager注册为IPersonManager实现,然后为主单例DataAdapter创建一个构造函数,该构造函数可以给出一个生成IPersonManager的Func。您通常可以为此目的定义自定义函数,在您的情况下,它将利用IoC从容器中保存的单个DataAdapter实例提供所需的实例数据。

警告:这些数据字段现在必须是公开可读的,以避免一些严重丑陋的反映;您可以将字段定义为只读字段或仅限get属性,以防止人们篡改它们,但您的消费者将能够看到它们。

编辑2:以下是我的想法:

//in your Ninject bindings:
kernel.Bind<DataAdapter>().ToSelf().InSingletonScope();
kernel.Bind<PersonManager>().ToSelf().InSingletonScope();
//to bind the interface
kernel.Bind<IPersonManager>()
   .ToMethod(c =>{ 
      var adapter = kernel.Get<DataAdapter>();
      //this is why these fields would have to be public
      var arg1 = new ConstructorArgument("adUserName", adapter._adUserName)
      var arg2 = new ConstructorArgument("adPassword", adapter._adPassword)
      var arg3 = new ConstructorArgument("client", adapter.client)
      //the names of the arguments must match PersonManager's constructor
      c.Kernel.Get<PersonManager>(arg1, arg2, arg3);
   });

//now in your DataAdapter, specify a constructor like this, and Ninject will provide:

public DataAdapter(Func<IPersonManager> personFunc)
{
   //_person should obviously not be instantiated where it's defined in this case
   _person = new Lazy<IPersonManager>(personFunc);
}

答案 1 :(得分:0)

  

(PersonManager构造函数的三个参数是   当前对象上的属性/字段。)

构造函数用于初始化对象。您尝试传递的参数此时没有赋值给它们。如果您的对象需要正确初始化这些值,那么在初始化时需要传入它们。

虽然您可以将属性转换为方法并将这些值传递给静态GetInstance方法,但只会在第一次调用GetInstance时设置此属性。这可能不是一个好主意,但可以做到。我会将您的Person属性转换为Method并接受这些参数并使用它们来初始化构造函数。这意味着你不会使用Lazy<T>,你的代码行会增加,但你会有更多可预测的行为。