【问题标题】:Why can't I inherit static classes?为什么我不能继承静态类?
【发布时间】:2010-10-20 22:12:11
【问题描述】:

我有几个类实际上并不需要任何状态。从组织的角度来看,我想把它们分成层次结构。

但我似乎无法为静态类声明继承。

类似的东西:

public static class Base
{
}

public static class Inherited : Base
{
}

不会工作。

为什么语言的设计者关闭了这种可能性?

【问题讨论】:

标签: c# inheritance static


【解决方案1】:

嗯……如果你只是有一个充满静态方法的非静态类……会不会有很大不同?

【讨论】:

  • 这就是留给我的。我这样做。而且我不喜欢它。
  • 我也是这样做的,但由于某种原因,当你知道你永远不会实例化对象时,它感觉不美观。我总是为这样的细节而苦恼,尽管它没有任何物质成本。
  • 在充满静态方法的非静态类中,您不能放置扩展方法:)
【解决方案2】:

来自here的引用:

这实际上是设计使然。似乎没有充分的理由继承静态类。它具有公共静态成员,您始终可以通过类名本身访问它们。 我看到的继承静态内容的唯一原因是不好的,例如节省了几个打字字符。

可能有理由考虑将静态成员直接带入作用域的机制(实际上我们将在 Orcas 产品周期之后考虑这一点),但静态类继承不是要走的路:这是使用错误的机制,并且仅适用于恰好位于静态类中的静态成员。

(Mads Torgersen,C# 语言 PM)

来自channel9的其他意见

.NET 中的继承仅适用于实例库。 静态方法是在类型级别而不是在实例级别定义的。这就是为什么覆盖不适用于静态方法/属性/事件...

静态方法只在内存中保存一次。没有为他们创建的虚拟表等。

如果你在 .NET 中调用一个实例方法,你总是给它当前实例。这被 .NET 运行时隐藏了,但它确实发生了。 每个实例方法都有一个指针(引用)作为第一个参数,指向运行该方法的对象。静态方法不会发生这种情况(因为它们是在类型级别定义的)。编译器应该如何决定选择调用的方法?

(littleguru)

作为一个有价值的想法,littleguru 对此问题有部分“解决方法”:Singleton 模式。

【讨论】:

  • 托格森方面确实缺乏想象力。我有一个很好的理由想要这样做:)
  • 这个怎么样?你有一个不是开源的 dll/你无权访问它的源代码,比如说 NUnit。您想扩展它的 Assert 类以具有类似 Throws(Action a) 的方法(是的,它也存在,但有时它不是。)您可以在项目中添加 Assert:NUnit.Framework.Assert类,然后添加一个 Throws 方法。问题解决了。在代码中使用它也更简洁......不必想出另一个类名并记住该类名,只需键入 Assert。并查看可用的方法。
  • @KonradMorawski 为静态类编写扩展方法是不可能的。 stackoverflow.com/questions/249222/…
  • @modiX 当然。你在这里误会了我。我的意思是,只要允许静态类的扩展方法,就可以(将来)提供 user420667 描述的场景中所需的功能。不需要静态继承。这就是为什么他的场景并没有让我觉得是引入静态继承的一个很好的理由。如果要在这方面对 C# 进行修改,为什么要进行重大的重新设计,而在实践中进行次要的设计也一样好。
  • @Learner 这不太公平。在 .Net 中,everything 继承自 object,每个人都应该知道这一点。所以静态类总是继承自object,无论你是否明确指定。
【解决方案3】:

这样想:你通过类型名访问静态成员,像这样:

MyStaticType.MyStaticMember();

如果你要从那个类继承,你必须通过新的类型名来访问它:

MyNewType.MyStaticMember();

因此,在代码中使用时,新项目与原始项目没有任何关系。对于像多态这样的事情,没有办法利用任何继承关系。

也许您认为您只是想扩展原始类中的一些项目。在这种情况下,没有什么可以阻止您在全新类型中使用原始成员。

也许您想将方法添加到现有的静态类型。您已经可以通过扩展方法做到这一点。

也许您希望能够在运行时将静态Type 传递给函数并调用该类型的方法,而无需确切知道该方法的作用。在这种情况下,您可以使用接口。

所以,最终你并没有真正从继承静态类中获得任何好处。

【讨论】:

  • 您不能通过扩展方法将方法添加到现有静态类型。请参阅我对已接受答案的评论以获取所需用途的示例。 (基本上,您只想将您命名为 MyNewType 的内容重命名为 MyStaticType,因此 MyStaticType: OldProject.MyStaticType)
  • 可以定义静态类型和虚拟静态成员的继承关系,这样SomeClass<T> where T:Foo 可以访问Foo 的成员,如果T 是一个覆盖某些虚拟静态的类Foo 的成员,泛型类将使用这些覆盖。甚至有可能定义一个约定,通过该约定,语言可以以与当前 CLR 兼容的方式这样做(例如,具有此类成员的类应定义一个包含此类实例成员的受保护非静态类以及静态字段持有该类型的实例)。
  • 我发现自己有一个案例,我认为继承静态类是正确的做法,尽管显然您仍然可以访问它们。我有一个静态类,用于将原始数据流发送到打印机(用于与工业标签打印机通信)。它有一堆 Windows API 声明。我现在发现自己正在编写另一个类来处理需要相同 API 调用的打印机。结合?不好。声明两次?丑陋的。继承?好,但不允许。
【解决方案4】:

你不能继承静态类的主要原因是它们是抽象的和密封的(这也阻止了它们的任何实例被创建)。

所以这个:

static class Foo { }

编译成这个 IL:

.class private abstract auto ansi sealed beforefieldinit Foo
  extends [mscorlib]System.Object
 {
 }

【讨论】:

  • 您是说静态实现为抽象+密封。他想知道为什么这样做。为什么要密封?
  • 这并不能完全回答问题;它只是重申了他所问的问题。
  • 好吧,OP 应该问“为什么要密封静态类”,而不是“为什么我不能从静态类继承?”,答案当然是 “因为他们'被密封了'。这个答案得到了我的投票。
  • 感谢分享静态类会自动编译为密封类 - 我想知道这是如何/何时发生的!
  • @AlexBudovski:这个答案是正确的,但就像在一个迷路的人问你“我在哪里”时告诉他们“你在我面前”一样无用。
【解决方案5】:

您想通过使用类层次结构实现的目标可以仅通过命名空间来实现。因此,支持命名空间的语言(如 C#)将无法实现静态类的类层次结构。由于您不能实例化任何类,您所需要的只是类定义的分层组织,您可以通过使用命名空间获得它

【讨论】:

  • 我有一个通用加载器,它接收一个类作为类型。该类有 5 个辅助方法和 2 个返回字符串(名称和描述)的方法。我可能选错了(我想是这样),但我找到的唯一解决方案是在通用加载器中实例化类,以访问方法......嗯。
  • 我猜另一种选择是巨大的字典。此外,当您说“您想要实现的目标”时,您应该说“您的目标是什么”或类似的东西。
【解决方案6】:

虽然您可以通过继承的类名访问“继承的”静态成员,但静态成员并不是真正继承的。这就是为什么它们不能是虚拟的或抽象的,也不能被覆盖的部分原因。在您的示例中,如果您声明了 Base.Method(),则编译器将对 Inherited.Method() 的调用映射回 Base.Method()。您也可以显式调用 Base.Method() 。你可以写一个小测试,用 Reflector 查看结果。

那么...如果你不能继承静态成员,如果静态类可以只包含个静态成员,那么继承一个静态类有什么好处呢?

【讨论】:

    【解决方案7】:

    您可以改用组合...这将允许您从静态类型访问类对象。但是还是不能实现接口或者抽象类

    【讨论】:

      【解决方案8】:

      静态类和类成员用于创建无需创建类实例即可访问的数据和函数。静态类成员可用于分隔独立于任何对象身份的数据和行为:无论对象发生什么,数据和函数都不会改变。当类中没有依赖于对象标识的数据或行为时,可以使用静态类。

      一个类可以声明为静态的,这表明它只包含静态成员。不能使用 new 关键字来创建静态类的实例。当加载包含该类的程序或命名空间时,.NET Framework 公共语言运行时 (CLR) 会自动加载静态类。

      使用静态类来包含与特定对象无关的方法。例如,创建一组不作用于实例数据并且不与代码中的特定对象关联的方法是一个常见的要求。您可以使用静态类来保存这些方法。

      以下是静态类的主要特征:

      1. 它们只包含静态成员。

      2. 它们不能被实例化。

      3. 它们是密封的。

      4. 它们不能包含实例构造函数(C# 编程指南)。

      因此,创建静态类与创建仅包含静态成员和私有构造函数的类基本相同。私有构造函数防止类被实例化。

      使用静态类的优点是编译器可以检查以确保没有意外添加任何实例成员。编译器将保证不能创建此类的实例。

      静态类是密封的,因此不能被继承。它们不能继承自除 Object 之外的任何类。静态类不能包含实例构造函数;但是,它们可以有一个静态构造函数。有关详细信息,请参阅静态构造函数(C# 编程指南)。

      【讨论】:

        【解决方案9】:

        当我们创建一个只包含静态成员和私有构造函数的静态类时。唯一的原因是静态构造函数阻止了该类的实例化,因为我们无法继承静态类。唯一的方法是访问使用类名本身作为静态类的成员。尝试继承静态类不是一个好主意。

        【讨论】:

          【解决方案10】:

          你可以做一些看起来像静态继承的事情。

          这是诀窍:

          public abstract class StaticBase<TSuccessor>
              where TSuccessor : StaticBase<TSuccessor>, new()
          {
              protected static readonly TSuccessor Instance = new TSuccessor();
          }
          

          那么你可以这样做:

          public class Base : StaticBase<Base>
          {
              public Base()
              {
              }
          
              public void MethodA()
              {
              }
          }
          
          public class Inherited : Base
          {
              private Inherited()
              {
              }
          
              public new static void MethodA()
              {
                  Instance.MethodA();
              }
          }
          

          Inherited 类本身不是静态的,但我们不允许创建它。它实际上继承了构建Base 的静态构造函数,并且Base 的所有属性和方法都可以作为静态的。现在唯一要做的就是为您需要公开给静态上下文的每个方法和属性制作静态包装器。

          有一些缺点,比如需要手动创建静态包装方法和new 关键字。但这种方法有助于支持与静态继承非常相似的东西。

          附: 我们用它来创建编译查询,它实际上可以用 ConcurrentDictionary 代替,但是具有线程安全性的静态只读字段就足够了。

          【讨论】:

            【解决方案11】:

            您可以做的解决方法是不使用静态类,而是隐藏构造函数,以便类静态成员是类外唯一可访问的东西。结果本质上是一个可继承的“静态”类:

            public class TestClass<T>
            {
                protected TestClass()
                { }
            
                public static T Add(T x, T y)
                {
                    return (dynamic)x + (dynamic)y;
                }
            }
            
            public class TestClass : TestClass<double>
            {
                // Inherited classes will also need to have protected constructors to prevent people from creating instances of them.
                protected TestClass()
                { }
            }
            
            TestClass.Add(3.0, 4.0)
            TestClass<int>.Add(3, 4)
            
            // Creating a class instance is not allowed because the constructors are inaccessible.
            // new TestClass();
            // new TestClass<int>();
            

            很遗憾,由于“设计”的语言限制,我们无法做到:

            public static class TestClass<T>
            {
                public static T Add(T x, T y)
                {
                    return (dynamic)x + (dynamic)y;
                }
            }
            
            public static class TestClass : TestClass<double>
            {
            }
            

            【讨论】:

              【解决方案12】:

              我的回答:糟糕的设计选择。 ;-)

              这是一场专注于语法影响的有趣辩论。在我看来,这个论点的核心是设计决策导致了密封的静态类。关注静态类名称的透明度出现在顶层而不是隐藏(“混淆”)在子名称后面?人们可以想象一种可以直接访问基类或子类的语言实现,令人困惑。

              一个伪示例,假设静态继承以某种方式定义。

              public static class MyStaticBase
              {
                  SomeType AttributeBase;
              }
              
              public static class MyStaticChild : MyStaticBase
              {
                  SomeType AttributeChild;
              }
              

              会导致:

               // ...
               DoSomethingTo(MyStaticBase.AttributeBase);
              // ...
              

              可能(会?)影响与

              相同的存储
              // ...
              DoSomethingTo(MyStaticChild.AttributeBase);
              // ...
              

              非常混乱!

              但是等等!编译器将如何处理在两者中定义相同签名的 MyStaticBase 和 MyStaticChild?如果孩子覆盖比我上面的例子不会改变相同的存储,也许?这会导致更多的混乱。

              我相信有限的静态继承有很强的信息空间理由。很快就会有更多关于限制的信息。这个伪代码显示了值:

              public static class MyStaticBase<T>
              {
                 public static T Payload;
                 public static void Load(StorageSpecs);
                 public static void Save(StorageSpecs);
                 public static SomeType AttributeBase
                 public static SomeType MethodBase(){/*...*/};
              }
              

              然后你得到:

              public static class MyStaticChild : MyStaticBase<MyChildPlayloadType>
              {
                 public static SomeType AttributeChild;
                 public static SomeType SomeChildMethod(){/*...*/};
                 // No need to create the PlayLoad, Load(), and Save().
                 // You, 'should' be prevented from creating them, more on this in a sec...
              } 
              

              用法如下:

              // ...
              MyStaticChild.Load(FileNamePath);
              MyStaticChild.Save(FileNamePath);
              doSomeThing(MyStaticChild.Payload.Attribute);
              doSomething(MyStaticChild.AttributeBase);
              doSomeThing(MyStaticChild.AttributeChild);
              // ...
              

              创建静态子项的人无需考虑序列化过程,只要他们了解可能对平台或环境的序列化引擎施加的任何限制。

              静态(单例和其他形式的“全局”)经常出现在配置存储周围。静态继承将允许在语法中清晰地表示这种责任分配,以匹配配置的层次结构。不过,正如我所展示的,如果实现了基本的静态继承概念,则很有可能会出现大量歧义。

              我相信正确的设计选择是允许具有特定限制的静态继承:

              1. 没有覆盖任何东西。孩子不能更换底座 属性,字段或方法,...重载应该没问题,因为 只要签名存在差异,编译器就可以 整理出孩子与基地。
              2. 只允许通用静态基,不能从 非泛型静态基础。

              您仍然可以通过通用引用 MyStaticBase&lt;ChildPayload&gt;.SomeBaseField 更改同一个商店。但是您会感到气馁,因为必须指定泛型类型。虽然子引用会更清晰:MyStaticChild.SomeBaseField

              我不是编译器编写者,所以我不确定我是否遗漏了一些关于在编译器中实现这些限制的困难。也就是说,我坚信信息空间需要有限的静态继承,而基本答案是因为设计选择不佳(或过于简单)而无法实现。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2015-01-19
                • 1970-01-01
                • 1970-01-01
                • 2010-09-28
                • 1970-01-01
                • 2023-03-29
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多