Was ist das schlimmste Problem in C # oder .NET? [geschlossen]


377

Ich habe kürzlich mit einem DateTimeObjekt 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.


157
return DateTime.Now.AddDays (1);
Crashmstr

23
AFAIK, die integrierten Werttypen sind alle unveränderlich, zumindest insofern, als jede im Typ enthaltene Methode ein neues Element zurückgibt, anstatt das vorhandene Element zu ändern. Zumindest kann ich mir keinen vorstellen, der das nicht tut: alles schön und konsequent.
Joel Coehoorn

6
Typ für veränderbare Werte: System.Collections.Generics.List.Enumerator :( (Und ja, Sie können sehen, dass es sich seltsam verhält, wenn Sie sich genug anstrengen.)
Jon Skeet

13
Der Intellisense bietet Ihnen alle Informationen, die Sie benötigen. Es heißt, es gibt ein DateTime-Objekt zurück. Wenn es nur die ändern würde, die Sie übergeben haben, wäre es eine nichtige Methode.
John Kraft

20
Nicht unbedingt: StringBuilder.Append (...) gibt beispielsweise "this" zurück. Das ist bei fließenden Schnittstellen durchaus üblich.
Jon Skeet

Antworten:


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

Blammo. Ihre App stürzt ohne Stack-Trace ab. Es passiert die ganze Zeit.

(Beachten Sie im Getter Großbuchstaben MyVaranstelle von Kleinbuchstaben myVar.)


112
und SO passend für diese Seite :)
gbjbaanb

62
Ich unterstreiche das private Mitglied, hilft sehr!
Chakrit

61
Ich benutze automatische Eigenschaften, wo ich kann, stoppt diese Art von Problem viel;)
TWith2Sugars

28
Dies ist ein großartiger Grund, Präfixe für Ihre privaten Felder zu verwenden (es gibt andere, aber dies ist ein guter): _myVar, m_myVar
jrista

205
@jrista: O bitte NEIN ... nicht m_ ... aargh das Grauen ...
fretje

254

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ögen System.String, aber nicht System.Windows.Forms.Form. Die Antwort ist, dass es nur in der aktuellen Assembly und in aussieht mscorlib.


Anonyme Methoden

C # 2.0 führte anonyme Methoden ein, die zu bösen Situationen wie diesen führten:

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();
        }
    }
}

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 iVariable 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:

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();
        }
    }
}

Verzögerte Ausführung von Iteratorblöcken

Dieser "Unit Test für arme Männer" besteht nicht - warum nicht?

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
        }
    }
}

Die Antwort ist, dass der Code in der Quelle des CapitalLettersCodes erst ausgeführt wird, wenn die MoveNext()Methode des Iterators zum ersten Mal aufgerufen wird.

Ich habe einige andere Kuriositäten auf meiner Brainteaser-Seite .


25
Das Iterator-Beispiel ist umständlich!
Jimmy

8
Warum teilen Sie dies nicht in drei Antworten auf, damit wir alle statt aller zusammen abstimmen können?
Chakrit

13
@chakrit: Rückblickend wäre das wahrscheinlich eine gute Idee gewesen, aber ich denke, es ist jetzt zu spät. Es könnte auch so ausgesehen haben, als hätte ich nur versucht, mehr Wiederholungen zu bekommen ...
Jon Skeet

19
Tatsächlich funktioniert Type.GetType, wenn Sie den AssemblyQualifiedName angeben. Type.GetType ("System.ServiceModel.EndpointNotFoundException, System.ServiceModel, Version = 3.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089");
Chilltemp

2
@kentaromiura: Die Überlastungsauflösung beginnt beim am meisten abgeleiteten Typ und arbeitet den Baum auf - aber nur bei Methoden, die ursprünglich in dem Typ deklariert wurden, den sie betrachten. Foo (int) überschreibt die Basismethode und wird daher nicht berücksichtigt. Foo (Objekt) ist anwendbar, daher stoppt die Überlastungsauflösung dort. Seltsam, ich weiß.
Jon Skeet

194

Ausnahmen erneut auslösen

Ein Gotcha, das viele neue Entwickler bekommt, ist die Re-Throw-Ausnahmesemantik.

Viel Zeit sehe ich Code wie den folgenden

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

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:

catch(Exception)
{
    throw;
}

Oder wickeln Sie die Ausnahme in eine andere ein und verwenden Sie die innere Ausnahme, um die ursprüngliche Stapelverfolgung abzurufen:

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

Zum Glück wurde ich in meiner ersten Woche von jemandem darüber unterrichtet und finde es im Code älterer Entwickler. Ist: catch () {throw; } Das gleiche wie das zweite Code-Snippet? catch (Ausnahme e) {throw; } Nur wird kein Ausnahmeobjekt erstellt und ausgefüllt?
StuperUser

Neben dem Fehler, throw ex (oder throw e) anstelle von throw zu verwenden, muss ich mich fragen, in welchen Fällen es sich lohnt, eine Ausnahme abzufangen, um sie erneut zu werfen.
Ryan Lundy

13
@Kyralessa: Es gibt viele Fälle: Zum Beispiel, wenn Sie eine Transaktion zurücksetzen möchten, bevor der Anrufer die Ausnahme erhält. Sie rollen zurück und werfen dann neu.
R. Martinho Fernandes

7
Ich sehe dies die ganze Zeit, wenn Leute Ausnahmen abfangen und erneut auslösen, nur weil ihnen beigebracht wird, dass sie alle Ausnahmen abfangen müssen, ohne zu bemerken, dass sie weiter oben im Aufrufstapel abgefangen werden. Es macht mich verrückt.
James Westgate

5
@ Kyralessa Der größte Fall ist, wenn Sie protokollieren müssen. Protokollieren Sie den Fehler in catch und werfen Sie erneut ..
nawfal

194

Das Heisenberg-Uhrenfenster

Dies kann Sie schwer beißen, wenn Sie Load-on-Demand-Aufgaben ausführen, wie folgt:

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

Angenommen, Sie haben an anderer Stelle Code, der Folgendes verwendet:

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

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 in CreateMyObj()sich.

Der Code trifft Ihren Haltepunkt in Zeile 3. Sie betreten den Code. Sie erwarten, den bedingten Code einzugeben, da dieser _myObjoffensichtlich null ist, oder? Äh ... also ... warum hat es die Bedingung übersprungen und ist direkt zu return _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 berechnet MyObjfü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 ...

Dekorieren Sie Ihre Immobilie mit [DebuggerBrowsable (DebuggerBrowsableState.Never)] oder [DebuggerDisplay ("<bei Bedarf geladen>")]. - Christian Hayter


10
genialer Fund! Sie sind kein Programmierer, Sie sind ein echter Debugger.
das. __curious_geek

26
Ich bin sogar auf die Variable gestoßen, nicht nur auf das Überwachungsfenster.
Richard Morgan

31
Dekorieren Sie Ihre Immobilie mit [DebuggerBrowsable(DebuggerBrowsableState.Never)]oder [DebuggerDisplay("<loaded on demand>")].
Christian Hayter

4
Wenn Sie eine Framework-Klasse entwickeln und eine Überwachungsfensterfunktionalität wünschen, ohne das Laufzeitverhalten einer träge erstellten Eigenschaft zu ändern, können Sie einen Proxy vom Typ Debugger verwenden, um den Wert zurückzugeben, falls er bereits erstellt wurde, und eine Nachricht, die die Eigenschaft nicht hat wurde konstruiert, wenn das der Fall ist. Die Lazy<T>Klasse (insbesondere für ihre ValueEigenschaft) ist ein Beispiel dafür, wo dies verwendet wird.
Sam Harwell

4
Ich erinnere mich an jemanden, der (aus irgendeinem Grund, den ich nicht verstehen kann) den Wert des Objekts in einer Überladung von geändert hat ToString. Jedes Mal, wenn er darüber schwebte, gab ihm der Tooltip einen anderen Wert - er konnte es nicht herausfinden ...
JNF

144

Hier ist eine andere Zeit, die mich erwischt:

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

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).


1
Ja, das hat mich auch. Ich denke, es sollte TimeSpan.SecondsPart oder etwas sein, um klarer zu machen, was es darstellt.
Dan Diplo

3
Auf Wieder Lektüre dieses habe ich zu fragen , warum TimeSpanselbst hat eine SecondsEigenschaft ü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.
MusiGenesis

2
Für mich ist es sinnvoll, dass TimeSpan.TotalSeconds ... die Gesamtzahl der Sekunden in der Zeitspanne zurückgibt.
Ed S.

16
@MusiGenesis die Eigenschaft ist nützlich. Was ist, wenn ich die in Teilen aufgeschlüsselte Zeitspanne anzeigen möchte? Angenommen, Ihre Zeitspanne entspricht einer Dauer von "3 Stunden 15 Minuten 10 Sekunden". Wie können Sie auf diese Informationen ohne die Eigenschaften Sekunden, Stunden, Minuten zugreifen?
SolutionYogi

1
In ähnlichen APIs habe ich die beiden verwendet SecondsPartund SecondsTotalunterschieden.
BlueRaja - Danny Pflughoeft

80

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

timer.Tick -= TimerTickEventHandler;

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.


1
Der Trick besteht darin, immer alle von Ihnen erstellten Ereignisabonnements freizugeben. Wenn Sie sich darauf verlassen, dass Forms dies für Sie erledigt, können Sie sicher sein, dass Sie sich daran gewöhnen und eines Tages vergessen, ein Ereignis irgendwo zu veröffentlichen, wo es durchgeführt werden muss.
Jason Williams

3
Es gibt hier einen MS-Connect-Vorschlag für schwache Referenzereignisse , der dieses Problem lösen würde. Meiner Meinung nach sollten wir das unglaublich schlechte Ereignismodell jedoch vollständig durch ein schwach gekoppeltes ersetzen, wie es von CAB verwendet wird.
BlueRaja - Danny Pflughoeft

+1 von mir, danke! Nein, danke für die Codeüberprüfung, die ich machen musste!
Bob Denny

@ BlueRaja-DannyPflughoeft Bei schwachen Ereignissen hast du ein weiteres Problem - du kannst keine Lambdas abonnieren. Sie können nicht schreibentimer.Tick += (s, e,) => { Console.WriteLine(s); }
Ark-Kun

@ Ark-kun ja Lambdas machen es noch schwieriger, du müsstest dein Lambda in einer Variablen speichern und das in deinem Teardown-Code verwenden. Zerstört irgendwie die Einfachheit des Schreibens von Lambdas, nicht wahr?
Timothy Walters

63

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:

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

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 ...

using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read))
{
    image = System.Drawing.Image.FromStream(fs);
}

10
Ich stimme zu, dass dieses Verhalten keinen Sinn ergibt. Ich kann keine andere Erklärung dafür finden als "Dieses Verhalten ist beabsichtigt".
MusiGenesis

1
Oh, und das Tolle an dieser Problemumgehung ist, wenn Sie versuchen, Image.ToStream (ich vergesse den genauen Namen sofort) aufzurufen, funktioniert es später nicht mehr.
Joshua

55
müssen einen Code überprüfen. Brb.
Esben Skov Pedersen

7
@EsbenSkovPedersen So ein einfacher, aber lustiger und trockener Kommentar. Meinen Tag gerettet.
Inisheer

51

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).


26
Deshalb bin ich zu MVC gewechselt ... habe Kopfschmerzen ...
Chakrit

29
Es gab eine ganz andere Frage, die speziell ASP.NET-Fallstricken gewidmet war (zu Recht). Das Grundkonzept von ASP.NET (Web-Apps für den Entwickler wie Windows-Apps erscheinen zu lassen) ist so schrecklich falsch, dass ich nicht sicher bin, ob es überhaupt als "Gotcha" gilt.
MusiGenesis

1
MusiGenesis Ich wünschte, ich könnte Ihren Kommentar hundertmal abstimmen.
Csauve

3
@MusiGenesis Es scheint jetzt falsch zu sein, aber zu der Zeit wollten die Leute, dass ihre Webanwendungen (Anwendungen sind das Schlüsselwort - ASP.NET WebForms war nicht wirklich zum Hosten eines Blogs konzipiert) sich genauso verhalten wie ihre Windows-Anwendungen. Dies hat sich erst vor relativ kurzer Zeit geändert und viele Leute sind "immer noch nicht ganz da". Das ganze Problem war, dass die Abstraktion viel zu undicht war - das Web verhielt sich nicht so sehr wie eine Desktop-Anwendung, dass es bei fast allen zu Verwirrung führte.
Luaan

1
Ironischerweise war das erste, was ich über ASP.NET gesehen habe, ein Video von Microsoft, das zeigt, wie einfach Sie mit ASP.NET eine Blog-Site erstellen können!
MusiGenesis

51

überladen == Operatoren und untypisierte Container (Arraylisten, Datensätze usw.):

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

Lösungen?

  • Verwenden string.Equals(a, b)Sie diese Option immer, wenn Sie Zeichenfolgentypen vergleichen

  • Verwenden Sie Generika, List<string>um sicherzustellen, dass beide Operanden Zeichenfolgen sind.


6
Sie haben dort zusätzliche Leerzeichen, die alles falsch machen - aber wenn Sie die Leerzeichen entfernen, bleibt die letzte Zeile wahr, da "meine" + "Zeichenfolge" immer noch eine Konstante ist.
Jon Skeet

1
ack! du hast recht :) ok, ich habe ein bisschen bearbeitet.
Jimmy

Bei solchen Verwendungen wird eine Warnung generiert.
Chakrit

11
Ja, einer der größten Fehler in der C # -Sprache ist der Operator == in der Klasse Object. Sie hätten uns zwingen sollen, ReferenceEquals zu verwenden.
Erikkallen

2
Zum Glück haben wir seit 2.0 Generika. Es gibt weniger Grund zur Sorge, wenn Sie im obigen Beispiel List <string> anstelle von ArrayList verwenden. Außerdem haben wir damit an Leistung gewonnen, yay! Ich verwurzele immer alte Verweise auf ArrayLists in unserem Legacy-Code.
JoelC

48
[Serializable]
class Hello
{
    readonly object accountsLock = new object();
}

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

Moral der Geschichte: Feldinitialisierer werden beim Deserialisieren eines Objekts nicht ausgeführt


8
Ja, ich hasse die .NET-Serialisierung, weil der Standardkonstruktor nicht ausgeführt wird. Ich wünschte, es wäre unmöglich, ein Objekt zu konstruieren, ohne Konstruktoren aufzurufen, aber leider nicht.
Roman Starkov

45

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):

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

Hoppla!


19
Geändert von mm zu MM - mm ist Minuten und MM ist Monate. Noch ein Gotcha, denke ich ...
Kobi

1
Ich könnte sehen, wie dies ein Gotcha wäre, wenn Sie es nicht wüssten (ich wusste es nicht) ... aber ich versuche herauszufinden, wann Sie das Verhalten wollen, bei dem Sie speziell versuchen, ein Datum zu drucken, das stimmt nicht mit Ihren regionalen Einstellungen überein.
Beska

6
@Beska: Da Sie in eine Datei schreiben, muss diese in einem bestimmten Format mit einem bestimmten Datumsformat vorliegen.
GvS

11
Ich bin der Meinung, dass die lokalisierten Standardeinstellungen schlechter sind als umgekehrt. Zumindest der Entwickler ignorierte die Lokalisierung vollständig. Der Code funktioniert auf Computern, die anders lokalisiert sind. Auf diese Weise funktioniert der Code wahrscheinlich nicht.
Joshua

32
Eigentlich glaube ich, dass der richtige Weg dies zu tun wäreDateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
BlueRaja - Danny Pflughoeft

44

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

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

Da dies 0 zurückgibt und nicht 1, wie die meisten erwarten würden


37
Ich hoffe, das würde die Leute nicht wirklich beißen - ich hoffe wirklich, dass sie es überhaupt nicht schreiben würden! (Es ist natürlich trotzdem interessant.)
Jon Skeet

12
Ich denke nicht, dass dies sehr dunkel ist ...
Chris Marasti-Georg

10
Zumindest in C # werden die Ergebnisse definiert, wenn sie unerwartet sind. In C ++ kann es 0 oder 1 sein oder ein anderes Ergebnis, einschließlich Programmbeendigung!
James Curran

7
Dies ist kein Gotcha; x = x ++ -> x = x, dann inkrementiere x .... x = ++ x -> inkrementiere x dann x = x
Kevin

28
@ Kevin: Ich denke nicht, dass es so einfach ist. Wenn x = x ++ äquivalent zu x = x gefolgt von x ++ wäre, wäre das Ergebnis x = 1. Stattdessen denke ich, dass zuerst der Ausdruck rechts vom Gleichheitszeichen ausgewertet wird (0 ergibt), dann x inkrementiert (x = 1), und schließlich wird die Zuweisung ausgeführt (x = 0 erneut geben).
Tim Goodman

39

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:

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

gibt Ihnen eine Ausgabe von (zum Beispiel):

0
0
0
0
0
0
0
156254
156254
156254

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:

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));

Gibt Ihnen diese Ausgabe:

C:\MyFolder\MySubFolder\log\
\log\
C:\MyFolder\MySubFolder\log\
\log\

17
Die Quantisierung von Zeiten in Intervallen von ~ 15 ms ist nicht auf mangelnde Genauigkeit des zugrunde liegenden Zeitmechanismus zurückzuführen (ich habe es versäumt, dies früher zu erläutern). Dies liegt daran, dass Ihre App in einem Multitasking-Betriebssystem ausgeführt wird. Windows meldet sich etwa alle 15 ms bei Ihrer App an. Während des kurzen Zeitabschnitts verarbeitet Ihre App alle Nachrichten, die seit Ihrem letzten Slice in der Warteschlange standen. Alle Ihre Anrufe innerhalb dieses Slice werden genau zur gleichen Zeit zurückgegeben, da sie alle genau zur gleichen Zeit getätigt werden.
MusiGenesis

2
@MusiGenesis: Ich weiß (jetzt), wie es funktioniert, aber es scheint mir irreführend, ein so genaues Maß zu haben, das nicht wirklich so genau ist. Es ist, als würde ich sagen, dass ich meine Größe in Nanometern kenne, wenn ich sie wirklich nur auf die nächsten zehn Millionen runde.
Damovisa

7
DateTime ist durchaus in der Lage, bis zu einem einzigen Tick zu speichern. Es ist DateTime.Now, das diese Genauigkeit nicht verwendet.
Ruben

16
Das Extra '\' ist ein Problem für viele Unix / Mac / Linux-Leute. Wenn es in Windows ein führendes '\' gibt, bedeutet dies, dass wir zum Stammverzeichnis des Laufwerks gehen möchten (dh C :), versuchen Sie es in einem CDBefehl, um zu sehen, was ich meine ... 1) Springen C:\Windows\System322) Typ CD \Users3) Woah! Jetzt bist du bei C:\Users... ERHALTEN? ... Path.Combine (@ "C: \ Windows \ System32", @ "\ Users") sollte zurückgeben, \Userswas genau das[current_drive_here]:\Users
Chakrit

8
Auch ohne den "Schlaf" funktioniert dies genauso. Dies hat nichts damit zu tun, dass die App alle 15 ms geplant wird. Die von DateTime.UtcNow aufgerufene native Funktion GetSystemTimeAsFileTime scheint eine schlechte Auflösung zu haben.
Jimbo

38

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.


3
Dasselbe kann immer noch passieren, wenn Sie sowohl stdout als auch stderr umleiten und zwei ReadToEnd-Aufrufe nacheinander verwenden. Für den sicheren Umgang mit stdout und stderr müssen Sie für jeden von ihnen einen Lesethread erstellen.
Sebastiaan M

34

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!


8
Bis. BRB, einige hundert LINQ-Abfragen neu optimierend ...
tsilb

30

Verwenden von Standardparametern mit virtuellen Methoden

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();

Ausgabe:
abgeleitete Basis


10
Seltsam, ich fand das völlig offensichtlich. Wenn der deklarierte Typ lautet Base, woher soll der Compiler den Standardwert erhalten, wenn nicht Base? 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.
Timwi

1
Warum sollte eine Implementierung einer Methode den Standardwert einer anderen Implementierung erhalten?
Staafl

1
@staafl Standardargumente werden zur Kompilierungszeit und nicht zur Laufzeit aufgelöst.
Fredoverflow

1
Ich würde sagen, dieses Gotcha ist im Allgemeinen der Standardparameter - die Leute merken oft nicht, dass sie zur Kompilierungszeit und nicht zur Laufzeit aufgelöst werden.
Luaan

4
@FredOverflow, meine Frage war konzeptionell. Obwohl das Verhalten für die Implementierung sinnvoll ist, ist es nicht intuitiv und eine wahrscheinliche Fehlerquelle. Meiner Meinung nach sollte der C # -Compiler das Überschreiben von Standardparameterwerten beim Überschreiben nicht zulassen.
Staafl

27

Wertobjekte in veränderlichen Sammlungen

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

mypoints[i].x = 10;

hat keine Wirkung.

mypoints[i] gibt eine Kopie von a zurück Point 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:

Cannot modify the return value of 'System.Collections.Generic.List<Foo>.this[int]' because it is not a variable

6
Ich kann sehen, warum das verwirrend ist, wenn man bedenkt, dass es tatsächlich mit Arrays funktioniert (entgegen Ihrer Antwort), aber nicht mit anderen dynamischen Sammlungen wie List <Point>.
Lasse V. Karlsen

2
Du hast recht. Vielen Dank. Ich habe meine Antwort korrigiert :). 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?
Bjarke Ebert

1
@Bjarke Ebert: Es gibt einige Fälle, in denen es sinnvoll wäre, aber leider gibt es für den Compiler keine Möglichkeit, diese zu identifizieren und zuzulassen. Beispiel für ein Verwendungsszenario: Eine unveränderliche Struktur, die einen Verweis auf ein quadratisches zweidimensionales Array zusammen mit einem "Rotate / Flip" -Indikator enthält. Die Struktur selbst wäre unveränderlich, daher sollte das Schreiben in ein Element einer schreibgeschützten Instanz in Ordnung sein, aber der Compiler weiß nicht, dass der Eigenschaftssetzer die Struktur nicht tatsächlich schreiben wird, und lässt sie daher nicht zu .
Supercat

25

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 Anglestattdessen eine Klasse abgehalten worden wäre ...


Ich bin überrascht, dass dies so viele positive Stimmen bekommen hat, wenn man bedenkt, dass meine anderen Fallstricke deutlich schlechter sind als diese
BlueRaja - Danny Pflughoeft

22

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

    class A
    {
    public:
        int i;
    };

ist funktional äquivalent zu dieser Strukturdefinition.

    struct A
    {
        int i;
    };

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.


13
Das Schlimmste ist also, dass sich die Leute nicht die Zeit nehmen, die Sprache zu lernen, bevor sie sie benutzen?
BlueRaja - Danny Pflughoeft

3
@ BlueRaja-DannyPflughoeft Eher wie das klassische Gotcha scheinbar ähnlicher Sprachen - sie verwenden sehr ähnliche Schlüsselwörter und in vielen Fällen Syntax, funktionieren aber auf sehr unterschiedliche Weise.
Luaan

19

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.


2
Der using () -Block löst dieses Problem sauber. Immer wenn Sie einen Aufruf zur Entsorgung sehen, können Sie die Verwendung mit () sofort und sicher umgestalten.
Jeremy Frey

5
Ich denke, das Problem bestand darin , IDisposable korrekt zu implementieren .
Mark Brackett

4
Andererseits kann die Gewohnheit using () Sie unerwartet beißen, wie bei der Arbeit mit PInvoke. Sie möchten nichts entsorgen, auf das die API noch verweist.
MusiGenesis

3
Die korrekte Implementierung von IDisposable ist sehr schwer zu verstehen, und selbst die besten Ratschläge, die ich zu diesem Thema gefunden habe (.NET Framework-Richtlinien), können verwirrend sein, bis Sie sie endgültig "verstanden" haben.
Quibblesome

1
Der beste Rat, den ich je zu IDisposable gefunden habe, stammt von Stephen Cleary, einschließlich drei einfacher Regeln und eines ausführlichen Artikels zu IDisposable
Roman

19

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:

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

Wir haben dieses Problem häufig, da der Serializer (WCF) alle ILists in Arrays umwandelt und Laufzeitfehler auftreten.


8
IMHO ist das Problem, dass Microsoft nicht genügend Schnittstellen für Sammlungen definiert hat. Meiner Meinung nach sollte es iEnumerable, iMultipassEnumerable (unterstützt Reset und garantiert, dass mehrere Durchgänge übereinstimmen), iLiveEnumerable (teilweise definierte Semantik haben, wenn sich die Sammlung während der Aufzählung ändert - Änderungen können in der Aufzählung auftreten oder nicht, sollten aber nicht verursachen falsche Ergebnisse oder Ausnahmen), iReadIndexable, iReadWriteIndexable usw. Da Schnittstellen andere Schnittstellen "erben" können, hätte dies nicht viel zusätzliche Arbeit verursacht (es würde NotImplemented-Stubs sparen).
Supercat

@supercat, das wäre für Anfänger und bestimmte langjährige Programmierer höllisch verwirrend. Ich finde die .NET-Sammlungen und ihre Schnittstellen wunderbar elegant. Aber ich schätze deine Demut. ;)
Jordanien

@Jordan: Seit ich das Obige geschrieben habe, habe ich beschlossen, dass ein besserer Ansatz darin bestanden hätte, sowohl eine Eigenschaft als auch einige "optionale" Methoden zu haben IEnumerable<T>und zu IEnumerator<T>unterstützen Features, 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 einen IEnumerable<T>Willen erhält, stärkere Versprechen benötigt als vorgesehen IEnumerable<T>. Ein Anruf ToListwürde zu einem IEnumerable<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 ...
Supercat

... ein Mittel, mit dem der empfangende Code bei BedarfIEnumerable<T> eine Kopie des Inhalts erstellen kann, dies jedoch nicht unnötig tun kann.
Supercat

Ihre Option ist absolut nicht lesbar. Wenn ich eine IList im Code sehe, weiß ich, womit ich arbeite, anstatt eine Features-Eigenschaft prüfen zu müssen. Programmierer vergessen gerne, dass ein wichtiges Merkmal von Code darin besteht, dass er nicht nur von Computern, sondern auch von Menschen gelesen werden kann. Der Namespace für .NET-Sammlungen ist nicht ideal, aber gut, und manchmal ist es nicht wichtig, ein Prinzip besser zu finden, um die beste Lösung zu finden. Der schlechteste Code, mit dem ich je gearbeitet habe, war Code, der versucht hat, DRY optimal anzupassen. Ich habe es verschrottet und neu geschrieben. Es war nur schlechter Code. Ich würde Ihr Framework überhaupt nicht verwenden wollen.
Jordanien

18

foreach Schleifen Variablenbereich!

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());

druckt fünf "amet", während das folgende Beispiel gut funktioniert

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());

11
Dies entspricht im Wesentlichen Jons Beispiel mit anonymen Methoden.
Mehrdad Afshari

3
Speichern Sie, dass es mit foreach noch verwirrender ist, wenn die Variable "s" einfacher mit der Variablen mit Gültigkeitsbereich zu mischen ist. Bei gemeinsamen for-Schleifen ist die Indexvariable für jede Iteration eindeutig dieselbe.
Mikko Rantanen

2
blogs.msdn.com/ericlippert/archive/2009/11/12/… und ja, ich wünschte, die Variable wäre "richtig".
Roman Starkov

2
Dies wurde in C # 5 behoben .
Johnbot

Sie drucken im Wesentlichen immer wieder dieselbe Variable, ohne sie zu ändern.
Jordanien

18

MS SQL Server kann keine Daten vor 1753 verarbeiten. Bezeichnenderweise stimmt dies nicht mit der .NET- DateTime.MinDateKonstante ü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.


17
Ehrlich gesagt denke ich, dass MS SQL Server dieses Recht hat und .Net falsch ist. Wenn Sie nachforschen, wissen Sie, dass Daten vor 1751 aufgrund von Kalenderänderungen, vollständig übersprungenen Tagen usw. unkonventionell werden. Die meisten RDBMs haben einen Grenzwert. Dies sollte Ihnen einen Ausgangspunkt geben: ancestry.com/learn/library/article.aspx?article=3358
NotMe

11
Außerdem ist das Datum 1753. Das war so ziemlich das erste Mal, dass wir einen fortlaufenden Kalender haben, ohne dass Daten übersprungen werden. In SQL 2008 wurden der Datentyp Datum und Datum / Uhrzeit2 eingeführt, der Daten vom 1.1.01 bis zum 31.12.19999 akzeptieren kann. Datumsvergleiche mit diesen Typen sollten jedoch mit Argwohn betrachtet werden, wenn Sie tatsächlich Daten vor 1753 vergleichen.
NotMe

Oh, richtig, 1753, korrigiert, danke.
Shaul Behr

Ist es wirklich sinnvoll, Datumsvergleiche mit solchen Daten durchzuführen? Ich meine, für History Channel ist das sehr sinnvoll, aber ich sehe mich nicht in der Lage, den genauen Wochentag zu erfahren, an dem Amerika entdeckt wurde.
Camilo Martin

5
Über Wikipedia am Julianischen Tag finden Sie ein 13-zeiliges Basisprogramm CALJD.BAS, das 1984 veröffentlicht wurde und Datumsberechnungen bis etwa 5000 v. Chr. Durchführen kann, wobei Schalttage und die übersprungenen Tage 1753 berücksichtigt werden. Ich verstehe also nicht, warum "modern" "Systeme wie SQL2008 sollten schlechter abschneiden. Möglicherweise sind Sie nicht an einer korrekten Datumsdarstellung im 15. Jahrhundert interessiert, andere jedoch, und unsere Software sollte dies ohne Fehler handhaben. Ein weiteres Problem sind Schaltsekunden. . .
Roland

18

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, ObjectTrackingEnableddie 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!


Ich habe gerade anderthalb Tage (und jede Menge Haare!) Damit verbracht, diesen BUG zu jagen ...
Surgical Coder

Dies wird als Parallelitätskonflikt bezeichnet und ist auch heute noch ein Problem, obwohl es jetzt bestimmte Möglichkeiten gibt, dies zu umgehen, obwohl sie dazu neigen, etwas hartnäckig zu sein. DataContext war ein Albtraum. O_o
Jordanien

17

Der Vertrag auf Stream.Read ist etwas, das ich bei vielen Leuten gesehen habe:

// 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);

Der Grund dafür ist, dass höchstens die angegebene Anzahl von Bytes Stream.Readgelesen 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:

    /// <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;
    }

1
Oder in Ihrem expliziten Beispiel : var r = new BinaryReader(stream); ulong data = r.ReadUInt64();. BinaryReader hat auch eine FillBufferMethode ...
jimbobmcgee

15

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>?


1
Auch Multithreading ist schmerzhaft. Alle diese Probleme außer dem Null-Ding sind in CAB behoben (dessen Funktionen eigentlich nur in die Sprache integriert werden sollten) - Ereignisse werden global deklariert, und jede Methode kann sich selbst als "Abonnent" eines Ereignisses deklarieren. Mein einziges Problem mit CAB ist, dass die globalen Ereignisnamen eher Zeichenfolgen als Aufzählungen sind (was durch intelligentere Aufzählungen wie Java behoben werden könnte, die von Natur aus als Zeichenfolgen funktionieren!) . CAB ist schwierig einzurichten, aber es ist ein einfacher Open-Source - Klon verfügbar hier .
BlueRaja - Danny Pflughoeft

3
Ich mag die Implementierung von .net-Ereignissen nicht. Das Ereignisabonnement sollte durch Aufrufen einer Methode behandelt werden, die das Abonnement hinzufügt und ein IDisposable zurückgibt, das bei Entsorgung das Abonnement löscht. Es ist kein spezielles Konstrukt erforderlich, das eine "add" - und eine "remove" -Methode kombiniert, deren Semantik etwas zwielichtig sein kann, insbesondere wenn versucht wird, einen Multicast-Delegaten hinzuzufügen und später zu entfernen (z. B. "B" gefolgt von "AB" hinzufügen und dann entfernen) "B" (verlässt "BA") und "AB" (verlässt immer noch "BA").
Ups

@supercat Wie würden Sie umschreiben button.Click += (s, e) => { Console.WriteLine(s); }?
Ark-Kun

Wenn ich mich getrennt von anderen Veranstaltungen IEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);});abmelden und über abmelden müsste clickSubscription.Dispose();. Wenn mein Objekt alle Abonnements während seiner gesamten Lebensdauer behalten MySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);}));und dann MySubscriptions.Dispose()alle Abonnements löschen würde.
Supercat

@ Ark-kun: Objekte, die außerhalb von Abonnements gekapselt sind, behalten zu müssen, mag als störend erscheinen, aber Abonnements als Entitäten zu betrachten, würde es ermöglichen, sie mit einem Typ zu aggregieren, der sicherstellen kann, dass sie alle bereinigt werden, was ansonsten sehr schwierig ist.
Supercat

14

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.

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);
    }
}

Dies druckt 5 10 5


5
Sie können eine nicht generische Basisklasse haben, die die Statik definiert und die Generika davon erbt. Obwohl ich in C # nie auf dieses Verhalten hereingefallen bin, erinnere ich mich noch an die langen Debugging-Stunden einiger C ++ - Vorlagen ... Eww! :)
Paulius

7
Seltsam, ich fand das offensichtlich. Denken Sie nur darüber nach, was es tun sollte, wenn ies den Typ hätte T.
Timwi

1
Der Typparameter ist Teil des Type. SomeGeneric<int>ist ein anderer Typ als SomeGeneric<string>; so hat natürlich jeder seinen eigenenpublic static int i
radarbob

13

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:

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);

Stellen Sie sich meine Überraschung beim File.Delete(file)Werfen vor FileNotFound!!

Was hier passiert, ist, dass die filesAufzä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 aufzulisten ToList():

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

Dies ist noch beängstigender, wenn Sie etwas mit mehreren Threads ausführen, wie:

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

und du findest heraus, dass content.Lengthnach 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 ...


Dies ist beabsichtigt. Es heißt verzögerte Ausführung. Es soll unter anderem TSQL-Konstrukte simulieren. Jedes Mal, wenn Sie aus einer SQL-Ansicht auswählen, erhalten Sie unterschiedliche Ergebnisse. Es ermöglicht auch die Verkettung, die für entfernte Datenspeicher wie SQL Server hilfreich ist. Andernfalls würde x.Select.Where.OrderBy 3 separate Befehle an die Datenbank senden ...
as9876

@AYS hast du das Wort "Gotcha" im Fragentitel verpasst?
Chakrit

Ich dachte, Gotcha bedeutete ein Versehen der Designer, nicht etwas Absichtliches.
As9876

Möglicherweise sollte es einen anderen Typ für nicht neu startbare IEnumerables geben. Wie AutoBufferedEnumerable? Man könnte es leicht umsetzen. Dieses Problem scheint hauptsächlich auf das mangelnde Wissen des Programmierers zurückzuführen zu sein. Ich glaube nicht, dass am aktuellen Verhalten etwas falsch ist.
Eldritch Conundrum

13

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.

int? i = null;
i++; // I would have expected an exception but runs fine and stays as null

Dies ist das Ergebnis davon, wie C # Operationen für nullfähige Typen nutzt. Es ist ein bisschen ähnlich wie NaN, das alles verbraucht, was man darauf wirft.
IllidanS4 will Monica

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!"

Ja, dieses Verhalten ist dokumentiert, aber das macht es sicherlich nicht richtig.


5
Ich bin anderer Meinung - wenn ein Wort in Großbuchstaben geschrieben ist, kann es eine besondere Bedeutung haben, dass Sie sich nicht mit Title Case anlegen möchten, z. B. "Präsident der USA" -> "Präsident der USA", nicht "Präsident der USA" USA".
Shaul Behr

5
@Shaul: In diesem Fall sollten sie dies als Parameter angeben , um Verwirrung zu vermeiden, da ich noch nie jemanden getroffen habe, der dieses Verhalten im Voraus erwartet hat - was dies zu einem Gotcha macht !
BlueRaja - Danny Pflughoeft
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.