web-dev-qa-db-ja.com

あなた自身のクラスの拡張メソッドをいつ書くべきですか?

私は最近、データクラスAddressがどこかで定義され、次に別の場所で定義されているコードベースを見ました。

fun Address.toAnschrift() = let { address ->
    Anschrift().apply {
        // mapping code here...
    }
}

このメソッドをアドレスに直接指定しないと混乱を招きました。拡張メソッドを使用するときに確立されたパターンまたはベストプラクティスはありますか?それとも、その本はまだ書かれている必要がありますか?

この例のKotlin構文にも関わらず、C#またはそれらの機能を備えた他の言語にも適用されるので、一般的なベストプラクティスに興味があることに注意してください。

8

拡張メソッドは、他の方法では実行できないものを提供しません。それらは、実際の技術的な利点を提供することなく、開発をより良くするための構文上の砂糖です。


拡張メソッドは比較的新しいものです。標準と慣習は通常、世論によって決定されます(さらに、何が悪いアイデアになるかについての長期的な経験も)、誰もが同意する明確な線を引くことができるようになったとは思いません。

しかし、同僚から聞いたことに基づいて、いくつかの議論を見ることができます。

1。通常は静的メソッドを置き換えます。

拡張メソッドは、通常、クラスメソッドではなく静的メソッドであるはずのものに基づいています。それらはクラスメソッドのように見えますが、実際にはそうではありません。

拡張メソッドは、クラスメソッドの代替と見なすべきではありません。むしろ構文的にugle staticメソッドに「クラスメソッド」のルックアンドフィールを与える方法として。

2。意図した方法が、ソースライブラリではなく、消費プロジェクトに固有である場合。

ライブラリとコンシューマーの両方を開発したからといって、適切な場所にロジックを配置しても構わないとは限りません。

例えば:

  • PersonDtoプロジェクトからのDataLayerクラスがあります。
  • WebUIプロジェクトがPersonDtoPersonViewModelに変換できるようにしたいと考えています。

この変換方法をDataLayerプロジェクトに追加することはできません(したくありません)。このメソッドはWebUIプロジェクトをスコープとするため、そのプロジェクト内に存在する必要があります。

拡張メソッドを使用すると、DataLayerライブラリを実装する必要なく、プロジェクト(およびプロジェクトのコンシューマー)内でこのメソッドにグローバルにアクセスできます。

。データクラスにデータのみを含める必要があると思われる場合

たとえば、Personクラスには、個人のプロパティが含まれています。ただし、一部のデータをフォーマットするいくつかのメソッドを使用できるようにしたいとします。

public class Person
{
    //The properties...

    public SecurityQuestionAnswers GetSecurityAnswers()
    {
        return new SecurityQuestionAnswers()
        {
            MothersMaidenName = this.Mother.MaidenName,
            FirstPetsName = this.Pets.OrderbyDescending(x => x.Date).FirstOrDefault()?.Name
        };
    }
}

クラスでデータとロジックを混ぜるのが嫌いな同僚をたくさん見ました。データのフォーマットなどの単純なことにはあまり同意しませんが、上の例では、PersonSecurityQuestionAnswersに依存する必要があるのは不快だと思います。

このメソッドを拡張メソッドに配置すると、純粋なデータクラスを破壊することを防ぎます。

この引数はスタイルの1つであることに注意してください。これは部分クラスに似ています。そうすることには技術的なメリットはほとんどありませんが、コードを複数のファイルに分割することができます。

4。ヘルパーメソッド

ほとんどのプロジェクトでは、ヘルパークラスを作成する傾向があります。これらは通常、簡単なフォーマットのためのメソッドのコレクションを提供する静的クラスです。

例えば。私が働いていたある会社には、使用したい特定の日時形式がありました。すべての場所にフォーマット文字列を貼り付けたり、フォーマット文字列をグローバル変数にしたりする代わりに、DateTime拡張メソッドを選択しました。

public static string ToCompanyFormat(this DateTime dt)
{
    return dt.ToString("...");
} 

通常の静的ヘルパーメソッドよりも優れていますか?私はそう思う。構文をクリーンアップします。の代わりに

DateTimeHelper.ToCompanyFormat(myObj.CreatedOn);

私はそれをできた:

myObj.CreatedOn.ToCompanyFormat();

これは、同じ構文の流暢バージョンに似ています。どちらか一方を持っていることによる技術的なメリットはありませんが、私はもっと好きです。


これらの議論はすべて主観的です。それらのいずれもそうでなければカバーすることができなかったケースをカバーしません。

  • すべての拡張メソッドは、通常の静的メソッドに簡単に書き直すことができます。
  • 拡張メソッドが存在しなくても、コードは部分クラスを使用してファイルに分離できます。

しかし、繰り返しになりますが、これらは極端に必要なものではないというあなたの主張を受け入れることができます。

  • 特定のクラス用であることを明確に示す名前を持つ静的メソッドを常に作成できるのに、なぜクラスメソッドが必要なのでしょうか。

この質問とあなたの質問への答えは同じです。

  • より良い構文
  • 可読性の向上とコードペダントリーの削減(コードは少ない文字数でポイントに到達します)
  • Intellisenseは、コンテキスト上意味のある結果を提供します(拡張メソッドは正しいタイプでのみ表示されます。静的クラスはどこでも使用できます)。

ただし、特筆すべき点は次のとおりです。

  • 拡張メソッドは、メソッドチェーンのコーディングスタイルを可能にします。これは、バニラメソッドのラッピング構文が多用されているのとは対照的に、最近人気が高まっています。同様の構文設定は、LINQのメソッド構文で見ることができます。
  • メソッドチェーンはクラスメソッドを使用しても実行できますが、これは静的メソッドにはまったく当てはまらないことに注意してください。エクステンションメソッドは、クラスメソッドではなく、静的メソッドであることに基づいています。
15
Flater

私は Flater に同意します。慣習を結晶化するのに十分な時間はありませんでしたが、.NET Frameworkクラスライブラリ自体の例はあります。 LINQメソッドが.NETに追加されたとき、それらはIEnumerableに直接追加されたのではなく、拡張メソッドとして追加されたことを考慮してください。

これから何が取れますか? LINQは

  1. 常に必要とは限らない機能のまとまりのセット、
  2. メソッドチェーンスタイルで使用するためのものです(例:A.B().C()の代わりにC(B(A)))。
  3. オブジェクトのプライベート状態へのアクセスを必要としません

これら3つの機能をすべて備えている場合、拡張メソッドは非常に理にかなっています。実際、メソッドのセットに機能#3と最初の2つの機能のいずれかがある場合、それらを拡張メソッドにすることを検討する必要があると私は主張します。

拡張メソッド:

  1. クラスのコアAPIを汚染しないようにすることができます。
  2. メソッドチェーンスタイルを許可するだけでなく、null値から呼び出された拡張メソッドが最初の引数としてnullを受け取るだけなので、null値をより柔軟に処理できます。
  3. アクセスすべきではないメソッドで、プライベート/保護された状態に誤ってアクセス/変更しないようにします。

拡張メソッドにはもう1つの利点があります。それでも、静的メソッドとして使用でき、値として直接高次関数に渡すことができます。したがって、MyMethodが拡張メソッドである場合、たとえばmyList.Select(x => x.MyMethod())ではなく、単にmyList.Select(MyMethod)を使用できます。私はもっ​​といいと思います。

5
James Jensen

拡張メソッドの原動力は、シールされているか別のアセンブリにあるかに関係なく、特定のタイプのすべてのクラスまたはインターフェースに必要な機能を追加できることです。この証拠はLINQコードで確認でき、すべての関数に関数を追加しますIEnumerable<T>オブジェクト。リストでもスタックでもかまいません。コンセプトは、将来関数を証明することであり、独自のIEnumerable<T> LINQを自動的に使用できます。

とは言っても、言語機能は十分に新しいため、いつ使用するか使用しないかについてのガイドラインはありません。ただし、以前は不可能であった多くのことが可能になります。

  • Fluent API(ほぼすべてのオブジェクトにFluent APIを追加する方法の例については Fluent Assertions を参照)
  • 機能の統合(NetCore Asp.NET MVCは拡張メソッドを使用して、依存関係とスタートアップコードの機能を結び付けます)
  • 機能の影響をローカライズする(冒頭のステートメントの例)

公平を期すために、時間だけがどれだけ遠すぎるか、またはどの時点で拡張メソッドが恩恵ではなく障害になるかを知ることができます。別の答えで述べたように、静的メソッドでは実行できない拡張メソッドには何もありません。ただし、慎重に使用すると、コードが読みやすくなります。

同じアセンブリで意図的に拡張メソッドを使用するのはどうですか?

コア機能を十分にテストおよび信頼して、絶対に必要になるまでそれをいじらないことには価値があると思います。新しいコードに依存するが、機能を提供する必要がある新しいコード新しいコードの利点のために拡張メソッドを使用して、新しいコードをサポートする機能を追加できます。これらの関数は新しいコードにのみ関係し、拡張メソッドのスコープはそれらが宣言されている名前空間に限定されます。

拡張メソッドの使用を盲目的に排除するつもりはありませんが、十分にテストされた既存のコアコードに新しい機能を本当に追加する必要があるかどうかを検討します。

4
Berin Loritsch

拡張メソッドを使用するときに確立されたパターンまたはベストプラクティスはありますか?

拡張メソッドを使用する最も一般的な理由の1つは、インターフェースを「変更するのではなく拡張する」手段としてです。 IFooIFoo2ではなく、後者が新しいメソッドBarを導入するのではなく、Barが拡張メソッドを介してIFooに追加されます。 。

LinqがWhereSelectなどに拡張メソッドを使用する理由の1つは、ユーザーがこれらのメソッドの独自のバージョンを定義できるようにするためです。これらのメソッドは、Linqクエリ構文でも使用されます。

それを超えて、私が使用するアプローチは、少なくとも.NETフレームワークで一般的に見られるものに適合しているようです。

  1. クラスに直接関連するメソッドをクラス自体の中に置く、
  2. クラスのコア機能に関連しないすべてのメソッドを拡張メソッドに配置します。

したがって、Option<T>がある場合、HasValueValue==などを型自体の中に入れます。しかし、モナドをT1からT2に変換するMap関数のようなものとして、拡張メソッドを追加します。

public static Option<TOutput> Map<TInput, TOutput>(this Option<TInput> input, 
                                                   Func<TInput, TOutput> f) => ...
1
David Arno