lom*_*axx 175 .net c# language-design
我最近一直与提供商合作,我遇到了一个有趣的情况,我希望有一个抽象的静态方法.我读了一些关于这个主题的帖子,这有点意义,但有一个很清楚的解释吗?
ang*_*son 153
静态方法不是这样实例化的,它们只是在没有对象引用的情况下可用.
对静态方法的调用是通过类名完成的,而不是通过对象引用完成的,调用它的IL代码将通过定义它的类的名称调用抽象方法,不一定是您使用的类的名称.
让我举个例子.
使用以下代码:
public class A
{
public static void Test()
{
}
}
public class B : A
{
}
Run Code Online (Sandbox Code Playgroud)
如果你打电话给B.Test,就像这样:
class Program
{
static void Main(string[] args)
{
B.Test();
}
}
Run Code Online (Sandbox Code Playgroud)
那么Main方法中的实际代码如下:
.entrypoint
.maxstack 8
L0000: nop
L0001: call void ConsoleApplication1.A::Test()
L0006: nop
L0007: ret
Run Code Online (Sandbox Code Playgroud)
正如你所看到的那样,调用A.Test,因为它是定义它的A类,而不是B.Test,即使你可以用这种方式编写代码.
如果您有类类型,比如在Delphi中,您可以在其中创建一个引用类型而不是对象的变量,那么您将更多地使用虚拟和抽象静态方法(以及构造函数),但它们不可用,因此静态调用在.NET中是非虚拟的.
我意识到IL设计者可以允许编译代码来调用B.Test,并在运行时解析调用,但它仍然不是虚拟的,因为你仍然需要在那里编写某种类名.
虚拟方法以及抽象方法仅在您使用变量时才有用,该变量在运行时可以包含许多不同类型的对象,因此您希望为变量中的当前对象调用正确的方法.使用静态方法,无论如何都需要通过类名,因此在编译时需要确切的调用方法,因为它不会也不会更改.
因此,.NET中不提供虚拟/抽象静态方法.
Dav*_*ier 43
静态方法不能被继承或覆盖,这就是为什么它们不能是抽象的.由于静态方法是在类的类型而不是实例上定义的,因此必须在该类型上显式调用它们.因此,当您想要在子类上调用方法时,您需要使用其名称来调用它.这使得继承无关紧要.
假设您可以暂时继承静态方法.想象一下这种情况:
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; }
}
Run Code Online (Sandbox Code Playgroud)
如果你调用Base.GetNumber(),将调用哪个方法?返回哪个值?很容易看出,如果不创建对象实例,继承就相当困难.没有继承的抽象方法只是没有主体的方法,因此无法调用.
0BL*_*BLU 19
使用.NET 6/C# 11/next/preview您可以使用“接口中的静态抽象成员”来做到这一点。
(在编写代码时,代码编译成功,但某些 IDE 在突出显示代码时出现问题)
using System;
namespace StaticAbstractTesting
{
public interface ISomeAbstractInterface
{
public abstract static string CallMe();
}
public class MyClassA : ISomeAbstractInterface
{
static string ISomeAbstractInterface.CallMe()
{
return "You called ClassA";
}
}
public class MyClassB : ISomeAbstractInterface
{
static string ISomeAbstractInterface.CallMe()
{
return "You called ClassB";
}
}
public class Program
{
public static void Main(string[] args)
{
UseStaticClassMethod<MyClassA>();
UseStaticClassMethod<MyClassB>();
}
public static void UseStaticClassMethod<T>() where T : ISomeAbstractInterface
{
Console.WriteLine($"{typeof(T).Name}.CallMe() result: {T.CallMe()}");
}
}
}
Run Code Online (Sandbox Code Playgroud)
由于这是运行时的重大变化,因此生成的 IL 代码看起来也非常干净,这意味着这不仅仅是语法糖。
public static void UseStaticClassMethodSimple<T>() where T : ISomeAbstractInterface {
IL_0000: constrained. !!T
IL_0006: call string StaticAbstractTesting.ISomeAbstractInterface::CallMe()
IL_000b: call void [System.Console]System.Console::WriteLine(string)
IL_0010: ret
}
Run Code Online (Sandbox Code Playgroud)
资源:
Chr*_*son 17
另一位受访者(McDowell)表示,多态只适用于对象实例.那应该是合格的; 有些语言将类视为"类"或"元类"类型的实例.这些语言确实支持实例和类(静态)方法的多态性.
C#,就像之前的Java和C++一样,不是这样的语言; 所述static关键字被明确地用于表示,该方法是静态结合,而不是动态的/虚拟的.
Con*_*rph 15
这个问题已经有12年历史了,但仍然需要给出更好的答案。正如评论中很少有人指出的那样,与所有答案所假装的相反,在 C# 中拥有静态抽象方法肯定是有意义的。正如哲学家丹尼尔·丹尼特(Daniel Dennett)所说,想象力的失败并不意味着洞察必然性。没有意识到 C# 不仅仅是一种 OOP 语言是一个常见的错误。对给定概念的纯粹 OOP 视角导致了有限的(在当前情况下是误导性的)检查。多态性不仅仅涉及子类型化多态性:它还包括参数多态性(又名泛型编程),并且 C# 长期以来一直支持这一点。在这个附加范例中,抽象类(和大多数类型)不仅用于向实例提供类型。它们还可以用作通用参数的界限;某些语言(例如 Haskell,还有最近的 Scala、Rust 或 Swift)的用户多年来已经理解的东西。
\n在这种情况下,您可能想做这样的事情:
\nvoid Catch<TAnimal>() where TAnimal : Animal\n{\n string scientificName = TAnimal.ScientificName; // abstract static property\n Console.WriteLine($"Let\'s catch some {scientificName}");\n \xe2\x80\xa6\n}\nRun Code Online (Sandbox Code Playgroud)\n在这里,表达可由子类专门化的静态成员的能力是完全有意义的!
\n不幸的是,C# 不允许抽象静态成员,但我想提出一种可以很好地模拟它们的模式。这种模式并不完美(它对继承施加了一些限制),但据我所知它是类型安全的。
\n主要思想是将抽象泛型伴随类(此处SpeciesFor<TAnimal>)与应包含静态抽象成员的类(此处Animal)相关联:
public abstract class SpeciesFor<TAnimal> where TAnimal : Animal\n{\n public static SpeciesFor<TAnimal> Instance { get { \xe2\x80\xa6 } }\n\n // abstract "static" members\n\n public abstract string ScientificName { get; }\n \n \xe2\x80\xa6\n}\n\npublic abstract class Animal { \xe2\x80\xa6 }\nRun Code Online (Sandbox Code Playgroud)\n现在我们想让这个工作:
\nvoid Catch<TAnimal>() where TAnimal : Animal\n{\n string scientificName = SpeciesFor<TAnimal>.Instance.ScientificName;\n Console.WriteLine($"Let\'s catch some {scientificName}");\n \xe2\x80\xa6\n}\nRun Code Online (Sandbox Code Playgroud)\n当然我们有两个问题需要解决:
\nAnimal提供特定的实例?SpeciesFor<TAnimal>SpeciesFor<TAnimal>.Instance检索此信息?解决1的方法如下:
\npublic abstract class Animal<TSelf> where TSelf : Animal<TSelf>\n{\n private Animal(\xe2\x80\xa6) {}\n \n public abstract class OfSpecies<TSpecies> : Animal<TSelf>\n where TSpecies : SpeciesFor<TSelf>, new()\n {\n protected OfSpecies(\xe2\x80\xa6) : base(\xe2\x80\xa6) { }\n }\n \n \xe2\x80\xa6\n}\nRun Code Online (Sandbox Code Playgroud)\n通过将构造函数设置为Animal<TSelf>private,我们可以确保它的所有子类也是内部类的子类Animal<TSelf>.OfSpecies<TSpecies>。所以这些子类必须指定一个TSpecies有new()边界的类型。
对于2,我们可以提供以下实现:
\npublic abstract class SpeciesFor<TAnimal> where TAnimal : Animal<TAnimal>\n{\n private static SpeciesFor<TAnimal> _instance;\n\n public static SpeciesFor<TAnimal> Instance => _instance ??= MakeInstance();\n\n private static SpeciesFor<TAnimal> MakeInstance()\n {\n Type t = typeof(TAnimal);\n while (true)\n {\n if (t.IsConstructedGenericType\n && t.GetGenericTypeDefinition() == typeof(Animal<>.OfSpecies<>))\n return (SpeciesFor<TAnimal>)Activator.CreateInstance(t.GenericTypeArguments[1]);\n t = t.BaseType;\n if (t == null)\n throw new InvalidProgramException();\n }\n }\n\n // abstract "static" members\n\n public abstract string ScientificName { get; }\n \n \xe2\x80\xa6\n}\nRun Code Online (Sandbox Code Playgroud)\n我们怎么知道里面的反射代码MakeInstance()永远不会抛出异常?正如我们已经说过的, 层次结构中的几乎所有类Animal<TSelf>也是 的子类Animal<TSelf>.OfSpecies<TSpecies>。所以我们知道对于这些类TSpecies必须提供特定的。由于约束,这种类型也必然是可构造的: new()。但这仍然遗漏了像Animal<Something>没有相关物种的抽象类型。现在我们可以说服自己,奇怪的重复模板模式 where TAnimal : Animal<TAnimal>使得无法编写,SpeciesFor<Animal<Something>>.Instance因为 typeAnimal<Something>永远不是 的子类型Animal<Animal<Something>>。
等等\xc3\xa0:
\npublic class CatSpecies : SpeciesFor<Cat>\n{\n // overriden "static" members\n\n public override string ScientificName => "Felis catus";\n public override Cat CreateInVivoFromDnaTrappedInAmber() { \xe2\x80\xa6 }\n public override Cat Clone(Cat a) { \xe2\x80\xa6 }\n public override Cat Breed(Cat a1, Cat a2) { \xe2\x80\xa6 }\n}\n\npublic class Cat : Animal<Cat>.OfSpecies<CatSpecies>\n{\n // overriden members\n\n public override string CuteName { get { \xe2\x80\xa6 } }\n}\n\npublic class DogSpecies : SpeciesFor<Dog>\n{\n // overriden "static" members\n\n public override string ScientificName => "Canis lupus familiaris";\n public override Dog CreateInVivoFromDnaTrappedInAmber() { \xe2\x80\xa6 }\n public override Dog Clone(Dog a) { \xe2\x80\xa6 }\n public override Dog Breed(Dog a1, Dog a2) { \xe2\x80\xa6 }\n}\n\npublic class Dog : Animal<Dog>.OfSpecies<DogSpecies>\n{\n // overriden members\n\n public override string CuteName { get { \xe2\x80\xa6 } }\n}\n\npublic class Program\n{\n public static void Main()\n {\n ConductCrazyScientificExperimentsWith<Cat>();\n ConductCrazyScientificExperimentsWith<Dog>();\n ConductCrazyScientificExperimentsWith<Tyranosaurus>();\n ConductCrazyScientificExperimentsWith<Wyvern>();\n }\n \n public static void ConductCrazyScientificExperimentsWith<TAnimal>()\n where TAnimal : Animal<TAnimal>\n {\n // Look Ma! No animal instance polymorphism!\n \n TAnimal a2039 = SpeciesFor<TAnimal>.Instance.CreateInVivoFromDnaTrappedInAmber();\n TAnimal a2988 = SpeciesFor<TAnimal>.Instance.CreateInVivoFromDnaTrappedInAmber();\n TAnimal a0400 = SpeciesFor<TAnimal>.Instance.Clone(a2988);\n TAnimal a9477 = SpeciesFor<TAnimal>.Instance.Breed(a0400, a2039);\n TAnimal a9404 = SpeciesFor<TAnimal>.Instance.Breed(a2988, a9477);\n \n Console.WriteLine(\n "The confederation of mad scientists is happy to announce the birth " +\n $"of {a9404.CuteName}, our new {SpeciesFor<TAnimal>.Instance.ScientificName}.");\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n这种模式的局限性在于(据我所知)不可能以令人满意的方式扩展类层次结构。例如,我们不能引入Mammal与MammalClass同伴关联的中间类。另一个是它不适用于接口中的静态成员,这比抽象类更灵活。
在这种情况下,静态字段和方法肯定需要继承:
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"?
Run Code Online (Sandbox Code Playgroud)
我们实际上覆盖了静态方法(在delphi中),它有点难看,但它可以很好地满足我们的需求.
我们使用它,所以类可以有一个没有类实例的可用对象列表,例如,我们有一个如下所示的方法:
class function AvailableObjects: string; override;
begin
Result := 'Object1, Object2';
end;
Run Code Online (Sandbox Code Playgroud)
它很丑陋但很必要,这样我们可以实例化所需的内容,而不是只是为了搜索可用的对象而实例化所有类.
这是一个简单的示例,但应用程序本身是一个客户端 - 服务器应用程序,它只有一个服务器中的所有类,以及多个不同的客户端,这些客户端可能不需要服务器拥有的所有内容,也永远不需要对象实例.
因此,与为每个客户端安装一个不同的服务器应用程序相比,这更容易维护.
希望这个例子很清楚.