为什么我不能继承静态类?

248

我有几个不需要任何状态的类。从组织的角度来看,我想把它们放进一个层次结构中。

但似乎我不能为静态类声明继承关系。

就像这样:

public static class Base
{
}

public static class Inherited : Base
{
}

这是行不通的。

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


可能是重复的问题:如何使静态类派生自对象? - Moslem Ben Dhaou
13个回答

190

来自这里的引文:

这实际上是设计如此。似乎没有理由继承静态类。您始终可以通过类名称本身访问其公共静态成员。我看到的唯一继承静态内容的原因是糟糕的,例如节省输入的几个字符。

有理由考虑直接将静态成员带入作用域的机制(事实上,在 Orcas 产品周期之后,我们将考虑此问题),但是静态类继承不是正确的方法:它是使用的错误机制,并且仅适用于恰好驻留在静态类中的静态成员。

(Mads Torgersen,C# 语言 PM)

来自 channel9 的其他意见

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

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

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

(littleguru)

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


111
托格森那边缺乏想象力,其实我有一个相当不错的理由想这么做 :) - Thorarin
14
这样怎么样?你有一个非开源的dll(动态链接库),或者说你没有访问它的源代码,比如 NUnit。你想扩展它的Assert类,添加一个像Throws<>(Action a)这样的方法(是的,这个方法已经存在了,但在某个时候可能不存在)。你可以在项目中添加一个 Assert:NUnit.Framework.Assert 类,并添加一个Throws方法。问题解决了。在代码中使用起来也更加简洁... 不用再想出另一个类名并记住那个类名,只需输入Assert.就能看到可用的方法了。 - user420667
4
无法为静态类编写扩展方法。https://dev59.com/u3VC5IYBdhLWcg3wliGe - Martin Braun
2
@modiX 当然。你在这里误解了我的意思。我的意思是,用户420667所描述的场景所需的功能可能仅需要允许为静态类提供扩展方法(将来)。不需要静态继承。这就是为什么他的情况没有让我觉得引入静态继承是一个很好的理由。如果C#在这方面进行修改,为什么要进行重大的重新设计,而只进行轻微的修改在实践中同样好呢? - Konrad Morawski
6
@Learner,这并不是很公平。在 .Net 中,所有的类都继承自 object,每个人都应该知道这一点。因此,无论你是否显式指定,静态类 始终 继承自 object - RenniePet
显示剩余4条评论

77
你不能继承静态类的主要原因是它们是抽象和封闭的(这也防止创建它们的任何实例)。
所以,这个问题的解释是:
static class Foo { }

编译成这个IL:

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

24
您说静态被实现为抽象+密封。他想知道为什么要这样做。为什么要密封? - Lucas
27
这并没有回答问题,它只是重申了他所问的问题。 - Igby Largeman
29
其实,该用户应该问“为什么静态类是密封的”,而不是“为什么我不能从静态类继承”,后者的答案当然是“因为它们是密封的”。这个回答得到了我的支持。 - Alex Budovski
6
谢谢分享静态类会自动编译为密封类的信息 - 我一直在想这是如何/何时发生的! - Mark
34
这个回答是正确的,但就像告诉一个迷路的人“你在我面前”一样无用,当他们问你“我在哪里”的时候。 - DeepSpace101
显示剩余4条评论

25

从这个角度来看:您可以通过类型名称访问静态成员,如下所示:

MyStaticType.MyStaticMember();

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

MyNewType.MyStaticMember();
因此,当在代码中使用新项目时,它与原始项目没有任何关系。这将无法利用继承关系,例如多态性。
也许您在考虑只想扩展原类中的某些项目。在这种情况下,没有任何阻止您在全新类型中使用原始成员。
也许您想为现有的静态类型添加方法。您已经可以通过扩展方法实现。
也许您希望能够在运行时传递静态的 Type 给函数,并调用该类型的方法,而不知道方法确切的操作。在这种情况下,您可以使用接口。
因此,从继承静态类中并没有真正获得任何好处。

5
通过扩展方法,您无法向现有的静态类型添加方法。请参见我在已接受答案上的评论,以获取所需用例示例。(基本上,您只需要将您已经命名为MyNewType的内容重命名为MyStaticType,因此MyStaticType:OldProject.MyStaticType) - user420667
2
可以使用静态类型和虚拟静态成员定义继承关系,以便SomeClass<T> where T:Foo可以访问Foo的成员,并且如果T是覆盖了Foo的一些虚拟静态成员的类,则泛型类将使用这些覆盖。甚至可以定义一种约定,使语言可以以与当前CLR兼容的方式执行此操作(例如,具有这些成员的类应定义一个包含这样的实例成员以及保护非静态类的静态字段,其中包含该类型的实例)。 - supercat
我发现自己遇到了这样一种情况,我相信继承一个静态类是正确的选择,尽管你可以通过其他方式访问它们。我有一个用于向打印机发送原始数据流(用于与工业标签打印机通信)的静态类。它有一堆Windows API声明。现在我发现自己正在编写另一个用于操作打印机的类,需要相同的API调用。合并?不好。声明两次?丑陋。继承?好,但不允许。 - Loren Pechtel

3

您可以使用组合代替...这样可以让您从静态类型中访问类对象。但仍然无法实现接口或抽象类。


3
嗯...如果您只有充满静态方法的非静态类,那么会有很大的区别吗?

3
这就是留给我的。我这样做。但我不喜欢它。 - User
我也曾经这样做过,但出于某种原因,当你知道你永远不会实例化对象时,它并不感觉美观。尽管没有任何实际成本,但我总是为这样的细节而苦恼。 - gdbj
在一个非静态类中充满了静态方法,你不能放置扩展方法 :) - Jakub Szułakiewicz

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

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

2

一种解决方法是不使用静态类,而是隐藏构造函数,这样类的静态成员是类外唯一可访问的内容。结果就是一个可继承的“静态”类:

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>
{
}

2
虽然你可以通过继承的类名称访问“继承”的静态成员,但静态成员并没有真正被继承。这在一定程度上解释了为什么它们不能是虚拟或抽象的,也不能被重写。在你的例子中,如果你声明了一个Base.Method(),编译器将会将对Inherited.Method()的调用映射回Base.Method()。因此,最好直接调用Base.Method()。你可以编写一个小测试来使用Reflector查看结果。
那么...如果不能继承静态成员,而静态类只能包含静态成员,那么继承静态类有什么好处呢?

1
你可以做一些看起来像静态继承的事情。
这里的诀窍是:
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 关键字。但这种方法有助于支持与静态继承非常相似的功能。

P.S. 我们用它来创建编译查询,但实际上可以用 ConcurrentDictionary 替换它,但静态只读字段及其线程安全已足够好。


1
我的回答是:糟糕的设计选择。;-)
这是一个关注语法影响的有趣辩论。在我看来,争论的核心是一种设计决策导致了密封静态类。关注静态类名称在顶层出现的透明度,而不是隐藏(“令人困惑”)在子名称后面?可以想象一种语言实现可以直接访问基类或子类,令人困惑。
一个伪例子,假设某种方式定义了静态继承。
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<ChildPayload>.SomeBaseField。但是,由于必须指定通用类型,因此您会受到阻碍。而子类引用则更清晰: MyStaticChild.SomeBaseField

我不是编译器的作者,所以我不确定是否遗漏了实现这些限制的困难。尽管如此,我坚信有一个信息空间需要有限的静态继承,而基本答案是由于设计选择不佳(或过于简单)而无法实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接