「ref」と「out」が多態性をサポートしないのはなぜですか?
-
05-07-2019 - |
質問
次のことを行ってください:
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
引数の両方で発生します。
解決
=============
更新:この回答をこのブログエントリの基礎として使用しました:
refパラメータとoutパラメータで型のバリエーションが許可されないのはなぜですか?
この問題の詳細については、ブログページを参照してください。すばらしい質問をありがとう。
=============
クラス Animal
、 Mammal
、 Reptile
、 Giraffe
、 Turtle があるとしますcode>および
Tiger
、明らかなサブクラス化関係。
メソッド void M(ref Mammal m)
があるとします。 M
は、 m
の読み取りと書き込みの両方が可能です。
Animal
型の変数をM
に渡すことはできますか?
いいえ。その変数には Turtle
を含めることができますが、 M
は哺乳類のみが含まれると想定します。 Turtle
は Mammal
ではありません。
結論1 : ref
パラメーターを「大きく」することはできません。 (哺乳類よりも動物の数が多いため、より多くのものを含めることができるため、変数は「大きく」なります。)
Giraffe
型の変数をM
に渡すことはできますか?
いいえ。 M
は m
に書き込むことができ、 M
は m
に Tiger
を書き込むことができます。 >。これで、実際には Giraffe
型の変数に Tiger
を入れました。
結論2 : ref
パラメータを「小さく」することはできません。
N(out Mammal n)
を検討します。
Giraffe
型の変数をN
に渡すことはできますか?
いいえ。 N
は n
に書き込むことができ、 N
は Tiger
を書き込むことができます。
結論3 : out
パラメーターを「小さく」することはできません。
Animal
型の変数をN
に渡すことはできますか?
うーん。
さて、なぜですか? n
は n
から読み取ることができません。書き込みのみが可能ですよね? Animal
型の変数に Tiger
を書き込むと、設定は完了ですよね?
間違っています。ルールは&quot; N
は n
&quot;にしか書き込めません。
ルールの概要は次のとおりです。
1) N
は、 N
が正常に戻る前に n
に書き込む必要があります。 ( N
がスローされた場合、すべてのベットはオフになります。)
2) n
は、 n
から何かを読み取る前に、 n
に何かを書き込む必要があります。
これにより、次の一連のイベントが許可されます。
-
Animal
型のフィールドx
を宣言します。 -
x
へのout
パラメータとしてx
を渡します。 -
N
はn
にTiger
を書き込みます。これはx
のエイリアスです。 - 別のスレッドで誰かが
Turtle
をx
に書き込みます。 -
N
はn
の内容を読み取ろうとし、タイプMammalの変数であると思われる
。Turtle
を発見します。
明らかにそれを違法にしたいのです。
結論4 : out
パラメーターを「大きく」することはできません。
最終的な結論: ref
パラメーターと out
パラメーターのいずれも、タイプが異なる場合があります。そうでなければ、検証可能な型安全性を破ることです。
他のヒント
どちらの場合も、ref / outパラメーターに値を割り当てることができる必要があるため。
bを参照としてFoo2メソッドに渡し、Foo2でa = new A()を指定しようとすると、これは無効になります。
書くことができない同じ理由:
B b = new A();
共分散(および反分散)の古典的なOOP問題に苦労しています。ウィキペディア:この事実は直感的な期待に反する可能性があるため、基本クラスの代わりに派生クラスを変更可能な(割り当て可能な)引数(およびアイテムが割り当て可能なコンテナ、ちょうど同じ理由) Liskovの原則を引き続き尊重します。なぜそうなのかは、既存の回答でスケッチされており、これらのwiki記事とそこからのリンクでさらに詳しく調べられています。
伝統的に静的に型保証されたままであるように見えるOOP言語は、「不正」です。 (非表示の動的型チェックを挿入するか、チェックするためにすべてのソースのコンパイル時検査が必要です);基本的な選択は、この共分散を放棄して実務者の困惑を受け入れるか(C#がここで行うように)、動的タイピングアプローチに移行する(最初のOOP言語であるSmalltalkが行うように)か、不変に移行する(単一-割り当て)関数型言語が行うようなデータ(不変性の下では、共分散をサポートできます。また、可変データの世界ではSquareサブクラスRectangleを使用できないなど、他の関連するパズルを回避できます)。
検討:
class C : A {}
class B : A {}
void Foo2(ref A a) { a = new C(); }
B b = null;
Foo2(ref b);
タイプセーフに違反する
他の応答はこの動作の背後にある理由を簡潔に説明していますが、この性質を実際に行う必要がある場合は、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 {}
}
Foo2
に ref B
を与えると、 Foo2
は A
B
の一部。
コンパイラがオブジェクトを明示的にキャストして、意図が何であるかを確認できるようにしたいと言っているのではありませんか?
Foo2(ref (A)b)
安全性の観点からは理にかなっていますが、参照によって渡される多目的オブジェクトの合法的な使用があるため、コンパイラがエラーではなく警告を出した場合、私はそれを好むでしょう。例:
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);
}
これはコンパイルされませんが、動作しますか?
タイプに実用的な例を使用すると、表示されます:
SqlConnection connection = new SqlConnection();
Foo(ref connection);
これで、祖先( i.e。 Object
)を取る関数ができました:
void Foo2(ref Object connection) { }
これで何が問題になる可能性がありますか?
void Foo2(ref Object connection)
{
connection = new Bitmap();
}
BitConnection
を SqlConnection
に割り当てることができました。
それはダメです。
他のユーザーともう一度やり直してください:
SqlConnection conn = new SqlConnection();
Foo2(ref conn);
void Foo2(ref DbConnection connection)
{
conn = new OracleConnection();
}
OracleConnection
を SqlConnection
の上に重ねます。
私の場合、私の関数はオブジェクトを受け入れましたが、何も送信できなかったため、単純に送信しました
object bla = myVar;
Foo(ref bla);
そしてそれは動作します
My FooはVB.NETにあり、内部の型をチェックし、多くのロジックを実行します
回答が重複しているが他の回答が長すぎる場合は謝罪します