C#:戻り値の型のオーバーライド


81

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キャストしなくても、プロパティをPooRadioactivePoo例えばながらCatまだの一部である可能性がありAnimal、ユーザーは必ずしも自分の放射性うんちについての意識や介護ではないかもしれないリスト。それが理にかなっていることを願っています...

私が見る限り、コンパイラは少なくともこれを許可していません。だから不可能だと思います。しかし、これに対する解決策として何をお勧めしますか?


2
ジェネリックはどうですか?彼らは助けてくれませんか?
Arnis Lapsa 2009年

3
Cat.Excrement()は、RadioActivePooのインスタンスをPooとして返すだけではいけませんか?あなたは共通のインターフェースを持っています、それを使ってください。(そしてヒステリックな例に感謝します。)
ホームトースト2009年

1
私も例に感謝します:以下の@goodgaiは抽象的なPooを作成することを提案しています-これをプログラマー以外の人にどうやって説明できるのだろうか...
Paolo Tedesco

1
@Konrad:その素晴らしいコメントをただ笑うべきか、それとも実際に指摘すべき非常に恐ろしいコードの臭いであるかどうかを尋ねるべきかどうかは少しわかりません(その場合はそれについて知りたいので: p)
Svish

3
私はほとんど私はこれらの例XDたかおかしいと自分自身に失望しています
Gurgadurgen

回答:


24

この問題にはすでに多くの解決策があることは知っていますが、既存の解決策で抱えていた問題を修正するものを思いついたと思います。

次の理由により、既存のソリューションのいくつかに満足できませんでした。

  • Paolo Tedescoの最初の解決策: CatとDogには共通の基本クラスがありません。
  • Paolo Tedescoの2番目の解決策:少し複雑で読みにくいです。
  • 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 ORCatで何もオーバーライドする必要はありません。使用例を次に示します。

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

これにより、「RadioactivePoo」が2回出力されます。これは、ポリモーフィズムが壊れていないことを示しています。

参考文献

  • 明示的なインターフェイスの実装
  • 新しい修飾子。この単純化されたソリューションでは使用しませんでしたが、より複雑なソリューションで必要になる場合があります。たとえば、BaseAnimalのインターフェイスを作成する場合は、「PooTypeExcrement」のデクレレーションで使用する必要があります。
  • ジェネリック修飾子(共分散)を出力します。繰り返しますが、このソリューションでは使用しませんでしたがMyType<Poo>、IAnimalからの戻りやBaseAnimalからの戻りのようなことMyType<PooType>をしたい場合は、2つの間でキャストできるようにするために使用する必要があります。

6
おい、これは超クールかもしれない。現時点ではこれ以上分析する時間がありませんが、あなたはそれを持っているようです。もしあなたがこれをクラックしたのなら、ハイタッチして共有してくれてありがとう。残念ながら、この「うんち」と「排泄物」のビジネスは大きな気晴らしです。
ニコラスピーターセン2013年

1
関連する問題の解決策を見つけたと思います。メソッドは継承された型を返す必要があります。つまり、「Animal」から継承された「Dog」のメソッドは、AnimalではなくDog(this)を返します。それは拡張メソッドで行われているので、私がそれに到達したときにここで共有するかもしれません。
ニコラスピーターセン2013年

型安全性に関しては、bruce.ExcementとbruceAsAnimal.Excrementの両方がRadioactivePoo型になりますか?
Anestis Kivranoglou 2017年

@AnestisKivranoglouはい、どちらもRadioactivePooタイプになります
rob

1
私は48時間SOを耕してきました、そしてこの答えはちょうどそれをクラックしました。そして、私は...「おい、これはとてもクールかもしれない」のようでした。
マーティン・ハンセン・レノックス

50

一般的な基本クラスはどうですか?

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から継承します(コメントで述べたように、私の最初のソリューションは継承を保持しませんでした)。
マーカーインターフェイスでクラスを明示的にマークする必要がありますが、これは面倒ですが、これによっていくつかのアイデアが得られる可能性があります...

2番目の編集@Svish:コードを変更して、拡張メソッドがiPooProviderから継承するという事実をまったく強制していないことを明示的に示しましたBaseAnimal。「さらに強く型付けされた」とはどういう意味ですか?


同じことを考えていた。
ドクタージョーンズ

9
犬と猫の間のポリモルフィズムを緩めませんか?
almog.ori 2009年

他にもオーバーライドしたいタイプがある場合はどうなりますか?技術的には、大量の型引数になってしまう可能性があります。これは私が迷惑だと思ったかもしれません...しかし、はい、これは解決策です。
Svish

@Svish:それが起こったら、依存性注入フレームワークを使用する時が来たと思います。
ブライアン

StronglyTypedExcrementメソッドは、iPooProviderがBaseAnimalであることをどのように認識しますか?それはただ推測しますか?それとも私には見えないものですか?そのメソッドをさらに強く型付けすることは可能でしょうか?
Svish

32

これはリターンタイプの共分散と呼ばれ、一部の人々の希望にもかかわらず、C#または.NETでは一般的にサポートされていません。

私がすることは、同じ署名を保持しますがENSURE、派生クラスに追加の句を追加して、これがを返すことを確認しますRadioActivePoo。つまり、構文ではできないことを契約で設計するのです。

他の人は偽物を好む代わりにそれすることをます。大丈夫だと思いますが、私は「インフラストラクチャ」のコード行を節約する傾向があります。コードのセマンティクスが十分に明確であれば、私は満足しています。コンパイル時のメカニズムではありませんが、契約による設計でそれを実現できます。

他の回答が示唆しているジェネリックについても同じです。放射性のうんちを返すよりも良い理由でそれらを使用しますが、それは私だけです。


ENSURE句とは何ですか?それはどのように機能しますか?.Netの属性ですか?
Svish

1
.Netでは、.Net 4.0のコードコントラクトを見る前に、ENSURE(x)句を単に「Debug.Assert(x)」と記述します。さらに参照するための例のために参照archive.eiffel.com/doc/manuals/technology/contract/page.htmlバートランド・メイヤー(1994)第11章による本ソフトウェアの構築、第2版、指向またはオブジェクト
ダニエルDaranas

5
「放射性のうんちを返すよりも良い理由でそれらを使用しますが、それは私だけです」私のお気に入りの引用のリストに含まれています:)
ダニエルダラナス2010

9

このオプションもあります(明示的なインターフェイスの実装)

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

基本クラスを使用してCatを実装する機能は失われますが、プラス面では、CatとDogの間のポリモーフィズムが維持されます。

しかし、追加された複雑さがそれだけの価値があるとは思えません。


4

'Excrement'を作成し、 'Excrement'を返すパブリックプロパティを非仮想のままにする保護された仮想メソッドを定義してみませんか。次に、派生クラスは基本クラスの戻り値の型をオーバーライドできます。

次の例では、「Excrement」を非仮想にしますが、派生クラスが適切な「Poo」を提供できるように、プロパティExcrementImplを提供します。派生型は、基本クラスの実装を非表示にすることで、「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; } }
}
}

この例では、これを理解するのが非常に困難になりました。しかし、素晴らしいコードです!
rposky 2012

2

私が間違っている場合は訂正してください。ただし、Pooから継承した場合にRadioActivePooを返すことができるようにするためのポリモルフィズムの要点ではありません。コントラクトは抽象クラスと同じですが、RadioActivePoo()を返すだけです。


2
あなたは完全に正しいですが、彼は余分なキャストといくつかのより強いタイピングを避けたいと思っています。これは主にジェネリックの目的です...
Paolo Tedesco

2

これを試して:

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

ここでの動物インターフェースのポイントは何ですか?それから何も継承しません。
2012

1

ジェネリックや拡張メソッドに依存せず、メソッドを隠す方法を見つけたと思います。ただし、ポリモーフィズムを壊す可能性があるため、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がすでに同様の解決策を投稿していることに気づいていませんでした。少なくとも私のものは基本クラスを変更する必要はありません。
Cybis 2010

あなたの「ソリューション」はDOESそれは本当にソリューションではありませんので、ブレーク多型を。一方、hjbソリューションは実際のソリューションであり、かなりスマートなIMHOです。
greenoldman 2011年

0

RadioactivePooがpooから派生し、ジェネリックを使用する場合に役立つ可能性があります。


0

ご参考までに。これは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


0

あなたの答えは共分散と呼ばれていると思います。

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

0

リターンインターフェイスを使用することもできます。あなたの場合、IPoo。

コメント基本クラスを使用しているため、この場合、ジェネリック型を使用するよりも望ましい方法です。


0

以下は、他のいくつかの回答の最良の側面のいくつかと、必要なタイプのプロパティをCat持つという重要な側面を可能にするテクニックを組み合わせたものですが、それを返すことができるのは、具体的には。ExcrementRadioactivePooPooAnimalBaseCat

呼び出し元は、実装に存在していてもジェネリックを使用する必要はありません。また、特別な名前を付けるために別の名前の関数を呼び出す必要もありません。 Poo

中間クラスAnimalWithSpecialisationsは、Excrementプロパティをシールするためだけに機能SpecialPooし、非公開プロパティを介して、派生戻り値の型のプロパティAnimalWithSpecialPoo<TPoo>を持つ派生クラスに接続し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;
  }

0

C#9は、共変オーバーライドの戻り値の型を提供します。基本的には:あなたは何をしたいだけで動作します


-1

まあ、実際には、継承された戻り値の型とは異なる具象型を返すことが可能です(静的メソッドの場合でも)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パフォーマンスに影響を与えるので、それを避けるようにしてください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.