【发布时间】:2010-10-20 22:12:11
【问题描述】:
我有几个类实际上并不需要任何状态。从组织的角度来看,我想把它们分成层次结构。
但我似乎无法为静态类声明继承。
类似的东西:
public static class Base
{
}
public static class Inherited : Base
{
}
不会工作。
为什么语言的设计者关闭了这种可能性?
【问题讨论】:
标签: c# inheritance static
我有几个类实际上并不需要任何状态。从组织的角度来看,我想把它们分成层次结构。
但我似乎无法为静态类声明继承。
类似的东西:
public static class Base
{
}
public static class Inherited : Base
{
}
不会工作。
为什么语言的设计者关闭了这种可能性?
【问题讨论】:
标签: c# inheritance static
嗯……如果你只是有一个充满静态方法的非静态类……会不会有很大不同?
【讨论】:
来自here的引用:
这实际上是设计使然。似乎没有充分的理由继承静态类。它具有公共静态成员,您始终可以通过类名本身访问它们。 我看到的继承静态内容的唯一原因是不好的,例如节省了几个打字字符。
可能有理由考虑将静态成员直接带入作用域的机制(实际上我们将在 Orcas 产品周期之后考虑这一点),但静态类继承不是要走的路:这是使用错误的机制,并且仅适用于恰好位于静态类中的静态成员。
(Mads Torgersen,C# 语言 PM)
来自channel9的其他意见
.NET 中的继承仅适用于实例库。 静态方法是在类型级别而不是在实例级别定义的。这就是为什么覆盖不适用于静态方法/属性/事件...
静态方法只在内存中保存一次。没有为他们创建的虚拟表等。
如果你在 .NET 中调用一个实例方法,你总是给它当前实例。这被 .NET 运行时隐藏了,但它确实发生了。 每个实例方法都有一个指针(引用)作为第一个参数,指向运行该方法的对象。静态方法不会发生这种情况(因为它们是在类型级别定义的)。编译器应该如何决定选择调用的方法?
(littleguru)
作为一个有价值的想法,littleguru 对此问题有部分“解决方法”:Singleton 模式。
【讨论】:
object,每个人都应该知道这一点。所以静态类总是继承自object,无论你是否明确指定。
这样想:你通过类型名访问静态成员,像这样:
MyStaticType.MyStaticMember();
如果你要从那个类继承,你必须通过新的类型名来访问它:
MyNewType.MyStaticMember();
因此,在代码中使用时,新项目与原始项目没有任何关系。对于像多态这样的事情,没有办法利用任何继承关系。
也许您认为您只是想扩展原始类中的一些项目。在这种情况下,没有什么可以阻止您在全新类型中使用原始成员。
也许您想将方法添加到现有的静态类型。您已经可以通过扩展方法做到这一点。
也许您希望能够在运行时将静态Type 传递给函数并调用该类型的方法,而无需确切知道该方法的作用。在这种情况下,您可以使用接口。
所以,最终你并没有真正从继承静态类中获得任何好处。
【讨论】:
SomeClass<T> where T:Foo 可以访问Foo 的成员,如果T 是一个覆盖某些虚拟静态的类Foo 的成员,泛型类将使用这些覆盖。甚至有可能定义一个约定,通过该约定,语言可以以与当前 CLR 兼容的方式这样做(例如,具有此类成员的类应定义一个包含此类实例成员的受保护非静态类以及静态字段持有该类型的实例)。
你不能继承静态类的主要原因是它们是抽象的和密封的(这也阻止了它们的任何实例被创建)。
所以这个:
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) 会自动加载静态类。
使用静态类来包含与特定对象无关的方法。例如,创建一组不作用于实例数据并且不与代码中的特定对象关联的方法是一个常见的要求。您可以使用静态类来保存这些方法。
以下是静态类的主要特征:
它们只包含静态成员。
它们不能被实例化。
它们是密封的。
它们不能包含实例构造函数(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 关键字。但这种方法有助于支持与静态继承非常相似的东西。
附: 我们用它来创建编译查询,它实际上可以用 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>();
很遗憾,由于“设计”的语言限制,我们无法做到:
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);
// ...
创建静态子项的人无需考虑序列化过程,只要他们了解可能对平台或环境的序列化引擎施加的任何限制。
静态(单例和其他形式的“全局”)经常出现在配置存储周围。静态继承将允许在语法中清晰地表示这种责任分配,以匹配配置的层次结构。不过,正如我所展示的,如果实现了基本的静态继承概念,则很有可能会出现大量歧义。
我相信正确的设计选择是允许具有特定限制的静态继承:
您仍然可以通过通用引用 MyStaticBase<ChildPayload>.SomeBaseField 更改同一个商店。但是您会感到气馁,因为必须指定泛型类型。虽然子引用会更清晰:MyStaticChild.SomeBaseField。
我不是编译器编写者,所以我不确定我是否遗漏了一些关于在编译器中实现这些限制的困难。也就是说,我坚信信息空间需要有限的静态继承,而基本答案是因为设计选择不佳(或过于简单)而无法实现。
【讨论】: