web-dev-qa-db-ja.com

C#は戻り値型の共分散をサポートしていますか?

私は.NETフレームワークを使用しており、すべてのWebサイトが使用するカスタムタイプのページを作成できるようにしたいと考えています。問題は、コントロールからページにアクセスしようとしたときに発生します。デフォルトのページではなく、特定のタイプのページを返すことができるようにしたい。これを行う方法はありますか?

public class MyPage : Page
{
    // My own logic
}

public class MyControl : Control
{
    public MyPage Page { get; set; }
}
72
Kyle Sletten

あなたが望むのは戻り型の共分散であるように聞こえます。 C#は戻り型の共分散をサポートしていません。

戻り型の共分散は、より具体的な型を返すもので、より具体性の低い型を返す基本クラスメソッドをオーバーライドする場所です。

abstract class Enclosure
{
    public abstract Animal Contents();
}
class Aquarium : Enclosure
{
    public override Fish Contents() { ... }
}

エンクロージャを介したコンテンツの消費者は動物を期待しているため、水族館はその要件を満たすだけでなく、動物が常に魚であるというより厳密な約束をすることを約束するため、これは安全です。

この種の共分散はC#ではサポートされておらず、サポートされることはほとんどありません。 CLRではサポートされていません。 (C++、およびCLRのC++/CLI実装でサポートされています。以下で提案するような魔法のヘルパーメソッドを生成することでサポートしています。)

(一部の言語は、正式なパラメータータイプの矛盾もサポートしています。これは、Fishを使用するメソッドをAnimalを使用するメソッドでオーバーライドできます。再び、契約が満たされます。基本クラスでは、Fishを処理し、クラスは、魚だけでなく、あらゆる動物を処理することを約束します。同様に、C#とCLRは、正式なパラメータータイプの矛盾をサポートしません。

この制限を回避する方法は、次のようなことです。

abstract class Enclosure
{
    protected abstract Animal GetContents();
    public Animal Contents() { return this.GetContents(); }
}
class Aquarium : Enclosure
{
    protected override Animal GetContents() { return this.Contents(); }
    public new Fish Contents() { ... }
}

これで、仮想メソッドをオーバーライドする利点と、コンパイル時タイプのAquariumを使用する場合のタイピングが強化される利点の両方が得られます。

148
Eric Lippert

インターフェイスを使用して、インターフェイスを明示的に実装することで回避しました。

public interface IFoo {
  IBar Bar { get; }
}
public class Foo : IFoo {
  Bar Bar { get; set; }
  IBar IFoo.Bar => Bar;
}
4
ChetPrickles

このブログ投稿シリーズでは、戻り型の共分散の問題について詳しく説明し、ILでそれを行う方法についても説明しています。

http://www.simple-talk.com/community/blogs/simonc/archive/2010/07/14/93495.aspx

http://www.simple-talk.com/community/blogs/simonc/archive/2010/07/16/93516.aspx

http://www.simple-talk.com/community/blogs/simonc/archive/2010/07/19/93562.aspx

リンクを投稿するだけで申し訳ありませんが、それは非常に詳細であり、ここでスニペットを引用することはそれほど役に立ちません。 ILコードを使用してそれを達成する方法を示します。

3
hatchet

これをMyControlオブジェクトに配置すると機能します。

 public new MyPage Page {get return (MyPage)Page; set;}'

別のタイプを返すため、プロパティをオーバーライドすることはできません...しかし、再定義することはできます。

この例では、共分散は比較的単純なので、共分散は必要ありません。あなたがしているのは、PageからベースオブジェクトMyPageを継承することだけです。 Controlの代わりにMyPageを返したいPageは、PageControlプロパティを再定義する必要があります。

2
Zhais

はい、共分散をサポートしていますが、達成しようとしている正確なことに依存します。

私はまた、物事のためにジェネリックを多く使用する傾向があります。つまり、次のようなことをするとき:

class X<T> {
    T doSomething() {
    }

}

class Y : X<Y> {
    Y doSomethingElse() {
    }
}

var Y y = new Y();
y = y.doSomething().doSomethingElse();

そして、あなたのタイプを「失う」ことはありません。

1
Cade Roux

親ツリーを上に移動することにより、任意のコントロールからページにアクセスできます。あれは

myParent = this;

while(myParent.parent != null)
  myParent = myParent.parent;

*コンパイルまたはテストしませんでした。

または、現在のコンテキストで親ページを取得します(バージョンによって異なります)。


次に、私がしたいことはこれです:コントロールで使用したい機能(たとえば、IHostingPage)でインターフェイスを作成します

次に、親ページ「IHostingPage Host =(IHostingPage)Parent;」をキャストしますコントロールから必要なページの関数を呼び出すように設定されています。

0
Hogan

試したことはありませんが、うまくいきませんか?

YourPageType myPage = (YourPageType)yourControl.Page;
0

はい。これには複数の方法がありますが、これは1つのオプションにすぎません。

「GetContext」などのメソッドを公開するカスタムインターフェイスをページに実装すると、特定の情報が返されます。その後、コントロールは単にページをリクエストしてキャストできます。

var myContextPage = this.Page as IMyContextGetter;

if(myContextPage != null)
   var myContext = myContextPage.GetContext();

その後、そのコンテキストを自由に使用できます。

0
Tejs

私はこの方法でそれを行います:

class R {
    public int A { get; set; }
}

class R1: R {
    public int B { get; set; }
}

class A
{        
    public R X { get; set; }
}

class B : A 
{
    private R1 _x;
    public new R1 X { get => _x; set { ((A)this).X = value; _x = value; } }
}
0
Indomitable