64

変更可能な構造体が一般的に悪であることは誰もが知っています。IEnumerable<T>.GetEnumerator()また、 type を返すためIEnumerator<T>、構造体はすぐに参照型にボックス化され、最初から単に参照型である場合よりも多くのコストがかかると確信しています。

では、なぜ BCL ジェネリック コレクションでは、すべての列挙子が変更可能な構造体なのでしょうか? きっと、それなりの理由があったに違いない。私が思いつく唯一のことは、構造体を簡単にコピーできるため、任意の時点で列挙子の状態を保持できることです。しかしCopy()、インターフェイスにメソッドを追加するIEnumeratorことはそれほど面倒ではなかったので、これ自体が論理的な正当化であるとは思えません。

デザインの決定に同意しなくても、その背後にある理由を理解できるようになりたい.

4

2 に答える 2

93

実際、これはパフォーマンス上の理由によるものです。BCL チームは、この点について多くの調査を行った後、不審で危険な慣行として正しく指摘されていること、つまり可変値型の使用を採用することを決定しました。

あなたは、なぜこれがボクシングを引き起こさないのかと尋ねます。これは、C# コンパイラが foreach ループで IEnumerable または IEnumerator にボックス化するコードを生成しないためです (回避できる場合)。

私たちが見るとき

foreach(X x in c)

最初に、c に GetEnumerator というメソッドがあるかどうかを確認します。存在する場合は、返される型にメソッド MoveNext とプロパティ current があるかどうかを確認します。存在する場合、foreach ループは、それらのメソッドとプロパティへの直接呼び出しを使用して完全に生成されます。「パターン」が一致しない場合にのみ、インターフェースの検索に戻ります。

これには 2 つの望ましい効果があります。

まず、コレクションがたとえば int のコレクションであるが、ジェネリック型が発明される前に作成された場合、Current の値をオブジェクトにボックス化し、それを int にボックス化解除するというボックス化のペナルティはかかりません。Current が 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();
    ...
}

using ブロック内で MoveNext を実行すると、列挙子が構造体であるか参照型であるかに関係なく、次の列挙子に移動することが期待されます。

残念ながら、現在の C# コンパイラにはバグがあります。このような状況にある場合、一貫性のない戦略を選択します。今日の動作は次のとおりです。

  • メソッドを介して変更される値型変数が通常のローカル変数である場合、通常どおり変更されます

  • しかし、巻き上げられたローカルである場合 (匿名関数またはイテレータ ブロック内のクローズド オーバー変数であるため)、ローカル実際には読み取り専用フィールドとして生成され、コピーでミューテーションが発生することを保証するギアは次のようになります。以上。

残念ながら、仕様ではこの問題に関するガイダンスはほとんど提供されていません。一貫性のないやり方をしているため、何かが壊れていることは明らかですが、何をするのが正しいのかはまったく明らかではありません。

于 2010-07-02T19:02:19.553 に答える
6

コンパイル時に構造体の型がわかっている場合、構造体メソッドはインライン化され、インターフェイスを介したメソッドの呼び出しは遅いため、答えは次のとおりです。パフォーマンス上の理由によるものです。

于 2010-07-02T18:46:48.563 に答える