我最近一直在使用提供者,我遇到了一个有趣的情况,我想有一个抽象类,它有一个抽象静态方法。我读了一些关于这个话题的帖子,它有点说得通,但有一个清晰的解释吗?


当前回答

This question is 12 years old but it still needs to be given a better answer. As few noted in the comments and contrarily to what all answers pretend it would certainly make sense to have static abstract methods in C#. As philosopher Daniel Dennett put it, a failure of imagination is not an insight into necessity. There is a common mistake in not realizing that C# is not only an OOP language. A pure OOP perspective on a given concept leads to a restricted and in the current case misguided examination. Polymorphism is not only about subtying polymorphism: it also includes parametric polymorphism (aka generic programming) and C# has been supporting this for a long time now. Within this additional paradigm, abstract classes (and most types) are not only used to provide a type to instances. They can also be used as bounds for generic parameters; something that has been understood by users of certain languages (like for example Haskell, but also more recently Scala, Rust or Swift) for years.

在这种情况下,你可能想这样做:

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}");
    …
}

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

如何确保Animal的子类的实现者为这个子类提供了SpeciesFor<TAnimal>的特定实例? 属性SpeciesFor<TAnimal>。实例检索此信息?

下面是我们如何解决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>的构造函数设为private,我们可以确保它的所有子类也是内部类Animal<TSelf>. ofspecies <TSpecies>的子类。因此,这些子类必须指定具有new()边界的TSpecies类型。

对于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; }
    
    …
}

How do we know that the reflection code inside MakeInstance() never throws? As we've already said, almost all classes within the hierarchy of Animal<TSelf> are also subclasses of Animal<TSelf>.OfSpecies<TSpecies>. So we know that for these classes a specific TSpecies must be provided. This type is also necessarily constructible thanks to constraint : new(). But this still leaves out abstract types like Animal<Something> that have no associated species. Now we can convince ourself that the curiously recurring template pattern where TAnimal : Animal<TAnimal> makes it impossible to write SpeciesFor<Animal<Something>>.Instance as type Animal<Something> is never a subtype of 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}.");
    }
}

这种模式的一个限制是不可能(据我所知)以令人满意的方式扩展类层次结构。例如,我们不能引入与MammalClass伙伴相关联的中介哺乳类。另一个原因是它不适用于接口中的静态成员,而接口比抽象类更灵活。

其他回答

This question is 12 years old but it still needs to be given a better answer. As few noted in the comments and contrarily to what all answers pretend it would certainly make sense to have static abstract methods in C#. As philosopher Daniel Dennett put it, a failure of imagination is not an insight into necessity. There is a common mistake in not realizing that C# is not only an OOP language. A pure OOP perspective on a given concept leads to a restricted and in the current case misguided examination. Polymorphism is not only about subtying polymorphism: it also includes parametric polymorphism (aka generic programming) and C# has been supporting this for a long time now. Within this additional paradigm, abstract classes (and most types) are not only used to provide a type to instances. They can also be used as bounds for generic parameters; something that has been understood by users of certain languages (like for example Haskell, but also more recently Scala, Rust or Swift) for years.

在这种情况下,你可能想这样做:

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}");
    …
}

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

如何确保Animal的子类的实现者为这个子类提供了SpeciesFor<TAnimal>的特定实例? 属性SpeciesFor<TAnimal>。实例检索此信息?

下面是我们如何解决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>的构造函数设为private,我们可以确保它的所有子类也是内部类Animal<TSelf>. ofspecies <TSpecies>的子类。因此,这些子类必须指定具有new()边界的TSpecies类型。

对于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; }
    
    …
}

How do we know that the reflection code inside MakeInstance() never throws? As we've already said, almost all classes within the hierarchy of Animal<TSelf> are also subclasses of Animal<TSelf>.OfSpecies<TSpecies>. So we know that for these classes a specific TSpecies must be provided. This type is also necessarily constructible thanks to constraint : new(). But this still leaves out abstract types like Animal<Something> that have no associated species. Now we can convince ourself that the curiously recurring template pattern where TAnimal : Animal<TAnimal> makes it impossible to write SpeciesFor<Animal<Something>>.Instance as type Animal<Something> is never a subtype of 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}.");
    }
}

这种模式的一个限制是不可能(据我所知)以令人满意的方式扩展类层次结构。例如,我们不能引入与MammalClass伙伴相关联的中介哺乳类。另一个原因是它不适用于接口中的静态成员,而接口比抽象类更灵活。

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

它目前作为预览特性在c# 10中可用。

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

在。net 6 / c# 10/next/preview中,你可以通过“接口中的静态抽象成员”来做到这一点。

(在编写代码时编译成功,但一些ide有突出显示代码的问题)

SharpLab演示

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()}");
        }
    }
}

由于这是运行时中的一个主要更改,因此产生的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
}

资源:

https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/static-abstract-interface-methods https://github.com/dotnet/csharplang/issues/4436