Di .NET, tipe nilai (C # struct
) tidak dapat memiliki konstruktor tanpa parameter. Menurut posting ini ini diamanatkan oleh spesifikasi CLI. Apa yang terjadi adalah bahwa untuk setiap tipe nilai konstruktor default dibuat (oleh kompiler?) Yang menginisialisasi semua anggota ke nol (atau null
).
Mengapa tidak diizinkan untuk mendefinisikan konstruktor default seperti itu?
Satu penggunaan sepele adalah untuk bilangan rasional:
public struct Rational {
private long numerator;
private long denominator;
public Rational(long num, long denom)
{ /* Todo: Find GCD etc. */ }
public Rational(long num)
{
numerator = num;
denominator = 1;
}
public Rational() // This is not allowed
{
numerator = 0;
denominator = 1;
}
}
Menggunakan versi C # saat ini, Rasional default adalah 0/0
yang tidak begitu keren.
PS : Akankah parameter default membantu menyelesaikan ini untuk C # 4.0 atau akankah konstruktor default yang ditentukan CLR dipanggil?
Jon Skeet menjawab:
Untuk menggunakan contoh Anda, apa yang Anda inginkan terjadi ketika seseorang melakukannya:
Rational[] fractions = new Rational[1000];
Haruskah itu berjalan melalui konstruktor Anda 1000 kali?
Tentu harus, itu sebabnya saya menulis konstruktor default di tempat pertama. CLR harus menggunakan konstruktor zeroing default ketika tidak ada konstruktor default eksplisit didefinisikan; dengan begitu Anda hanya membayar untuk apa yang Anda gunakan. Lalu jika saya ingin 1000 kontainer non-default Rational
(dan ingin mengoptimalkan 1000 konstruksi), saya akan menggunakan List<Rational>
bukan array.
Alasan ini, dalam pikiran saya, tidak cukup kuat untuk mencegah definisi konstruktor default.
Rational()
memanggil ctor parameterless daripadaRational(long num=0, long denom=1)
.new Rational()
akan memanggil konstruktor jika ada, namun jika tidak ada,new Rational()
akan setara dengandefault(Rational)
. Bagaimanapun Anda didorong untuk menggunakan sintaksdefault(Rational)
ketika Anda ingin "nilai nol" dari struct Anda (yang merupakan angka "buruk" dengan desain yang Anda usulkanRational
). Nilai default untuk tipe nilaiT
selaludefault(T)
. Jadinew Rational[1000]
tidak akan pernah memanggil konstruktor struct.denominator - 1
di dalam struct, sehingga nilai default menjadi 0/1Then if I want a container of 1000 non-default Rationals (and want to optimize away the 1000 constructions) I will use a List<Rational> rather than an array.
Mengapa Anda mengharapkan array untuk memanggil konstruktor yang berbeda ke Daftar untuk struct?Jawaban:
Catatan: jawaban di bawah ini ditulis jauh sebelum C # 6, yang berencana memperkenalkan kemampuan untuk mendeklarasikan konstruktor tanpa parameter dalam struct - tetapi mereka masih tidak akan dipanggil dalam semua situasi (misalnya untuk pembuatan array)(pada akhirnya fitur ini tidak ditambahkan ke C # 6 ).EDIT: Saya sudah mengedit jawaban di bawah ini karena wawasan Grauenwolf tentang CLR.
CLR memungkinkan tipe nilai memiliki konstruktor tanpa parameter, tetapi C # tidak. Saya percaya ini karena itu akan memperkenalkan harapan bahwa konstruktor akan dipanggil ketika tidak. Sebagai contoh, pertimbangkan ini:
CLR mampu melakukan ini dengan sangat efisien hanya dengan mengalokasikan memori yang sesuai dan memusatkan semuanya. Jika harus menjalankan konstruktor MyStruct 1000 kali, itu akan jauh lebih efisien. (Bahkan, tidak - jika Anda melakukan memiliki konstruktor parameterless, itu tidak bisa berjalan ketika Anda membuat sebuah array, atau ketika Anda memiliki variabel contoh diinisiasi.)
Aturan dasar dalam C # adalah "nilai default untuk semua jenis tidak dapat bergantung pada inisialisasi apa pun". Sekarang mereka bisa membiarkan konstruktor tanpa parameter didefinisikan, tetapi kemudian tidak mengharuskan konstruktor untuk dieksekusi dalam semua kasus - tetapi itu akan menyebabkan lebih banyak kebingungan. (Atau setidaknya, jadi saya percaya argumennya masuk.)
EDIT: Untuk menggunakan contoh Anda, apa yang Anda inginkan terjadi ketika seseorang melakukannya:
Haruskah itu berjalan melalui konstruktor Anda 1000 kali?
EDIT: (Menjawab lebih banyak pertanyaan) Konstruktor tanpa parameter tidak dibuat oleh kompiler. Tipe nilai tidak harus memiliki konstruktor sejauh menyangkut CLR - meskipun ternyata bisa jika Anda menulisnya di IL. Ketika Anda menulis "
new Guid()
" dalam C # yang memancarkan IL berbeda dengan apa yang Anda dapatkan jika Anda memanggil konstruktor normal. Lihat pertanyaan SO ini untuk sedikit lebih banyak tentang aspek itu.Saya menduga bahwa tidak ada tipe nilai dalam kerangka kerja dengan konstruktor tanpa parameter. Tidak diragukan lagi NDepend dapat memberi tahu saya jika saya memintanya dengan cukup baik ... Fakta bahwa C # melarang itu adalah petunjuk yang cukup besar bagi saya untuk berpikir itu mungkin ide yang buruk.
sumber
Rational[] fractions = new Rational[1000];
juga menyia-nyiakan banyak pekerjaan jikaRational
kelas bukan sebuah struct? Jika demikian, mengapa kelas memiliki ctor default?Rational
kelas, Anda akan berakhir dengan 1000 referensi referensi kosong.Str adalah tipe nilai dan tipe nilai harus memiliki nilai default segera setelah dideklarasikan.
Jika Anda mendeklarasikan dua bidang seperti di atas tanpa membuat instance, maka pecahkan debugger,
m
akan menjadi nol tetapim2
tidak akan. Mengingat ini, konstruktor tanpa parameter tidak masuk akal, pada kenyataannya semua konstruktor pada struct tidak memberikan nilai, hal itu sendiri sudah ada hanya dengan mendeklarasikannya. Memang m2 dapat dengan senang hati digunakan dalam contoh di atas dan metodenya disebut, jika ada, dan bidang serta propertinya dimanipulasi!sumber
new
itu, lalu coba gunakan anggotanyaMeskipun CLR memungkinkannya, C # tidak mengizinkan struct untuk memiliki konstruktor tanpa parameter default. Alasannya adalah bahwa, untuk tipe nilai, kompiler secara default tidak menghasilkan konstruktor default, juga tidak menghasilkan panggilan ke konstruktor default. Jadi, bahkan jika Anda mendefinisikan konstruktor default, itu tidak akan dipanggil, dan itu hanya akan membingungkan Anda.
Untuk menghindari masalah seperti itu, kompiler C # melarang definisi konstruktor default oleh pengguna. Dan karena itu tidak menghasilkan konstruktor default, Anda tidak bisa menginisialisasi bidang ketika mendefinisikannya.
Atau alasan besarnya adalah bahwa suatu struktur adalah tipe nilai dan tipe nilai diinisialisasi dengan nilai default dan konstruktor digunakan untuk inisialisasi.
Anda tidak harus instantiate struct Anda dengan
new
kata kunci. Alih-alih bekerja seperti int; Anda dapat langsung mengaksesnya.Structs tidak dapat mengandung konstruktor tanpa parameter eksplisit. Anggota Struct secara otomatis diinisialisasi ke nilai default mereka.
Konstruktor (parameter-kurang) default untuk struct dapat menetapkan nilai yang berbeda dari keadaan all-zeroed yang akan menjadi perilaku tak terduga. Oleh karena itu .NET runtime melarang konstruktor default untuk sebuah struct.
sumber
MyStruct s;
tidak memanggil konstruktor default yang Anda berikan.Anda dapat membuat properti statis yang menginisialisasi dan mengembalikan nomor "rasional" default:
Dan gunakan seperti:
sumber
Rational.Zero
mungkin akan sedikit membingungkan.Penjelasan singkat:
Dalam C ++, struct dan kelas hanyalah dua sisi dari koin yang sama. Satu-satunya perbedaan nyata adalah bahwa yang satu bersifat publik dan yang lainnya bersifat pribadi.
Di .NET , ada perbedaan yang jauh lebih besar antara struct dan kelas. Yang utama adalah bahwa struct menyediakan semantik tipe-nilai, sementara kelas menyediakan semantik tipe-referensi. Ketika Anda mulai memikirkan implikasi dari perubahan ini, perubahan lain mulai lebih masuk akal juga, termasuk perilaku konstruktor yang Anda gambarkan.
sumber
new
benar - benar harus ditulis untuk memanggil konstruktor. Dalam C ++ konstruktor dipanggil dengan cara tersembunyi, pada deklarasi atau instanciation array. Dalam C # baik semuanya adalah pointer jadi mulai dari nol, baik itu sebuah struct dan harus memulai sesuatu, tetapi ketika Anda tidak dapat menulisnew
... (seperti array init), itu akan melanggar aturan C # yang kuat.Saya belum melihat yang setara dengan solusi akhir yang akan saya berikan, jadi ini dia.
gunakan offset untuk memindahkan nilai dari default 0 ke nilai apa pun yang Anda suka. di sini properti harus digunakan alih-alih langsung mengakses bidang. (mungkin dengan fitur c # 7 yang mungkin Anda lebih baik mendefinisikan bidang cakupan properti sehingga mereka tetap terlindungi dari akses langsung dalam kode.)
Solusi ini berfungsi untuk struct sederhana dengan hanya tipe nilai (tanpa tipe ref atau nullable struct).
Ini berbeda dari jawaban ini, pendekatan ini bukan casing utama tetapi menggunakan offset yang akan bekerja untuk semua rentang.
contoh dengan enums sebagai bidang.
Seperti yang saya katakan trik ini mungkin tidak berfungsi dalam semua kasus, bahkan jika struct hanya memiliki bidang nilai, hanya Anda yang tahu jika itu berfungsi dalam kasus Anda atau tidak. hanya memeriksa. tetapi Anda mendapatkan ide umum.
sumber
Hanya kasus khusus itu. Jika Anda melihat pembilang 0 dan penyebut 0, berpura-puralah memiliki nilai yang Anda inginkan.
sumber
Nullable<T>
(misalnyaint?
).new Rational(x,y)
mana x dan y adalah 0?Apa yang saya gunakan adalah operator null-penggabungan (??) dikombinasikan dengan bidang dukungan seperti ini:
Semoga ini membantu ;)
Catatan: tugas penggabungan nol saat ini adalah proposal fitur untuk C # 8.0.
sumber
Anda tidak dapat menentukan konstruktor default karena Anda menggunakan C #.
Structs dapat memiliki konstruktor default di .NET, meskipun saya tidak tahu bahasa tertentu yang mendukungnya.
sumber
Inilah solusi saya untuk dilema konstruktor default. Saya tahu ini adalah solusi yang terlambat, tetapi saya pikir perlu dicatat bahwa ini adalah solusi.
mengabaikan fakta bahwa saya memiliki struct statis yang disebut null, (Catatan: Ini hanya untuk semua kuadran positif), menggunakan get; set; di C #, Anda dapat mencoba / menangkap / akhirnya, untuk menangani kesalahan di mana tipe data tertentu tidak diinisialisasi oleh konstruktor default Point2D (). Saya kira ini sulit dipahami sebagai solusi untuk beberapa orang pada jawaban ini. Itu sebabnya saya menambahkan milik saya. Menggunakan fungsionalitas pengambil dan penyetel dalam C # akan memungkinkan Anda untuk memotong konstruktor default ini tidak masuk akal dan mencoba menangkap apa yang tidak Anda inisialisasi. Bagi saya ini berfungsi dengan baik, untuk orang lain Anda mungkin ingin menambahkan beberapa pernyataan if. Jadi, Jika Anda menginginkan pengaturan Numerator / Denominator, kode ini mungkin membantu. Saya hanya ingin menegaskan kembali bahwa solusi ini tidak terlihat bagus, mungkin bekerja lebih buruk dari sudut pandang efisiensi, tetapi, untuk seseorang yang datang dari versi C # yang lebih lama, menggunakan tipe data array memberi Anda fungsi ini. Jika Anda hanya menginginkan sesuatu yang berfungsi, coba ini:
sumber
sumber