Apa gotcha terburuk di C # atau .NET? [Tutup]

377

Saya baru-baru ini bekerja dengan DateTimeobjek, dan menulis sesuatu seperti ini:

DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?

Dokumentasi intellisense untuk AddDays()mengatakan itu menambahkan hari ke tanggal, yang tidak - itu benar-benar mengembalikan tanggal dengan hari ditambahkan ke dalamnya, jadi Anda harus menuliskannya seperti:

DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt; // tomorrow's date

Yang ini telah menggigit saya beberapa kali sebelumnya, jadi saya pikir akan berguna untuk membuat katalog C # gotchas terburuk.

MusiGenesis
sumber
157
return DateTime.Now.AddDays (1);
crashmstr
23
AFAIK, tipe nilai bawaan semuanya tidak dapat diubah, setidaknya dalam metode apa pun yang disertakan dengan tipe mengembalikan item baru daripada memodifikasi item yang ada. Paling tidak, saya tidak bisa memikirkan satu pun dari atas kepala saya yang tidak melakukan ini: semua bagus dan konsisten.
Joel Coehoorn
6
Jenis nilai yang dapat diubah: System.Collections.Generics.List.Enumerator :( (Dan ya, Anda dapat melihatnya berperilaku aneh jika Anda berusaha cukup keras.)
Jon Skeet
13
Intellisense memberi Anda semua info yang Anda butuhkan. Dikatakan itu mengembalikan objek DateTime. Jika itu hanya mengubah yang Anda lewati, itu akan menjadi metode batal.
John Kraft
20
Tidak harus: StringBuilder.Append (...) mengembalikan "ini" misalnya. Itu cukup umum di antarmuka yang lancar.
Jon Skeet

Jawaban:

304
private int myVar;
public int MyVar
{
    get { return MyVar; }
}

Blammo. Aplikasi Anda macet tanpa jejak tumpukan. Terjadi sepanjang waktu.

(Perhatikan modal MyVaralih-alih huruf kecil myVarpada pengambil).

Eric Z Beard
sumber
112
dan SANGAT tepat untuk situs ini :)
gbjbaanb
62
Saya memberi garis bawah pada anggota pribadi, banyak membantu!
chakrit
61
Saya menggunakan properti otomatis di mana saya bisa, berhenti banyak masalah seperti ini;)
TWith2Sugars
28
Ini adalah alasan besar untuk menggunakan prefiks untuk bidang pribadi Anda (ada orang lain, tapi ini adalah satu yang baik): _myVar, m_myVar
jrista
205
@jrista: O silakan NO ... tidak M_ ... aargh horor yang ...
fretje
254

Ketik.Tipe

Salah satu yang saya lihat menggigit banyak orang adalah Type.GetType(string). Mereka bertanya-tanya mengapa itu bekerja untuk tipe dalam perakitan mereka sendiri, dan beberapa jenis suka System.String, tetapi tidak System.Windows.Forms.Form. Jawabannya adalah bahwa ia hanya terlihat dalam perakitan saat ini dan di mscorlib.


Metode anonim

C # 2.0 memperkenalkan metode anonim, yang mengarah ke situasi buruk seperti ini:

using System;
using System.Threading;

class Test
{
    static void Main()
    {
        for (int i=0; i < 10; i++)
        {
            ThreadStart ts = delegate { Console.WriteLine(i); };
            new Thread(ts).Start();
        }
    }
}

Apa yang akan dicetak? Yah, itu sepenuhnya tergantung pada penjadwalan. Ini akan mencetak 10 angka, tetapi mungkin tidak akan mencetak 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 yang mungkin Anda harapkan. Masalahnya adalah bahwa itu adalah ivariabel yang telah ditangkap, bukan nilainya pada saat penciptaan delegasi. Ini dapat diselesaikan dengan mudah dengan variabel lokal ekstra dari cakupan yang tepat:

using System;
using System.Threading;

class Test
{
    static void Main()
    {
        for (int i=0; i < 10; i++)
        {
            int copy = i;
            ThreadStart ts = delegate { Console.WriteLine(copy); };
            new Thread(ts).Start();
        }
    }
}

Eksekusi blok iterator yang ditangguhkan

"Unit test orang miskin" ini tidak lulus - mengapa tidak?

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Test
{
    static IEnumerable<char> CapitalLetters(string input)
    {
        if (input == null)
        {
            throw new ArgumentNullException(input);
        }
        foreach (char c in input)
        {
            yield return char.ToUpper(c);
        }
    }

    static void Main()
    {
        // Test that null input is handled correctly
        try
        {
            CapitalLetters(null);
            Console.WriteLine("An exception should have been thrown!");
        }
        catch (ArgumentNullException)
        {
            // Expected
        }
    }
}

Jawabannya adalah bahwa kode dalam sumber CapitalLetterskode tidak dieksekusi sampai metode iterator MoveNext()pertama kali dipanggil.

Saya memiliki beberapa keanehan lain di halaman brainteasers saya .

Jon Skeet
sumber
25
Contoh iterator licik!
Jimmy
8
mengapa tidak membagi ini menjadi 3 jawaban sehingga kita dapat memilih masing-masing alih-alih semuanya?
chakrit
13
@chakrit: Kalau dipikir-pikir, itu mungkin ide yang bagus, tapi saya pikir sudah terlambat sekarang. Itu mungkin juga tampak seperti saya hanya mencoba untuk mendapatkan lebih banyak perwakilan ...
Jon Skeet
19
Sebenarnya Type.GetType berfungsi jika Anda memberikan AssemblyQualifiedName. Type.GetType ("System.ServiceModel.EndpointNotFoundException, System.ServiceModel, Versi = 3.0.0.0, Budaya = netral, PublicKeyToken = b77a5c561934e089");
chilltemp
2
@kentaromiura: Resolusi kelebihan dimulai pada jenis yang paling diturunkan dan bekerja di pohon - tetapi hanya melihat metode yang awalnya dinyatakan dalam jenis yang dilihatnya. Foo (int) menimpa metode dasar, jadi tidak dipertimbangkan. Foo (objek) berlaku, jadi resolusi kelebihan berhenti di sana. Aneh, saya tahu.
Jon Skeet
194

Melempar kembali pengecualian

Gotcha yang mendapat banyak pengembang baru, adalah semantik pengecualian lemparan ulang.

Banyak waktu saya melihat kode seperti berikut ini

catch(Exception e) 
{
   // Do stuff 
   throw e; 
}

Masalahnya adalah itu menghapus jejak stack dan membuat masalah diagnosis jauh lebih sulit, karena Anda tidak dapat melacak di mana pengecualian berasal.

Kode yang benar adalah pernyataan melempar tanpa argumen:

catch(Exception)
{
    throw;
}

Atau bungkus pengecualian di yang lain, dan gunakan pengecualian dalam untuk mendapatkan jejak tumpukan asli:

catch(Exception e) 
{
   // Do stuff 
   throw new MySpecialException(e); 
}
Sam Saffron
sumber
Sangat beruntung, saya diajari tentang hal ini di minggu pertama saya oleh seseorang dan menemukannya dalam kode pengembang yang lebih senior. Is: catch () {throw; } Sama seperti potongan kode kedua? catch (Exception e) {throw; } hanya itu tidak membuat objek Pengecualian dan mengisinya?
StuperUser
Selain kesalahan menggunakan throw ex (atau throw e) bukan hanya throw, saya harus bertanya-tanya kasus apa yang ada saat itu layak untuk menangkap pengecualian hanya untuk melemparkannya lagi.
Ryan Lundy
13
@ Kirralessa: ada banyak kasus: misalnya, jika Anda ingin mengembalikan transaksi, sebelum penelepon mendapatkan pengecualian. Anda kembalikan dan kemudian rethrow.
R. Martinho Fernandes
7
Saya melihat ini sepanjang waktu di mana orang menangkap dan menggabungkan kembali pengecualian hanya karena mereka diajari bahwa mereka harus menangkap semua pengecualian, tidak menyadari bahwa itu akan ditangkap lebih jauh di tumpukan panggilan. Itu membuatku gila.
James Westgate
5
@ Kirralessa kasus terbesar adalah ketika Anda harus melakukan logging. Catat kesalahan dalam tangkapan, dan rethrow ..
nawfal
194

Jendela Jaga Heisenberg

Ini dapat menggigit Anda dengan buruk jika Anda melakukan hal-hal berdasarkan permintaan, seperti ini:

private MyClass _myObj;
public MyClass MyObj {
  get {
    if (_myObj == null)
      _myObj = CreateMyObj(); // some other code to create my object
    return _myObj;
  }
}

Sekarang katakanlah Anda memiliki beberapa kode di tempat lain menggunakan ini:

// blah
// blah
MyObj.DoStuff(); // Line 3
// blah

Sekarang Anda ingin men-debug CreateMyObj()metode Anda . Jadi Anda meletakkan breakpoint pada Jalur 3 di atas, dengan maksud untuk masuk ke dalam kode. Hanya untuk ukuran yang baik, Anda juga meletakkan breakpoint pada baris di atas yang mengatakan _myObj = CreateMyObj();, dan bahkan breakpoint di dalamnya CreateMyObj().

Kode tersebut mencapai breakpoint Anda di Jalur 3. Anda masuk ke dalam kode. Anda berharap memasukkan kode kondisional, karena _myObjjelas nol, kan? Uh ... jadi ... mengapa itu melewati kondisi dan langsung return _myObj? ?! Anda mengarahkan mouse ke _myObj ... dan memang, itu memiliki nilai! Bagaimana itu bisa terjadi?!

Jawabannya adalah bahwa IDE Anda menyebabkannya mendapatkan nilai, karena Anda memiliki jendela "jam tangan" terbuka - terutama jendela jam tangan "Autos", yang menampilkan nilai-nilai semua variabel / properti yang relevan dengan jalur eksekusi saat ini atau sebelumnya. Ketika Anda mencapai breakpoint Anda di Jalur 3, jendela arloji memutuskan bahwa Anda akan tertarik untuk mengetahui nilai MyObj- jadi di balik layar, mengabaikan salah satu breakpoints Anda , ia pergi dan menghitung nilai MyObjuntuk Anda - termasuk panggilan untuk CreateMyObj()itu menetapkan nilai _myObj!

Karena itulah saya menyebutnya Heisenberg Watch Window - Anda tidak dapat mengamati nilainya tanpa mempengaruhinya ... :)

KENA KAU!


Sunting - Saya merasa komentar ChristianHayter layak dimasukkan dalam jawaban utama, karena sepertinya solusi yang efektif untuk masalah ini. Jadi, kapan pun Anda memiliki properti bermalas-malasan ...

Hiasi properti Anda dengan [DebuggerBrowsable (DebuggerBrowsableState.Never)] atau [DebuggerDisplay ("<dimuat sesuai permintaan>")]. - Christian Hayter

Shaul Behr
sumber
10
menemukan brilian! Anda bukan seorang programmer, Anda seorang debugger nyata.
ini. __curious_geek
26
Saya sudah mengalami ini bahkan melayang di atas variabel, bukan hanya jendela arloji.
Richard Morgan
31
Hiasi properti Anda dengan [DebuggerBrowsable(DebuggerBrowsableState.Never)]atau [DebuggerDisplay("<loaded on demand>")].
Christian Hayter
4
Jika Anda mengembangkan kelas kerangka kerja dan ingin fungsi jendela tontonan tanpa mengubah perilaku runtime dari properti yang dibangun dengan malas, Anda bisa menggunakan proxy jenis debugger untuk mengembalikan nilai jika sudah dibangun, dan pesan bahwa properti belum telah dibangun jika itu yang terjadi. The Lazy<T>kelas (khususnya untuk yang Valueproperti) adalah salah satu contoh di mana ini digunakan.
Sam Harwell
4
Saya ingat seseorang yang (karena alasan tertentu saya tidak dapat mengerti) mengubah nilai objek dalam kelebihan ToString. Setiap kali dia mendekatinya, tooltip memberinya nilai yang berbeda - dia tidak bisa mengetahuinya ...
JNF
144

Inilah waktu lain yang membuat saya:

static void PrintHowLong(DateTime a, DateTime b)
{
    TimeSpan span = a - b;
    Console.WriteLine(span.Seconds);        // WRONG!
    Console.WriteLine(span.TotalSeconds);   // RIGHT!
}

TimeSpan.Seconds adalah bagian detik dari rentang waktu (2 menit dan 0 detik memiliki nilai detik 0).

TimeSpan.TotalSeconds adalah seluruh rentang waktu yang diukur dalam detik (2 menit memiliki nilai total detik 120).

Jon B
sumber
1
Ya, yang itu juga punya saya. Saya pikir itu harus TimeSpan.SecondsPart atau sesuatu untuk membuatnya lebih jelas apa yang diwakilinya.
Dan Diplo
3
Pada ulang membaca ini, saya harus heran mengapa TimeSpanbahkan memiliki sebuah Secondsproperti sama sekali. Ngomong-ngomong, siapakah yang memberi keledai tikus berapa porsi jangka waktu kedua? Ini nilai arbitrer, tergantung pada unit; Saya tidak bisa membayangkan penggunaan praktis untuk itu.
MusiGenesis
2
Masuk akal bagi saya bahwa TimeSpan.TotalSeconds akan mengembalikan ... jumlah total detik dalam rentang waktu.
Ed S.
16
@MusiGenesis properti ini berguna. Bagaimana jika saya ingin menampilkan rentang waktu pecah berkeping-keping? Misalnya, Timespan Anda mewakili durasi '3 jam 15 menit 10 detik'. Bagaimana Anda dapat mengakses informasi ini tanpa properti Detik, Jam, Menit?
SolutionYogi
1
Dalam API serupa, saya telah menggunakan SecondsPartdan SecondsTotaluntuk membedakan keduanya.
BlueRaja - Danny Pflughoeft
80

Memori bocor karena Anda tidak membatalkan acara.

Ini bahkan menangkap beberapa pengembang senior yang saya tahu.

Bayangkan formulir WPF dengan banyak hal di dalamnya, dan di suatu tempat di sana Anda berlangganan sebuah acara. Jika Anda tidak berhenti berlangganan maka seluruh formulir disimpan dalam memori setelah ditutup dan tidak direferensikan.

Saya percaya masalah yang saya lihat adalah membuat DispatchTimer dalam bentuk WPF dan berlangganan acara Tick, jika Anda tidak melakukan - = pada penghitung waktu, memori Anda bocor!

Dalam contoh ini, kode teardown Anda seharusnya

timer.Tick -= TimerTickEventHandler;

Yang ini sangat rumit karena Anda membuat instance DispatchTimer di dalam formulir WPF, jadi Anda akan berpikir bahwa itu akan menjadi referensi internal yang ditangani oleh proses Pengumpulan Sampah ... sayangnya DispatchTimer menggunakan daftar langganan dan layanan internal statis. meminta pada utas UI, sehingga referensi 'dimiliki' oleh kelas statis.

Timothy Walters
sumber
1
Caranya adalah dengan selalu melepaskan semua langganan acara yang Anda buat. Jika Anda mulai mengandalkan Formulir yang melakukannya untuk Anda, Anda dapat yakin bahwa Anda akan membiasakan diri dan suatu hari akan lupa untuk merilis sebuah acara di suatu tempat di mana itu perlu dilakukan.
Jason Williams
3
Ada saran MS-connect untuk acara referensi yang lemah di sini yang akan menyelesaikan masalah ini, meskipun menurut saya kita harus sepenuhnya mengganti model acara yang sangat buruk dengan yang ditambah dengan lemah, seperti yang digunakan oleh CAB.
BlueRaja - Danny Pflughoeft
+1 dari saya, terima kasih! Yah, tidak, terima kasih atas pekerjaan ulasan kode yang harus saya lakukan!
Bob Denny
@ BlueRaja-DannyPflughoeft Dengan acara yang lemah Anda memiliki gotcha lain - Anda tidak dapat berlangganan lambdas. Anda tidak dapat menulistimer.Tick += (s, e,) => { Console.WriteLine(s); }
Ark-kun
@ Ark-kun ya lambdas membuatnya lebih sulit, Anda harus menyimpan lambda Anda ke variabel dan menggunakannya dalam kode teardown Anda. Agak menghancurkan kesederhanaan menulis lambdas bukan?
Timothy Walters
63

Mungkin bukan benar-benar gotcha karena perilaku ini ditulis dengan jelas dalam MSDN, tetapi telah mematahkan leher saya sekali karena saya merasa agak kontra-intuitif:

Image image = System.Drawing.Image.FromFile("nice.pic");

Orang ini meninggalkan "nice.pic"file terkunci sampai gambar dibuang. Pada saat saya menghadapinya, saya pikir akan lebih baik memuat ikon dengan cepat dan tidak menyadari (pada awalnya) bahwa saya berakhir dengan lusinan file yang terbuka dan terkunci! Gambar melacak dari mana file itu dimuat dari ...

Bagaimana cara mengatasinya? Saya pikir satu kapal akan melakukan pekerjaan itu. Saya mengharapkan parameter tambahan untuk FromFile(), tetapi tidak memilikinya, jadi saya menulis ini ...

using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read))
{
    image = System.Drawing.Image.FromStream(fs);
}
jdehaan
sumber
10
Saya setuju bahwa perilaku ini tidak masuk akal. Saya tidak dapat menemukan penjelasan apa pun selain "perilaku ini sesuai desain".
MusiGenesis
1
Oh dan yang hebat tentang penyelesaian masalah ini adalah jika Anda mencoba menelepon Image.ToStream (saya lupa nama persisnya) nanti tidak akan berfungsi.
Joshua
55
perlu memeriksa beberapa kode. Brb.
Esben Skov Pedersen
7
@EsbenSkovPedersen Komentar yang sederhana namun lucu & kering. Membuatku bahagia.
Inisheer
51

Jika Anda menghitung ASP.NET, saya akan mengatakan siklus hidup bentuk web adalah gotcha yang cukup besar bagi saya. Saya telah menghabiskan banyak waktu men-debug kode webform yang ditulis dengan buruk, hanya karena banyak pengembang tidak benar-benar mengerti kapan harus menggunakan event handler mana (termasuk saya, sayangnya).

Erik van Brakel
sumber
26
Itu sebabnya saya pindah ke MVC ... sakit kepala negara ...
chakrit
29
Ada seluruh pertanyaan lain yang ditujukan khusus untuk ASP.NET gotchas (memang seharusnya begitu). Konsep dasar ASP.NET (membuat aplikasi web tampak seperti aplikasi windows untuk pengembang) begitu salah arah sehingga saya tidak yakin itu bahkan dianggap sebagai "gotcha".
MusiGenesis
1
MusiGenesis Saya berharap saya dapat memilih komentar Anda seratus kali.
csauve
3
@MusiGenesis Tampaknya salah arah sekarang, tetapi pada saat itu, orang menginginkan aplikasi web mereka (aplikasi menjadi kata kunci - ASP.NET WebForms tidak benar-benar dirancang untuk meng-host blog) untuk berperilaku sama dengan aplikasi windows mereka. Ini hanya berubah relatif baru-baru ini dan banyak orang masih "tidak cukup di sana". Seluruh masalah adalah bahwa abstraksi itu terlalu bocor - web tidak berperilaku seperti aplikasi desktop sehingga menyebabkan kebingungan di hampir semua orang.
Luaan
1
Ironisnya, hal pertama yang saya lihat tentang ASP.NET adalah video dari Microsoft yang menunjukkan betapa mudahnya Anda bisa membuat situs blog menggunakan ASP.NET!
MusiGenesis
51

kelebihan muatan == operator dan kontainer yang tidak diketik (daftar array, dataset, dll.):

string my = "my ";
Debug.Assert(my+"string" == "my string"); //true

var a = new ArrayList();
a.Add(my+"string");
a.Add("my string");

// uses ==(object) instead of ==(string)
Debug.Assert(a[1] == "my string"); // true, due to interning magic
Debug.Assert(a[0] == "my string"); // false

Solusi?

  • selalu gunakan string.Equals(a, b)ketika Anda membandingkan tipe string

  • menggunakan obat generik ingin List<string>memastikan bahwa kedua operan adalah string.

Jimmy
sumber
6
Anda memiliki ruang ekstra di sana yang membuat semuanya salah - tetapi jika Anda mengeluarkan spasi, baris terakhir masih akan benar karena "string" + "saya" masih konstan.
Jon Skeet
1
ack! Anda benar :) ok, saya edit sedikit.
Jimmy
peringatan dihasilkan pada penggunaan tersebut.
chakrit
11
Ya, salah satu kelemahan terbesar dengan bahasa C # adalah operator == di Object kelas. Mereka seharusnya memaksa kami untuk menggunakan ReferenceEquals.
erikkallen
2
Untungnya, sejak 2.0 kami memiliki obat generik. Ada sedikit yang perlu dikhawatirkan jika Anda menggunakan List <string> pada contoh di atas, bukan ArrayList. Ditambah lagi, kita sudah memperoleh kinerja darinya, yay! Saya selalu membongkar referensi lama ke ArrayLists dalam kode lawas kami.
JoelC
48
[Serializable]
class Hello
{
    readonly object accountsLock = new object();
}

//Do stuff to deserialize Hello with BinaryFormatter
//and now... accountsLock == null ;)

Moral dari cerita: Inisialisasi lapangan tidak berjalan ketika deserialisasi objek

Nicolas Dorier
sumber
8
Ya, saya benci serialisasi .NET karena tidak menjalankan konstruktor default. Saya berharap itu tidak mungkin untuk membangun objek tanpa memanggil konstruktor, tetapi sayangnya tidak.
Roman Starkov
45

DateTime.ToString ("hh / MM / tttt") ; Ini sebenarnya tidak akan selalu memberi Anda hh / MM / tttt tetapi sebaliknya akan mempertimbangkan pengaturan regional dan mengganti pemisah tanggal Anda tergantung di mana Anda berada. Jadi Anda mungkin mendapatkan dd-MM-yyyy atau sesuatu yang serupa.

Cara yang tepat untuk melakukan ini adalah dengan menggunakan DateTime.ToString ("dd '/' MM '/' yyyy");


DateTime.ToString ("r") seharusnya dikonversi ke RFC1123, yang menggunakan GMT. GMT berada dalam sepersekian detik dari UTC, namun specifier format "r" tidak dikonversi ke UTC , bahkan jika DateTime yang dimaksud ditentukan sebagai Lokal.

Ini menghasilkan gotcha berikut (bervariasi tergantung pada seberapa jauh waktu lokal Anda dari UTC):

DateTime.Parse("Tue, 06 Sep 2011 16:35:12 GMT").ToString("r")
>              "Tue, 06 Sep 2011 17:35:12 GMT"

Aduh!

romkyns
sumber
19
Berubah mm ke MM - mm adalah menit, dan MM adalah bulan. Gotcha lain, saya kira ...
Kobi
1
Saya bisa melihat bagaimana ini akan menjadi gotcha jika Anda tidak mengetahuinya (saya tidak) ... tapi saya mencoba mencari tahu kapan Anda ingin perilaku di mana Anda secara khusus mencoba mencetak tanggal yang tidak cocok dengan apa pengaturan regional Anda.
Beska
6
@Beska: Karena Anda menulis ke file, itu harus dalam format tertentu, dengan format tanggal yang ditentukan.
GvS
11
Saya berpendapat bahwa default yang dilokalkan lebih buruk daripada sebaliknya. Setidaknya pengembang mengabaikan pelokalan sepenuhnya kode bekerja pada mesin dilokalkan berbeda. Dengan cara ini, kodenya mungkin tidak berfungsi.
Joshua
32
Sebenarnya saya percaya cara yang tepat untuk melakukan ini adalahDateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
BlueRaja - Danny Pflughoeft
44

Saya melihat ini diposting hari yang lain, dan saya pikir itu cukup jelas, dan menyakitkan bagi mereka yang tidak tahu

int x = 0;
x = x++;
return x;

Karena itu akan mengembalikan 0 dan bukan 1 seperti yang diharapkan kebanyakan orang

Penjual Mitchel
sumber
37
Saya harap itu tidak benar-benar menggigit orang - saya sangat berharap mereka tidak akan menulisnya! (Tentu saja ini menarik.)
Jon Skeet
12
Saya tidak berpikir ini sangat tidak jelas ...
Chris Marasti-Georg
10
Setidaknya, dalam C #, hasilnya didefinisikan, jika tidak terduga. Dalam C ++, bisa 0 atau 1, atau hasil lainnya termasuk penghentian program!
James Curran
7
Ini bukan gotcha; x = x ++ -> x = x, lalu selisih x .... x = ++ x -> selisih x lalu x = x
Kevin
28
@ Kevin: Saya pikir itu tidak sesederhana itu. Jika x = x ++ sama dengan x = x diikuti oleh x ++, maka hasilnya akan menjadi x = 1. Sebaliknya, saya pikir apa yang terjadi adalah pertama ekspresi di sebelah kanan tanda sama dengan dievaluasi (memberi 0), kemudian x adalah bertambah (memberi x = 1), dan akhirnya tugas dilakukan (memberi x = 0 sekali lagi).
Tim Goodman
39

Aku agak terlambat ke pesta ini, tapi aku punya dua gotcha yang baru-baru ini menggigitku:

Resolusi DateTime

Properti Ticks mengukur waktu dalam 10-juta detik (100 nanodetik blok), namun resolusinya bukan 100 nanodetik, ini sekitar 15 ms.

Kode ini:

long now = DateTime.Now.Ticks;
for (int i = 0; i < 10; i++)
{
    System.Threading.Thread.Sleep(1);
    Console.WriteLine(DateTime.Now.Ticks - now);
}

akan memberi Anda output (misalnya):

0
0
0
0
0
0
0
156254
156254
156254

Demikian pula, jika Anda melihat DateTime.Now.Millisecond, Anda akan mendapatkan nilai dalam potongan bulat 15,625ms: 15, 31, 46, dll.

Perilaku khusus ini bervariasi dari satu sistem ke sistem lainnya , tetapi ada gotcha terkait resolusi lainnya di API tanggal / waktu ini.


Jalan. Menggabungkan

Cara yang bagus untuk menggabungkan jalur file, tetapi tidak selalu berperilaku seperti yang Anda harapkan.

Jika parameter kedua dimulai dengan \karakter, itu tidak akan memberi Anda path lengkap:

Kode ini:

string prefix1 = "C:\\MyFolder\\MySubFolder";
string prefix2 = "C:\\MyFolder\\MySubFolder\\";
string suffix1 = "log\\";
string suffix2 = "\\log\\";

Console.WriteLine(Path.Combine(prefix1, suffix1));
Console.WriteLine(Path.Combine(prefix1, suffix2));
Console.WriteLine(Path.Combine(prefix2, suffix1));
Console.WriteLine(Path.Combine(prefix2, suffix2));

Memberi Anda hasil ini:

C:\MyFolder\MySubFolder\log\
\log\
C:\MyFolder\MySubFolder\log\
\log\
Damovisa
sumber
17
Kuantisasi waktu dalam interval ~ 15 ms bukan karena kurangnya keakuratan dalam mekanisme waktu yang mendasarinya (saya lalai menguraikan ini sebelumnya). Itu karena aplikasi Anda berjalan di dalam OS multi-tasking. Windows memeriksa dengan aplikasi Anda setiap 15ms atau lebih, dan selama irisan kecil itu terjadi, aplikasi Anda memproses semua pesan yang antri sejak irisan terakhir Anda. Semua panggilan Anda dalam irisan itu mengembalikan waktu yang sama persis karena semuanya dibuat secara efektif pada waktu yang sama persis.
MusiGenesis
2
@MusiGenesis: Saya tahu (sekarang) bagaimana cara kerjanya, tetapi tampaknya menyesatkan saya untuk memiliki ukuran yang tepat yang tidak terlalu tepat. Ini seperti mengatakan bahwa saya tahu tinggi saya dalam nanometer ketika sebenarnya saya hanya membulatkannya ke sepuluh juta terdekat.
Damovisa
7
DateTime cukup mampu menyimpan hingga satu centang; itu DateTime. Sekarang tidak menggunakan keakuratan itu.
Ruben
16
Ekstra '\' adalah gotcha bagi banyak orang unix / mac / linux. Di Windows, jika ada '' yang memimpin, itu berarti bahwa kami ingin pergi root drive (yaitu C :) coba dalam CDperintah untuk melihat apa yang saya maksud .... 1) Goto C:\Windows\System322) Ketik CD \Users3) Woah! Sekarang Anda berada di C:\Users... GOT IT? ... Path.Combine (@ "C: \ Windows \ System32", @ "\ Users") harus mengembalikan \Usersyang artinya tepat[current_drive_here]:\Users
chakrit
8
Bahkan tanpa 'tidur' ini melakukan cara yang sama. Ini tidak ada hubungannya dengan aplikasi yang dijadwalkan setiap 15 ms. Fungsi asli yang disebut oleh DateTime.UtcNow, GetSystemTimeAsFileTime, tampaknya memiliki resolusi yang buruk.
Jimbo
38

Ketika Anda memulai proses (menggunakan System.Diagnostics) yang menulis ke konsol, tetapi Anda tidak pernah membaca aliran Console.Out, setelah sejumlah output tertentu aplikasi Anda tampaknya akan menggantung.

pengguna25306
sumber
3
Hal yang sama masih dapat terjadi ketika Anda mengarahkan ulang stdout dan stderr dan menggunakan dua panggilan ReadToEnd secara berurutan. Untuk penanganan stdout dan stderr yang aman, Anda harus membuat utas baca untuk masing-masing stdout.
Sebastiaan M
34

Tidak ada pintasan operator di Linq-To-Sql

Lihat di sini .

Singkatnya, di dalam klausa kondisional dari kueri Linq-To-Sql, Anda tidak dapat menggunakan pintasan bersyarat seperti ||dan &&untuk menghindari pengecualian referensi nol; Linq-To-Sql mengevaluasi kedua sisi operator OR atau AND bahkan jika kondisi pertama meniadakan kebutuhan untuk mengevaluasi kondisi kedua!

Shaul Behr
sumber
8
TIL. BRB, mengoptimalkan kembali beberapa ratus pertanyaan LINQ ...
tsilb
30

Menggunakan parameter default dengan metode virtual

abstract class Base
{
    public virtual void foo(string s = "base") { Console.WriteLine("base " + s); }
}

class Derived : Base
{
    public override void foo(string s = "derived") { Console.WriteLine("derived " + s); }
}

...

Base b = new Derived();
b.foo();

Output:
basis yang diturunkan

BlueRaja - Danny Pflughoeft
sumber
10
Aneh, saya pikir ini sangat jelas. Jika tipe yang dideklarasikan adalah Base, dari mana kompiler harus mendapatkan nilai default dari jika tidak Base? Saya akan berpikir sedikit lebih mengerti bahwa nilai default dapat berbeda jika tipe yang dideklarasikan adalah tipe turunan , meskipun metode yang disebut (statis) adalah metode dasar.
Timwi
1
mengapa satu implementasi metode mendapatkan nilai default dari implementasi lain?
staafl
1
@staafl Argumen default diselesaikan pada waktu kompilasi, bukan runtime.
fredoverflow
1
Saya akan mengatakan bahwa gotcha ini adalah parameter default pada umumnya - orang sering tidak menyadari bahwa mereka diselesaikan pada waktu kompilasi, bukan pada saat dijalankan.
Luaan
4
@ FredOverflow, pertanyaan saya bersifat konseptual. Meskipun perilaku masuk akal melalui implementasi, itu tidak intuitif dan kemungkinan merupakan sumber kesalahan. IMHO, penyusun C # seharusnya tidak mengizinkan pengubahan nilai parameter default saat mengganti.
staafl
27

Nilai objek dalam koleksi yang bisa diubah

struct Point { ... }
List<Point> mypoints = ...;

mypoints[i].x = 10;

tidak berpengaruh.

mypoints[i]mengembalikan salinan Pointobjek nilai. C # dengan senang hati memungkinkan Anda memodifikasi bidang salinan. Diam-diam tidak melakukan apa-apa.


Pembaruan: Ini tampaknya diperbaiki di C # 3.0:

Cannot modify the return value of 'System.Collections.Generic.List<Foo>.this[int]' because it is not a variable
Bjarke Ebert
sumber
6
Saya dapat melihat mengapa hal itu membingungkan, mengingat memang memang bekerja dengan array (bertentangan dengan jawaban Anda), tetapi tidak dengan koleksi dinamis lainnya, seperti List <Point>.
Lasse V. Karlsen
2
Kamu benar. Terima kasih. Saya memperbaiki jawaban saya :). arr[i].attr=adalah sintaks khusus untuk array yang tidak dapat Anda kode dalam wadah pustaka; (. Mengapa (<ekspresi nilai>). attr = <expr> diizinkan sama sekali? Bisakah ini masuk akal?
Bjarke Ebert
1
@ Bjarke Ebert: Ada beberapa kasus di mana itu masuk akal, tetapi sayangnya tidak ada cara bagi kompiler untuk mengidentifikasi dan mengizinkannya. Skenario penggunaan sampel: Struct yang tidak berubah yang menyimpan referensi ke array dua dimensi persegi bersama dengan indikator "rotate / flip". Struct itu sendiri akan berubah, jadi menulis ke elemen instance read-only harus baik-baik saja, tetapi kompiler tidak akan tahu bahwa setter properti sebenarnya tidak akan menulis struct, dan dengan demikian tidak akan mengizinkannya .
supercat
25

Mungkin bukan yang terburuk, tetapi beberapa bagian kerangka kerja .net menggunakan derajat sementara yang lain menggunakan radian (dan dokumentasi yang muncul dengan Intellisense tidak pernah memberi tahu Anda, Anda harus mengunjungi MSDN untuk mengetahuinya)

Semua ini bisa dihindari dengan memiliki Anglekelas sebagai gantinya ...

BlueRaja - Danny Pflughoeft
sumber
Saya terkejut ini mendapat begitu banyak upvotes, mengingat gotcha saya yang lain jauh lebih buruk dari ini
BlueRaja - Danny Pflughoeft
22

Untuk programmer C / C ++, transisi ke C # adalah alami. Namun, gotcha terbesar yang saya temui (dan telah saya lihat dengan orang lain membuat transisi yang sama) tidak sepenuhnya memahami perbedaan antara kelas dan struct di C #.

Dalam C ++, kelas dan struct identik; mereka hanya berbeda dalam visibilitas default, di mana kelas default untuk visibilitas pribadi dan struct default untuk visibilitas publik. Dalam C ++, definisi kelas ini

    class A
    {
    public:
        int i;
    };

secara fungsional setara dengan definisi struct ini.

    struct A
    {
        int i;
    };

Di C #, bagaimanapun, kelas adalah tipe referensi sementara struct adalah tipe nilai. Hal ini membuat sebuah BESAR perbedaan dalam (1) memutuskan kapan untuk menggunakan satu atas yang lain, (2) menguji objek kesetaraan, (3) kinerja (misalnya, tinju / pembukaan kemasan), dll

Ada semua jenis informasi di web terkait dengan perbedaan antara keduanya (mis., Di sini ). Saya akan sangat mendorong siapa pun yang melakukan transisi ke C # untuk setidaknya memiliki pengetahuan tentang perbedaan dan implikasinya.

Matt Davis
sumber
13
Jadi, gotcha terburuk adalah orang tidak mau repot-repot belajar bahasa sebelum menggunakannya?
BlueRaja - Danny Pflughoeft
3
@ BlueRaja-DannyPflughoeft Lebih seperti gotcha klasik dari bahasa yang tampaknya serupa - mereka menggunakan kata kunci yang sangat mirip dan dalam banyak kasus sintaksis, tetapi bekerja dengan cara yang sangat berbeda.
Luaan
19

Pengumpulan dan Buang Sampah (). Meskipun Anda tidak perlu melakukan apa pun untuk membebaskan memori , Anda masih harus membebaskan sumber daya melalui Buang (). Ini adalah hal yang sangat mudah untuk dilupakan ketika Anda menggunakan WinForms, atau melacak objek dengan cara apa pun.

Jeff Kotula
sumber
2
Blok using () memecahkan masalah ini dengan rapi. Setiap kali Anda melihat panggilan untuk Buang, Anda dapat segera dan aman menolak untuk menggunakan menggunakan ().
Jeremy Frey
5
Saya pikir kekhawatirannya adalah menerapkan IDisposable dengan benar.
Mark Brackett
4
Di sisi lain, kebiasaan menggunakan () dapat menggigit Anda secara tidak terduga, seperti ketika bekerja dengan PInvoke. Anda tidak ingin membuang sesuatu yang masih dirujuk oleh API.
MusiGenesis
3
Menerapkan IDisposable dengan benar sangat sulit untuk dipahami dan bahkan saran terbaik yang saya temukan dalam hal ini (.NET Framework Guidelines) dapat membingungkan untuk diterapkan hingga Anda akhirnya "mengerti".
Quibblesome
1
Saran terbaik yang pernah saya temukan di IDisposable berasal dari Stephen Cleary, termasuk tiga aturan mudah dan artikel mendalam tentang IDisposable
Roman Starkov
19

Array implement IList

Tapi jangan menerapkannya. Saat Anda memanggil Tambah, ia memberi tahu Anda bahwa itu tidak berfungsi. Jadi mengapa sebuah kelas mengimplementasikan sebuah antarmuka ketika tidak dapat mendukungnya?

Kompilasi, tetapi tidak berfungsi:

IList<int> myList = new int[] { 1, 2, 4 };
myList.Add(5);

Kami memiliki masalah ini banyak, karena serializer (WCF) mengubah semua ILists menjadi array dan kami mendapatkan kesalahan runtime.

Stefan Steinegger
sumber
8
IMHO, masalahnya adalah Microsoft tidak memiliki antarmuka yang cukup untuk koleksi. IMHO, harus memiliki iEnumerable, iMultipassEnumerable (mendukung Reset, dan menjamin beberapa pass akan cocok), iLiveEnumerable (akan memiliki semantik yang ditentukan sebagian jika koleksi berubah selama enumerasi - perubahan mungkin atau mungkin tidak muncul dalam enumerasi, tetapi tidak boleh menyebabkan hasil atau pengecualian palsu), iReadIndexable, iReadWriteIndexable, dll. Karena antarmuka dapat "mewarisi" antarmuka lain, ini tidak akan menambah banyak pekerjaan tambahan, jika ada (itu akan menghemat notImplemented stubs).
supercat
@ Supercat, itu akan membingungkan bagi pemula dan coders lama. Saya pikir koleksi .NET dan antarmuka mereka sangat elegan. Tapi saya menghargai kerendahan hati Anda. ;)
Jordan
@Jordan: Sejak menulis di atas, saya telah memutuskan bahwa pendekatan yang lebih baik adalah memiliki keduanya IEnumerable<T>dan IEnumerator<T>mendukung Featuresproperti serta beberapa metode "opsional" yang kegunaannya akan ditentukan oleh apa yang dilaporkan "Fitur". Saya mendukung poin utama saya, yaitu bahwa ada beberapa kasus di mana kode yang menerima IEnumerable<T>akan membutuhkan janji yang lebih kuat daripada yang IEnumerable<T>disediakan. Memanggil ToListakan menghasilkan suatu IEnumerable<T>yang menjunjung tinggi janji-janji seperti itu, tetapi dalam banyak kasus akan menjadi tidak perlu mahal. Saya berpendapat bahwa harus ada ...
supercat
... sarana dengan mana kode yang menerima IEnumerable<T>dapat membuat salinan konten jika diperlukan tetapi dapat menahan diri dari melakukan hal yang tidak perlu.
supercat
Pilihan Anda sama sekali tidak dapat dibaca. Ketika saya melihat IList dalam kode saya tahu apa yang saya kerjakan daripada harus menyelidiki properti Fitur. Programmer suka melupakan bahwa fitur penting dari kode adalah dapat dibaca oleh orang bukan hanya komputer. Koleksi namespace .NET tidak ideal tetapi bagus, dan kadang-kadang menemukan solusi terbaik bukan masalah pas prinsip lebih ideal. Beberapa kode terburuk yang pernah saya kerjakan adalah kode yang mencoba menyesuaikan KERING idealnya. Saya menghapusnya dan menulis ulang. Itu hanya kode yang buruk. Saya tidak ingin menggunakan kerangka kerja Anda sama sekali.
Jordan
18

foreach loop lingkup variabel!

var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    l.Add(() => s);
}

foreach (var a in l)
    Console.WriteLine(a());

mencetak lima "amet", sedangkan contoh berikut berfungsi dengan baik

var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    var t = s;
    l.Add(() => t);
}

foreach (var a in l)
    Console.WriteLine(a());
Brian J Cardiff
sumber
11
Ini pada dasarnya setara dengan contoh Jon dengan metode anonim.
Mehrdad Afshari
3
Simpan bahwa itu bahkan lebih membingungkan dengan foreach di mana variabel "s" lebih mudah untuk dicampur dengan variabel scoped. Dengan for-loop umum, variabel indeks jelas adalah sama untuk setiap iterasi.
Mikko Rantanen
2
blogs.msdn.com/ericlippert/archive/2009/11/12/… dan ya, berharap variabel tersebut dicakup "dengan benar".
Roman Starkov
2
Ini diperbaiki di C # 5 .
Johnbot
Anda pada dasarnya hanya mencetak variabel yang sama berulang-ulang tanpa mengubahnya.
Jordan
18

MS SQL Server tidak dapat menangani tanggal sebelum 1753. Secara signifikan, itu tidak selaras dengan DateTime.MinDatekonstanta .NET , yaitu 1/1/1. Jadi, jika Anda mencoba menyelamatkan mindate, tanggal cacat (seperti baru-baru ini terjadi pada saya dalam impor data) atau hanya tanggal lahir William Sang Penakluk, Anda akan berada dalam masalah. Tidak ada solusi bawaan untuk ini; jika Anda perlu bekerja dengan tanggal sebelum 1753, Anda harus menulis solusi sendiri.

Shaul
sumber
17
Sejujurnya saya pikir MS SQL Server memiliki hak ini dan. Net salah. Jika Anda melakukan penelitian maka Anda tahu bahwa tanggal sebelum 1751 menjadi funky karena perubahan kalender, hari-hari benar-benar dilewati, dll. Sebagian besar RDBM memiliki beberapa titik cut off. Ini akan memberikan Anda titik awal: ancestry.com/learn/library/article.aspx?article=3358
Notme
11
Juga, tanggalnya adalah 1753 .. Yang merupakan pertama kalinya kami memiliki kalender berkelanjutan tanpa tanggal yang dilewati. SQL 2008 memperkenalkan tipe data Date dan datetime2 yang dapat menerima tanggal dari 1/1/01 hingga 12/31/9999. Namun, perbandingan tanggal menggunakan tipe-tipe itu harus dilihat dengan curiga jika Anda benar-benar membandingkan tanggal pra-1753.
NotMe
Oh, benar, 1753, dikoreksi, terima kasih.
Shaul Behr
Apakah masuk akal untuk melakukan perbandingan tanggal dengan tanggal seperti itu? Maksud saya, untuk History Channel ini masuk akal, tetapi saya tidak melihat diri saya ingin tahu hari yang tepat dalam seminggu Amerika ditemukan.
Camilo Martin
5
Melalui Wikipedia pada Hari Julian Anda dapat menemukan program dasar 13 baris CALJD.BAS diterbitkan pada tahun 1984 yang dapat melakukan perhitungan tanggal kembali ke sekitar 5000 SM, dengan mempertimbangkan hari kabisat dan hari-hari yang dilewati pada tahun 1753. Jadi saya tidak melihat mengapa "modern "Sistem seperti SQL2008 seharusnya lebih buruk. Anda mungkin tidak tertarik dengan representasi tanggal yang benar di abad ke-15, tetapi yang lain mungkin, dan perangkat lunak kami harus menangani ini tanpa bug. Masalah lainnya adalah detik kabisat. . .
Roland
18

The Gotcha Caching Gotcha Jahat

Lihat pertanyaan saya yang mengarah pada penemuan ini, dan blogger yang menemukan masalah.

Singkatnya, DataContext menyimpan cache dari semua objek Linq-to-Sql yang pernah Anda muat. Jika ada orang lain yang membuat perubahan pada catatan yang sebelumnya Anda muat, Anda tidak akan bisa mendapatkan data terbaru, bahkan jika Anda secara eksplisit memuat ulang catatan!

Ini karena properti yang dipanggil ObjectTrackingEnabledpada DataContext, yang secara default adalah benar. Jika Anda menyetel properti itu menjadi false, catatan akan dimuat lagi setiap kali ... TETAPI ... Anda tidak dapat mempertahankan perubahan apa pun pada catatan itu dengan SubmitChanges ().

KENA KAU!

Shaul Behr
sumber
Iv hanya menghabiskan satu setengah hari (dan banyak rambut!) Mengejar BUG ini ...
Coder Bedah
Ini disebut konflik konkurensi dan masih menjadi gotcha hari ini meskipun ada cara-cara tertentu di sekitar ini sekarang meskipun mereka cenderung agak berat tangan. DataContext adalah mimpi buruk. O_o
Jordan
17

Kontrak pada Stream. Baca adalah sesuatu yang saya lihat membuat banyak orang tersandung:

// Read 8 bytes and turn them into a ulong
byte[] data = new byte[8];
stream.Read(data, 0, 8); // <-- WRONG!
ulong data = BitConverter.ToUInt64(data);

Alasan ini salah adalah yang Stream.Readakan membaca paling banyak jumlah byte yang ditentukan, tetapi sepenuhnya bebas untuk membaca hanya 1 byte, bahkan jika 7 byte lainnya tersedia sebelum akhir aliran.

Itu tidak membantu bahwa penampilan ini begitu mirip dengan Stream.Writeyang ini dijamin telah menulis semua byte jika kembali tanpa terkecuali. Juga tidak membantu bahwa kode di atas berfungsi hampir setiap saat . Dan tentu saja itu tidak membantu bahwa tidak ada metode yang siap pakai dan nyaman untuk membaca dengan tepat N byte dengan benar.

Jadi, untuk menutup lubang, dan meningkatkan kesadaran akan hal ini, berikut adalah contoh cara yang benar untuk melakukan ini:

    /// <summary>
    /// Attempts to fill the buffer with the specified number of bytes from the
    /// stream. If there are fewer bytes left in the stream than requested then
    /// all available bytes will be read into the buffer.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="buffer">Buffer to write the bytes to.</param>
    /// <param name="offset">Offset at which to write the first byte read from
    ///                      the stream.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    /// <returns>Number of bytes read from the stream into buffer. This may be
    ///          less than requested, but only if the stream ended before the
    ///          required number of bytes were read.</returns>
    public static int FillBuffer(this Stream stream,
                                 byte[] buffer, int offset, int length)
    {
        int totalRead = 0;
        while (length > 0)
        {
            var read = stream.Read(buffer, offset, length);
            if (read == 0)
                return totalRead;
            offset += read;
            length -= read;
            totalRead += read;
        }
        return totalRead;
    }

    /// <summary>
    /// Attempts to read the specified number of bytes from the stream. If
    /// there are fewer bytes left before the end of the stream, a shorter
    /// (possibly empty) array is returned.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    public static byte[] Read(this Stream stream, int length)
    {
        byte[] buf = new byte[length];
        int read = stream.FillBuffer(buf, 0, length);
        if (read < length)
            Array.Resize(ref buf, read);
        return buf;
    }
Roman Starkov
sumber
1
Atau, dalam contoh eksplisit Anda: var r = new BinaryReader(stream); ulong data = r.ReadUInt64();. BinaryReader memiliki FillBuffermetode juga ...
jimbobmcgee
15

Acara

Saya tidak pernah mengerti mengapa acara adalah fitur bahasa. Mereka rumit untuk digunakan: Anda perlu memeriksa nol sebelum menelepon, Anda perlu membatalkan pendaftaran (sendiri), Anda tidak dapat menemukan siapa yang terdaftar (misalnya: apakah saya mendaftar?). Mengapa acara bukan hanya kelas di perpustakaan? Pada dasarnya spesialis List<delegate>?

Stefan Steinegger
sumber
1
Multithreading juga menyakitkan. Semua masalah ini kecuali hal-nol diperbaiki dalam CAB (yang fitur-fiturnya harus benar-benar hanya dibangun ke dalam bahasa) - peristiwa dinyatakan secara global, dan metode apa pun dapat menyatakan dirinya sebagai "pelanggan" dari peristiwa apa pun. Satu-satunya masalah saya dengan CAB adalah bahwa nama-nama acara global adalah string daripada enum (yang dapat diperbaiki oleh enum yang lebih cerdas, seperti Java, yang secara inheren berfungsi sebagai string!) . CAB sulit diatur, tetapi ada klon open-source sederhana yang tersedia di sini .
BlueRaja - Danny Pflughoeft
3
Saya tidak suka implementasi acara .net. Langganan acara harus ditangani dengan memanggil metode yang menambahkan langganan dan mengembalikan IDisposable yang, ketika Buang, akan menghapus langganan. Tidak perlu untuk konstruksi khusus yang menggabungkan metode "tambah" dan "hapus" yang semantiknya mungkin agak cerdik, terutama jika seseorang mencoba menambah dan kemudian menghapus delegasi multicast (mis. Tambah "B" diikuti oleh "AB", lalu hapus "B" (meninggalkan "BA") dan "AB" (masih meninggalkan "BA"). Ups
supercat
@supercat Bagaimana Anda menulis ulang button.Click += (s, e) => { Console.WriteLine(s); }?
Ark-kun
Jika saya harus dapat berhenti berlangganan secara terpisah dari acara lain, IEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);});dan berhenti berlangganan melalui clickSubscription.Dispose();. Jika objek saya akan menyimpan semua langganan sepanjang masa hidupnya, MySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);}));dan kemudian MySubscriptions.Dispose()untuk membunuh semua langganan.
supercat
@ Ark-kun: Harus menjaga benda-benda yang merangkum langganan luar mungkin tampak seperti gangguan, tetapi mengenai langganan sebagai entitas akan memungkinkan untuk menggabungkan mereka dengan jenis yang dapat memastikan mereka semua dibersihkan, sesuatu yang sebaliknya sangat sulit.
supercat
14

Hari ini saya memperbaiki bug yang berhasil dihindari untuk waktu yang lama. Bug ada di kelas umum yang digunakan dalam skenario multi-ulir dan bidang int statis digunakan untuk menyediakan sinkronisasi bebas kunci menggunakan Interlocked. Bug disebabkan karena setiap instantiation dari kelas generik untuk suatu tipe memiliki statiknya sendiri. Jadi setiap utas memiliki bidang statis sendiri dan tidak menggunakan kunci seperti yang dimaksudkan.

class SomeGeneric<T>
{
    public static int i = 0;
}

class Test
{
    public static void main(string[] args)
    {
        SomeGeneric<int>.i = 5;
        SomeGeneric<string>.i = 10;
        Console.WriteLine(SomeGeneric<int>.i);
        Console.WriteLine(SomeGeneric<string>.i);
        Console.WriteLine(SomeGeneric<int>.i);
    }
}

Ini mencetak 5 10 5

Pratik
sumber
5
Anda dapat memiliki kelas dasar non-generik, yang mendefinisikan statika, dan mewarisi generik darinya. Meskipun saya tidak pernah menyukai perilaku ini dalam C # - Saya masih ingat waktu debugging yang lama dari beberapa templat C ++ ... Eww! :)
Paulius
7
Aneh, saya pikir ini sudah jelas. Coba pikirkan apa yang harus dilakukan jika imemiliki tipe T.
Timwi
1
Parameter tipe adalah bagian dari Type. SomeGeneric<int>adalah Tipe berbeda dari SomeGeneric<string>; jadi tentu saja masing-masing memiliki sendiripublic static int i
radarbob
13

Enumerables dapat dievaluasi lebih dari satu kali

Ini akan menggigit Anda ketika Anda memiliki enumerable yang malas disebutkan dan Anda mengulanginya dua kali dan mendapatkan hasil yang berbeda. (atau Anda mendapatkan hasil yang sama tetapi dieksekusi dua kali tidak perlu)

Misalnya, saat menulis tes tertentu, saya memerlukan beberapa file temp untuk menguji logikanya:

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName());

foreach (var file in files)
    File.WriteAllText(file, "HELLO WORLD!");

/* ... many lines of codes later ... */

foreach (var file in files)
    File.Delete(file);

Bayangkan betapa terkejutnya saya ketika File.Delete(file)melempar FileNotFound!!

Apa yang terjadi di sini adalah bahwa filesenumerable mendapat iterasi dua kali (hasil dari iterasi pertama tidak diingat) dan pada setiap iterasi baru Anda akan menelepon ulang Path.GetTempFilename()sehingga Anda akan mendapatkan satu set nama file temp yang berbeda.

Solusinya adalah, tentu saja, ingin menghitung nilai dengan menggunakan ToArray()atau ToList():

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName())
    .ToArray();

Ini bahkan lebih menakutkan ketika Anda melakukan sesuatu yang multi-threaded, seperti:

foreach (var file in files)
    content = content + File.ReadAllText(file);

dan Anda tahu content.Lengthmasih 0 setelah semua menulis !! Anda kemudian mulai memeriksa dengan teliti bahwa Anda tidak memiliki kondisi balapan ketika .... setelah satu jam yang terbuang ... Anda tahu itu hanya hal kecil yang bisa Anda dapatkan.

chakrit
sumber
Ini dengan desain. Ini disebut eksekusi yang ditangguhkan. Antara lain, itu dimaksudkan untuk mensimulasikan konstruksi TSQL. Setiap kali Anda memilih dari tampilan sql Anda mendapatkan hasil yang berbeda. Ini juga memungkinkan rantai yang berguna untuk penyimpanan data jarak jauh, seperti SQL Server. Kalau tidak x.Select.Where.OrderBy akan mengirim 3 perintah terpisah ke database ...
as9876
@AYS, apakah Anda melewatkan kata "Gotcha" di judul pertanyaan?
chakrit
Saya pikir gotcha berarti pengawasan dari para desainer, bukan sesuatu yang disengaja.
as9876
Mungkin harus ada tipe lain untuk IEnumerables non-restartable. Suka, AutoBufferedEnumerable? Orang bisa menerapkannya dengan mudah. Gotcha ini tampaknya sebagian besar disebabkan oleh kurangnya pengetahuan programmer, saya tidak berpikir ada yang salah dengan perilaku saat ini.
Eldritch Conundrum
13

Baru saja menemukan yang aneh yang membuat saya terjebak dalam debug untuk sementara waktu:

Anda dapat menambah nol untuk int nullable tanpa membuang kutipan dan nilainya tetap nol.

int? i = null;
i++; // I would have expected an exception but runs fine and stays as null
DevDave
sumber
Itulah hasil dari bagaimana C # meningkatkan operasi untuk tipe yang dapat dibatalkan. Ini sedikit mirip dengan NaN yang menghabiskan semua yang Anda lemparkan ke sana.
IllidanS4 ingin Monica kembali
10
TextInfo textInfo = Thread.CurrentThread.CurrentCulture.TextInfo;

textInfo.ToTitleCase("hello world!"); //Returns "Hello World!"
textInfo.ToTitleCase("hElLo WoRld!"); //Returns "Hello World!"
textInfo.ToTitleCase("Hello World!"); //Returns "Hello World!"
textInfo.ToTitleCase("HELLO WORLD!"); //Returns "HELLO WORLD!"

Ya, perilaku ini didokumentasikan, tetapi itu tentu saja tidak membuatnya benar.

BlueRaja - Danny Pflughoeft
sumber
5
Saya tidak setuju - ketika sebuah kata dalam huruf kapital semua, itu dapat memiliki makna khusus bahwa Anda tidak ingin mengacaukan Judul Kasus, misalnya "presiden Amerika Serikat" -> "Presiden Amerika Serikat", bukan "Presiden Amerika Serikat". Amerika Serikat".
Shaul Behr
5
@ Samul: Dalam hal ini, mereka harus menentukan ini sebagai parameter untuk menghindari kebingungan, karena saya belum pernah bertemu orang yang mengharapkan perilaku ini sebelumnya - yang menjadikan ini sebagai gotcha !
BlueRaja - Danny Pflughoeft