おそらく、ほとんどすべての.NET開発者は、標準的なデータ型を扱う際のルーチンアクションのコーディングと定型コードの削減の利便性のために、標準ライブラリの機能が十分でない場合に遭遇しました。
ほとんどすべてのプロジェクトで、Common、ProjectName.Commonなどの形式のアセンブリと名前空間が表示されます。これらには、列挙型列挙、Nullable構造、文字列、コレクション-IEnumerable <T>列挙、配列、リストとコレクション。
原則として、これらの追加は拡張メソッドメカニズムを使用して実装されます。 多くの場合、拡張メソッドのメカニズムに基づいて構築されたモナド実装の存在を観察できます。
(今後、予期せず発生する問題を検討します。IEnumerable<T>の独自の拡張機能を作成し、IQueryable <T>で作業を行っているときに気付かない場合があります)。
この記事の執筆は、長年の記事「 空の転送のチェック 」の翻訳と発展した議論を読んで触発されました。
記事は古いですが、特に記事の例に似たコードはプロジェクトごとに実際の作業で遭遇する必要があったため、トピックは依然として関連しています。
元の記事では、作業プロジェクトに追加されたCommon-library全体に本質的に関係する質問が提起されました。
問題は、製品プロジェクトでこのような拡張機能が急いで追加されることです。 開発者は新しい機能の作成に携わっていますが、基本的なインフラストラクチャを作成、検討、デバッグする時間はありません。
さらに、原則として、開発者は、彼に必要な共通の補数を追加して、この追加がその機能からのケースに合わせて調整されるように作成し、これが一般的な追加であるため、客観的なロジックと標準プラットフォームライブラリで行われているように、普遍的である。
その結果、プロジェクトの多数のCommonサブフォルダーで、 元の記事で指定されたコードのデポジットが取得されます。
public void Foo<T>(IEnumerable<T> items) { if(items == null || items.Count() == 0) { // } }
著者は、Count()メソッドの問題を指摘し、そのような拡張メソッドの作成を提案しました。
public static bool IsNullOrEmpty<T>(this IEnumerable<T> items) { return items == null || !items.Any(); }
しかし、そのような方法の存在はすべての問題を解決するわけではありません:
- コメントでは、Any()メソッドが1回の反復を行うというトピックで議論が開始されました。これは、コレクション(IsNullOrEmptyをチェックした後)の後続の反復が最初からではなく、2番目の要素から実行されたときに問題を引き起こす可能性があります。彼女は知りません
- Any()メソッドが検証用に別のイテレータを作成するという異議を受け取った(これは特定のオーバーヘッドであることに注意してください)。
ここで、実際の「無限」シーケンスIEnumerable <T>-配列、リスト、およびコレクション自体を除くすべての標準.NETコレクションは、Countプロパティを提供する標準のIReadOnlyCollection <T>インターフェイスを実装します。請求書付きのイテレーターは不要です。費用。
したがって、2つの拡張メソッドを作成することをお勧めします。
public static bool IsNullOrEmpty<T>(this IReadOnlyCollection<T> items) { return items == null || items.Count == 0; } public static bool IsNullOrEmpty<T>(this IEnumerable<T> items) { return items == null || !items.Any(); }
この場合、IsNullOrEmpty <T>を呼び出すと、拡張機能が呼び出されるオブジェクトのタイプに応じて、コンパイラーによって適切なメソッドが選択されます。 どちらの場合も、呼び出し自体は同じように見えます。
ただし、さらに議論の中で、コメンテーターの1人が、おそらく、IQueryable <T>(IEnumerable <T>から継承したデータベースクエリを処理するための「無限」シーケンスのインターフェイス)に対して、Count()メソッドの呼び出しが最も最適であると指摘しました。
このバージョンでは、さまざまなORM(EF、EFCore、Linq2Sqlで動作するかどうかのチェックを含む)を検証する必要があります。検証する場合は、3番目のメソッドを作成する必要があります。
実際、IQueryable <T>には、Any()、Count()、およびコレクションを操作する他のメソッド(System.Linq.Queryableクラス)の拡張実装があり、ORMで動作するように設計されています。 IEnumerable <T>(クラスSystem.Linq.Enumerable)。
この場合、おそらく、クエリ可能なバージョンのAny()は、クエリ可能なバージョンのCount()== 0よりもさらに最適に動作します。
必要なクエリ可能バージョンのAny()またはCount()を呼び出すために、IsNullOrEmptyチェックを呼び出すには、IQueryable <T>入力パラメーターを持つ新しいメソッドが必要です。
したがって、3番目のメソッドを作成する必要があります。
public static bool IsNullOrEmpty<T>(this IQueryable<T> items) { return items == null || items.Count() == 0; }
または
public static bool IsNullOrEmpty<T>(this IQueryable<T> items) { return items == null || !items.Any(); }
その結果、「空」のコレクションの単純なnullセーフチェックを実装するために、すべてのケース(すべての場合)を修正するには、少し調査を行い、3つの拡張メソッドを実装する必要がありました。
そして、最初の段階でメソッドの一部のみ、たとえば最初の2つだけを作成した場合(これらのメソッドは必要ありません。製品の機能を作成する必要があります)、次のようになります。
- これらのメソッドが登場するとすぐに、製品コードで使用され始めます。
- ある時点で、IsNullOrEmptyのEnumerableバージョンへの呼び出しはORMコードに浸透し、これらの呼び出しは確かに最適に機能しません。
- 次に何をする? メソッドのクエリ可能なバージョンを追加し、プロジェクトを再構築しますか? (新しい拡張メソッドのみを追加し、製品コードには触れません。再構築後、必要なメソッドへの切り替えは自動的に行われます。)これには、製品全体の回帰テストが必要になります。
同じ理由で、これらのすべてのメソッドを1つのアセンブリと1つの名前空間(たとえば、EnumerableExtensionsとQueryableExtensionsなどの異なるクラスで可能)に実装することが望ましいので、誤って名前空間またはアセンブリを切断した場合、IQueryable <T>のときの状態に戻りませんコレクションは、通常のEnumerable拡張を使用して機能します。
私の意見では、ほとんどすべてのプロジェクトにこのような拡張機能が豊富にあることは、標準ライブラリとプラットフォームモデル全体の精緻化が不十分であることを示しています。
プラットフォームでNot Nullabilityがサポートされていれば、問題の一部は自動的に解決されます。別の部分-標準ライブラリが、標準データ型を扱うためのより広範な拡張ケースを考慮している場合。
さらに、現代的な方法で実装されています-それは、一般化(ジェネリック)を使用した拡張の形式です。
これについては、次の記事で詳しく説明します。
PS興味深いことに、Kotlinとその標準ライブラリを見ると、その開発中に他の言語の経験が明らかに慎重に研究されました。まず、私の意見では、Java、C#、Rubyです。豊富な拡張機能が存在する場合、標準タイプで動作するためにマイクロライブラリの独自の「自転車」実装を追加する必要はありません。