Setelah menulis lebih dari satu artikel tentang
Veeam Academy , kami memutuskan untuk membuka dapur internal kecil dan menawarkan kepada Anda beberapa contoh dalam C # yang kami analisis dengan siswa kami. Ketika mengkompilasi mereka, kami dipandu oleh fakta bahwa audiens kami adalah pengembang pemula, tetapi juga bisa menarik bagi programmer berpengalaman untuk mencari di bawah kucing. Tujuan kami adalah untuk menunjukkan seberapa dalam lubang kelinci itu, sementara pada saat yang sama menjelaskan fitur struktur internal C #.
Di sisi lain, kami akan senang mendengar komentar dari kolega yang berpengalaman yang akan menunjukkan kekurangan dalam contoh kami, atau berbagi sendiri. Mereka suka menggunakan pertanyaan seperti itu di wawancara, jadi pasti kita semua punya sesuatu untuk diceritakan.
Kami berharap bahwa pilihan kami akan bermanfaat bagi Anda, membantu menyegarkan kembali pengetahuan Anda atau hanya tersenyum.

Contoh 1
Struktur dalam C #. Dengan mereka, bahkan pengembang yang berpengalaman sering memiliki pertanyaan, yang sangat sering digunakan oleh semua jenis tes online.
Contoh pertama kami adalah contoh perhatian dan pengetahuan tentang apa yang menggunakan blok memperluas menjadi. Dan juga topik yang cukup untuk komunikasi selama wawancara.
Pertimbangkan kodenya:
public struct SDummy : IDisposable { private bool _dispose; public void Dispose() { _dispose = true; } public bool GetDispose() { return _dispose; } static void Main(string[] args) { var d = new SDummy(); using (d) { Console.WriteLine(d.GetDispose()); } Console.WriteLine(d.GetDispose()); } }
Apa yang akan dicetak metode Utama ke konsol?Perhatikan bahwa SDummy adalah struktur yang mengimplementasikan antarmuka IDisposable, sehingga variabel tipe SDummy dapat digunakan dalam blok using.
Menurut
spesifikasi bahasa C #, menggunakan pernyataan untuk tipe signifikan pada waktu kompilasi meluas ke blok coba-akhirnya:
try { Console.WriteLine(d.GetDispose()); } finally { ((IDisposable)d).Dispose(); }
Jadi, dalam kode kami, metode GetDispose () dipanggil di dalam blok using, yang mengembalikan bidang Boolean _dispose, nilai yang belum ditetapkan untuk objek d (hanya diatur dalam metode Buang (), yang belum dipanggil) dan oleh karena itu nilainya dikembalikan Standarnya adalah False. Apa selanjutnya
Dan kemudian yang paling menarik.
Menjalankan garis di blok akhirnya
((IDisposable)d).Dispose();
biasanya mengarah ke tinju. Ini tidak sulit untuk dilihat, misalnya, di
sini (di kanan atas Hasil, pertama-tama pilih C #, dan kemudian IL):
Dalam hal ini, metode Buang sudah dipanggil untuk objek lain, dan bukan untuk objek d sama sekali.
Jalankan program kami dan lihat bahwa program tersebut benar-benar menampilkan "False False" pada konsol. Tapi apakah itu sesederhana itu? :)
Faktanya, TIDAK ADA KEMASAN YANG TERJADI. Apa, menurut Eric Lippert, dilakukan demi optimasi (lihat di
sini dan di
sini ).
Tetapi, jika tidak ada kemasan (yang dengan sendirinya tampak mengejutkan), mengapa "False False" dan bukan "False True" di layar, karena Buang sekarang harus diterapkan pada objek yang sama?!?
Dan di sini bukan untuk itu!
Lihatlah
kompiler C # yang memperluas program kami ke:
public struct SDummy : IDisposable { private bool _dispose; public void Dispose() { _dispose = true; } public bool GetDispose() { return _dispose; } private static void Main(string[] args) { SDummy sDummy = default(SDummy); SDummy sDummy2 = sDummy; try { Console.WriteLine(sDummy.GetDispose()); } finally { ((IDisposable)sDummy2).Dispose(); } Console.WriteLine(sDummy.GetDispose()); } }
Ada variabel baru sDummy2, di mana metode Buang () diterapkan!
Dari mana variabel tersembunyi ini berasal?
Mari kita beralih ke
spec lagi :
Pernyataan menggunakan formulir 'menggunakan pernyataan (ekspresi)' memiliki tiga kemungkinan ekspansi yang sama. Dalam hal ini, ResourceType secara implisit adalah tipe kompilasi-waktu dari ekspresi ... Variabel 'sumber daya' tidak dapat diakses di, dan tidak terlihat oleh, pernyataan tertanam.
T.O. variabel sDummy tidak terlihat dan tidak dapat diakses oleh pernyataan tertanam dari blok menggunakan, dan semua operasi di dalam ekspresi ini dilakukan dengan variabel sDummy2 lain.
Akibatnya, metode Utama menampilkan konsol "False False", dan bukan "False True", karena banyak dari mereka yang menemukan contoh ini untuk pertama kalinya percaya. Dalam hal ini, pastikan untuk diingat bahwa tidak ada kemasan, tetapi variabel tersembunyi tambahan dibuat.
Kesimpulan umum adalah ini: tipe nilai yang bisa berubah adalah kejahatan yang sebaiknya dihindari.
Contoh serupa dipertimbangkan di
sini . Jika topiknya menarik, kami sangat merekomendasikan mengintip.
Saya ingin mengucapkan terima kasih khusus kepada
SergeyT atas komentar berharga tentang contoh ini.
Contoh 2
Konstruktor dan urutan panggilan mereka adalah salah satu topik utama bahasa pemrograman berorientasi objek. Kadang-kadang urutan panggilan seperti itu mungkin mengejutkan dan, bahkan lebih buruk, bahkan "mengisi" program pada saat yang paling tidak terduga.
Jadi, pertimbangkan kelas MyLogger:
class MyLogger { static MyLogger innerInstance = new MyLogger(); static MyLogger() { Console.WriteLine("Static Logger Constructor"); } private MyLogger() { Console.WriteLine("Instance Logger Constructor"); } public static MyLogger Instance { get { return innerInstance; } } }
Misalkan kelas ini memiliki beberapa logika bisnis yang kita perlukan untuk mendukung logging (fungsionalitas tidak begitu penting saat ini).
Mari kita lihat apa yang ada di kelas MyLogger kami:
- Konstruktor statis ditentukan
- Ada konstruktor pribadi tanpa parameter
- Variabel dalam tertutup variabel statis didefinisikan
- Dan ada properti statis terbuka Instance untuk berkomunikasi dengan dunia luar
Untuk memudahkan analisis contoh ini, kami menambahkan output konsol sederhana ke konstruktor kelas.
Di luar kelas (tanpa menggunakan trik seperti refleksi) kita hanya dapat menggunakan properti Instance statis publik, yang dapat kita sebut seperti ini:
class Program { public static void Main() { var logger = MyLogger.Instance; } }
Apa yang akan dihasilkan oleh program ini?Kita semua tahu bahwa konstruktor statis dipanggil sebelum mengakses setiap anggota kelas (dengan pengecualian konstanta). Dalam hal ini, diluncurkan hanya sekali dalam domain aplikasi.
Dalam kasus kami, kami beralih ke anggota kelas - properti Instance, yang seharusnya menyebabkan konstruktor statis untuk memulai pertama, dan kemudian konstruktor dari instance kelas akan dipanggil. Yaitu program akan menampilkan:
Pembuat Logger Statis
Pembuat Logger Instans
Namun, setelah memulai program, kami masuk ke konsol:
Pembuat Logger Instans
Pembuat Logger Statis
Bagaimana bisa begitu? Instance constructor berfungsi sebelum konstruktor statis?!?
Jawab: Ya!
Dan inilah alasannya.
Standar C # ECMA-334 menyatakan berikut ini untuk kelas statis:
17.4.5.1: โJika konstruktor statis (ยง17.11) ada di kelas, eksekusi inisialisasi bidang statis terjadi segera sebelum mengeksekusi konstruktor statis itu.
...
17.11: ... Jika sebuah kelas berisi bidang statis dengan inisialisasi, inisialisasi tersebut dieksekusi dalam urutan teks segera sebelum mengeksekusi konstruktor statis(Yang dalam terjemahan gratis berarti: jika ada konstruktor statis di kelas, maka inisialisasi bidang statis dimulai segera SEBELUM konstruktor statis dimulai.
...
Jika kelas berisi bidang statis dengan inisialisasi, maka inisialisasi tersebut diluncurkan dalam urutan dalam teks program SEBELUM konstruktor statis dijalankan.)
Dalam kasus kami, bidang statis innerInstance dideklarasikan bersama dengan initializer, yang merupakan konstruktor dari instance kelas. Menurut standar ECMA, penginisialisasi harus dipanggil SEBELUM memanggil konstruktor statis. Apa yang terjadi dalam program kami: konstruktor instance, menjadi penginisialisasi bidang statis, disebut SEBELUM konstruktor statis. Setuju, tidak terduga.
Perhatikan bahwa ini hanya berlaku untuk inisialisasi bidang statis. Secara umum, konstruktor statis disebut SEBELUM memanggil konstruktor dari instance kelas.
Seperti, misalnya, di sini:
class MyLogger { static MyLogger() { Console.WriteLine("Static Logger Constructor"); } public MyLogger() { Console.WriteLine("Instance Logger Constructor"); } } class Program { public static void Main() { var logger = new MyLogger(); } }
Dan program ini diharapkan untuk output ke konsol:
Pembuat Logger Statis
Pembuat Logger Instans

Contoh 3
Pemrogram sering harus menulis fungsi tambahan (utilitas, pembantu, dll.) Untuk membuat hidup mereka lebih mudah. Biasanya, fungsi-fungsi tersebut cukup sederhana dan seringkali hanya mengambil beberapa baris kode. Tapi Anda bisa tersandung bahkan tiba-tiba.
Misalkan kita perlu mengimplementasikan fungsi yang memeriksa nomor untuk keanehan (mis. Bahwa angka tersebut tidak dapat dibagi 2 tanpa sisa).
Suatu implementasi mungkin terlihat seperti ini:
static bool isOddNumber(int i) { return (i % 2 == 1); }
Sekilas, semuanya baik-baik saja dan, misalnya, untuk angka 5.7 dan 11, kami diharapkan Benar.
Apa yang akan dikembalikan fungsi isOddNumber (-5)?-5 adalah angka ganjil, tetapi sebagai jawaban untuk fungsi kita, kita mendapatkan False!
Mari cari tahu apa alasannya.
Menurut
MSDN , berikut ini ditulis tentang sisa% operator divisi:
โUntuk operan integer, hasil dari% b adalah nilai yang dihasilkan oleh a - (a / b) * bโ
Dalam kasus kami, untuk a = -5, b = 2 kita dapatkan:
-5% 2 = (-5) - ((-5) / 2) * 2 = -5 + 4 = -1
Tapi -1 selalu tidak sama dengan 1, yang menjelaskan hasil kami False.
% Operator peka terhadap tanda operan. Karena itu, agar tidak menerima "kejutan" seperti itu, lebih baik membandingkan hasilnya dengan nol, yang tidak memiliki tanda:
static bool isOddNumber(int i) { return (i % 2 != 0); }
Atau dapatkan fungsi terpisah untuk memeriksa paritas dan mengimplementasikan logika melalui itu:
static bool isEvenNumber(int i) { return (i % 2 == 0); } static bool isOddNumber(int i) { return !isEvenNumber(i); }
Contoh 4
Setiap orang yang diprogram dalam C #, mungkin bertemu dengan LINQ, yang sangat nyaman untuk bekerja dengan koleksi, membuat kueri, memfilter, dan mengagregasi data ...
Kami tidak akan mencari di bawah tenda LINQ. Mungkin kita akan melakukannya lain kali.
Sementara itu, perhatikan contoh kecil:
int[] dataArray = new int[] { 0, 1, 2, 3, 4, 5 }; int summResult = 0; var selectedData = dataArray.Select( x => { summResult += x; return x; }); Console.WriteLine(summResult);
Apa yang akan dihasilkan kode ini?Kita mendapatkan di layar nilai dari summResult variabel, yang sama dengan nilai awal, yaitu 0.
Mengapa ini terjadi?
Dan karena definisi kueri LINQ dan peluncuran kueri ini adalah dua operasi yang dilakukan secara terpisah. Dengan demikian, definisi permintaan tidak berarti peluncuran / eksekusi.
Variabel summResult digunakan di dalam delegasi anonim dalam metode Select: elemen dari array dataArray diurutkan secara berurutan dan ditambahkan ke variabel sumResult.
Kita dapat mengasumsikan bahwa kode kita akan mencetak jumlah elemen dari array dataArray. Tapi LINQ tidak berfungsi seperti itu.
Pertimbangkan variabel Data yang dipilih. Kata kunci var adalah "gula sintaksis", yang dalam banyak kasus mengurangi ukuran kode program dan meningkatkan keterbacaannya. Dan tipe nyata dari variabel data yang dipilih mengimplementasikan antarmuka IEnumerable. Yaitu kode kami terlihat seperti ini:
IEnumerable<int> selectedData = dataArray.Select( x => { summResult += x; return x; });
Di sini kita mendefinisikan kueri (Query), tetapi kueri itu sendiri tidak dimulai. Dengan cara yang sama, Anda bisa bekerja dengan database dengan menentukan kueri SQL sebagai string, tetapi untuk mendapatkan hasilnya, lihat database dan jalankan kueri ini secara eksplisit.
Artinya, sejauh ini kami hanya menetapkan permintaan, tetapi belum meluncurkannya. Inilah sebabnya mengapa nilai variabel summResult tetap tidak berubah. Kueri dapat diluncurkan, misalnya, menggunakan metode ToArray, ToList atau ToDictionary:
int[] dataArray = new int[] { 0, 1, 2, 3, 4, 5 }; int summResult = 0;
Kode ini sudah akan menampilkan nilai dari variabel sumResult, sama dengan jumlah semua elemen dari array dataArray, sama dengan 15.
Kami menemukan jawabannya. Lalu apa yang akan ditampilkan program ini di layar?
int[] dataArray = new int[] { 0, 1, 2, 3, 4, 5 };
Variabel groupedData (baris 3) sebenarnya mengimplementasikan antarmuka IEnumerable dan pada dasarnya mendefinisikan permintaan ke sumber data dataArray. Ini berarti bahwa untuk delegasi anonim bekerja, yang mengubah nilai variabel sumResult, permintaan ini harus dijalankan secara eksplisit. Tetapi tidak ada peluncuran seperti itu di program kami. Oleh karena itu, nilai variabel sumResult akan diubah hanya di baris 2, dan kami tidak dapat mempertimbangkan hal-hal lain dalam perhitungan kami.
Maka mudah untuk menghitung nilai dari summResult variabel, yang masing-masing, 15 + 7, yaitu 22.
Contoh 5
Mari kita katakan segera - kita tidak mempertimbangkan contoh ini di kuliah kita di Akademi, tetapi kadang-kadang kita membahasnya saat rehat kopi bukan sebagai lelucon.
Terlepas dari kenyataan bahwa itu hampir tidak indikatif dari sudut pandang menentukan tingkat pengembang, kami bertemu contoh ini dalam beberapa tes yang berbeda. Mungkin digunakan untuk keserbagunaan, karena berfungsi sama di C dan C ++, serta di C # dan Java.
Jadi biarkan ada satu baris kode:
int i = (int)+(char)-(int)+(long)-1;
Apa yang akan menjadi nilai dari variabel i?Jawab: 1
Anda mungkin berpikir bahwa aritmatika numerik digunakan di sini untuk ukuran masing-masing jenis dalam byte, karena tanda-tanda โ+โ dan โ-โ agak tidak terduga ditemukan di sini untuk konversi jenis.
Dalam C #, tipe integer dikenal 4 byte panjang, 8 panjang, karakter 2.
Maka mudah untuk berpikir bahwa baris kode kita akan sama dengan ekspresi aritmatika berikut:
int i = (4)+(2)-(4)+(8)-1;
Namun, ini tidak benar. Dan untuk membingungkan dan mengarahkan dengan alasan yang salah, contohnya dapat diubah, misalnya, seperti ini:
int i = (int)+(char)-(int)+(long)-sizeof(int);
Tanda-tanda "+" dan "-" digunakan dalam contoh ini bukan sebagai operasi aritmatika biner, tetapi sebagai operator unary. Maka baris kode kami hanyalah urutan konversi jenis eksplisit yang dicampur dengan panggilan ke operasi yang tidak terarah, yang dapat ditulis sebagai berikut:
int i = (int)(

Tertarik dengan Belajar di Veeam Academy?
Sekarang ada satu set untuk intensif musim semi pada C # di St. Petersburg, dan kami mengundang semua orang untuk menjalani pengujian online di situs web
Veeam Academy.Kursus dimulai pada 18 Februari 2019, berlangsung hingga pertengahan Mei dan, seperti biasa, akan sepenuhnya gratis. Pendaftaran untuk siapa saja yang ingin menjalani ujian masuk sudah tersedia di situs web Academy:
academy.veeam.ru