【问题标题】:How To: Auto Instantiate Singleton in C#如何:在 C# 中自动实例化单例
【发布时间】:2023-03-05 03:24:01
【问题描述】:

我想要一个在程序启动时自动实例化的单例。

我所说的“自动实例化”是指 Singleton 中的代码应该在程序启动时自行实例化,而不需要其他代码的任何调用或声明。

所以我想要类似下面的东西来实例化并在程序启动时写出“MySingleton Instantiated”(主代码不做任何事情)......

static class MySingleton
{
    private static MySingleton self = new MySingleton();

    protected MySingleton()
    {
        System.Console.WriteLine("MySingleton Instantiated");
    }
}

除了这不起作用,因为 C# 只会在需要时初始化类的静态成员,即当它们被访问/等时。

那我该怎么办?这个可以吗?

我没有亲自用 C++ 做过这件事(有一段时间没用过 C++),但我很确定它可以用 C++ 完成,但不确定 C#。

感谢任何帮助。谢谢。


我真正想做的是... 会有很多这样的单例类(随着时间的推移可以添加更多),所有这些都将继承自一个通用(抽象)父类(又名 PClass)。

PClass 将有一个静态成员,它是 PClass 的集合...和一个将自身添加到集合中的构造函数...

然后理论上所有的单例将自动添加到集合中(因为当它们被实例化时,基 PClass 构造函数被调用并将新对象添加到集合中)......然后可以在不了解任何信息的情况下使用集合已经实现了哪些子(单例)类,并且可以随时添加新的子(单例)类,而无需更改任何其他代码。

不幸的是,我无法让孩子(单身人士)实例化自己...搞砸了我的小计划,导致了这篇文章。

希望我解释得足够好。


附言。是的,我意识到对单例及其使用有不好的感觉......但它们有时很有用,即使撒旦自己制作了单例,我仍然想知道我的问题是否可以在 C# 中解决。谢谢大家。

【问题讨论】:

    标签: c# singleton


    【解决方案1】:

    Chris 提到的 IoC 方法可能是最好的,但我能想到的“最佳”解决方案是用反射和属性做一些时髦的事情,如下所示:

    public class InitOnLoad : Attribute 
    { 
        public static void Initialise()
        {
            // get a list of types which are marked with the InitOnLoad attribute
            var types = 
                from t in AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
                where t.GetCustomAttributes(typeof(InitOnLoad), false).Count() > 0
                select t;
    
            // process each type to force initialise it
            foreach (var type in types)
            {
                // try to find a static field which is of the same type as the declaring class
                var field = type.GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).Where(f => f.FieldType == type).FirstOrDefault();
                // evaluate the static field if found
                if (field != null) field.GetValue(null);
            }
        }
    }
    
    [InitOnLoad]
    public class Foo
    {
        public static Foo x = new Foo();
    
        private Foo()
        {
            Console.WriteLine("Foo is automatically initialised");
        }
    }
    
    public class Bar
    {
        public static Bar x = new Bar();
    
        private Bar()
        {
            Console.WriteLine("Bar is only initialised as required");
        }
    }
    

    将调用 InitOnLoad.Initialise() 添加到您的 main 方法中。

    您可以取消该属性,但这可能会导致不必要的类型被初始化并不必要地消耗内存(例如上面代码中的 Bar)。

    还值得注意的是,这不会处理任何动态加载的程序集中包含的类型,除非您再次调用 Initialise,但基于您的问题(“程序启动时自动实例化”),这听起来并不喜欢你的问题。

    【讨论】:

    • 在更新问题后,可以将属性替换为祖先类型检查。然后可以将 Initialise 代码移动到基类中的静态构造函数,只要在应用程序中引用了基类,它就会导致所有的祖先也被加载。
    • 我听说过 Reflection 但还没有使用它...想知道它是否可以用于这样的事情...我认为这听起来是最好的解决方案,您的关注up 评论基本上准确地描述了我想要做的事情。有了反射,我认为这是可以实现的。谢谢。
    【解决方案2】:

    虽然 .NET 模块在理论上 (IIRC) 可以对模块加载等做出反应,但这在 C# 中是不可用的。在某些框架(如 ASP.NET)中,您可以通过配置使用钩子,例如通过处理程序或通过 global.asax.cs 来破解它 - 但是,对于常规 C# 应用程序(控制台、winform 等),您必须手动触发它。例如,将调用承载 Main 入口点的类上的静态构造函数。

    那么:这里的用例是什么?什么时候延迟加载方法不行?

    【讨论】:

      【解决方案3】:

      根据您要执行的操作,我会放弃真正的 Singleton 的想法,而改用 IoC 库。

      查看 StructureMap、Castle Windsor、Ninject 和/或 Autofac。

      这将允许您通过 IoC 库将类创建为单例,拥有任意数量的类,但它只是一个普通的旧类。

      Singleton 存在一个问题,即它们确实会破坏应用程序的可测试性(通过单元测试)。

      只需在“Singleton Considered Harmful”上进行谷歌搜索,您就会看到更多参考资料。

      或者,您也可以使用简单的类工厂/方法工厂模式。

      【讨论】:

      • 我不熟悉 IoC 库......而且对于我的需求,它似乎比我想要的更复杂?目前作为一种解决方案,我基本上是在做一个工厂方法。但它没有那么好。
      • IoC 并没有那么复杂,真的。额外的好处是,由于自动依赖注入,IoC 还可以帮助您的应用程序的其余部分。即使你不使用它来解决这个问题,IoC 也值得研究。
      【解决方案4】:

      我怀疑如果不从Main 做任何事情,这是可能的。如果你不使用它,即使在类中添加static MySingleton() {} 也不能保证它的实例化。

      【讨论】:

        【解决方案5】:

        您基本上是在要求 .NET 框架在加载程序集时调用某个函数。该函数将是 PClass 实例注册器。

        C# 中没有DllMain。您可以通过在 main 方法中使用程序集级属性和一些代码来模拟这一点,该方法在程序集加载时进行侦听。该属性可以指定一个“DllMain”入口点或指向您的 PCIass 继承类。

        【讨论】:

          【解决方案6】:

          顺便说一句,请允许我指出,这并不是通常实现的单例模式。这是 C# 中的普通(简化)实现(为简洁起见,不包括线程安全之类的东西):

          public sealed class Singleton
          {
          static readonly Singleton _instance = new Singleton();
          
          // private ctor
          Singleton() {}
          
           public static Singleton Instance
           {
            get { return _instance; }
           }
          }
          

          您的代码中的这一行甚至不应该编译!

          protected MySingleton()
          

          说了这么多,我同意那个询问你用例的人的观点。很高兴知道。 =)

          【讨论】:

          • 是的,通常人们有一个像你演示的实例访问器,但我的问题是我不希望任何其他代码直接访问单例......我只希望它自己实例化...所以不需要访问器。我还编辑了我的问题以添加用例说明。
          • 顺便说一句,受保护的构造函数有什么问题。它编译得很好。
          • 当您在静态类上添加 ctor(任何访问级别)时,您应该会收到编译器错误(“静态类不能有实例构造函数”)。
          【解决方案7】:

          我不相信你想要的在 .Net 框架中是可能的。基本上,您希望诸如运行时之类的东西来通知类型或在它们上调用一些预定义的静态方法,表明应用程序已加载并正在运行,或者提供类似的机制。想想开销。运行时唯一确保的是自动调用程序的主入口方法。就是这样。在那里你可以设置和初始化它们,但它没有什么自动的。您仍在显式地连接事物并进行方法调用。

          【讨论】:

          • 我开始认为这也不可能......但它应该是!我很确定它可以在 C++ 中完成,虽然我实际上并没有这样做......而且很明显 C# 不是 C++,但是这种类型的事情不能在 C# 中完成,这很麻烦。
          • 我怀疑这在 C++ 中也是可能的。我记得正如 Mark Gravell 提到的,每次加载、卸载、附加线程等时,Windows 都会调用每个 dll 中的一些标准回调,但即便如此,您仍需要显式初始化您的单例。另外,在 .NET 中,您无权访问这些回调。一种可能性是拥有自己的引导程序来托管 .NET 运行时。然后你可以使用 CLR 托管 API 或一些技巧来做你想做的事,但为什么呢?我认为你应该重新考虑你的设计和这个要求的必要性。
          【解决方案8】:

          “PClass 将有一个静态成员,它是 PClass 的集合...”

          听起来像是可以通过反射轻松完成的事情。您不需要在启动时运行的代码;您可以在需要时构建这些类的列表。

          这是一个示例,当您第一次读取 Instances 属性时将加载列表一次,将查看当时加载的所有程序集(如果您只想查看与 PClass 相同的程序集,您可以稍微简化一下,但您的问题没有指定您要查看的程序集),并将任何 PClass 后代(但不是 PClass 本身)的单个实例添加到列表中。每个 PClass 后代都需要一个无参数构造函数,但您不需要任何类构造函数。

          public class PClass
          {
              private static List<PClass> m_instances;
              public static IList<PClass> Instances
              {
                  get
                  {
                      if (m_instances == null)
                          m_instances = LoadInstanceList();
                      return m_instances;
                  }
              }
              private static List<PClass> LoadInstanceList()
              {
                  foreach (var assembly in AppDomain.GetAssemblies())
                  {
                      foreach (var type in assembly.GetTypes())
                      {
                          if (type.IsAssignableTo(typeof(PClass)) && type != typeof(PClass))
                              m_instances.Add(Activator.CreateInstance(type));
                      }
                  }
              }
          }
          

          【讨论】:

          • 谢谢,Richard 先带着反射的东西进来了……所以把支票给了他。但无论如何感谢您的回答。你不能在这里做多项检查吗?
          • Activator.CreateInstance 需要(至少在默认情况下)一个默认的公共构造函数。给出的示例单例没有提供,这就是为什么我使用反射找到与声明类相同类型的字段,然后获取该字段的值。
          猜你喜欢
          • 2011-12-30
          • 1970-01-01
          • 1970-01-01
          • 2012-08-18
          • 1970-01-01
          • 1970-01-01
          • 2018-11-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多