Buat objek baru atau setel ulang setiap properti?

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

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

Misalkan saya memiliki objek myObjectdari MyClassdan saya harus mengatur ulang sifat-sifatnya, adalah lebih baik untuk membuat objek baru atau menetapkan kembali masing-masing properti? Asumsikan saya tidak memiliki penggunaan tambahan dengan instance lama.

myObject = new MyClass();

atau

myObject.Prop1 = null;
myObject.Prop2 = null;
myObject.Prop3 = null;
Lonceng
sumber
14
Tidak dapat dijawab tanpa konteks.
CodesInChaos
2
@CodesInChaos, memang. Contoh spesifik di mana membuat objek baru mungkin tidak disukai: Pengumpulan Objek dan berusaha untuk meminimalkan alokasi untuk berurusan dengan GC.
XNargaHuntress
@ XGundam05 Tetapi meskipun begitu, sangat tidak mungkin teknik yang disarankan dalam OP ini adalah cara yang tepat untuk menghadapinya
Ben Aaronson
2
Suppose I have an object myObject of MyClass and I need to reset its properties- Jika mengatur ulang properti objek Anda diperlukan atau jika berguna untuk memodelkan domain masalah, mengapa tidak membuat reset()metode untuk MyClass. Lihat jawaban HarrisonPaine di bawah ini
Brandin

Jawaban:

49

Membuat instance objek baru selalu lebih baik, maka Anda memiliki 1 tempat untuk menginisialisasi properti (konstruktor) dan dapat dengan mudah memperbaruinya.

Bayangkan Anda menambahkan properti baru ke kelas, Anda lebih suka memperbarui konstruktor daripada menambahkan metode baru yang juga menginisialisasi ulang semua properti.

Sekarang, ada beberapa kasus di mana Anda mungkin ingin menggunakan kembali objek, yang mana properti sangat mahal untuk diinisialisasi ulang dan Anda ingin menyimpannya. Namun ini akan lebih spesialis, dan Anda akan memiliki metode khusus untuk menginisialisasi ulang semua properti lainnya. Anda masih ingin membuat objek baru terkadang bahkan untuk situasi ini.

gbjbaanb
sumber
6
Kemungkinan pilihan ketiga: myObject = new MyClass(oldObject);. Meskipun ini akan menyiratkan salinan yang tepat dari objek asli dalam contoh baru.
AmazingDreams
Saya pikir new MyClass(oldObject)ini adalah solusi terbaik untuk kasus-kasus di mana inisialisasi mahal.
rickcnagy
8
Salin konstruktor lebih gaya C ++. .NET tampaknya mendukung metode Clone () yang mengembalikan instance baru yang merupakan salinan persisnya.
Eric
2
Konstruktor dapat dengan mudah melakukan apa pun kecuali memanggil metode Inisialisasi publik () sehingga Anda masih memiliki satu titik inisialisasi yang dapat dipanggil titik apa saja untuk secara efektif membuat objek segar "baru". Saya telah menggunakan perpustakaan yang memiliki sesuatu seperti antarmuka IInitialize untuk objek yang dapat digunakan dalam kumpulan objek dan digunakan kembali untuk kinerja.
Chad Schouggins
16

Anda tentu harus lebih suka membuat objek baru di sebagian besar kasus. Masalah dengan menetapkan kembali semua properti:

  • Membutuhkan setter publik pada semua properti, yang secara drastis membatasi tingkat enkapsulasi yang dapat Anda berikan
  • Mengetahui apakah Anda memiliki penggunaan tambahan untuk instance lama berarti Anda harus tahu di mana-mana bahwa instance lama sedang digunakan. Jadi jika kelas Adan kelas Bsama-sama lulus instance dari kelas Cmaka mereka harus tahu apakah mereka akan pernah lulus contoh yang sama, dan jika demikian apakah yang lain masih menggunakannya. Ini erat pasangan kelas yang sebaliknya tidak punya alasan untuk menjadi.
  • Mengarah pada kode berulang - seperti yang ditunjukkan gbjbaanb, jika Anda menambahkan parameter ke konstruktor, di mana pun yang memanggil konstruktor akan gagal dikompilasi, tidak ada bahaya kehilangan tempat. Jika Anda hanya menambahkan properti publik, Anda harus memastikan untuk menemukan dan memperbarui secara manual setiap tempat di mana objek "diatur ulang".
  • Meningkatkan kompleksitas. Bayangkan Anda membuat dan menggunakan instance kelas dalam satu lingkaran. Jika Anda menggunakan metode Anda, maka Anda sekarang harus melakukan inisialisasi terpisah saat pertama kali melalui loop, atau sebelum loop dimulai. Either way adalah kode tambahan yang harus Anda tulis untuk mendukung dua cara inisialisasi ini.
  • Berarti kelas Anda tidak dapat melindungi diri dari keadaan tidak valid. Bayangkan Anda menulis sebuah Fractionkelas dengan pembilang dan penyebut, dan ingin memastikan bahwa itu selalu dikurangi (yaitu gcd pembilang dan penyebutnya adalah 1). Ini tidak mungkin dilakukan dengan baik jika Anda ingin orang-orang mengatur pembilang dan penyebutnya secara publik, karena mereka dapat bertransisi melalui negara yang tidak valid untuk berpindah dari satu keadaan yang valid ke yang lain. Misalnya 1/2 (valid) -> 2/2 (tidak valid) -> 2/3 (valid).
  • Sama sekali tidak idiomatis untuk bahasa tempat Anda bekerja, meningkatkan gesekan kognitif bagi siapa pun yang memelihara kode.

Ini semua adalah masalah yang cukup signifikan. Dan apa yang Anda dapatkan sebagai imbalan atas kerja ekstra yang Anda buat adalah ... tidak ada. Membuat instance objek adalah, secara umum, sangat murah, sehingga manfaat kinerja hampir selalu dapat diabaikan.

Seperti jawaban lain yang disebutkan, satu-satunya kinerja waktu yang mungkin menjadi perhatian yang relevan adalah jika kelas Anda mengerjakan pekerjaan konstruksi yang sangat mahal. Tetapi bahkan dalam kasus itu, agar teknik ini berfungsi, Anda harus dapat memisahkan bagian yang mahal dari properti yang Anda atur ulang, sehingga Anda dapat menggunakan pola bobot terbang atau yang serupa.


Sebagai catatan tambahan, beberapa masalah di atas dapat dikurangi sedikit dengan tidak menggunakan setter dan sebaliknya memiliki public Resetmetode di kelas Anda yang mengambil parameter yang sama dengan konstruktor. Jika karena alasan tertentu Anda memang ingin turun rute reset ini, itu mungkin cara yang lebih baik untuk melakukannya.

Namun, kompleksitas tambahan dan pengulangan yang menambahkan, bersama dengan poin-poin di atas yang tidak dibahas, masih merupakan argumen yang sangat persuasif untuk tidak melakukannya, terutama ketika ditimbang dengan manfaat yang tidak ada.

Ben Aaronson
sumber
Saya pikir kasus penggunaan yang jauh lebih penting untuk mengatur ulang daripada membuat objek baru adalah jika Anda benar-benar mengatur ulang objek. YAITU. jika Anda ingin kelas lain yang menunjuk ke objek untuk melihat objek baru setelah Anda meresetnya.
Taemyr
@Taemyr Mungkin, tetapi OP secara eksplisit mengatur itu dalam pertanyaan
Ben Aaronson
9

Mengingat contoh yang sangat umum, sulit untuk mengatakannya. Jika "mengatur ulang properti" masuk akal semantik dalam kasus domain, itu akan lebih masuk akal bagi konsumen kelas Anda untuk menelepon

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

Dari

MyObject = new MyClass();

Saya TIDAK AKAN PERNAH meminta konsumen panggilan kelas Anda

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

Jika kelas mewakili sesuatu yang dapat diatur ulang, itu harus mengekspos fungsionalitas itu melalui Reset()metode, daripada mengandalkan memanggil konstruktor atau secara manual mengatur propertinya.

Harrison Paine
sumber
5

Seperti yang disarankan Harrison Paine dan Brandin, saya akan menggunakan kembali objek yang sama dan memfaktisasi inisialisasi properti dalam metode 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; }
}
Antoine Trouve
sumber
Persis apa yang ingin saya jawab. Ini adalah yang terbaik dari kedua dunia - Dan tergantung pada kasus penggunaan, satu-satunya pilihan yang layak (Instance-Pooling)
Falco
2

Jika pola penggunaan yang dimaksudkan untuk suatu kelas adalah bahwa satu pemilik akan menyimpan referensi untuk setiap instance, tidak ada kode lain yang akan menyimpan salinan referensi, dan itu akan sangat umum bagi pemilik untuk memiliki loop yang perlu, berkali-kali , " isi "contoh kosong, gunakan itu untuk sementara, dan jangan pernah membutuhkannya lagi (pertemuan kelas yang umum dengan kriteria seperti itu StringBuilder) maka mungkin berguna dari sudut pandang kinerja bagi kelas untuk memasukkan metode untuk mengatur ulang contoh ke seperti- Kondisi baru. Optimalisasi seperti itu sepertinya tidak akan bernilai banyak jika alternatifnya hanya membutuhkan pembuatan beberapa ratus contoh, tetapi biaya untuk membuat jutaan atau milyaran contoh objek dapat bertambah.

Pada catatan terkait, ada beberapa pola yang dapat digunakan untuk metode yang perlu mengembalikan data dalam objek:

  1. Metode menciptakan objek baru; mengembalikan referensi.

  2. Metode menerima referensi ke objek yang bisa berubah, dan mengisinya.

  3. Metode menerima variabel tipe referensi sebagai refparameter dan menggunakan objek yang ada jika sesuai, atau mengubah variabel untuk mengidentifikasi objek baru.

Pendekatan pertama seringkali paling mudah secara semantik. Yang kedua agak canggung di sisi panggilan, tetapi dapat menawarkan kinerja yang lebih baik jika penelepon akan sering dapat membuat satu objek dan menggunakannya ribuan kali. Pendekatan ketiga agak canggung semantik, tetapi dapat membantu jika suatu metode perlu mengembalikan data dalam sebuah array dan pemanggil tidak akan tahu ukuran array yang diperlukan. Jika kode panggilan menyimpan satu-satunya referensi ke array, menulis ulang referensi itu dengan referensi ke array yang lebih besar akan secara semantik setara dengan hanya membuat array lebih besar (yang merupakan perilaku yang diinginkan secara semantik). Meskipun menggunakan List<T>mungkin lebih baik daripada menggunakan array yang diubah ukurannya secara manual dalam banyak kasus, array struktur menawarkan semantik yang lebih baik dan kinerja yang lebih baik daripada daftar struktur.

supercat
sumber
1

Saya pikir kebanyakan orang menjawab untuk menyukai membuat objek baru kehilangan skenario kritis: pengumpulan sampah (GC). GC dapat memiliki hit kinerja nyata dalam aplikasi yang menciptakan banyak objek (pikirkan game, atau aplikasi ilmiah).

Katakanlah saya memiliki pohon ekspresi yang mewakili ekspresi matematika, di mana di dalam simpul adalah simpul fungsi (ADD, SUB, MUL, DIV), dan simpul daun adalah simpul terminal (X, e, PI). Jika kelas simpul saya memiliki metode Evaluate (), kemungkinan akan memanggil anak-anak secara berulang dan mengumpulkan data melalui data node. Paling tidak, semua simpul daun perlu membuat objek Data dan kemudian mereka dapat digunakan kembali pada jalan menaiki pohon sampai nilai akhir dievaluasi.

Sekarang katakanlah saya memiliki ribuan pohon ini dan saya mengevaluasi mereka dalam satu lingkaran. Semua objek Data tersebut akan memicu GC dan menyebabkan kinerja, yang besar (hilangnya utilisasi CPU hingga 40% untuk beberapa proses di aplikasi saya - Saya telah menjalankan profiler untuk mendapatkan data).

Solusi yang memungkinkan? Gunakan kembali objek data tersebut dan panggil beberapa .Reset () pada mereka setelah Anda selesai menggunakannya. Node daun tidak akan lagi memanggil 'Data baru ()', mereka akan memanggil beberapa metode Pabrik yang menangani siklus hidup objek.

Saya tahu ini karena saya memiliki aplikasi yang telah mengenai masalah ini dan ini menyelesaikannya.

Jonathan Lavering
sumber