7

考虑以下类:

public abstract class Animal
{
    public abstract Animal GiveBirth();
}

public class Monkey : Animal
{
    public override Animal GiveBirth()
    {
        return new Monkey();
    }
}

public class Snake : Animal
{
    public override Animal GiveBirth()
    {
        return new Snake();
    }
}

//That one doesnt makes sense.
public class WeirdHuman: Animal
{
    public override Animal GiveBirth()
    {
        return new Monkey();
    }
}

我正在寻找一种方法来强制执行被覆盖GiveBirth方法的返回类型,以便它始终返回实际的类类型,这样就不会WeirdHuman产生Monkey.

我觉得答案是关于泛型类型,但我不知道我该怎么做。

预期结果示例:

public abstract class Animal
{
    public abstract /*here a way to specify concrete type*/ GiveBirth();
}

public class Monkey : Animal
{
    public override Monkey GiveBirth() //Must returns an actual Monkey
    {
        return new Monkey();
    }
}

如果解释清楚,“绝对不可能”可能是一个答案。

4

4 回答 4

6

这是协变返回,C# 不支持。我每天都在感叹这件事。最好的解决方法是使用泛型返回类型并在泛型类型上指定 where 条件,但这也可能导致您在未来遇到其他具有匹配泛型参数要求的问题。

public abstract class Animal<TBirthType> where TBirthType : Animal<TBirthType>
{
    public abstract TBirthType GiveBirth();
}

public class Monkey<TBirthType> : Animal<TBirthType> where TBirthType : Monkey<TBirthType>
{
    public override TBirthType GiveBirth()
    {
        return new Monkey<Monkey>();
    }
}

或者,如果您不需要任何进一步的继承,您可以关闭泛型。

public class Monkey : Animal<Monkey>
{
    public override Monkey GiveBirth()
    {
        return new Monkey();
    }
}

请注意,单独的协方差仍然不足以确保不会形成行为不端的派生类型,但它允许将返回的类型指定为正在使用的类型。尽管如此,仍然没有办法将其锁定在抽象类之外。您也许可以通过在基本级别实现的方法的反射来管理运行时检查,该方法将在运行时检查类型,但这也可能非常混乱。

于 2013-04-23T14:37:49.077 回答
3

你可以做这样的事情,它强制实现者Animal<T>实现一个Animal<T> GiveBirth()返回与类型参数相同类型的方法,它本身被限制为一种动物。

这不是您想要的,但您可以看到:

public abstract class Animal<T> where T: Animal<T>
{
    public abstract Animal<T> GiveBirth();
}

public class Monkey: Animal<Monkey>
{
    public override Animal<Monkey> GiveBirth()
    {
        return new Monkey();
    }
}

public class Snake: Animal<Snake>
{
    public override Animal<Snake> GiveBirth()
    {
        return new Snake();
    }
}

public class WeirdHuman: Animal<WeirdHuman>
{
    public override Animal<WeirdHuman> GiveBirth()
    {
        return new Monkey(); // Won't compile of course.
    }
}

如果你注释掉这些public override Animal<Monkey> GiveBirth()方法,你会看到编译器抱怨并说:

错误 1 ​​'ConsoleApplication1.Monkey' 没有实现继承的抽象成员 'ConsoleApplication1.Animal.GiveBirth()'

不幸的是,您必须使用SomeKindOfAnimal: Animal<SomeKindOfAnimal>语法声明类,但也许这对您有用。

另见此线程。

唉,这不太行,因为它允许你这样做:

public class Monkey: Animal<WeirdHuman>
{
    public override Animal<WeirdHuman> GiveBirth()
    {
        return new WeirdHuman();
    }
}

换句话说,它把类型参数约束为一种动物,同时也约束返回类型GiveBirth()与类型参数相同;但仅此而已。在某些情况下,这已经足够了,但可能不适合您的目的。

不过,也许这种方法值得了解。

于 2013-04-23T14:46:56.957 回答
3

据我所知,没有干净的方法可以纯粹在单个类层次结构中支持这一点。使用重复的泛型类型参数,例如

public class Animal<T> where T : Animal<T> { }

如果您控制整个层次结构可能是可以接受的,因此可以排除像

public class WierdHuman<Monkey> { }

您真正想要的是类似于 Haskell 的类型类,您可以在其中抽象类本身的具体类型。在 C# 中最接近的方法是定义一个实现所需功能的代理对象,然后将其传递到您需要的任何地方。

在您的情况下,这意味着创建一个用于分娩的接口,并为每种具体的动物类型实现它。

然后,需要此功能的方法需要“类型类实例”的额外参数。这些方法可以将通用动物类型限制为相同:

public interface ISpawn<T> where T : Animal
{
    public T GiveBirth();
}

public void Populate<T>(T parent, ISpawn<T> spawn) where T : Animal
{
}
于 2013-04-23T15:04:35.357 回答
0

如果您的基类由于各种原因而无法泛型,则此方法可能很有用:

abstract class Animal {
}
interface ICanGiveBirth<T> {
   T GiveBirth();
}
static class CanGiveBirthHelper {
   public static T GiveBirth<T>(this T v) where T: ICanGiveBirth<T> => v.GiveBirth();
}
class Monkey : Animal, ICanGiveBirth<Monkey> {
   public Monkey GiveBirth() {
      throw new NotImplementedException();
   }
}
class Snake : Animal, ICanGiveBirth<Snake> {
   public Snake GiveBirth() {
      throw new NotImplementedException();
   }
}

如果您无法向子类添加接口,并且仍然无法向 Base 类型添加泛型,则此方法可能很有用:(不幸的是,您无法使 GiveBirthImpl 受保护,因为不允许辅助类位于基类中)

abstract class Animal {
   public abstract T GiveBirthImpl<T>() where T:Animal;
}
static class CanGiveBirthHelper {
   public static T GiveBirth<T>(this T v) where T: Animal => v.GiveBirthImpl<T>();
}
class Monkey : Animal {
   public override T GiveBirthImpl<T>() {
      throw new NotImplementedException();
   }
}
class Snake : Animal {
   public override T GiveBirthImpl<T>() {
      throw new NotImplementedException();
   }
}

在这两种情况下,这都会按预期工作:

class Tester
{
   Monkey TestIt() => new Monkey().GiveBirth();
}
于 2021-08-05T08:52:42.090 回答