【问题标题】:How to ensure that my class Initialize method is called only once, what would be the best approach?如何确保我的类 Initialize 方法只被调用一次,最好的方法是什么?
【发布时间】:2011-10-27 13:22:13
【问题描述】:

我目前正在使用 Unity IoC Container,这是我的 AppConfig 类。如您所见,Initialize 方法应该只被调用一次,并且我使用了双重锁检查来确保这一点。

如果我的方法不是最好的方法,那么实现这一目标的最佳方法是什么?

public interface IAppConfig
{
    /// <summary>
    /// Gets the admin username.
    /// </summary>
    /// <value>The admin username.</value>
    string AdminUsername { get; }

    /// <summary>
    /// Gets the admin password.
    /// </summary>
    /// <value>The admin password.</value>
    string AdminPassword { get; }

    /// <summary>
    /// Initializes this instance.
    /// </summary>
    void Initialize();
}

/// <summary>
/// A singleton App config which helps reading from web.config
/// its lifetime is controlled by Unity.
/// </summary>
public class AppConfig : IAppConfig
{
    #region Fields

    /// <summary>
    /// the injectable config manager
    /// </summary>
    private readonly IConfigManager _configManager;

    private readonly ILogger _logger;

    private static readonly object LockObject = new object();

    private static bool _initialized = false;

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="AppConfig"/> class.
    /// </summary>
    public AppConfig(IConfigManager configManager, ILogger logger)
    {
        this._configManager = configManager;
        this._logger = logger;
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets the admin username.
    /// </summary>
    /// <value>The admin username.</value>
    public string AdminUsername { get; private set; }

    /// <summary>
    /// Gets the admin password.
    /// </summary>
    /// <value>The admin password.</value>
    public string AdminPassword { get; private set; }

    #endregion

    #region Methods

    public void Initialize()
    {
        if (_initialized)
        {
            throw new ApplicationException("Initialize method should be called only once");
        }

        lock(LockObject)
        {
            if (_initialized) return;

            var adminUserNameSetting = _configManager.AppSettings[ConfigKeys.AdminUsername];

            if (adminUserNameSetting == null)
            {
                throw new ApplicationException("AdminUsername key not found");
            }

            this.AdminUsername = adminUserNameSetting.Value;

            if (String.IsNullOrWhiteSpace(this.AdminUsername))
            {
                _logger.LogError("AdminUsername not found");
            }

            // log
            var adminPasswordSetting = _configManager.AppSettings[ConfigKeys.AdminPassword];

            if (adminPasswordSetting == null)
            {
                throw new ApplicationException("AdminPassword key not found");
            }

            this.AdminPassword = adminPasswordSetting.Value;

            if (String.IsNullOrWhiteSpace(this.AdminPassword))
            {
                _logger.LogError("AdminPassword not found");
            }
            _initialized = true;
        }
    }

    #endregion
}

在 Unity 中,我使用以下代码:

// IAppConfig
container.RegisterType<IAppConfig, AppConfig>(new ContainerControlledLifetimeManager(),
                                              new InjectionConstructor(configManager,
                                                                       logger));
var appConfig = container.Resolve<IAppConfig>();
appConfig.Initialize();

【问题讨论】:

  • 为什么不调用一次。这个超级顶级的防御性编程是怎么回事。
  • 我会调用一次,但我想确保它被调用一次而不是错误调用两次!
  • 对源代码进行静态分析是一个更好的解决方案,而不是使用锁定机制将源代码破解。
  • TL;DR,从构造函数调用它?
  • 从构造函数调用它的问题是它变得不太可单元测试。

标签: c# asp.net design-patterns singleton unity-container


【解决方案1】:

我认为Initalize() 方法更像是一个实现问题。这意味着它可能根本不应该出现在界面中。

初始化实例最好留给构造函数。

如果您确实需要延迟初始化,那么您使用 bool 和锁的解决方案似乎没问题。

【讨论】:

  • 谢谢。好吧,我通常会保持我的构造函数干净并主要使用它来设置私有字段并在必须计算、验证或初始化其他属性时使用延迟初始化。急切初始化与延迟初始化。
  • 如果其他属性必须在稍后进行计算、验证或初始化,为什么不对这些属性实现延迟加载,以便类负责这项工作,不是调用代码?这就是封装的一半。
  • 因为所有这些属性都应该一起初始化,所以延迟加载不起作用。最初我在构造函数中使用了急切的初始化,但我认为由于上述原因我会延迟初始化。
【解决方案2】:

根据您在 Initialize 方法中所做的判断,我认为您需要研究的是将该类注册为单例并持久化容器。您可以在此处查看执行此操作的示例:

http://gunnarpeipman.com/2008/04/unity-and-singletons/

【讨论】:

  • 我已经将该类注册为单例;您是否正确阅读了我的代码?
  • 单例也是我的直觉,是的,他确实正确阅读了您的代码,因为您的 AppConfig 类不遵循 C# 的单例模式,如公共构造函数所示。
  • 就像我说的,它已经是单例了!它的生命周期由 Unity 控制。
【解决方案3】:

好的,所以您依靠 Unity 来确保您的课程是单例的。尽管 C# 的代码模式非常简单。见here。然后在构造函数中调用初始化代码。

在任何情况下,我都会将您的初始化标志声明为 volatile,因为代码是 atmo。

【讨论】:

  • 没有使用 volatile 的原因是因为我使用的是双重检查锁,所以不需要它。 volatile 确保一个线程检索另一个线程写入的最新值,并且代码确保它没有 volatile。
【解决方案4】:

我更喜欢有一个静态类实例变量来检查它是否已在 get 访问器中初始化。通过实例属性访问该类,您将控制该类的初始化次数。这几乎是默认的 C# 单例模式:

public static class MySingleton 
{
    private static Mutex instanceLock = new Mutex();

    private static MySingleton instance;
    public static MySingleton Instance
    {
        get
        {
            instanceLock.WaitOne();
            if(instance == null)
            {
                instance = new MySingleton();
            }
            instanceLock.ReleaseMutex();
            return instance;
         }
    }

    private MySingleton()
    {
        Initialize();
    }

    private void Initialize()
    {
        // Initialize
    }
}

public class MyOtherClass
{
    private MySingleton singleton = MySingleton.Instance;
}

【讨论】:

  • 这不是默认的 .net 模式。您可以在“private static MySingleton = new MySingleton()”中新建实例,而不需要任何代码。 csharpindepth.com/articles/Singleton
猜你喜欢
  • 2012-04-04
  • 2012-11-01
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多