6


5

辞書値c#
Net team decided on not changing the value when looping over

辞書。 キーを変更できない場合は理解できますが、なぜ価値があるのでしょうか? また、LINQを使用しており、キーをIenumerableの形式に戻す場合、値を変更できますか? 遅延読み込みには果たすべき役割がありますか?

4 Answer


10


_ BCLチームは、辞書をループするときに_value_への変更を許可しないことに決めたのはなぜですか。 _key_は変更できないことを理解していますが、なぜ値の変更を許可しないのですか? _

辞書を作成したチームの_definitively_を話すことはできませんが、教育を受けた_guesses_を作成することはできます。

第一に、良いプログラムは出力の正確さについて厳しいと言われますが、受け入れるものは許します。 _これが良い設計原則であるとは思わない。_これは、_バギーな呼び出し側_が_未定義_および_未サポート_の振る舞いに_依存関係をとることができるため、_悪い_の設計原則である。 これにより、下位互換性の負担が生じます。 (確かにこれは、すべてのブラウザーベンダーが他のすべてのブラウザーが誤ったHTMLを受け入れることとの互換性を要求されるWebブラウザーの世界で見られます。)

コンポーネントはコントラクトを定義します。入力として受け入れる情報、サポートする操作、生成する結果を示します。 契約にない入力を受け入れる場合、または契約にない操作をサポートする場合、あなたがしていることは、基本的に_new_契約、文書化されておらず、サポートされておらず、将来壊れる可能性のある契約を作成することですバージョン。 あなたは基本的に時限爆弾を構築していて、それが消えると、顧客(おそらくサポートされていないことをしていることさえ知らなかった)が壊れるか、コンポーネントプロバイダーが彼らがやった契約を永久にサポートしなければならない実際にはサインアップしません。

したがって、_契約を厳密に施行することは、優れた設計原則です。 コレクションに対するIEnumerableの規約は、「反復中にコレクションを変更することは違法です」。 この契約の実装者は、「特定の変更が安全であることを知っているので、それらを許可します」と言うことを選択できます。そして、突然、あなたはもう契約を実装していません。 人々が依存するようになる、文書化されていない別の契約を実装しています。

契約が不要な場合でも、単純に契約を「強制」する方が適切です。 そうすることで、将来、一部の発信者が契約を破ってそれを取り消すことを心配することなく、文書化された契約に依存する自由があり、いつまでもそうすることができると期待しています。

反復中に値を変更できる辞書を設計するのは簡単です。 そうすることで、コンポーネントプロバイダーがその機能をオフにすることができなくなります。また、提供する必要はありません。そのため、発信者が契約に違反することを許可するよりも、誰かがエラーを発生させる方がよいでしょう。

2番目の推測:辞書タイプは封印されていないため、拡張できます。 サードパーティは、列挙中に値が変更された場合に不変式に違反するような方法で辞書を拡張する場合があります。 たとえば、誰かが辞書を拡張して、_value_でソートできるように列挙したとします。

ディクショナリを取得してそれに何らかの処理を行うメソッドを作成する場合、サードパーティの拡張機能でさえも、すべてのディクショナリで動作することを想定しています。 拡張を目的とするコンポーネントの設計者は、未知の第三者がその契約の実施に依存している可能性があるため、オブジェクトがその契約を実施することを保証するために、通常よりもさらに注意する必要があります。 反復中に値を変更することをサポートできない辞書が存在する可能性があるため、基本クラスもそれをサポートするべきではありません。そうでなければ、派生クラスの基本クラスの代替可能性に違反することになります。

_ また、LINQを使用していて、IEnumerableの形式でキーを取得している場合、値を変更できますか? _

規則は、繰り返し処理中に辞書が変更されない可能性があるということです。 LINQを使用して反復を行うかどうかは関係ありません。反復は反復です。

_ 遅延読み込みには果たすべき役割がありますか? _

もちろんです。 LINQクエリを定義しても、何も繰り返されないことに注意してください。クエリ式の結果はクエリオブジェクトです。 実際の反復がコレクションに対して発生するのは、そのオブジェクトに対して反復する場合のみです。 あなたが言う時:

var bobs = from item in items where item.Name == "Bob" select item;

ここでは繰り返しは発生しません。 あなたが言うまではありません

foreach(var bob in bobs) ...

アイテムの繰り返しが発生します。


4


KeyValuePairは構造体であり、可変構造体は悪であるため、読み取り専用になりました。

一部の値を変更するには、KeyValuePairsを反復処理し、必要なすべての更新を保存できます。 繰り返しが完了したら、更新のリストをループして適用できます。 LINQを使用せずに実行する方法の例を次に示します。

    Dictionary dict = new Dictionary();
    dict["foo"] = "bar";
    dict["baz"] = "qux";

    List> updates = new List>();
    foreach (KeyValuePair kvp in dict)
    {
        if (kvp.Key.Contains("o"))
            updates.Add(new KeyValuePair(kvp.Key, kvp.Value + "!"));
    }

    foreach (KeyValuePair kvp in updates)
    {
        dict[kvp.Key] = kvp.Value;
    }


1


理由はわかりませんが、間接的に値を参照することでこの制限を回避できます。 たとえば、次のような別のクラスを単純に参照するクラスを作成できます。

class StrongRef
{
    public StrongRef(ObjectType actualObject)
    {
        this.actualObject = actualObject;
    }

    public ObjectType ActualObject
    {
        get { return this.actualObject; }
        set { this.actualObject = value; }
    }

    private ObjectType actualObject;
}

次に、代わりにdicitonaryの値を変更すると、間接参照を変更できます。 (次のルーチンは、死んだ犬を同一の生きている動物に置き換えるために、ブルーピーターによって使用されているということを、良い権威で聞いたことがあります。)

public void ReplaceDeadDogs()
{
    foreach (KeyValuePair> namedDog in dogsByName)
    {
        string name = namedDog.Key;
        StrongRef dogRef = namedDog.Value;

        // update the indirect reference
        dogRef.ActualObject = PurchaseNewDog();
    }
}

他の代替方法は、反復時に必要な変更を2番目のディクショナリーに記録してから、これらの変更を後で適用する(2番目のディシトナリーを反復する)か、ループの完了後に元の代わりにこれを反復して使用しながら完全な置換ディクショナリーを構築することです。


0


このMicrosoftサポートの返信(「ハッシュテーブル」に基づいていますが、同じことが当てはまります)

_ ここで重要なのは、列挙子がコレクション全体のスナップショットを提供するように設計されていることですが、パフォーマンス上の理由から、コレクション全体を別の一時配列またはそのようなものにコピーしません。 代わりに、ライブコレクションを使用し、誰かがコレクションを変更したことを検出すると例外をスローします。 はい、それはこの種のコードを書くのを難しくします…​ _