なぜBCLコレクションはクラスではなく構造列挙器を使用するのですか?
-
02-10-2019 - |
質問
私たちは皆知っています 可変構造体は悪です 一般に。私もそれがかなり確信しています IEnumerable<T>.GetEnumerator()
タイプを返します IEnumerator<T>
, 、構造体はすぐに参照タイプにボックス化され、最初から参照タイプである場合よりもコストがかかります。
では、なぜBCLジェネリックコレクションでは、すべての列挙者が可変構造体となっているのでしょうか?確かに正当な理由がなければなりませんでした。私に発生する唯一のことは、構造体を簡単にコピーできるため、列挙された状態を任意のポイントで保存できることです。しかし、追加します Copy()
方法への方法 IEnumerator
インターフェイスはそれほど面倒ではなかったので、これはそれ自体が論理的な正当化であるとは思いません。
設計上の決定に同意しなくても、その背後にある推論を理解できるようになりたいと思います。
解決
確かに、それはパフォーマンス上の理由です。 BCLチームはaをしました 多く 疑わしく危険な慣行としてあなたが正しく呼ぶものと一緒に行くことを決定する前のこの点に関する研究の研究:可変値タイプの使用。
これがボクシングを引き起こさない理由を尋ねます。これは、C#コンパイラが、それを回避できれば、foreachループでienumerableまたはiEnumeratorにボックスするコードを生成しないためです!
私たちが見るとき
foreach(X x in c)
最初に行うことは、CがGetEnumeratorというメソッドがあるかどうかを確認することです。もしそうなら、返されるタイプにMovenextとプロパティ電流があるかどうかを確認します。もしそうなら、foreachループは、これらのメソッドとプロパティへの直接呼び出しを使用して完全に生成されます。 「パターン」が一致できない場合にのみ、インターフェイスを探すことに戻ります。
これには2つの望ましい効果があります。
まず、コレクションがINTのコレクションであるが、一般的なタイプが発明される前に書かれた場合、それは電流の値をオブジェクトにボクシングするボクシングペナルティを取得し、それをINTに解除することはありません。電流がINTを返すプロパティである場合、使用するだけです。
第二に、列挙器が値のタイプである場合、列挙器をienumeratorにボックス化しません。
私が言ったように、BCLチームはこれについて多くの調査を行い、大部分の時間、割り当てのペナルティが と扱いをします 列挙器は十分に大きかったので、それを狂ったバグを引き起こす可能性があるにもかかわらず、値タイプにする価値がありました。
たとえば、これを考慮してください。
struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
h = somethingElse;
}
あなたは、Hを変異させようとする試みが失敗することを非常に正しく期待しています、そしてそれは実際にそうします。コンパイラは、保留中の処分があるものの価値を変更しようとしていることを検出します。そうすることで、実際に処分する必要があるオブジェクトを実際に処分することはできません。
今、あなたが持っていたとしましょう:
struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
h.Mutate();
}
ここでは何が起きるのですか?コンパイラが、hが読み取られたフィールドである場合に行うことを行うことを合理的に期待するかもしれません。 コピーを作成し、コピーを変更します メソッドが処分する必要がある値に捨てないようにするため。
しかし、それはここで何が起こるべきかについての私たちの直感と矛盾しています:
using (Enumerator enumtor = whatever)
{
...
enumtor.MoveNext();
...
}
使用するブロック内でモーベンスを行うことを期待しています 意思 構造体であるかrefタイプであるかに関係なく、列挙器を次の列挙器に移動します。
残念ながら、今日のC#コンパイラにはバグがあります。あなたがこの状況にある場合、私たちは一貫してどの戦略を追うかを選択します。今日の行動は次のとおりです。
メソッドを介して変異している値タイプの変数が通常のローカルである場合、通常は変異します
しかし、それが巻き上げられたローカルである場合(それが匿名関数の閉鎖変数またはイテレーターブロック内)、ローカル は 実際には読み取り専用フィールドとして生成され、コピーで突然変異が発生することを保証するギアが引き継ぎます。
残念ながら、このスペックはこの問題に関するほとんどガイダンスを提供しません。私たちが一貫性のないことをしているので、明らかに何かが壊れていますが、何が 右 やることはまったく明確ではありません。
他のヒント
structメソッドは、structのタイプがコンパイル時間で既知である場合に導入され、インターフェイスを介して呼び出しメソッドが遅いため、回答はパフォーマンスの理由のためです。