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

StackOverflow https://stackoverflow.com/questions/251868

質問

見ていた Anders による C# 4.0 と C# 5.0 のスニーク プレビューについての講演, そこで、C# でオプションのパラメーターが使用できる場合、すべてのパラメーターを指定する必要がないメソッドを宣言する推奨方法は何になるのかについて考えさせられました。

たとえば、次のようなもの FileStream クラスには、論理的な「ファミリー」に分割できる約 15 の異なるコンストラクターがあります。以下は文字列からのもの、 IntPtr そして、からのもの 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 からは、密接に関連するコンストラクターとメソッドのグループをオプションのパラメーターを持つ単一のメソッドにする方が合理的ですか? それとも、従来の多多重オーバーロード メカニズムに固執する十分な理由はありますか?

役に立ちましたか?

解決

次のことを検討します:

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

デフォルトがどのように機能するかを確認していませんが、 const フィールドへの参照とほぼ同じように、デフォルト値が呼び出しコードにベイクされると想定しています。それは通常大丈夫です-デフォルト値への変更はとにかくかなり重要です-しかし、それらは考慮すべきものです。

他のヒント

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

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

VB6でオプションのバックを使用しましたが、それを逃してしまったため、C#でのXMLコメントの重複を減らすことができます。

私はDelphiをオプションのパラメーターとともに永久に使用しています。代わりにオーバーロードを使用するように切り替えました。

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

そして、私は一般に1つの super メソッドがあり、残りはそのメソッドの単純なラッパーであるという概念が好きです。

間違いなく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コンパイラをまだ使用していませんが、コンパイラが単純にオーバーロードを発生させることを知ってショックを受けることはありません。

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

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

デフォルトがメソッドに近いものを保持するため、オプションのパラメータを楽しみにしています。したがって、単に「拡張」を呼び出すオーバーロードの数十行ではなく、メソッドの場合、メソッドを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の場合、物事はすぐに混雑する可能性があります。

オプションの引数やオーバーロードを使用するべきか否かについては議論の余地がありますが、最も重要なのは、それぞれが代替不可能な独自の領域を持っているということです。

オプションの引数は、名前付き引数と組み合わせて使用​​すると、COM 呼び出しのすべてのオプションを含む長い引数リストと組み合わせると非常に便利です。

オーバーロードは、メソッドがさまざまな引数の型 (例の 1 つにすぎません) を操作でき、内部でキャストを行う場合などに非常に役立ちます。意味のある (既存のオーバーロードによって受け入れられる) データ型をフィードするだけです。オプションの引数を使用するとこれに勝るものはありません。

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

特に:オーバーロードの量に応じてドキュメント作成の労力が急速に増加する可能性があり、おそらく既存のオーバーロードから既存のコメントをコピーすることになります。値を生成せず、 DRY-principle に違反するため、これは非常に迷惑です。 )。一方、オプションのパラメーターでは、すべてのパラメーターが文書化されている正確に1つの場所があり、入力中にその意味とデフォルト値が表示されます。

最後になりましたが、APIのコンシューマーである場合は、実装の詳細を調べることさえできない場合があります(ソースコードがない場合)。そのため、どのスーパーメソッドを参照する機会がありませんオーバーロードされたものはラッピングされています。したがって、ドキュメントを読んで、すべてのデフォルト値がそこにリストされていることを望んでいますが、常にそうであるとは限りません。

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

オプションのパラメーターに関する注意点の 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 (または広く使用される可能性がある場合はパブリック メソッド) でオプションのパラメーターを使用することを避けます。

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

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

オーバーロードされたメソッド: Outおよびrefパラメーターを定義できます。 コードサイズは大きくなりますが、オーバーロードされたメソッドは理解しやすいです。

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

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

    decimal basePrice = CalculateBasePrice(productName);

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

Discountパラメーターは、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)のようなステートメントに沸騰するとき、それは等しく悪い考えです。

これらのリンクの詳細な分析を見つけることができます:オプションのパラメーターの回避および Nullパラメーターの回避

これらは(おそらく?)APIをゼロからモデル化するために概念的に同等の2つの方法ですが、残念ながら、野生の古いクライアントのランタイム後方互換性を考慮する必要がある場合、微妙な違いがあります。私の同僚(Brentに感謝!)は、この素晴らしい投稿:オプションの引数に関するバージョン管理の問題。それからの引用:

  

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

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

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

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

例:

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);
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top