384 Stimmen

Was ist der schlimmste Fehler in C# oder .NET?

Ich habe kürzlich mit einem DateTime Objekt, und schrieb etwas wie dieses:

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

Die Intellisense-Dokumentation für AddDays() sagt, dass es einen Tag zum Datum hinzufügt, was nicht der Fall ist - es devuelve ein Datum mit einem hinzugefügten Tag, also müssen Sie es so schreiben:

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

Dies hat mich schon einige Male gebissen, also dachte ich, es wäre nützlich, die schlimmsten C#-Fehler zu katalogisieren.

158 Stimmen

Return DateTime.Now.AddDays(1);

24 Stimmen

Soweit ich weiß, sind die eingebauten Werttypen alle unveränderlich, zumindest insofern, als jede Methode, die mit dem Typ verbunden ist, ein neues Element zurückgibt, anstatt das vorhandene Element zu verändern. Zumindest fällt mir spontan kein Typ ein, der dies nicht tut: alles schön und konsistent.

1 Stimmen

Community-Wiki, so viel Spam in SO jetzt. Wenn Fragen subjektiv sind (keine endgültige Antwort), sollte es Community Wiki sein.

308voto

Eric Z Beard Punkte 36325
private int myVar;
public int MyVar
{
    get { return MyVar; }
}

Blammo. Ihre Anwendung stürzt ohne Stack-Trace ab. Das passiert immer wieder.

(Hinweis Kapital MyVar anstelle von Kleinbuchstaben myVar im Getter).

258voto

Jon Skeet Punkte 1325502

Typ.GetType

Diejenige, die ich bei vielen Menschen beobachtet habe, ist Type.GetType(string) . Sie fragen sich, warum es für Typen in ihrer eigenen Assembly funktioniert, und einige Typen wie System.String , aber nicht System.Windows.Forms.Form . Die Antwort ist, dass es nur in der aktuellen Baugruppe und in mscorlib .


Anonyme Methoden

Mit C# 2.0 wurden anonyme Methoden eingeführt, die zu unangenehmen Situationen wie dieser führen:

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, das hängt ganz von der Terminplanung ab. Es werden 10 Zahlen gedruckt, aber wahrscheinlich nicht 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, was man erwarten könnte. Das Problem ist, dass es sich um die i Variable, die erfasst wurde, und nicht ihren Wert zum Zeitpunkt der Erstellung des Delegaten. Dies kann leicht durch eine zusätzliche lokale Variable mit dem richtigen Umfang 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();
        }
    }
}

Aufgeschobene Ausführung von Iteratorblöcken

Dieser "Einheitstest des armen Mannes" wird nicht bestanden - 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 im Quelltext der CapitalLetters Code wird nicht ausgeführt, bis der Iterator die MoveNext() Methode zum ersten Mal aufgerufen wird.

Ich habe noch einige andere Merkwürdigkeiten auf meiner Rätselseite .

202voto

Shaul Behr Punkte 35201

Das Heisenberg-Uhrenfenster

Das kann sich negativ auswirken, wenn Sie z. B. Load-on-demand-Sachen machen:

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

Nehmen wir nun an, Sie haben an anderer Stelle einen Code, der dies verwendet:

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

Jetzt wollen Sie Ihr Debugging CreateMyObj() Methode. Sie setzen also einen Haltepunkt in Zeile 3, mit der Absicht, in den Code einzugreifen. Vorsichtshalber setzen Sie auch einen Haltepunkt in der Zeile darüber, in der es heißt _myObj = CreateMyObj(); und sogar einen Haltepunkt innerhalb von CreateMyObj() selbst.

Der Code trifft auf Ihren Haltepunkt in Zeile 3. Sie steigen in den Code ein. Sie erwarten, dass Sie den bedingten Code eingeben, denn _myObj ist offensichtlich null, richtig? Äh... also... warum hat es die Bedingung übersprungen und ist direkt zu return _myObj ?! Sie fahren mit der Maus über _myObj... und tatsächlich, es hat einen Wert! Wie konnte das passieren?!

Die Antwort ist, dass Ihre IDE den Wert ermittelt hat, weil Sie ein "Watch"-Fenster geöffnet haben - insbesondere das "Autos"-Watch-Fenster, das die Werte aller Variablen/Eigenschaften anzeigt, die für die aktuelle oder vorherige Ausführungszeile relevant sind. Als Sie Ihren Haltepunkt in Zeile 3 erreichten, entschied das Überwachungsfenster, dass Sie den Wert von MyObj - also hinter den Kulissen, einen Ihrer Haltepunkte zu ignorieren berechnet es den Wert von MyObj für Sie - einschließlich der Aufforderung an CreateMyObj() die den Wert von _myObj setzt!

Deshalb nenne ich es auch das Heisenbergsche Uhrenfenster - man kann den Wert nicht beobachten, ohne ihn zu beeinflussen... :)

GOTCHA!


Editer - Ich denke, der Kommentar von @ChristianHayter verdient es, in die Hauptantwort aufgenommen zu werden, da er eine wirksame Lösung für dieses Problem zu sein scheint. Also immer, wenn Sie eine Lazy-Loaded-Eigenschaft haben...

Schmücken Sie Ihre Eigenschaft mit [DebuggerBrowsable(DebuggerBrowsableState.Never)] oder [DebuggerDisplay("<auf Anfrage geladen>")] - Christian Hayter

195voto

Sam Saffron Punkte 124121

Ausnahmen wieder einwerfen

Ein Problem, das viele neue Entwickler beschäftigt, ist die Semantik der Re-Throw-Exception.

Oftmals sehe ich Code wie den folgenden

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

Das Problem ist, dass der Stack-Trace gelöscht wird und die Diagnose von Problemen erschwert wird, da man nicht nachvollziehen kann, woher die Ausnahme stammt.

Der richtige Code ist entweder die throw-Anweisung ohne Args:

catch(Exception)
{
    throw;
}

Oder Sie können die Ausnahme in eine andere Ausnahme verpacken und die innere Ausnahme verwenden, um den ursprünglichen Stack-Trace zu erhalten:

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

146voto

Jon B Punkte 49459

Hier ist ein weiterer Fall, der mich beschäftigt:

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 hat einen Sekundenwert von 0).

TimeSpan.TotalSeconds ist die gesamte Zeitspanne, gemessen in Sekunden (2 Minuten haben einen Gesamtwert von 120 Sekunden).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X