最近我一直在与提供程序合作,我遇到了一个有趣的情况,我想要一个具有抽象静态方法的抽象类。我阅读了一些关于该主题的帖子,这有点道理,但是有一个很好的清晰解释吗?
10 回答
静态方法不是这样实例化的,它们只是在没有对象引用的情况下可用。
对静态方法的调用是通过类名完成的,而不是通过对象引用,调用它的中间语言 (IL) 代码将通过定义它的类的名称来调用抽象方法,不一定是你使用的类。
让我举个例子。
使用以下代码:
public class A
{
public static void Test()
{
}
}
public class B : A
{
}
如果你调用 B.Test,像这样:
class Program
{
static void Main(string[] args)
{
B.Test();
}
}
那么Main方法里面的实际代码如下:
.entrypoint
.maxstack 8
L0000: nop
L0001: call void ConsoleApplication1.A::Test()
L0006: nop
L0007: ret
如您所见,调用是对 A.Test 进行的,因为定义它的是 A 类,而不是 B.Test,即使您可以这样编写代码。
如果你有类类型,比如在 Delphi 中,你可以在其中创建一个引用类型而不是对象的变量,那么你将更多地使用虚拟和抽象静态方法(以及构造函数),但它们不可用并且因此静态调用在 .NET 中是非虚拟的。
我意识到 IL 设计人员可以允许编译代码以调用 B.Test,并在运行时解析调用,但它仍然不是虚拟的,因为您仍然需要在那里编写某种类名。
虚拟方法以及抽象方法仅在您使用变量时有用,该变量在运行时可以包含许多不同类型的对象,因此您希望为变量中的当前对象调用正确的方法。对于静态方法,无论如何您都需要通过类名,因此要调用的确切方法在编译时是已知的,因为它不能也不会改变。
因此,虚拟/抽象静态方法在 .NET 中不可用。
静态方法不能被继承或覆盖,这就是它们不能抽象的原因。由于静态方法是在类的类型而不是实例上定义的,因此必须在该类型上显式调用它们。所以当你想调用一个子类的方法时,你需要使用它的名字来调用它。这使得继承无关紧要。
假设您可以暂时继承静态方法。想象一下这个场景:
public static class Base
{
public static virtual int GetNumber() { return 5; }
}
public static class Child1 : Base
{
public static override int GetNumber() { return 1; }
}
public static class Child2 : Base
{
public static override int GetNumber() { return 2; }
}
如果调用 Base.GetNumber(),会调用哪个方法?返回哪个值?很容易看出,如果不创建对象实例,继承是相当困难的。没有继承的抽象方法只是没有主体的方法,所以不能被调用。
另一位受访者 (McDowell) 表示,多态性仅适用于对象实例。那应该是合格的;有些语言确实将类视为“类”或“元类”类型的实例。这些语言确实支持实例和类(静态)方法的多态性。
C# 与之前的 Java 和 C++ 一样,不是这样的语言;static
关键字显式用于表示该方法是静态绑定的,而不是动态/虚拟的。
在这种情况下,静态字段和方法肯定需要继承:
abstract class Animal
{
protected static string[] legs;
static Animal() {
legs=new string[0];
}
public static void printLegs()
{
foreach (string leg in legs) {
print(leg);
}
}
}
class Human: Animal
{
static Human() {
legs=new string[] {"left leg", "right leg"};
}
}
class Dog: Animal
{
static Dog() {
legs=new string[] {"left foreleg", "right foreleg", "left hindleg", "right hindleg"};
}
}
public static void main() {
Dog.printLegs();
Human.printLegs();
}
//what is the output?
//does each subclass get its own copy of the array "legs"?
添加到前面的解释中,静态方法调用在编译时绑定到特定方法,从而排除了多态行为。
我们实际上覆盖了静态方法(在 delphi 中),它有点难看,但它可以很好地满足我们的需求。
我们使用它,因此类可以在没有类实例的情况下拥有其可用对象的列表,例如,我们有一个如下所示的方法:
class function AvailableObjects: string; override;
begin
Result := 'Object1, Object2';
end;
这很丑但很有必要,这样我们就可以实例化需要的东西,而不是为了搜索可用对象而实例化所有类。
这是一个简单的例子,但应用程序本身是一个客户端-服务器应用程序,它在一个服务器中拥有所有可用的类,以及多个不同的客户端,它们可能不需要服务器拥有的所有东西,也永远不需要对象实例。
因此,这比为每个客户端拥有一个不同的服务器应用程序要容易得多。
希望这个例子很清楚。
这个问题已有 12 年历史,但仍然需要给出更好的答案。正如评论中很少有人指出的那样,与所有答案所假装的相反,在 C# 中使用静态抽象方法肯定是有意义的。正如哲学家丹尼尔丹尼特所说,想象力的失败并不是对必然性的洞察。没有意识到 C# 不仅仅是一种 OOP 语言,这是一个常见的错误。对给定概念的纯 OOP 视角会导致受限且在当前情况下被误导的检查。多态性不仅仅是对多态性进行细分:它还包括参数多态性(又名泛型编程),而 C# 已经支持这一点很长时间了。在这个额外的范例中,抽象类(和大多数类型)不仅用于为实例提供类型。它们也可以用作泛型参数的界限;多年来,某些语言(例如 Haskell,以及最近的 Scala、Rust 或 Swift)的用户已经理解的东西。
在这种情况下,您可能想要执行以下操作:
void Catch<TAnimal>() where TAnimal : Animal
{
string scientificName = TAnimal.ScientificName; // abstract static property
Console.WriteLine($"Let's catch some {scientificName}");
…
}
在这里,表达可以由子类专门化的静态成员的能力完全有意义!
不幸的是,C# 不允许抽象静态成员,但我想提出一种可以很好地模拟它们的模式。这种模式并不完美(它对继承施加了一些限制),但据我所知它是类型安全的。
主要思想是将抽象伴随类(此处SpeciesFor<TAnimal>
)与应包含静态抽象成员的类(此处)相关联Animal
:
public abstract class SpeciesFor<TAnimal> where TAnimal : Animal
{
public static SpeciesFor<TAnimal> Instance { get { … } }
// abstract "static" members
public abstract string ScientificName { get; }
…
}
public abstract class Animal { … }
现在我们想让这项工作:
void Catch<TAnimal>() where TAnimal : Animal
{
string scientificName = SpeciesFor<TAnimal>.Instance.ScientificName;
Console.WriteLine($"Let's catch some {scientificName}");
…
}
当然我们有两个问题需要解决:
- 我们如何确保子类的实现者
Animal
提供该子类的特定实例SpeciesFor<TAnimal>
? - 该物业如何
SpeciesFor<TAnimal>.Instance
检索此信息?
这是我们如何解决1:
public abstract class Animal<TSelf> where TSelf : Animal<TSelf>
{
private Animal(…) {}
public abstract class OfSpecies<TSpecies> : Animal<TSelf>
where TSpecies : SpeciesFor<TSelf>, new()
{
protected OfSpecies(…) : base(…) { }
}
…
}
通过将构造函数设为Animal<TSelf>
私有,我们确保它的所有子类也是内部类的子类Animal<TSelf>.OfSpecies<TSpecies>
。所以这些子类必须指定一个TSpecies
有new()
边界的类型。
对于 2,我们可以提供以下实现:
public abstract class SpeciesFor<TAnimal> where TAnimal : Animal<TAnimal>
{
private static SpeciesFor<TAnimal> _instance;
public static SpeciesFor<TAnimal> Instance => _instance ??= MakeInstance();
private static SpeciesFor<TAnimal> MakeInstance()
{
Type t = typeof(TAnimal);
while (true)
{
if (t.IsConstructedGenericType
&& t.GetGenericTypeDefinition() == typeof(Animal<>.OfSpecies<>))
return (SpeciesFor<TAnimal>)Activator.CreateInstance(t.GenericTypeArguments[1]);
t = t.BaseType;
if (t == null)
throw new InvalidProgramException();
}
}
// abstract "static" members
public abstract string ScientificName { get; }
…
}
我们怎么知道里面的反射代码MakeInstance()
永远不会抛出?正如我们已经说过的,几乎所有层次结构中Animal<TSelf>
的类也是Animal<TSelf>.OfSpecies<TSpecies>
. TSpecies
所以我们知道,对于这些类,必须提供特定的。由于约束,这种类型也必然是可构造的: new()
。但这仍然排除了Animal<Something>
没有相关物种的抽象类型。现在我们可以说服自己奇怪地重复出现的模板模式where TAnimal : Animal<TAnimal>
使得我们无法编写SpeciesFor<Animal<Something>>.Instance
,因为 typeAnimal<Something>
永远不是Animal<Animal<Something>>
.
等等:
public class CatSpecies : SpeciesFor<Cat>
{
// overriden "static" members
public override string ScientificName => "Felis catus";
public override Cat CreateInVivoFromDnaTrappedInAmber() { … }
public override Cat Clone(Cat a) { … }
public override Cat Breed(Cat a1, Cat a2) { … }
}
public class Cat : Animal<Cat>.OfSpecies<CatSpecies>
{
// overriden members
public override string CuteName { get { … } }
}
public class DogSpecies : SpeciesFor<Dog>
{
// overriden "static" members
public override string ScientificName => "Canis lupus familiaris";
public override Dog CreateInVivoFromDnaTrappedInAmber() { … }
public override Dog Clone(Dog a) { … }
public override Dog Breed(Dog a1, Dog a2) { … }
}
public class Dog : Animal<Dog>.OfSpecies<DogSpecies>
{
// overriden members
public override string CuteName { get { … } }
}
public class Program
{
public static void Main()
{
ConductCrazyScientificExperimentsWith<Cat>();
ConductCrazyScientificExperimentsWith<Dog>();
ConductCrazyScientificExperimentsWith<Tyranosaurus>();
ConductCrazyScientificExperimentsWith<Wyvern>();
}
public static void ConductCrazyScientificExperimentsWith<TAnimal>()
where TAnimal : Animal<TAnimal>
{
// Look Ma! No animal instance polymorphism!
TAnimal a2039 = SpeciesFor<TAnimal>.Instance.CreateInVivoFromDnaTrappedInAmber();
TAnimal a2988 = SpeciesFor<TAnimal>.Instance.CreateInVivoFromDnaTrappedInAmber();
TAnimal a0400 = SpeciesFor<TAnimal>.Instance.Clone(a2988);
TAnimal a9477 = SpeciesFor<TAnimal>.Instance.Breed(a0400, a2039);
TAnimal a9404 = SpeciesFor<TAnimal>.Instance.Breed(a2988, a9477);
Console.WriteLine(
"The confederation of mad scientists is happy to announce the birth " +
$"of {a9404.CuteName}, our new {SpeciesFor<TAnimal>.Instance.ScientificName}.");
}
}
这种模式的一个限制是不可能(据我所知)以令人满意的方式扩展类层次结构。例如,我们不能引入与同伴Mammal
关联的中间类。MammalClass
另一个是它不适用于比抽象类更灵活的接口中的静态成员。
使用.NET 6
/C# preview
您可以使用“接口中的静态抽象成员”来做到这一点。
(在编写代码时编译成功,但某些 IDE 无法突出显示代码)
using System;
namespace StaticAbstractTesting
{
public interface ISomeAbstractInterface
{
public abstract static string CallMe();
}
public class MyClassA : ISomeAbstractInterface
{
static string ISomeAbstractInterface.CallMe()
{
return "You called ClassA";
}
}
public class MyClassB : ISomeAbstractInterface
{
static string ISomeAbstractInterface.CallMe()
{
return "You called ClassB";
}
}
public class Program
{
public static void Main(string[] args)
{
UseStaticClassMethod<MyClassA>();
UseStaticClassMethod<MyClassB>();
}
public static void UseStaticClassMethod<T>() where T : ISomeAbstractInterface
{
Console.WriteLine($"{typeof(T).Name}.CallMe() result: {T.CallMe()}");
}
}
}
资源:
抽象方法是隐式虚拟的。抽象方法需要一个实例,但静态方法没有实例。因此,您可以在抽象类中拥有静态方法,但它不能是静态抽象(或抽象静态)。
它目前在 C# 10 中作为预览功能提供。