126

次のようにします。

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ます。

4

10 に答える 10

172

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

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

refおよびoutパラメーターでタイプの変更が許可されないのはなぜですか?

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

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

Animal明らかなサブクラス化Mammalの関係Reptileを持つクラス、、、、、およびがGiraffeあるとします。TurtleTiger

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


Animalタイプの変数をに渡すことができますMか?

いいえ。その変数には、を含めることができますがTurtleM哺乳類のみが含まれていると想定されます。ATurtleはではありませんMammal

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


Giraffeタイプの変数をに渡すことができますMか?

いいえ。にM書き込むことができ、に書き込みたい場合がmあります。これで、実際にはタイプの変数にを入れました。MTigermTigerGiraffe

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


ここで考えてみましょうN(out Mammal n)

Giraffeタイプの変数をに渡すことができますNか?

いいえ。に書き込むNことができ、を書き込みたい場合がnあります。NTiger

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


Animalタイプの変数をに渡すことができますNか?

うーん。

さて、なぜですか? Nから読み取ることはできませんn、それはそれに書き込むことしかできませんよね?Tigerタイプの変数にaを書き込むと、Animalすべて設定されますよね?

間違い。ルールは「N書き込みしかできない」ではありませんn

ルールは簡単に言うと:

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

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

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

  • xタイプのフィールドを宣言しますAnimal
  • パラメータxとしてに渡します。outN
  • NのエイリアスであるにをTiger書き込みます。nx
  • 別のスレッドで、誰かがにを書き込みTurtleますx
  • Nの内容を読み込もうとし、型の変数であると考えているものnを発見します。TurtleMammal

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

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


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

基本型理論のこれらの問題に関心がある場合は、C#4.0で共変性と反変性がどのように機能するかについての私のシリーズを読むことを検討してください。

于 2009-07-30T15:18:13.783 に答える
31

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

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

B b = new A();
于 2009-07-30T14:57:39.857 に答える
10

共変性(および反変性)の古典的なOOP問題に苦しんでいます。ウィキペディアを参照してください。この事実は直感的な期待に反する可能性がありますが、可変(割り当て可能)引数の代わりに派生クラスを置き換えることを数学的に許可することは不可能です(およびまた、リスコフの原則を尊重しながら、同じ理由でアイテムを割り当てることができるコンテナもあります。なぜそうなるのかは、既存の回答にスケッチされており、これらのwiki記事とそこからのリンクでさらに深く探求されています。

従来は静的に型セーフでありながらそうしているように見えるOOP言語は、「不正行為」です(非表示の動的型チェックを挿入するか、チェックするためにすべてのソースのコンパイル時検査を要求します)。基本的な選択は、この共分散をあきらめて実践者の困惑を受け入れるか(C#がここで行うように)、動的型付けアプローチに移行するか(最初のOOP言語であるSmalltalkが行ったように)、または不変に移行する(単一-代入)データは、機能言語と同様に(不変性の下で、共分散をサポートできます。また、可変データの世界でSquareサブクラスRectangleを使用できないなど、他の関連するパズルを回避できます)。

于 2009-07-30T15:21:18.443 に答える
4

検討:

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

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

B b = null;
Foo2(ref b);

型安全性に違反します

于 2009-07-30T15:20:42.517 に答える
2

の一部を埋める方法しか知らないためFoo2、を与えるref Bと不正なオブジェクトが生成されるためです。Foo2AB

于 2009-07-30T14:57:44.583 に答える
0

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

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

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

于 2012-03-14T10:58:22.197 に答える
0

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

object bla = myVar;
Foo(ref bla);

そして、それはうまくいきます

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

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

于 2018-06-20T11:47:45.620 に答える