关于.net:为什么我不能在C#中使用抽象静态方法?

关于.net:为什么我不能在C#中使用抽象静态方法?

Why can't I have abstract static methods in C#?

最近,我与提供者进行了大量的合作,我遇到了一个有趣的情况,我希望有一个抽象类,它有一个抽象的静态方法。我读了一些关于这个话题的文章,这有点道理,但是有没有一个清晰的解释?


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

对静态方法的调用是通过类名完成的,而不是通过对象引用完成的,并且调用它的中间语言(IL)代码将通过定义它的类的名称调用抽象方法,而不一定是使用的类的名称。

让我举个例子。

使用以下代码:

1
2
3
4
5
6
7
8
9
10
public class A
{
    public static void Test()
    {
    }
}

public class B : A
{
}

如果您调用b.test,如下所示:

1
2
3
4
5
6
7
class Program
{
    static void Main(string[] args)
    {
        B.Test();
    }
}

那么主方法中的实际代码如下:

1
2
3
4
5
6
.entrypoint
.maxstack 8
L0000: nop
L0001: call void ConsoleApplication1.A::Test()
L0006: nop
L0007: ret

正如您所看到的,调用是对a.test的,因为它是定义它的a类,而不是对b.test的,即使您可以这样编写代码。

如果您有类类型,如Delphi中的类类型,在Delphi中,您可以使变量引用类型而不是对象,那么您将更多地使用虚拟方法和抽象静态方法(以及构造函数),但它们不可用,因此静态调用在.NET中是非虚拟的。

我意识到IL设计者可以允许编译代码来调用b.test,并在运行时解析调用,但它仍然不是虚拟的,因为您仍然需要在那里编写某种类名。

虚拟方法(因此是抽象方法)只有在使用一个变量时才有用,该变量在运行时可以包含许多不同类型的对象,因此您希望为变量中的当前对象调用正确的方法。对于静态方法,您无论如何都需要通过一个类名,所以在编译时确切的调用方法是已知的,因为它不能也不会改变。

因此,虚拟/抽象静态方法在.NET中不可用。


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

假设您可以暂时继承静态方法。想象一下这个场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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++一样,不是这样的语言;EDCOX1的0个关键字被显式地用来表示该方法是静态绑定的而不是动态的/虚拟的。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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中),这有点难看,但它可以满足我们的需求。

我们使用它,这样类就可以在没有类实例的情况下拥有其可用对象的列表,例如,我们有一个如下所示的方法:

1
2
3
4
class function AvailableObjects: string; override;
begin
  Result := 'Object1, Object2';
end;

这很难看,但很有必要,这样我们就可以实例化所需的内容,而不是让所有类都实例化以搜索可用的对象。

这是一个简单的例子,但是应用程序本身是一个客户机-服务器应用程序,它只在一个服务器上有所有可用的类,以及多个不同的客户机,它们可能不需要服务器拥有的一切,也不需要对象实例。

因此,这比为每个客户机提供一个不同的服务器应用程序更容易维护。

希望例子是清楚的。


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


推荐阅读