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

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

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

就像这样:

public static class Base
{
}


public static class Inherited : Base
{
}

不会起作用。

为什么语言的设计者们拒绝了这种可能性呢?

168909 次浏览

嗯…如果你只是用静态方法填充非静态类,情况会有很大不同吗?

引用自在这里:

这实际上是经过设计的。似乎没有很好的理由来继承静态类。它具有公共静态成员,您总是可以通过类名本身访问这些成员。我所见过的继承静态内容的唯一原因都是不好的原因,比如保存几个输入字符。

可能有理由考虑将静态成员直接引入作用域的机制(实际上,我们将在Orcas产品周期之后考虑这一点),但静态类继承不是正确的方法:使用这种机制是错误的,而且只适用于恰好驻留在静态类中的静态成员。

(Mads Torgersen, c# Language PM)

来自9频道的其他观点

静态方法是在类型级别上定义的,而不是在实例级别上。这就是为什么覆盖不工作与静态方法/属性/事件…

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

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

(littleguru)

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

可以这样考虑:你通过类型名访问静态成员,就像这样:

MyStaticType.MyStaticMember();

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

MyNewType.MyStaticMember();

因此,在代码中使用时,新项与原始项没有关系。没有办法利用任何继承关系来处理多态性之类的事情。

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

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

也许你希望能够在运行时将静态Type传递给函数,并调用该类型的方法,而不知道该方法具体做什么。在这种情况下,您可以使用接口。

所以,最终你不会从继承静态类中获得任何东西。

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

所以这个:

static class Foo { }

编译到这个IL:

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

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

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

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

你可以用合成代替…这将允许您从静态类型访问类对象。但仍然不能实现接口或抽象类

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

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

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

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

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

  2. 它们不能被实例化。

  3. 它们是密封的。

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

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

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

静态类是密封的,因此不能被继承。它们不能从Object以外的任何类继承。静态类不能包含实例构造函数;但是,它们可以有一个静态构造函数。要了解更多信息,请参阅静态构造函数(c#编程指南)。

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

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

诀窍是这样的:

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 >注: 我们使用它来创建已编译的查询,这实际上可以用ConcurrentDictionary代替,但是一个具有线程安全性的静态只读字段就足够了

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

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>();

不幸的是,由于“by-design”语言限制,我们无法做到:

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


public static class TestClass : TestClass<double>
{
}

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

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

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

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

我不是编译器的作者,所以我不确定我是否忽略了在编译器中实现这些限制的困难。也就是说,我坚信有限的静态继承需要信息空间,基本的答案是你不能因为糟糕的(或过于简单的)设计选择。

我在尝试针对第三方库编写IComparer<T>实现时遇到了这个问题,其中T是嵌入在类中的枚举,如下所示:

public class TheClass
{
public enum EnumOfInterest
{
}
}

但是因为枚举是在第三方库类中定义的,所以我不能编写比较器,因为下面给出了一个“不能扩展列表”。错误:

public class MyComparer : IComparer<TheClass.EnumOfInterest>
{
}

我甚至没有扩展静态类——我只是实现类中定义的枚举的比较器。