web-dev-qa-db-ja.com

「ref」と「out」が多態性をサポートしないのはなぜですか?

次の点を考慮してください。

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= compile-time error: 
                     // "The 'ref' argument doesn't match the parameter type"
    }

    void Foo(A a) {}

    void Foo2(ref A a) {}  
}

上記のコンパイル時エラーが発生するのはなぜですか?これは、ref引数とout引数の両方で発生します。

124
Andreas Grech

=============

更新:私はこの回答をこのブログエントリの基礎として使用しました。

refおよびoutパラメータが型のバリエーションを許可しないのはなぜですか?

この問題の詳細については、ブログページを参照してください。素晴らしい質問をありがとう。

=============

クラスAnimalMammalReptileGiraffeTurtleTigerがあり、サブクラスの関係。

ここで、メソッドvoid M(ref Mammal m)があるとします。 Mmの読み取りと書き込みの両方ができます。


Animal型の変数をMに渡すことはできますか?

いいえ。その変数にはTurtleを含めることができますが、Mは哺乳類のみを含むと想定します。 TurtleMammalではありません。

結論1refパラメータを「大きく」することはできません。 (哺乳類よりも動物の数が多いため、より多くのものを含めることができるため、変数は「大きく」なります。)


Giraffe型の変数をMに渡すことはできますか?

いいえ。Mmに書き込むことができ、MTigermに書き込むことができます。これで、Tigerを実際にはGiraffe型の変数に入れました。

結論2refパラメータを「小さく」することはできません。


次に、N(out Mammal n)について考えます。

Giraffe型の変数をNに渡すことはできますか?

いいえ。Nnに書き込むことができ、NTigerに書き込むことができます。

結論3outパラメータを「小さく」することはできません。


Animal型の変数をNに渡すことはできますか?

うーん。

さて、なぜですか? Nnから読み取れません。書き込みのみが可能ですよね? TigerAnimal型の変数に書き込んで、すべて完了しましたよね?

違う。ルールは「Nnにのみ書き込み可能」ではありません。

ルールは、簡単に言えば:

1)Nが正常に戻る前に、nNに書き込む必要があります。 (Nがスローした場合、すべてのベットがオフになります。)

2)Nは、nから何かを読み取る前に、nに何かを書き込む必要があります。

これにより、この一連のイベントが可能になります。

  • タイプxのフィールドAnimalを宣言します。
  • xoutパラメータとしてNに渡します。
  • NTigernに書き込みます。これはxのエイリアスです。
  • 別のスレッドで、誰かがTurtlexに書き込みます。
  • Nnの内容を読み取ろうとし、TurtleMammal型の変数であると考えているものから発見します。

明らかにそれを違法にしたいのです。

結論4outパラメータを「大きく」することはできません。


最終的な結論refoutのどちらのパラメーターも型を変えることはできません。そうでなければ、検証可能な型安全性を破ることになります。

基本型理論のこれらの問題に興味がある場合は、 C#4.0での共分散と反変の動作に関する私のシリーズ を読むことを検討してください。

169
Eric Lippert

どちらの場合でも、ref/outパラメータに値を割り当てることができる必要があるためです。

参照としてbをFoo2メソッドに渡そうとし、Foo2でa = new A()をアサートしようとすると、これは無効になります。
あなたが書けないのと同じ理由:

B b = new A();
29
maciejkow

あなたは古典的なOOP(covarianceの問題(および反変))と格闘しています、 wikipedia :この事実は直感的な期待に反する可能性があるので、基本クラスの代わりに派生クラスを変更可能な(割り当て可能な)引数(および同じように割り当て可能なアイテムのコンテナー)に置き換えることは数学的に不可能です理由) リスコフの原則を尊重しながら なぜそうなのかは、既存の回答にスケッチされており、これらのWikiの記事とリンクからさらに深く探求されています。

従来のように静的に型保証されたままであるように見えるOOP言語は「不正」です(非表示の動的型チェックを挿入するか、チェックするためにすべてのソースのコンパイル時検査が必要です)。基本的な選択は次のとおりです。この共分散をあきらめて、開業医の困惑を受け入れる(C#がここで行うようにする)か、動的型付けアプローチに移動する(最初のOOP言語、Smalltalk、 )、または関数型言語が行うように、不変(単一割り当て)データに移動します(不変性の下では、共分散をサポートできます。また、変更可能なデータの世界で四角形のサブクラスの四角形を使用できないなど、他の関連するパズルも回避できます)。 。

10
Alex Martelli

考慮してください:

class C : A {}
class B : A {}

void Foo2(ref A a) { a = new C(); } 

B b = null;
Foo2(ref b);

タイプセーフに違反する

4
Henk Holterman

他の応答ではこの動作の背後にある理由を簡潔に説明していますが、この種の性質を実際に行う必要がある場合は、Foo2をジェネリックメソッドにすることで同様の機能を実現できることを言及する価値があると思います。

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= no compile error!
    }

    void Foo(A a) {}

    void Foo2<AType> (ref AType a) where AType: A {}  
}
3
BrendanLoBuglio

Foo2 a ref Bは、Foo2は、AB部分を埋める方法しか知りません。

2
CannibalSmith

タイプの実用的な例を使用すると、次のようになります。

SqlConnection connection = new SqlConnection();
Foo(ref connection);

これで、祖先ieObject):

void Foo2(ref Object connection) { }

何が問題になる可能性がありますか?

void Foo2(ref Object connection)
{
   connection = new Bitmap();
}

BitmapSqlConnectionに割り当てました。

それはまずいです。


他のユーザーとやり直してください:

SqlConnection conn = new SqlConnection();
Foo2(ref conn);

void Foo2(ref DbConnection connection)
{
    conn = new OracleConnection();
}

OracleConnectionSqlConnectionの上に詰めました。

0
Ian Boyd

コンパイラーがオブジェクトを明示的にキャストして、意図が何であるかが確実にわかるようにしたいと言っているのではないですか?

Foo2(ref (A)b)
0
dlamblin

私の場合、関数はオブジェクトを受け入れ、何も送信できなかったので、単純に行いました

object bla = myVar;
Foo(ref bla);

そしてそれはうまくいく

私のFooはVB.NETにあり、内部の型をチェックし、多くのロジックを実行します

私の答えが重複しているが、他の人が長すぎると申し訳ありません

0
Shereef Marzouk

安全性の観点からは理にかなっていますが、参照で渡されるpolymoprhicオブジェクトの正当な使用があるため、コンパイラーがエラーではなく警告を出した場合、私はそれを優先しました。例えば.

class Derp : interfaceX
{
   int somevalue=0; //specified that this class contains somevalue by interfaceX
   public Derp(int val)
    {
    somevalue = val;
    }

}


void Foo(ref object obj){
    int result = (interfaceX)obj.somevalue;
    //do stuff to result variable... in my case data access
    obj = Activator.CreateInstance(obj.GetType(), result);
}

main()
{
   Derp x = new Derp();
   Foo(ref Derp);
}

これはコンパイルされませんが、動作しますか?

0
Oofpez