Pertanyaan Strategi untuk meneruskan warna (menghindari ref?)


Saya mem-refactoring kode dalam aplikasi windows yang menua, dan saya menemukan pola yang tidak saya sukai: Sebuah kelas memiliki variabel warna global, seperti berikut:

private Color myForegroundColor = Color.Azure;
private Color myBackgroundColor = Color.Empty;
// ...etc. 

Ada banyak dari ini, dan mereka sedang diedarkan dengan referensi metode yang bertanggung jawab untuk mengatur bagian-bagian tertentu dari UI.

Saya kumpulkan itu Color adalah struct, dan bahwa setiap warna oleh karena itu dilewatkan oleh ref untuk menghindari membuat salinan baru setiap kali metode dipanggil. Yaitu sesuatu seperti:

// Avoid creating a copy of myForgroundColor inside SetUpButton():
MyHelperClass.SetUpButton(ref myForegroundColor); 

Saya tidak bisa menahan perasaan bahwa penggunaan ini ref di seluruh kelas ini dan kelas terkait buruk. Rasanya seperti "bau kode", meskipun aku tidak bisa benar-benar tahu kenapa.

Saya telah melihat beberapa pos pada masalah yang serupa, dengan rekomendasi seperti "gunakan kelas yang berisi warna, yang kemudian dilewatkan sebagai jenis nilai", tetapi tidak sepenuhnya jelas bagaimana cara terbaik untuk melakukan ini.

Yang ingin saya lakukan adalah membuat sesuatu yang mirip dengan yang berikut:

public class ColorContainer
{
    public UiSettingsContainer()
    {
        MyColor = Color.Black;
        MyNextColor = Color.Blue;
        // ..etc...
    }

    public Color MyColor { get; private set; }
    // ...etc....
}

Ini akan membiarkan saya mengendalikan warna, tetapi implikasi pada ingatan sedikit tidak jelas bagi saya; jika saya membuat instance dari kelas ini dan meneruskannya ke metode yang membutuhkan info tentang warna yang terkandung, tidak akan salinan a color (dengan itu menjadi struct) dibuat segera setelah menerapkan metode mengimplementasikannya?

Apakah saya benar dengan asumsi bahwa kode ini akan membuat salinan baru, dan dengan demikian kurang efektif ...

// Assumption: This creates a new copy of color in memory.
public void SetSomeColor(Color col){ 
    someComponent.color = col; 
}

// Calling it:
SetSomeColor(myColorContainerInstance.MyColor);

... daripada kode ini, yang hanya akan menggunakan struct yang sudah ada ?:

// Question: Does this avoid creating a new copy of MyColor in memory?
public void SetSomeColor(ColorContainer container){ 
    someComponent.color = container.MyColor; 
}

// Calling it:
SetSomeColor(myColorContainerInstance);

Saat ini saya condong ke arah solusi yang serupa dengan yang berikut ini, di mana saya mengumpulkan warna dalam kelas terpisah dan mengatur ulang kode sedikit, tetapi tetap menggunakan ref. Dalam hal ini, bagaimanapun, MyColor harus menjadi bidang publik di ColorContainer, yang berarti saya akan memiliki kontrol lebih kecil atas siapa yang dapat mengatur nilainya:

// Assumption: This creates a new copy of color in memory.
public void SetSomeColor(ref Color col){ 
    someComponent.color = col; 
}

// Calling it:
SetSomeColor(ref myColorContainerInstance.MyColor);

Apakah ini solusi yang baik, atau adakah strategi yang lebih baik untuk menangani sumber daya seperti ini?


5
2018-01-03 10:25


asal


Jawaban:


Semua ini baunya seperti pengoptimalan dini, bagian 3 dan 4 dari tautan khusus, jadi ...

Solusi lain adalah dengan hanya menghapus ref, dan salin Color struct kapan pun dibutuhkan. Struct itu sendiri tidak terlalu besar (4 byte anggota dan 4 bool anggota), dan kecuali Anda memanggil kode yang mengubah warna beberapa juta kali per detik, waktu dan memori yang diperlukan tidak menjadi masalah.


4
2018-01-03 10:37



Analogi "slots" telah lama menjadi salah satu favorit saya untuk jenis hal ini. Setiap parameter metode (pertimbangkan tangan kanan tugas sebagai parameter juga) adalah slot. Setiap slot harus diisi dengan sesuatu dengan ukuran dan "bentuk" yang benar (jenis) agar suatu metode dapat dipanggil (atau tugas untuk diproses).

Dalam hal metode Anda membutuhkan ref Color Anda mengisi slot dengan penunjuk ukuran apa pun ke Color struct dalam memori. Tentu saja saya tidak bermaksud penunjuk gaya C, tetapi itu masih hal yang sama - itu adalah angka yang menunjukkan lokasi sumber daya yang ingin Anda gunakan bahkan jika itu tidak diwakili dalam kode seperti itu. Dalam kasus Color(tanpa ref) Anda mengisinya dengan Color struct itu sendiri.

Bergantung pada platform yang telah Anda kompilasi, nilai yang Anda keluarkan (untuk pengulangan warna) akan menjadi 32 atau 64 bit panjangnya. Susunan warna (System.Windows.Media.Color) itu sendiri hanya 32 bit panjangnya (atau 64 bit panjangnya jika Anda menggunakan System.Drawing.Color) membuat ini menjadi proposisi yang tidak menarik - membuat skenario kasus rata-rata sama (dalam hal jumlah salinan dari pointer dan ukuran dari hal-hal yang dimuat ke stack) sebagai melewati struct dengan nilai - lebih baik hanya dalam 64 bit struct / 32 bit platform pairing dan lebih buruk hanya dalam 32 bit struct / 64 bit platform pairing. Nilai sebenarnya dari struct akan tetap disalin ke slot targetnya bahkan setelah upaya ini untuk menggunakannya menggunakan instance yang sama.

Sekarang, menggabungkan warna-warna dalam satu kelas (di mana dengan ref passing adalah default) mengubah gambar ini sedikit. Jika kelas Anda berisi mengatakan 3 warna, Anda punya 96 bit (atau 192 bit) data warna yang terdapat di dalamnya, memberikan sekitar 64 bit informasi untuk menemukan lokasi "paket" yang benar untuk informasi dalam memori . Warna masih akan disalin ke dalam slot target mereka bahkan setelah pengemasan; tetapi sekarang kami telah menambahkan overhead dari harus baik ldfld (bidang beban) /panggilan (memanggil metode pra-diselesaikan - akses properti) + ldfld /callvirt (memanggil metode penyelesaian runtime - akses properti) + ldfld untuk benar-benar mendapatkan nilainya. Dari sudut pandang kinerja, ini tidak benar-benar membantu Anda, kecuali Anda berniat untuk mengirimkan banyak data dan kemudian tidak menggunakannya.

Singkat cerita - kecuali ada beberapa pengelompokan logis informasi warna yang ingin Anda capai, jangan repot-repot. Nilai jenis pada tumpukan dibersihkan segera ketika tumpukan bingkai muncul sehingga kecuali program Anda berjalan pada ~ 8 byte pendek dari total memori sistem Anda, Anda benar-benar tidak mendapatkan apa-apa dengan pendekatan ref. Kelas wrapper untuk koleksi warna kemungkinan akan membuat kode lebih bersih / lebih baik, tetapi tidak berperforma lebih baik.


1
2018-01-03 11:16



Melewati struct oleh ref pada umumnya adalah hal yang baik kecuali jika (1) seseorang menginginkan nilai semantik berlalu, atau (2) structnya kecil dan seseorang dapat hidup dengan semantik pass-by-value.

Jika salah satu akan sering ingin sekitar beberapa variabel (yang mungkin struct sendiri) sebagai kelompok, mungkin akan membantu untuk mendeklarasikan tipe struct transparan untuk menahan mereka, dan kemudian melewati itu dengan ref.

Perhatikan bahwa meneruskan struct dengan ref pada dasarnya memiliki biaya yang sama dengan melewatkan referensi kelas berdasarkan nilai. Menulis kelas untuk menahan struct, murni sehingga seseorang dapat menghindari penggunaan ref parameter, tidak cenderung menjadi kemenangan kinerja. Dalam beberapa kasus, mungkin berguna untuk memiliki tipe

class MutableHolder<T>
{ public T Value; }

yang kemudian dapat menerapkan semantik referensi ke jenis struct apa pun, tetapi saya hanya menyarankan untuk melakukannya jika seseorang perlu mempertahankan referensi di luar lingkup saat ini. Dalam kasus di mana refakan cukup, yang harus digunakan ref.


1
2018-01-13 00:33