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


181

我最近一直在与提供程序一起工作,遇到了一个有趣的情况,我想拥有一个抽象类,该抽象类具有抽象的静态方法。我阅读了有关该主题的几篇文章,这很有道理,但是有一个很好的清晰解释吗?


3
请保持开放状态,以便将来进行改进。
Mark Biek

6
我认为问题在于,对于这种情况,C#需要另一个关键字。您需要一个方法,其返回值仅取决于调用它的类型。如果所说的类型未知,则不能将其称为“静态”。但是一旦知道类型,它将变为静态。这个想法是“未解决的静态”,它还不是静态的,但是一旦我们知道接收类型,它将是静态的。这是一个非常好的概念,这就是程序员不断要求它的原因。但这与设计者对语言的思考方式不太吻合。
William Jockusch 2015年

@WilliamJockusch接收类型是什么意思?如果我调用BaseClass.StaticMethod(),则BaseClass是它可以用来做出决定的唯一类型。但在此级别上它是抽象的,因此无法解析该方法。如果改为正确调用DerivedClass.StaticMethod,则基类无关紧要。
马丁·卡波迪奇

在基类中,该方法是无法解析的,您不能使用它。您需要派生类型或对象(而对象又将具有派生类型)。您应该能够调用baseClassObject.Method()或DerivedClass.Method()。您不能调用BaseClass.Method(),因为那不会给您类型。
威廉Jockusch

Answers:


156

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

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


4
不幸的是,与在C#中完成运算符重载的方式相结合,这消除了要求子类为给定的运算符重载提供实现的可能性。
克里斯·莫斯基尼

23
我不觉得这是答案的定义非常有用的Test()A,而不是抽象的和潜在定义B\

5
泛型类型参数有效地充当了不可持久的“类型”变量,虚拟静态方法在这种情况下可能很有用。例如,如果某个Car类型具有虚拟静态CreateFromDescription工厂方法,则接受一个Car受约束的通用类型的代码T可以调用T.CreateFromDescription以生成type的汽车T。如果定义此方法的每个类型都持有嵌套类的静态单例实例,而该嵌套类泛型持有虚拟的“静态”方法,则可以在CLR中很好地支持这种构造。
超级猫

45

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

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

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


33
给定您的方案,我会说Base.GetNumber()将返回5;Child1.GetNumber()返回1;Child2.GetNumber()返回2;您能证明我错了,以帮助我理解您的推理吗?谢谢
Luis Filipe

您认为Base.GetNumber()返回5的面孔表示您已经了解发生了什么。通过返回基本值,就没有继承。
David Wengier

60
为什么在世界上,Base.GetNumber()会返回除5之外的其他任何内容?这是基类中的方法-那里只有一个选项。
Artem Russakovskii 09年

4
@ArtemRussakovskii:假设有人吃过int DoSomething<T>() where T:Base {return T.GetNumber();}。如果DoSomething<Base>()可以返回五个,而DoSomething<Child2>()返回两个,则似乎很有用。这种能力不仅对玩具示例有用,而且对类似的东西也有用,在这种情况下class Car {public static virtual Car Build(PurchaseOrder PO);},每个派生的类Car都必须定义一个方法,该方法可以在给定购买订单的情况下构建实例。
超级猫

4
非静态继承存在完全相同的“问题”。
方舟k 2014年

18

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

像以前的Java和C ++一样,C#并不是这种语言。所述static关键字被明确地用于表示,该方法是静态结合,而不是动态的/虚拟的。


9

在这种情况下,绝对需要静态字段和方法的继承:

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

4
不,数组“ legs”只有一个实例。输出是不确定的,因为您不知道静态构造函数将被调用的顺序(实际上根本无法保证将调用基类静态构造函数)。“需要”是一个相当绝对的术语,其中“欲望”可能更准确。
山姆

legs应该是静态的抽象属性。
Little Endian

8

为了补充前面的解释,静态方法调用在编译时绑定到特定的方法,而是排除了多态行为。


C#是静态类型的;据我了解,对多态方法的调用在编译时也受到约束-也就是说,CLR并没有解决在运行时调用哪个方法的问题。
亚当·托利

那么,您认为多态性如何在CLR上起作用?您的解释只是排除了虚拟方法的派发。
Rytmis

评论实际上没有那么有用。我邀请(以“据我所知”)有用的演讲,想想也许您可以提供更多的内容-看到人们来这里寻找答案而不是侮辱。虽然,似乎我可能对同一件事感到内--我确实将上述评论作为一个问题:C#不在编译时评估这些东西吗?
亚当·托利

抱歉,我不是故意侮辱(尽管我确实承认对此反应很敏捷;-)。我的问题的重点是,如果您有以下类:class Base {public virtual void Method(); }类派生:Base {公共重写void Method(); 然后编写:基本实例= new Derived(); instance.Method(); 调用站点上的编译时类型信息是,当实际实例为Derived时,我们有一个Base实例。因此,编译器无法解析要调用的确切方法。相反,它会发出“callvirt” IL指令,它告诉运行时调度..
Rytmis

1
谢谢男人,多数民众赞成在翔实!猜猜我已经足够长时间推迟进入IL了,祝我好运。
亚当·托利

5

我们实际上重写了静态方法(在delphi中),这有点丑陋,但它可以很好地满足我们的需求。

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

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

这很丑陋,但是很必要,这样我们可以实例化所需的东西,而不是实例化所有类以仅搜索可用的对象。

这是一个简单的示例,但是应用程序本身是一个客户端-服务器应用程序,仅在一台服务器上具有所有可用的类,并且多个不同的客户端可能不需要服务器拥有的所有内容,也永远不需要对象实例。

因此,与为每个客户端使用一个不同的服务器应用程序相比,维护起来容易得多。

希望这个例子很清楚。


0

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


1
-1虚方法不需要实例,除非经过设计。您实际上并没有解决这个问题,反而使它变了。
FallenAvatar,2013年
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.