89

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それが理にかなっていることを願っています...

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

4

15 に答える 15

51

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

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。「さらに強く型付けされた」とはどういう意味ですか?

于 2009-06-26T12:35:43.900 に答える
35

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

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

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

このソリューションを使用すると、DogORCatで何もオーバーライドする必要はありません。使用例を次に示します。

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つの間でキャストできるようにするために使用する必要があります。
于 2012-12-05T05:43:17.897 に答える
33

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

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

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

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

于 2009-06-26T12:42:22.123 に答える
10

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

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

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

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

于 2009-06-26T13:21:53.773 に答える
4

「排泄物」を作成する保護された仮想メソッドを定義し、非仮想の「排泄物」を返すパブリック プロパティを保持しないのはなぜですか。その後、派生クラスは基本クラスの戻り値の型をオーバーライドできます。

次の例では、'Excrement' を非仮想にしますが、プロパティ ExcrementImpl を提供して、派生クラスが適切な 'Poo' を提供できるようにします。派生型は、基本クラスの実装を非表示にすることで、'排泄物' の戻り値の型をオーバーライドできます。

元:

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; } }
}
}
于 2009-10-01T12:59:32.783 に答える
2

私が間違っている場合は訂正してくださいが、Pooから継承した場合にRadioActivePooを返すことができるように多形のポイント全体ではありません。コントラクトは抽象クラスと同じですが、RadioActivePoo()を返すだけです。

于 2009-06-26T12:37:10.620 に答える
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(); } 
        }
    }
}
于 2009-06-26T12:53:33.783 に答える
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; } }
}
于 2010-02-20T02:43:48.923 に答える
0

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

于 2009-06-26T12:35:14.023 に答える
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();
    }
}
于 2013-03-27T22:26:56.360 に答える
0

インターフェイスの戻り値を使用するだけです。あなたの場合、IPoo.

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

于 2015-05-28T22:32:09.983 に答える
-1

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

于 2012-12-18T20:52:28.283 に答える