C#4.0でオーバーロードまたはオプションのパラメーターを使用してメソッドを宣言する必要がありますか?


93

C#4.0についてのアンダースの話とC#5.0のプレビューを見ていて、C#でオプションのパラメーターが利用できる場合、すべてのパラメーターを指定する必要のないメソッドを宣言するために推奨される方法は何かを考えましたか?

たとえば、何かFileStreamのクラスは、論理の家族'という文字列から、以下の例のものに分けることができる15程度異なるコンストラクタ、からのものがあるIntPtrとAからのものをSafeFileHandle

FileStream(string,FileMode);
FileStream(string,FileMode,FileAccess);
FileStream(string,FileMode,FileAccess,FileShare);
FileStream(string,FileMode,FileAccess,FileShare,int);
FileStream(string,FileMode,FileAccess,FileShare,int,bool);

このタイプのパターンは、代わりに3つのコンストラクターを使用し、デフォルトのパラメーターにオプションのパラメーターを使用することで簡略化できるように思えます。これにより、コンストラクターの異なるファミリーをより明確にすることができますBCLで作成された、私はこの種の状況について仮説的に話している]。

どう思いますか?C#4.0以降では、コンストラクターとメソッドの密接に関連するグループをオプションのパラメーターを持つ単一のメソッドにする方が理にかなっていますか、それとも従来の多くのオーバーロードメカニズムに固執する十分な理由がありますか?

回答:


120

以下を検討します。

  • オプションのパラメーターをサポートしない言語からコードを使用する必要がありますか?その場合は、オーバーロードを含めることを検討してください。
  • オプションのパラメーターに激しく反対するメンバーがチームにいますか?(場合によっては、主張を論じるよりも、嫌いな決定を下したほうが簡単なことがあります。)
  • コードのビルド間でデフォルトが変更されないことに自信がありますか?可能であれば、呼び出し元はそれで問題ありませんか?

デフォルトがどのように機能するかは確認していませんが、デフォルト値がconstフィールドへの参照と同じように、呼び出しコードに組み込まれると思います。これは通常は問題ありません-デフォルト値の変更はとにかくかなり重要です-しかし、それらは考慮すべきことです。


20
実用主義に関する知恵の+1:場合によっては、主張を論じるよりも、嫌いな決断をする方が簡単な場合があります。
legends2k 14

13
@romkyns:いいえ、オーバーロードの影響はポイント3と同じではありません。オーバーロードがデフォルトを提供する場合、デフォルトはライブラリコード内にあります。したがって、デフォルトを変更して新しいバージョンのライブラリを提供すると、呼び出し元は再コンパイルせずに新しいデフォルトを確認してください。一方、オプションのパラメーターを使用する場合は、新しいデフォルトを「確認」するために再コンパイルする必要があります。多くの場合、それ重要な区別ではありませんが、それ区別です。
Jon Skeet、2015

こんにちは@JonSkeet、オプションのパラメーターを持つ関数と、どのメソッドが呼び出されるオーバーロードを持つ関数の両方を使用するかを知りたいですか?たとえばAdd(int a、int b)とAdd(int a、int b、int c = 0)と関数呼び出しは言う:Add(5,10); オーバーロードされた関数またはオプションのパラメーター関数と呼ばれるメソッドはどれですか?ありがとう:)
SHEKHAR SHETE 2016

@Shekshar:やってみた?詳細については仕様を読んでください。ただし、基本的にタイブレーカーでは、コンパイラーがオプションのパラメーターを入力する必要のない方法が優先されます。
ジョンスキート2016

@JonSkeetちょうど今私は上記で試してみました...関数のオーバーロードはオプションのパラメーター
より優先され

19

メソッドオーバーロードが通常、異なる数の引数で同じことを実行する場合、デフォルトが使用されます。

メソッドオーバーロードがそのパラメーターに基づいて異なる機能を実行する場合、オーバーロードが引き続き使用されます。

私はVB6日にオプションのバックを使用しましたが、それを逃してしまったため、C#でのXMLコメントの重複が大幅に減少します。


11

Delphiとオプションのパラメータをずっと使ってきました。代わりにオーバーロードの使用に切り替えました。

より多くのオーバーロードを作成しようとすると、オプションのパラメーターフォームと常に競合するため、とにかくそれらを非オプションに変換する必要があるためです。

そして、私は一般に1つのスーパーメソッドがあり、残りはそのメソッドのより単純なラッパーであるという概念を気に入っています。


1
私はこれに非常に同意しますが、本質的にすべて「オプション」(デフォルトで置き換えることができる)である複数(3+)のパラメーターを取るメソッドがある場合、多くの順列になる可能性があるという警告がありますメソッドシグネチャの利点はありません。考えてみましょうFoo(A, B, C)必要がありFoo(A)Foo(B)Foo(C)Foo(A, B)Foo(A, C)Foo(B, C)
Dan Lugg

7

私は間違いなく4.0のオプションのパラメーター機能を使用します。それはとんでもないことを取り除きます...

public void M1( string foo, string bar )
{
   // do that thang
}

public void M1( string foo )
{
  M1( foo, "bar default" ); // I have always hated this line of code specifically
}

...そして、発信者がそれらを見ることができる場所に値を配置します...

public void M1( string foo, string bar = "bar default" )
{
   // do that thang
}

はるかにシンプルで、エラーが発生しにくくなります。私は実際にこれを過負荷ケースのバグとして見ました...

public void M1( string foo )
{
   M2( foo, "bar default" );  // oops!  I meant M1!
}

私はまだ4.0コンパイラーで遊んでいませんが、コンパイラーが単にオーバーロードを出力するだけであることを知ってショックを受けることはありません。


6

オプションのパラメーターは本質的にメタデータの一部であり、メソッドコールを処理するコンパイラーに、呼び出しサイトに適切なデフォルトを挿入するように指示します。対照的に、オーバーロードは、コンパイラーが多数のメソッドの1つを選択できる手段を提供します。その一部はデフォルト値を提供する場合があります。それらをサポートしない言語で書かれたコードからオプションのパラメーターを指定するメソッドを呼び出そうとすると、コンパイラーは「オプション」パラメーターを指定する必要がありますが、オプションのパラメーターを指定せずにメソッドを呼び出すのはデフォルト値と等しいパラメータでそれを呼び出すことと同等であり、そのような言語がそのようなメソッドを呼び出すことに障害はありません。

呼び出しサイトでのオプションのパラメーターのバインドの重要な結果は、コンパイラーで使用可能なターゲットコードのバージョンに基づいて値が割り当てられることです。アセンブリにデフォルト値5のFooメソッドBoo(int)があり、アセンブリBarにへの呼び出しが含まれているFoo.Boo()場合、コンパイラはそれをとして処理しFoo.Boo(5)ます。デフォルト値が6に変更され、アセンブリFooが再コンパイルされる場合、は、その新しいバージョンので再コンパイルされない限り、または再コンパイルされるまで、Barを呼び出し続けます。したがって、変更される可能性のあるものにオプションのパラメータを使用することは避けてください。Foo.Boo(5)Foo


Re:「したがって、変更される可能性のあるものにオプションのパラメーターを使用することは避けてください。変更がクライアントコードに気付かれない場合、これが問題になる可能性があることに同意します。ただし、デフォルト値がメソッドオーバーロード内に隠されている場合にも同じ問題が存在しますvoid Foo(int value) … void Foo() { Foo(42); }。外部からは、呼び出し元はどのデフォルト値が使用されるのか、またいつ変更されるのかを知りません。そのための文書を監視する必要があります。オプションのパラメーターのデフォルト値は、それと同じように見ることができます。documentation-in-codeのデフォルト値は何ですか。
stakx-2017年

@stakx:パラメータのないオーバーロードがパラメータ付きのオーバーロードにチェーンしている場合、そのパラメータの「デフォルト」値を変更してオーバーロードの定義を再コンパイルすると、呼び出し元のコードが再コンパイルされていなくも、使用する値が変更されます。
スーパーキャット2017年

真実ですが、それは他のものより問題を多くしません。1つのケース(メソッドのオーバーロード)では、呼び出しコードはデフォルト値に何の意味もありません。これは、呼び出し側のコードがオプションのパラメーターとその意味をまったく気にしない場合に適しています。その他の場合(デフォルト値を持つオプションのパラメーター)、以前にコンパイルされた呼び出しコードは、デフォルト値が変更されても影響を受けません。これは、呼び出し元のコードが実際にパラメーターを気にする場合にも適しています。ソースでそれを省略することは、「現在提案されているデフォルト値は私には問題ない」と言っているようなものです。
stakx-2017年

ここで私が述べようとしている点は、どちらのアプローチにも(指摘したように)結果はあるものの、本質的に有利でも不利でもないということです。それは、呼び出しコードのニーズと目標にも依存します。そのPOVから、私はあなたの回答の最後の文の評決がやや厳しすぎることに気付きました。
stakx-2017年

@stakx:「使用しない」ではなく「使用しない」と言いました。Xを変更すると、Yの次の再コンパイルによってYの動作が変更される場合、Xの再コンパイルごとにYも再コンパイルするようにビルドシステムを構成する必要があります(処理速度が遅くなります)、またはプログラマーが変更するリスクが生じる次回のコンパイル時にYを破壊するような方法でXを行い、後で完全に無関係な理由でYが変更されたときにのみ、そのような破壊を発見します。デフォルトのパラメーターは、その利点がそのコストを上回る場合にのみ使用してください。
スーパーキャット2017年

4

オプションの引数またはオーバーロードを使用する必要があるかどうかは議論できますが、最も重要なのは、それぞれにかけがえのない独自の領域があることです。

オプションの引数を名前付き引数と組み合わせて使用​​すると、COM呼び出しのいくつかのlong-argument-lists-with-all-optionalsと組み合わせると非常に役立ちます。

メソッドが多くの異なる引数型(例の1つにすぎない)を操作でき、内部でキャストを行う場合など、オーバーロードは非常に便利です。意味のあるデータ型をフィードするだけです(既存のオーバーロードで受け入れられます)。オプションの引数でそれを打つことはできません。


3

デフォルトに近いメソッドを保持するため、オプションのパラメーターを楽しみにしています。したがって、「expanded」メソッドを呼び出すだけの数十行のオーバーロードの代わりに、メソッドを1回定義するだけで、オプションのパラメーターのデフォルトがメソッドシグネチャに表示されます。私はむしろ見たい:

public Rectangle (Point start = Point.Zero, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

これの代わりに:

public Rectangle (Point start, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

public Rectangle (int width, int height) :
    this (Point.Zero, width, height)
{
}

明らかにこの例は非常に単純ですが、5つのオーバーロードがあるOPの場合、非常にすばやく混雑する可能性があります。


7
オプションのパラメーターは最後にすべきだと聞いたことがありますか?
Ilya Ryzhenkov 2008年

あなたのデザインに依存します。おそらく、 'start'引数は、そうでない場合を除いて、通常は重要です。おそらく、どこかで同じ署​​名があり、別の意味を持っています。不自然な例として、public Rectangle(int width、int height、Point innerSquareStart、Point innerSquareEnd){}
Robert P

13
彼らが話で言ったことから、オプションのパラメーターは必須パラメーターの後にある必要があります。
グレッグビーチ、

3

オプションパラメーターの私のお気に入りの側面の1つは、メソッド定義に移動しなくても、パラメーターを指定しないとパラメーターがどうなるかを確認できることです。Visual Studioは単にデフォルト値を表示しますメソッド名を入力すると、パラメーターのを。オーバーロードメソッドを使用すると、ドキュメントを読む(利用できる場合でも)か、メソッドの定義(利用できる場合)およびオーバーロードがラップするメソッドに直接移動するのに悩まされます。

特に、ドキュメント化の作業はオーバーロードの量とともに急速に増加する可能性があり、おそらく既存のオーバーロードから既存のコメントをコピーすることになります。値を生成せず、DRY原理を壊すため、これは非常に迷惑です。一方、オプションのパラメーターを使用すると、すべてのパラメーターが文書化されている場所1つだけあり、入力中にそれらの意味とデフォルト値を確認できます。

最後に重要なことですが、APIを利用している場合は、実装の詳細を検査するオプションがなく(ソースコードがない場合)、オーバーロードされたスーパーメソッドを確認する機会がありません。包んでいます。したがって、ドキュメントを読んですべてのデフォルト値がそこにリストされていることを期待して立ち往生していますが、これは常にそうであるとは限りません。

もちろん、これはすべての側面を扱う答えではありませんが、これまでカバーしていないものを追加すると思います。


1

それらは(おそらく?)APIを最初からモデル化するために利用できる概念的に同等の2つの方法ですが、実際に古いクライアントのランタイム下位互換性を検討する必要がある場合、残念ながら微妙な違いがあります。私の同僚(Brentに感謝!)がこの素晴らしい投稿を指摘してくれました:オプションの引数によるバージョン管理の問題。それからの引用:

オプションのパラメーターが最初にC#4に導入された理由は、COM相互運用性をサポートするためでした。それでおしまい。そして今、私たちはこの事実の完全な影響について学んでいます。オプションのパラメータを持つメソッドがある場合、コンパイル時の重大な変更を引き起こす恐れがないため、オプションのパラメータを追加してオーバーロードを追加することはできません。また、これは常にランタイムの重大な変更であるため、既存のオーバーロードを削除することはできません。インターフェースのように扱う必要があります。この場合の唯一の手段は、新しいメソッドを新しい名前で作成することです。したがって、APIでオプションの引数を使用する場合は、このことに注意してください。


1

オプションパラメータの注意点の1つは、リファクタリングが意図しない結果をもたらすバージョン管理です。例:

初期コード

public string HandleError(string message, bool silent=true, bool isCritical=true)
{
  ...
}

これが上記のメソッドの多くの呼び出し元の1つであると想定します。

HandleError("Disk is full", false);

ここではイベントはサイレントではなく、クリティカルとして扱われます。

ここで、リファクタリング後にすべてのエラーでユーザーにプロンプ​​トが表示されたことがわかり、サイレントフラグが不要になったとします。それを削除します。

リファクタリング後

前の呼び出しはまだコンパイルされており、リファクタリングを変更せずにすり抜けるとします。

public string HandleError(string message, /*bool silent=true,*/ bool isCritical=true)
{
  ...
}

...

// Some other distant code file:
HandleError("Disk is full", false);

false意図しない効果が発生し、イベントはクリティカルとして扱われなくなります。

これは、コンパイルエラーや実行時エラーが発生しないため、微妙な欠陥につながる可能性があります(これこれなど、オプションの他の警告とは異なります)

この同じ問題には多くの形があることに注意してください。他の1つのフォームの概要はこちら

メソッドを呼び出すときに名前付きパラメーターを厳密に使用すると、次のような問題が回避されることにも注意してくださいHandleError("Disk is full", silent:false)。ただし、他のすべての開発者(またはパブリックAPIのユーザー)がそうすることを想定するのは現実的ではない場合があります。

これらの理由により、他の説得力のある考慮事項がない限り、オプションのパラメーターをパブリックAPI(または、広く使用されている場合はパブリックメソッド)で使用することは避けます。


0

オプションのパラメーター、メソッドのオーバーロードには、それぞれ利点と欠点があります。どちらを選択するかは、ユーザーの好みによって異なります。

オプションパラメータ:.Net 4.0でのみ使用できます。オプションのパラメーターはコードサイズを減らします。outおよびrefパラメータを定義することはできません

オーバーロードされたメソッド:Outおよびrefパラメーターを定義できます。コードサイズは増加しますが、オーバーロードされたメソッドは簡単に理解できます。


0

多くの場合、オプションのパラメーターを使用して実行を切り替えます。例えば:

decimal GetPrice(string productName, decimal discountPercentage = 0)
{

    decimal basePrice = CalculateBasePrice(productName);

    if (discountPercentage > 0)
        return basePrice * (1 - discountPercentage / 100);
    else
        return basePrice;
}

ここの割引パラメーターは、if-then-elseステートメントにフィードするために使用されます。認識されなかった多態性があり、if-then-elseステートメントとして実装されました。このような場合、2つの制御フローを2つの独立したメソッドに分割する方がはるかに適切です。

decimal GetPrice(string productName)
{
    decimal basePrice = CalculateBasePrice(productName);
    return basePrice;
}

decimal GetPrice(string productName, decimal discountPercentage)
{

    if (discountPercentage <= 0)
        throw new ArgumentException();

    decimal basePrice = GetPrice(productName);

    decimal discountedPrice = basePrice * (1 - discountPercentage / 100);

    return discountedPrice;

}

このようにして、割引なしで電話を受けることからもクラスを保護しました。その呼び出しは、発信者が割引があると考えていることを意味しますが、実際には割引はまったくありません。そのような誤解は簡単にバグを引き起こす可能性があります。

このような場合は、オプションのパラメーターを使用せずに、呼び出し元が現在の状況に適した実行シナリオを明示的に選択するようにします。

この状況は、nullになる可能性のあるパラメーターがある場合と非常に似ています。実装がのようなステートメントになると、それは同様に悪い考えif (x == null)です。

あなたはこれらのリンクを詳細に分析を見つけることができます:オプションのパラメータを回避し、ヌルパラメータを回避します


0

オプションの代わりにオーバーロードを使用するときに、簡単に追加するには:

一緒にしか意味をなさないパラメーターが多数ある場合は、それらにオプションを導入しないでください。

または、より一般的には、メソッドシグネチャが意味のない使用パターンを有効にする場合は常に、可能な呼び出しの順列の数を制限します。たとえば、オプションの代わりにオーバーロードを使用する(このルールは、同じデータ型のパラメーターが複数ある場合にも当てはまります。ここでは、ファクトリメソッドやカスタムデータ型などのデバイスが役立ちます)。

例:

enum Match {
    Regex,
    Wildcard,
    ContainsString,
}

// Don't: This way, Enumerate() can be called in a way
//         which does not make sense:
IEnumerable<string> Enumerate(string searchPattern = null,
                              Match match = Match.Regex,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);

// Better: Provide only overloads which cannot be mis-used:
IEnumerable<string> Enumerate(SearchOption searchOption = SearchOption.TopDirectoryOnly);
IEnumerable<string> Enumerate(string searchPattern, Match match,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.