BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース .NET Core 3.0 での Systems.Collections

.NET Core 3.0 での Systems.Collections

ブックマーク

原文(投稿日:2019/01/30)へのリンク

.NET 3.0 の System.Collections タグが付けられた issue のリストに目を通すと、受理されたものと却下されたものがそれぞれあることがわかる。このレポートでは、それらの主要なものをいくつか取り上げる。

IList<T>.RemoveAll とデフォルトインターフェイスメソッド

IList<T> は、要素を特定するかインデックスを指定すると要素を取り除くことができるが、予測に基づいて要素を取り除くための手段が無い。理論的には、この機能は .NET の新しいバージョンに破壊的変更をせずに取り入れることができる。これはデフォルトインターフェイスメソッドを用いることによって実現できる。

これをサポートするための第一歩は.NET Standard 2.1 におけるデフォルトインターフェイスメソッドだ。しかしこの機能は .NET Framework 4.8 では提供されない。このバージョンは、オリジナルの .NET Framework の最後のメジャーバージョンだと考えられている。

結果として、もし IList<T> が変更された場合、 .NET Standard と .NET Framework は永久に異なるバージョンをもつことになる。すなわち、 IList<T> を明示的に実装する場合 #if ブロックを用いて区別しなければならない。このインターフェイスの暗黙的な実装やデフォルト実装を許可する場合は、このシナリオは問題とはならない。

今のところ、デフォルトインターフェイスメソッドを実際に使用することは、 .NET Standard 2.1 で提案されたとしても、差し当たって計画されていない。他の課題、例えば C# 以外の言語におけるサポートなどは、依然として検討の余地がある。 Microsoft のプログラムマネージャーである Immo Landwerth 氏は “VB と F# がこれらをフルサポートしたときには、基本クラスライブラリでも活用することになるだろう ” と述べている。

BitArray の最適化

Streaming SIMD Extensions 2 としても知られる SSE2 は、2000年の古いCPU技術だ。大規模データを扱う際にパフォーマンスを発揮するが、採用が遅くなった。例えば Visual C++ では2012年まで既定での SSE2 サポートが有効化されていなかった。 .NET の世界では Microsoft の .NET 実装より先に Mono が2008年に SIMD コードをサポートしていた。

BitArray にとって SSE2 の利用は巨大な配列を扱う際のパフォーマンスブーストに大いに役立つ。 1000個 の整数に対する バイナリ OR 演算をテストすると、平均計算時間は 656.6ns から 169.8ns に減少した。この改善は小さな配列にとっては効果がなく、 8個 の整数 (256ビット) より少ない場合だと SSE2 を用いた方が遅くなることもある。プルリクエストの SSE2 のコード以外に 小さな配列に対して行われる特別対応 も参照可能だ。

Span<T>BitArray を最適化するために用いられる。 SSE2 と異なり、 Span<T> を使用するために必要な変更によってコード自体もシンプルになった部分もある

イミュータブルコレクションビルダーとピットオブサクセス

.NET API 設計のコアコンセプトは、 “pit of success” として知られる。ある API はクラスを不適切に使用することを不便にする、あるいは困難にするべきだ、というのが基本的な考え方だ。対象的に、最も容易な手段が、とるべき正しい手段となる。 proposal #21055 でその例を見ることができる。

イミュータブルコレクションを扱う場合、ミュータブルな “ビルダー” オブジェクトを導入すると便利だ。 ImmutableArray.Builder や ImmutableDictionary.Builder などを使用した場合、 ToImmutable() を呼び出して、対応するイミュータブルコレクション型に固定することが想定されている。

しかし、他の種類のコレクションをソースとして使用する場合は IEnumerable<T>.ToImmutableArray()IEnumerable<T>.ToImmutableDictionary() などを呼び出す必要がある。これらはジェネリックな拡張メソッドであり、ビルダーのネイティブな ToImmutable() メソッドを使用した場合よりも遅い。しかし慣習と一貫性から、多くの開発者たちは拡張メソッドを使用している。より良い選択肢があることを知らないこともある。

ピットオブサクセスを生み出すために、 ImmutableArray.Builder とその仲間が、対応する ToImmutable() メソッドに単純にリダイレクトするだけの ToImmutableXxx メソッドを追加している。それらはインスタンスメソッドとして追加されるため、拡張メソッドと同名で置き換えられる。

Dictionary を列挙する際の要素削除

コレクションから要素を削除することは時に苛立たしい。コレクションは列挙の途中で変更することができないからだ。何らかの方法でコレクションを変更すると、列挙子は無効とマークされ、破棄される。

回避策はコレクションのコピーを作成することだ。コピーを列挙する間にお路地なるを変更することができる。しかしこれはコストがかかる方法だ。とくに Dictionary<K,V> の場合はハッシュコードを2回計算することになり、顕著だ。

前述した列挙子の無効化は “version” と呼ばれる内部フィールドによって扱われる。単純に要素が削除されたときにバージョン値を加算しないことによって、先にコピーを作成する必要がなくなる。

一方で TrimExcess()EnsureCapacity() メソッドは、これらが “列挙を破壊する可能性がある方法でバッキングストレージを変更しうるため” バージョン加算が追加された。いずれかのメソッドが for-each ループ内部で呼び出された場合、これは破壊的変更となりうる。

ObservableCollection、AddRange、そして WPF

Collection<T>ObservableCollection<T> クラスによくある批判は AddRange メソッドが無いことだ。 List<T> クラスには存在するこのメソッドは、ループで毎回追加するよりもはるかに効果的にコレクションを組み合わせることができる。同様のメソッドとして InsertRange、RemoveRange、そして RaplaceRange がある。

これら存在しないメソッドを追加することは一見簡単に見える。 ObservableCollection<T>, の場合、 CollectionChanged イベントによってすでに複数の要素が追加されたり取り除かれたことが検知できる。

この “簡単な修正” による問題は WPF だ。10年以上前の設計上の欠陥により、コレクションを扱うビルトインコントロールのほとんどが、1つ以上の要素の変更イベントを扱うことができない。 WPF はインターフェイスをプログラミングするのではなく、 ObservableCollection<T> の特定の実装で動作するように設計されている。すなわち、そこに AddRange メソッドを追加することは破壊的変更と同様である。

WPF を修正してコレクションの変更イベントを正しく扱えるようにする要求は、将来的な改善としてタグ付けされている。一方で Collection<T> ObservableCollection<T>範囲追加する設計の詳細は引き続き議論されている。

この記事に星をつける

おすすめ度
スタイル

BT