このトピックに関する広大なインターネット開発で見つかったものは私を満足させませんでした。 たとえば、 Linq to Collectionsライブラリは、 IReadOnlyList <>インターフェイスに対してのみ一部のLINQ操作のみの効果的な置換を提供し、最適化が不十分であり、何らかの理由でベースタイプに多くのあいまいな拡張メソッドを強制的に追加します。 このテーマの別のプロジェクトであるCountableSharpは、 IReadOnlyCollection <>インターフェイスに対してのみ少数の最適化されたLINQ操作を提供します。この場合、ベースSystem.Linq.Enumerableへのすべての呼び出しを委任するデコレーターが返され、 Countプロパティのみが事前に計算され、完全なコレクションを繰り返し処理します。
さらに、 Collection.LINQライブラリを使用することをお勧めします。これは、 IReadOnlyインターフェイスを実装するコレクションのほとんどの操作の効率的な実装を提供することにより、LINQをオブジェクトに最適化します。
まず第一に、私は自分自身の小さな余談を許可します。 おそらくご存じのように、 IReadOnlyシリーズのインターフェイスには明らかにセット用のインターフェイスがありません。 したがって、このような明白な形式で自分で作成します 。
public interface IReadOnlyFiniteSet<T> : IReadOnlyCollection<T> { bool Contains (T item); }
ここで曖昧なのは、 IReadOnlyCollection <>の継承に関連付けられたセットの有限性だけです。 または、 IEnumerable <>から継承したIReadOnlySet <>の無限セットの中間インターフェイスを作成できます。 ただし、無限のセットは学術的な関心のみであり、どこで使用できるかを想像するのは難しいため、実用的な必要はありません。 基本クラスライブラリに存在するすべてのセットは、 IReadOnlyFiniteSet <>に必要なメソッドを既に実装しているため、実装に問題はありません。 次に、 IReadOnlyFiniteSet <>インターフェイスを含むLINQ操作の最適化について説明し、いつかはベースライブラリの一部となることを期待します。
そのため、 Collection.LINQで実装される操作と、それらが「オブジェクトへのLINQ」の効率をどの程度正確に向上させるかを検討します。
最適化が不要な操作を指定することから始めます。 集約メソッドAggregate()、Average()、Min()、Max()、Sum()は、 IEnumerable <>が必要で十分なインターフェイスであり、コレクションではなく値を返すため、最適化は必要ありません。 同じ理由で、オーバーロードメソッドAll()、Any()、Count()、LongCount()、First()、FirstOrDefault()、Last()、LastOrDefault()、Single()、SingleOrDefault()、最適化する必要はありません。述語フィルターパラメーターを取得します。 述語の存在は、 IEnumerable <>で十分な徹底的な検索の必要性を示します。 明らかに、ToArray()、ToList()、ToDictionary()、ToLookup()メソッドは、セマンティック上、完全な列挙とコピーの作成を意味するため、最適化も必要ありません。 ToArray()メソッドで配列を作成する場合のみ、コレクション内の要素の数を事前に知ることでわずかに最適化できます。
Empty()、Range()、およびRepeat()コレクションを作成するメソッドには、明らかな最適化が1つ必要です。これらは、ベースのIEnumerable <>ではなく、特定のIReadOnlyインターフェイスを返す必要があります。
次に、主な最適化について説明します。 コレクションを返す操作では、結果はデコレーターとして作成されます。デコレーターは、予備の反復や要素のコピーを作成することなく、メンバーへの呼び出しを入力コレクションに直接委任します。 複数の操作を順番に適用するときにこの最適化が機能するためには、返されるコレクションもIReadOnly型であることも重要です。 LINQ to Objectsには、同様の内部最適化が既に実装されています。多くのメソッドは、まず入力コレクションによって一部のインターフェイスの実装をチェックし、それらを使用してアクションをより効率的に実行します。 ただし、もちろん、 IReadOnlyインターフェイス(LINQが登場した時点では存在していなかった)は使用されず、返されるコレクションには常に基本型IEnumerable <>のみが含まれます。 私のライブラリでは、次のLINQ操作で直接装飾最適化が使用されています。
タイプIReadOnlyCollection <>のコレクションの場合 | |
Any()、Count()、LongCount() | Countプロパティを使用してすぐに結果を返します |
DefaultIfEmpty() | Countプロパティ、ソースまたは単一要素に応じて、IReadOnlyCollection <>を返します。 |
()、Zip()を選択します | return IReadOnlyCollection <>-デコレータ |
スキップ()、テイク() | Countプロパティ、オリジナル、空、またはデコレータに応じて、IReadOnlyCollection <>を返します。 |
連結() | ソースまたはデコレータのCountプロパティに応じて、IReadOnlyCollection <>を返します |
リバース() | IReadOnlyCollection <>を返します。Countプロパティ、列挙の要求時にコレクションの完全なコピーを作成するソースまたはデコレータに応じて |
OrderBy()、OrderByDescending()、ThenBy()、ThenByDescending() | IReadOnlyCollection <>を返します。Countプロパティ、列挙の要求時にコレクションの完全なコピーとソートされたインデックスを作成するソースまたはデコレーターに応じて |
タイプIReadOnlyFiniteSet <>のセット ( IReadOnlyCollection <>コレクションで利用可能なものに加えて) | |
含む() | Contains()メソッドを使用してすぐに結果を返します |
個別() | 元のIReadOnlyFiniteSet <>を返します-セット |
()、Intersect()、Union()を除く | IReadOnlyFiniteSet <>を返します。小さい方の入力セットで反復処理を作成する場合、 Countプロパティ、ソースプロパティの1つ、空またはデコレータに応じて |
DefaultIfEmpty() | Countプロパティ、ソースまたは単一要素に応じて、IReadOnlyFiniteSet <>を返します。 |
リバース() | IReadOnlyFiniteSet <>を返します。Countプロパティ、セットの完全なコピーを作成するソースまたはデコレーターに応じて |
タイプIReadOnlyList <>のリストの場合 (コレクションIReadOnlyCollection <>で使用できるものに加えて) | |
ElementAt()、ElementAtOrDefault()、First()、FirstOrDefault()、Last()、LastOrDefault() | IReadOnlyList <>メソッドを使用して結果をすぐに返す |
DefaultIfEmpty() | Countプロパティ、ソースまたは単一要素に応じて、IReadOnlyList <>を返します。 |
スキップ()、テイク()、セレクト()、連結()、ジップ()、リバース() | return IReadOnlyList <>-デコレータ |
OrderBy()、OrderByDescending()、ThenBy()、ThenByDescending() | IReadOnlyList <>を返します。Countプロパティに応じて、ソートされたインデックスを作成するソースまたはデコレーターは、位置番号と列挙によって要素の受信を委任します |
さらに、 IReadOnlyインターフェイスに関連しないCollection.Linqのいくつかの最適化について説明します。
- LINQ SequenceEqual()オペレーションは、このインターフェイスを実装するコレクション(3年後にLINQに登場)のIStructuralEquatableインターフェイスのEquals()メソッドへの直接委任として実装されます。
- オプションパラメーターの広範な使用(1年後にLINQが登場)により、メソッドのオーバーロードの数を大幅に削減しました。
- 明らかに欠落しているセット操作が追加されます-SymmetricExcept()メソッドの形式の対称的な違いです。
System.Linq.Enumerableライブラリよりも何も見逃さず、より効率的にしたことを確認するために、 ソースを常に確認しました。
Collection.LINQライブラリを使用するのは簡単です。コードを1行ずつ記述するだけで十分です。
using System.Linq;
行を追加
using BusinessClassLibrary.Collections.Linq;
クエリ構文の形式または流な形式でのLINQ to Objectsの使用方法は関係ありません。 ライブラリを接続した後、 IReadOnlyインターフェイスを実装するコレクションがinputである場合、LINQコードは自動的に最適なメソッドを使用します 。 唯一の例外は、拡張メソッドではないSystem.Linq.Enumerableクラスのコレクションを作成するためのメソッドです:Enumerable.Empty()、Enumerable.Range()、およびEnumerable.Repeat()。 これらのメソッドは、それぞれ手動でReadOnlyFiniteSet.Empty()/ ReadOnlyList.Empty()、ReadOnlyFiniteSet.Range()/ ReadOnlyList.Range()およびReadOnlyList.Repeat()に置き換える必要があります。
ライブラリ全体は、githubの公開プロジェクトで提示されます。 読者の要望に応じて、ライブラリを含むヌゲットパッケージも作成されました。