web-dev-qa-db-ja.com

派生型でオーバーライドできますか?

私の知る限り、C#2.0では次のことはできません。

public class Father
{
    public virtual Father SomePropertyName
    {
        get
        {
            return this;
        }
    }
}

public class Child : Father
{
    public override Child SomePropertyName
    {
        get
        {
            return this;
        }
    }
}

私は派生クラスのプロパティを「新規」として作成することで問題を回避していますが、もちろんそれはポリモーフィックではありません。

public new Child SomePropertyName

2.0で解決策はありますか?この問題に対処する3.5の機能についてはどうですか?

40
Luis Filipe

これは、型の安全性の問題のため、.NET言語では不可能です。タイプセーフ言語では、戻り値に共分散を、パラメータに反分散を指定する必要があります。このコードを取る:

_class B {
    S Get();
    Set(S);
}
class D : B {
    T Get();
    Set(T);
}
_

Getメソッドの場合、共分散とは、TSまたはSから派生した型でなければならないことを意味します。そうでない場合、D型の変数に格納されているB型のオブジェクトへの参照がある場合、B.Get()を呼び出したときに、オブジェクトをS back-型システムの破壊。

Setメソッドの場合、反変とは、TSまたはSの派生型である必要があることを意味します。それ以外の場合、D型のオブジェクトへの参照がB型の変数に格納されている場合、B.Set(X)を呼び出したときに、XはタイプSですが、タイプTではありません。D::Set(T)は、予期しないタイプのオブジェクトを取得します。

C#では、ゲッター/セッターのペアが1つしかない場合でも、プロパティのオーバーロード時に型の変更を許可しないという意識的な決定がありました。そうしないと、動作に一貫性がなくなります( "つまり、ゲッターを使用するタイプは変更できますが、ゲッターとセッターの両方を使用するタイプは変更できませんか?なぜ変更できないのですか?!?」-匿名の代替ユニバースの初心者).

26
Alex Lyman

再宣言(新規)することはできますが、(同じ名前で)再宣言とオーバーライドを同時に行うことはできません。 1つのオプションは、保護されたメソッドを使用して詳細を非表示にすることです。これにより、ポリモーフィズムと非表示の両方を同時に行うことができます。

public class Father
{
    public Father SomePropertyName
    {
        get {
            return SomePropertyImpl();
        }
    }
    protected virtual Father SomePropertyImpl()
    {
        // base-class version
    }
}

public class Child : Father
{
    public new Child SomePropertyName
    {
        get
        { // since we know our local SomePropertyImpl actually returns a Child
            return (Child)SomePropertyImpl();
        }
    }
    protected override Father SomePropertyImpl()
    {
        // do something different, might return a Child
        // but typed as Father for the return
    }
}
43
Marc Gravell

いいえ。ただし、ジェネリックを2以上で使用できます。

public class MyClass<T> where T: Person
{
    public virtual T SomePropertyName
    {
        get
        {
            return  ...;
        }
    }
}

次に、父と子は同じクラスのジェネリックバージョンです

11
Keith

Wikipedia から:

C#プログラミング言語では、戻り値型の共分散とデリゲートのパラメーターの逆分散の両方のサポートが言語のバージョン2.0で追加されました。メソッドのオーバーライドでは、共分散も反分散もサポートされていません。

ただし、プロパティの共分散については何も明示されていません。

7
dalle

父と子に共通のインターフェースを作成して、そのインターフェースのタイプを返すことができます。

2
VVS

いいえ。C#はこのアイデアをサポートしていません(「戻り型の共分散」と呼ばれています)。

ウィキペディアから:

C#プログラミング言語では、戻り値型の共分散とデリゲートのパラメーターの逆分散の両方のサポートが言語のバージョン2.0で追加されました。メソッドのオーバーライドでは、共分散も反分散もサポートされていません。

再宣言(新規)することはできますが、(同じ名前で)再宣言とオーバーライドを同時に行うことはできません。 1つのオプションは、保護されたメソッドを使用して詳細を非表示にすることです。これにより、ポリモーフィズムと非表示の両方を同時に行うことができます。

最善の解決策は、ジェネリックを使用することです。

public class MyClass<T> where T: Person
{
   public virtual T SomePropertyNameA
   {        
      get { return  ...; }    
   }
}//Then the Father and Child are generic versions of the same class
1
Micah

これは私が来ることができる最も近いものです(これまでのところ):

    public sealed class JustFather : Father<JustFather> {}

    public class Father<T> where T : Father<T>
    { public virtual T SomePropertyName
        { get { return (T) this; }
        }
    }

    public class Child : Father<Child>
    { public override Child SomePropertyName
        { get { return  this; }
        }
    }

JustFatherクラスがないと、他の派生型でない限り、Father<T>をインスタンス化できません。

1
Mark Cidade

いいえ。C#はこのアイデアをサポートしていません(「戻り型の共分散」と呼ばれています)。ただし、これは可能です。

public class FatherProp
{
}

public class ChildProp: FatherProp
{
}


public class Father
{
    public virtual FatherProp SomePropertyName
    {
        get
        {
            return new FatherProp();
        }
    }
}


public class Child : Father
{
    public override FatherProp SomePropertyName
    {
        get
        {
            // override to return a derived type instead
            return new ChildProp();
        }
    }
}

つまり、基本クラスで定義されたコントラクトを使用しますが、派生型を返します。この点をより明確にするために、より詳細なサンプルを作成しました。「これ」を再度返しても何も変わりません。

返されたオブジェクトを実際のタイプ(つまり、「someObjectがChildPropの場合」)かどうかをテストすることは可能ですが(乱雑です)、そのタイプに対して正しいことを行う仮想メソッドを呼び出す方が適切です。

基本クラスの仮想メソッド(この場合は仮想プロパティ)には実装があるだけでなく、コントラクトも定義されています。子クラスは、このコントラクトに適合した場合(つまり、SomePropertyNameが型「」のオブジェクトを返す場合、SomePropertyNameの異なる実装を提供できます。 FatherProp」)。 「FatherProp」から派生したタイプ「ChildProp」のオブジェクトを返すことは、この規約を満たしています。ただし、「子」のコントラクトを変更することはできません。このコントラクトは、「父」の子孫のすべてのクラスに適用されます。

一歩下がってより広い設計を見ると、C#ツールキットには他にも考えたい言語構成要素があります-ジェネリック、またはインターフェイスです。

1
Anthony