为什么我不能在C#中使用抽象静态方法?

时间:2008-08-06 11:04:34

标签: c# .net language-design

我最近一直在和providers合作,我遇到了一个有趣的情况,我希望有一个抽象的静态方法。我读了一些关于这个主题的帖子,这有点意义,但有一个很好的明确解释吗?

8 个答案:

答案 0 :(得分:148)

静态方法不是实例化的,它们只是在没有对象引用的情况下可用。

对静态方法的调用是通过类名完成的,而不是通过对象引用,调用它的中间语言(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中没有虚拟/抽象静态方法。

答案 1 :(得分:42)

静态方法不能被继承或覆盖,这就是为什么它们不能是抽象的。由于静态方法是在类的类型而不是实例上定义的,因此必须在该类型上显式调用它们。因此,当您想要在子类上调用方法时,您需要使用其名称来调用它。这使得继承无关紧要。

假设您暂时可以继承静态方法。想象一下这种情况:

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(),会调用哪个方法?返回哪个值?很容易看出,如果不创建对象实例,继承就相当困难。没有继承的抽象方法只是没有主体的方法,因此无法调用。

答案 2 :(得分:16)

另一位受访者(McDowell)表示,多态只适用于对象实例。那应该是合格的;有些语言将类视为“类”或“元类”类型的实例。这些语言确实支持实例和类(静态)方法的多态性。

C#,就像之前的Java和C ++一样,不是这样的语言;显式使用static关键字表示该方法是静态绑定的,而不是动态/虚拟的。

答案 3 :(得分:8)

要添加到前面的解释中,静态方法调用绑定到编译时的特定方法,而不是排除多态行为。

答案 4 :(得分:8)

这种情况肯定需要静态字段和方法的继承:

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"?

答案 5 :(得分:5)

我们实际上覆盖静态方法(在delphi中),它有点难看,但它可以很好地满足我们的需求。

我们使用它,所以类可以有一个没有类实例的可用对象列表,例如,我们有一个如下所示的方法:

class function AvailableObjects: string; override;
begin
  Result := 'Object1, Object2';
end; 

这很丑陋但必要,这样我们就可以实例化所需的内容,而不是只是为了搜索可用的对象而实例化所有类。

这是一个简单的示例,但应用程序本身是一个客户端 - 服务器应用程序,它只有一个服务器中的所有类,以及多个不同的客户端,这些客户端可能不需要服务器拥有的所有内容,也永远不需要对象实例。

因此,与为每个客户端安装一个不同的服务器应用程序相比,这更容易维护。

希望这个例子很明确。

答案 6 :(得分:3)

这个问题已经 12 年了,但仍然需要给出更好的答案。正如评论中很少提到的那样,与所有答案相反,在 C# 中使用静态抽象方法肯定是有意义的。正如哲学家丹尼尔·丹尼特 (Daniel Dennett) 所说,想象力的失败并不是对必然性的洞察。一个常见的错误是没有意识到 C# 不仅仅是一种 OOP 语言。对给定概念的纯 OOP 观点会导致检查受到限制,并且在当前情况下会被误导。多态不仅是对多态进行子化:它还包括参数多态(又名泛型编程),C# 长期以来一直支持这一点。有了这个额外的范式,抽象类(和大多数类型)不仅用于为实例提供类型。它们也可以用作通用参数的边界;某些语言(例如 Haskell,但最近的 Scala、Rust 或 Swift)的用户已经理解了多年。

在这种情况下,您可能想要做这样的事情:

clear
k = [0; 0; 1;];
j = [0; 1; 0;];
i = [1; 0; 0;];
starts = zeros(3,3);
ends = [i j k];
t = linspace(0,1,100);
yaw = pi/4;
pitch = pi/4;
roll = pi/4;
h = quiver3(starts(1,:), starts(2,:), starts(3,:), ends(1,:), ends(2,:), ends(3,:)); % create quiver object
axis([-.5 .8 -.5 1 0 1]); % choose axis size
view([-40 25]) % choose viewing angle
axis manual % this freezes axis size
for k = 1:length(t)
    yawDCM = [cos(yaw*t(k)) sin(yaw*t(k)) 0; -sin(yaw*t(k)) cos(yaw*t(k)) 0; 0 0 1*t(k)];
    pitchDCM = [cos(pitch*t(k)) 0 -sin(pitch*t(k)); 0 1*t(k) 0; sin(pitch*t(k)) 0 cos(pitch*t(k));];
    rollDCM = [1*t(k) 0 0; 0 cos(roll*t(k)) sin(roll*t(k)); 0 -sin(roll*t(k)) cos(roll*t(k));];
    Q = yawDCM*pitchDCM*rollDCM;
    ends2 = Q*ends;
    h.XData = starts(:,1); h.YData = starts(:,2); h.ZData = starts(:,2); % update quiver properties
    h.UData = ends2(:,1); h.VData = ends2(:,2); h.WData = ends2(:,3); % update quiver properties
    pause(.02);
end

在这里,表达可以被子类特化的静态成员的能力完全有道理

不幸的是,C# 不允许抽象静态成员,但我想提出一种可以相当好地模拟它们的模式。这种模式并不完美(它对继承施加了一些限制)但据我所知它是类型安全的。

主要思想是将抽象伴随类(此处为 void Catch<TAnimal>() where TAnimal : Animal { string scientificName = TAnimal.ScientificName; // abstract static property Console.WriteLine($"Let's catch some {scientificName}"); … } )与应包含静态抽象成员的类(此处为 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 { … }

当然我们有两个问题需要解决:

  1. 我们如何确保 void Catch<TAnimal>() where TAnimal : Animal { string scientificName = SpeciesFor<TAnimal>.Instance.ScientificName; Console.WriteLine($"Let's catch some {scientificName}"); … } 的子类的实现者向该子类提供 Animal 的特定实例?
  2. 属性 SpeciesFor<TAnimal> 如何检索此信息?

这里是我们如何解决 1:

SpeciesFor<TAnimal>.Instance

通过将 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 类型。

对于 2,我们可以提供以下实现:

new()

我们怎么知道 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 类型永远不是 Animal<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 类。另一个是它不适用于比抽象类更灵活的接口中的静态成员。

答案 7 :(得分:0)

抽象方法是隐式虚拟的。抽象方法需要实例,但静态方法没有实例。因此,您可以在抽象类中使用静态方法,它不能是静态抽象(或抽象静态)。