197

最近我一直在与提供程序合作,我遇到了一个有趣的情况,我想要一个具有抽象静态方法的抽象类。我阅读了一些关于该主题的帖子,这有点道理,但是有一个很好的清晰解释吗?

4

10 回答 10

161

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

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

于 2008-08-06T11:30:07.093 回答
44

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

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

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

于 2008-08-06T11:21:11.667 回答
20

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

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

于 2008-08-09T07:01:57.000 回答
10

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

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"?
于 2010-02-18T05:36:44.097 回答
8

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

于 2008-08-06T11:24:41.957 回答
5

我们实际上覆盖了静态方法(在 delphi 中),它有点难看,但它可以很好地满足我们的需求。

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

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

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

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

因此,这比为每个客户端拥有一个不同的服务器应用程序要容易得多。

希望这个例子很清楚。

于 2008-08-18T19:01:59.430 回答
5

这个问题已有 12 年历史,但仍然需要给出更好的答案。正如评论中很少有人指出的那样,与所有答案所假装的相反,在 C# 中使用静态抽象方法肯定是有意义的。正如哲学家丹尼尔丹尼特所说,想象力的失败并不是对必然性的洞察。没有意识到 C# 不仅仅是一种 OOP 语言,这是一个常见的错误。对给定概念的纯 OOP 视角会导致受限且在当前情况下被误导的检查。多态性不仅仅是对多态性进行细分:它还包括参数多态性(又名泛型编程),而 C# 已经支持这一点很长时间了。在这个额外的范例中,抽象类(和大多数类型)不仅用于为实例提供类型。它们也可以用作泛型参数的界限;多年来,某些语言(例如 Haskell,以及最近的 Scala、Rust 或 Swift)的用户已经理解的东西。

在这种情况下,您可能想要执行以下操作:

void Catch<TAnimal>() where TAnimal : Animal
{
    string scientificName = TAnimal.ScientificName; // abstract static property
    Console.WriteLine($"Let's catch some {scientificName}");
    …
}

在这里,表达可以由子类专门化的静态成员的能力完全有意义

不幸的是,C# 不允许抽象静态成员,但我想提出一种可以很好地模拟它们的模式。这种模式并不完美(它对继承施加了一些限制),但据我所知它是类型安全的。

主要思想是将抽象伴随类(此处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}");
    …
}

当然我们有两个问题需要解决:

  1. 我们如何确保子类的实现者Animal提供该子类的特定实例SpeciesFor<TAnimal>
  2. 该物业如何SpeciesFor<TAnimal>.Instance检索此信息?

这是我们如何解决1:

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>。所以这些子类必须指定一个TSpeciesnew()边界的类型。

对于 2,我们可以提供以下实现:

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,因为 typeAnimal<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关联的中间类。MammalClass另一个是它不适用于比抽象类更灵活的接口中的静态成员。

于 2021-02-05T21:35:56.190 回答
1

使用.NET 6/C# 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()}");
        }
    }
}

资源:

于 2021-10-21T18:10:45.047 回答
0

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

于 2009-05-14T11:11:59.220 回答
0

它目前在 C# 10 中作为预览功能提供。

于 2021-12-22T20:35:58.800 回答