【问题标题】:C#: Overriding return typesC#:覆盖返回类型
【发布时间】:2010-11-06 03:21:56
【问题描述】:

有没有办法覆盖 C# 中的返回类型?如果是,如何,如果不是,为什么以及推荐的方法是什么?

我的情况是我有一个带有抽象基类及其后代的接口。我想这样做(不是真的,但作为一个例子!):

public interface Animal
{
   Poo Excrement { get; }
}

public class AnimalBase
{
   public virtual Poo Excrement { get { return new Poo(); } }
}

public class Dog
{
  // No override, just return normal poo like normal animal
}

public class Cat
{
  public override RadioactivePoo Excrement { get { return new RadioActivePoo(); } }
}

RadioactivePoo当然继承自Poo

我想要这样做的原因是,那些使用Cat 对象的人可以使用Excrement 属性,而不必将Poo 转换为RadioactivePoo,而例如Cat 仍然可以是Animal 列出用户可能不一定知道或关心他们的放射性便便的地方。希望这是有道理的......

据我所知,编译器至少不允许这样做。所以我想这是不可能的。但是你会推荐什么来解决这个问题?

【问题讨论】:

  • 泛型呢?他们不会帮忙吗?
  • 不应该 Cat.Excrement() 只是将 RadioActivePoo 的实例返回为便便吗?你有一个通用的界面,使用它。 (感谢歇斯底里的例子。)
  • 我也想对这个例子表示感谢:下面的@goodgai 建议创建抽象便便 - 我想知道如何向非程序员解释这个......
  • @Konrad:有点不确定我是否应该只是嘲笑那个精彩的评论,或者我是否还应该问一下它是否实际上是一种非常可怕的代码气味,应该是指出(因为在那种情况下我想知道它:p)
  • 我对自己发现这些例子有多有趣感到几乎失望 XD

标签: c# inheritance types covariance overriding


【解决方案1】:

如果 RadioactivePoo 从便便派生然后使用泛型可能会有所帮助。

【讨论】:

    【解决方案2】:

    泛型基类呢?

    public class Poo { }
    public class RadioactivePoo : Poo { }
    
    public class BaseAnimal<PooType> 
        where PooType : Poo, new() {
        PooType Excrement {
            get { return new PooType(); }
        }
    }
    
    public class Dog : BaseAnimal<Poo> { }
    public class Cat : BaseAnimal<RadioactivePoo> { }
    

    编辑:一种新的解决方案,使用扩展方法和标记接口...

    public class Poo { }
    public class RadioactivePoo : Poo { }
    
    // just a marker interface, to get the poo type
    public interface IPooProvider<PooType> { }
    
    // Extension method to get the correct type of excrement
    public static class IPooProviderExtension {
        public static PooType StronglyTypedExcrement<PooType>(
            this IPooProvider<PooType> iPooProvider) 
            where PooType : Poo {
            BaseAnimal animal = iPooProvider as BaseAnimal;
            if (null == animal) {
                throw new InvalidArgumentException("iPooProvider must be a BaseAnimal.");
            }
            return (PooType)animal.Excrement;
        }
    }
    
    public class BaseAnimal {
        public virtual Poo Excrement {
            get { return new Poo(); }
        }
    }
    
    public class Dog : BaseAnimal, IPooProvider<Poo> { }
    
    public class Cat : BaseAnimal, IPooProvider<RadioactivePoo> {
        public override Poo Excrement {
            get { return new RadioactivePoo(); }
        }
    }
    
    class Program { 
        static void Main(string[] args) {
            Dog dog = new Dog();
            Poo dogPoo = dog.Excrement;
    
            Cat cat = new Cat();
            RadioactivePoo catPoo = cat.StronglyTypedExcrement();
        }
    }
    

    这种方式 Dog 和 Cat 都从 Animal 继承(如 cmets 中所述,我的第一个解决方案没有保留继承)。
    必须用标记接口显式标记类,这很痛苦,但也许这可以给你一些想法......

    第二次编辑 @Svish:我修改了代码以明确表明扩展方法没有以任何方式强制执行iPooProvider 继承自BaseAnimal 的事实。你说的“更强大的类型”是什么意思?

    【讨论】:

    • 只是在想同样的事情。
    • 你对 Dog 和 Cat 之间的多态性不了解吗?
    • 如果您还想覆盖其他类型怎么办?从技术上讲,您最终可能会得到一大堆类型参数。我想我可能觉得这很烦人......但是,这是一个解决方案。
    • @Svish:我想一旦发生这种情况,是时候使用依赖注入框架了。
    • StronglyTypedExcrement 方法如何知道 iPooProvider 是 BaseAnimal?它只是猜测吗?还是我看不到的东西?是否有可能使该方法的类型更强?
    【解决方案3】:

    如果我错了,请纠正我,但如果它继承自 Poo,多态性的全部意义不是能够返回 RadioActivePoo,合同将与抽象类相同,但只返回 RadioActivePoo()

    【讨论】:

    • 你是完全正确的,但他想避免额外的演员和一些更强的打字,这主要是泛型的用途......
    【解决方案4】:

    这称为返回类型协方差,尽管有些人的wishes,但通常在 C# 或 .NET 中不受支持。

    我要做的是保持相同的签名,但在派生类中添加一个额外的ENSURE 子句,我确保这个子句返回RadioActivePoo。所以,简而言之,我会通过合同设计来做我不能通过语法做的事情。

    其他人更喜欢fake 它。没关系,我想,但我倾向于节省“基础设施”代码行。如果代码的语义足够清晰,我很高兴,并且通过契约设计让我实现了这一点,尽管它不是编译时机制。

    other answers 建议的泛型也是如此。我会出于更好的理由使用它们,而不是仅仅返回放射性便便 - 但这就是我。

    【讨论】:

    • 什么是 ENSURE 子句?那将如何运作?它是 .Net 中的属性吗?
    • 在 .Net 中,在看到 .Net 4.0 的代码合同之前,我将 ENSURE(x) 子句简单地编写为“Debug.Assert(x)”。如需更多参考资料,请参阅 archive.eiffel.com/doc/manuals/technology/contract/page.html 或面向对象的软件构造,第 2 版,Bertrand Meyer (1994) 第 11 章。
    • “我会使用它们而不是仅仅返回放射性便便 - 但这只是我”属于我最喜欢的自己的报价列表:)
    【解决方案5】:

    试试这个:

    namespace ClassLibrary1
    {
        public interface Animal
        {   
            Poo Excrement { get; }
        }
    
        public class Poo
        {
        }
    
        public class RadioactivePoo
        {
        }
    
        public class AnimalBase<T>
        {   
            public virtual T Excrement
            { 
                get { return default(T); } 
            }
        }
    
    
        public class Dog : AnimalBase<Poo>
        {  
            // No override, just return normal poo like normal animal
        }
    
        public class Cat : AnimalBase<RadioactivePoo>
        {  
            public override RadioactivePoo Excrement 
            {
                get { return new RadioactivePoo(); } 
            }
        }
    }
    

    【讨论】:

    • 这里的 Animal 接口有什么意义?没有任何东西继承自它。
    【解决方案6】:

    还有这个选项(显式接口实现)

    public class Cat:Animal
    {
      Poo Animal.Excrement { get { return Excrement; } }
      public RadioactivePoo Excrement { get { return new RadioactivePoo(); } }
    }
    

    您失去了使用基类来实现 Cat 的能力,但从好的方面来说,您保留了 Cat 和 Dog 之间的多态性。

    但我怀疑增加的复杂性是否值得。

    【讨论】:

      【解决方案7】:

      为什么不定义一个受保护的虚拟方法来创建“排泄物”并保持返回“排泄物”的公共属性非虚拟。然后派生类可以覆盖基类的返回类型。

      在下面的示例中,我将“Excrement”设为非虚拟,但提供了 ExcrementImpl 属性以允许派生类提供正确的“Poo”。然后,派生类型可以通过隐藏基类实现来覆盖“Excrement”的返回类型。

      例如:

      namepace ConsoleApplication8
      
      {
      public class Poo { }
      
      public class RadioactivePoo : Poo { }
      
      public interface Animal
      {
          Poo Excrement { get; }
      }
      
      public class AnimalBase
      {
          public Poo Excrement { get { return ExcrementImpl; } }
      
          protected virtual Poo ExcrementImpl
          {
              get { return new Poo(); }
          }
      }
      
      public class Dog : AnimalBase
      {
          // No override, just return normal poo like normal animal
      }
      
      public class Cat : AnimalBase
      {
          protected override Poo ExcrementImpl
          {
              get { return new RadioactivePoo(); }
          }
      
          public new RadioactivePoo Excrement { get { return (RadioactivePoo)ExcrementImpl; } }
      }
      }
      

      【讨论】:

      • 这个例子让这变得更加难以理解。但是代码很棒!
      【解决方案8】:

      我想我找到了一种方法,它不依赖于泛型或扩展方法,而是方法隐藏。但是,它可能会破坏多态性,因此如果您进一步从 Cat 继承,请特别小心。

      我希望这篇文章仍然可以帮助某人,尽管晚了 8 个月。

      public interface Animal
      {
          Poo Excrement { get; }
      }
      
      public class Poo
      {
      }
      
      public class RadioActivePoo : Poo
      {
      }
      
      public class AnimalBase : Animal
      {
          public virtual Poo Excrement { get { return new Poo(); } }
      }
      
      public class Dog : AnimalBase
      {
          // No override, just return normal poo like normal animal
      }
      
      public class CatBase : AnimalBase
      {
          public override Poo Excrement { get { return new RadioActivePoo(); } }
      }
      
      public class Cat : CatBase
      {
          public new RadioActivePoo Excrement { get { return (RadioActivePoo) base.Excrement; } }
      }
      

      【讨论】:

      • 哎呀,没关系。我没有意识到 hjb417 已经发布了类似的解决方案。至少我的不需要修改基类。
      • 你的“解决方案”确实破坏了多态性,所以它不是真正的解决方案。另一方面,hjb 解决方案是真正的解决方案,而且非常聪明,恕我直言。
      【解决方案9】:

      我知道这个问题已经有很多解决方案,但我想我已经想出了一个解决我在现有解决方案中遇到的问题。

      我对现有的一些解决方案不满意,原因如下:

      • Paolo Tedesco 的第一个解决方案: Cat 和 Dog 没有共同的基类。
      • Paolo Tedesco 的第二个解决方案:有点复杂,难以阅读。
      • Daniel Daranas 的解决方案:这行得通,但它会用大量不必要的强制转换和 Debug.Assert() 语句弄乱你的代码。
      • hjb417 的解决方案:此解决方案不允许您将逻辑保留在基类中。此示例中的逻辑非常简单(调用构造函数),但在实际示例中并非如此。

      我的解决方案

      这个解决方案应该通过使用泛型和方法隐藏来克服我上面提到的所有问题。

      public class Poo { }
      public class RadioactivePoo : Poo { }
      
      interface IAnimal
      {
          Poo Excrement { get; }
      }
      
      public class BaseAnimal<PooType> : IAnimal
          where PooType : Poo, new()
      {
          Poo IAnimal.Excrement { get { return (Poo)this.Excrement; } }
      
          public PooType Excrement
          {
              get { return new PooType(); }
          }
      }
      
      public class Dog : BaseAnimal<Poo> { }
      public class Cat : BaseAnimal<RadioactivePoo> { }
      

      使用此解决方案,您无需覆盖 Dog OR Cat 中的任何内容!以下是一些示例用法:

      Cat bruce = new Cat();
      IAnimal bruceAsAnimal = bruce as IAnimal;
      Console.WriteLine(bruce.Excrement.ToString());
      Console.WriteLine(bruceAsAnimal.Excrement.ToString());
      

      这将输出:“RadioactivePoo”两次,表明多态性没有被破坏。

      进一步阅读

      • Explicit Interface Implementation
      • new Modifier。我没有在这个简化的解决方案中使用它,但您可能需要在更复杂的解决方案中使用它。例如,如果您想为 BaseAnimal 创建一个接口,则需要在“PooType Excrement”的声明中使用它。
      • out Generic Modifier (Covariance)。同样,我没有在这个解决方案中使用它,但如果你想做一些事情,比如从 IAnimal 中返回 MyType&lt;Poo&gt; 并从 BaseAnimal 中返回 MyType&lt;PooType&gt;,那么你需要使用它才能在两者之间进行转换。

      【讨论】:

      • 伙计,这可能非常酷。我目前没有时间进一步分析,但看起来你可能已经明白了,如果你真的破解了这个,高五,谢谢分享。不幸的是,这种“便便”和“排泄物”业务会让人分心。
      • 我想我找到了一个相关问题的解决方案,其中一个方法必须返回继承的类型——即从 'Animal' 继承的 'Dog' 中的方法仍然返回 Dog (this) ,而不是动物。它使用扩展方法完成,我可能会在这里分享。
      • 关于类型安全性,bruce.Excement 和 bruceAsAnimal.Excrement 的类型都是 RadioactivePoo ??
      • @AnestisKivranoglou 是的,两者都是 RadioactivePoo 类型
      • 我已经耕耘了 48 小时,而这个答案刚刚破解了它。我当时想……“伙计,这可能太酷了。”
      【解决方案10】:

      仅供参考。这在 Scala 中很容易实现。

      trait Path
      
      trait Resource
      {
          def copyTo(p: Path): Resource
      }
      class File extends Resource
      {
          override def copyTo(p: Path): File = new File
          override def toString = "File"
      }
      class Directory extends Resource
      {
          override def copyTo(p: Path): Directory = new Directory
          override def toString = "Directory"
      }
      
      val test: Resource = new Directory()
      test.copyTo(null)
      

      这是一个你可以玩的活生生的例子:http://www.scalakata.com/50d0d6e7e4b0a825d655e832

      【讨论】:

        【解决方案11】:

        我相信你的答案叫做协方差。

        class Program
        {
            public class Poo
            {
                public virtual string Name { get{ return "Poo"; } }
            }
        
            public class RadioactivePoo : Poo
            {
                public override string Name { get { return "RadioactivePoo"; } }
                public string DecayPeriod { get { return "Long time"; } }
            }
        
            public interface IAnimal<out T> where T : Poo
            {
                T Excrement { get; }
            }
        
            public class Animal<T>:IAnimal<T> where T : Poo 
            {
                public T Excrement { get { return _excrement ?? (_excrement = (T) Activator.CreateInstance(typeof (T), new object[] {})); } } 
                private T _excrement;
            }
        
            public class Dog : Animal<Poo>{}
            public class Cat : Animal<RadioactivePoo>{}
        
            static void Main(string[] args)
            {
                var dog = new Dog();
                var cat = new Cat();
        
                IAnimal<Poo> animal1 = dog;
                IAnimal<Poo> animal2 = cat;
        
                Poo dogPoo = dog.Excrement;
                //RadioactivePoo dogPoo2 = dog.Excrement; // Error, dog poo is not RadioactivePoo.
        
                Poo catPoo = cat.Excrement;
                RadioactivePoo catPoo2 = cat.Excrement;
        
                Poo animal1Poo = animal1.Excrement;
                Poo animal2Poo = animal2.Excrement;
                //RadioactivePoo animal2RadioactivePoo = animal2.Excrement; // Error, IAnimal<Poo> reference do not know better.
        
        
                Console.WriteLine("Dog poo name: {0}",dogPoo.Name);
                Console.WriteLine("Cat poo name: {0}, decay period: {1}" ,catPoo.Name, catPoo2.DecayPeriod);
                Console.WriteLine("Press any key");
        
                var key = Console.ReadKey();
            }
        }
        

        【讨论】:

          【解决方案12】:

          您可以只使用返回一个接口。在你的情况下,IPoo。

          在您的情况下,这比使用泛型类型更可取,因为您使用的是注释基类。

          【讨论】:

            【解决方案13】:

            嗯,实际上可以返回一个与继承的返回类型不同的具体类型(即使对于静态方法),这要感谢dynamic

            public abstract class DynamicBaseClass
            {
                public static dynamic Get (int id) { throw new NotImplementedException(); }
            }
            
            public abstract class BaseClass : DynamicBaseClass
            {
                public static new BaseClass Get (int id) { return new BaseClass(id); }
            }
            
            public abstract class DefinitiveClass : BaseClass
            {
                public static new DefinitiveClass Get (int id) { return new DefinitiveClass(id);
            }
            
            public class Test
            {
                public static void Main()
                {
                    var testBase = BaseClass.Get(5);
                    // No cast required, IntelliSense will even tell you
                    // that var is of type DefinitiveClass
                    var testDefinitive = DefinitiveClass.Get(10);
                }
            }
            

            我在为我的公司编写的 API 包装器中实现了这一点。如果您计划开发 API,这有可能在某些用例中提高可用性和开发体验。不过dynamic的使用对性能有影响,尽量避免。

            【讨论】:

              【解决方案14】:

              以下结合了其他几个答案的一些最佳方面以及一种技术,以允许 Cat 的关键方面具有所需 RadioactivePoo 类型的 Excrement 属性,但能够将其返回为仅仅Poo,如果我们只知道我们有一个AnimalBase,而不是专门的Cat

              调用者不需要使用泛型,即使它们存在于实现中,也不需要调用不同名称的函数来获得特殊的Poo

              中间类AnimalWithSpecialisations 仅用于密封Excrement 属性,通过非公共SpecialPoo 属性将其连接到派生类AnimalWithSpecialPoo&lt;TPoo&gt;,该派生类具有派生返回类型的Excrement 属性。

              如果Cat 是唯一一个其Poo 在任何方面都是特殊的动物,或者我们不希望Excrement 的类型成为Cat 的主要定义特征,则中间泛型类可以在层次结构中被跳过,因此Cat 直接派生自AnimalWithSpecialisations,但如果有几种不同的动物,其主要特征是它们的Poo 在某种程度上是特殊的,将“样板”分离到中间类会有所帮助保持Cat 类本身相当干净,尽管代价是一些额外的虚函数调用。

              示例代码显示大多数预期操作“按预期”工作。

              public interface IExcretePoo<out TPoo>
                where TPoo : Poo
              {
                TPoo Excrement { get; }
              }
              
              public class Poo
              { }
              
              public class RadioactivePoo : Poo
              { }
              
              public class AnimalBase : IExcretePoo<Poo>
              {
                public virtual Poo Excrement { get { return new Poo(); } }
              }
              
              public class Dog : AnimalBase
              {
                // No override, just return normal poo like normal animal
              }
              
              public abstract class AnimalWithSpecialisations : AnimalBase
              {
                // this class connects AnimalBase to AnimalWithSpecialPoo<TPoo>
                public sealed override Poo Excrement { get { return SpecialPoo; } }
              
                // if not overridden, our "special" poo turns out just to be normal animal poo...
                protected virtual Poo SpecialPoo { get { return base.Excrement; } }
              }
              
              public abstract class AnimalWithSpecialPoo<TPoo> : AnimalWithSpecialisations, IExcretePoo<TPoo>
                where TPoo : Poo
              {
                sealed protected override Poo SpecialPoo { get { return Excrement; } }
                public new abstract TPoo Excrement { get; }
              }
              
              public class Cat : AnimalWithSpecialPoo<RadioactivePoo>
              {
                public override RadioactivePoo Excrement { get { return new RadioactivePoo(); } }
              }
              
              class Program
              {
                static void Main(string[] args)
                {
                  Dog dog = new Dog();
                  Poo dogPoo = dog.Excrement;
              
                  Cat cat = new Cat();
                  RadioactivePoo catPoo = cat.Excrement;
              
                  AnimalBase animal = cat;
              
                  Poo animalPoo = catPoo;
                  animalPoo = animal.Excrement;
              
                  AnimalWithSpecialPoo<RadioactivePoo> radioactivePooingAnimal = cat;
                  RadioactivePoo radioactivePoo = radioactivePooingAnimal.Excrement;
              
                  IExcretePoo<Poo> pooExcreter = cat; // through this interface we don't know the Poo was radioactive.
                  IExcretePoo<RadioactivePoo> radioactivePooExcreter = cat; // through this interface we do.
              
                  // we can replace these with the dog equivalents:
                  animal = dog;
                  animalPoo = dogPoo;
                  pooExcreter = dog;
              
                  // but we can't do:
                  // radioactivePooExcreter = dog;
                  // radioactivePooingAnimal = dog;
                  // radioactivePoo = dogPoo;
                }
              

              【讨论】:

                【解决方案15】:

                C#9 为我们提供了协变覆盖返回类型。基本上:你想要的就行了

                【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2017-03-20
                • 2013-12-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-06-06
                相关资源
                最近更新 更多