コンストラクターまたは宣言でクラスフィールドを初期化しますか?


413

私は最近C#とJavaでプログラミングをしていて、クラスフィールドを初期化するのに最適な場所がどこか知りたいです。

宣言時に行う必要がありますか?:

public class Dice
{
    private int topFace = 1;
    private Random myRand = new Random();

    public void Roll()
    {
       // ......
    }
}

またはコンストラクタで?:

public class Dice
{
    private int topFace;
    private Random myRand;

    public Dice()
    {
        topFace = 1;
        myRand = new Random();
    }

    public void Roll()
    {
        // .....
    }
}

ベテランの何人かがベストプラクティスだと思うのは本当に気になります。一貫性を保ち、1つのアプローチに固執したい。


3
構造体の場合、インスタンスフィールドの初期化子を持つことはできないため、コンストラクタを使用するしかありません。
ヨーヨー、2014

:あなたは、「ベストプラクティス」育てているので satisfice.com/blog/archives/5164forbes.com/sites/mikemyatt/2012/08/15/best-practices-arent/...
スティーブンC

回答:


310

私のルール:

  1. 宣言内のデフォルト値で初期化しないでください(nullfalse00.0...)。
  2. フィールドの値を変更するコンストラクターパラメーターがない場合は、宣言で初期化を優先します。
  3. コンストラクター・パラメーターのためにフィールドの値が変更された場合は、コンストラクターに初期化を入れてください。
  4. 実践に一貫性を持たせる(最も重要なルール)。

4
私はkokosがメンバーをデフォルト値(0、false、nullなど)に初期化しないことを意味すると思います。コンパイラーがそれを行うからです(1.)。ただし、フィールドをデフォルト値以外に初期化する場合は、宣言でそれを行う必要があります(2.)。混乱させるのは「デフォルト」という言葉の使い方かもしれません。
リッキーヘルゲソン

95
ルール1に同意しません-デフォルト値を指定しないことで(コンパイラーによって初期化されているかどうかに関係なく)、特定の言語のデフォルト値がどうなるかを推測するか、ドキュメントを探す必要があります。読みやすくするために、常にデフォルト値を指定します。
James

32
型のデフォルト値default(T)は常に、の内部バイナリ表現を持つ値です0
Olivier Jacot-Descombes 2013年

15
ルール1が好きかどうかに関係なく、コンストラクターの終了時に明示的に初期化する必要がある読み取り専用フィールドでは使用できません。
ヨーヨー、2014

36
ルール1に同意しない人には同意しません。他の人がC#言語を学ぶことを期待してもかまいません。各foreachループに「これによりリスト内のすべてのアイテムに対して以下が繰り返される」とコメントしないのと同じように、C#のデフォルト値を常に再表示する必要はありません。また、C#に初期化されていないセマンティクスがあるというふりをする必要もありません。値が存在しないことには明確な意味があるため、省略しても問題ありません。明示的であることが理想的である場合は、new新しいデリゲートを作成するときに常に使用する必要があります(C#1で必要でした)。しかし、誰がそれをしますか?言語は良心的なプログラマー向けに設計されています。
エドワードブレイ2017年

149

C#では問題ではありません。あなたが与える2つのコードサンプルは完全に同等です。最初の例では、C#コンパイラー(またはCLRですか?)は空のコンストラクターを構築し、それらがコンストラクター内にあるかのように変数を初期化します(Jon Skeetが以下のコメントで説明する微妙なニュアンスがあります)。すでにコンストラクタがある場合は、「上」の初期化はその上部に移動されます。

ベストプラクティスの観点では、前者は後者に比べてエラーが発生しにくいため、誰かが簡単に別のコンストラクタを追加して、連鎖することを忘れる可能性があります。


4
GetUninitializedObjectでクラスを初期化することを選択した場合、これは正しくありません。ctor内にあるものは何も変更されませんが、フィールド宣言が実行されます。
Wolf5、2013年

28
実際には重要です。基本クラスコンストラクターが派生メソッドでオーバーライドされる仮想メソッドを呼び出す場合(これは一般に悪い考えですが、発生する可能性があります)、インスタンス変数初期化子を使用すると、メソッドが呼び出されたときに変数が初期化されます。コンストラクターではありません。(インスタンス変数初期化子は、基本クラスのコンストラクターが呼び出されるに実行されます。)
Jon Skeet 2013

2
本当に?私はこの情報をC#を介してリヒターのCLR(第2版だと思います)から取得したことを誓います。これの要点は、これが構文糖(誤って読んだ可能性がありますか?)であり、CLRが変数をコンストラクターにジャム処理したことです。しかし、これはそうではないことを述べています。つまり、メンバーの初期化は、ベースインクターで仮想を呼び出し、問題のクラスでオーバーライドを行うというクレイジーなシナリオで、クターの初期化の前に発生する可能性があります。私は正しく理解しましたか?これを見つけましたか?5年前の投稿に対するこの最新のコメントについて困惑しました(OMGは5年になりますか?)。
2013

@Quibblesome:子クラスコンストラクターには、親コンストラクターへのチェーン呼び出しが含まれます。親コンストラクターがすべてのコードパスで1回だけ呼び出され、その呼び出しの前に構築中のオブジェクトが限定的に使用される場合、言語コンパイラーはそれより前に好きなだけ多くまたは少ないコードを自由に含めることができます。C#の私の不満の1つは、フィールドを初期化するコードとコンストラクターパラメーターを使用するコードを生成できる一方で、コンストラクターパラメーターに基づいてフィールドを初期化するメカニズムを提供しないことです。
スーパーキャット2015

1
@Quibblesome、以前の問題は、値が割り当てられてWCFメソッドに渡される場合に問題になります。これにより、データがモデルのデフォルトのデータ型にリセットされます。ベストプラクティスは、コンストラクター(後で1つ)で初期化を行うことです。
Pranesh Janarthanan

16

C#のセマンティクスは、ここではJavaと少し異なります。C#では、宣言での割り当ては、スーパークラスコンストラクターを呼び出す前に実行されます。Javaでは、この直後に「this」を使用できるようになり(特に匿名の内部クラスに役立ちます)、2つの形式のセマンティクスが実際に一致することを意味します。

可能であれば、フィールドをfinalにします。


15

一つ注意点があると思います。私はかつてそのようなエラーを犯しました:派生クラスの内部で、抽象基本クラスから継承されたフィールドを「宣言時に初期化」しようとしました。その結果、「ベース」と新しく宣言されたフィールドの2つのフィールドセットが存在し、デバッグにかなりの時間がかかりました。

レッスン:継承されたフィールドを初期化するには、コンストラクター内で行います。


では、フィールドをで参照する場合derivedObject.InheritedField、それはあなたのベースを参照しているのですか、それとも派生したものを参照していますか?
RayLuo 2017年

6

あなたの例のタイプを想定すると、コンストラクタのフィールドを初期化することを間違いなく好む。例外的なケースは次のとおりです。

  • 静的クラス/メソッドのフィールド
  • static / final / et alと入力されたフィールド

私は常にクラスの一番上にあるフィールドリストを目次(ここに含まれるものであり、使用方法ではない)と考え、コンストラクターを紹介と考えています。もちろんメソッドは章です。


なぜ「間違いなく」そうなのでしょうか。理由を説明せずに、スタイルの設定のみを提供しました。@quibblesomeの回答によると、待ってください、心配しないでください。これらは「まったく同等」であるため、実際には個人的なスタイルの好みにすぎません。
RayLuo 2017年

4

私があなたに言った場合、それは依存しますか?

私は一般的にすべてを初期化し、一貫した方法でそれを行います。はい、それは過度に明示的ですが、保守も少し簡単です。

パフォーマンスが心配な場合は、やらなければならないことだけを初期化し、費用対効果が最も高い領域に配置します。

リアルタイムシステムでは、変数と定数のどちらが必要かについて疑問を投げかけます。

C ++では、どちらの場所でも初期化を行わずにInit()関数に移動することがよくあります。どうして?まあ、C ++では、オブジェクトの構築中に例外をスローする可能性のあるものを初期化している場合、メモリリークが発生します。


4

多くのさまざまな状況があります。

空のリストが必要です

状況は明らかです。リストを準備し、誰かがリストにアイテムを追加したときに例外がスローされないようにするだけです。

public class CsvFile
{
    private List<CsvRow> lines = new List<CsvRow>();

    public CsvFile()
    {
    }
}

私は価値を知っています

デフォルトで必要な値を正確に知っているか、他のロジックを使用する必要があります。

public class AdminTeam
{
    private List<string> usernames;

    public AdminTeam()
    {
         usernames = new List<string>() {"usernameA", "usernameB"};
    }
}

または

public class AdminTeam
{
    private List<string> usernames;

    public AdminTeam()
    {
         usernames = GetDefaultUsers(2);
    }
}

可能な値を持つ空のリスト

時にはデフォルトで空のリストを期待しますが、別のコンストラクタを介して値を追加する可能性があります。

public class AdminTeam
{
    private List<string> usernames = new List<string>();

    public AdminTeam()
    {
    }

    public AdminTeam(List<string> admins)
    {
         admins.ForEach(x => usernames.Add(x));
    }
}

3

Javaでは、宣言付きの初期化子は、使用されるコンストラクター(複数ある場合)またはコンストラクターのパラメーター(引数がある場合)に関係なく、フィールドは常に同じ方法で初期化されることを意味しますが、コンストラクターは後で値を変更します(最終でない場合)。したがって、宣言で初期化子を使用すると、初期化された値は、使用されるコンストラクターやコンストラクターに渡されるパラメーターに関係なく、すべての場合にフィールドが持つ値であることがリーダーに示唆されます。したがって、構築されたすべてのオブジェクトの値が同じである場合に限り、常に初期化子を宣言とともに使用します。


3

C#の設計では、インライン初期化が推奨されます。そうでない場合は、言語ではありません。コード内の異なる場所間の相互参照を回避できる場合はいつでも、通常はより良い方法です。

静的フィールドの初期化と整合性の問題もあります。これは、最高のパフォーマンスを得るためにインラインにする必要があります。コンストラクター設計のフレームワーク設計ガイドラインは次のように述べています。

ランタイムは明示的に定義された静的コンストラクタを持たない型のパフォーマンスを最適化できるため、静的コンストラクタを明示的に使用するのではなく、インラインで静的フィールドを初期化することを検討してください。

この文脈での「考慮」とは、そうするべきである正当な理由がない限り、そうすることを意味します。静的初期化子フィールドの場合、正当な理由は、初期化が複雑すぎてインラインでコーディングできない場合です。


2

一貫性を保つことが重要ですが、これは自分自身に問うべき質問です。「他に何かコンストラクタはありますか?」

通常は、クラス自体が変数のハウジングとして機能する以外は何もしないデータ転送のモデルを作成しています。

これらのシナリオでは、通常、メソッドやコンストラクターはありません。特に宣言に合わせてリストを初期化できるので、リストを初期化するためだけにコンストラクタを作成するのはばかげたことでしょう。

他の多くの人が言ったように、それはあなたの使用法に依存します。シンプルに保ち、必要のないものを追加しないでください。


2

複数のコンストラクターがある状況を考えてみましょう。初期化はコンストラクターごとに異なりますか?それらが同じになる場合、なぜ各コンストラクタに対して繰り返すのですか?これはkokosステートメントと一致していますが、パラメーターに関連していない可能性があります。たとえば、オブジェクトがどのように作成されたかを示すフラグを保持したいとします。次に、そのフラグは、コンストラクターのパラメーターに関係なく、コンストラクターごとに異なる方法で初期化されます。一方、各コンストラクターに対して同じ初期化を繰り返すと、一部のコンストラクターでは初期化パラメーターを(意図せずに)変更する可能性がありますが、他のコンストラクターでは変更しない可能性があります。したがって、ここでの基本的な概念は、共通のコードには共通の場所があり、異なる場所で繰り返されないようにする必要があるということです。


1

宣言で値を設定すると、パフォーマンスが少し向上します。コンストラクタで設定すると、実際には2回設定されます(最初はデフォルト値に設定され、次にctorでリセットされます)。


2
C#では、フィールドは常に最初にデフォルト値に設定されます。初期化子が存在しても違いはありません。
ジェフリーLホイットリッジ

0

私は通常、コンストラクターを試して、依存関係を取得し、関連するインスタンスメンバーを初期化するだけです。これにより、クラスを単体テストする場合の作業が楽になります。

インスタンス変数に割り当てる値が、コンストラクターに渡すパラメーターの影響を受けない場合は、宣言時に割り当てます。


0

ベストプラクティスに関する質問への直接の回答ではありませんが、重要で関連する再確認ポイントは、ジェネリッククラス定義の場合、コンパイラーに任せてデフォルト値で初期化するか、フィールドを初期化するために特別なメソッドを使用する必要があるということです。デフォルト値に変更します(コードを読みやすくするために絶対に必要な場合)。

class MyGeneric<T>
{
    T data;
    //T data = ""; // <-- ERROR
    //T data = 0; // <-- ERROR
    //T data = null; // <-- ERROR        

    public MyGeneric()
    {
        // All of the above errors would be errors here in constructor as well
    }
}

また、ジェネリックフィールドをデフォルト値に初期化する特別なメソッドは次のとおりです。

class MyGeneric<T>
{
    T data = default(T);

    public MyGeneric()
    {           
        // The same method can be used here in constructor
    }
}

0

ロジックやエラー処理が必要ない場合:

  • 宣言時にクラスフィールドを初期化する

ロジックまたはエラー処理が必要な場合:

  • コンストラクターでクラスフィールドを初期化する

これは、初期化値が使用可能であり、初期化を1行に置くことができる場合にうまく機能します。ただし、この形式の初期化は単純であるため、制限があります。初期化に何らかのロジック(たとえば、エラー処理や複雑な配列を満たすためのforループなど)が必要な場合、単純な割り当てでは不十分です。インスタンス変数はコンストラクターで初期化でき、エラー処理やその他のロジックを使用できます。

https://docs.oracle.com/javase/tutorial/java/javaOO/initial.htmlから。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.