Je travaillais récemment avec un DateTime
objet et j'ai écrit quelque chose comme ceci:
DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?
La documentation d'intellisense AddDays()
indique qu'elle ajoute un jour à la date, ce qu'elle ne fait pas - elle retourne en fait une date avec un jour ajouté, vous devez donc l'écrire comme suit :
DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt; // tomorrow's date
Celui-ci m'a mordu plusieurs fois auparavant, j'ai donc pensé qu'il serait utile de cataloguer les pires pièges en C #.
Réponses:
Blammo. Votre application se bloque sans trace de pile. Arrive tout le temps.
(Remarquez les majuscules
MyVar
au lieu des minusculesmyVar
dans le getter.)la source
Type.GetType
Celui que j'ai vu mordre beaucoup de gens est
Type.GetType(string)
. Ils se demandent pourquoi cela fonctionne pour les types de leur propre assemblage, et certains types commeSystem.String
, mais pasSystem.Windows.Forms.Form
. La réponse est qu'il ne regarde que dans l'assemblage actuel et dansmscorlib
.Méthodes anonymes
C # 2.0 a introduit des méthodes anonymes, conduisant à des situations désagréables comme celle-ci:
Qu'est-ce que cela imprimera? Eh bien, cela dépend entièrement de la programmation. Il imprimera 10 chiffres, mais il n'imprimera probablement pas 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ce à quoi vous pourriez vous attendre. Le problème est que c'est la
i
variable qui a été capturée, pas sa valeur au moment de la création du délégué. Cela peut être résolu facilement avec une variable locale supplémentaire de la bonne portée:Exécution différée des blocs d'itérateur
Ce «test unitaire du pauvre» ne passe pas - pourquoi pas?
La réponse est que le code dans la source du
CapitalLetters
code n'est pas exécuté jusqu'à ce que laMoveNext()
méthode de l'itérateur soit appelée pour la première fois.J'ai d'autres bizarreries sur ma page casse - tête .
la source
Re-lever des exceptions
Un piège qui reçoit de nombreux nouveaux développeurs est la sémantique d'exception de re-throw.
Beaucoup de temps, je vois du code comme celui-ci
Le problème est qu'il efface la trace de la pile et rend le diagnostic des problèmes beaucoup plus difficile, car vous ne pouvez pas suivre l'origine de l'exception.
Le code correct est soit l'instruction throw sans argument:
Ou encapsuler l'exception dans une autre, et utiliser l'exception interne pour obtenir la trace de pile d'origine:
la source
La fenêtre de surveillance de Heisenberg
Cela peut vous mordre gravement si vous effectuez des tâches de chargement à la demande, comme ceci:
Supposons maintenant que vous ayez du code ailleurs en utilisant ceci:
Vous voulez maintenant déboguer votre
CreateMyObj()
méthode. Vous avez donc mis un point d'arrêt sur la ligne 3 ci-dessus, avec l'intention d'entrer dans le code. Juste 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 à l'intérieur deCreateMyObj()
lui-même.Le code atteint votre point d'arrêt sur la ligne 3. Vous entrez dans le code. Vous vous attendez à entrer le code conditionnel, car
_myObj
est évidemment nul, non? Euh ... alors ... pourquoi at-il sauté la condition et est allé directement àreturn _myObj
?! Vous passez votre souris sur _myObj ... et en effet, cela a une valeur! Comment est-ce arrivé?!La réponse est que votre IDE lui a fait obtenir une valeur, car 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 sur 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, en ignorant l'un de vos points d'arrêt , il est allé et a calculé la valeur deMyObj
pour vous - y compris l'appel àCreateMyObj()
ce définit la valeur de _myObj!C'est pourquoi j'appelle cela la fenêtre de surveillance Heisenberg - vous ne pouvez pas observer la valeur sans l'affecter ... :)
JE T'AI EU!
Edit - Je pense que le commentaire de @ ChristianHayter mérite d'être inclus dans la réponse principale, car il ressemble à une solution de contournement efficace pour ce problème. Donc, chaque fois que vous avez une propriété paresseuse ...
la source
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
ou[DebuggerDisplay("<loaded on demand>")]
.Lazy<T>
classe (en particulier pour saValue
propriété) est un exemple d'utilisation.ToString
. Chaque fois qu'il la survolait, l'infobulle lui donnait une valeur différente - il n'arrivait pas à le comprendre ...Voici une autre fois qui me fait:
TimeSpan.Seconds est la partie en secondes de l'intervalle de temps (2 minutes et 0 seconde a une valeur de secondes de 0).
TimeSpan.TotalSeconds est la durée totale mesurée en secondes (2 minutes ont une valeur totale de 120 secondes).
la source
TimeSpan
même a uneSeconds
propriété du tout. De toute façon, qui donne au cul d'un rat ce que sont les secondes d'une tranche de temps? C'est une valeur arbitraire et dépendante de l'unité; Je ne peux concevoir aucune utilisation pratique pour cela.SecondsPart
etSecondsTotal
pour distinguer les deux.Fuite de mémoire car vous n'avez pas décroché les événements.
Cela a même surpris certains développeurs seniors que je connais.
Imaginez un formulaire WPF contenant beaucoup de choses, et quelque part là-dedans, vous vous abonnez à un événement. Si vous ne vous désabonnez pas, le formulaire entier est conservé en mémoire après avoir été fermé et dé-référencé.
Je crois que le problème que j'ai vu était la création d'un DispatchTimer dans le formulaire WPF et l'abonnement à l'événement Tick, si vous ne faites pas de - = sur le minuteur, votre formulaire perd de la mémoire!
Dans cet exemple, votre code de démontage devrait avoir
Celui-ci est particulièrement délicat puisque vous avez créé l'instance de DispatchTimer dans le formulaire WPF, vous pensez donc que ce serait une référence interne gérée par le processus de récupération de place ... malheureusement, DispatchTimer utilise une liste interne statique d'abonnements et de services demandes sur le thread d'interface utilisateur, de sorte que la référence est «détenue» par la classe statique.
la source
timer.Tick += (s, e,) => { Console.WriteLine(s); }
Peut-être pas vraiment un problème parce que le comportement est écrit clairement dans MSDN, mais m'a cassé le cou une fois parce que je l'ai trouvé plutôt contre-intuitif:
Ce gars laisse le
"nice.pic"
fichier verrouillé jusqu'à ce que l'image soit supprimée. Au moment où je l'ai fait face, je pensais que ce serait bien de charger des icônes à la volée et je ne savais pas (au début) que je me retrouvais avec des dizaines de fichiers ouverts et verrouillés! L'image garde une trace de l'endroit où elle a chargé le fichier ...Comment résoudre ça? Je pensais qu'un paquebot ferait l'affaire. Je m'attendais à un paramètre supplémentaire pour
FromFile()
, mais n'en avais aucun, alors j'ai écrit ceci ...la source
Si vous comptez ASP.NET, je dirais que le cycle de vie des formulaires Web est un gros problème pour moi. J'ai passé d'innombrables heures à déboguer du code de formulaires Web mal écrit, simplement parce que beaucoup de développeurs ne comprennent pas vraiment quand utiliser quel gestionnaire d'événements (moi y compris, malheureusement).
la source
opérateurs == surchargés et conteneurs non typés (listes, ensembles de données, etc.):
Solutions?
toujours utiliser
string.Equals(a, b)
lorsque vous comparez des types de chaîneen utilisant des génériques comme
List<string>
pour garantir que les deux opérandes sont des chaînes.la source
Morale de l'histoire: les initialiseurs de champ ne sont pas exécutés lors de la désérialisation d'un objet
la source
DateTime.ToString ("jj / MM / aaaa") ; Ce ne sera en fait pas vous donnera toujours le jj / mm / aaaa, mais il tiendra compte des paramètres régionaux et remplacera votre séparateur de date en fonction de l'endroit où vous vous trouvez. Donc, vous pourriez obtenir jj-MM-aaaa ou quelque chose de similaire.
La bonne façon de procéder consiste à utiliser DateTime.ToString ("dd '/' MM '/' yyyy");
DateTime.ToString ("r") est censé se convertir en RFC1123, qui utilise GMT. GMT est à une fraction de seconde de UTC, et pourtant le spécificateur de format "r" ne se convertit pas en UTC , même si le DateTime en question est spécifié comme Local.
Il en résulte le piège suivant (varie en fonction de la distance de votre heure locale par rapport à UTC):
Oups!
la source
DateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
J'ai vu celui-ci posté l'autre jour, et je pense qu'il est assez obscur et douloureux pour ceux qui ne connaissent pas
Comme cela renverra 0 et non 1 comme la plupart s'y attendraient
la source
Je suis un peu en retard à cette fête, mais j'ai deux accrochages qui m'ont tous deux mordu récemment:
Résolution DateTime
La propriété Ticks mesure le temps en 10 millionièmes de seconde (blocs de 100 nanosecondes), mais la résolution n'est pas de 100 nanosecondes, elle est d'environ 15 ms.
Ce code:
vous donnera une sortie de (par exemple):
De même, si vous regardez DateTime.Now.Millisecond, vous obtiendrez des valeurs en segments arrondis de 15,625 ms: 15, 31, 46, etc.
Ce comportement particulier varie d'un système à l'autre , mais il existe d'autres problèmes liés à la résolution dans cette API de date / heure.
Path.Combine
Un excellent moyen de combiner les chemins d'accès aux fichiers, mais il ne se comporte pas toujours comme vous vous y attendez.
Si le deuxième paramètre commence par un
\
caractère, il ne vous donnera pas un chemin complet:Ce code:
Vous donne cette sortie:
la source
CD
commande pour voir ce que je veux dire ... 1) GotoC:\Windows\System32
2) TypeCD \Users
3) Woah! Maintenant, vous êtesC:\Users
... OBTENU? ... Path.Combine (@ "C: \ Windows \ System32", @ "\ Users") devrait\Users
[current_drive_here]:\Users
Lorsque vous démarrez un processus (à l'aide de System.Diagnostics) qui écrit sur la console, mais que vous ne lisez jamais le flux Console.Out, après une certaine quantité de sortie, votre application semble se bloquer.
la source
Aucun raccourci opérateur dans Linq-To-Sql
Voyez ici .
En bref, à l'intérieur de la clause conditionnelle d'une requête Linq-To-Sql, vous ne pouvez pas utiliser de raccourcis conditionnels comme
||
et&&
pour éviter les exceptions de référence nulles; Linq-To-Sql évalue les deux côtés de l'opérateur OR ou AND même si la première condition évite d'avoir à évaluer la deuxième condition!la source
Utilisation de paramètres par défaut avec des méthodes virtuelles
la source
Base
, d'où le compilateur devrait-il obtenir la valeur par défaut sinonBase
? J'aurais pensé que c'est un peu plus gênant que la valeur par défaut peut être différente si le type déclaré est le type dérivé , même si la méthode appelée (statiquement) est la méthode de base.Objets de valeur dans des collections mutables
n'a aucun effet.
mypoints[i]
renvoie une copie d'unPoint
objet valeur. C # vous permet avec bonheur de modifier un champ de la copie. Ne rien faire en silence.Mise à jour: cela semble être corrigé dans C # 3.0:
la source
arr[i].attr=
est une syntaxe spéciale pour les tableaux que vous ne pouvez pas coder dans des conteneurs de bibliothèque; (. Pourquoi est-ce que (<expression de valeur>). attr = <expr> est-il autorisé? Peut-il avoir un sens?Peut-être pas le pire, mais certaines parties du framework .net utilisent des degrés tandis que d'autres utilisent des radians (et la documentation qui apparaît avec Intellisense ne vous dit jamais lequel, vous devez visiter MSDN pour le savoir)
Tout cela aurait pu être évité en ayant une
Angle
classe à la place ...la source
Pour les programmeurs C / C ++, la transition vers C # est naturelle. Cependant, le plus gros problème que j'ai rencontré personnellement (et que j'ai vu avec d'autres faire la même transition) n'est pas de comprendre pleinement la différence entre les classes et les structures en C #.
En C ++, les classes et les structures sont identiques; ils ne diffèrent que par la visibilité par défaut, où les classes par défaut à la visibilité privée et les structures par défaut à la visibilité publique. En C ++, cette définition de classe
est fonctionnellement équivalent à cette définition de structure.
En C #, cependant, les classes sont des types de référence tandis que les structures sont des types de valeur. Cela fait un GRAND différence dans (1) la décision de les utiliser les uns par rapport aux autres, (2) le test de l'égalité des objets, (3) les performances (par exemple, boxe / déballage), etc.
Il existe toutes sortes d'informations sur le Web liées aux différences entre les deux (par exemple, ici ). J'encourage fortement toute personne effectuant la transition vers C # à avoir au moins une connaissance pratique des différences et de leurs implications.
la source
Collecte des ordures et disposer (). Bien que vous n'ayez rien à faire pour libérer de la mémoire , vous devez quand même libérer des ressources via Dispose (). C'est une chose extrêmement facile à oublier lorsque vous utilisez WinForms ou suivez des objets de quelque manière que ce soit.
la source
Implémentation de tableaux
IList
Mais ne l'implémentez pas. Lorsque vous appelez Ajouter, il vous indique que cela ne fonctionne pas. Alors pourquoi une classe implémente-t-elle une interface alors qu'elle ne peut pas la supporter?
Compile, mais ne fonctionne pas:
Nous avons beaucoup ce problème, car le sérialiseur (WCF) transforme tous les ILists en tableaux et nous obtenons des erreurs d'exécution.
la source
IEnumerable<T>
et deIEnumerator<T>
prendre en charge uneFeatures
propriété ainsi que certaines méthodes "facultatives" dont l'utilité serait déterminée par ce que les "fonctionnalités" ont rapporté. Je maintiens mon point principal, cependant, qui est qu'il y a des cas où le code recevant unIEnumerable<T>
aura besoin de promesses plus fortes que cellesIEnumerable<T>
fournies. AppelerToList
donnerait unIEnumerable<T>
tel qui tient ces promesses, mais serait dans de nombreux cas inutilement cher. Je pense qu'il devrait y avoir ...IEnumerable<T>
pourrait faire une copie du contenu si nécessaire mais pourrait s'abstenir de le faire inutilement.foreach boucle l'étendue des variables!
imprime cinq "amet", tandis que l'exemple suivant fonctionne très bien
la source
MS SQL Server ne peut pas gérer les dates antérieures à 1753. Significativement, cela n'est pas synchronisé avec la
DateTime.MinDate
constante .NET , qui est 1/1/1. Donc, si vous essayez de sauver un esprit, une date malformée (comme cela m'est arrivé récemment lors d'une importation de données) ou simplement la date de naissance de Guillaume le Conquérant, vous aurez des ennuis. Il n'existe aucune solution de contournement intégrée pour cela; si vous devez probablement travailler avec des dates antérieures à 1753, vous devez écrire votre propre solution de contournement.la source
The Nasty Linq Caching Gotcha
Voir ma question qui a conduit à cette découverte, et le blogueur qui a découvert le problème.
En bref, le DataContext conserve un cache de tous les objets Linq-to-Sql que vous avez déjà chargés. Si quelqu'un d'autre modifie un enregistrement que vous avez précédemment chargé, vous ne pourrez pas obtenir les dernières données, même si vous rechargez explicitement l'enregistrement!
Cela est dû à une propriété appelée
ObjectTrackingEnabled
sur le DataContext, qui par défaut est vraie. Si vous définissez cette propriété sur false, l'enregistrement sera à nouveau chargé à chaque fois ... MAIS ... vous ne pouvez pas conserver les modifications apportées à cet enregistrement avec SubmitChanges ().JE T'AI EU!
la source
Le contrat sur Stream.Read est quelque chose que j'ai vu beaucoup de gens:
La raison pour laquelle c'est faux est que vous
Stream.Read
lirez au plus le nombre d'octets spécifié, mais qu'il est entièrement libre de lire seulement 1 octet, même si 7 autres octets sont disponibles avant la fin du flux.Cela n'aide pas que cela ressemble à
Stream.Write
ce qui est garanti d'avoir écrit tous les octets s'il retourne sans exception. Cela n'aide pas non plus que le code ci-dessus fonctionne presque tout le temps . Et bien sûr, cela n'aide pas qu'il n'y ait pas de méthode prête à l'emploi pour lire correctement N octets correctement.Donc, pour boucher le trou et augmenter la prise de conscience de cela, voici un exemple d'une bonne façon de procéder:
la source
var r = new BinaryReader(stream); ulong data = r.ReadUInt64();
. BinaryReader a aussi uneFillBuffer
méthode ...Événements
Je n'ai jamais compris pourquoi les événements sont une fonctionnalité linguistique. Ils sont compliqués à utiliser: vous devez vérifier la valeur null avant d'appeler, vous devez vous désinscrire (vous-même), vous ne pouvez pas savoir qui est inscrit (par exemple: je me suis inscrit?). Pourquoi un événement n'est-il pas simplement une classe dans la bibliothèque? Fondamentalement, un spécialiste
List<delegate>
?la source
button.Click += (s, e) => { Console.WriteLine(s); }
?IEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);});
et me désinscrire viaclickSubscription.Dispose();
. Si mon objet devait conserver tous les abonnements tout au long de sa durée de vie,MySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);}));
puisMySubscriptions.Dispose()
tuer tous les abonnements.Aujourd'hui, j'ai corrigé un bug qui échappait depuis longtemps. Le bogue se trouvait dans une classe générique utilisée dans un scénario multithread et un champ int statique a été utilisé pour fournir une synchronisation sans verrouillage à l'aide d'Interlocked. Le bogue était dû au fait que chaque instanciation de la classe générique pour un type a sa propre statique. Ainsi, chaque thread a son propre champ statique et il n'a pas été utilisé de verrou comme prévu.
Cela imprime 5 10 5
la source
i
avait le typeT
.Type
.SomeGeneric<int>
est un type différent deSomeGeneric<string>
; alors bien sûr, chacun a son proprepublic static int i
Les énumérables peuvent être évalués plusieurs fois
Cela vous mordra lorsque vous aurez un énumérable paresseusement énuméré et que vous le répéterez deux fois et obtiendrez des résultats différents. (ou vous obtenez les mêmes résultats mais il s'exécute deux fois inutilement)
Par exemple, lors de l'écriture d'un certain test, j'avais besoin de quelques fichiers temporaires pour tester la logique:
Imaginez ma surprise quand
File.Delete(file)
jetteFileNotFound
!!Ce qui se passe ici, c'est que l'
files
énumérable a été itéré deux fois (les résultats de la première itération ne sont tout simplement pas mémorisés) et à chaque nouvelle itération, vous serez appelé de nouveauPath.GetTempFilename()
afin d'obtenir un ensemble différent de noms de fichiers temporaires.La solution est, bien sûr, d'énumérer avec impatience la valeur en utilisant
ToArray()
ouToList()
:C'est encore plus effrayant lorsque vous faites quelque chose de multi-thread, comme:
et vous découvrez
content.Length
est toujours 0 après toutes les écritures !! Vous commencez ensuite à vérifier rigoureusement que vous n'avez pas de condition de course quand .... après une heure perdue ... vous vous êtes rendu compte que c'est juste cette toute petite chose énumérable que vous avez oubliée ....la source
Je viens de trouver un étrange qui m'a coincé dans le débogage pendant un certain temps:
Vous pouvez incrémenter null pour un entier nullable sans lancer une excecption et la valeur reste nulle.
la source
Oui, ce comportement est documenté, mais cela ne le rend certainement pas correct.
la source