web-dev-qa-db-ja.com

C#でのコンストラクターパラメーターの検証-ベストプラクティス

コンストラクターパラメーターの検証のベストプラクティスは何ですか?

簡単なC#を考えてみます。

public class MyClass
{
    public MyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            throw new ArgumentException("Text cannot be empty");

        // continue with normal construction
    }
}

例外をスローすることは許容されますか?

私が遭遇した代替案は、インスタンス化する前の事前検証でした:

public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
        {
            MessageBox.Show("Text cannot be empty");
            return null;
        }
        else
        {
            return new MyClass(text);
        }
    }
}
35
MPelletier

私はすべての検証をコンストラクターで実行する傾向があります。ほとんどの場合、不変オブジェクトを作成するため、これは必須です。あなたの特定のケースでは、これは許容できると思います。

if (string.IsNullOrEmpty(text))
    throw new ArgumentException("message", "text");

.NET 4を使用している場合は、これを行うことができます。もちろん、これは、空白のみを含む文字列を無効と見なすかどうかによって異なります。

if (string.IsNullOrWhiteSpace(text))
    throw new ArgumentException("message", "text");
27
ChaosPandion

多くの人が、コンストラクターは例外をスローすべきではないと述べています。 このページ のKyleGは、たとえばそれだけを行います。正直なところ、それができない理由は思いつきません。

C++では、コンストラクターから例外をスローすることはお勧めできません。参照されていない初期化されていないオブジェクトを含むメモリが割り当てられたままになるためです(つまり、従来のメモリリークです)。おそらくそれがスティグマの原因です-多くの古い学校のC++開発者がC#の学習を半分にアーシングし、C++から知っていることをそれに適用しました。対照的に、Objective-CではAppleは割り当てステップを初期化ステップから分離しているため、その言語のコンストラクターcan例外をスローします。

C#は、コンストラクタへの失敗した呼び出しからメモリをリークできません。 .NETフレームワークの一部のクラスでも、コンストラクターで例外がスローされます。

23
Ant

クラスをそのセマンティックの使用に関して一貫した状態にすることができない場合、例外IFFをスローします。それ以外の場合は行いません。オブジェクトが不整合な状態で存在することは絶対に許可しないでください。これには、完全なコンストラクターを提供しないことも含まれます(オブジェクトが実際に完全に構​​築される前に空のコンストラクター+ initialize()を用意するなど)...ただ言うだけです!

ピンチでは、誰もがそれを行います。先日、非常に狭い範囲で使用される非常に狭いオブジェクトについて行いました。いつの日か、私や他の誰かが実際にはそのスリップの代償を払うでしょう。

「コンストラクタ」とは、クライアントがオブジェクトを構築するために呼び出すものを意味することに注意してください。これは、 "Constructor"という名前の実際のコンストラクト以外の簡単なものでもかまいません。たとえば、C++でのこのようなものは、IMNSHOの原則に違反しません。

struct funky_object
{
  ...
private:
  funky_object();
  bool initialize(std::string);

  friend boost::optional<funky_object> build_funky(std::string);
};
boost::optional<funky_object> build_funky(std::string str)
{
  funky_object fo;
  if (fo.initialize(str)) return fo;
  return boost::optional<funky_object>();
}

funky_objectを作成する唯一の方法はbuild_funkyを呼び出すことであるため、実際の「コンストラクタ」がジョブを完了しなくても、無効なオブジェクトの存在を許可しないという原則はそのまま残ります。

それは疑わしい利益(多分損失さえ)のための多くの余分な仕事です。私はまだ例外ルートを好みます。

13
Edward Strange

この場合、ファクトリーメソッドを使用します。基本的に、クラスにはプライベートコンストラクタのみを設定し、オブジェクトのインスタンスを返すファクトリメソッドを設定します。初期パラメーターが無効な場合は、nullを返し、呼び出し元のコードに処理を決定させます。

public class MyClass
{
    private MyClass(string text)
    {
        //normal construction
    }

    public static MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            return null;
        else
            return new MyClass(text);
    }
}
public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        var cls = MyClass.MakeMyClass(text);
        if(cls == null)
             //show messagebox or throw exception
        return cls;
    }
}

条件が例外的でない限り、例外をスローしないでください。この場合、空の値を簡単に渡すことができると思います。その場合、このパターンを使用することで、MyClassの状態を有効に保ちながら、例外とそれに伴うパフォーマンスの低下を回避できます。

9
Donn Relacion
  • コンストラクターには副作用があってはなりません。
    • プライベートフィールドの初期化以上のものは、副作用と見なす必要があります。
    • 副作用のあるコンストラクターは、単一責任原則(SRP)を破り、オブジェクト指向プログラミング(OOP)の精神に反して実行されます。
  • コンストラクターは軽量でなければならず、決して失敗してはなりません。
    • たとえば、コンストラクター内のtry-catchブロックを見ると、いつも身震いします。コンストラクタは例外をスローしたり、エラーをログに記録したりしてはなりません。

これらのガイドラインに合理的に疑問を投げかけ、「しかし、私はこれらの規則に従わないので、私のコードは正常に動作します!」それに対して、私は「そうでなくなるまで、それは本当かもしれない」と答えるでしょう。

  • コンストラクター内の例外とエラーは非常に予想外です。そうするように指示されない限り、将来のプログラマーは、これらのコンストラクター呼び出しを防御的なコードで囲む傾向はありません。
  • 本番環境で何かが失敗した場合、生成されたスタックトレースは解析が難しい場合があります。スタックトレースの先頭はコンストラクター呼び出しを指している可能性がありますが、コンストラクターで多くのことが発生し、失敗した実際のLOCを指しているとは限りません。
    • これが事実であった多くの.NETスタックトレースを解析しました。
2
Jim G.

私の好みはデフォルトを設定することですが、Javaにはこのようなことを行う「Apache Commons」ライブラリがあり、それもかなり良いアイデアだと思います。私はわかりません。無効な値がオブジェクトを使用できない状態にする場合に例外をスローすることに関する問題。文字列は良い例ではありませんが、貧しい人のDIの場合はどうなりますか?nullは、たとえばICustomerRepositoryインターフェースの代わりに渡されました。このような状況では、例外をスローすることが処理の正しい方法だと思います。

0
Wayne Molina

MyClassが何をしているのかによります。 MyClassが実際にデータリポジトリクラスであり、パラメーターテキストが接続文字列である場合、ベストプラクティスはArgumentExceptionをスローすることです。ただし、MyClassが(たとえば)StringBuilderクラスの場合は、空白のままにしておくことができます。

したがって、それはメソッドに対するパラメーターテキストの重要性に依存します。オブジェクトはnullまたは空白の値で意味がありますか?

0
Steven Striga