【问题标题】:Question about non-static members of a singleton (C#)关于单例的非静态成员的问题(C#)
【发布时间】:2010-11-08 11:12:33
【问题描述】:

我有一个关于单身人士的问题,我想我知道答案……但每次出现这个场景时,我都会稍微猜测一下自己,所以我想知道具体的答案。

假设我设置了两个类...

 public class ClassA
 {
     private static ClassA _classA;

     public static ClassA Instance { get { return _classA ?? LoadClassA(); } }

     private ClassA(){}

     public static ClassA LoadClassA()
     {
         _classA = new ClassA();
         return _classA;
     }

     private ClassB _classB = new ClassB();

     public ClassB ClassB { get { return _classB; } set { _classB = value; } }
 }


 public class ClassB
 {
 }

我的问题很简单。

我想知道如果我访问 ClassA 的单例, _classB 字段是否也被视为静态字段?即使我没有将 _classB 声明为静态成员。

我一直基本上只是猜测 _classB 它被视为静态(一个内存位置),但我想确定。我错了吗?每次从单例 ClassA 访问 _classB 时,是否都会为它创建一个新对象……即使内存中只有一个 ClassA?还是因为我在声明中更新了 _classB,导致它只有一个实例?

提前致谢, -马特

【问题讨论】:

  • 肯定会有一个 _classB 的实例。

标签: c# design-patterns static singleton


【解决方案1】:

令我惊讶的是,没有人指出ClassBClassA 的公共属性,具有公共设置器。因此,没有什么能阻止任何人写这样的东西:

ClassA.Instance.ClassB = new ClassB();

这将丢弃在创建ClassA 的单独实例时创建的ClassB 实例,并将其替换为新创建的ClassB 实例。

一个相关的场景是,只要_classB 在派生类中是可设置的(例如,如果ClassB 的设置器被保护),就不能保证只有一个@ 实例987654329@ever.

我想编写此类代码的正确方法是将ClassAClassB 实现为单例,并使ClassB 成为ClassA 的内部类(否则您将拥有2 个不相关的类@987654334 @ 和 ClassB 并通过 ClassA.Instance.ClassB 获得对 ClassB 的引用,这(在那种情况下)与 ClassB.Instance 没有什么不同。

如果ClassB 只能在ClassA 内实例化,那么在派生类中可能存在ClassB 可实例化的其他实例。

【讨论】:

    【解决方案2】:

    正如定义的 ClassA 违反了单例的定义。想象一下两个线程同时调用静态 Instance 属性。由于访问不同步,您可能会获得两个不同的 ClassA 实例,从而获得两个不同的 ClassB 实例。

    1. Thread1 调用 Instance 属性,由于 _classA 为 null,它进入 LoadClassA 方法
    2. Thread2 调用 Instance 属性,由于 _classA 仍为 null,它进入 LoadClassA 方法
    3. Thread1 和 Thread2 获得 ClassA 的两个不同实例

    【讨论】:

    • 我不确定它“违反定义”,因为在单线程应用程序中,它会很好。但是正如您所指出的,存在线程安全问题。
    【解决方案3】:

    _classB 是 ClassA 的实例(非静态)成员。 ClassA 的每个实例都将有一个 _classB 实例(给定您编写的字段初始值设定项)。因此,如果您使用单例模式访问 ClassA,因此总是(至多)加载一个 ClassA 实例,那么您将始终(至多)通过 ClassA 加载一个 ClassB 实例。

    现在,由于 ClassB 有一个公共的默认构造函数,其他遥远的东西可能会自行创建实例。如果这是一个问题,请考虑将 ClassB 类设为私有。此外,由于 ClassA 有一个公共的默认构造函数,没有什么能阻止人们创建他们想要的任意数量的实例。您可以将无参数构造函数设为私有:

    private ClassA() {}
    

    【讨论】:

      【解决方案4】:

      _classB 不是静态的; ClassA 的实例包含对 _classB 实例的引用;如果 ClassA 的实例是静态的(就像在单例实例中一样),那么它仍将包含对 _classB 的引用,但 ClassA 的新实例将包含对(可能)不同的 _classB 实例的不同引用。因此,如果您引用同一个 ClassA 实例(通过单例模式),您仍然会得到同一个 _classB 实例;但是如果你创建一个新的 ClassA 实例,你会得到一个不同的 _classB 实例,这就是为什么 _classB 不是静态的。

      【讨论】:

        【解决方案5】:

        因为 ClassA 是一个单例,所以在您的应用程序中只有一个实例。在该类实例中,有一个存储 ClassB 变量的成员。一旦初始化此变量,它就是一个实例变量,但由于您只有 1 个 ClassA 实例,因此您将始终获得相同的 ClassB 实例。

        另外,我建议不要使用您创建内部实例的方式,因为它不是线程安全的。

        private static ClassA _instance = new ClassA();
        
        private ClassA() {}
        
        public static ClassA Insance { get { return _instance; } }
        

        通过不延迟加载和立即初始化,您可以确保线程安全。

        【讨论】:

          【解决方案6】:

          如果您只需要一个实例,为什么不将 ClassB 标记为静态。

          在我看来,编写清晰的代码向您展示完整的意图总是更好。这样,下一个处理您的代码的人就不必玩猜谜游戏来确定您想要做什么。

          【讨论】:

          • 将一个类标记为静态使得无法实例化该类的任何实例。显然,ClassA 和 ClassB 的实例都是必需的。
          【解决方案7】:

          通过这样的声明:

          private ClassB _classB = new ClassB();
          

          每当调用 ClassA 的构造函数时,您都会将 _classB 实例化为 ClassB 的新实例。

          对于单例模式,调用 ClassA 的构造函数的唯一方法是使用静态方法(在您的情况下是通过 Instance 属性),这可以有效地保证只创建一个 ClassA。

          这确保 _classB 只会被更新一次,但它是非静态的。但是,如果将来有人将 ClassA 更改为不再是单例,那么您将开始创建 ClassB 的多个实例。如果 _classB 真的是静态的,那么情况就不是这样了。

          【讨论】:

            【解决方案8】:

            当您创建单例时,您正在创建非静态类型的单个静态实例。

            在这种情况下,您的类型(A 类)包含对另一种类型(B 类)的引用。静态实例将保存对 B 类对象的单个实例的单个引用。从技术上讲,它不是“静态的”,但由于它植根于一个静态对象(A 类实例),它的行为就像一个静态变量。您将始终拥有一个且只有一个 B 类对象(由您的 A 类实例指向)。您永远不会在 A 类中创建多个 B 类实例。

            但是,没有什么可以阻止在其他地方生成第二个 B 类实例 - 这将是一个不同的实例。

            【讨论】:

            • 除非我发现这里有问题,否则您还应该为ClassA 创建一个私有构造函数。如果你不这样做,就不会有任何东西阻止外部代码创建ClassA 的新实例,这将意味着ClassB 的更多实例(因为每个ClassA 将有独立的副本)。
            • 这也让我感到困惑,但我想可能只是因为它大约是凌晨 2 点 :)
            【解决方案9】:

            单例模式确保ClassB 的一个实例始终可以从ClassA 的单例实例访问。单例模式的全部意义在于它保证ClassA 的一个实例 在任何时候都可用,因此只有一个对_classB 的引用(尽管由于ClassA 是可变的,所以这个引用可以改变)。

            但请注意,ClassB范围 仍然是实例级别而非静态级别。编译器将从不 做任何奇怪的事情,以至于使用与您指示不同的范围说明符。无论您是否使用单例,您仍必须通过实例访问对 ClassB 的引用。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-11-04
              • 1970-01-01
              • 2012-07-26
              • 1970-01-01
              • 2011-09-30
              • 1970-01-01
              相关资源
              最近更新 更多