「アウト」とは、大まかに言うと「出力位置にのみ表示される」という意味です。
「in」は、大まかに言えば、「入力位置にのみ表示される」ことを意味します。
実際の話はそれよりも少し複雑ですが、ほとんどの場合これが当てはまるため、キーワードが選択されました。
インターフェイスのメソッドまたはデリゲートによって表されるメソッドについて考えてみます。
delegate void Foo</*???*/ T>(ref T item);
Tは入力位置に表示されますか?はい。呼び出し元は、アイテムを介してTの値を渡すことができます。呼び出し先のFooはそれを読むことができます。したがって、Tを「アウト」とマークすることはできません。
Tは出力位置に表示されますか?はい。呼び出し先はアイテムに新しい値を書き込むことができ、呼び出し元はそれを読み取ることができます。したがって、Tを「in」とマークすることはできません。
したがって、Tが「ref」仮パラメータに含まれている場合、Tをinまたはoutとしてマークすることはできません。
物事がどのようにうまくいかないかのいくつかの実際の例を見てみましょう。これが合法であると仮定します。
delegate void X<out T>(ref T item);
...
X<Dog> x1 = (ref Dog d)=>{ d.Bark(); }
X<Animal> x2 = x1; // covariant;
Animal a = new Cat();
x2(ref a);
私の猫を犬にしましょう。猫の鳴き声を作りました。「アウト」は合法ではありません。
「で」はどうですか?
delegate void X<in T>(ref T item);
...
X<Animal> x1 = (ref Animal a)=>{ a = new Cat(); }
X<Dog> x2 = x1; // contravariant;
Dog d = new Dog();
x2(ref d);
そして、犬しか入れられない変数に猫を入れるだけです。Tも「in」とマークすることはできません。
outパラメータはどうですか?
delegate void Foo</*???*/T>(out T item);
?これで、Tは出力位置にのみ表示されます。Tを「アウト」としてマークすることは合法である必要がありますか?
残念だけど違う。「out」は、実際には舞台裏の「ref」と同じです。「out」と「ref」の唯一の違いは、コンパイラーが、呼び出し先によって割り当てられる前にoutパラメーターからの読み取りを禁止していることと、呼び出し先が正常に戻る前にコンパイラーが割り当てを要求することです。このインターフェイスの実装をC#以外の.NET言語で記述した人は、初期化される前にアイテムから読み取ることができるため、入力として使用できます。したがって、この場合、Tを「アウト」としてマークすることは禁止されています。それは残念ですが、私たちにできることは何もありません。CLRの型安全性規則に従わなければなりません。
さらに、「out」パラメータの規則は、に書き込まれる前に入力に使用できないことです。書き込み後の入力に使用できないというルールはありません。許可したとしましょう
delegate void X<out T>(out T item);
class C
{
Animal a;
void M()
{
X<Dog> x1 = (out Dog d) =>
{
d = null;
N();
if (d != null)
d.Bark();
};
x<Animal> x2 = x1; // Suppose this were legal covariance.
x2(out this.a);
}
void N()
{
if (this.a == null)
this.a = new Cat();
}
}
もう一度猫の鳴き声を作りました。Tを「アウト」にすることはできません。
この方法で入力用のパラメーターを使用することは非常に愚かですが、合法です。
更新:C#7がin
正式なパラメーター宣言として追加されました。これは、両方がin
あり、out
2つの意味があることを意味します。これにより、混乱が生じます。それを明確にしましょう:
in
、out
およびパラメータリストref
の正式なパラメータ宣言では、「このパラメータは呼び出し元によって提供された変数のエイリアスです」を意味します。
ref
「呼び出し先はエイリアス変数の読み取りまたは書き込みが可能であり、呼び出し前に割り当てられていることがわかっている必要があります。
out
「呼び出し先は、正常に戻る前に、エイリアスを介してエイリアス変数を書き込む必要があります」を意味します。また、変数が明確に割り当てられていない可能性があるため、呼び出し先はエイリアスを書き込む前にエイリアスを介してエイリアス変数を読み取ってはならないことも意味します。
in
「呼び出し先はエイリアス変数を読み取ることはできますが、エイリアスを介して書き込むことはできません」を意味します。の目的はin
、まれなパフォーマンスの問題を解決することです。この問題では、大きな構造体を「値で」渡す必要がありますが、そうするにはコストがかかります。実装の詳細として、in
パラメーターは通常、ポインターサイズの値を介して渡されます。これは、値によるコピーよりも高速ですが、間接参照では低速です。
- CLRの観点からは、、、
in
およびout
はref
すべて同じものです。誰がいつどの変数を読み書きするかについてのルールでは、CLRは知りません。
- 分散に関するルールを適用するのはCLRであるため、適用されるルールはパラメータに
ref
も適用されin
ますout
。
対照的に、in
およびout
on型パラメーター宣言は、それぞれ「この型パラメーターを共変的に使用してはならない」および「この型パラメーターを反変的に使用してはならない」ことを意味します。
上記のように、これらの修飾子には、を選択in
しました。これは、「入力」位置で使用され、「出力」位置で使用されるためです。これは厳密には当てはまりませんが、99.9%のユースケースでは十分に当てはまり、有用なニーモニックです。out
IFoo<in T, out U>
T
U
interface IFoo<in T, out U> { void Foo(in T t, out U u); }
それが機能するはずであるように見えるので、それは違法であるのは残念です。CLR検証者の観点からは、これらは両方ともref
パラメーターであり、したがって読み取り/書き込みであるため、機能しません。
これは、論理的に連携するはずの2つの機能が、実装の詳細な理由で連携してうまく機能しないという、奇妙で意図しない状況の1つにすぎません。