[解決済み] グッドプラクティスかバッドプラクティスか?ゲッターでオブジェクトを初期化する
質問
私には奇妙な癖があるようです...少なくとも私の同僚はそう言っています。私たちは一緒に小さなプロジェクトに取り組んでいます。私がクラスを書いた方法は、(簡略化した例)です。
[Serializable()]
public class Foo
{
public Foo()
{ }
private Bar _bar;
public Bar Bar
{
get
{
if (_bar == null)
_bar = new Bar();
return _bar;
}
set { _bar = value; }
}
}
ですから、基本的には、ゲッターが呼ばれて、フィールドがまだヌルであるときだけ、どのフィールドも初期化するようにしています。どこにも使われていないプロパティを初期化しないことで、過負荷を減らすことができると考えたのです。
ETA: なぜこのようにしたかというと、私のクラスには別のクラスのインスタンスを返すプロパティがいくつかあり、そのプロパティもまた別のクラスのプロパティを持ち、といった具合になります。トップクラスのコンストラクタを呼び出すと、その後にこれらのクラスすべてのコンストラクタが呼び出されます。 常に が必要です。
このやり方に対して、個人的な好み以外に異論はないのでしょうか?
UPDATE: この質問に関して多くの異なる意見を考慮し、私は自分の受け入れた答えを守ります。しかし、私はこのコンセプトをより良く理解し、いつ使用し、いつ使用しないかを決めることができるようになりました。
短所
- スレッドセーフの問題
- 値がNULLの場合、quot;setter"リクエストに従わない。
- マイクロ最適化
- 例外処理はコンストラクタで行う必要があります。
- クラスのコードでNullをチェックする必要がある
長所
- マイクロ最適化
- プロパティがNULLを返すことはない
- 重いオブジェクトのロードを遅延させるか回避する。
短所のほとんどは私の現在のライブラリには適用できませんが、quot;マイクロ最適化"が実際に何かを全く最適化しているかどうかはテストする必要があるでしょう。
最後の更新です。
さて、答えを変えました。私の最初の質問は、これが良い習慣かどうかということでした。そして、私は今、そうではないと確信しています。多分、私はまだ私の現在のコードのいくつかの部分でそれを使用しますが、無条件ではありませんし、間違いなくすべての時間ではありません。だから、習慣をなくして、使う前に考えてみようと思います。みなさん、ありがとうございました。
どのように解決するのですか?
ここにあるのは、quot;遅延初期化" の - 素朴な - 実装です。
簡単な答え
遅延初期化の使用 無条件に は良いアイデアではありません。それはそれでよいのですが、この解決策がもたらす影響を考慮しなければなりません。
背景と説明
具体的な実装方法。
まず、あなたの具体的なサンプルと、その実装がなぜ素朴なものであると考えるかを見てみましょう。
-
に違反している。 最小驚嘆の原則(POLS) . プロパティに値が割り当てられると、その値が返されることが期待されます。あなたの実装では、これは
null
:foo.Bar = null; Assert.Null(foo.Bar); // This will fail
-
これは、かなりのスレッド問題を導入しています。の呼び出し元が2つある場合
foo.Bar
の2つの異なるインスタンスを取得する可能性があります。Bar
との接続がなく、そのうちの1つはFoo
インスタンスを作成します。このインスタンスに加えられたすべての変更はBar
インスタンスは静かに失われます。
これもPOLSに違反するケースです。プロパティの格納された値のみにアクセスする場合、スレッドセーフであることが期待されます。プロパティのゲッターを含め、クラスが単にスレッドセーフでないと主張することはできますが、これは通常のケースではないため、適切に文書化する必要があります。さらに、この問題の導入は、まもなく見るように不要です。
一般的には
ここで、一般的な遅延初期化について見てみましょう。
遅延初期化は、通常、オブジェクトの構築を遅延させるために使用されます。
構築するのに時間がかかるもの、あるいは多くのメモリを消費するもの
が完全に構築されると
これは、遅延初期化を使用する非常に正当な理由です。
しかし、このようなプロパティは通常、セッターを持たないので、上で指摘した最初の問題は解消されます。
さらに、スレッドセーフな実装が使用されるでしょう。
Lazy<T>
- を使用することで、2つ目の問題を回避することができます。
遅延プロパティの実装において、この2点を考慮しても、このパターンの一般的な問題点として以下の点が挙げられます。
-
オブジェクトの構築に失敗し、プロパティ・ゲッターから例外が発生する可能性があります。これはまた別の POLS 違反であるため、回避する必要があります。また プロパティのセクション の中で、プロパティ・ゲッターは例外を投げてはいけないと明記しています。
<ブロッククオートプロパティゲッターから例外をスローしないようにする。
プロパティゲッターは、前提条件を持たない単純な操作であるべきです。ゲッターが例外を投げる可能性がある場合、そのプロパティをメソッドとして再設計することを検討してください。
-
コンパイラによる自動最適化、すなわちインライン化、分岐予測に問題があります。詳しくは ビル・Kの回答 に詳しい説明があります。
これらの点を踏まえての結論は以下の通りです。
怠惰に実装された1つのプロパティごとに、これらの点を考慮する必要がありました。
つまり、ケースバイケースの判断であり、一般的なベストプラクティスとして捉えることはできないということです。
このパターンにはその場所がありますが、クラスを実装する際の一般的なベストプラクティスではありません。無条件に使用するべきではありません。 というのも、上記のような理由があるからです。
このセクションでは、他の人が遅延初期化を無条件に使用する論拠として提唱しているいくつかの点について説明したいと思います。
-
シリアライゼーション。
EricJは1つのコメントでこう述べている。シリアライズされる可能性のあるオブジェクトは、デシリアライズされるときにそのコントラクターが呼び出されることはありません(シリアライザーによりますが、多くの一般的なものはこのような挙動をします)。初期化コードをコンストラクタに入れると、デシリアライズのための追加サポートを提供しなければならないことになります。このパターンでは、そのような特別なコーディングを避けることができます。
この議論にはいくつかの問題があります。
- ほとんどのオブジェクトはシリアライズされることはありません。必要でないときに何らかのサポートを追加することは、以下のことに違反します。 YAGNI .
- クラスがシリアライゼーションをサポートする必要がある場合、一見シリアライゼーションとは関係のない回避策を講じることなく、シリアライゼーションを可能にする方法が存在します。
-
マイクロオプティマイゼーション。 あなたの主な主張は、誰かが実際にアクセスするときだけオブジェクトを構築したい、というものです。つまり、実際にはメモリ使用量の最適化について話しているわけです。
私は以下の理由から、この主張には賛成できません。- ほとんどの場合、メモリ内のオブジェクトが数個増えたところで、何ら影響はありません。最近のコンピュータには十分なメモリが搭載されています。プロファイラで実際に問題が確認されたケースでなければ、これは 時期尚早な最適化 そして、それを否定する正当な理由もあります。
-
このような最適化が正当化される場合があるという事実は認めます。しかし、このような場合でも、遅延初期化が正しい解決策であるとは思えません。これには2つの理由があります。
- 怠惰な初期化は、潜在的にパフォーマンスを低下させる。ほんのわずかかもしれませんが、Billの回答が示すように、その影響は一見して思ったよりも大きいのです。つまり、このアプローチは基本的にパフォーマンスとメモリをトレードするものなのです。
- もし、クラスの一部だけを使用することが一般的なユースケースであるような設計があれば、それは設計自体に問題があることを示唆しています。問題のクラスは、複数の責任を負っている可能性が高いです。解決策としては、そのクラスをより焦点を絞ったいくつかのクラスに分割することです。
関連
-
[解決済み】2年前のMSDateを把握する【クローズド
-
[解決済み】Unityでゲームオブジェクトのすべての子をループスルーして破壊する方法?
-
[解決済み] WPFのTextBlockで自動縦スクロールバー?
-
[解決済み] ディープクローンオブジェクト
-
[解決済み] なぜList<T>を継承しないのですか?
-
[解決済み] クラス内の項目の並び順。フィールド、プロパティ、コンストラクター、メソッド
-
[解決済み] ASP.NET Web APIでエラーを返すためのベストプラクティス
-
[解決済み] パブリックフィールドとオートマチックプロパティ
-
[解決済み】TをEnumに拘束するGenericメソッドの作成
-
[解決済み] コンストラクタを継承する方法は?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】エラー。「戻り値を変更できません」 C#
-
[解決済み】C#におけるtypedefの等価性
-
[解決済み] エンティティタイプ <type> は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み] [Solved] 不正な文字列値: '\xEFxBFxBD' for column
-
[解決済み】Visual studio 2019がデバッグ時にフリーズする件
-
[解決済み】値をNULLにすることはできません。パラメータ名:source
-
[解決済み】OnCollisionEnter2Dが実行されない?
-
[解決済み】 C# 条件演算子エラー 代入、call、increment、decrement、await、new object 式のみ文として使用可能です。
-
[解決済み】ファイルやアセンブリ、またはその依存関係の1つをロードできませんでした。
-
[解決済み】データが存在しないのに読み込もうとする試みが無効である