我最近一直在和providers合作,我遇到了一个有趣的情况,我希望有一个抽象的静态方法。我读了一些关于这个主题的帖子,这有点意义,但有一个很好的明确解释吗?
答案 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 { … }
当然我们有两个问题需要解决:
void Catch<TAnimal>() where TAnimal : Animal
{
string scientificName = SpeciesFor<TAnimal>.Instance.ScientificName;
Console.WriteLine($"Let's catch some {scientificName}");
…
}
的子类的实现者向该子类提供 Animal
的特定实例?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)
抽象方法是隐式虚拟的。抽象方法需要实例,但静态方法没有实例。因此,您可以在抽象类中使用静态方法,它不能是静态抽象(或抽象静态)。