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


Answers:


174

这里引文:

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

也许有理由考虑将静态成员直接带入范围的机制(实际上,我们将在Orcas产品周期之后考虑这一点),但是静态类继承并不是要走的路:这是使用错误的机制,并且有效仅针对碰巧驻留在静态类中的静态成员。

(Mads Torgersen,C#语言PM)

来自channel9的其他意见

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

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

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

(小小古鲁)

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


92
确实,Torgersen方面缺乏想象力。我有充分的理由要这样做:)
Thorarin 2010年

10
这个怎么样?您有一个不是开源的dll /您无权访问它的源代码,例如NUnit。您想扩展它的Assert类,使其具有Throws <>(Action a)之类的方法(是的,那里也存在,但有时不是。)您可以在项目中添加Assert:NUnit.Framework.Assert。类,然后添加一个Throws方法。问题解决了。也可以在代码中使用它更干净...无需输入另一个类名并记住该类名,只需键入Assert。并查看可用的方法。
user420667

4
@KonradMorawski不可能为静态类编写扩展方法。stackoverflow.com/questions/249222/...
马丁布朗

2
@modiX当然。你在这里误解了我。我的意思是,仅允许静态类的扩展方法就可以(将来)提供user420667描述的方案中所需的功能。无需静态继承。这就是为什么他的情况并不能使我成为引入静态继承的一个很好的理由。如果要在这方面对C#进行修改,那么在实践中,次要的也一样,为什么要进行重大的重新设计。
Konrad Morawski 2014年

5
@Learner这不是很公平。在.Net中,所有内容都继承自object,每个人都应该知道这一点。因此,无论您是否明确指定静态类,静态类都始终继承自object
RenniePet

71

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

所以这:

static class Foo { }

编译为此IL:

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

17
您是说static是作为抽象+密封实现的。他想知道为什么这样做。为什么要密封?
卢卡斯,

22
这并不能完全解决问题。这只是重申了他所问的问题。
Igby Largeman 2010年

27
好吧,OP应该问“为什么静态类是密封的”,而不是“为什么我不能从静态类继承?”,答案当然是“因为它们是密封的”。这个答案得到我的投票。
亚历克斯·布多夫斯基

6
感谢您分享静态类会自动编译为密封类的情况-我想知道这种情况是何时发生的!
标记

22
@AlexBudovski:这个答案是正确的,但是当他们问你“我在哪里”时,告诉失落的人“你在我面前”就没用了。
DeepSpace101 2014年

24

这样考虑:您可以通过类型名称访问静态成员,如下所示:

MyStaticType.MyStaticMember();

如果要从该类继承,则必须通过新的类型名称访问它:

MyNewType.MyStaticMember();

因此,在代码中使用时,新项目与原始项目没有任何关系。对于诸如多态之类的东西,将无法利用任何继承关系。

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

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

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

因此,最后,从继承静态类中实际上并不会获得任何好处。


5
您不能通过扩展方法将方法添加到现有静态类型中。请查看我对已接受答案的评论,以获取所需用法示例。(基本上,您只想将已命名为MyNewType的名称重命名为MyStaticType,因此MyStaticType:OldProject.MyStaticType)
2011年

2
可以用一种静态类型和虚拟静态成员定义继承关系,使得a SomeClass<T> where T:Foo可以访问的成员Foo,并且如果T是覆盖某些虚拟静态成员的Foo类,则泛型类将使用这些覆盖。甚至有可能定义一个约定,通过该约定,语言可以以与当前CLR兼容的方式来这样做(例如,具有此类成员的类应定义一个受保护的非静态类,其中包含此类实例成员以及一个静态字段)持有该类型的实例)。
2012年

我发现自己有一个案例,我相信继承静态类是正确的做法,尽管显然您仍然可以访问它们。我有一个静态类,用于将原始数据流发送到打印机(用于与工业标签打印机通信)。它有一堆Windows API声明。现在,我发现自己编写了另一类与打印机打乱的类,该类需要相同的API调用。结合?不好。声明两次?丑陋。继承?很好,但不允许。
洛伦·佩希特尔

3

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


我有一个通用的加载器,它接收一个类作为类型。该类有5个辅助方法和2个返回字符串(名称和描述)的方法。我可能选择错了(我是这样认为的),但是我发现的唯一解决方案是在通用加载程序中实例化该类,以访问方法……嗯。
ANeves,2010年

我猜,另一种选择是一本巨大的字典。另外,当您说“您想要实现什么”时,您应该说“您似乎要达到的目标”或类似的内容。
2010年

3

您可以改用composition ...这将允许您从静态类型访问类对象。但仍然无法实现接口或抽象类


2

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

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


1

嗯...如果您只是用静态方法填充了非静态类,那会大不相同吗?


2
那就是我剩下的。我是这样的。而且我不喜欢它。
用户

我也以这种方式完成此操作,但是由于某些原因,当您知道永远不会实例化该对象时,就不会感觉到美观。尽管它没有任何材料成本,但我总是为类似的细节而苦恼。
gdbj

在充满静态方法的非静态类中,您不能放置扩展方法:)
JakubSzułakiewicz19年

1

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

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

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班是不是静态的本身,但我们不允许创建它。实际上,它继承了builds的静态构造函数Base,并且所有属性和方法都Base可以用作静态。现在剩下要做的就是为需要暴露给静态上下文的每个方法和属性制作静态包装器了。

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

PS我们用它来创建编译的查询,实际上可以用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

我不是编译器作家,所以我不确定是否缺少在编译器中实现这些限制的困难。就是说,我坚信有一个信息空间需要有限的静态继承,而基本的答案是您不能因为设计错误(或过于简单)而无法这样做。


0

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

可以将一个类声明为静态类,这表明该类仅包含静态成员。无法使用new关键字创建静态类的实例。加载包含静态类的程序或名称空间时,静态类将由.NET Framework公共语言运行时(CLR)自动加载。

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

以下是静态类的主要功能:

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

  2. 它们无法实例化。

  3. 他们被密封。

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

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

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

静态类是密封的,因此无法继承。它们不能从Object以外的任何类继承。静态类不能包含实例构造函数;但是,它们可以具有静态构造函数。有关更多信息,请参见静态构造函数(C#编程指南)。


-1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.