次の点を考慮してください。
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
とTiger
があり、サブクラスの関係。
ここで、メソッドvoid M(ref Mammal m)
があるとします。 M
はm
の読み取りと書き込みの両方ができます。
Animal
型の変数をM
に渡すことはできますか?
いいえ。その変数にはTurtle
を含めることができますが、M
は哺乳類のみを含むと想定します。 Turtle
はMammal
ではありません。
結論1:ref
パラメータを「大きく」することはできません。 (哺乳類よりも動物の数が多いため、より多くのものを含めることができるため、変数は「大きく」なります。)
Giraffe
型の変数をM
に渡すことはできますか?
いいえ。M
はm
に書き込むことができ、M
はTiger
をm
に書き込むことができます。これで、Tiger
を実際にはGiraffe
型の変数に入れました。
結論2:ref
パラメータを「小さく」することはできません。
次に、N(out Mammal n)
について考えます。
Giraffe
型の変数をN
に渡すことはできますか?
いいえ。N
はn
に書き込むことができ、N
はTiger
に書き込むことができます。
結論3:out
パラメータを「小さく」することはできません。
Animal
型の変数をN
に渡すことはできますか?
うーん。
さて、なぜですか? N
はn
から読み取れません。書き込みのみが可能ですよね? Tiger
をAnimal
型の変数に書き込んで、すべて完了しましたよね?
違う。ルールは「N
はn
にのみ書き込み可能」ではありません。
ルールは、簡単に言えば:
1)N
が正常に戻る前に、n
がN
に書き込む必要があります。 (N
がスローした場合、すべてのベットがオフになります。)
2)N
は、n
から何かを読み取る前に、n
に何かを書き込む必要があります。
これにより、この一連のイベントが可能になります。
x
のフィールドAnimal
を宣言します。x
をout
パラメータとしてN
に渡します。N
はTiger
をn
に書き込みます。これはx
のエイリアスです。Turtle
をx
に書き込みます。N
はn
の内容を読み取ろうとし、Turtle
をMammal
型の変数であると考えているものから発見します。明らかにそれを違法にしたいのです。
結論4:out
パラメータを「大きく」することはできません。
最終的な結論:ref
とout
のどちらのパラメーターも型を変えることはできません。そうでなければ、検証可能な型安全性を破ることになります。
基本型理論のこれらの問題に興味がある場合は、 C#4.0での共分散と反変の動作に関する私のシリーズ を読むことを検討してください。
どちらの場合でも、ref/outパラメータに値を割り当てることができる必要があるためです。
参照としてbをFoo2メソッドに渡そうとし、Foo2でa = new A()をアサートしようとすると、これは無効になります。
あなたが書けないのと同じ理由:
B b = new A();
あなたは古典的なOOP(covarianceの問題(および反変))と格闘しています、 wikipedia :この事実は直感的な期待に反する可能性があるので、基本クラスの代わりに派生クラスを変更可能な(割り当て可能な)引数(および同じように割り当て可能なアイテムのコンテナー)に置き換えることは数学的に不可能です理由) リスコフの原則を尊重しながら なぜそうなのかは、既存の回答にスケッチされており、これらのWikiの記事とリンクからさらに深く探求されています。
従来のように静的に型保証されたままであるように見えるOOP言語は「不正」です(非表示の動的型チェックを挿入するか、チェックするためにすべてのソースのコンパイル時検査が必要です)。基本的な選択は次のとおりです。この共分散をあきらめて、開業医の困惑を受け入れる(C#がここで行うようにする)か、動的型付けアプローチに移動する(最初のOOP言語、Smalltalk、 )、または関数型言語が行うように、不変(単一割り当て)データに移動します(不変性の下では、共分散をサポートできます。また、変更可能なデータの世界で四角形のサブクラスの四角形を使用できないなど、他の関連するパズルも回避できます)。 。
考慮してください:
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
a ref B
は、Foo2
は、A
のB
部分を埋める方法しか知りません。
タイプの実用的な例を使用すると、次のようになります。
SqlConnection connection = new SqlConnection();
Foo(ref connection);
これで、祖先(ieObject
):
void Foo2(ref Object connection) { }
何が問題になる可能性がありますか?
void Foo2(ref Object connection)
{
connection = new Bitmap();
}
Bitmap
をSqlConnection
に割り当てました。
それはまずいです。
他のユーザーとやり直してください:
SqlConnection conn = new SqlConnection();
Foo2(ref conn);
void Foo2(ref DbConnection connection)
{
conn = new OracleConnection();
}
OracleConnection
をSqlConnection
の上に詰めました。
コンパイラーがオブジェクトを明示的にキャストして、意図が何であるかが確実にわかるようにしたいと言っているのではないですか?
Foo2(ref (A)b)
私の場合、関数はオブジェクトを受け入れ、何も送信できなかったので、単純に行いました
object bla = myVar;
Foo(ref bla);
そしてそれはうまくいく
私のFooはVB.NETにあり、内部の型をチェックし、多くのロジックを実行します
私の答えが重複しているが、他の人が長すぎると申し訳ありません
安全性の観点からは理にかなっていますが、参照で渡される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);
}
これはコンパイルされませんが、動作しますか?