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


8

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

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

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

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


4
「住所」のドイツ語版である「Anschrift」では、これは国に依存しない住所タイプの国固有の変換のようです。国別コードを別の場所に保持することは理にかなっています。これは実際の質問に対する回答ではありませんが、この例で彼らがなぜそれをしたのかを説明しています。
R.シュミッツ2018年

1
»拡張メソッドを使用すると、新しい派生型を作成したり、再コンパイルしたり、元の型を変更したりせずに、既存の型にメソッドを「追加」できます« docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…。»実装したクラスライブラリの場合、拡張メソッドを使用しないでください...ソースコードを所有するライブラリに重要な機能を追加する場合は、アセンブリのバージョン管理に関する標準の.NET Frameworkガイドラインに従う必要があります« tl; drコードを所有している場合は、拡張メソッドは必要ありません。
トーマスジャンク

回答:


15

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


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

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

1.通常、それらは静的メソッドを置き換えます。

エクステンションメソッドは、クラスメソッドではなく、静的メソッドであることに基づいています。それらはクラスメソッドのように見えますが、実際にはそうではありません。

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

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

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

例えば:

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

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

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

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

たとえば、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
        };
    }
}

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

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

この引数はスタイルの1つであることに注意してください。これは部分クラスに似ています。そうすることには技術的なメリットはほとんどありませんが、コードがよりクリーンであると考える場合(たとえば、多くの人がクラスを使用しているが、追加のカスタムメソッドは気にしない場合)、コードを複数のファイルに分離できます。

4.ヘルパーメソッド

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

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

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

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

DateTimeHelper.ToCompanyFormat(myObj.CreatedOn);

私はそれをできた:

myObj.CreatedOn.ToCompanyFormat();

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


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

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

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

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

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

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

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

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

5
ポイント2.は、私が最も説得力があると思うものであり、私がこれを自分で行った理由です。場合によっては、コードベース全体で広く使用されているクラスと、カプセル化するプロジェクトまたはサードパーティのライブラリが含まれていることを除いて、クラスにあるはずのように見えるメソッドがある場合があります。独自のクラスの拡張メソッドは、これを処理する論理的な方法です。
アストリッド

@Astrid:それが最も技術的な理由だと思います。はい。
2018年

とてもいい答えです。C#の特定の使用例の1つは、インターフェイスに動作がないことです。拡張メソッドにより、メソッドを提供できます。
RubberDuck、2018年

「技術的メリット」とはどういう意味ですか?パフォーマンスのようなもの?コードの可読性は、それ以上ではないにしても、同様に重要です。
Robert Harvey

@RobertHarvey技術的には、構文上の砂糖だけではないことを意味します。たとえば、部分クラスを同じアセンブリ内の多くのファイルに分散しても、技術的な利点はありません。クラスとその拡張メソッドをさまざまなアセンブリに分散します。もちろんコードの可読性は依然として重要ですが、技術的な観点からは重要ではありません。
2018年

5

慣習具体化するのに十分な時間はなかったと私は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)。私はもっ​​といいと思います。


4
LINQが拡張メソッドのセットとして実装された理由は、LINQがインターフェイスを介した共通の操作のセットになることを意図しており、インターフェイスに実装を配置できないためです。それはあなたのポイント(1)と(2)とは実際には何の関係もありません。これは、同じ問題に対するJavaのソリューションとは対照的です。これは、インターフェース定義での実装を許可することで、インターフェースの乱用の世界への扉を開きます。
Ant P

「インターフェイスに実装を置くことはできません」。まだではありませんが、C#8がマシンにヒットした場合、その説明は真実ではなくなります:)
David Arno

@DavidArno本当に?私はC#の世界からしばらく離れています。残念です。私には一歩後退しているように見えます...
Ant P

4

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

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

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

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

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

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

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


1
Fluent APIの場合は+1。
Robert Harvey

@RobertHarvey、「同じアセンブリ内」の見出しの下の最後の段落?そのアセンブリのソースコードがあれば、それを使って何でも好きなことができますが、コンパイルされると封印されます。
Berin Loritsch

どうやら私のポイントを効果的に伝えていなかったので、コメントを削除しました。
ロバートハーヴェイ

1

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

拡張メソッドを使用する最も一般的な理由の1つは、インターフェースを「変更するのではなく拡張する」手段としてです。むしろ持っているよりもIFoo及びIFoo2、ここで後者を導入新しい方法BarBarに追加されるIFoo拡張メソッドを介し。

LINQのは、のための拡張メソッドを使用する理由の一つWhereSelectなどは、ユーザーがその後、あまりにもLINQクエリの構文で使用されているこれらのメソッドの独自のバージョンを定義することを可能にするためにあります。

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

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

私が持っているのであればOption<T>、私のようなもの置くところHasValueValue==型自体の内部などを。しかし、のようなものMapからモナドを変換する関数、T1にはT2、私は、拡張メソッドに入れたいです:

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

1
One of the reasons Linq uses extension methods for Where, Select etc is in order to allow users to define their own versions of these methods, which are then used by the Linq query syntax too. -それでよろしいですか?Linq構文はラムダ式(つまり、ファーストクラスの関数)を広範囲に使用するため、独自のバージョンのSelectおよびを作成する必要はありませんWhere。私は、人々がこれらの関数の独自のバージョンを展開しているケースを知りません。
ロバートハーヴェイ

そうは言っても、あなたの答えはこれまでのすべての答えの中で最も説得力があると思います。拡張メソッドは、インターフェイスを進化させる優れた方法です。これが、Linq用に作成された理由です。
Robert Harvey


@RobertHarvey if (!predicate(item))そのコードのをに切り替えると、if (predicate(item))私のバージョンのを使用しているため、結果が変化しWhereます。
David Arno

はい、わかりました。しかし、誰もこれを行うことはありませんでした。彼らは単に述語を変更します。それが述語を持つことの要点です。関数を変更せずに条件を変更します。
ロバートハーベイ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.