Quel est le pire problème en C # ou .NET? [fermé]

377

Je travaillais récemment avec un DateTimeobjet 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 #.

MusiGenesis
la source
157
return DateTime.Now.AddDays (1);
crashmstr
23
AFAIK, les types de valeur intégrés sont tous immuables, du moins en ce que toute méthode incluse avec le type renvoie un nouvel élément plutôt que de modifier l'élément existant. Au moins, je ne peux pas penser à un sur le dessus de ma tête qui ne fasse pas cela: tous gentils et cohérents.
Joel Coehoorn
6
Type de valeur mutable: System.Collections.Generics.List.Enumerator :( (Et oui, vous pouvez le voir se comporter bizarrement si vous essayez assez fort.)
Jon Skeet
13
L'intellisense vous donne toutes les informations dont vous avez besoin. Il indique qu'il renvoie un objet DateTime. Si cela modifiait simplement celui que vous avez transmis, ce serait une méthode nulle.
John Kraft,
20
Pas nécessairement: StringBuilder.Append (...) retourne "this" par exemple. C'est assez courant dans les interfaces fluides.
Jon Skeet

Réponses:

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

Blammo. Votre application se bloque sans trace de pile. Arrive tout le temps.

(Remarquez les majuscules MyVarau lieu des minuscules myVardans le getter.)

Eric Z Beard
la source
112
et donc approprié pour ce site :)
gbjbaanb
62
Je mets des soulignements sur le membre privé, aide beaucoup!
chakrit
61
J'utilise les propriétés automatiques là où je peux, arrête ce genre de problème beaucoup;)
TWith2Sugars
28
C'est une GRANDE raison d'utiliser des préfixes pour vos champs privés (il y en a d'autres, mais c'est un bon): _myVar, m_myVar
jrista
205
@jrista: O s'il vous plaît NON ... pas m_ ... aargh l'horreur ...
fretje
254

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 comme System.String, mais pas System.Windows.Forms.Form. La réponse est qu'il ne regarde que dans l'assemblage actuel et dans mscorlib.


Méthodes anonymes

C # 2.0 a introduit des méthodes anonymes, conduisant à 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 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 ivariable 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:

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érateur

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 dans la source du CapitalLetterscode n'est pas exécuté jusqu'à ce que la MoveNext()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 .

Jon Skeet
la source
25
L'exemple de l'itérateur est sournois!
Jimmy
8
pourquoi ne pas diviser cela en 3 réponses afin que nous puissions voter chacun au lieu de tous ensemble?
chakrit
13
@chakrit: Rétrospectivement, cela aurait probablement été une bonne idée, mais je pense qu'il est trop tard maintenant. Il aurait peut-être aussi semblé que j'essayais simplement d'obtenir plus de représentants ...
Jon Skeet
19
En fait, Type.GetType fonctionne si vous fournissez AssemblyQualifiedName. Type.GetType ("System.ServiceModel.EndpointNotFoundException, System.ServiceModel, Version = 3.0.0.0, Culture = neutre, PublicKeyToken = b77a5c561934e089");
chilltemp
2
@kentaromiura: La résolution de surcharge commence au type le plus dérivé et fonctionne dans l'arborescence - mais en ne regardant que les méthodes initialement déclarées dans le type qu'il regarde. Foo (int) remplace la méthode de base, donc n'est pas considéré. Foo (objet) est applicable, donc la résolution de surcharge s'arrête là. Bizarre, je sais.
Jon Skeet
194

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

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

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:

catch(Exception)
{
    throw;
}

Ou encapsuler l'exception dans une autre, et utiliser l'exception interne pour obtenir la trace de pile d'origine:

catch(Exception e) 
{
   // Do stuff 
   throw new MySpecialException(e); 
}
Sam Saffron
la source
Fort heureusement, quelqu'un m'a appris cela au cours de ma première semaine et je l'ai trouvé dans le code des développeurs plus expérimentés. Est: catch () {throw; } Identique au deuxième extrait de code? catch (Exception e) {throw; } seulement, il ne crée pas d'objet Exception et ne le remplit pas?
StuperUser
Outre l'erreur d'utiliser throw ex (ou throw e) au lieu de simplement throw, je dois me demander dans quels cas il vaut la peine d'attraper une exception pour la lancer à nouveau.
Ryan Lundy
13
@Kyralessa: il existe de nombreux cas: par exemple, si vous souhaitez annuler une transaction, avant que l'appelant ne reçoive l'exception. Vous rétrogradez puis rétrogradez.
R. Martinho Fernandes
7
Je vois cela tout le temps où les gens interceptent et rejettent les exceptions simplement parce qu'on leur apprend qu'ils doivent intercepter toutes les exceptions, sans se rendre compte qu'il sera intercepté plus haut dans la pile des appels. Cela me rend fou.
James Westgate
5
@Kyralessa, le plus gros cas est celui de la journalisation. Enregistrez l'erreur dans catch, and rethrow ..
nawfal
194

La fenêtre de surveillance de Heisenberg

Cela peut vous mordre gravement si vous effectuez des tâches de 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;
  }
}

Supposons maintenant que vous ayez du code ailleurs en utilisant ceci:

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

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 de CreateMyObj()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 _myObjest é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 de MyObjpour 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 ...

Décorez votre propriété avec [DebuggerBrowsable (DebuggerBrowsableState.Never)] ou [DebuggerDisplay ("<chargé à la demande>")]. - Christian Hayter

Shaul Behr
la source
10
trouvaille géniale! vous n'êtes pas programmeur, vous êtes un vrai débogueur.
ça. __curious_geek
26
J'ai rencontré cela même en survolant la variable, pas seulement la fenêtre de surveillance.
Richard Morgan
31
Décorez votre propriété avec [DebuggerBrowsable(DebuggerBrowsableState.Never)]ou [DebuggerDisplay("<loaded on demand>")].
Christian Hayter
4
Si vous développez une classe d'infrastructure et souhaitez des fonctionnalités de fenêtre de surveillance sans modifier le comportement d'exécution d'une propriété construite paresseusement, vous pouvez utiliser un proxy de type débogueur pour renvoyer la valeur si elle a déjà été construite et un message indiquant que la propriété n'a pas été construit si tel est le cas. La Lazy<T>classe (en particulier pour sa Valuepropriété) est un exemple d'utilisation.
Sam Harwell
4
Je me souviens de quelqu'un qui (pour une raison que je ne peux pas comprendre) a changé la valeur de l'objet dans une surcharge de ToString. Chaque fois qu'il la survolait, l'infobulle lui donnait une valeur différente - il n'arrivait pas à le comprendre ...
JNF
144

Voici une autre fois qui me fait:

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

Jon B
la source
1
Ouais, celui-là m'a aussi. Je pense que cela devrait être TimeSpan.SecondsPart ou quelque chose pour rendre plus clair ce qu'il représente.
Dan Diplo
3
En relisant ceci, je dois me demander pourquoi TimeSpan même a une Secondsproprié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.
MusiGenesis
2
Il me semble logique que TimeSpan.TotalSeconds renvoie ... le nombre total de secondes dans l'intervalle de temps.
Ed S.
16
@MusiGenesis la propriété est utile. Que se passe-t-il si je souhaite afficher la durée divisée en morceaux? Par exemple, supposons que votre Timespan représente une durée de «3 heures 15 minutes 10 secondes». Comment pouvez-vous accéder à ces informations sans les propriétés Secondes, Heures, Minutes?
SolutionYogi
1
Dans des API similaires, j'ai utilisé SecondsPartet SecondsTotalpour distinguer les deux.
BlueRaja - Danny Pflughoeft
80

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

timer.Tick -= TimerTickEventHandler;

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.

Timothy Walters
la source
1
L'astuce consiste à toujours libérer tous les abonnements aux événements que vous créez. Si vous commencez à compter sur les formulaires pour le faire pour vous, vous pouvez être sûr que vous prendrez l'habitude et un jour oublierez de publier un événement quelque part où cela doit être fait.
Jason Williams
3
Il y a une suggestion MS-connect pour des événements de référence faibles ici qui résoudrait ce problème, bien qu'à mon avis, nous devrions simplement remplacer entièrement le modèle d'événements incroyablement pauvre par un modèle d'événements faiblement couplé, comme celui utilisé par CAB.
BlueRaja - Danny Pflughoeft
+1 de moi, merci! Eh bien, non merci pour le travail de révision du code que j'ai dû faire!
Bob Denny
@ BlueRaja-DannyPflughoeft Avec des événements faibles, vous avez un autre gotcha - vous ne pouvez pas vous abonner lambdas. Vous ne pouvez pas écriretimer.Tick += (s, e,) => { Console.WriteLine(s); }
Ark-kun
@ Ark-kun oui, les lambdas rendent les choses encore plus difficiles, vous devez enregistrer votre lambda dans une variable et l'utiliser dans votre code de démontage. Détruit un peu la simplicité d'écrire des lambdas, n'est-ce pas?
Timothy Walters
63

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:

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

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

using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read))
{
    image = System.Drawing.Image.FromStream(fs);
}
jdehaan
la source
10
Je suis d'accord que ce comportement n'a aucun sens. Je ne trouve aucune explication autre que "ce comportement est voulu par la conception".
MusiGenesis
1
Oh et ce qui est génial avec cette solution de contournement, c'est que si vous essayez d'appeler Image.ToStream (j'oublie le nom exact), cela ne fonctionnera pas.
Joshua
55
besoin de vérifier un code. Brb.
Esben Skov Pedersen
7
@EsbenSkovPedersen Un commentaire simple mais drôle et sec. J'ai fait ma journée.
Inisheer
51

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

Erik van Brakel
la source
26
C'est pourquoi je suis passé à MVC ... Viewstate Headaches ...
chakrit
29
Il y avait une toute autre question consacrée spécifiquement aux accrochages ASP.NET (à juste titre). Le concept de base d'ASP.NET (faire en sorte que les applications Web ressemblent à des applications Windows pour le développeur) est si horriblement erroné que je ne suis même pas sûr qu'il compte même comme un "piège".
MusiGenesis
1
MusiGenesis J'aimerais pouvoir voter jusqu'à cent fois votre commentaire.
csauve
3
@MusiGenesis Cela semble erroné maintenant, mais à l'époque, les gens voulaient que leurs applications Web (applications étant le mot clé - ASP.NET WebForms n'était pas vraiment conçu pour héberger un blog) pour se comporter de la même manière que leurs applications Windows. Cela n'a changé que relativement récemment et beaucoup de gens "ne sont pas encore là". Tout le problème était que l'abstraction était beaucoup trop fuyante - le web ne se comportait pas tellement comme une application de bureau qu'il entraînait la confusion chez presque tout le monde.
Luaan
1
Ironiquement, la première chose que j'ai vue sur ASP.NET a été une vidéo de Microsoft montrant comment vous pouvez facilement créer un site de blog en utilisant ASP.NET!
MusiGenesis
51

opérateurs == surchargés et conteneurs non typés (listes, ensembles de données, etc.):

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

Solutions?

  • toujours utiliser string.Equals(a, b) lorsque vous comparez des types de chaîne

  • en utilisant des génériques comme List<string>pour garantir que les deux opérandes sont des chaînes.

Jimmy
la source
6
Vous avez des espaces supplémentaires qui rendent tout faux - mais si vous supprimez les espaces, la dernière ligne sera toujours vraie car "ma" + "chaîne" est toujours une constante.
Jon Skeet
1
ack! tu as raison :) ok, j'ai un peu édité.
Jimmy
un avertissement est généré sur ces utilisations.
chakrit
11
Oui, l'un des plus gros défauts du langage C # est l'opérateur == dans la classe Object. Ils auraient dû nous forcer à utiliser ReferenceEquals.
erikkallen
2
Heureusement, depuis 2.0, nous avons eu des génériques. Il y a moins à s'inquiéter si vous utilisez List <string> dans l'exemple ci-dessus au lieu de ArrayList. De plus, nous en avons gagné en performances, yay! J'élimine toujours les anciennes références aux listes de tableaux dans notre code hérité.
JoelC
48
[Serializable]
class Hello
{
    readonly object accountsLock = new object();
}

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

Morale de l'histoire: les initialiseurs de champ ne sont pas exécutés lors de la désérialisation d'un objet

Nicolas Dorier
la source
8
Oui, je déteste la sérialisation .NET pour ne pas avoir exécuté le constructeur par défaut. J'aimerais qu'il soit impossible de construire un objet sans appeler de constructeurs, mais hélas ce n'est pas le cas.
Roman Starkov
45

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

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

Oups!

romkyns
la source
19
Modifié mm en MM - mm correspond aux minutes et MM correspond aux mois. Un autre piège, je suppose ...
Kobi
1
Je pourrais voir comment ce serait un piège si vous ne le saviez pas (je ne le savais pas) ... mais j'essaie de comprendre quand vous voudriez le comportement où vous essayez spécifiquement d'imprimer une date qui ne correspond pas à vos paramètres régionaux.
Beska
6
@Beska: Parce que vous écrivez dans un fichier, cela doit être dans un format spécifique, avec un format de date spécifié.
GvS
11
Je suis d'avis que les valeurs par défaut localisées sont pires que l'inverse. Au moins le développeur a complètement ignoré la localisation, le code fonctionne sur des machines localisées différemment. De cette façon, le code ne fonctionne probablement pas.
Joshua
32
En fait, je pense que la bonne façon de procéder seraitDateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
BlueRaja - Danny Pflughoeft
44

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

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

Comme cela renverra 0 et non 1 comme la plupart s'y attendraient

Vendeurs Mitchel
la source
37
J'espère que cela ne mordra pas les gens - j'espère vraiment qu'ils ne l'écriront pas en premier lieu! (C'est intéressant de toute façon, bien sûr.)
Jon Skeet
12
Je ne pense pas que ce soit très obscur ...
Chris Marasti-Georg
10
Au moins, en C #, les résultats sont définis, s'ils sont inattendus. En C ++, ce pourrait être 0 ou 1, ou tout autre résultat incluant l'arrêt du programme!
James Curran
7
Ce n'est pas un piège; x = x ++ -> x = x, puis incrémenter x .... x = ++ x -> incrémenter x puis x = x
Kevin
28
@Kevin: Je ne pense pas que ce soit aussi simple que cela. Si x = x ++ était équivalent à x = x suivi de x ++, le résultat serait x = 1. Au lieu de cela, je pense que ce qui se passe est d'abord l'expression à droite du signe égal est évaluée (donnant 0), puis x est incrémenté (donnant x = 1), et enfin l'affectation est effectuée (donnant x = 0 encore une fois).
Tim Goodman
39

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:

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

vous donnera une sortie de (par exemple):

0
0
0
0
0
0
0
156254
156254
156254

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:

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

Vous donne cette sortie:

C:\MyFolder\MySubFolder\log\
\log\
C:\MyFolder\MySubFolder\log\
\log\
Damovisa
la source
17
La quantification des temps dans des intervalles de ~ 15 ms n'est pas due à un manque de précision dans le mécanisme de synchronisation sous-jacent (j'ai négligé de développer cela plus tôt). C'est parce que votre application s'exécute dans un système d'exploitation multitâche. Windows se connecte à votre application toutes les 15 ms environ, et pendant la petite tranche de temps qu'elle obtient, votre application traite tous les messages qui ont été mis en file d'attente depuis votre dernière tranche. Tous vos appels au sein de cette tranche retournent exactement au même moment car ils sont tous passés effectivement au même moment.
MusiGenesis
2
@MusiGenesis: Je sais (maintenant) comment cela fonctionne, mais il me semble trompeur d'avoir une mesure aussi précise qui n'est pas vraiment aussi précise. C'est comme dire que je connais ma taille en nanomètres alors qu'en fait je ne fais que l'arrondir au dix million près.
Damovisa
7
DateTime est tout à fait capable de stocker jusqu'à un seul tick; c'est DateTime, qui n'utilise pas cette précision.
Ruben
16
Le '\' supplémentaire est un piège pour de nombreuses personnes unix / mac / linux. Dans Windows, s'il y a un '\', c'est que nous voulons aller à la racine du lecteur (c'est-à-dire C :) essayez-le dans une CDcommande pour voir ce que je veux dire ... 1) Goto C:\Windows\System322) Type CD \Users3) Woah! Maintenant, vous êtes C:\Users... OBTENU? ... Path.Combine (@ "C: \ Windows \ System32", @ "\ Users") devrait \Users[current_drive_here]:\Users
renvoyer
8
Même sans le «sommeil», cela fonctionne de la même manière. Cela n'a rien à voir avec la programmation de l'application toutes les 15 ms. La fonction native appelée par DateTime.UtcNow, GetSystemTimeAsFileTime, semble avoir une mauvaise résolution.
Jimbo
38

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.

user25306
la source
3
La même chose peut toujours se produire lorsque vous redirigez à la fois stdout et stderr et utilisez deux appels ReadToEnd en séquence. Pour une manipulation sûre de stdout et stderr, vous devez créer un thread de lecture pour chacun d'eux.
Sebastiaan M
34

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!

Shaul Behr
la source
8
TIL. BRB, ré-optimise quelques centaines de requêtes LINQ ...
tsilb
30

Utilisation de paramètres par défaut avec des méthodes virtuelles

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

Sortie:
base dérivée

BlueRaja - Danny Pflughoeft
la source
10
Bizarre, je pensais que c'était complètement évident. Si le type déclaré est Base, d'où le compilateur devrait-il obtenir la valeur par défaut sinon Base? 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.
Timwi
1
pourquoi une implémentation d'une méthode obtiendrait-elle la valeur par défaut d'une autre implémentation?
staafl
1
@staafl Les arguments par défaut sont résolus au moment de la compilation et non à l'exécution.
fredoverflow
1
Je dirais que ce piège est les paramètres par défaut en général - les gens ne se rendent souvent pas compte qu'ils sont résolus au moment de la compilation plutôt qu'au moment de l'exécution.
Luaan
4
@FredOverflow, ma question était conceptuelle. Bien que le comportement soit logique par rapport à l'implémentation, il n'est pas intuitif et est probablement une source d'erreurs. À mon humble avis, le compilateur C # ne devrait pas permettre de modifier les valeurs des paramètres par défaut lors de la substitution.
staafl
27

Objets de valeur dans des collections mutables

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

mypoints[i].x = 10;

n'a aucun effet.

mypoints[i]renvoie une copie d'un Pointobjet 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:

Cannot modify the return value of 'System.Collections.Generic.List<Foo>.this[int]' because it is not a variable
Bjarke Ebert
la source
6
Je peux voir pourquoi cela prête à confusion, étant donné que cela fonctionne en effet avec des tableaux (contrairement à votre réponse), mais pas avec d'autres collections dynamiques, comme List <Point>.
Lasse V. Karlsen
2
Tu as raison. Merci. J'ai corrigé ma réponse :). 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?
Bjarke Ebert
1
@Bjarke Ebert: Dans certains cas, cela aurait du sens, mais malheureusement, le compilateur n'a aucun moyen de les identifier et de les autoriser. Exemple de scénario d'utilisation: une structure immuable qui contient une référence à un tableau carré à deux dimensions avec un indicateur "rotation / retournement". La structure elle-même serait immuable, donc l'écriture dans un élément d'une instance en lecture seule devrait être correcte, mais le compilateur ne saura pas que le setter de propriétés n'écrira pas réellement la structure, et ne le permettra donc pas. .
supercat
25

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 Angleclasse à la place ...

BlueRaja - Danny Pflughoeft
la source
Je suis surpris que cela ait eu autant de votes positifs, étant donné que mes autres problèmes sont bien pires que cela
BlueRaja - Danny Pflughoeft
22

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

    class A
    {
    public:
        int i;
    };

est fonctionnellement équivalent à cette définition de structure.

    struct A
    {
        int i;
    };

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.

Matt Davis
la source
13
Donc, le pire problème, c'est que les gens ne prennent pas la peine de prendre le temps d'apprendre la langue avant de l'utiliser?
BlueRaja - Danny Pflughoeft
3
@ BlueRaja-DannyPflughoeft Plus comme le gotcha classique de langues apparemment similaires - ils utilisent des mots clés très similaires et dans de nombreux cas la syntaxe, mais fonctionnent de manière très différente.
Luaan
19

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.

Jeff Kotula
la source
2
Le bloc using () résout parfaitement ce problème. Chaque fois que vous voyez un appel à Dispose, vous pouvez immédiatement refactoriser en toute sécurité l'utilisation de ().
Jeremy Frey
5
Je pense que la préoccupation implémentait correctement IDisposable.
Mark Brackett,
4
D'un autre côté, l'habitude using () peut vous mordre de façon inattendue, comme lorsque vous travaillez avec PInvoke. Vous ne voulez pas supprimer un élément auquel l'API fait toujours référence.
MusiGenesis
3
La mise en œuvre d'IDisposable correctement est très difficile à comprendre et même les meilleurs conseils que j'ai trouvés à ce sujet (directives du .NET Framework) peuvent être déroutants à appliquer jusqu'à ce que vous les obteniez enfin.
Quibblesome
1
Le meilleur conseil que j'ai jamais trouvé sur IDisposable vient de Stephen Cleary, y compris trois règles simples et un article détaillé sur IDisposable
Roman Starkov
19

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:

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

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.

Stefan Steinegger
la source
8
À mon humble avis, le problème est que Microsoft n'a pas suffisamment d'interfaces définies pour les collections. À mon humble avis, il devrait avoir iEnumerable, iMultipassEnumerable (prend en charge la réinitialisation et garantit que plusieurs passes correspondront), iLiveEnumerable (aurait une sémantique partiellement définie si la collection change pendant l'énumération - les modifications peuvent ou non apparaître dans l'énumération, mais ne doivent pas provoquer résultats erronés ou exceptions), iReadIndexable, iReadWriteIndexable, etc. Parce que les interfaces peuvent "hériter" d'autres interfaces, cela n'aurait pas ajouté beaucoup de travail supplémentaire, le cas échéant (cela sauverait les stubs NotImplemented).
supercat
@supercat, ce serait déroutant pour les débutants et certains codeurs de longue date. Je pense que les collections .NET et leurs interfaces sont merveilleusement élégantes. Mais j'apprécie votre humilité. ;)
Jordan
@Jordan: Depuis la rédaction de ce qui précède, j'ai décidé qu'une meilleure approche aurait été d'avoir les deux IEnumerable<T>et de IEnumerator<T>prendre en charge une Featuresproprié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 un IEnumerable<T>aura besoin de promesses plus fortes que celles IEnumerable<T>fournies. Appeler ToListdonnerait un IEnumerable<T>tel qui tient ces promesses, mais serait dans de nombreux cas inutilement cher. Je pense qu'il devrait y avoir ...
supercat
... un moyen par lequel le code recevant un IEnumerable<T>pourrait faire une copie du contenu si nécessaire mais pourrait s'abstenir de le faire inutilement.
supercat
Votre option n'est absolument pas lisible. Lorsque je vois une IList dans le code, je sais avec quoi je travaille plutôt que d'avoir à sonder une propriété Features. Les programmeurs aiment oublier qu'une caractéristique importante du code est qu'il peut être lu par des personnes et pas seulement par des ordinateurs. L'espace de noms des collections .NET n'est pas idéal mais il est bon, et parfois trouver la meilleure solution n'est pas une question d'adapter un principe plus idéalement. Certains des pires codes avec lesquels j'ai travaillé sont des codes qui essayaient de s'adapter parfaitement à DRY. Je l'ai mis au rebut et réécrit. C'était juste du mauvais code. Je ne voudrais pas du tout utiliser votre framework.
Jordan
18

foreach boucle l'étendue des variables!

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

imprime cinq "amet", tandis que l'exemple suivant fonctionne très bien

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());
Brian J Cardiff
la source
11
Ceci est essentiellement équivalent à l'exemple de Jon avec des méthodes anonymes.
Mehrdad Afshari
3
Sauf que c'est encore plus déroutant avec foreach où la variable "s" est plus facile à mélanger avec une variable de portée. Avec des boucles for communes, la variable d'index est clairement la même pour chaque itération.
Mikko Rantanen
2
blogs.msdn.com/ericlippert/archive/2009/11/12/… et oui, j'aimerais que la variable soit définie "correctement".
Roman Starkov
2
Cela a été corrigé dans C # 5 .
Johnbot
Vous imprimez essentiellement la même variable encore et encore sans la modifier.
Jordan
18

MS SQL Server ne peut pas gérer les dates antérieures à 1753. Significativement, cela n'est pas synchronisé avec la DateTime.MinDateconstante .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.

Shaul
la source
17
Franchement, je pense que MS SQL Server a ce droit et .Net est faux. Si vous faites des recherches, vous savez que les dates antérieures à 1751 deviennent bizarres en raison de changements de calendrier, de jours complètement ignorés, etc. La plupart des RDBM ont un certain point de coupure. Cela devrait vous donner un point de départ: ancestry.com/learn/library/article.aspx?article=3358
NotMe
11
De plus, la date est 1753 .. Ce qui était à peu près la première fois que nous avons un calendrier continu sans que les dates soient sautées. SQL 2008 a introduit le type de données Date et datetime2 qui peut accepter les dates du 1/1/01 au 31/12/9999. Cependant, les comparaisons de dates utilisant ces types doivent être considérées avec suspicion si vous comparez vraiment des dates antérieures à 1753.
NotMe
Oh, c'est vrai, 1753, corrigé, merci.
Shaul Behr,
Est-il vraiment judicieux de faire des comparaisons de dates avec de telles dates? Je veux dire, pour History Channel, cela a beaucoup de sens, mais je ne me vois pas vouloir connaître le jour précis de la semaine où l'Amérique a été découverte.
Camilo Martin
5
Grâce à Wikipedia sur Julian Day, vous pouvez trouver un programme de base de 13 lignes CALJD.BAS publié en 1984 qui peut faire des calculs de date à environ 5000 avant JC, en tenant compte des jours bissextiles et des jours sautés en 1753. Je ne vois donc pas pourquoi "moderne" "des systèmes comme SQL2008 devraient faire pire. Vous pourriez ne pas être intéressé par une représentation correcte de la date au 15ème siècle, mais d'autres le pourraient, et notre logiciel devrait gérer cela sans bugs. Un autre problème est les secondes intercalaires. . .
Roland
18

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

Shaul Behr
la source
Iv vient de passer une journée et demie (et plein de cheveux!) À traquer ce BUG ...
Surgical Coder
C'est ce qu'on appelle un conflit de concurrence et c'est toujours un problème aujourd'hui même s'il existe certains moyens de contourner ce problème, bien qu'ils aient tendance à être un peu lourds. DataContext était un cauchemar. O_o
Jordan
17

Le contrat sur Stream.Read est quelque chose que j'ai vu beaucoup de gens:

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

La raison pour laquelle c'est faux est que vous Stream.Readlirez 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.Writece 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:

    /// <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;
    }
Roman Starkov
la source
1
Ou, dans votre exemple explicite: var r = new BinaryReader(stream); ulong data = r.ReadUInt64();. BinaryReader a aussi une FillBufferméthode ...
jimbobmcgee
15

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

Stefan Steinegger
la source
1
En outre, le multithreading est douloureux. Tous ces problèmes, mais la chose nulle, sont corrigés dans CAB (dont les fonctionnalités devraient vraiment être intégrées au langage) - les événements sont déclarés globalement, et toute méthode peut se déclarer "abonné" à tout événement. Mon seul problème avec CAB est que les noms d'événements globaux sont des chaînes plutôt que des énumérations (qui pourraient être fixées par des énumérations plus intelligentes, comme Java, qui fonctionnent intrinsèquement comme des chaînes!) . Le CAB est difficile à configurer, mais il existe un simple clone open-source disponible ici .
BlueRaja - Danny Pflughoeft
3
Je n'aime pas la mise en œuvre des événements .net. L'abonnement à un événement doit être géré en appelant une méthode qui ajoute l'abonnement et renvoie un IDisposable qui, lorsqu'il sera supprimé, supprimera l'abonnement. Il n'y a pas besoin d'une construction spéciale combinant une méthode "add" et "remove" dont la sémantique peut être quelque peu douteuse, surtout si l'on tente d'ajouter puis de supprimer un délégué de multidiffusion (par exemple, ajouter "B" suivi de "AB", puis supprimer "B" (quittant "BA") et "AB" (quittant toujours "BA"). Oups.
supercat
@supercat Comment réécririez-vous button.Click += (s, e) => { Console.WriteLine(s); }?
Ark-kun
Si je devais pouvoir me désinscrire séparément des autres événements, IEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);});et me désinscrire via clickSubscription.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);}));puis MySubscriptions.Dispose()tuer tous les abonnements.
supercat
@ Ark-kun: Devoir garder des objets qui encapsulent des abonnements extérieurs peut sembler une nuisance, mais en considérant les abonnements comme des entités, il serait possible de les agréger avec un type qui peut garantir qu'ils seront tous nettoyés, ce qui est par ailleurs très difficile.
supercat
14

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.

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

Cela imprime 5 10 5

Pratik
la source
5
vous pouvez avoir une classe de base non générique, qui définit la statique et en hérite les génériques. Bien que je n'aie jamais craqué pour ce comportement en C # - je me souviens encore des longues heures de débogage de certains modèles C ++ ... Eww! :)
Paulius
7
Bizarre, je pensais que c'était évident. Pensez simplement à ce qu'il devrait faire s'il iavait le type T.
Timwi
1
Le paramètre type fait partie du Type. SomeGeneric<int>est un type différent de SomeGeneric<string>; alors bien sûr, chacun a son proprepublic static int i
radarbob
13

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:

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

Imaginez ma surprise quand File.Delete(file)jette FileNotFound!!

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 nouveau Path.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()ou ToList():

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

C'est encore plus effrayant lorsque vous faites quelque chose de multi-thread, comme:

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

et vous découvrez content.Lengthest 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 ....

chakrit
la source
C'est par conception. C'est ce qu'on appelle une exécution différée. Entre autres, il est destiné à simuler des constructions TSQL. Chaque fois que vous sélectionnez dans une vue SQL, vous obtenez des résultats différents. Il permet également le chaînage, ce qui est utile pour les magasins de données distants, tels que SQL Server. Sinon, x.Select.Where.OrderBy enverrait 3 commandes distinctes à la base de données ...
as9876
@AYS avez-vous manqué le mot "Gotcha" dans le titre de la question?
chakrit
Je pensais que gotcha signifiait une surveillance des concepteurs, pas quelque chose d'intentionnel.
as9876
Il devrait peut-être y avoir un autre type pour les IEnumerables non redémarrables. Comme, AutoBufferedEnumerable? On pourrait l'implémenter facilement. Ce problème semble principalement dû au manque de connaissances du programmeur, je ne pense pas qu'il y ait quelque chose de mal avec le comportement actuel.
Eldritch Conundrum
13

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.

int? i = null;
i++; // I would have expected an exception but runs fine and stays as null
DevDave
la source
C'est le résultat de la façon dont C # exploite les opérations pour les types nullables. C'est un peu similaire à NaN consommant tout ce que vous lui lancez.
IllidanS4 veut que Monica revienne le
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!"

Oui, ce comportement est documenté, mais cela ne le rend certainement pas correct.

BlueRaja - Danny Pflughoeft
la source
5
Je ne suis pas d'accord - quand un mot est en majuscules, il peut avoir une signification particulière que vous ne voulez pas vous tromper avec le titre, par exemple "président des États-Unis" -> "président des États-Unis", pas "président des Etats-Unis".
Shaul Behr
5
@Shaul: Dans ce cas, ils devraient spécifier cela comme paramètre pour éviter toute confusion, car je n'ai jamais rencontré quelqu'un qui s'attendait à ce comportement à l'avance - ce qui en fait un piège !
BlueRaja - Danny Pflughoeft