【问题标题】:Abstract method with strongly typed return type具有强类型返回类型的抽象方法
【发布时间】:2013-04-16 19:55:18
【问题描述】:

考虑以下类:

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

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

【问题讨论】:

    标签: c# generics inheritance overriding abstract-class


    【解决方案1】:

    这是协变返回,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();
        }
    }
    

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

    【讨论】:

    • 我就是这么想的。我看不出有任何方法可以指定泛型类型必须实现实际的类 Type。比如where T : this 什么的……
    • 支持它的语言有哪些?
    • Java 支持它。 .Net 没有任何支持,因为 CLR 是根本问题。希望未来的版本会增加支持,但据我了解,需要进行重大更改才能使其正常工作。
    • @Johnny5 - 刚刚更新了我的答案以包含一个也关闭泛型的版本。如果您只需要一级继承,这将很有用。
    • 返回类型协方差是不够的,因为无法强制从 GiveBirth 返回的具体类型与封闭类类型匹配。
    【解决方案2】:

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

    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
    {
    }
    

    【讨论】:

      【解决方案3】:

      你可以这样做,强制Animal&lt;T&gt; 的实现者实现一个Animal&lt;T&gt; 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&lt;Monkey&gt; GiveBirth() 方法,您会看到编译器抱怨并说:

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

      很遗憾,您必须使用SomeKindOfAnimal: Animal&lt;SomeKindOfAnimal&gt; 语法声明类,但也许这对您有用。

      (Also see this thread.)

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

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

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

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

      【讨论】:

      • 然后当有人制作Monkey : Animal&lt;WeirdHuman&gt; 并制作出非常糟糕的婴儿时会发生什么?
      • @Servy :) 好吧,GiveBirth() 方法仍然被限制为只返回一个Monkey,所以这满足了 的要求,这是一种强制覆盖的返回类型的方法GiveBirth 方法,以便它始终返回实际的类类型”(来自 OP)。如果你愿意,你可以说猴子是怪人!
      • 但不限于返回实现接口的类的类型,只要是实现接口的类选择的任何类型。
      • @Servy:是的,是的;类型参数被限制为某种动物,GiveBirth() 返回类型被限制为与类型参数相同,仅此而已。
      【解决方案4】:

      如果您的基类由于各种原因不能是通用的,则此方法可能有用:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-15
        • 1970-01-01
        • 2020-02-24
        • 1970-01-01
        • 2014-04-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多