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.
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.
Risposte:
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:
T : new(string)o doveT : new(string, int)var e = new Foo(); e { Bar = baz };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,System.IOclassi, come Stream, sono un po 'mal progettate; qualsiasi interfaccia che richiede alcune implementazioni per essere lanciata NotSupportedExceptionè un cattivo design,IListdovrebbe essere molto più semplice di quello che è; in effetti, questo può essere vero per molte delle interfacce di raccolta concreta, come ICollection,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,
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.IArithmetic; sono possibili anche altre utili interfacce operatore condivise,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.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:
Piuttosto per favore? :-)
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.
Reset()metodo è IEnumerator<T>stato un errore (per i blocchi iteratori, le specifiche del linguaggio richiedono anche che questo generi un'eccezione)IEnumerable<out T>e Func<in T, out TResult>, ma non tipi concreti (come List<T>).ApplicationException piuttosto cadde in disgrazia - è stato un errore?Contains, quindi Add), quindi una raccolta che sincronizza operazioni distinte non è poi così utile
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.using/ lock, magari consentendo loro di condividere una sintassi riutilizzabile (estensibile?); puoi simularlo restituendo IDisposablee usando using, ma avrebbe potuto essere più chiaroFoo(SqlConnection! connection)(che inietta un controllo nullo / throw) sarebbe carina (al contrario di int?ecc.)
dynamic, oppure puoi abilitarlo in questo modoforeachespansione, il che significa che i metodi anon / lambda catturano la singola variabile, piuttosto che una per iterazione (doloroso con threading / async / ecc)
ApplicationExceptionstato un errore, non utile come speravano. Hanno anche detto che System.Exceptionavrebbe dovuto essere abstract.
TextWriter è una classe base di StreamWriter. wtf?
Questo mi confonde sempre all'estremo.
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.
class Foo { new(int j) {i = j} int i; }
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.
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)
{
}
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.
DBNull.Value, quando esso nullstesso sarebbe stato perfettamente adeguato a rappresentare NULL. Fortunatamente LINQ-to-SQL usa solo null per NULL.
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.
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? :)
(int x) => x * (x -1);può significare Func<int, int>o può significareExpression<Func<int, int>>
+non è definito per object.
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).
La proprietà ICollection.SyncRoot :
I generici avrebbero dovuto esserci dall'inizio:
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.
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.
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.
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.
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.
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.
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 #…).
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?
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.
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
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.
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.
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.
IEnumerator?
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)
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.
DateTime.Nowè la condizione di gara più ovvia al mondo, ma +1 per il resto
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.
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 :-)
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.
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.
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.
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.Transform(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.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).WeakReference<T>(puoi facilmente scrivere il tuo, ma userà i cast internamente).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.DBNull.Valueesiste anche se nullsarebbe servito ugualmente bene allo stesso scopo.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();.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.
IListReader<T>;) - Uso la parola "sorgente" come il contrario di "sink" (un'interfaccia di sola scrittura).
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.
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.
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.
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 }
typeof(Color)! = typeof(SpecialColors).
enum SpecialColors { blue = Colors.blue, red = Colors.red, yellow = Colors.Yellow }
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:
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)