web-dev-qa-db-ja.com

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

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オブジェクトを使用する人がExcrementPooにキャストしなくてもRadioactivePooプロパティを使用できるようにするためです。 Catは、Animalリストの一部である可能性があり、ユーザーは必ずしも放射性うんちを認識または気にかけているとは限りません。理にかなったことを願っています...

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

73
Svish

ジェネリック基本クラスはどうですか?

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

[〜#〜] edit [〜#〜]:拡張メソッドとマーカーインターフェイスを使用した新しいソリューション...

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から継承します(コメントで述べたように、私の最初の解決策は継承を保持しませんでした)。
[。

SECOND EDIT @Svish:iPooProviderBaseAnimalを継承するという事実を拡張メソッドが強制しないことを明確に示すためにコードを変更しました。 「さらに強く型付けされた」とはどういう意味ですか?

46
Paolo Tedesco

これはreturn type covarianceと呼ばれ、一部の人々の wishes にもかかわらず、一般的にC#または.NETではサポートされていません。

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

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

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

31
Daniel Daranas

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

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

  • パオロ・テデスコの最初の解決策:猫と犬には共通の基本クラスがありません。
  • 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 OR Cat!で何かをオーバーライドする必要はありません!以下に使用例を示します。

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

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

さらに読む

  • 明示的なインターフェイスの実装
  • 新しい修飾子 。この単純化されたソリューションでは使用しませんでしたが、より複雑なソリューションで必要になる場合があります。たとえば、BaseAnimalのインターフェイスを作成する場合、「PooType Excrement」の宣言で使用する必要があります。
  • out Generic Modifier(Covariance) 。繰り返しますが、このソリューションでは使用しませんでしたが、return MyType<Poo> IAnimalから返されたMyType<PooType> BaseAnimalから、2つの間でキャストできるようにするために使用する必要があります。
18
rob

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

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

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

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

9
Rasmus Faber

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

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

E.x .:

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; } }
}
}
4

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

2
almog.ori

これを試して:

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

ジェネリックや拡張メソッドに依存するのではなく、メソッドの非表示に依存する方法を見つけたと思います。ただし、ポリモーフィズムを壊す可能性があるため、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; } }
}
1
Cybis

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

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

0
bobbyalex

インターフェイスを返すだけを使用できます。あなたの場合、IPoo。

コメントベースクラスを使用しているため、これは一般的な型を使用するよりも望ましい方法です。

0
David Shaskin

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の使用はパフォーマンスに影響を与えるため、避けてください。

0
wobuntu

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

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
Pierre Grondin