web-dev-qa-db-ja.com

新しいオブジェクトを作成するか、すべてのプロパティをリセットしますか?

 public class MyClass
    {
        public object Prop1 { get; set; }

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

myObjectのオブジェクトMyClassがあり、そのプロパティをリセットする必要があるとします。新しいオブジェクトを作成するか、各プロパティを再割り当てする方が良いですか?古いインスタンスを使用する必要がないと仮定します。

myObject = new MyClass();

または

myObject.Prop1 = null;
myObject.Prop2 = null;
myObject.Prop3 = null;
29
Bells

新しいオブジェクトのインスタンス化は常に優れており、プロパティ(コンストラクター)を初期化する場所が1つあり、簡単に更新できます。

クラスに新しいプロパティを追加するとします。すべてのプロパティを再初期化する新しいメソッドを追加するのではなく、コンストラクタを更新したいとします。

現在、オブジェクトを再利用したい場合があります。プロパティを再初期化するには非常にコストがかかり、それを保持したい場合があります。ただし、これはより専門的であり、他のすべてのプロパティを再初期化する特別な方法があります。このような場合でも、新しいオブジェクトを作成する必要がある場合があります。

49
gbjbaanb

vast大多数のケースで新しいオブジェクトを作成することを間違いなく好むはずです。すべてのプロパティの再割り当てに関する問題:

  • すべてのプロパティにパブリックセッターが必要です。これにより、提供できるカプセル化のレベルが大幅に制限されます
  • 古いインスタンスをさらに使用するかどうかを知ることは、古いインスタンスが使用されていることをどこでも知る必要があることを意味します。したがって、クラスAとクラスBの両方にクラスCのインスタンスが渡される場合、同じインスタンスが渡されるかどうかを確認する必要があります。他の人はまだそれを使用しています。これは、他の理由がないクラスを密結合します。
  • Gbjbaanbが示すように、コードの繰り返しにつながります。コンストラクターにパラメーターを追加すると、コンストラクターを呼び出すすべての場所でコンパイルが失敗し、スポットを見逃す危険はありません。パブリックプロパティを追加するだけの場合は、オブジェクトが「リセット」されるすべての場所を手動で見つけて更新する必要があります。
  • 複雑さが増します。ループでクラスのインスタンスを作成して使用しているとします。メソッドを使用する場合は、最初にループを介して、またはループを開始する前に、個別の初期化を行う必要があります。どちらの方法も、これらの2つの初期化方法をサポートするために作成する必要がある追加のコードです。
  • クラスが無効な状態になるのを防ぐことができないことを意味します。分子と分母を使用してFractionクラスを作成し、それが常に削減されるように強制したいと想像してください(つまり、分子と分母のgcdは1でした)。分子と分母をパブリックに設定できるようにしたい場合、無効な状態から別の有効な状態に移行する可能性があるため、これをうまく行うことは不可能です。例えば。 1/2(有効)-> 2/2(無効)-> 2/3(有効)。
  • 作業している言語でまったく慣用的ではないため、コードを保守している人の認知摩擦が高まります。

これらはすべてかなり重大な問題です。そして、あなたが作成した追加の仕事と引き換えに得るものは...何もありません。オブジェクトのインスタンスを作成することは、一般的に信じられないほど安いので、パフォーマンスのメリットはほとんど常に無視できます。

他の回答が述べたように、パフォーマンスに関連する唯一の時間パフォーマンスは、クラスが構築にかなりのコストのかかる作業を行うかどうかです。ただし、その場合でも、この手法が機能するには、リセットするプロパティから高価な部分を分離できる必要があるため、 flyweight pattern を使用できます。または代わりに同様。


補足として、上記の問題のsomeは、セッターを使用せず、代わりにpublic Resetメソッドは、コンストラクターと同じパラメーターを取るクラスのメソッドです。何らかの理由でこのリセットルートを使用したい場合は、おそらくそれがはるかに優れた方法です。

それでも、追加された追加の複雑さと繰り返しは、それが対処していない上記の点とともに、それを行うことに対して非常に説得力のある議論です。特に、存在しない利点と比較検討する場合はそうです。

16
Ben Aaronson

非常に一般的な例を考えると、わかりにくいです。ドメインの場合に「プロパティのリセット」が意味的に意味をなす場合、クラスのコンシューマが呼び出す方が意味があります。

_MyObject.Reset(); // Sets all necessary properties to null
_

より

_MyObject = new MyClass();
_

私はあなたのクラスの呼び出しのコンシューマーを作ることを決して必要としません

_MyObject.Prop1 = null;
MyObject.Prop2 = null; // and so on
_

クラスがリセット可能なものを表す場合、コンストラクターの呼び出しやプロパティの手動設定に依存するのではなく、Reset()メソッドを通じてその機能を公開する必要があります。

9
Harrison Paine

Harrison PaineとBrandinが示唆するように、同じオブジェクトを再利用して、Resetメソッドのプロパティの初期化を因数分解します。

public class MyClass
{
    public MyClass() { this.Reset() }

    public void Reset() {
        this.Prop1 = whatever
        this.Prop2 = you name it
        this.Prop3 = oh yeah
    }

    public object Prop1 { get; set; }

    public object Prop2 { get; set; }

    public object Prop3 { get; set; }
}
5
Antoine Trouve

クラスの意図された使用パターンが単一の所有者が各インスタンスへの参照を保持することである場合、他のコードは参照のコピーを保持せず、所有者が必要とするループを持つことは非常に一般的です何度も、空白のインスタンスを「埋める」、一時的に使用し、二度と必要としない(このような基準を満たす一般的なクラスはStringBuilderです)、パフォーマンスの観点からは有用かもしれませんインスタンスを新しい状態にリセットするメソッドを含むクラス。代替手段が数百のインスタンスの作成のみを必要とする場合、このような最適化はあまり価値がありませんが、数百万または数十億のオブジェクトインスタンスを作成するコストが追加される可能性があります。

関連する注意点として、オブジェクトのデータを返す必要があるメソッドに使用できるパターンがいくつかあります。

  1. メソッドは新しいオブジェクトを作成します。参照を返します。

  2. メソッドは、可変オブジェクトへの参照を受け入れ、それを埋めます。

  3. メソッドは、参照型変数をrefパラメーターとして受け入れ、適切な場合は既存のオブジェクトを使用するか、変数を変更して新しいオブジェクトを識別します。

最初のアプローチは、多くの場合、意味的に最も簡単です。 2番目は、呼び出し側では少し厄介ですが、呼び出し元が1つのオブジェクトを頻繁に作成して何千回も使用できる場合は、パフォーマンスが向上する可能性があります。 3番目のアプローチは意味的に少し厄介ですが、メソッドが配列でデータを返す必要があり、呼び出し元が必要な配列サイズを知らない場合に役立ちます。呼び出しコードが配列への唯一の参照を保持している場合、より大きな配列への参照でその参照を書き換えることは、意味的に配列を大きくすることと同じです(これは、意味的に望ましい動作です)。多くの場合、List<T>の使用は手動でサイズ変更された配列を使用するよりも優れていますが、構造体の配列は構造体のリストよりも優れたセマンティクスとパフォーマンスを提供します。

2
supercat

新しいオブジェクトの作成を支持することに答えるほとんどの人は、ガベージコレクション(GC)という重要なシナリオを見逃していると思います。 GCは、多くのオブジェクトを作成するアプリケーション(ゲームや科学アプリケーションなど)で実際のパフォーマンスに影響を与える可能性があります。

数式を表す式ツリーがあるとします。内部ノードは関数ノード(ADD、SUB、MUL、DIV)で、リーフノードはターミナルノード(X、e、PI)です。私のノードクラスにEvaluate()メソッドがある場合、それはおそらく子を再帰的に呼び出し、Dataノードを介してデータを収集します。少なくとも、すべてのリーフノードはDataオブジェクトを作成する必要があり、それらは最終的な値が評価されるまでツリーの途中で再利用できます。

ここで、これらのツリーが何千もあり、ループでそれらを評価しているとしましょう。これらのすべてのデータオブジェクトがGCをトリガーし、パフォーマンスに大きな影響を与えます(アプリの一部の実行で最大40%のCPU使用率の損失-データを取得するためにプロファイラーを実行しました)。

可能な解決策は?それらのデータオブジェクトを再利用し、使用が終わったら、それらに対して.Reset()を呼び出すだけです。リーフノードは「new Data()」を呼び出さなくなり、オブジェクトのライフサイクルを処理するファクトリメソッドを呼び出します。

この問題に該当するアプリケーションがあり、これで問題が解決したので、これを知っています。

1