380 votes

Quel est le pire gotcha en C# ou .NET ?

J'ai récemment travaillé avec un DateTime et écrit quelque chose comme ceci :

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

La documentation intellisense pour AddDays() dit qu'il ajoute un jour à la date, ce qui n'est pas le cas - en fait renvoie à une date à laquelle on ajoute un jour, il faut donc l'écrire comme ça :

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

J'ai déjà eu à faire face à ce problème à plusieurs reprises. J'ai donc pensé qu'il serait utile de répertorier les pires erreurs de C#.

158 votes

Retourner DateTime.Now.AddDays(1) ;

24 votes

À ma connaissance, les types de valeurs intégrés sont tous immuables, du moins dans la mesure où toute méthode incluse dans le type renvoie un nouvel élément plutôt que de modifier l'élément existant. Du moins, je n'en vois pas un seul qui ne fasse pas cela : tout est beau et cohérent.

1 votes

Wiki communautaire, tellement de spam dans SO maintenant. Lorsque les questions sont subjectives (pas de réponse définitive), il devrait s'agir d'un wiki communautaire.

306voto

Eric Z Beard Points 18473
private int myVar;
public int MyVar
{
    get { return MyVar; }
}

Blammo. Votre application se plante sans trace de pile. Ça arrive tout le temps.

(Avis capital MyVar au lieu des minuscules myVar dans le getter).

113 votes

Et si approprié pour ce site :)

62 votes

J'ai mis des underscores sur le membre privé, ça aide beaucoup !

2 votes

Celui-ci remporte la palme en raison de sa méchanceté et de son ironie liée à Stack Overflow.

254voto

Jon Skeet Points 692016

Type.GetType

Celui que j'ai vu mordre beaucoup de gens est Type.GetType(string) . Ils se demandent pourquoi cela fonctionne pour les types dans leur propre assemblage, et certains types comme System.String mais pas System.Windows.Forms.Form . La réponse est qu'il ne regarde que dans l'assemblage actuel et dans le fichier mscorlib .


Méthodes anonymes

C# 2.0 a introduit les méthodes anonymes, ce qui a conduit à des situations désagréables comme celle-ci :

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

Qu'est-ce que ça va imprimer ? Eh bien, cela dépend entièrement de la programmation. Il imprimera 10 chiffres, mais probablement pas 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ce à quoi on pourrait s'attendre. Le problème est que c'est le i qui a été capturée, et non sa valeur au moment de la création du délégué. Ceci peut être résolu facilement avec une variable locale supplémentaire de la bonne portée :

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

Exécution différée des blocs d'itérateurs

Ce "test unitaire du pauvre" ne passe pas - pourquoi pas ?

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

La réponse est que le code à l'intérieur de la source de l'application CapitalLetters n'est pas exécuté tant que le code de l'itérateur n'a pas été modifié. MoveNext() est appelée pour la première fois.

J'ai d'autres bizarreries sur mon page des casse-têtes .

25 votes

L'exemple de l'itérateur est sournois !

1 votes

Cependant, c'est comme ça que c'est censé fonctionner. Il est difficile de s'y retrouver, mais en y jouant un peu, c'est en fait très utile.

9 votes

Pourquoi ne pas diviser cette question en 3 réponses afin que nous puissions voter pour chacune d'entre elles au lieu de toutes ensemble ?

197voto

Shaul Points 8267

La fenêtre de la montre Heisenberg

Cela peut vous mordre si vous faites du chargement à la demande, comme ceci :

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

Maintenant, disons que vous avez du code ailleurs qui utilise ceci :

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

Maintenant, vous voulez déboguer votre CreateMyObj() méthode. Vous placez donc un point d'arrêt sur la ligne 3 ci-dessus, avec l'intention d'entrer dans le code. Pour faire bonne mesure, vous mettez également un point d'arrêt sur la ligne ci-dessus qui dit _myObj = CreateMyObj(); et même un point d'arrêt dans CreateMyObj() lui-même.

Le code atteint votre point d'arrêt à la ligne 3. Vous entrez dans le code. Vous vous attendez à entrer le code conditionnel, car _myObj est évidemment nulle, non ? Euh... alors... pourquoi il a ignoré la condition et est passé directement à... return _myObj ? ! Vous passez votre souris sur _myObj... et effectivement, il a une valeur ! Comment cela est-il arrivé ? !

La réponse est que votre IDE lui a donné une valeur, parce que vous avez une fenêtre de "surveillance" ouverte - en particulier la fenêtre de surveillance "Autos", qui affiche les valeurs de toutes les variables/propriétés pertinentes pour la ligne d'exécution actuelle ou précédente. Lorsque vous avez atteint votre point d'arrêt à la ligne 3, la fenêtre de surveillance a décidé que vous seriez intéressé de connaître la valeur de MyObj - donc dans les coulisses, ignorer l'un de vos points d'arrêt il a calculé la valeur de MyObj pour vous - y compris l'appel à CreateMyObj() qui définit la valeur de _myObj !

C'est pourquoi je l'appelle la fenêtre d'observation d'Heisenberg - vous ne pouvez pas observer la valeur sans l'affecter... :)

GOTCHA !


Modifier - Je pense que le commentaire de @ChristianHayter mérite d'être inclus dans la réponse principale, car il semble être une solution de contournement efficace pour ce problème. Donc, chaque fois que vous avez une propriété chargée paresseusement...

Décorez votre propriété avec [DebuggerBrowsable(DebuggerBrowsableState.Never)] ou [DebuggerDisplay("<loaded on demand>")]. - Christian Hayter

10 votes

Brillante découverte ! vous n'êtes pas un programmeur, vous êtes un vrai débogueur.

2 votes

Cela m'a rendu fou la semaine dernière, mais pour un constructeur statique plutôt que pour un chargeur paresseux. J'avais beau essayer, je n'arrivais pas à trouver mes points d'arrêt, même si le code était clairement exécuté. J'ai fini par utiliser la méthode low-tech Debug.WriteLine approche.

26 votes

J'ai rencontré ce problème même en survolant la variable, pas seulement la fenêtre de surveillance.

194voto

Sam Saffron Points 56236

Rejeter les exceptions

Un problème auquel se heurtent beaucoup de nouveaux développeurs est la sémantique de l'exception de relance.

Souvent, je vois un code comme le suivant

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

Le problème est que cela efface la trace de la pile et rend le diagnostic des problèmes beaucoup plus difficile, car vous ne pouvez pas savoir d'où vient l'exception.

Le code correct est soit l'instruction throw sans argument :

catch(Exception)
{
    throw;
}

Ou envelopper l'exception dans une autre exception, et utiliser l'exception interne pour obtenir la trace de la pile originale :

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

0 votes

Très heureusement, quelqu'un m'a appris cela dès ma première semaine et je l'ai trouvé dans le code de développeurs plus expérimentés. Est : catch() { throw ; } La même chose que le deuxième extrait de code ? catch(Exception e) { throw ; } sauf qu'il ne crée pas un objet Exception et ne le remplit pas ?

0 votes

Outre l'erreur d'utiliser throw ex (ou throw e) au lieu de simplement throw, je me demande dans quels cas il vaut la peine d'attraper une exception pour ensuite la relancer.

13 votes

@Kyralessa : il y a de nombreux cas : par exemple, si vous voulez annuler une transaction, avant que l'appelant ne reçoive l'exception. Vous faites un rollback puis un rethrow.

145voto

Jon B Points 26872

Voici un autre temps qui me touche :

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

TimeSpan.Seconds est la partie secondes de l'intervalle de temps (2 minutes et 0 seconde ont une valeur de 0).

TimeSpan.TotalSeconds est la durée totale mesurée en secondes (2 minutes ont une valeur totale en secondes de 120).

0 votes

Je ne le savais pas. Heureusement, j'utilise habituellement Environment.TickCount.

0 votes

Il y a environ un an, celui-ci m'a brûlé pendant des semaines.

1 votes

Ouais, celui-là m'a eu aussi. Je pense que ça devrait être TimeSpan.SecondsPart ou quelque chose comme ça pour que ce que ça représente soit plus clair.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X