Qual è il peggior gotcha in C # o .NET? [chiuso]


377

Recentemente stavo lavorando con un DateTimeoggetto e ho scritto qualcosa del genere:

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

La documentazione di intellisense per AddDays()dice che aggiunge un giorno alla data, che non lo fa - in realtà restituisce una data con un giorno aggiunto ad essa, quindi devi scriverla come:

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

Questo mi ha morso un numero di volte prima, quindi ho pensato che sarebbe stato utile catalogare i peggiori C # gotchas.


157
return DateTime.Now.AddDays (1);
crashmstr,

23
AFAIK, i tipi di valore incorporati sono tutti immutabili, almeno in quanto qualsiasi metodo incluso con il tipo restituisce un nuovo elemento anziché modificare l'elemento esistente. Almeno, non riesco a pensare a uno fuori dalla testa che non lo fa: tutto bello e coerente.
Joel Coehoorn,

6
Tipo di valore mutabile: System.Collections.Generics.List.Enumerator :( (E sì, puoi vederlo comportarsi in modo strano se ci provi abbastanza.)
Jon Skeet,

13
L'intellisense ti dà tutte le informazioni di cui hai bisogno. Dice che restituisce un oggetto DateTime. Se solo alterasse quello che hai passato, sarebbe un metodo nullo.
John Kraft,

20
Non necessariamente: StringBuilder.Append (...) restituisce "questo" per esempio. È abbastanza comune nelle interfacce fluenti.
Jon Skeet,

Risposte:


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

Blammo. L'app si arresta in modo anomalo senza traccia dello stack. Succede tutte le volte.

(Nota maiuscola MyVaranziché minuscolamyVar nel getter.)


112
e COSÌ appropriato per questo sito :)
gbjbaanb,

62
Ho messo il trattino basso sul membro privato, aiuta molto!
Chakrit,

61
Uso le proprietà automatiche dove posso, ferma molto questo tipo di problema;)
TWith2Sugars

28
Questo è un ottimo motivo per usare i prefissi per i tuoi campi privati ​​(ce ne sono altri, ma questo è un buon motivo): _myVar, m_myVar
jrista

205
@jrista: O per favore NO ... non m_ ... aargh the horror ...
fretje,

254

Type.GetType

Quello che ho visto mordere molte persone è Type.GetType(string). Si chiedono perché funzioni per i tipi nel proprio assemblaggio e alcuni tipi come System.String, ma non System.Windows.Forms.Form. La risposta è che appare solo nell'assieme corrente e dentro mscorlib.


Metodi anonimi

C # 2.0 ha introdotto metodi anonimi, portando a brutte situazioni come questa:

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

Cosa verrà stampato? Bene, dipende interamente dalla programmazione. Stampa 10 numeri, ma probabilmente non stampa 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, che è quello che ti aspetteresti. Il problema è che iè stata acquisita la variabile, non il suo valore nel punto in cui è stato creato il delegato. Questo può essere risolto facilmente con una variabile locale aggiuntiva del giusto ambito:

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

Esecuzione differita di blocchi iteratori

Questo "test unitario dei poveri" non passa - perché no?

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 risposta è che il codice all'interno della sorgente del CapitalLetterscodice non viene eseguito fino all'iteratoreMoveNext() viene chiamato per la prima volta metodo .

Ho altre stranezze nella pagina dei miei rompicapo .


25
L'esempio dell'iteratore è subdolo!
Jimmy,

8
perché non dividere questo in 3 risposte in modo che possiamo votare ciascuno invece di tutti insieme?
chakrit,

13
@chakrit: A posteriori, probabilmente sarebbe stata una buona idea, ma penso che sia troppo tardi ora. Potrebbe anche sembrare che stavo solo cercando di ottenere più rappresentante ...
Jon Skeet,

19
In realtà Type.GetType funziona se si fornisce AssemblyQualifiedName. Type.GetType ("System.ServiceModel.EndpointNotFoundException, System.ServiceModel, Version = 3.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089");
Chilltemp,

2
@kentaromiura: la risoluzione del sovraccarico inizia dal tipo più derivato e funziona sull'albero, ma solo guardando i metodi originariamente dichiarati nel tipo che sta guardando. Foo (int) ignora il metodo di base, quindi non viene considerato. Foo (oggetto) è applicabile, quindi la risoluzione di sovraccarico si ferma qui. Strano, lo so.
Jon Skeet,

194

Ri-lanciare eccezioni

Un gotcha che ottiene molti nuovi sviluppatori, è la semantica dell'eccezione del re-lancio.

Un sacco di tempo vedo codice come il seguente

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

Il problema è che cancella la traccia dello stack e rende molto più difficile la diagnosi dei problemi, poiché non è possibile tenere traccia dell'origine dell'eccezione.

Il codice corretto è l'istruzione throw senza argomenti:

catch(Exception)
{
    throw;
}

O racchiudendo l'eccezione in un'altra e usando l'eccezione interna per ottenere la traccia dello stack originale:

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

Per fortuna, mi è stato insegnato su questo nella mia prima settimana da qualcuno e lo trovo nel codice degli sviluppatori più anziani. È: catch () {throw; } Lo stesso del secondo frammento di codice? catch (Exception e) {lancio; } solo non crea un oggetto Exception e lo popola?
StuperUser

Oltre all'errore di usare il tiro ex (o il tiro e) invece di lanciare, devo chiedermi quali casi ci sono quando vale la pena catturare un'eccezione solo per lanciarlo di nuovo.
Ryan Lundy,

13
@Kyralessa: ci sono molti casi: ad esempio, se si desidera eseguire il rollback di una transazione, prima che il chiamante ottenga l'eccezione. Esegui il rollback e quindi ripeti.
R. Martinho Fernandes,

7
Lo vedo sempre in cui le persone catturano e ricodificano le eccezioni solo perché viene loro insegnato che devono catturare tutte le eccezioni, senza rendersi conto che verrà catturato più in alto nello stack di chiamate. Mi fa impazzire.
James Westgate,

5
@Kyralessa il caso più grande è quando devi fare il log. Registra l'errore in catch e riprova ...
nawfal

194

La finestra di Heisenberg

Questo può morderti male se stai facendo cose load-on-demand, come questo:

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

Ora supponiamo che tu abbia del codice altrove usando questo:

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

Ora vuoi eseguire il debug del tuo CreateMyObj()metodo. Quindi metti un punto di interruzione sulla riga 3 sopra, con l'intenzione di entrare nel codice. Solo per una buona misura, hai anche messo un breakpoint sulla linea sopra che dice _myObj = CreateMyObj();, e persino un breakpoint dentro di CreateMyObj()sé.

Il codice raggiunge il punto di interruzione sulla riga 3. Si entra nel codice. Ti aspetti di inserire il codice condizionale, perché _myObjovviamente è nullo, giusto? Uh ... quindi ... perché ha saltato la condizione ed è andato dritto a return _myObj?! Passa il mouse sopra _myObj ... e in effetti ha un valore! Come è successo?!

La risposta è che il tuo IDE gli ha fatto ottenere un valore, perché hai una finestra "watch" aperta, in particolare la finestra "Autos", che mostra i valori di tutte le variabili / proprietà rilevanti per la linea di esecuzione corrente o precedente. Quando raggiungi il tuo punto di interruzione sulla Linea 3, la finestra dell'orologio ha deciso che ti sarebbe interessato conoscere il valore di MyObj- quindi dietro le quinte, ignorando qualsiasi tuo punto di interruzione , è andato e ha calcolato il valore MyObjper te - inclusa la chiamata a CreateMyObj()quella imposta il valore di _myObj!

Ecco perché la chiamo Heisenberg Watch Window: non puoi osservare il valore senza influenzarlo ... :)

GOTCHA!


modificare - Sento che il commento di @ ChristianHayter merita l'inclusione nella risposta principale, perché sembra una soluzione efficace per questo problema. Quindi ogni volta che hai una proprietà pigra ...

Decorare la proprietà con [DebuggerBrowsable (DebuggerBrowsableState.Never)] o [DebuggerDisplay ("<caricato su richiesta>")]. - Christian Hayter


10
brillante scoperta! non sei un programmatore, sei un vero debugger.
questo. __curious_geek,

26
Mi sono imbattuto in questo anche passando sopra la variabile, non solo la finestra di controllo.
Richard Morgan,

31
Decora la tua proprietà con [DebuggerBrowsable(DebuggerBrowsableState.Never)]o [DebuggerDisplay("<loaded on demand>")].
Christian Hayter,

4
Se si sta sviluppando una classe framework e si desidera la funzionalità watch window senza alterare il comportamento di runtime di una proprietà costruita pigramente, è possibile utilizzare un proxy di tipo debugger per restituire il valore se è già stato costruito e un messaggio che la proprietà non ha stato costruito se questo è il caso. La Lazy<T>classe (in particolare per la sua Valueproprietà) è un esempio di dove viene utilizzata.
Sam Harwell,

4
Ricordo qualcuno che (per qualche motivo non riesco a capire) ha cambiato il valore dell'oggetto in un sovraccarico di ToString. Ogni volta che ci passava sopra, il suggerimento gli dava un valore diverso - non riusciva a capirlo ...
JNF

144

Ecco un'altra volta che mi prende:

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

TimeSpan.Seconds è la porzione di secondi dell'intervallo di tempo (2 minuti e 0 secondi ha un valore di secondi pari a 0).

TimeSpan.TotalSeconds è l'intero intervallo di tempo misurato in secondi (2 minuti ha un valore di secondi totali di 120).


1
Sì, anche quello ha me. Penso che dovrebbe essere TimeSpan.SecondsPart o qualcosa del genere per rendere più chiaro ciò che rappresenta.
Dan Diplo,

3
Nel rileggere questo, mi chiedo perché abbiaTimeSpan persino una proprietà. Chi dà il culo a un topo qual è la seconda parte di un periodo di tempo, comunque? È un valore arbitrario dipendente dall'unità; Non riesco a concepire alcun uso pratico per questo. Seconds
MusiGenesis,

2
Per me ha senso che TimeSpan.TotalSeconds restituirebbe ... il numero totale di secondi nell'intervallo di tempo.
Ed S.

16
@MusiGenesis la proprietà è utile. Cosa succede se desidero visualizzare i tempi suddivisi in pezzi? Ad esempio, supponiamo che il tuo intervallo di tempo rappresenti la durata di "3 ore 15 minuti 10 secondi". Come puoi accedere a queste informazioni senza le proprietà Secondi, Ore, Minuti?
SolutionYogi

1
In API simili, ho usato SecondsParte SecondsTotalper distinguere i due.
BlueRaja - Danny Pflughoeft

80

Perdita di memoria perché non hai sganciato gli eventi.

Questo ha persino catturato alcuni sviluppatori senior che conosco.

Immagina un modulo WPF con molte cose al suo interno e da qualche parte lì ti iscrivi a un evento. Se non si annulla l'iscrizione, l'intero modulo viene mantenuto in memoria dopo essere stato chiuso e de-referenziato.

Credo che il problema che ho riscontrato sia stato la creazione di un DispatchTimer nel modulo WPF e l'iscrizione all'evento Tick, se non si esegue un - = sul timer il modulo perde memoria!

In questo esempio il tuo codice di smontaggio dovrebbe avere

timer.Tick -= TimerTickEventHandler;

Questo è particolarmente complicato da quando hai creato l'istanza di DispatchTimer all'interno del modulo WPF, quindi potresti pensare che sarebbe un riferimento interno gestito dal processo Garbage Collection ... sfortunatamente DispatchTimer utilizza un elenco interno statico di abbonamenti e servizi richieste sul thread dell'interfaccia utente, quindi il riferimento è "posseduto" dalla classe statica.


1
Il trucco è rilasciare sempre tutti gli abbonamenti agli eventi che crei. Se inizi a fare affidamento sul fatto che i moduli lo facciano per te, puoi essere sicuro che ti abituerai e un giorno dimenticherai di rilasciare un evento da qualche parte dove deve essere fatto.
Jason Williams,

3
C'è un suggerimento di MS-Connect per gli eventi di riferimento deboli qui che risolverebbe questo problema, anche se a mio parere dovremmo sostituire completamente il modello di eventi incredibilmente poveri con uno debolmente accoppiati, come quello usato da CAB.
BlueRaja - Danny Pflughoeft,

+1 da parte mia, grazie! Bene, no grazie per il lavoro di revisione del codice che ho dovuto fare!
Bob Denny,

@ BlueRaja-DannyPflughoeft Con eventi deboli hai un altro gotcha - non puoi iscriverti a lambdas. Non puoi scriveretimer.Tick += (s, e,) => { Console.WriteLine(s); }
Ark-kun,

@ Ark-kun sì, i lambda lo rendono ancora più difficile, dovresti salvare il tuo lambda in una variabile e usarlo nel tuo codice di smontaggio. Kinda distrugge la semplicità di scrivere lambdas vero?
Timothy Walters,

63

Forse non è proprio un gotcha perché il comportamento è scritto chiaramente in MSDN, ma una volta mi è rotto il collo perché l'ho trovato piuttosto intuitivo:

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

Questo ragazzo lascia il "nice.pic" file bloccato fino a quando l'immagine non viene eliminata. Al momento l'ho affrontato, anche se sarebbe stato bello caricare le icone al volo e non mi ero reso conto (all'inizio) che avevo finito con decine di file aperti e bloccati! L'immagine tiene traccia di dove aveva caricato il file da ...

Come risolverlo? Ho pensato che una fodera avrebbe fatto il lavoro. Mi aspettavo un parametro aggiuntivo per FromFile(), ma non ne avevo, quindi ho scritto questo ...

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

10
Sono d'accordo che questo comportamento non ha senso. Non riesco a trovare alcuna spiegazione al di fuori di "questo comportamento è di progettazione".
MusiGenesis,

1
Oh, e la cosa grandiosa di questa soluzione alternativa è se provi a chiamare Image.ToStream (dimentico il nome esatto di mano) in seguito non funzionerà.
Joshua,

55
è necessario controllare un po 'di codice. Brb.
Esben Skov Pedersen,

7
@EsbenSkovPedersen Un commento così semplice ma divertente e asciutto. Ha reso la mia giornata.
Inisheer,

51

Se conti ASP.NET, direi che il ciclo di vita dei moduli web è un grosso problema per me. Ho trascorso innumerevoli ore a eseguire il debug di codici di moduli web scritti male, solo perché molti sviluppatori non capiscono davvero quando utilizzare quale gestore di eventi (me incluso, purtroppo).


26
Ecco perché mi sono trasferito a MVC ... mal di testa viewstate ...
chakrit

29
C'era un'intera altra domanda dedicata specificamente ai gotcha ASP.NET (meritatamente). Il concetto di base di ASP.NET (fare in modo che le app Web appaiano come app di Windows per gli sviluppatori) è talmente orribilmente sbagliato che non sono sicuro che valga anche come "gotcha".
MusiGenesis,

1
MusiGenesis Vorrei poter votare il tuo commento cento volte.
csauve,

3
@MusiGenesis Ora sembra fuorviato, ma all'epoca le persone volevano che le loro applicazioni web (le applicazioni fossero la parola chiave - ASP.NET WebForms non fosse realmente progettato per ospitare un blog) si comportassero allo stesso modo delle loro applicazioni Windows. Questo è cambiato solo relativamente di recente e molte persone "non ci sono ancora". L'intero problema era che l'astrazione era troppo trapelata: il web non si comportava come un'applicazione desktop così tanto da creare confusione in quasi tutti.
Luaan,

1
Ironia della sorte, la prima cosa che ho mai visto su ASP.NET è stato un video di Microsoft che dimostra come è possibile creare facilmente un sito di blog utilizzando ASP.NET!
MusiGenesis,

51

sovraccaricato == operatori e contenitori non tipizzati (array, set di dati, ecc.):

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

Soluzioni?

  • utilizzare sempre string.Equals(a, b)quando si confrontano i tipi di stringa

  • usare generici come List<string>per assicurarsi che entrambi gli operandi siano stringhe.


6
Hai degli spazi extra lì dentro che rendono tutto sbagliato - ma se togli gli spazi, l'ultima riga sarà comunque vera poiché "my" + "string" è ancora una costante.
Jon Skeet,

1
ack! hai ragione :) ok, ho modificato un po '.
Jimmy,

viene generato un avviso su tali usi.
chakrit,

11
Sì, uno dei maggiori difetti del linguaggio C # è l'operatore == nella classe Object. Avrebbero dovuto costringerci a usare ReferenceEquals.
erikkallen,

2
Per fortuna, dal 2.0 abbiamo avuto generici. C'è meno di cui preoccuparsi se si utilizza List <string> nell'esempio sopra invece di ArrayList. Inoltre abbiamo ottenuto prestazioni da esso, yay! Eseguo sempre il rooting dei vecchi riferimenti ad ArrayLists nel nostro codice precedente.
JoelC

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

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

Morale della storia: gli inizializzatori di campo non vengono eseguiti durante la deserializzazione di un oggetto


8
Sì, odio la serializzazione .NET per non eseguire il costruttore predefinito. Vorrei che fosse impossibile costruire un oggetto senza chiamare alcun costruttore, ma purtroppo non lo è.
Roman Starkov,

45

DateTime.ToString ("gg / MM / aaaa") ; Questo in realtà non ti darà sempre gg / MM / aaaa, ma prenderà in considerazione le impostazioni regionali e sostituirà il separatore di date a seconda di dove ti trovi. Quindi potresti ottenere gg-MM-aaaa o qualcosa di simile.

Il modo giusto per farlo è usare DateTime.ToString ("dd '/' MM '/' yyyy");


DateTime.ToString ("r") dovrebbe essere convertito in RFC1123, che utilizza GMT. GMT è a una frazione di secondo da UTC, eppure l'identificatore di formato "r" non viene convertito in UTC , anche se DateTime in questione è specificato come Locale.

Ciò si traduce nel seguente gotcha (varia in base alla distanza dell'ora locale da UTC):

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

Ops!


19
Modificato da mm a MM - mm è minuti e MM è mesi. Un altro gotcha, immagino ...
Kobi l'

1
Potrei vedere come questo sarebbe un gotcha se non lo sapessi (non lo sapevo) ... ma sto cercando di capire quando vorresti il ​​comportamento in cui stai specificamente cercando di stampare una data che non corrisponde alle impostazioni internazionali.
Beska,

6
@Beska: poiché stai scrivendo su un file, deve essere in un formato specifico, con un formato data specificato.
GvS

11
Sono dell'opinione che la localizzazione delle impostazioni predefinite sia peggiore del contrario. Almeno lo sviluppatore ha ignorato completamente la localizzazione, il codice funziona su macchine localizzate in modo diverso. In questo modo, il codice probabilmente non funziona.
Joshua,

32
In realtà credo che il modo corretto per farlo sarebbeDateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
BlueRaja - Danny Pflughoeft

44

L'ho visto pubblicato l'altro giorno e penso che sia piuttosto oscuro e doloroso per coloro che non lo sanno

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

Poiché ciò restituirà 0 e non 1 come la maggior parte si aspetterebbe


37
Spero che in realtà non morda le persone - spero davvero che non lo scriverebbero in primo luogo! (È comunque interessante, ovviamente.)
Jon Skeet,

12
Non credo sia molto oscuro ...
Chris Marasti-Georg,

10
Almeno, in C #, i risultati sono definiti, se inaspettati. In C ++, potrebbe essere 0 o 1 o qualsiasi altro risultato inclusa la chiusura del programma!
James Curran,

7
Questo non è un gotcha; x = x ++ -> x = x, quindi incremento x .... x = ++ x -> incremento x quindi x = x
Kevin,

28
@ Kevin: Non penso che sia così semplice. Se x = x ++ fosse equivalente a x = x seguito da x ++, il risultato sarebbe x = 1. Invece, penso che ciò che accade sia prima l'espressione a destra del segno uguale sia valutata (dando 0), quindi x è incrementato (dando x = 1), e infine l'assegnazione viene eseguita (dando di nuovo x = 0).
Tim Goodman,

39

Sono un po 'in ritardo per questa festa, ma ho due gotcha che mi hanno morso di recente:

Risoluzione DateTime

La proprietà Ticks misura il tempo in 10 milionesimi di secondo (100 blocchi di nanosecondi), tuttavia la risoluzione non è di 100 nanosecondi, è di circa 15ms.

Questo codice:

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

ti darà un output di (ad esempio):

0
0
0
0
0
0
0
156254
156254
156254

Allo stesso modo, se guardi DateTime.Now.Millisecond, otterrai valori in blocchi arrotondati di 15,625 ms: 15, 31, 46, ecc.

Questo comportamento particolare varia da sistema a sistema , ma ci sono altri gotcha relativi alla risoluzione in questa API data / ora.


Path.Combine

Un ottimo modo per combinare i percorsi dei file, ma non si comporta sempre come previsto.

Se il secondo parametro inizia con un \carattere, non ti darà un percorso completo:

Questo codice:

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

Ti dà questo risultato:

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

17
La quantizzazione dei tempi a intervalli di ~ 15 ms non è dovuta a una mancanza di precisione nel meccanismo di temporizzazione sottostante (ho trascurato di approfondire prima). È perché l'app è in esecuzione all'interno di un sistema operativo multi-tasking. Windows effettua il check-in con la tua app ogni 15 ms circa e durante il piccolo intervallo di tempo che riceve, l'app elabora tutti i messaggi in coda dall'ultima sezione. Tutte le chiamate all'interno di quella sezione restituiscono esattamente lo stesso momento perché sono state tutte eseguite esattamente nello stesso momento.
MusiGenesis,

2
@MusiGenesis: so (ora) come funziona, ma mi sembra fuorviante avere una misura così precisa che non è poi così precisa. È come dire che conosco la mia altezza in nanometri quando in realtà la sto solo arrotondando ai dieci milioni più vicini.
Damovisa,

7
DateTime è abbastanza in grado di memorizzare fino a un singolo segno di spunta; è DateTime.Now che non utilizza questa precisione.
Ruben,

16
L'ulteriore '\' è un gotcha per molte persone unix / mac / linux. In Windows, se c'è un '\' iniziale, significa che vogliamo andare alla radice dell'unità (cioè C :) provalo in un CDcomando per vedere cosa intendo .... 1) Vai a C:\Windows\System322) Digita CD \Users3) Woah! Ora sei a C:\Users... AVERLO? ... Path.Combine (@ "C: \ Windows \ System32", @ "\ Users") dovrebbe tornare, il \Usersche significa precisamente il[current_drive_here]:\Users
chakrit

8
Anche senza il 'sonno', questo funziona allo stesso modo. Ciò non ha nulla a che fare con l'app pianificata ogni 15 ms. La funzione nativa chiamata da DateTime.UtcNow, GetSystemTimeAsFileTime, sembra avere una risoluzione scadente.
Jimbo,

38

Quando avvii un processo (usando System.Diagnostics) che scrive sulla console, ma non leggi mai la Console. Fuori flusso, dopo una certa quantità di output la tua app sembrerà bloccarsi.


3
Lo stesso può succedere anche quando si reindirizzano sia stdout che stderr e si utilizzano due chiamate ReadToEnd in sequenza. Per una gestione sicura sia di stdout che di stderr devi creare un thread di lettura per ognuno di essi.
Sebastiaan M,

34

Nessuna scorciatoia operatore in Linq-To-Sql

Vedi qui .

In breve, all'interno della clausola condizionale di una query Linq-To-Sql, non è possibile utilizzare scorciatoie condizionali come ||ed &&evitare eccezioni di riferimento null; Linq-To-Sql valuta entrambi i lati dell'operatore OR o AND anche se la prima condizione ovvia alla necessità di valutare la seconda condizione!


8
TIL. BRB, ri-ottimizzazione di alcune centinaia di query LINQ ...
tsilb,

30

Utilizzo dei parametri predefiniti con metodi virtuali

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

Uscita:
base derivata


10
Strano, ho pensato che fosse del tutto ovvio. Se il tipo dichiarato è Base, da dove dovrebbe il compilatore ottenere il valore predefinito se no Base? Avrei pensato che fosse un po ' più gotcha che il valore predefinito può essere diverso se il tipo dichiarato è il tipo derivato , anche se il metodo chiamato (staticamente) è il metodo di base.
Timwi,

1
perché un'implementazione di un metodo dovrebbe ottenere il valore predefinito di un'altra implementazione?
staafl

1
@staafl Gli argomenti predefiniti vengono risolti in fase di compilazione, non in fase di esecuzione.
Fredoverflow,

1
Direi che questo gotcha è i parametri predefiniti in generale - le persone spesso non si rendono conto di essere risolte in fase di compilazione, piuttosto che in fase di esecuzione.
Luaan,

4
@FredOverflow, la mia domanda era concettuale. Sebbene il comportamento abbia senso rispetto all'implementazione, non è intuitivo e può essere una fonte di errori. IMHO il compilatore C # non dovrebbe consentire la modifica dei valori dei parametri predefiniti durante l'override.
staafl

27

Valore oggetti in raccolte mutabili

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

mypoints[i].x = 10;

non ha alcun effetto.

mypoints[i]restituisce una copia di un Pointoggetto valore. C # consente felicemente di modificare un campo della copia. Non fare nulla in silenzio.


Aggiornamento: questo sembra essere corretto in C # 3.0:

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

6
Posso capire perché ciò sia confuso, considerando che funziona davvero con gli array (contrariamente alla tua risposta), ma non con altre raccolte dinamiche, come List <Point>.
Lasse V. Karlsen,

2
Hai ragione. Grazie. Ho corretto la mia risposta :). arr[i].attr=è una sintassi speciale per gli array che non è possibile codificare nei contenitori della libreria; (. Perché (<espressione di valore>). attr = <espr> è assolutamente consentito? Può mai avere senso?
Bjarke Ebert,

1
@Bjarke Ebert: ci sono alcuni casi in cui avrebbe senso, ma sfortunatamente non c'è modo per il compilatore di identificarli e permetterli. Scenario di utilizzo del campione: uno Struct immutabile che contiene un riferimento a una matrice quadrata bidimensionale con un indicatore "ruota / capovolgi". La stessa struttura sarebbe immutabile, quindi scrivere su un elemento di un'istanza di sola lettura dovrebbe andare bene, ma il compilatore non saprà che il setter di proprietà non scriverà effettivamente la struttura e quindi non lo consentirà .
supercat

25

Forse non il peggio, ma alcune parti del framework .net usano gradi mentre altri usano i radianti (e la documentazione che appare con Intellisense non ti dice mai quale, devi visitare MSDN per scoprirlo)

Tutto ciò avrebbe potuto essere evitato con una Angleclasse invece ...


Sono sorpreso che questo abbia ottenuto così tanti voti, considerando che i miei altri gotcha sono significativamente peggiori di questo
BlueRaja - Danny Pflughoeft

22

Per i programmatori C / C ++, la transizione a C # è naturale. Tuttavia, il più grande gotcha che ho incontrato personalmente (e ho visto con gli altri fare la stessa transizione) non è comprendere appieno la differenza tra classi e strutture in C #.

In C ++, le classi e le strutture sono identiche; differiscono solo per la visibilità predefinita, dove le classi passano automaticamente alla visibilità privata e le strutture predefinite per la visibilità pubblica. In C ++, questa definizione di classe

    class A
    {
    public:
        int i;
    };

è funzionalmente equivalente a questa definizione di struttura.

    struct A
    {
        int i;
    };

In C #, tuttavia, le classi sono tipi di riferimento mentre le strutture sono tipi di valore. Questo fa una GRANDE differenza nel (1) decidere quando usare l'uno sull'altro, (2) testare l'uguaglianza degli oggetti, (3) le prestazioni (es. Boxe / unboxing), ecc.

Ci sono tutti i tipi di informazioni sul web relative alle differenze tra i due (ad esempio, qui ). Incoraggerei vivamente chiunque effettui il passaggio a C # per avere almeno una conoscenza pratica delle differenze e delle loro implicazioni.


13
Quindi, la cosa peggiore è che le persone non si preoccupano di dedicare del tempo all'apprendimento della lingua prima di usarla?
BlueRaja - Danny Pflughoeft il

3
@ BlueRaja-DannyPflughoeft Più come il classico gotcha di lingue apparentemente simili - usano parole chiave molto simili e in molti casi sintassi, ma funzionano in modo molto diverso.
Luaan,

19

Garbage collection e Dispose (). Anche se non devi fare nulla per liberare memoria , devi comunque liberare risorse tramite Dispose (). Questa è una cosa immensamente facile da dimenticare quando si utilizza WinForms o si tracciano oggetti in qualsiasi modo.


2
Il blocco using () risolve ordinatamente questo problema. Ogni volta che vedi una chiamata a Dispose, puoi immediatamente e in tutta sicurezza refactoring usare ().
Jeremy Frey,

5
Penso che la preoccupazione stia implementando IDisposable correttamente.
Mark Brackett,

4
D'altra parte, l'abitudine using () può morderti inaspettatamente, come quando lavori con PInvoke. Non vuoi disporre di qualcosa a cui l'API fa ancora riferimento.
MusiGenesis,

3
L'implementazione di IDisposable correttamente è molto difficile da capire e anche il miglior consiglio che ho trovato su questo (Linee guida di .NET Framework) può essere confuso da applicare fino a quando non lo si ottiene.
Quibblesome

1
Il miglior consiglio che abbia mai trovato su IDisposable viene da Stephen Cleary, tra cui tre semplici regole e un approfondito articolo su IDisposable
Roman Starkov,

19

Implementazione di array IList

Ma non implementarlo. Quando chiami Aggiungi, ti dice che non funziona. Quindi perché una classe implementa un'interfaccia quando non può supportarla?

Compila, ma non funziona:

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

Abbiamo questo problema molto, perché il serializzatore (WCF) trasforma tutti gli IList in array e otteniamo errori di runtime.


8
IMHO, il problema è che Microsoft non ha abbastanza interfacce definite per le raccolte. IMHO, dovrebbe avere iEnumerable, iMultipassEnumerable (supporta Reimposta e garantisce la corrispondenza di più passaggi), iLiveEnumerable (avrebbe una semantica parzialmente definita se la raccolta cambia durante l'enumerazione - le modifiche possono o meno apparire nell'enumerazione, ma non dovrebbero causare risultati o eccezioni fasulli), iReadIndexable, iReadWriteIndexable, ecc. Poiché le interfacce possono "ereditare" altre interfacce, questo non avrebbe aggiunto molto lavoro extra, se presente (risparmierebbe gli stub NotImplemented).
supercat

@supercat, sarebbe confuso per i principianti e alcuni programmatori di lunga data. Penso che le raccolte .NET e le loro interfacce siano meravigliosamente eleganti. Ma apprezzo la tua umiltà. ;)
Giordania,

@Jordan: Da quando ho scritto quanto sopra, ho deciso che un approccio migliore sarebbe stato avere IEnumerable<T>e IEnumerator<T>supportare sia una Featuresproprietà che alcuni metodi "opzionali" la cui utilità sarebbe stata determinata da ciò che "Funzionalità" riportava. Tuttavia, sostengo il mio punto principale, ovvero che ci sono casi in cui la ricezione di codice IEnumerable<T>richiede una promessa più forte di quella IEnumerable<T>fornita. Chiamare ToListprodurrebbe un risultato IEnumerable<T>che mantiene tali promesse, ma in molti casi sarebbe inutilmente costoso. Suppongo che ci dovrebbe essere ...
supercat

... un mezzo con cui il codice che riceve un IEnumerable<T>potrebbe fare una copia del contenuto se necessario, ma potrebbe astenersi dal farlo inutilmente.
supercat

La tua opzione non è assolutamente leggibile. Quando vedo un IList nel codice, so con cosa sto lavorando piuttosto che dover esaminare una proprietà Features. Ai programmatori piace dimenticare che una caratteristica importante del codice è che può essere letta da persone non solo da computer. Lo spazio dei nomi delle raccolte .NET non è l'ideale ma è buono, e talvolta trovare la soluzione migliore non è una questione di adattamento di un principio più idealmente. Alcuni dei peggiori codici con cui ho lavorato sono stati i codici che hanno cercato di adattarsi perfettamente a DRY. L'ho scartato e riscritto. Era solo un pessimo codice. Non vorrei affatto usare il tuo framework.
Giordania,

18

foreach loops variabili scope!

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

stampa cinque "amet", mentre l'esempio seguente funziona bene

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
Questo è essenzialmente equivalente all'esempio di Jon con metodi anonimi.
Mehrdad Afshari,

3
Salva che è ancora più confuso con foreach dove la variabile "s" è più facile da mescolare con la variabile con ambito. Con i for-loop comuni, la variabile indice è chiaramente la stessa per ogni iterazione.
Mikko Rantanen,

2
blogs.msdn.com/ericlippert/archive/2009/11/12/… e sì, vorrei che la variabile fosse portata "correttamente".
Roman Starkov,

2
Questo è stato risolto in C # 5 .
Johnbot,

In sostanza, stai semplicemente stampando ripetutamente la stessa variabile senza modificarla.
Giordania,

18

MS SQL Server non è in grado di gestire date precedenti al 1753. Significativamente, non è sincronizzato con la DateTime.MinDatecostante .NET , che è 1/1/1. Quindi, se provi a salvare una mentalità, una data non valida (come mi è successo di recente in un'importazione di dati) o semplicemente la data di nascita di William the Conqueror, avrai dei problemi. Non esiste una soluzione alternativa per questo; se è probabile che tu debba lavorare con date precedenti al 1753, devi scrivere la tua soluzione alternativa.


17
Francamente penso che MS SQL Server abbia questo diritto e .Net sia sbagliato. Se fai la ricerca allora sai che le date precedenti al 1751 diventano funky a causa delle modifiche del calendario, dei giorni completamente saltati, ecc. La maggior parte dei RDBM ha un punto di interruzione. Questo dovrebbe dare un punto di partenza: ancestry.com/learn/library/article.aspx?article=3358
NotMe

11
Inoltre, la data è del 1753 .. Che era praticamente la prima volta che abbiamo un calendario continuo senza che le date vengano saltate. SQL 2008 ha introdotto il tipo di dati Date e datetime2 che può accettare date dal 01/01/01 al 31/12/9999. Tuttavia, i confronti di date che usano questi tipi dovrebbero essere visti con sospetto se si stanno davvero confrontando date precedenti al 1753.
NotMe

Oh, giusto, 1753, corretto, grazie.
Shaul Behr,

Ha davvero senso fare confronti con tali date? Voglio dire, per History Channel questo ha molto senso, ma non mi vedo voler sapere il giorno preciso della settimana in cui l'America è stata scoperta.
Camilo Martin,

5
Tramite Wikipedia il giorno giuliano è possibile trovare un programma di base di 13 righe CALJD.BAS pubblicato nel 1984 che può fare calcoli datati intorno al 5000 a.C., tenendo conto dei giorni bisestili e dei giorni saltati nel 1753. Quindi non vedo perché "moderno "i sistemi come SQL2008 dovrebbero fare di peggio. Potresti non essere interessato a una corretta rappresentazione della data nel 15 ° secolo, ma altri potrebbero, e il nostro software dovrebbe gestirlo senza bug. Un altro problema è saltare i secondi. . .
Roland,

18

La cattiva cache di Linq Gotcha

Vedi la mia domanda che ha portato a questa scoperta e al blogger che ha scoperto il problema.

In breve, DataContext mantiene una cache di tutti gli oggetti Linq-to-Sql che tu abbia mai caricato. Se qualcun altro apporta delle modifiche a un record che hai precedentemente caricato, non sarai in grado di ottenere i dati più recenti, anche se ricarichi esplicitamente il record!

Ciò è dovuto a una proprietà chiamata ObjectTrackingEnabledsu DataContext, che per impostazione predefinita è vera. Se imposti tale proprietà su false, il record verrà caricato di nuovo ogni volta ... MA ... non puoi persistere alcuna modifica a quel record con SubmitChanges ().

GOTCHA!


Iv ha appena trascorso un giorno e mezzo (e un sacco di capelli!) A caccia di questo BUG ...
Surgical Coder

Questo si chiama conflitto di concorrenza ed è ancora oggi un gotcha anche se ci sono alcuni modi per aggirare questo, sebbene tendano ad essere un po 'pesanti. DataContext è stato un incubo. O_o
Giordania,

17

Il contratto su Stream.Read è qualcosa che ho visto inciampare in molte persone:

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

Il motivo per cui questo è sbagliato è che Stream.Readleggerà al massimo il numero specificato di byte, ma è completamente gratuito per leggere solo 1 byte, anche se sono disponibili altri 7 byte prima della fine dello stream.

Non aiuta che sia così simile a Stream.Write, che è garantito per aver scritto tutti i byte se ritorna senza eccezioni. Inoltre, non aiuta il fatto che il codice sopra riportato funzioni quasi sempre . E ovviamente non aiuta che non esista un metodo pronto e conveniente per leggere correttamente esattamente N byte.

Quindi, per tappare il buco e aumentare la consapevolezza di questo, ecco un esempio di un modo corretto per farlo:

    /// <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
Oppure, nel tuo esplicito esempio var r = new BinaryReader(stream); ulong data = r.ReadUInt64();. BinaryReader ha anche un FillBuffermetodo ...
jimbobmcgee

15

eventi

Non ho mai capito perché gli eventi siano una caratteristica linguistica. Sono complicati da usare: è necessario verificare la presenza di null prima di chiamare, è necessario annullare la registrazione (se stessi), non è possibile scoprire chi è registrato (ad esempio: mi sono registrato?). Perché un evento non è solo una lezione in biblioteca? Fondamentalmente uno specialista List<delegate>?


1
Inoltre, il multithreading è doloroso. Tutti questi problemi, tranne il nulla, sono risolti in CAB (le cui funzionalità dovrebbero essere semplicemente integrate nel linguaggio) - gli eventi sono dichiarati a livello globale e qualsiasi metodo può dichiararsi "abbonato" di qualsiasi evento. Il mio unico problema con CAB è che i nomi degli eventi globali sono stringhe anziché enumerazioni (che potrebbero essere risolte da enumerazioni più intelligenti, come Java ha, che intrinsecamente funzionano come stringhe!) . CAB è difficile da configurare, ma non v'è un semplice clone open-source disponibile qui .
BlueRaja - Danny Pflughoeft,

3
Non mi piace l'implementazione di eventi .net. La sottoscrizione agli eventi deve essere gestita chiamando un metodo che aggiunge la sottoscrizione e restituisce un IDisposable che, una volta eliminato, eliminerà la sottoscrizione. Non è necessario un costrutto speciale che combini un metodo "aggiungi" e "rimuovi" la cui semantica può essere un po 'complicata, specialmente se si tenta di aggiungere e successivamente rimuovere un delegato multicast (ad esempio, aggiungere "B" seguito da "AB", quindi rimuovere "B" (lasciando "BA") e "AB" (lasciando ancora "BA").
Ops

@supercat Come riscriveresti button.Click += (s, e) => { Console.WriteLine(s); }?
Ark-kun,

Se dovessi essere in grado di annullare l'iscrizione separatamente dagli altri eventi IEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);});e annullare l'iscrizione tramite clickSubscription.Dispose();. Se il mio oggetto manterrà tutti gli abbonamenti per tutta la sua vita, MySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);}));e quindi MySubscriptions.Dispose()per uccidere tutti gli abbonamenti.
supercat,

@ Ark-kun: dover conservare oggetti che incapsulano abbonamenti esterni potrebbe sembrare un fastidio, ma considerare gli abbonamenti come entità consentirebbe di aggregarli con un tipo in grado di garantire che tutti vengano ripuliti, cosa altrimenti molto difficile.
supercat,

14

Oggi ho corretto un bug che è sfuggito a lungo. Il bug era in una classe generica che veniva utilizzata in uno scenario multi-thread e un campo int statico veniva utilizzato per fornire la sincronizzazione senza blocco tramite Interlocked. Il bug è stato causato perché ogni istanza della classe generica per un tipo ha il suo statico. Quindi ogni thread ha il suo campo statico e non è stato usato un blocco come previsto.

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

Questo stampa 5 10 5


5
puoi avere una classe di base non generica, che definisce la statica ed ereditare da essa i generici. Anche se non mi sono mai innamorato di questo comportamento in C #, ricordo ancora le lunghe ore di debug di alcuni modelli C ++ ... Eww! :)
Paulius,

7
Strano, pensavo fosse ovvio. Basti pensare a cosa dovrebbe fare se iavesse il tipo T.
Timwi,

1
Il parametro type fa parte di Type. SomeGeneric<int>è un tipo diverso da SomeGeneric<string>; quindi ovviamente ognuno ha il suopublic static int i
radarbob il

13

Gli enumerabili possono essere valutati più di una volta

Ti morderà quando hai un elenco numerato pigramente e ci provi due volte e ottieni risultati diversi. (o ottieni gli stessi risultati ma viene eseguito due volte inutilmente)

Ad esempio, mentre scrivevo un certo test, avevo bisogno di alcuni file temporanei per testare la logica:

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

Immagina la mia sorpresa quando File.Delete(file)lancia FileNotFound!!

Quello che sta succedendo qui è che l' filesenumerabile è stato ripetuto due volte (i risultati della prima iterazione semplicemente non lo sono ricordati) e su ogni nuova iterazione che verrai richiamato in Path.GetTempFilename()modo da ottenere un diverso set di nomi di file temporanei.

La soluzione è, ovviamente, quella di enumerare il valore utilizzando ToArray()o ToList():

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

Questo è ancora più spaventoso quando stai facendo qualcosa di multi-thread, come:

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

e scopri che content.Lengthè ancora 0 dopo tutte le scritture !! Quindi inizi a controllare rigorosamente che non hai una condizione di gara quando ... dopo un'ora sprecata ... hai capito che è solo quella piccola cosa enumerabile che hai dimenticato ...


Questo è di progettazione. Si chiama esecuzione differita. Tra le altre cose, è pensato per simulare costrutti TSQL. Ogni volta che selezioni da una vista sql ottieni risultati diversi. Consente inoltre il concatenamento, utile per gli archivi di dati remoti, come SQL Server. Altrimenti x.Select.Where.OrderBy invierebbe 3 comandi separati al database ...
as9876

@AYS ti sei perso la parola "Gotcha" nel titolo della domanda?
Chakrit,

Pensavo che Gotcha significasse una supervisione dei designer, non qualcosa di intenzionale.
as9876,

Forse dovrebbe esserci un altro tipo per IEnumerables non riavviabili. Come AutoBufferedEnumerable? Si potrebbe implementarlo facilmente. Questo gotcha sembra principalmente dovuto alla mancanza di conoscenza del programmatore, non credo che ci sia qualcosa di sbagliato nel comportamento attuale.
Eldritch Conundrum,

13

Ne ho appena trovato uno strano che mi ha bloccato per un po 'nel debug:

È possibile incrementare null per un int nullable senza lanciare un'eccitazione e il valore rimane nullo.

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

Questo è il risultato di come C # sfrutta le operazioni per i tipi nullable. È un po 'simile a NaN che consuma tutto ciò che si lancia.
IllidanS4 vuole Monica indietro il

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

Sì, questo comportamento è documentato, ma certamente non lo rende giusto.


5
Non sono d'accordo - quando una parola è in maiuscolo, può avere un significato speciale che non vuoi fare casini con il Title Case, ad esempio "presidente degli Stati Uniti" -> "Presidente degli Stati Uniti", non "Presidente degli Stati Uniti d'America".
Shaul Behr,

5
@Shaul: In tal caso, dovrebbero specificare questo come parametro per evitare confusione, perché non ho mai incontrato nessuno che si aspettasse questo comportamento in anticipo - il che lo rende un gotcha !
BlueRaja - Danny Pflughoeft
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.