Ich habe kürzlich mit einem DateTime
Objekt gearbeitet und so etwas geschrieben:
DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?
Die IntelliSense - Dokumentation AddDays()
sagt , dass es einen Tag zum Zeitpunkt hinzufügt, die es nicht - es tatsächlich gibt ein Datum mit einem Tag hinzugefügt, so dass Sie schreiben müssen , um es wie:
DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt; // tomorrow's date
Dieser hat mich schon einige Male gebissen, daher dachte ich, es wäre nützlich, die schlimmsten C # -Gotchas zu katalogisieren.
Antworten:
Blammo. Ihre App stürzt ohne Stack-Trace ab. Es passiert die ganze Zeit.
(Beachten Sie im Getter Großbuchstaben
MyVar
anstelle von KleinbuchstabenmyVar
.)quelle
Type.GetType
Der, den ich viele Leute beißen gesehen habe, ist
Type.GetType(string)
. Sie fragen sich, warum es für Typen in ihrer eigenen Baugruppe funktioniert, und einige Typen mögenSystem.String
, aber nichtSystem.Windows.Forms.Form
. Die Antwort ist, dass es nur in der aktuellen Assembly und in aussiehtmscorlib
.Anonyme Methoden
C # 2.0 führte anonyme Methoden ein, die zu bösen Situationen wie diesen führten:
Was wird das ausdrucken? Nun, es hängt ganz von der Planung ab. Es werden 10 Zahlen gedruckt, aber wahrscheinlich nicht 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, was Sie vielleicht erwarten. Das Problem ist, dass die
i
Variable erfasst wurde und nicht ihr Wert zum Zeitpunkt der Erstellung des Delegaten. Dies kann leicht mit einer zusätzlichen lokalen Variablen des richtigen Bereichs gelöst werden:Verzögerte Ausführung von Iteratorblöcken
Dieser "Unit Test für arme Männer" besteht nicht - warum nicht?
Die Antwort ist, dass der Code in der Quelle des
CapitalLetters
Codes erst ausgeführt wird, wenn dieMoveNext()
Methode des Iterators zum ersten Mal aufgerufen wird.Ich habe einige andere Kuriositäten auf meiner Brainteaser-Seite .
quelle
Ausnahmen erneut auslösen
Ein Gotcha, das viele neue Entwickler bekommt, ist die Re-Throw-Ausnahmesemantik.
Viel Zeit sehe ich Code wie den folgenden
Das Problem besteht darin, dass der Stack-Trace gelöscht wird und die Diagnose von Problemen erheblich erschwert wird, da Sie nicht nachverfolgen können, woher die Ausnahme stammt.
Der richtige Code ist entweder die throw-Anweisung ohne Argumente:
Oder wickeln Sie die Ausnahme in eine andere ein und verwenden Sie die innere Ausnahme, um die ursprüngliche Stapelverfolgung abzurufen:
quelle
Das Heisenberg-Uhrenfenster
Dies kann Sie schwer beißen, wenn Sie Load-on-Demand-Aufgaben ausführen, wie folgt:
Angenommen, Sie haben an anderer Stelle Code, der Folgendes verwendet:
Jetzt möchten Sie Ihre
CreateMyObj()
Methode debuggen . Sie setzen also einen Haltepunkt in Zeile 3 oben, um in den Code einzusteigen. Nur für ein gutes Maß setzen Sie auch einen Haltepunkt in die Zeile darüber_myObj = CreateMyObj();
und sogar einen Haltepunkt inCreateMyObj()
sich.Der Code trifft Ihren Haltepunkt in Zeile 3. Sie betreten den Code. Sie erwarten, den bedingten Code einzugeben, da dieser
_myObj
offensichtlich null ist, oder? Äh ... also ... warum hat es die Bedingung übersprungen und ist direkt zureturn _myObj
?! Sie bewegen Ihre Maus über _myObj ... und tatsächlich hat es einen Wert! Wie ist das passiert?!Die Antwort ist, dass Ihre IDE einen Wert erhalten hat, weil Sie ein "Überwachungs" -Fenster geöffnet haben - insbesondere das Überwachungsfenster "Autos", in dem die Werte aller Variablen / Eigenschaften angezeigt werden, die für die aktuelle oder vorherige Ausführungszeile relevant sind. Wenn Sie Ihren Haltepunkt auf der Linie 3, entschied das Ãœberwachungsfenster , dass Sie daran interessiert wäre , den Wert zu kennen
MyObj
- so hinter den Kulissen, ignoriert alle Ihre Haltepunkte , es ging und den Wert berechnetMyObj
für Sie - einschließlich der Anruf ,CreateMyObj()
dass setzt den Wert von _myObj!Deshalb nenne ich das das Heisenberg-Uhrfenster - man kann den Wert nicht beobachten, ohne ihn zu beeinflussen ... :)
ERWISCHT!
Bearbeiten - Ich bin der Meinung, dass der Kommentar von @ ChristianHayter die Aufnahme in die Hauptantwort verdient, da er eine effektive Problemumgehung für dieses Problem darstellt. Also, wann immer Sie eine faul geladene Eigenschaft haben ...
quelle
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
oder[DebuggerDisplay("<loaded on demand>")]
.Lazy<T>
Klasse (insbesondere für ihreValue
Eigenschaft) ist ein Beispiel dafür, wo dies verwendet wird.ToString
. Jedes Mal, wenn er darüber schwebte, gab ihm der Tooltip einen anderen Wert - er konnte es nicht herausfinden ...Hier ist eine andere Zeit, die mich erwischt:
TimeSpan.Seconds ist der Sekundenanteil der Zeitspanne (2 Minuten und 0 Sekunden haben einen Sekundenwert von 0).
TimeSpan.TotalSeconds ist die gesamte in Sekunden gemessene Zeitspanne (2 Minuten haben einen Gesamtsekundenwert von 120).
quelle
TimeSpan
selbst hat eineSeconds
Eigenschaft überhaupt. Wer gibt einem Rattenarsch den Sekundenanteil einer Zeitspanne? Es ist ein beliebiger, einheitenabhängiger Wert. Ich kann mir keinen praktischen Nutzen dafür vorstellen.SecondsPart
undSecondsTotal
unterschieden.Speicherverlust, weil Sie keine Ereignisse aufgehoben haben.
Dies hat sogar einige leitende Entwickler, die ich kenne, erwischt.
Stellen Sie sich ein WPF-Formular mit vielen Dingen vor, und irgendwo dort abonnieren Sie eine Veranstaltung. Wenn Sie sich nicht abmelden, bleibt das gesamte Formular nach dem Schließen und De-Referenzieren im Speicher.
Ich glaube, das Problem, das ich gesehen habe, war das Erstellen eines DispatchTimers im WPF-Formular und das Abonnieren des Tick-Ereignisses, wenn Sie auf dem Timer kein - = ausführen, verliert Ihr Formular Speicher!
In diesem Beispiel sollte Ihr Teardown-Code haben
Dies ist besonders schwierig, da Sie die Instanz des DispatchTimers im WPF-Formular erstellt haben. Sie würden also denken, dass es sich um eine interne Referenz handelt, die vom Garbage Collection-Prozess verarbeitet wird. Leider verwendet der DispatchTimer eine statische interne Liste von Abonnements und Diensten Anforderungen im UI-Thread, sodass die Referenz der statischen Klasse gehört.
quelle
timer.Tick += (s, e,) => { Console.WriteLine(s); }
Vielleicht nicht wirklich ein Gotcha, weil das Verhalten klar in MSDN geschrieben ist, aber mir einmal den Hals gebrochen hat, weil ich es eher kontraintuitiv fand:
Dieser Typ verlässt die
"nice.pic"
Datei gesperrt, bis das Bild entsorgt ist. Zu der Zeit, als ich damit konfrontiert wurde, dachte ich, es wäre schön, Symbole im laufenden Betrieb zu laden, und merkte (zunächst) nicht, dass ich Dutzende offener und gesperrter Dateien hatte! Das Bild verfolgt, woher die Datei geladen wurde ...Wie kann man das lösen? Ich dachte, ein Einzeiler würde den Job machen. Ich erwartete einen zusätzlichen Parameter für
FromFile()
, hatte aber keinen, also schrieb ich diesen ...quelle
Wenn Sie ASP.NET zählen, würde ich sagen, dass der Lebenszyklus von Webformularen für mich ein ziemlich großes Problem darstellt. Ich habe unzählige Stunden damit verbracht, schlecht geschriebenen Webforms-Code zu debuggen, nur weil viele Entwickler nicht wirklich verstehen, wann sie welchen Event-Handler verwenden sollen (ich eingeschlossen, leider).
quelle
überladen == Operatoren und untypisierte Container (Arraylisten, Datensätze usw.):
Lösungen?
Verwenden
string.Equals(a, b)
Sie diese Option immer, wenn Sie Zeichenfolgentypen vergleichenVerwenden Sie Generika,
List<string>
um sicherzustellen, dass beide Operanden Zeichenfolgen sind.quelle
Moral der Geschichte: Feldinitialisierer werden beim Deserialisieren eines Objekts nicht ausgeführt
quelle
DateTime.ToString ("TT / MM / JJJJ") ; Dies gibt Ihnen nicht immer TT / MM / JJJJ, sondern berücksichtigt die regionalen Einstellungen und ersetzt Ihr Datumstrennzeichen, je nachdem, wo Sie sich befinden. Sie könnten also TT-MM-JJJJ oder ähnliches bekommen.
Der richtige Weg, dies zu tun, ist die Verwendung von DateTime.ToString ("TT '/' MM '/' JJJJ");
DateTime.ToString ("r") soll in RFC1123 konvertieren, das GMT verwendet. GMT ist innerhalb von Sekundenbruchteilen von UTC entfernt, und dennoch wird der Formatbezeichner "r" nicht in UTC konvertiert , selbst wenn die betreffende DateTime als Local angegeben ist.
Dies führt zu folgendem Problem (hängt davon ab, wie weit Ihre Ortszeit von UTC entfernt ist):
Hoppla!
quelle
DateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
Ich habe gesehen, dass dieses Buch neulich veröffentlicht wurde, und ich denke, es ist ziemlich dunkel und schmerzhaft für diejenigen, die es nicht wissen
Da dies 0 zurückgibt und nicht 1, wie die meisten erwarten würden
quelle
Ich bin etwas spät zu dieser Party, aber ich habe zwei Fallstricke, die mich beide kürzlich gebissen haben:
DateTime-Auflösung
Die Ticks-Eigenschaft misst die Zeit in 10-Millionstelsekunden (100 Nanosekundenblöcke). Die Auflösung beträgt jedoch nicht 100 Nanosekunden, sondern etwa 15 ms.
Dieser Code:
gibt Ihnen eine Ausgabe von (zum Beispiel):
Wenn Sie sich DateTime.Now.Millisecond ansehen, erhalten Sie ebenfalls Werte in gerundeten Blöcken von 15,625 ms: 15, 31, 46 usw.
Dieses spezielle Verhalten ist von System zu System unterschiedlich , es gibt jedoch auch andere auflösungsbezogene Fallstricke in dieser Datums- / Zeit-API.
Path.Combine
Eine großartige Möglichkeit, Dateipfade zu kombinieren, aber sie verhält sich nicht immer so, wie Sie es erwarten würden.
Wenn der zweite Parameter mit einem
\
Zeichen beginnt , erhalten Sie keinen vollständigen Pfad:Dieser Code:
Gibt Ihnen diese Ausgabe:
quelle
CD
Befehl, um zu sehen, was ich meine ... 1) SpringenC:\Windows\System32
2) TypCD \Users
3) Woah! Jetzt bist du beiC:\Users
... ERHALTEN? ... Path.Combine (@ "C: \ Windows \ System32", @ "\ Users") sollte zurückgeben,\Users
was genau das[current_drive_here]:\Users
Wenn Sie einen Prozess starten (mithilfe von System.Diagnostics), der in die Konsole schreibt, den Console.Out-Stream jedoch nie liest, scheint Ihre App nach einer bestimmten Ausgabemenge hängen zu bleiben.
quelle
Keine Operatorverknüpfungen in Linq-To-Sql
Siehe hier .
Kurz gesagt, innerhalb der Bedingungsklausel einer Linq-To-Sql-Abfrage können Sie keine bedingten Verknüpfungen wie
||
und verwenden&&
, um Nullreferenzausnahmen zu vermeiden. Linq-To-Sql wertet beide Seiten des ODER- oder UND-Operators aus, auch wenn die erste Bedingung die Bewertung der zweiten Bedingung überflüssig macht!quelle
Verwenden von Standardparametern mit virtuellen Methoden
quelle
Base
, woher soll der Compiler den Standardwert erhalten, wenn nichtBase
? Ich habe gedacht , es ist ein bisschen mehr Gotcha dass der Standardwert verschieden sein kann , wenn der deklarierte Typ der ist abgeleitete Typ, auch wenn das Verfahren (statisch) ist die Basismethode genannt.Wertobjekte in veränderlichen Sammlungen
hat keine Wirkung.
mypoints[i]
gibt eine Kopie von a zurückPoint
Wertobjekts zurück. Mit C # können Sie gerne ein Feld der Kopie ändern. Im Stillen nichts tun.Update: Dies scheint in C # 3.0 behoben zu sein:
quelle
arr[i].attr=
ist eine spezielle Syntax für Arrays, die Sie nicht in Bibliothekscontainern codieren können; (. Warum ist (<Werteausdruck>). attr = <Ausdruck> überhaupt zulässig? Kann es jemals Sinn machen?Vielleicht nicht das Schlimmste, aber einige Teile des .net-Frameworks verwenden Grad, während andere Bogenmaß verwenden (und die Dokumentation, die mit Intellisense angezeigt wird, sagt Ihnen nie, welche, Sie müssen MSDN besuchen, um dies herauszufinden).
All dies hätte vermieden werden können, indem
Angle
stattdessen eine Klasse abgehalten worden wäre ...quelle
Für C / C ++ - Programmierer ist der Übergang zu C # ein natürlicher. Das größte Problem, dem ich persönlich begegnet bin (und bei anderen den gleichen Übergang gesehen habe), ist jedoch, den Unterschied zwischen Klassen und Strukturen in C # nicht vollständig zu verstehen.
In C ++ sind Klassen und Strukturen identisch. Sie unterscheiden sich nur in der Standardsichtbarkeit, wobei Klassen standardmäßig die private Sichtbarkeit und Strukturen standardmäßig die öffentliche Sichtbarkeit verwenden. In C ++ diese Klassendefinition
ist funktional äquivalent zu dieser Strukturdefinition.
In C # sind Klassen jedoch Referenztypen, während Strukturen Werttypen sind. Das macht a GROSSE Unterschied bei (1) der Entscheidung, wann eine übereinander verwendet werden soll, (2) dem Testen der Objektgleichheit, (3) der Leistung (z. B. Boxen / Unboxen) usw.
Es gibt alle Arten von Informationen im Web, die sich auf die Unterschiede zwischen den beiden beziehen (z . B. hier ). Ich würde jedem, der den Übergang zu C # vollzieht, dringend empfehlen, zumindest über die Unterschiede und ihre Auswirkungen Bescheid zu wissen.
quelle
Speicherbereinigung und Entsorgung (). Obwohl Sie nichts tun müssen, um Speicher freizugeben , müssen Sie dennoch Ressourcen über Dispose () freigeben. Dies ist sehr leicht zu vergessen, wenn Sie WinForms verwenden oder Objekte auf irgendeine Weise verfolgen.
quelle
Arrays implementieren
IList
Aber implementieren Sie es nicht. Wenn Sie Add aufrufen, wird angezeigt, dass dies nicht funktioniert. Warum implementiert eine Klasse eine Schnittstelle, wenn sie diese nicht unterstützen kann?
Kompiliert, funktioniert aber nicht:
Wir haben dieses Problem häufig, da der Serializer (WCF) alle ILists in Arrays umwandelt und Laufzeitfehler auftreten.
quelle
IEnumerable<T>
und zuIEnumerator<T>
unterstützenFeatures
, deren Nützlichkeit durch die von "Features" gemeldeten Eigenschaften bestimmt würde. Ich stehe jedoch zu meinem Hauptpunkt: Es gibt Fälle, in denen Code, der einenIEnumerable<T>
Willen erhält, stärkere Versprechen benötigt als vorgesehenIEnumerable<T>
. Ein AnrufToList
würde zu einemIEnumerable<T>
Ergebnis führen, das solche Versprechen einhält, aber in vielen Fällen unnötig teuer wäre. Ich würde davon ausgehen, dass es ...IEnumerable<T>
eine Kopie des Inhalts erstellen kann, dies jedoch nicht unnötig tun kann.foreach Schleifen Variablenbereich!
druckt fünf "amet", während das folgende Beispiel gut funktioniert
quelle
MS SQL Server kann keine Daten vor 1753 verarbeiten. Bezeichnenderweise stimmt dies nicht mit der .NET-
DateTime.MinDate
Konstante überein, die 1/1/1 beträgt. Wenn Sie also versuchen, ein Mindate, ein falsches Datum (wie es mir kürzlich bei einem Datenimport passiert ist) oder einfach das Geburtsdatum von William the Conqueror zu retten, werden Sie in Schwierigkeiten geraten. Hierfür gibt es keine integrierte Problemumgehung. Wenn Sie wahrscheinlich mit Daten vor 1753 arbeiten müssen, müssen Sie Ihre eigene Problemumgehung schreiben.quelle
Das böse Linq Caching Gotcha
Siehe meine Frage , die zu dieser Entdeckung geführt hat, und den Blogger , der das Problem entdeckt hat.
Kurz gesagt, der DataContext speichert einen Cache aller Linq-to-Sql-Objekte, die Sie jemals geladen haben. Wenn jemand anderes Änderungen an einem zuvor geladenen Datensatz vornimmt, können Sie die neuesten Daten nicht abrufen, selbst wenn Sie den Datensatz explizit neu laden!
Dies liegt an einer Eigenschaft,
ObjectTrackingEnabled
die im DataContext aufgerufen wird und standardmäßig true ist. Wenn Sie diese Eigenschaft auf false setzen, wird der Datensatz jedes Mal neu geladen ... ABER ... Sie können mit SubmitChanges () keine Änderungen an diesem Datensatz beibehalten.ERWISCHT!
quelle
Der Vertrag auf Stream.Read ist etwas, das ich bei vielen Leuten gesehen habe:
Der Grund dafür ist, dass höchstens die angegebene Anzahl von Bytes
Stream.Read
gelesen wird , aber völlig frei ist nur 1 Byte zu lesen, auch wenn weitere 7 Bytes vor dem Ende des Stromes zur Verfügung steht.Es hilft nicht , dass das sieht so ähnlich wie
Stream.Write
, das ist die Bytes alle geschrieben haben garantiert , wenn es ohne Ausnahme zurückgibt. Es hilft auch nicht, dass der obige Code fast immer funktioniert . Und natürlich hilft es nicht, dass es keine vorgefertigte und bequeme Methode gibt, um genau N Bytes richtig zu lesen.Um das Loch zu schließen und das Bewusstsein dafür zu schärfen, finden Sie hier ein Beispiel für einen korrekten Weg, dies zu tun:
quelle
var r = new BinaryReader(stream); ulong data = r.ReadUInt64();
. BinaryReader hat auch eineFillBuffer
Methode ...Veranstaltungen
Ich habe nie verstanden, warum Ereignisse ein Sprachmerkmal sind. Sie sind kompliziert zu bedienen: Sie müssen vor dem Anruf auf Null prüfen, Sie müssen sich abmelden, Sie können nicht herausfinden, wer registriert ist (z. B. habe ich mich registriert?). Warum ist eine Veranstaltung nicht nur eine Klasse in der Bibliothek? Grundsätzlich ein Spezialist
List<delegate>
?quelle
button.Click += (s, e) => { Console.WriteLine(s); }
?IEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);});
abmelden und über abmelden müssteclickSubscription.Dispose();
. Wenn mein Objekt alle Abonnements während seiner gesamten Lebensdauer behaltenMySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);}));
und dannMySubscriptions.Dispose()
alle Abonnements löschen würde.Heute habe ich einen Fehler behoben, der sich lange Zeit entzogen hat. Der Fehler befand sich in einer generischen Klasse, die in einem Multithread-Szenario verwendet wurde, und ein statisches int-Feld wurde verwendet, um eine sperrfreie Synchronisation mit Interlocked bereitzustellen. Der Fehler wurde verursacht, weil jede Instanziierung der generischen Klasse für einen Typ eine eigene statische Aufladung hat. Jeder Thread hat also ein eigenes statisches Feld und es wurde nicht wie vorgesehen eine Sperre verwendet.
Dies druckt 5 10 5
quelle
i
es den Typ hätteT
.Type
.SomeGeneric<int>
ist ein anderer Typ alsSomeGeneric<string>
; so hat natürlich jeder seinen eigenenpublic static int i
Aufzählungen können mehrmals ausgewertet werden
Es wird Sie beißen, wenn Sie eine träge aufgezählte Aufzählung haben und Sie zweimal darüber iterieren und unterschiedliche Ergebnisse erhalten. (oder Sie erhalten die gleichen Ergebnisse, aber es wird zweimal unnötig ausgeführt)
Zum Beispiel brauchte ich beim Schreiben eines bestimmten Tests einige temporäre Dateien, um die Logik zu testen:
Stellen Sie sich meine Überraschung beim
File.Delete(file)
Werfen vorFileNotFound
!!Was hier passiert, ist, dass die
files
Aufzählung zweimal wiederholt wurde (die Ergebnisse der ersten Iteration werden einfach nicht gespeichert) und bei jeder neuen Iteration, die Sie erneut aufrufen würden,Path.GetTempFilename()
sodass Sie einen anderen Satz temporärer Dateinamen erhalten.Die Lösung besteht natürlich darin, den Wert mit
ToArray()
oder aufzulistenToList()
:Dies ist noch beängstigender, wenn Sie etwas mit mehreren Threads ausführen, wie:
und du findest heraus, dass
content.Length
nach all den Schreibvorgängen immer noch 0 ist !! Sie beginnen dann rigoros zu überprüfen, ob Sie keine Rennbedingung haben, wenn ... nach einer verschwendeten Stunde ... Sie herausgefunden haben, dass es nur das winzige kleine Enumerable-Gotcha-Ding ist, das Sie vergessen haben ...quelle
Ich habe gerade eine seltsame gefunden, bei der ich eine Weile im Debug stecken geblieben bin:
Sie können null für ein nullfähiges int erhöhen, ohne eine Ausnahme auszulösen, und der Wert bleibt null.
quelle
Ja, dieses Verhalten ist dokumentiert, aber das macht es sicherlich nicht richtig.
quelle