具有依赖注入的单例类 C#

kle*_*ius 5 singleton dependency-injection unity-container

我们有一个带有 QCServiceLog 类的外部项目,该类具有由 Unity 解析的 ILogging 依赖项。\n但 QCServiceLog 是一个 Singleton 类,如以下示例所示:

\n
private readonly ILogging _logging = null;\n\nprivate static QCServiceLog _instance = null;\npublic static QCServiceLog Instance\n{\n    get\n    {\n        return _instance;\n    }\n}\n\npublic QCServiceLog(ILogging logging)\n{\n    _logging = logging;\n    if (_instance == null)\n    {\n        _instance = this;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我们正在尝试使用它,在我们的解决方案中,我们进行了如下注册:

\n
uc.RegisterType<ILogging, QCFileManager>(new ContainerControlledLifetimeManager());\n
Run Code Online (Sandbox Code Playgroud)\n

但由于 QCServiceLog 是单例,我们相信代码永远不会通过构造函数,因此 _instance 永远不会实例化。\n我们使用它来执行以下操作:

\n
QCServiceLog.Instance.Log(ex);\n
Run Code Online (Sandbox Code Playgroud)\n

Singleton 是否正确实现?我们相信它\xc2\xb4s永远不会做的QCServiceLog。

\n

你怎么认为?我们可以在不更改外部项目的情况下做些什么吗?\n你可以想象的例外是:

\n

你调用的对象是空的。

\n

我将衷心感谢您的帮助!

\n

Dav*_*ack 5

您提供的代码不是线程安全的 - 请查看此处Implementing the Singleton Pattern in C#

只要始终遵循以下规则,线程安全问题就不应该成为问题:

  1. QCServiceLog始终注册为单例(您似乎正在这样做)。
  2. 没有代码可以手动创建 的实例QCServiceLog。由于您的构造函数是公共的,因此这是可能的。

作为正确实现模式的问题,请将下面的代码视为解决线程安全问题的几种可能的替代方案之一。一般来说,有一些首选的替代方案(请参阅我上面提到的链接),但考虑到您使用 DI 的情况,我建议使用此方案。这至少可以保证您的单例_logging实例不会被手动实例创建覆盖:

private static readonly object _syncLock = new object();

public QCServiceLog(ILogging logging)
{
    if (_logging == null)
    {
        lock(_syncLock)
        {
            if (_logging == null)
            {
                _logging = logging;
            }
        }
    }

    if (_instance == null)
    {
        lock(_syncLock)
        {
            if (_instance == null)
            {
                _instance = this;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

另一个问题是构造函数没有隐藏。因此,即使您的 IoC 容器将类注册为 Singleton,仍然可以使用运算符在代码中的任何位置构造此类的实例new。我在我开发的一个应用程序中发现了由这件事引起的内存泄漏(目的是通过 DI/IoC 使用,它注册为单例,但通过代码手动更新。


Nig*_*888 2

Singleton 是否正确实现?

是的。但在获得它的静态实例之前,需要先从容器中实例化它。

换句话说,你不能使用这一行:

QCServiceLog.Instance.Log(ex);
Run Code Online (Sandbox Code Playgroud)

直到您第一次致电:

container.Resolve<QCServiceLog>();
Run Code Online (Sandbox Code Playgroud)

(假设您已经向 Unity 注册了一个实例ILogging),因为第一次调用不会命中构造函数来填充_instance值。

您最好直接将其注入需要的地方:

public class Foo(QCServiceLog log)
Run Code Online (Sandbox Code Playgroud)

或者创建一个包装类,其中包含在使用它之前正确连接它的逻辑,并通过 DI 容器注入。

无论哪种方式,您应该注意 DI 主要用于您的应用程序。3rd 方实用程序不一定以 DI 友好的方式执行操作,因此您可能需要使用外观适配器,以使它们在您的应用程序上下文中使用 DI 友好。