这个问题已有 12 年历史,但仍需要给出更好的答案。正如 cmets 中很少有人指出的那样,与所有答案所假装的相反,在 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}");
…
}
当然我们有两个问题要解决:
- 我们如何确保
Animal 子类的实现者向该子类提供SpeciesFor<TAnimal> 的特定实例?
-
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> 的子类。所以这些子类必须指定一个TSpecies 类型,它有一个new() 边界。
对于 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 写成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}.");
}
}
这种模式的一个限制是(据我所知)不可能以令人满意的方式扩展类层次结构。例如,我们不能引入与 MammalClass 同伴关联的中介 Mammal 类。另一个是它不适用于比抽象类更灵活的接口中的静态成员。