Difetti di progettazione in C # (.NET) [chiuso]


84

Quali sono alcuni dei maggiori difetti di progettazione in C # o .NET Framework in generale?

Esempio: non esiste un tipo di stringa non annullabile e devi controllare DBNull quando recuperi i valori da un IDataReader.


In che senso sono quei difetti di progettazione?
Juliet

Con IDataReader puoi usare IsDBNull invece di controllare manualmente
Marc Gravell

9
Spingi Jon Skeet per parlare delle classi sigillate;)
johnc

3
È abbastanza facile correggere IDataReader con un metodo di estensione: vedere weblogs.asp.net/skillet/archive/2008/06/18/… .
Robert Rossney,

@lagerdalek - Farei +1 su quel commento se potessi; ben ricordato
Marc Gravell

Risposte:


39

Sono decisamente d'accordo con questo post (per coloro che fanno la cacca per la mancanza di ToString, c'è un attributo debugger per fornire un formato personalizzato per la tua classe).

In cima all'elenco sopra, aggiungerei anche le seguenti richieste ragionevoli:

  1. tipi di riferimento non nullable come complemento ai tipi di valore nullable,
  2. consentire l'override del costruttore vuoto di una struttura,
  3. consentire vincoli di tipo generico per specificare classi sigillate,
  4. Sono d'accordo con un altro poster qui che ha richiesto firme di costruttori arbitrari quando utilizzato come vincoli, ad es. dove T : new(string)o doveT : new(string, int)
  5. Sono anche d'accordo con un altro poster qui sulla correzione di eventi, sia per elenchi di eventi vuoti che nell'impostazione simultanea (sebbene quest'ultima sia complicata),
  6. gli operatori dovrebbero essere definiti come metodi di estensione e non come metodi statici della classe (o non solo come metodi statici almeno),
  7. consentire proprietà e metodi statici per le interfacce (Java ha questo, ma C # no),
  8. consentire l'inizializzazione degli eventi negli inizializzatori di oggetti (attualmente sono consentiti solo i campi e le proprietà),
  9. perché la sintassi "inizializzatore oggetto" è utilizzabile solo quando si crea un oggetto? Perché non renderlo disponibile in qualsiasi momento, ad es.var e = new Foo(); e { Bar = baz };
  10. correggere il comportamento enumerabile quadratico ,
  11. tutte le raccolte dovrebbero avere istantanee immutabili per l'iterazione (cioè la modifica della raccolta non dovrebbe invalidare l'iteratore),
  12. le tuple sono facili da aggiungere, ma un tipo algebrico chiuso efficiente come " Either<T>" non lo è, quindi mi piacerebbe un modo per dichiarare un tipo algebrico chiuso e applicare una corrispondenza di pattern esaustiva su di esso (fondamentalmente supporto di prima classe per il pattern del visitatore, ma molto più efficiente); quindi prendi le enumerazioni, estendile con un supporto esaustivo per la corrispondenza dei modelli e non consentire casi non validi,
  13. Mi piacerebbe il supporto per il pattern matching in generale, ma almeno per il test del tipo di oggetto; Mi piace anche la sintassi degli interruttori proposta in un altro post qui,
  14. Sono d'accordo con un altro post che le System.IOclassi, come Stream, sono un po 'mal progettate; qualsiasi interfaccia che richiede alcune implementazioni per essere lanciata NotSupportedExceptionè un cattivo design,
  15. IListdovrebbe essere molto più semplice di quello che è; in effetti, questo può essere vero per molte delle interfacce di raccolta concreta, come ICollection,
  16. troppi metodi generano eccezioni, come IDictionary per esempio,
  17. Preferirei una forma di eccezioni verificate migliore di quella disponibile in Java (vedere la ricerca sui sistemi di tipi e di effetti per sapere come farlo),
  18. risolvere vari fastidiosi casi d'angolo nella risoluzione di sovraccarico del metodo generico; ad esempio, prova a fornire due metodi di estensione sovraccaricati, uno che opera sui tipi di riferimento e l'altro sui tipi di strutture nullable, e guarda come piace alla tua inferenza del tipo,
  19. fornire un modo per riflettere in modo sicuro sui nomi dei campi e dei membri per interfacce come INotifyPropertyChanged, che prendono il nome del campo come una stringa; puoi farlo usando un metodo di estensione che accetta un lambda con a MemberExpression, ie. () => Foo, ma non è molto efficiente,
    • Aggiornamento: C # 6.0 ha aggiunto l' nameof()operatore per i nomi dei singoli membri, ma non funziona in generics ( nameof(T) == "T"invece del nome dell'argomento di tipo effettivo: devi ancora farlo typeof(T).Name)) - né ti consente di ottenere una stringa "percorso" , ad esempio nameof(this.ComplexProperty.Value) == "Value"limitandone le possibili applicazioni.
  20. consentire gli operatori nelle interfacce e implementare tutti i tipi di numeri principali IArithmetic; sono possibili anche altre utili interfacce operatore condivise,
  21. rendere più difficile la modifica dei campi / proprietà degli oggetti, o per lo meno, consentire l'annotazione di campi immutabili e fare in modo che il controllo del tipo lo imponga (trattatelo come proprietà solo getter per i chrissakes, non è difficile!); infatti, unifica i campi e le proprietà in un modo più sensato poiché non ha senso averli entrambi; Le proprietà automatiche di C # 3.0 sono un primo passo in questa direzione, ma non vanno abbastanza lontano,
    • Aggiornamento: sebbene C # contenga la readonlyparola chiave e C # 6.0 abbia aggiunto proprietà automatiche di sola lettura, sebbene non sia rigoroso come il supporto del linguaggio vero per tipi e valori immutabili.
  22. semplificare la dichiarazione dei costruttori; Mi piace l'approccio di F #, ma l'altro post qui che richiede semplicemente "nuovo" invece del nome della classe è almeno meglio,

Per ora è abbastanza suppongo. Queste sono tutte irritazioni in cui mi sono imbattuto nell'ultima settimana. Probabilmente potrei andare avanti per ore se ci metto davvero la testa. C # 4.0 sta già aggiungendo argomenti denominati, facoltativi e predefiniti, che approvo con enfasi.

Ora per una richiesta irragionevole:

  1. sarebbe davvero, davvero bello se C # / CLR potesse supportare il polimorfismo del costruttore di tipi, ad es. generici rispetto a generici,

Piuttosto per favore? :-)


1
Per quanto riguarda # 1, ogni tipo deve avere un valore predefinito o il sistema deve fornire un mezzo per eseguire un costruttore ogni volta che viene allocata una variabile o un campo di un tipo particolare. Preferirei quest'ultimo (un ramo del n. 2) ma il n. 1 potrebbe essere adattato se i metodi / proprietà non virtuali potessero essere decorati per specificare che dovrebbero essere chiamati senza un controllo nullo. Ciò consentirebbe a cose come i campi "String" di comportarsi come se fossero predefiniti su una stringa vuota anziché nulla (poiché la funzione statica "length" di String potrebbe restituire 0 se invocata su una stringa nulla).
supercat

1
Per quanto riguarda # 2, sarebbe utile in generale se gli struct potessero specificare non solo un costruttore diverso da fill-with-zero, ma anche un costruttore di copia diverso da byte-per-byte-copy. In realtà, mi piacerebbe davvero vedere un framework simile a .net che potrebbe fare un buon lavoro nel riconoscere che le entità possono avere valore o semantica di riferimento e consentire di etichettare oggetti heap di tipo valore mutabili, condivisi-immutabili o non vincolati (un oggetto di valore non vincolato può essere modificato se la sua modalità è prima CompareExchange'd in mutable, o può essere condiviso se la sua modalità è prima CompareExchange'd in shared).
supercat

1
Punti eccellenti! Per # 1, un'annotazione del sistema di tipi è la soluzione comune, ma preferisco propagare i vincoli del costruttore tramite variabili di tipo, come T: new (). Re: # 2, buon punto sui costruttori di copie, ma sarei felice con costruttori più generali sulla falsariga che ho descritto sopra. Ancora meglio sarebbe eliminare completamente i costruttori come metodi distinti e semplicemente renderli metodi statici. Ciò consente modelli di costruzione più semplici e più generali, in particolare se consentiamo metodi statici sulle interfacce. Constructors-as-static-methods + static-methods-in-interfaces risolve anche # 1.
naasking

3
# 3: Qual è lo scopo di utilizzare una classe sealed come parametro di tipo generico, ad esempio Foo <T> dove T: string? # 11: Ok, quindi ho List<T>un milione di Ts. Come proponi che l'istantanea venga scattata in modo efficiente? # 21: Usa la readonlyparola chiave .... anche se ci sono alcuni buoni suggerimenti qui, sono principalmente solo questo: suggerimenti, non difetti di progettazione.
Qwertie

2
Questa è una risposta molto interessante, ma penso che dovremmo aggiornare con le funzionalità C # 6. Esempio: gli elementi 19 e 21 sono stati implementati =)
eduardobr

72
  • il Reset()metodo è IEnumerator<T>stato un errore (per i blocchi iteratori, le specifiche del linguaggio richiedono anche che questo generi un'eccezione)
  • i metodi di riflessione che restituiscono gli array erano, dal punto di vista di Eric, un errore
  • la covarianza degli array era e rimane una stranezza
    • Aggiornamento: C # 4.0 con .NET 4.0 ha aggiunto il supporto di covariante / controvarianza a interfacce generiche (come IEnumerable<out T>e Func<in T, out TResult>, ma non tipi concreti (come List<T>).
  • ApplicationException piuttosto cadde in disgrazia - è stato un errore?
  • raccolte sincronizzate: un'idea carina, ma non necessariamente utile nella realtà: di solito è necessario sincronizzare più operazioni ( Contains, quindi Add), quindi una raccolta che sincronizza operazioni distinte non è poi così utile
    • Aggiornamento: I System.Collections.Concurrenttipi , con TryAdd, GetOrAdd, TryRemove, ecc sono stati aggiunti nel .NET Framework 4.0 - anche se i metodi che accettano un delegato di fabbrica non garantiscono la fabbrica verrà richiamato solo una volta per tasto.
  • si sarebbe potuto fare un uso maggiore del pattern using/ lock, magari consentendo loro di condividere una sintassi riutilizzabile (estensibile?); puoi simularlo restituendo IDisposablee usando using, ma avrebbe potuto essere più chiaro
  • blocchi iteratori: nessun modo semplice per controllare gli argomenti in anticipo (piuttosto che pigramente). Certo, puoi scrivere due metodi concatenati, ma è brutto
  • sarebbe bello l'immutabilità più semplice; C # 4.0 aiuta un po ' , ma non abbastanza
  • no "questo parametro di tipo ref non può essere nullo", sebbene i contratti (in 4.0) aiutino in questo modo. Ma la sintassi come Foo(SqlConnection! connection)(che inietta un controllo nullo / throw) sarebbe carina (al contrario di int?ecc.)
  • mancanza di supporto di operatori e costruttori non predefiniti con generici; C # 4.0 risolve un po 'questo problema con dynamic, oppure puoi abilitarlo in questo modo
  • la variabile iteratore viene dichiarata fuori durante l' foreachespansione, il che significa che i metodi anon / lambda catturano la singola variabile, piuttosto che una per iterazione (doloroso con threading / async / ecc)

IEnumerable! = IEnumerable <object> è davvero strano
Rauhotz

2
Bene, la cosa IEnumerable è una sbornia 1.1; puoi usare .Cast <object> () con LINQ, almeno
Marc Gravell

8
La gente di BCL ha detto che è ApplicationExceptionstato un errore, non utile come speravano. Hanno anche detto che System.Exceptionavrebbe dovuto essere abstract.
Jay Bazuzi

2
Non annullabile: dovrebbe essere un errore di compilazione passare un tipo di riferimento normale T a qualcosa che accetta un T non annullabile! (proprio come non puoi passare int? a int). Passare nell'altra direzione va bene, ovviamente.
Jay Bazuzi

1
@ Jon Harrop: IMHO, avrebbe dovuto esserci il supporto per gli array immutabili e per i riferimenti agli array di sola lettura. Forse anche per alcune altre varianti di array (ad esempio un "array ridimensionabile" (riferimento indiretto) o un riferimento di array con un offset e un limite).
supercat

60

TextWriter è una classe base di StreamWriter. wtf?

Questo mi confonde sempre all'estremo.


19
+1 Devo cercarlo ogni volta. (Whaddaya significa che non posso nuovo TextWriter ()?)
Nicholas Piasecki

1
Grazie a Dio ... pensavo di essere solo io.
IJ Kennedy

? È sempre qualcosa che scrive testo, ma solo StreamWriter lo fa in un flusso. Sembra abbastanza semplice.
Jon Hanna

4
Il nome StreamWriter non rende il fatto che scrive testo abbastanza ovvio IMO. Suona isolato come se scrivesse solo byte e TextWriter sarebbe un'implementazione di fantasia in cima che convertirà l'api di (string toWrite) in byte per te. Ora, se si chiamasse StreamTextWriter, sarebbe immediatamente ovvio ma un po 'lungo :(
Quibblesome

44

Un piccolo C # pet peev - i costruttori usano la sintassi C ++ / Java di avere il costruttore con lo stesso nome della classe.

New()o ctor()sarebbe stato molto più carino.

E certo, strumenti come coderush rendono questo meno un problema per la ridenominazione delle classi, ma da un POV di leggibilità, New () fornisce grande chiarezza.


Ehm, come potrebbe sapere di cosa stai cercando di creare una nuova istanza?
BlueRaja - Danny Pflughoeft

4
@BlueRaja: Scott si riferisce alla denominazione dei costruttori nelle classi. class Foo { new(int j) {i = j} int i; }
Dalle

Sebbene sia d'accordo al 100% che ctor () o constructor () sarebbero stati migliori (le Newparole chiave non - maiuscole sono contro le convenzioni), esito a definirlo un difetto di progettazione. Volevano attirare gli sviluppatori C ++ / Java esistenti e prendere in prestito molte stupide vecchie convenzioni sintattiche probabilmente li ha aiutati a raggiungere il loro obiettivo.
Qwertie

relativo a questa domanda: stackoverflow.com/questions/32101993/c-sharp-sorted-linkedlist non c'è modo (utilizzando solo .NET) per ordinare semplicemente una LinkedList con MergeSort, bucketsort o qualsiasi altro algoritmo di ordinamento ma utilizzando linq che è molto più lento rispetto a un'implementazione ad hoc.
CoffeDeveloper

29

Non capisco che non puoi fare

dove T: nuovo (U)

Quindi dichiari che il tipo generico T ha un costruttore non predefinito.

modificare:

Voglio farlo:

public class A 
{
    public A(string text) 
    {

    }
}


public class Gen<T> where T : new(string text) 
{

}

Un tipo generico dichiara come userete gli oggetti di quel tipo, cioè la loro interfaccia. Il costruttore è un dettaglio di implementazione di quell'interfaccia, che non riguarda il consumatore. Quando è necessario creare un'istanza parametrizzata, utilizzare una factory.
Bryan Watts

Per info, sebbene non possa eseguire il controllo in fase di compilazione, c'è del codice in MiscUtil per usare i costruttori non predefiniti (in generics) in modo efficiente, cioè senza Activator.CreateInstance o reflection.
Marc Gravell

Perché non ha senso e crea confusione su alcuni usi. Tuttavia può essere utile quando si lavora con oggetti immutabili.
Pop Catalin

7
In generale, la mancanza di vincoli per i membri è fastidiosa, sì.
MichaelGG

3
amen, ho sempre voluto questo
Steve,

20

Sono davvero sorpreso di essere il primo a menzionare questo:

I set di dati tipizzati ADO.NET non espongono colonne nullable come proprietà di tipi nullable. Dovresti essere in grado di scrivere questo:

int? i = myRec.Field;
myRec.Field = null;

Invece, devi scrivere questo, che è semplicemente stupido:

int? i = (int?)myRec.IsFieldNull() ? (int?)null : myRec.Field;
myRec.SetFieldNull();

Questo era fastidioso in .NET 2.0, ed è ancora più fastidioso ora che devi usare jiggery-pokery come sopra nelle tue belle query LINQ.

È anche fastidioso che il Add<TableName>Rowmetodo generato sia altrettanto insensibile alla nozione di tipi nullable. Tanto più che i TableAdaptermetodi generati non lo sono.

Non c'è molto in .NET che mi faccia sentire come se il team di sviluppo avesse detto "Ok, ragazzi, siamo abbastanza vicini - spediscilo!" Ma questo lo fa di sicuro.


Sono assolutamente d'accordo! Questo mi infastidisce ogni volta che devo usarlo (il che è spesso). Argh! +1
Eyvind,

2
Il minimo che potrebbero fare sarebbe inventare una nuova classe DataSetV2 (nome errato - solo per motivi di argomento), che utilizza tipi di valore nullable invece di DBNull dappertutto.
Christian Hayter,

Non dimentichiamo l'assurdità di richiedere un valore speciale DBNull.Value, quando esso nullstesso sarebbe stato perfettamente adeguato a rappresentare NULL. Fortunatamente LINQ-to-SQL usa solo null per NULL.
Qwertie

In realtà quell'assurdità è la roccia su cui è stato costruito l'intero assurdo edificio.
Robert Rossney

20
  1. Non sono un grande fan delle classi Stream, StringWriter, StringReader, TextReader, TextWriter ... semplicemente non è intuitivo cosa sia cosa.
  2. IEnumerable.Reset che genera un'eccezione per gli iteratori. Ho alcuni componenti di terze parti che chiamano sempre reset quando databound, mi richiede di trasmettere prima a un elenco per usarli.
  3. Il serializzatore Xml dovrebbe avere elementi IDictionary serializzati
  4. Mi sono completamente dimenticato dell'HttpWebRequest e dell'API FTP che dolore nel mio ... (grazie per il commento Nicholas per ricordarmelo :-)

Modifica
5. Un altro mio fastidio è come System.Reflection.BindingFlags, ha usi diversi a seconda del metodo che stai utilizzando. In FindFields, ad esempio, cosa significano CreateInstance o SetField? Questo è un caso in cui hanno sovraccaricato il significato dietro questa enumerazione che è confusa.


1
+1 Devo cercare una qualsiasi delle classi XmlTextWriter, TextWriter, ecc. Ogni volta. Lo stesso con la roba HttpWebRequest / Response. API completamente non intuitiva lì.
Nicholas Piasecki

+ 1-1 = 0: XmlTextWriter ecc., Sono d'accordo che è impossibile dedurre veramente cosa siano dal nome. HttpWebRequest Non sono d'accordo Lo trovo abbastanza intuitivo.
AnthonyWJones,

A ciascuno il suo suppongo. Immagino che con FTP mi aspetterei un'astrazione di livello superiore rispetto a quello che hanno.
JoshBerke

15

Non so se potrei arrivare a dire che è un difetto di progettazione, ma sarebbe davvero bello se potessi dedurre un'espressione lambda nello stesso modo in cui puoi in VB:

VB:

Dim a = Function(x) x * (x - 1)

C #

Sarebbe bello se potesse farlo:

var a = x => x * (x - 1);

Invece di doverlo fare:

Func<int, int> a = x => x * (x - 1);

Mi rendo conto che non è molto più lungo, ma in Code Golf ogni personaggio conta dannatamente! Non ne tengono conto quando progettano questi linguaggi di programmazione? :)


3
Microsoft dovrebbe prendere in considerazione Code Golf quando progetta i linguaggi?
jrcs3

3
@ Ray Burns: come fa a sapere in VB? VB lo supporta, quindi qual è la differenza?
BenAlabaster

3
@RayBurns Tipo inferenza? Lo uso dal 1989.
RD1

3
I lama sono omoiconici in C #. il frammento (int x) => x * (x -1);può significare Func<int, int>o può significareExpression<Func<int, int>>
Scott Weinstein

3
@ BenAlabaster: VB supporta operatori aritmetici con associazione tardiva. C # deve risolverli in fase di compilazione. È una differenza linguistica. Ad esempio VB può aggiungere due oggetti insieme. C # non può perché +non è definito per object.
ricorsivo

14
  1. La classe System.Object :

    • Equals e GetHashCode: non tutte le classi sono confrontabili o hash, dovrebbero essere spostate su un'interfaccia. Mi viene in mente IEquatable o IComparable (o simile).

    • ToString: non tutte le classi possono essere convertite in una stringa, dovrebbero essere spostate in un'interfaccia. Mi viene in mente IFormattable (o simile).

  2. La proprietà ICollection.SyncRoot :

    • Promuove un design scadente, una serratura esterna è quasi sempre più utile.
  3. I generici avrebbero dovuto esserci dall'inizio:


1
1. Quei metodi sono così comuni che è stato deciso che i casi strani erano ok, importa se qualcuno può chiamare Object.Equals nella tua classe? È noto che potrebbe esserci o meno un'implementazione e richiedere: IEquatable, IFormattable sul 99% delle classi è strano.
Guvante

1
È utile essere in grado, ad esempio, di costruire un dizionario con oggetti definiti dall'utente come chiavi, utilizzando l'uguaglianza di riferimento predefinita, senza dover aggiungere esplicitamente codice agli oggetti definiti dall'utente a tale scopo. Considererei Finalize uno spreco molto più grande (un'alternativa migliore sarebbe stata avere oggetti che necessitano di finalizzazione implementano iFinalizable e si registrano esplicitamente per la finalizzazione). OTOH, avrebbe dovuto esserci un supporto più intrinseco per iDisposable, inclusa la chiamata a Dispose se un costruttore genera un'eccezione.
supercat

1
@supercat: tutto ciò che serve è aggiornare EqualityComparer<T>.Defaultcorrettamente. Quindi entrambi var dict = new Dictionary<object, string>(EqualityComparer<object>.Default)e var dict = new Dictionary<object, string>()useranno il confronto / l'uguaglianza dei riferimenti.
Dalle

1
@supercat: Ciò che descrivi è esattamente ciò che EqualityComparer<T>.Defaultfa. Non c'è bisogno di controllare ad ogni ricerca. L'operatore di confronto è una proprietà per l' Dictionaryistanza e ognuno Dictionarysa quale sta utilizzando.
dalle

1
@supercat: un dizionario deve utilizzare il tipo più generico (ovvero la classe base comune) come chiave, l'utilizzo sia di stringhe che di DateTime nello stesso dizionario non avrebbe alcun significato a meno che non si utilizzi il confronto dei riferimenti, a meno che non sia dimostrato un confronto definito dall'utente è. Ricordare che il nome di questo argomento è "C # (.NET) Design Flaws".
dalle

12

Una delle cose che mi irrita è il Predicate<T> != Func<T, bool>paradosso. Sono entrambi delegati di tipo T -> boole tuttavia non sono compatibili con le assegnazioni.


C'è un trucco usando Delegate.Create e alcuni casting per fare la conversione, ma almeno essere in grado di fare un cast esplicito sarebbe bello (posso capire la mancanza di supporto per implicito comunque)
Guvante

Il design dei delegati in generale è difettoso; ad esempio la mancanza di eventi deboli (l'implementazione di eventi deboli lato sorgente senza uno sforzo speciale da parte dell'abbonato può essere eseguita solo con un po ' di riflessione e ReflectionPermission, vedere codeproject.com/Articles/29922/Weak-Events-in-C ), e l'inefficienza che deriva dal requisito che i delegati devono essere tipi di riferimento (i delegati sarebbero stati più veloci e avrebbero utilizzato 1/3 della memoria in molti casi, se fossero stati tipi di valore - allora sarebbero semplicemente una coppia di puntatori che potresti passare allo stack.)
Qwertie

11

Alcune persone (ISV) desiderano che tu possa compilarlo in codice macchina in fase di compilazione e collegarlo, al fine di creare un eseguibile nativo che non necessita del run-time dotNet.


Dovresti essere in grado di NGEN il tuo programma prima di eseguirlo.
Otávio Décio

Non è la stessa cosa che rimuovere le dipendenze del runtime. Salva solo la prima esecuzione JIT sul codice.
Ed S.


Non c'è uno strumento di offuscamento che fa questo? Incorporerà il framework nel tuo exe in modo da non doverlo distribuire. Non riesco a ricordare il suo nome ... non è quello di PreEmptive ...
JoshBerke,

2
Postbuild di Xenocode non lo fa? Sarebbe bello se Visual Studio avesse un modo per farlo ...
BenAlabaster

11

Sappiamo così tanto sulle giuste tecniche OO. Disaccoppiamento, programmazione per contratto, prevenzione di eredità impropria, uso appropriato delle eccezioni, principio aperto / chiuso, sostituibilità di Liskov e così via. Tuttavia, i framework .Net non utilizzano le migliori pratiche.

Per me l'unico più grande difetto nel design di .Net non è stare sulle spalle dei giganti; promuovere paradigmi di programmazione tutt'altro che ideali per le masse di programmatori che utilizzano i loro framework .

Se MS avesse prestato attenzione a questo, il mondo dell'ingegneria del software avrebbe potuto fare grandi passi in avanti in termini di qualità, stabilità e scalabilità in questo decennio, ma ahimè sembra che stia regredendo.


4
+1 per lo sfogo sul bersaglio; A questo aggiungerei l'incapacità di sottoclassare ogni classe, la mancanza di interfacce per le classi fondamentali e la riluttanza a correggere i bug del framework anche dopo diversi anni
Steven A. Lowe

1
Se andiamo al sodo, stiamo dicendo che i framework .Net sono così scadenti che è difficile stabilire quale sia il difetto peggiore? Mi sento meglio dopo uno sfogo e apprezzo il voto positivo perché mi aspettavo di essere rimproverato dai ragazzi fan della SM.
Daniel Paull,

Non ho votato in nessun modo, e comunque sono molto riluttante a dare voti negativi, ma sto cercando di capire perché NON MI INTERESSA.
Mike Dunlavey,

2
Penso che tu stia dicendo "non è così buono come potrebbe essere", che è una non risposta. Niente è perfetto. Fornisci dettagli.
jcollum

3
No, sto dicendo che ci sono numerosi casi in cui il design è ovviamente imperfetto, quindi quando pensi che lo facciano bene, lo sbagliano ancora. Ad esempio, il mio post sui forum MSDN qui: social.msdn.microsoft.com/forums/en-US/wpf/thread/…
Daniel Paull,

11

Non mi piace l'istruzione switch C #.

Vorrei qualcosa di simile

switch (a) {
  1    : do_something;
  2    : do_something_else;
  3,4  : do_something_different;
  else : do_something_weird; 
}

Quindi niente più interruzioni (facili da dimenticare) e la possibilità di separare diversi valori con virgole.


In realtà penso che sarebbe ancora meglio se richiedesse una singola istruzione o un blocco tra parentesi graffe, come tutto il resto in C #. Senza la capacità di fallire, l'attuale sintassi di interruzione è un po 'dubbia e non si limita allo scopo. (AFAIK, potrebbe, non lo so)
Tamas Czinege,

Non capisco cosa intendi. Pubblica qui la tua dichiarazione di commutazione "ideale". Non voglio cadere ma valori separati da virgole.
tuinstoel

8
A proposito, sono d'accordo con OP. switchè fondamentalmente rotto in tutti i linguaggi che emulano la versione deliberatamente paralizzata del C (ottimizzata per la velocità!). VB se la cava molto meglio, ma è ancora anni luce indietro rispetto ai linguaggi con pattern matching (Haskell, F #…).
Konrad Rudolph

1
tuinostel: qualcosa come switch (a) {case 1 {do_something; } caso 2 {do_something_else; }} - ovvero, sbarazzarsi dell'istruzione break e richiedere blocchi di codice appropriati per ogni caso
Tamas Czinege,

2
Avere break sembra un errore in generale, non è un errore di compilazione emetterlo? Sembra essere lì solo per facilitare la transizione da C a C # (ovvero insegnare agli sviluppatori che non possono cadere automaticamente)
Guvante

10

Eventi in C #, dove devi controllare esplicitamente i listener. Non era questo il punto degli eventi, da trasmettere a chiunque fosse lì? Anche se non ce ne sono?


1
È fastidioso che non ci sia zucchero per questo quando lo vuoi, capisco perché lo fanno duro, incoraggia a non istanziare gli argomenti dell'evento se non necessario
ShuggyCoUk

Hm. O non capisco o non sono d'accordo, o entrambi :-). Non posso dire di essermi mai sentito scoraggiato a istanziare qualcosa. Questo mi sa di ottimizzazione prematura.
Thomas Eyde,

I metodi parziali sono una valida alternativa, in alcuni casi.
Robert Harvey,

IN PIÙ tutti gli accoppiamenti che ciò causa ... MS ha CAB che risolve entrambi questi problemi, ma CAB ha molti problemi propri a causa delle limitazioni in C # (ad es. Stringhe piuttosto che enumerazioni come argomenti degli eventi) - perché non limitarsi a creare liberamente -Eventi accoppiati parte della lingua !?
BlueRaja - Danny Pflughoeft

9

Il comportamento orribile (e abbastanza invisibile alla maggior parte delle persone) O (N ^ 2) degli iteratori annidati / ricorsivi .

Sono abbastanza sbigottito che lo sappiano, sappiano come risolverlo, ma non è visto come una priorità sufficiente per meritare l'inclusione.

Lavoro con strutture ad albero tutto il tempo e devo correggere il codice di persone altrimenti intelligenti quando inavvertitamente introducono operazioni molto costose in questo modo.

La bellezza di "yield foreach" è che la sintassi più semplice e più facile incoraggia un codice corretto e performante. Questo è il "pozzo del successo" a cui penso dovrebbero aspirare prima di aggiungere nuove funzionalità per il successo a lungo termine della piattaforma.


Questo è particolarmente imperfetto, perché va contro le aspettative intuitive del comportamento di O e quindi intrappolerà molte persone
oefe

7

Alcune classi implementano interfacce ma non implementano molti dei metodi di tale interfaccia, ad esempio Array implementa IList ma 4 metodi su 9 generano NotSupportedException http://msdn.microsoft.com/en-us/library/system.array_members .aspx


Bene, non puoi cambiare il numero di elementi in un Array, quindi non c'è niente che Aggiungi, Cancella, Inserisci e Rimuovi (At) possa fare se non lanciare NotSupported ... In effetti, mi aspetterei QUALSIASI implementazione di IList che restituisca true per IsFixedSize li getterebbe.
CB

@CB un po 'in ritardo alla festa immagino :) Ma se Array non può soddisfare "IList", perché implementarlo comunque? Questa è una violazione del principio L in SOLID.
Ala Sendon

7

Membri statici e tipi annidati nelle interfacce.

Ciò è particolarmente utile quando un membro dell'interfaccia ha un parametro di un tipo specifico per l'interfaccia ( ad esempio un enum). Sarebbe bello annidare il tipo enum nel tipo di interfaccia.


1
Non è molto simile al tuo altro suggerimento?
RCIX

1
No, questo riguarda il linguaggio C # e l'altro riguarda il framework. Non a tutti interessa la distinzione, quindi lasciatemi dire che questa riguarda ciò che è consentito e l'altra riguarda ciò che viene fornito.
Jay Bazuzi,

6

La terribilmente pericolosa natura predefinita degli eventi. Il fatto che tu possa chiamare un evento e trovarti in uno stato incoerente a causa della rimozione degli abbonati è semplicemente orribile. Vedi gli eccellenti articoli di Jon Skeet e Eric Lippert per ulteriori letture sull'argomento.


Non mi dispiacerebbe se gli eventi non fossero thread-safe per impostazione predefinita (potrebbe aumentare le prestazioni nel codice a thread singolo); la cosa sciocca è che aggiungi / rimuovi sono sicuri per impostazione predefinita, ma che il modo naturale per attivare un evento non è sicuro e non esiste alcuna funzionalità per renderlo facilmente sicuro.
Qwertie

@ Qwertie: La cosa più sciocca è che per un po 'di tempo, aggiungi / rimuovi utilizzerà il blocco e tuttavia non sarà thread-safe.
supercat

6
  • null ovunque.

  • const Da nessuna parte.

  • Le API sono incoerenti, ad esempio la modifica di un array restituisce voidma l'aggiunta a StringBufferrestituisce lo stesso mutabile StringBuffer.

  • Le interfacce di raccolta sono incompatibili con strutture di dati immutabili, ad esempio Addin System.Collections.Generic.IList<_>non può restituire un risultato.

  • Nessuna digitazione strutturale, quindi scrivi System.Windows.Media.Effects.SamplingMode.Bilinearinvece di solo Bilinear.

  • Mutevole IEnumeratorinterfaccia implementata da classi quando dovrebbe essere un immutabili struct.

  • Uguaglianza e confronto sono un disastro: hai System.IComparablee Equalspoi hai anche avuto System.IComparable<_>, System.IEquatable, System.Collections.IComparer, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable, System.Collections.Generic.IComparere System.Collections.Generic.IEqualityComparer.

  • Le tuple dovrebbero essere strutture, ma le strutture inibiscono inutilmente l'eliminazione delle chiamate di coda, quindi uno dei tipi di dati più comuni e fondamentali allocherà inutilmente e distruggerà il parallelismo scalabile.


Non esiste una digitazione strutturale nel CLR, ma sembra che la digitazione strutturale sia confusa con l'inferenza del tipo o con la caratteristica Ruby nota come "simboli". La tipizzazione strutturale sarebbe se il CLR considerasse Func <int, bool> e Predicate <int> come lo stesso tipo, o almeno convertibili in modo implicito.
Qwertie

A proposito di confronto, non dimenticare Comparer <T>!
Qwertie

@Qwertie mi riferivo alle funzionalità del linguaggio di programmazione come le varianti polimorfiche in OCaml. La libreria LablGL di OCaml ha molti esempi interessanti di tipizzazione strutturale utili nel contesto della grafica. Niente a che vedere con l'inferenza del tipo e solo tangenzialmente correlato ai simboli.
JD

1
Come si usa una struttura immutabile IEnumerator?
supercat

5

0 moonlighting as enum

peculiarità di enum: http://blogs.msdn.com/abhinaba/archive/2007/01/09/more-peculiarites-of-enum.aspx

come illustrato da questo buon esempio: http://plus.kaist.ac.kr/~shoh/postgresql/Npgsql/apidocs/Npgsql.NpgsqlParameterCollection.Add_overload_3.html

il mio suggerimento, fai buon uso del segno "@":

invece di:

if ((myVar & MyEnumName.ColorRed)! = 0)

Usa questo:

if ((myVar & MyEnumName.ColorRed)! = @ 0)


1
+1 enum è una delle poche cose che Java ha fatto bene mentre C # no
BlueRaja - Danny Pflughoeft

5

Per aggiungere alla lunga lista di buoni punti già fatti da altri:

  • DateTime.Now == DateTime.Now nella maggior parte dei casi, ma non in tutti.

  • Stringche è immutabile ha un sacco di opzioni per la costruzione e la manipolazione, ma StringBuilder(che è mutevole) no.

  • Monitor.Entere Monitor.Exitavrebbero dovuto essere metodi di istanza, quindi invece di modificare un oggetto specifico per il blocco, potresti creare un nuovo a Monitore bloccarlo.

  • I distruttori non avrebbero mai dovuto essere chiamati distruttori. La specifica ECMA li chiama finalizzatori, il che è molto meno confuso per la folla C ++, ma la specifica del linguaggio si riferisce ancora a loro come distruttori.


3
Quella DateTime.Nowè la condizione di gara più ovvia al mondo, ma +1 per il resto
BlueRaja - Danny Pflughoeft

Non è tanto il fatto che sia una condizione di gara, ma il fatto che ne abbiano fatto una proprietà. Le proprietà sembrano esattamente come i campi, quindi è un comportamento piuttosto sorprendente IMO.
Brian Rasmussen

4
@Brian Rasmussen: DateTime.Now è abbastanza correttamente una proprietà, poiché viene modificata non leggendola, ma da fattori esterni. Se si legge una proprietà come SomeForm.Width, e poi - dopo che l'utente ha ridimensionato il form - lo si legge di nuovo, il valore sulla seconda lettura sarà diverso. Sebbene sia possibile che l'esecuzione del primo DateTime.Now richieda abbastanza tempo da influenzare il valore letto dal secondo, tale effetto non sarebbe diverso da qualsiasi altra funzione la cui esecuzione ha richiesto la stessa quantità di tempo.
supercat

4

Il modo in cui usiamo le proprietà a volte mi irrita. Mi piace pensarli come l'equivalente dei metodi getFoo () e setFoo () di Java. Ma non lo sono.

Se le linee guida sull'utilizzo delle proprietà stabiliscono che le proprietà dovrebbero essere in grado di essere impostate in qualsiasi ordine in modo che la serializzazione possa funzionare, allora sono inutili per la convalida del tempo di setter. Se vieni da uno sfondo in cui ti piace impedire a un oggetto di permettere a se stesso di entrare in uno stato non valido, le proprietà non sono la tua soluzione. A volte non riesco a vedere come siano migliori dei membri pubblici, dal momento che siamo così limitati nel tipo di cose che dovremmo fare nelle proprietà.

A tal fine, ho sempre desiderato (questo è principalmente pensare ad alta voce qui, vorrei solo fare qualcosa del genere) di poter estendere in qualche modo la sintassi delle proprietà. Immagina qualcosa di simile:


private string password;

public string Password
{
    // Called when being set by a deserializer or a persistence
    // framework
    deserialize
    {
       // I could put some backward-compat hacks in here. Like
       // weak passwords are grandfathered in without blowing up
       this.password = value;
    }
    get
    {
       if (Thread.CurrentPrincipal.IsInRole("Administrator"))
       {
           return this.password;
       }
       else
       {
           throw new PermissionException();
       }
    }
    set
    {
       if (MeetsPasswordRequirements(value))
       {
           throw new BlahException();
       }
       this.password = value;
    }
    serialize
    {
        return this.password;
    }
}

Non sono sicuro se sia utile o come sarebbe l'accesso a quelli. Ma vorrei solo poter fare di più con le proprietà e trattarle davvero come metodi get and set.


3
Credo che forniscano l'interfaccia ISerializable e il costruttore implicito per fare cose del genere, ovvero quando non si desidera che il serializzatore chiami solo le proprietà. Mentre un po 'più di lavoro, sembra che tu stia già facendo la maggior parte di quel lavoro con il tuo metodo.
Guvante

4

I metodi di estensione sono carini ma sono un brutto modo per risolvere problemi che avrebbero potuto essere risolti in modo più pulito con veri mixin (guarda ruby ​​per vedere di cosa sto parlando), a proposito di mixin. Un modo davvero carino per aggiungerli alla lingua sarebbe stato quello di consentire l'uso di generici per l'ereditarietà. Questo ti permette di estendere le classi esistenti in un bel modo orientato agli oggetti:

public class MyMixin<T> : T
{
    // etc...
}

questo può essere usato in questo modo per estendere una stringa, ad esempio:

var newMixin = new MyMixin<string>();

È molto più potente dei metodi di estensione perché ti consente di sovrascrivere i metodi, ad esempio per avvolgerli consentendo funzionalità simili a AOP all'interno del linguaggio.

Scusa per lo sfogo :-)


5
Interessante, ma preferisco i metodi di estensione. Se ottengo una libreria che contiene un sacco di metodi di estensione per le stringhe, non voglio dover modificare tutti i riferimenti di stringa a MyMixin <string> per ottenere il nuovo materiale. È un po 'minore, certo, ma l'aggiunta trasparente di metodi è ciò che rende i metodi di estensione così belli.
RCIX

A proposito, lo sapevi che funziona già?
RCIX

2
Non vedo come LINQ potrebbe funzionare in questo modo
BlueRaja - Danny Pflughoeft

2
@ RCIX: I mixin suonano come pensavo che i metodi di estensione dovrebbero funzionare. Il problema nel rendere impliciti i metodi di estensione è che significa che i membri della classe reale devono avere la priorità sui metodi di estensione. Se viene definito un metodo di estensione Graphics.DrawParallelogram (Pen p, Point v1, Point v2, Point v3) e successivamente viene aggiunta una funzione DrawParallelogram a System.Graphics che utilizza i punti in un ordine diverso, il codice che utilizza il metodo di estensione si interromperà senza avvertimento. A proposito, ci sarebbero stati problemi nell'usare due punti per i metodi di estensione (ad esempio object..method ()?)
supercat

3

Microsoft non risolverà bug evidenti nel framework e non fornirà hook in modo che gli utenti finali possano risolverli.

Inoltre, non è possibile applicare la patch binaria agli eseguibili .NET in fase di esecuzione e non è possibile specificare versioni private delle librerie .NET framework senza applicare la patch binaria alle librerie native (per intercettare la chiamata di caricamento) e ILDASM non è ridistribuibile, quindi non posso automatizzare comunque la patch.


1
Quali ovvi bug del framework intendi?
Robert Rossney,

1
# 1 Fare clic su un controllo figlio parzialmente visibile di un controllo scorrevole. Il controllo viene spostato nella visualizzazione prima di ricevere l'evento MouseDown, facendo in modo che il clic si trovi da qualche altra parte sul controllo del previsto. È peggio nelle viste ad albero, dove attiva anche un'operazione di trascinamento.
Joshua,


3
  • Essere in grado di invocare un metodo di estensione su una variabile nulla è discutibile, ad es

    oggetto a = null; a.MyExtMethod (); // questo è richiamabile, supponiamo che da qualche parte abbia definito MyExtMethod

    Potrebbe essere utile ma è ambiguo su argomenti di eccezione di riferimento nullo.

  • Un "difetto" di denominazione. 'C' di "configurazione" in System.configuration.dll dovrebbe essere in maiuscolo.

  • La gestione delle eccezioni. L'eccezione dovrebbe essere rilevata o lanciata forzatamente come in Java, il compilatore dovrebbe controllarla al momento della compilazione. Gli utenti non devono fare affidamento sui commenti per le informazioni sulle eccezioni all'interno della chiamata di destinazione.


3
Molto utile, però - ho un metodo di estensione "ThrowIfNull` per il controllo dei parametri ;-p
Marc Gravell

2
Puoi farlo? ugh ThrowIfNull è un'estensione interessante ma sembra sbagliata.
JoshBerke,

1
In CLR semplice è possibile richiamare metodi di istanza su riferimenti null e se il metodo non accede all'oggetto o ai suoi campi, la chiamata non genera un'eccezione di riferimento null. (Non puoi farlo in C # perché usa callvirt anche per metodi non virtuali)
Pop Catalin

7
l'eccezione è assolutamente sbagliata. Non puoi fallire velocemente se DEVI catturare ogni dannata eccezione che potrebbe essere lanciata nello stack di chiamate. Ma vorrei che fosse molto più facile identificare tutte le eccezioni che potrebbero essere lanciate in una particolare chiamata e il suo stack di chiamate risultante ...

3
@ Will: La gestione delle eccezioni è icky sia in Java che in .net, poiché il meccanismo utilizzato lega intimamente tre concetti che sono in qualche modo correlati ma anche un po 'ortogonali: (1) Che tipo di cosa (i) è andata storta (un errore di limiti di array, un timeout di I / O, ecc.); (2) Se un determinato codice deve agire di conseguenza; (3) A che punto il problema dovrebbe essere considerato "risolto". Considera una routine che dovrebbe mutare un oggetto con dati letti da un oggetto IEnumerable. Cosa dovrebbe accadere se si verifica un'eccezione nell'elaborazione di tale IEnumerable?
supercat

3

Il metodo .Parameters.Add () su SqlCommand nella V1 del framework è stato progettato in modo orribile: uno degli overload fondamentalmente non funzionerebbe se si passasse un parametro con un valore (int) di 0 - questo li ha portati a creare il metodo .Parameters.AddWithValue () sulla classe SqlCommand.


Sono d'accordo, ma penso che tu intenda il metodo SqlCommand.Parameters.Add ().
Matt Peterson

3
  1. Non ci sono sottoinsiemi di ICollection<T>e IList<T>; come minimo, un'interfaccia di raccolta covariante di sola lettura IListSource<out T>(con un enumeratore, indicizzatore e Count) sarebbe stata estremamente utile.
  2. .NET non supporta delegati deboli . Le soluzioni alternative sono nella migliore delle ipotesi goffe e le soluzioni alternative lato ascoltatore sono impossibili in caso di attendibilità parziale (è richiesto ReflectionPermission).
  3. È vietata l'unificazione generica dell'interfaccia anche quando ha senso e non causa problemi.
  4. A differenza di C ++, i tipi restituiti covarianti non sono consentiti in .NET
  5. Non è possibile confrontare bit per bit due tipi di valore per l'uguaglianza. In una struttura dati funzionale " persistente ", stavo scrivendo un fileTransform(Sequence<T>, Func<T,T>) funzione che doveva determinare rapidamente se la funzione restituisce lo stesso valore o un valore diverso. Se la funzione non modifica la maggior parte / tutti i suoi argomenti, la sequenza di output può condividere parte della memoria dalla sequenza di input. Senza la possibilità di confrontare bit per bit qualsiasi tipo di valore T, è necessario utilizzare un confronto molto più lento, il che danneggia enormemente le prestazioni.
  6. .NET non sembra in grado di supportare le interfacce ad-hoc (come quelle offerte in Go o Rust) in modo performante. Tali interfacce ti avrebbero permesso di eseguire il cast List<T>su un ipotetico IListSource<U>(dove T: U) anche se la classe non implementa esplicitamente quell'interfaccia. Esistono almeno tre diverse librerie (scritte in modo indipendente) per fornire questa funzionalità (con svantaggi in termini di prestazioni, ovviamente - se fosse possibile una soluzione alternativa perfetta, non sarebbe giusto definirla un difetto in .NET).
  7. Altri problemi di prestazioni: IEnumerator richiede due chiamate di interfaccia per iterazione. I puntatori a metodo semplice (delegati aperti di dimensioni IntPtr) o delegati di tipo valore (IntPtr * 2) non sono possibili. Gli array a dimensione fissa (di tipo arbitrario T) non possono essere incorporati nelle classi. Non c'è WeakReference<T>(puoi facilmente scrivere il tuo, ma userà i cast internamente).
  8. Il fatto che tipi di delegato identici siano considerati incompatibili (nessuna conversione implicita) è stato un fastidio per me in alcune occasioni (ad esempio Predicate<T>vs Func<T,bool>). Spesso vorrei che potessimo avere una tipizzazione strutturale per interfacce e delegati, per ottenere un accoppiamento più flessibile tra i componenti, perché in .NET non è sufficiente che le classi in DLL indipendenti implementino la stessa interfaccia - devono anche condividere un riferimento comune a una terza DLL che definisce l'interfaccia.
  9. DBNull.Valueesiste anche se nullsarebbe servito ugualmente bene allo stesso scopo.
  10. C # non ha operatore ?? =; devi scrivere variable = variable ?? value. In effetti, ci sono alcuni punti in C # che mancano inutilmente di simmetria. Ad esempio puoi scrivere if (x) y(); else z();(senza parentesi graffe), ma non puoi scrivere try y(); finally z();.
  11. Quando si crea un thread, è impossibile fare in modo che il thread figlio erediti i valori locali del thread dal thread padre. Non solo il BCL non lo supporta, ma non puoi implementarlo da solo a meno che non crei tutti i thread manualmente; anche se ci fosse un evento di creazione di thread, .NET non può dirti i "genitori" o "figli" di un determinato thread.
  12. Il fatto che ci siano due diversi attributi di lunghezza per diversi tipi di dati, "Lunghezza" e "Conteggio", è un fastidio minore.
  13. Potrei andare avanti all'infinito sul design scadente di WPF ... e WCF (sebbene abbastanza utile per alcuni scenari) è anche pieno di verruche. In generale, il rigonfiamento, la mancanza di intuitività e la documentazione limitata di molte delle più recenti sottolibrerie del BCL mi rendono riluttante a usarle. Molte delle nuove cose avrebbero potuto essere molto più semplici, più piccole, più facili da usare e da capire, più liberamente accoppiate, meglio documentate, applicabili a più casi d'uso, più veloci e / o più fortemente tipizzate.
  14. Sono stato spesso morso dall'accoppiamento inutile tra proprietà getter e setter: in una classe derivata o un'interfaccia derivata, non puoi semplicemente aggiungere un setter quando la classe base o l'interfaccia base ha solo un getter; se si sovrascrive un getter, non è consentito definire un setter; e non puoi definire il setter come virtuale ma il getter come non virtuale.

Sono d'accordo con te su un sottoinsieme di IList<T>, anche se userei IReadableByIndex<out T>e IAppendable<in T>. Molte delle tue altre cose sono strilli con cui sarei d'accordo anch'io.
supercat

È un nome davvero lungo. Forse potremmo scendere a compromessi IListReader<T>;) - Uso la parola "sorgente" come il contrario di "sink" (un'interfaccia di sola scrittura).
Qwertie

Forse IListSource<in T>o IReadableList<out T>. Può essere utile avere tipi di interfaccia di base che includono metodi che non esistono in tutti i derivati, anche se penso che spesso sia bene avere interfacce un po 'specializzate. Ad esempio, si potrebbe avere un IList<T>che contiene metodi di ridimensionamento che possono o non possono funzionare e un IResizableList<T>che implementa gli stessi metodi, ma garantisce che dovrebbero funzionare. Un tale approccio può essere utile nei casi in cui un campo potrebbe contenere l'unico riferimento esistente a un elenco modificabile o un riferimento condiviso a uno immutabile.
supercat

In tal caso, il codice che vuole modificare il contenuto della lista controllerebbe se si tratta di un tipo modificabile e, in caso contrario, genererebbe una nuova istanza mutabile contenente gli stessi elementi dell'elenco immutabile e quindi inizierebbe a usarlo. Sarebbe fastidioso se il codice dovesse costantemente digitare il campo ogni volta che desidera utilizzare un metodo di mutazione su di esso.
supercat

@supercat Questo è solo fastidioso perché C # non fornisce un modo davvero semplice per verificare che un'interfaccia sia implementata e usarla immediatamente. MS dovrebbe aggiungere una funzionalità linguistica per renderlo più semplice. La mia tecnica preferita sarebbe un'espressione vincolante if (rl:(list as IResizableList<T>) != null) rl.Add(...);, ma ci sono altre proposte. In qualità di autore di varie raccolte e adattatori di raccolte, ciò che mi infastidisce è scrivere molti metodi fittizi che generano eccezioni. In qualità di fan della sicurezza dei tipi, non voglio che mi sia permesso chiamare metodi illegali. Un fan di IntelliSense, non voglio vederli elencati.
Qwertie

2

Una cosa che mi ha colpito in 1.x è stata quando si utilizza il System.Xml.XmlValidatingReader, la ValidationEventHandlers ValidationEventArgsnon espone il sottostante XmlSchemaException(contrassegnato come interno) che ha tutte le informazioni utili come linenumbere position. Invece ci si aspetta che lo analizzi dalla proprietà della stringa del messaggio o utilizzi la riflessione per estrarlo. Non così buono quando si desidera restituire un errore più disinfettato all'utente finale.


1

Non mi piace che non puoi usare i valori di un enum in un altro enum, ad esempio:

    enum Colors { white, blue, green, red, black, yellow }

    enum SpecialColors { Colors.blue, Colors.red, Colors.Yellow } 

2
Questo non ha nemmeno senso, però. typeof(Color)! = typeof(SpecialColors).
Kirk Woll

10
È abbastanza facile da fare:enum SpecialColors { blue = Colors.blue, red = Colors.red, yellow = Colors.Yellow }
Trystan Spangler

0

Le variabili tipizzate in modo implicito sono state implementate male IMO. So che dovresti davvero usarli solo quando lavori con espressioni Linq, ma è fastidioso non poterli dichiarare al di fuori dell'ambito locale.

Da MSDN:

  • var può essere utilizzato solo quando una variabile locale viene dichiarata e inizializzata nella stessa istruzione; la variabile non può essere inizializzata su null, su un gruppo di metodi o su una funzione anonima.
  • var non può essere utilizzato sui campi nell'ambito della classe.
  • Le variabili dichiarate utilizzando var non possono essere utilizzate nell'espressione di inizializzazione. In altre parole, questa espressione è legale: int i = (i = 20); ma questa espressione produce un errore in fase di compilazione: var i = (i = 20);
  • Non è possibile inizializzare più variabili di tipo implicito nella stessa istruzione.
  • Se un tipo denominato var è nell'ambito, la parola chiave var si risolverà in quel nome di tipo e non verrà trattata come parte di una dichiarazione di variabile locale tipizzata in modo implicito.

Il motivo per cui penso che sia una cattiva implementazione è che lo chiamano var, ma è molto lontano dall'essere una variante. È solo una sintassi abbreviata per non dover digitare il nome completo della classe (tranne quando viene utilizzato con Linq)


Sicuramente anonimo (nuovo {...}), non implicitamente digitato (var)
Marc Gravell

Rileggi solo quello che ho postato ed è stato sbagliato. Intendevo variabili tipizzate implicitamente tho
lomaxx

1
Eric Lippert ha spiegato perché var non può essere utilizzato al di fuori dei metodi, è fondamentalmente perché crea una scatola nera di possibilità. blogs.msdn.com/ericlippert/archive/2009/01/26/…
Guvante

1
var non è inteso come variante! è precisamente per la stenografia (soprattutto i tipi anon). goditi la dinamica quando arriva ...
ShuggyCoUk
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.