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

最近我一直在与提供商合作,我来了跨越一个有趣的情况,我想有一个抽象的类有一个抽象的静态方法。我在这个主题上阅读了几篇文章,它有道理,但是有一个很好的明确解释吗?

0
额外 编辑
意见: 1
请保留这些以允许将来的改进。
额外 作者 Mark Biek,
在基类中,该方法未解决,并且不能使用它。你需要一个派生类型或一个对象(这又将有一个派生类型)。你应该能够调用baseClassObject.Method()或DerivedClass.Method()。你不能调用BaseClass.Method(),因为它没有给你类型。
额外 作者 William Jockusch,
我认为问题在于C#需要另一个关键字,正是出于这种情况。你想要一个方法,它的返回值只取决于它被调用的类型。如果所述类型未知,则不能将其称为“静态”。但一旦知道类型,它就会变成静态的。 “未解决的静态”是这个想法 - 它不是静态的,但是一旦我们知道接收类型,它就会是。这是一个非常好的概念,这就是程序员不断要求它的原因。但它并不完全符合设计师对该语言的看法。
额外 作者 William Jockusch,
额外 作者 peterh,
@WilliamJockusch接收类型是什么意思?如果我调用BaseClass.StaticMethod(),那么BaseClass是它可以用来作出决定的唯一类型。但在这个层面上它是抽象的,所以该方法无法解决。如果你改为调用DerivedClass.StaticMethod,那么基类是无关紧要的。
额外 作者 Martin Capodici,
作为一个附注, TextWriter.NullStreamWriter.Null 显示了围绕这个主题的类似尝试。
额外 作者 Ken Kin,

7 答案

为了增加前面的解释,静态方法调用被绑定到编译时的特定方法,这相当于排除了多态行为。

0
额外
那么你怎么认为多态性在CLR上起作用呢?你的解释只是排除了虚拟方法派遣。
额外 作者 Rytmis,
道歉,我的意思不是侮辱(尽管我确实承认有点反应迟钝;-)。我的问题的重点是,如果你有这些类:class Base {public virtual void Method(); }类Derived:Base {public override void Method(); }并写入:Base instance = new Derived(); instance.Method();调用站点上的编译时类型信息是,当实际实例是Derived时,我们已获得Base的实例。所以编译器无法解析要调用的确切方法。相反,它会发出一个“callvirt”IL指令,告诉运行时分派。
额外 作者 Rytmis,
...而对于静态方法,没有实例可以开启虚拟调度,因此它总是必须发出“调用”指令。现在,我扩大了我的答案,我意识到这是一个糟糕的答案。 ;-)
额外 作者 Rytmis,
谢谢你,这是信息!猜猜我已经潜入IL中足够长的时间了,祝我好运。
额外 作者 Adam Tolley,
这可能不是一个有用的评论。我邀请了(以“我的理解是”)有用的话语,想也许你可以提供多一点内容 - 看到,因为人们来这里寻找答案,而不是侮辱。虽然,看来我可能是犯了同样的事情 - 我真正的意思的问题,上述评论:不C#在编译时评估这些东西?
额外 作者 Adam Tolley,
C#是静态类型的;根据我的理解,在编译时也会调用多态方法 - 也就是说CLR不会在运行时解决调用哪个方法。
额外 作者 Adam Tolley,

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

对静态方法的调用是通过类名实现的,而不是通过对象引用完成的,调用它的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中不可用。

0
额外
泛型类型参数有效地表现为不可持久的“类型”变量,而虚拟静态方法在这种情况下可能很有用。例如,如果有一个具有虚拟静态 CreateFromDescription 工厂方法的 Car 类型,那么接受 Car 约束泛型的代码> T 可以调用 T.CreateFromDescription 来产生一个类型为 T 的汽车。如果定义这种方法的每个类型都拥有一个嵌套类泛型的静态单例实例,该实例包含虚拟的“静态”方法,那么这种构造在CLR中可以得到很好的支持。
额外 作者 supercat,
结合在C#中完成运算符重载的方式,这不幸地消除了需要子类为给定的运算符重载提供实现的可能性。
额外 作者 Chris Moschini,

我们实际上重写了静态方法(在delphi中),它有点难看,但它对我们的需求来说工作得很好。

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

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

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

这是一个简单的例子,但是应用程序本身就是一个客户端 - 服务器应用程序,它只有一个服务器提供所有的类,而多个不同的客户端可能不需要服务器拥有的所有东西,永远不需要对象实例。

所以这比维护每个客户端的服务器应用程序要容易得多。

希望这个例子很清楚。

0
额外

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

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

0
额外

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

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

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

0
额外
@ArtemRussakovskii:假设有一个 int DoSomething (),其中T:Base {return T.GetNumber();} 。如果 DoSomething ()可以返回5,而 DoSomething ()会返回2,这似乎很有用。这种能力不仅对玩具的例子有用,而且对于像 class Car {public static virtual Car Build(PurchaseOrder PO);} 类的东西也是有用的,其中每个类派生自 Car >必须定义一种方法,可以根据采购订单构建实例。
额外 作者 supercat,
为什么在这个世界上Base.GetNumber()会返回5以外的其他东西?这是基类中的一种方法 - 这里只有一个选项。
额外 作者 Artem Russakovskii,
非静态继承有完全相同的“问题”。
额外 作者 Ark-kun,
鉴于你的情况,我会说Base.GetNumber()将返回5; Child1.GetNumber()返回1; Child2.GetNumber()返回2;你能证明我错了,帮助我理解你的推理吗?谢谢
额外 作者 Luis Filipe,
你认为Base.GetNumber()返回5在脸上,意味着你已经明白是怎么回事。通过返回基值,不会继承继续。
额外 作者 David Wengier,

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

0
额外
除了设计之外,-1个虚拟方法不需要实例。而且你实际上并没有解决这个问题,而是偏离了这个问题。
额外 作者 TJMonk15,

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

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"?
0
额外
不,只有一个数组“腿”的实例。输出是不确定的,因为你不知道静态构造函数将被调用的顺序(实际上并不保证基类静态构造函数将被调用)。 “需要”是一个相当绝对的术语,“欲望”可能更准确。
额外 作者 Sam,
legs 应该是一个静态的抽象属性。
额外 作者 AbleArcher,
那么它不会强制我调用静态构造函数。但我现在就拿...
额外 作者 Evren Ozturk,