C # (.NET) Designfehler [geschlossen]

85

Was sind einige der größten Designfehler in C # oder .NET Framework im Allgemeinen?

Beispiel: Es gibt keinen nicht nullbaren Zeichenfolgentyp und Sie müssen beim Abrufen von Werten aus einem IDataReader nach DBNull suchen.

Rauhotz
quelle
Inwiefern sind diese Designfehler?
Julia
Mit IDataReader können Sie IsDBNull verwenden, anstatt manuell zu prüfen
Marc Gravell
9
Cue Jon Skeet, um über versiegelte Klassen zu sprechen;)
Johnc
3
Es ist ziemlich einfach, IDataReader mit einer Erweiterungsmethode zu reparieren: siehe weblogs.asp.net/skillet/archive/2008/06/18/… .
Robert Rossney
@lagerdalek - Ich würde diesen Kommentar +1 geben, wenn ich könnte; gut erinnert
Marc Gravell

Antworten:

39

Ich stimme diesem Beitrag nachdrücklich zu (für diejenigen, die das Fehlen von ToString vermasseln, gibt es ein Debugger-Attribut, um ein benutzerdefiniertes Format für Ihre Klasse bereitzustellen).

Zusätzlich zu der obigen Liste möchte ich die folgenden angemessenen Anforderungen hinzufügen:

  1. nicht nullbare Referenztypen als Ergänzung zu nullbaren Werttypen,
  2. Erlaube das Überschreiben des leeren Konstruktors einer Struktur.
  3. generische Typeinschränkungen zulassen, um versiegelte Klassen anzugeben,
  4. Ich stimme hier einem anderen Poster zu, das willkürliche Konstruktorsignaturen angefordert hat, wenn es als Einschränkungen verwendet wurde, d. H. wo T : new(string)oder woT : new(string, int)
  5. Ich stimme hier auch einem anderen Poster über das Beheben von Ereignissen zu, sowohl für leere Ereignislisten als auch in der gleichzeitigen Einstellung (obwohl letzteres schwierig ist).
  6. Operatoren sollten als Erweiterungsmethoden definiert werden und nicht als statische Methoden der Klasse (oder zumindest nicht nur als statische Methoden).
  7. statische Eigenschaften und Methoden für Schnittstellen zulassen (Java hat dies, C # jedoch nicht),
  8. Ereignisinitialisierung in Objektinitialisierern zulassen (derzeit sind nur Felder und Eigenschaften zulässig),
  9. Warum kann die Syntax "Objektinitialisierer" nur beim Erstellen eines Objekts verwendet werden? Warum nicht jederzeit verfügbar machen, dh.var e = new Foo(); e { Bar = baz };
  10. quadratisches Aufzählungsverhalten korrigieren ,
  11. Alle Sammlungen sollten unveränderliche Snapshots für die Iteration enthalten (dh das Mutieren der Sammlung sollte den Iterator nicht ungültig machen).
  12. Tupel lassen sich leicht hinzufügen, ein effizienter geschlossener algebraischer Typ wie " Either<T>" jedoch nicht. Daher würde ich gerne einen geschlossenen algebraischen Typ deklarieren und einen umfassenden Mustervergleich erzwingen (im Grunde erstklassige Unterstützung für das Besuchermuster, aber weitaus effizienter); Nehmen Sie einfach Aufzählungen, erweitern Sie sie mit umfassender Unterstützung für den Mustervergleich und lassen Sie keine ungültigen Fälle zu.
  13. Ich würde die Unterstützung für den Mustervergleich im Allgemeinen lieben, aber zumindest für das Testen von Objekttypen. Ich mag auch die Switch-Syntax, die in einem anderen Beitrag hier vorgeschlagen wurde.
  14. Ich stimme einem anderen Beitrag zu, dass die System.IOKlassen Streametwas schlecht gestaltet sind; Jede Schnittstelle, für deren Ausführung einige Implementierungen erforderlich sind, NotSupportedExceptionist ein schlechtes Design.
  15. IListsollte viel einfacher sein als es ist; in der Tat kann dies für viele der konkreten Sammlungsschnittstellen zutreffen, wie z ICollection.
  16. Zu viele Methoden lösen Ausnahmen aus, wie z. B. IDictionary.
  17. Ich würde eine Form von geprüften Ausnahmen bevorzugen, die besser ist als die in Java verfügbare (siehe die Forschung zu Typ- und Effektsystemen, wie dies getan werden kann).
  18. Behebung verschiedener lästiger Eckfälle bei der generischen Methodenüberlastungsauflösung; Versuchen Sie beispielsweise, zwei überladene Erweiterungsmethoden bereitzustellen, eine für Referenztypen und eine für nullfähige Strukturtypen, und sehen Sie, wie Ihre Typinferenz dies mag.
  19. bieten eine Möglichkeit, Feld- und Mitgliedsnamen für Schnittstellen wie diese INotifyPropertyChanged, die den Feldnamen als Zeichenfolge verwenden , sicher zu reflektieren ; Sie können dies tun, indem Sie eine Erweiterungsmethode verwenden, die ein Lambda mit a MemberExpression, dh, verwendet. () => Foo, aber das ist nicht sehr effizient,
    • Update: C # 6.0 hat den nameof()Operator für einzelne Mitgliedsnamen hinzugefügt , funktioniert jedoch nicht in Generika ( nameof(T) == "T"anstelle des tatsächlichen Namens des Typarguments: Sie müssen dies noch tun typeof(T).Name). Außerdem können Sie keine "Pfad" -String abrufen , z. B. nameof(this.ComplexProperty.Value) == "Value"Einschränkung seiner möglichen Anwendungen.
  20. Zulassen von Operatoren in Schnittstellen und Implementieren aller Kernnummerntypen IArithmetic; andere nützliche gemeinsam genutzte Bedienerschnittstellen sind ebenfalls möglich,
  21. Machen Sie es schwieriger, Objektfelder / -eigenschaften zu mutieren, oder erlauben Sie zumindest das Annotieren unveränderlicher Felder und lassen Sie die Typprüfung dies erzwingen (behandeln Sie es einfach als Nur-Getter-Eigenschaft, um Himmels willen, es ist nicht schwer!). Vereinheitlichen Sie Felder und Eigenschaften auf vernünftigere Weise, da es keinen Sinn macht, beides zu haben. Die automatischen Eigenschaften von C # 3.0 sind ein erster Schritt in diese Richtung, aber sie gehen nicht weit genug.
    • Update: Während C # das readonlySchlüsselwort hatte und C # 6.0 schreibgeschützte automatische Eigenschaften hinzufügte, ist es nicht so streng wie die echte Sprachunterstützung für unveränderliche Typen und Werte.
  22. das Deklarieren von Konstruktoren vereinfachen; Ich mag den Ansatz von F #, aber der andere Beitrag hier, der einfach "neu" anstelle des Klassennamens erfordert, ist zumindest besser.

Das reicht wohl erstmal. Dies sind alles Irritationen, auf die ich in der letzten Woche gestoßen bin. Ich könnte wahrscheinlich stundenlang weitermachen, wenn ich mich wirklich darauf konzentrieren würde. C # 4.0 fügt bereits benannte, optionale und Standardargumente hinzu, die ich ausdrücklich befürworte.

Nun zu einer unvernünftigen Bitte:

  1. Es wäre wirklich sehr schön, wenn C # / CLR den Typkonstruktor-Polymorphismus unterstützen könnte, d. h. Generika über Generika,

Bitte schön? :-)

naasking
quelle
1
In Bezug auf # 1 muss jeder Typ entweder einen Standardwert haben, oder das System muss ein Mittel zum Ausführen eines Konstruktors bereitstellen, wenn eine Variable oder ein Feld eines bestimmten Typs zugewiesen wird. Ich würde letzteres bevorzugen (ein Ableger von # 2), aber # 1 könnte untergebracht werden, wenn nicht virtuelle Methoden / Eigenschaften dekoriert werden könnten, um anzugeben, dass sie ohne Nullprüfung aufgerufen werden sollen. Dies würde es Dingen wie "String" -Feldern ermöglichen, sich so zu verhalten, als würden sie standardmäßig eine leere Zeichenfolge anstelle von null verwenden (da die statische "Länge" -Funktion von String 0 zurückgeben könnte, wenn sie für eine Nullzeichenfolge aufgerufen wird).
Supercat
1
In Bezug auf # 2 wäre es im Allgemeinen nützlich, wenn Strukturen nicht nur einen anderen Konstruktor als Fill-with-Zero angeben könnten, sondern auch einen anderen Kopierkonstruktor als Byte-für-Byte-Kopie. Eigentlich würde ich mir wirklich ein .net-ähnliches Framework wünschen, das gut erkennen kann, dass Entitäten möglicherweise Wert- oder Referenzsemantik haben, und ermöglicht, dass Heap-Objekte vom Werttyp als veränderlich, gemeinsam unveränderlich oder nicht festgeschrieben gekennzeichnet werden (Ein nicht festgeschriebenes Wertobjekt kann mutiert werden, wenn sein Modus zuerst auf veränderbar umgestellt wird, oder es kann freigegeben werden, wenn sein Modus zuerst auf gemeinsam genutzt wird.)
Supercat
1
Hervorragende Punkte! Für # 1 ist eine Annotation eines Typsystems die übliche Lösung, aber ich bevorzuge es, Konstruktoreinschränkungen über Typvariablen wie T: new () zu verbreiten. Betreff: # 2, guter Punkt zu Kopierkonstruktoren, aber ich würde mich über allgemeinere Konstruktoren in der oben beschriebenen Richtung freuen. Noch besser wäre es, Konstruktoren als unterschiedliche Methoden vollständig zu eliminieren und sie einfach zu statischen Methoden zu machen. Dies ermöglicht einfachere und allgemeinere Konstruktionsmuster, insbesondere wenn statische Methoden für Schnittstellen zulässig sind. Konstruktoren als statische Methoden + statische Methoden in Schnittstellen lösen auch # 1.
Naasking
3
# 3: Was bringt es, eine versiegelte Klasse als generischen Typparameter zu verwenden, z. B. Foo <T> wobei T: string? # 11: Okay, ich habe List<T>eine Million Ts. Wie schlagen Sie vor, dass der Schnappschuss effizient aufgenommen wird? # 21: Verwenden Sie das readonlySchlüsselwort ... obwohl es hier einige gute Vorschläge gibt, sind es meistens nur diese - Vorschläge, keine Designfehler.
Qwertie
2
Dies ist eine sehr interessante Antwort, aber ich denke, wir sollten mit C # 6-Funktionen aktualisieren. Beispiel: Die Punkte 19 und 21 wurden implementiert =)
eduardobr
72
  • Die Reset()Methode IEnumerator<T>war ein Fehler (für Iteratorblöcke verlangt die Sprachspezifikation sogar , dass dies eine Ausnahme auslöst)
  • Die Reflexionsmethoden, die Arrays zurückgeben, waren nach Erics Ansicht ein Fehler
  • Array-Kovarianz war und ist eine Kuriosität
    • Update: C # 4.0 mit .NET 4.0 hat generischen Schnittstellen (wie IEnumerable<out T>und Func<in T, out TResult>, aber keine konkreten Typen (wie List<T>)) Unterstützung für Kovarianten / Kontravarianzen hinzugefügt .
  • ApplicationException eher in Ungnade gefallen - war das ein Fehler?
  • synchronisierte Sammlungen - eine nette Idee, aber in der Realität nicht unbedingt nützlich: Normalerweise müssen Sie ( dann ) mehrere Operationen synchronisieren , sodass eine Sammlung, die unterschiedliche Operationen synchronisiert, nicht allzu nützlich ist ContainsAdd
    • Update: Die System.Collections.ConcurrentTypen , mit TryAdd, GetOrAdd, TryRemove, etc wurden in .NET Framework 4.0 hinzugefügt - obwohl Methoden , die eine Fabrik Delegierten nehmen nicht die Fabrik Garantie wird nur einmal pro Taste aufgerufen werden.
  • Das using/ lockpattern hätte besser genutzt werden können - vielleicht könnten sie eine wiederverwendbare (erweiterbare?) Syntax verwenden; Sie können dies simulieren, indem Sie zurückkehren IDisposableund verwenden using, aber es hätte klarer sein können
  • Iteratorblöcke: Keine einfache Möglichkeit, Argumente vorab zu überprüfen (anstatt träge). Natürlich können Sie zwei verkettete Methoden schreiben, aber das ist hässlich
  • einfachere Unveränderlichkeit wäre schön; C # 4.0 hilft ein bisschen , aber nicht genug
  • Keine Unterstützung für "Dieser Ref-Typ-Parameter kann nicht null sein" - obwohl Verträge (in 4.0) dabei etwas helfen. Aber Syntax wie Foo(SqlConnection! connection)(die einen Null-Check / einfügt throw) wäre schön (im Gegensatz zu int?etc)
  • mangelnde Unterstützung von Operatoren und Nicht-Standardkonstruktoren mit Generika; C # 4.0 löst dieses Problem etwas mit dynamic, oder Sie können es ermöglichen , wie dies
  • Die Iteratorvariable wird außerhalb der Zeit in der foreachErweiterung deklariert , was bedeutet, dass anon-Methods / Lambdas die einzelne Variable erfassen und nicht eine pro Iteration (schmerzhaft beim Threading / async / etc).
Marc Gravell
quelle
IEnumerable! = IEnumerable <Objekt> ist in der Tat seltsam
Rauhotz
2
Nun, die IEnumerable-Sache ist ein 1.1-Kater; Sie können .Cast <Objekt> () mindestens mit LINQ verwenden
Marc Gravell
8
Die BCL-Leute sagten, das ApplicationExceptionsei ein Fehler - nicht nützlich, wie sie gehofft hatten. Sie sagten auch, das System.Exceptionhätte sein sollen abstract.
Jay Bazuzi
2
Nicht nullbar: Es sollte ein Kompilierungsfehler sein, einen regulären Referenztyp T an etwas zu übergeben, das ein nicht nullbares T annimmt! (Genau wie Sie int? nicht an int übergeben können). Die andere Richtung zu passieren ist natürlich in Ordnung.
Jay Bazuzi
1
@ Jon Harrop: IMHO sollte es Unterstützung für unveränderliche Arrays und für schreibgeschützte Array-Referenzen geben. Möglicherweise auch für einige andere Array-Varianten (z. B. ein "anpassbares Array" (indirekte Referenz) oder eine Array-Referenz mit einem Offset und einer Bindung).
Supercat
60

TextWriter ist eine Basisklasse von StreamWriter. wtf?

Das verwirrt mich immer bis zum Äußersten.

Quibblesome
quelle
19
+1 Ich muss es jedes Mal nachschlagen. (Whaddaya meine, ich kann keinen neuen TextWriter ()?)
Nicholas Piasecki
1
Gott sei Dank ... Ich dachte, es sei nur ich.
IJ Kennedy
? Es ist immer etwas, das Text schreibt, aber nur StreamWriter tut dies für einen Stream. Scheint ziemlich einfach zu sein.
Jon Hanna
4
Der Name StreamWriter macht die Tatsache, dass er Text schreibt, IMO nicht offensichtlich genug. Es klingt isoliert, als würde es nur Bytes schreiben, und TextWriter wäre eine ausgefallene Implementierung, die die API von (string toWrite) für Sie in Bytes konvertiert. Nun, wenn es StreamTextWriter heißt, dann sicher, wäre es sofort offensichtlich, aber ein bisschen lang :(
Quibblesome
44

Ein kleiner C # pet peev - Konstruktor verwendet die C ++ / Java-Syntax, bei der der Konstruktor den gleichen Namen wie die Klasse hat.

New()oder ctor()wäre viel schöner gewesen.

Tools wie Coderush machen dies zwar weniger zu einem Problem beim Umbenennen von Klassen, aber New () bietet aus Gründen der Lesbarkeit eine große Klarheit.

Scott Weinstein
quelle
Ähm, wie würde es wissen, wovon Sie versuchen, eine neue Instanz zu erstellen?
BlueRaja - Danny Pflughoeft
4
@BlueRaja: Scott bezieht sich auf die Benennung von Konstruktoren in Klassen. class Foo { new(int j) {i = j} int i; }
dalle
Obwohl ich zu 100% zustimme, dass ctor () oder constructor () besser gewesen wäre (nicht - NewGroßbuchstaben sind gegen die Konvention), zögere ich, es als Designfehler zu bezeichnen. Sie wollten bestehende C ++ / Java-Entwickler gewinnen, und das Ausleihen vieler dummer alter syntaktischer Konventionen half ihnen wohl dabei, ihr Ziel zu erreichen.
Qwertie
Im Zusammenhang mit dieser Frage: stackoverflow.com/questions/32101993/c-sharp-sorted-linkedlist Es gibt keine Möglichkeit (nur mit .NET), eine LinkedList einfach mit MergeSort, Bucketsort oder einem anderen Sortieralgorithmus zu sortieren, aber mit Linq, das viel langsamer ist als eine Ad-hoc-Implementierung.
CoffeDeveloper
29

Ich verstehe nicht, dass du es nicht kannst

wo T: neu (U)

Sie deklarieren also, dass der generische Typ T einen nicht standardmäßigen Konstruktor hat.

bearbeiten:

Ich möchte das machen:

public class A 
{
    public A(string text) 
    {

    }
}


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

}
tuinstoel
quelle
Ein generischer Typ gibt an, wie Sie Objekte dieses Typs verwenden, dh deren Schnittstelle. Der Konstruktor ist ein Implementierungsdetail dieser Schnittstelle, das nicht das Anliegen des Verbrauchers ist. Wenn Sie eine parametrisierte Instanz erstellen müssen, verwenden Sie eine Factory.
Bryan Watts
Für Informationen gibt es in MiscUtil Code, mit dem nicht standardmäßige Konstruktoren (in Generika) effizient verwendet werden können, dh ohne Activator.CreateInstance oder Reflection, obwohl keine Überprüfung zur Kompilierungszeit möglich ist.
Marc Gravell
Weil es keinen Sinn macht und bei einigen Anwendungen verwirrend ist. Trotzdem kann es nützlich sein, wenn Sie mit unveränderlichen Objekten arbeiten.
Pop Catalin
7
Im Allgemeinen ist das Fehlen von Einschränkungen für Mitglieder ärgerlich, ja.
MichaelGG
3
Amen, ich wollte das schon immer
Steve
20

Ich bin wirklich überrascht, dass ich als erster diesen erwähne:

ADO.NET-typisierte Datensätze machen nullfähige Spalten nicht als Eigenschaften nullbarer Typen verfügbar. Sie sollten in der Lage sein, dies zu schreiben:

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

Stattdessen muss man das schreiben, was einfach nur dumm ist:

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

Dies war in .NET 2.0 ärgerlich, und es ist jetzt noch ärgerlicher, dass Sie in Ihren netten, ordentlichen LINQ-Abfragen Jiggery-Pokery wie oben verwenden müssen.

Es ist auch ärgerlich, dass die generierte Add<TableName>RowMethode für den Begriff der nullbaren Typen ähnlich unempfindlich ist. Dies gilt umso mehr, als die generierten TableAdapterMethoden dies nicht tun .

In .NET gibt es nicht viel, was mir das Gefühl gibt, dass das Entwicklerteam sagte: "Okay, Jungs, wir sind nah genug dran - versenden Sie es!" Aber das tut es sicher.

Robert Rossney
quelle
Ich stimme voll und ganz zu! Das nervt mich jedes Mal, wenn ich es benutzen muss (was oft der Fall ist). Argh! +1
Eyvind
2
Das Mindeste, was sie tun könnten, wäre, eine neue DataSetV2-Klasse (schlechter Name - nur aus Gründen des Arguments) zu entwickeln, die durchgehend nullfähige Werttypen anstelle von DBNull verwendet.
Christian Hayter
Vergessen wir nicht die Absurdität, einen besonderen Wert zu verlangen DBNull.Value, wenn nuller für die Darstellung von NULL vollkommen ausreichend gewesen wäre. Glücklicherweise verwendet LINQ-to-SQL nur null für NULL.
Qwertie
Tatsächlich ist diese Absurdität der Fels, auf dem das gesamte absurde Gebäude errichtet wurde.
Robert Rossney
20
  1. Ich bin kein großer Fan der Klassen Stream, StringWriter, StringReader, TextReader, TextWriter ... es ist einfach nicht intuitiv, was was ist.
  2. IEnumerable.Reset löst eine Ausnahme für Iteratoren aus. Ich habe einige Komponenten von Drittanbietern, die immer das Zurücksetzen aufrufen, wenn sie datengebunden sind. Daher muss ich sie zuerst in eine Liste umwandeln, um diese zu verwenden.
  3. Xml Serializer sollte IDictionary-Elemente serialisiert haben
  4. Ich habe die HttpWebRequest & FTP API total vergessen, was für ein Schmerz in meinem .... (danke für den Kommentar Nicholas, der mich daran erinnert :-)

Bearbeiten
5. Ein weiteres Ärgernis von mir ist, wie System.Reflection.BindingFlags je nach verwendeter Methode unterschiedliche Verwendungszwecke hat. Was bedeutet beispielsweise in FindFields CreateInstance oder SetField? Dies ist ein Fall, in dem sie die Bedeutung hinter dieser Aufzählung überladen haben, was verwirrend ist.

JoshBerke
quelle
1
+1 Ich muss jedes Mal eine der Klassen XmlTextWriter, TextWriter usw. nachschlagen. Gleiches gilt für das HttpWebRequest / Response-Zeug. Völlig unintuitive API dort.
Nicholas Piasecki
+ 1-1 = 0: XmlTextWriter usw. Ich stimme zu, dass es unmöglich ist, aus dem Namen wirklich zu schließen, was sie sind. HttpWebRequest Ich stimme nicht zu, ich finde es ziemlich intuitiv.
AnthonyWJones
Jedem sein eigenes, nehme ich an. Ich denke, mit FTP würde ich eine höhere Abstraktion erwarten als das, was sie haben.
JoshBerke
15

Ich weiß nicht, dass ich sagen würde, dass es sich um einen Designfehler handelt, aber es wäre wirklich schön, wenn Sie einen Lambda-Ausdruck auf die gleiche Weise wie in VB ableiten könnten:

VB:

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

C #

Es wäre schön, wenn dies möglich wäre:

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

Anstatt dies tun zu müssen:

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

Mir ist klar, dass es nicht mehr lange dauert, aber im Code Golf zählt jeder Charakter verdammt! Berücksichtigen sie das nicht, wenn sie diese Programmiersprachen entwerfen? :) :)

BenAlabaster
quelle
3
Microsoft sollte Code Golf beim Entwerfen von Sprachen berücksichtigen?
jrcs3
3
@ Ray Burns: Woher weiß es in VB? VB unterstützt es, also was ist der Unterschied?
BenAlabaster
3
@ RayBurns Typinferenz? Ich benutze das seit 1989.
RD1
3
Lamdas sind in C # homoikonisch. Das Fragment (int x) => x * (x -1);kann bedeuten Func<int, int>oder es kann bedeutenExpression<Func<int, int>>
Scott Weinstein
3
@ BenAlabaster: VB unterstützt spät gebundene arithmetische Operatoren. C # muss sie zur Kompilierungszeit auflösen. Es ist ein Sprachunterschied. Zum Beispiel kann VB zwei Objekte zusammenfügen. C # kann nicht, weil +nicht für definiert ist object.
rekursiv
14
  1. Die System.Object- Klasse:

    • Equals und GetHashCode - nicht alle Klassen sind vergleichbar oder hashbar, sollten auf eine Schnittstelle verschoben werden. IEquatable oder IComparable (oder ähnliches) fällt mir ein.

    • ToString - Nicht alle Klassen können in eine Zeichenfolge konvertiert werden. Sie sollten in eine Schnittstelle verschoben werden. IFormattable (oder ähnliches) fällt mir ein.

  2. Die ICollection.SyncRoot- Eigenschaft:

    • Fördert schlechtes Design, ein externes Schloss ist fast immer nützlicher.
  3. Generika sollten von Anfang an dabei gewesen sein:

    • Der System.Collections- Namespace enthält viele mehr oder weniger veraltete Klassen und Schnittstellen.
dalle
quelle
1
1. Diese Methoden sind so verbreitet, dass entschieden wurde, dass die Oddball-Fälle in Ordnung sind. Ist es wichtig, ob jemand Object.Equals in Ihrer Klasse aufrufen kann? Es ist bekannt, dass es möglicherweise eine Implementierung gibt oder nicht, und dies erfordert: IEquatable, IFormattable für 99% der Klassen ist ungerade.
Guvante
1
Es ist nützlich, beispielsweise ein Wörterbuch mit benutzerdefinierten Objekten als Schlüssel unter Verwendung der Standardreferenzgleichheit erstellen zu können, ohne den benutzerdefinierten Objekten zu diesem Zweck explizit Code hinzufügen zu müssen. Ich würde Finalize als viel größere Verschwendung betrachten (eine bessere Alternative wäre gewesen, Objekte, für die eine Finalisierung erforderlich ist, iFinalizable zu implementieren und sich explizit für die Finalisierung zu registrieren). OTOH, es hätte mehr Unterstützung für iDisposable geben sollen, einschließlich des Aufrufs von Dispose, wenn ein Konstruktor eine Ausnahme auslöst.
Supercat
1
@supercat: Alles was benötigt wird ist ein EqualityComparer<T>.Defaultkorrektes Update . Dann beide var dict = new Dictionary<object, string>(EqualityComparer<object>.Default)und var dict = new Dictionary<object, string>()verwenden Referenzvergleich / Gleichheit.
dalle
1
@supercat: Was Sie beschreiben, ist genau das, was EqualityComparer<T>.Defaulttut. Sie müssen nicht bei jeder Suche nachsehen. Der Vergleicher ist eine Eigenschaft für die DictionaryInstanz, und jeder Dictionaryweiß, welche er verwendet.
dalle
1
@supercat: Ein Wörterbuch muss den allgemeinsten Typ (dh die gemeinsame Basisklasse) als Schlüssel verwenden. Die Verwendung von Strings und DateTime im selben Wörterbuch würde keinen Sinn ergeben, es sei denn, es wird ein Referenzvergleich verwendet, es sei denn, ein benutzerdefinierter Vergleich wird nachgewiesen ist. Denken Sie daran, dass der Name dieses Themas "C # (.NET) Design Flaws" lautet.
Dale
12

Eines der Dinge, die mich irritieren, ist das Predicate<T> != Func<T, bool>Paradoxon. Sie sind beide Delegierte des Typs T -> boolund dennoch nicht zuweisungskompatibel.

Greg Beech
quelle
Es gibt einen Trick mit Delegate.Create und einige Castings, um die Konvertierung
durchzuführen
Das Design der Delegierten im Allgemeinen ist fehlerhaft; Zum Beispiel das Fehlen schwacher Ereignisse (das Implementieren quellenseitiger schwacher Ereignisse ohne besonderen Aufwand des Abonnenten kann nur mit einer Reihe von Reflection und ReflectionPermission erfolgen, siehe codeproject.com/Articles/29922/Weak-Events-in-C ). und Ineffizienz, die sich aus der Anforderung ergibt, dass Delegierte Referenztypen sein müssen (Delegierte wären schneller gewesen und hätten in vielen Fällen 1/3 so viel Speicher verbraucht, wenn sie Werttypen gewesen wären - dann wären sie einfach ein Paar von Zeiger, die Sie auf dem Stapel weitergeben könnten.)
Qwertie
11

Einige Leute (ISVs) wünschen sich, dass Sie es zur Erstellungszeit zu Maschinencode kompilieren und verknüpfen können, um eine native ausführbare Datei zu erstellen, die die dotNet-Laufzeit nicht benötigt.

ChrisW
quelle
Sie sollten in der Lage sein, Ihr Programm vor dem Ausführen zu deaktivieren.
Otávio Décio
Nicht dasselbe wie das Entfernen von Abhängigkeiten der Laufzeit. Sie speichern nur den ersten JIT-Lauf über Ihren Code.
Ed S.
Gibt es nicht ein Verschleierungswerkzeug, das dies tut? Das Framework wird in Ihre Exe eingebettet, sodass Sie es nicht bereitstellen müssen. Ich kann mich nicht an den Namen erinnern ... es ist nicht der Name von
PreEmptive
2
Macht Xenocodes Postbuild das nicht? Es wäre schön, wenn Visual Studio eine Möglichkeit hätte, dies zu tun ...
BenAlabaster
11

Wir wissen so viel über die richtigen OO-Techniken. Entkopplung, vertragliche Programmierung, Vermeidung unzulässiger Vererbung, angemessene Verwendung von Ausnahmen, offenes / geschlossenes Prinzip, Liskov-Substituierbarkeit usw. In den .Net-Frameworks werden noch keine Best Practices verwendet.

Für mich liegt der größte Fehler im Design von .Net nicht auf den Schultern von Riesen. Förderung weniger als idealer Programmierparadigmen für die Masse der Programmierer, die ihre Frameworks verwenden .

Wenn MS dies beachtet hätte, hätte die Welt der Softwareentwicklung in diesem Jahrzehnt große Fortschritte in Bezug auf Qualität, Stabilität und Skalierbarkeit machen können, aber leider scheint sie rückläufig zu sein.

Daniel Paull
quelle
4
+1 für das Ziel-Rant; Ich würde dem die Unfähigkeit hinzufügen, jede Klasse zu unterordnen, das Fehlen von Schnittstellen für grundlegende Klassen und die Zurückhaltung, Framework-Fehler auch nach einigen Jahren zu beheben
Steven A. Lowe
1
Wenn wir auf den Punkt kommen, sagen wir dann, dass die .Net-Frameworks so schlecht sind, dass es schwierig ist, sich darauf zu einigen, welcher Fehler der schlimmste ist? Ich fühle mich nach einer Entlüftung besser und schätze die positive Abstimmung, da ich erwartet hatte, von MS-Fan-Jungs niedergeschrien zu werden.
Daniel Paull
Ich habe so oder so nicht abgestimmt, und trotzdem zögere ich sehr, Abstimmungen abzugeben, aber ich versuche herauszufinden, warum ich mich nur nicht darum kümmere.
Mike Dunlavey
2
Ich denke, Sie sagen "es ist nicht so gut wie es sein könnte", was keine Antwort ist. Nichts ist perfekt. Geben Sie Einzelheiten an.
JCollum
3
Nein, ich sage, dass es zahlreiche Fälle gibt, in denen das Design offensichtlich fehlerhaft ist. Wenn Sie glauben, dass sie es richtig machen, verstehen sie es immer noch falsch. Zum Beispiel mein Beitrag in MSDN-Foren hier: social.msdn.microsoft.com/forums/en-US/wpf/thread/…
Daniel Paull
11

Ich mag die C # switch-Anweisung nicht.

Ich hätte gerne so etwas

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

Also keine Pausen mehr (leicht zu vergessen) und die Möglichkeit, verschiedene Werte durch Kommas zu trennen.

tuinstoel
quelle
Eigentlich denke ich, es wäre noch besser, wenn es entweder eine einzelne Anweisung oder einen Block in geschweiften Klammern erfordern würde - wie alles andere in C #. Ohne die Fähigkeit, durchzufallen, ist die aktuelle Unterbrechungssyntax etwas zwielichtig und beschränkt sich auch nicht auf den Umfang. (AFAIK, es könnte sein, ich weiß nicht)
Tamas Czinege
Ich verstehe nicht, was du meinst. Veröffentlichen Sie hier Ihre eigene 'ideale' switch-Anweisung. Ich möchte nicht durchfallen, sondern durch Kommas getrennte Werte.
Tuinstoel
8
Übrigens stimme ich OP zu. switchist grundsätzlich in allen Sprachen kaputt, die die absichtlich verkrüppelte Version von C emulieren (optimiert für Geschwindigkeit!). VB schneidet viel besser ab, liegt aber immer noch Lichtjahre hinter Sprachen mit Mustervergleich (Haskell, F #…).
Konrad Rudolph
1
tuinostel: so etwas wie switch (a) {case 1 {do_something; } case 2 {do_something_else; }} - das heißt, die break-Anweisung loszuwerden und für jeden Fall die richtigen Codeblöcke zu benötigen
Tamas Czinege
2
Eine Pause zu haben scheint im Allgemeinen ein Fehler zu sein. Ist es nicht ein Kompilierungsfehler, sie auszugeben? Es scheint nur da zu sein, um den Übergang von C zu C # zu erleichtern (auch bekannt als das Unterrichten von Entwicklern, durch die sie nicht automatisch fallen können)
Guvante
10

Ereignisse in C #, bei denen Sie explizit nach Listenern suchen müssen. War das nicht der Punkt bei Ereignissen, an jemanden zu senden, der gerade dort war? Auch wenn es keine gibt?

Thomas Eyde
quelle
1
Es ist ärgerlich, dass es dafür keinen Zucker gibt, wenn Sie es wollen. Ich verstehe, warum sie es schwierig machen. Es ermutigt dazu, die Argumente des Ereignisses nicht zu instanziieren, wenn sie nicht benötigt werden
ShuggyCoUk
Hm. Entweder verstehe ich nicht oder stimme nicht zu oder beides :-). Ich kann nicht sagen, dass ich mich jemals entmutigt gefühlt habe, etwas zu instanziieren. Das riecht für mich nach vorzeitiger Optimierung.
Thomas Eyde
Teilmethoden sind in einigen Fällen eine praktikable Alternative.
Robert Harvey
PLUS all die Kopplung, die dies verursacht ... MS hat CAB, das diese beiden Probleme behebt, aber CAB hat viele eigene Probleme aufgrund von Einschränkungen in C # (z. B. Zeichenfolgen anstelle von Aufzählungen als Ereignisthemen) - warum nicht einfach locker machen -gekoppelte Ereignisse Teil der Sprache !?
BlueRaja - Danny Pflughoeft
9

Das schreckliche (und für die meisten Menschen völlig unsichtbare) O (N ^ 2) -Verhalten verschachtelter / rekursiver Iteratoren .

Ich bin ziemlich enttäuscht, dass sie darüber Bescheid wissen und wissen, wie man es behebt, aber es wird nicht als ausreichend priorisiert angesehen, um die Aufnahme zu verdienen.

Ich arbeite die ganze Zeit mit baumartigen Strukturen und muss den Code ansonsten kluger Leute korrigieren, wenn sie auf diese Weise versehentlich sehr teure Operationen einführen.

Das Schöne an "Yield Foreach" ist, dass die einfachere, einfachere Syntax korrekten, performanten Code fördert. Dies ist die "Grube des Erfolgs" , nach der sie meiner Meinung nach streben sollten, bevor sie neue Funktionen für den langfristigen Erfolg der Plattform hinzufügen.

ShuggyCoUk
quelle
Dies ist besonders fehlerhaft, da es den intuitiven Erwartungen an das O-Verhalten
widerspricht und
7

Einige Klassen implementieren Schnittstellen, aber sie implementieren nicht viele der Methoden dieser Schnittstelle, z. B. Array implementiert IList, aber 4 von 9 Methoden lösen NotSupportedException http://msdn.microsoft.com/en-us/library/system.array_members aus .aspx

ggf31416
quelle
Nun, Sie können die Anzahl der Elemente in einem Array nicht ändern, sodass Add, Clear, Insert und Remove (At) nichts anderes tun können, als NotSupported auszulösen. Tatsächlich würde ich JEDE Implementierung von IList erwarten, für die true zurückgegeben wird IsFixedSize würde auf sie werfen.
CB
@CB ein bisschen spät zur Party, denke ich :) Aber wenn Array "IList" nicht erfüllen kann, warum sollte es trotzdem implementiert werden? Das ist eine Verletzung von L im SOLID-Prinzip.
Flügelspieler Sendon
7

Statische Elemente und verschachtelte Typen in Schnittstellen.

Dies ist besonders nützlich, wenn ein Schnittstellenmitglied einen Parameter eines Typs hat, der für die Schnittstelle spezifisch ist ( zenum . B. einen ). Es wäre schön, den Aufzählungstyp im Schnittstellentyp zu verschachteln.

Jay Bazuzi
quelle
1
Ist das nicht sehr ähnlich zu deinem anderen Vorschlag?
RCIX
1
Nein, in diesem Fall geht es um die C # -Sprache und im anderen um das Framework. Nicht jeder kümmert sich um die Unterscheidung, also lassen Sie mich sagen, dass es bei diesem um das geht, was erlaubt ist, und bei dem anderen um das, was bereitgestellt wird.
Jay Bazuzi
6

Die schrecklich gefährliche Standardnatur von Ereignissen. Die Tatsache, dass Sie ein Ereignis aufrufen können und sich aufgrund des Entfernens von Abonnenten in einem inkonsistenten Zustand befinden, ist einfach schrecklich. Weitere Informationen zu diesem Thema finden Sie in den hervorragenden Artikeln von Jon Skeet und Eric Lippert .

jasonh
quelle
Es würde mir nichts ausmachen, wenn Ereignisse standardmäßig nicht threadsicher wären (dies könnte die Leistung in Single-Thread-Code erhöhen). Was albern ist, ist, dass Hinzufügen / Entfernen standardmäßig sicher ist, aber dass die natürliche Art, ein Ereignis auszulösen, unsicher ist und es keine Möglichkeit gibt, es einfach sicher zu machen.
Qwertie
@Qwertie: Was noch dümmer ist, ist, dass das Hinzufügen / Entfernen für eine Weile eine Sperrung verwenden würde und dennoch nicht threadsicher ist.
Supercat
6
  • null überall.

  • const nirgends.

  • APIs sind inkonsistent, z. B. das Mutieren einer Array-Rückgabe void, das Anhängen an eine StringBufferRückgabe jedoch dieselbe veränderbare StringBuffer.

  • Sammlungsschnittstellen sind nicht mit unveränderlichen Datenstrukturen kompatibel, z. B. Addin System.Collections.Generic.IList<_>kann kein Ergebnis zurückgeben.

  • Keine strukturelle Eingabe, also schreiben Sie System.Windows.Media.Effects.SamplingMode.Bilinearstatt nur Bilinear.

  • Veränderbare IEnumeratorSchnittstelle, die von Klassen implementiert wird, wenn sie unveränderlich sein soll struct.

  • Gleichheit und Vergleich ist ein einziges Chaos: Sie haben System.IComparableund Equalsaber dann haben Sie auch bekommen System.IComparable<_>, System.IEquatable, System.Collections.IComparer, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable, System.Collections.Generic.IComparerund System.Collections.Generic.IEqualityComparer.

  • Tupel sollten Strukturen sein, aber Strukturen verhindern unnötig die Eliminierung von Tail Calls, sodass einer der häufigsten und grundlegendsten Datentypen unnötig zugewiesen wird und skalierbare Parallelität zerstört wird.

JD
quelle
Es gibt keine strukturelle Typisierung in der CLR, aber Sie scheinen strukturelle Typisierung mit Typinferenz oder mit der als "Symbole" bekannten Ruby-Funktion verwechselt zu haben. Strukturelle Typisierung wäre, wenn die CLR Func <int, bool> und Predicate <int> als denselben Typ oder zumindest implizit konvertierbar betrachtet.
Qwertie
Apropos Vergleich, vergessen Sie nicht Comparer <T>!
Qwertie
@Qwertie Ich bezog mich auf Programmiersprachenfunktionen wie polymorphe Varianten in OCaml. Die LablGL-Bibliothek von OCaml enthält viele interessante Beispiele für strukturelle Typisierung, die im Kontext von Grafiken nützlich sind. Nichts mit Typinferenz zu tun und nur tangential mit Symbolen verbunden.
JD
1
Wie würde man eine unveränderliche Struktur verwenden IEnumerator?
Supercat
5

0 Mondschein als Aufzählung

Besonderheiten der Aufzählung: http://blogs.msdn.com/abhinaba/archive/2007/01/09/more-peculiarites-of-enum.aspx

Wie dieses gute Beispiel zeigt: http://plus.kaist.ac.kr/~shoh/postgresql/Npgsql/apidocs/Npgsql.NpgsqlParameterCollection.Add_overload_3.html

Mein Vorschlag, nutzen Sie das "@" - Zeichen:

anstatt:

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

benutze das:

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

Michael Buen
quelle
1
+1 Aufzählung ist eines der wenigen Dinge, die Java richtig gemacht hat, während C # es nicht tat
BlueRaja - Danny Pflughoeft
5

So ergänzen Sie die lange Liste der guten Punkte, die bereits von anderen gemacht wurden:

  • DateTime.Now == DateTime.Now in den meisten, aber nicht allen Fällen.

  • StringWas unveränderlich ist, hat eine Reihe von Optionen für Konstruktion und Manipulation, aber StringBuilder(was veränderlich ist) nicht.

  • Monitor.Enterund Monitor.Exitsollten Instanzmethoden gewesen sein. Anstatt ein bestimmtes Objekt zum Sperren neu zu erstellen, können Sie ein neues Objekt erstellen Monitorund dieses sperren.

  • Destruktoren sollten niemals Destruktoren genannt worden sein. Die ECMA-Spezifikation nennt sie Finalizer, was für die C ++ - Menge viel weniger verwirrend ist, aber die Sprachspezifikation bezeichnet sie immer noch als Destruktoren.

Brian Rasmussen
quelle
3
Der DateTime.Noweine ist die offensichtlichste Rennbedingung der Welt, aber +1 für den Rest
BlueRaja - Danny Pflughoeft
Es ist nicht so sehr, dass es eine Rennbedingung ist, sondern die Tatsache, dass sie es zu einem Eigentum gemacht haben. Eigenschaften sehen genauso aus wie Felder, daher ist es IMO eher überraschend.
Brian Rasmussen
4
@Brian Rasmussen: DateTime.Now ist eine Eigenschaft, da sie nicht durch Lesen, sondern durch äußere Faktoren geändert wird. Wenn man eine Eigenschaft wie SomeForm.Width liest und sie dann - nachdem der Benutzer die Größe des Formulars geändert hat - erneut liest, ist der Wert beim zweiten Lesen anders. Während es möglich ist, dass die Ausführung des ersten DateTime.Now lange genug dauert, um den vom zweiten gelesenen Wert zu beeinflussen, unterscheidet sich dieser Effekt nicht von anderen Funktionen, deren Ausführung dieselbe Zeit in Anspruch genommen hat.
Supercat
4

Die Art und Weise, wie wir Eigenschaften verwenden, irritiert mich manchmal. Ich stelle sie mir gerne als Äquivalent zu den Methoden getFoo () und setFoo () von Java vor. Aber das sind sie nicht.

Wenn in den Richtlinien zur Verwendung von Eigenschaften festgelegt ist, dass Eigenschaften in beliebiger Reihenfolge festgelegt werden können, damit die Serialisierung funktioniert, sind sie für die Überprüfung der Setterzeit unbrauchbar. Wenn Sie aus einem Hintergrund kommen, in dem Sie verhindern möchten, dass sich ein Objekt jemals in einen ungültigen Zustand versetzt, sind Eigenschaften nicht Ihre Lösung. Manchmal scheitern ich nur um zu sehen , wie sie sind besser als öffentliche Mitglieder, da sind wir so begrenzt , welche Arten von Dingen , die wir sind angeblich in Eigenschaften zu tun.

Zu diesem Zweck habe ich mir immer gewünscht (das wird hier meistens laut gedacht, ich wünschte nur, ich könnte so etwas tun), dass ich die Eigenschaftssyntax irgendwie erweitern könnte. Stellen Sie sich so etwas vor:


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

Ich bin mir nicht sicher, ob das nützlich ist oder wie der Zugriff darauf aussehen würde. Aber ich wünschte nur, ich könnte mehr mit Eigenschaften anfangen und sie wirklich wie get- und set-Methoden behandeln.

Nicholas Piasecki
quelle
3
Ich glaube, sie bieten die ISerializable-Schnittstelle und den impliziten Konstruktor, um solche Dinge zu tun, auch wenn Sie nicht möchten, dass der Serializer nur die Eigenschaften aufruft. Während ein bisschen mehr Arbeit, sieht es so aus, als würden Sie den größten Teil dieser Arbeit bereits mit Ihrer Methode erledigen.
Guvante
4

Erweiterungsmethoden sind nett, aber sie sind eine hässliche Möglichkeit, Probleme zu lösen, die mit echten Mixins sauberer hätten gelöst werden können (siehe Ruby, um zu sehen, wovon ich spreche), zum Thema Mixins. Eine wirklich gute Möglichkeit, sie der Sprache hinzuzufügen, wäre gewesen, Generika für die Vererbung zu verwenden. Auf diese Weise können Sie vorhandene Klassen auf eine schöne objektorientierte Weise erweitern:

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

Dies kann folgendermaßen verwendet werden, um eine Zeichenfolge zu erweitern:

var newMixin = new MyMixin<string>();

Es ist weitaus leistungsfähiger als Erweiterungsmethoden, da Sie damit Methoden überschreiben können, z. B. um sie zu verpacken und AOP-ähnliche Funktionen in der Sprache zu ermöglichen.

Entschuldigung für das Geschwätz :-)

Mendelt
quelle
5
Interessant, aber ich bevorzuge Erweiterungsmethoden. Wenn ich eine Bibliothek erhalte, die eine Reihe von Erweiterungsmethoden für Zeichenfolgen enthält, muss ich nicht alle Zeichenfolgenreferenzen in MyMixin <string> ändern, um die neuen Inhalte zu erhalten. Es ist zwar ein bisschen klein, aber diese transparente Hinzufügung von Methoden macht Erweiterungsmethoden so schön.
RCIX
Übrigens wussten Sie, dass das schon funktioniert?
RCIX
2
Ich sehe nicht, wie LINQ so funktionieren könnte
BlueRaja - Danny Pflughoeft
2
@RCIX: Mixins klingen so, wie ich es mir vorgestellt habe. Erweiterungsmethoden sollten funktionieren. Das Problem bei der Implikation von Erweiterungsmethoden besteht darin, dass echte Klassenmitglieder Vorrang vor Erweiterungsmethoden haben müssen. Wenn eine Erweiterungsmethode Graphics.DrawParallelogram (Stift p, Punkt v1, Punkt v2, Punkt v3) definiert ist und später eine DrawParallelogram-Funktion zu System.Graphics hinzugefügt wird, die Punkte in einer anderen Reihenfolge verwendet, wird der Code, der die Erweiterungsmethode verwendet, ohne unterbrochen Warnung. Übrigens, hätte es ein Problem gegeben, zwei Punkte für Erweiterungsmethoden zu verwenden (z. B. object..method ()?)
supercat
3

Microsoft behebt keine offensichtlichen Fehler im Framework und stellt keine Hooks bereit, damit Endbenutzer diese beheben können.

Es gibt auch keine Möglichkeit, ausführbare .NET-Dateien zur Laufzeit binär zu patchen und keine privaten Versionen von .NET Framework-Bibliotheken anzugeben, ohne die nativen Bibliotheken binär zu patchen (um den Ladeaufruf abzufangen), und ILDASM ist nicht weiterverteilbar, sodass ich nicht automatisieren kann der Patch trotzdem.

Joshua
quelle
1
Welche offensichtlichen Framework-Fehler meinen Sie?
Robert Rossney
1
# 1 Klicken Sie auf ein teilweise sichtbares untergeordnetes Steuerelement eines scrollbaren Steuerelements. Das Steuerelement wird in die Ansicht verschoben, bevor es das MouseDown-Ereignis empfängt. Dadurch befindet sich der Klick an einer anderen Stelle im Steuerelement als erwartet. In Baumansichten ist es schlimmer, wenn es auch einen Ziehvorgang auslöst.
Joshua
1
# 2 dies # 3 dies # 4 dies
BlueRaja - Danny Pflughoeft
3
  • Die Möglichkeit, eine Erweiterungsmethode für eine Nullvariable aufzurufen, ist fraglich, z

    Objekt a = null; a.MyExtMethod (); // Dies ist aufrufbar. Nehmen wir an, irgendwo wurde MyExtMethod definiert

    Es könnte praktisch sein, ist jedoch bei Nullreferenz-Ausnahmethemen nicht eindeutig.

  • Ein Namensfehler. 'C' von "configuration" in System.configuration.dll sollte groß geschrieben werden.

  • Ausnahmebehandlung. Ausnahmen sollten wie in Java gewaltsam abgefangen oder ausgelöst werden. Der Compiler sollte sie beim Kompilieren überprüfen. Benutzer sollten sich nicht auf Kommentare für Ausnahmeinformationen innerhalb des Zielaufrufs verlassen.

codemeit
quelle
3
Sehr praktisch - ich habe eine "ThrowIfNull" -Erweiterungsmethode zur Parameterprüfung ;-p
Marc Gravell
2
du kannst das? ugh ThrowIfNull ist eine interessante Erweiterung, aber das scheint einfach falsch zu sein.
JoshBerke
1
In der einfachen CLR können Sie Instanzmethoden für Nullreferenzen aufrufen. Wenn die Methode nicht auf das Objekt oder seine Felder zugreift, löst der Aufruf keine Nullreferenzausnahme aus. (Sie können dies nicht in C # tun, da Callvirt auch für nicht virtuelle Methoden verwendet wird)
Pop Catalin
7
Die Ausnahmesache ist absolut falsch. Sie können nicht schnell scheitern, wenn Sie jede verdammte Ausnahme abfangen MÜSSEN, die in den Aufrufstapel geworfen wird. Aber ich wünschte, es wäre viel einfacher, alle Ausnahmen zu identifizieren, die in einem bestimmten Anruf und dem daraus resultierenden Aufrufstapel ausgelöst werden könnten ...
3
@Will: Die Ausnahmebehandlung ist sowohl in Java als auch in .net schwierig, da der verwendete Mechanismus drei Konzepte, die etwas verwandt, aber auch etwas orthogonal sind, eng miteinander verbindet: (1) Welche Art von Dingen ist schief gelaufen (ein Array begrenzt Fehler, ein E / A-Timeout usw.); (2) ob ein bestimmter Code als Ergebnis Maßnahmen ergreifen sollte; (3) Ab wann sollte das Problem als "gelöst" betrachtet werden. Stellen Sie sich eine Routine vor, die ein Objekt mit Daten mutieren soll, die aus einer IEnumerable gelesen wurden. Was soll passieren, wenn bei der Verarbeitung dieser IEnumerable eine Ausnahme auftritt?
Supercat
3

Die .Parameters.Add () -Methode für den SqlCommand in V1 des Frameworks wurde fürchterlich entworfen - eine der Überladungen würde grundsätzlich nicht funktionieren, wenn Sie einen Parameter mit dem Wert (int) von 0 übergeben würden - dies führte dazu, dass sie erstellt wurden die Methode .Parameters.AddWithValue () für die SqlCommand-Klasse.

Dave Markle
quelle
Ich stimme zu, aber ich denke, Sie meinen die SqlCommand.Parameters.Add () -Methode.
Matt Peterson
3
  1. Es gibt keine Teilmengen von ICollection<T>und IList<T>; Zumindest wäre eine kovariante schreibgeschützte Erfassungsschnittstelle IListSource<out T>(mit Enumerator, Indexer und Count) äußerst nützlich gewesen.
  2. .NET unterstützt keine schwachen Delegaten . Die Problemumgehungen sind bestenfalls umständlich, und Problemlösungen auf der Hörerseite sind bei teilweisem Vertrauen nicht möglich (ReflectionPermission ist erforderlich).
  3. Die generische Vereinheitlichung der Schnittstelle ist verboten, auch wenn sie sinnvoll ist und keine Probleme verursacht.
  4. Im Gegensatz zu C ++ sind kovariante Rückgabetypen in .NET nicht zulässig
  5. Es ist nicht möglich, zwei Werttypen bitweise auf Gleichheit zu vergleichen. In einer funktionalen " persistenten " Datenstruktur habe ich eine Transform(Sequence<T>, Func<T,T>)Funktion geschrieben, mit der schnell festgestellt werden konnte, ob die Funktion denselben oder einen anderen Wert zurückgibt. Wenn die Funktion die meisten / alle Argumente nicht ändert, kann die Ausgabesequenz einen Teil / den gesamten Speicher der Eingabesequenz gemeinsam nutzen. Ohne die Möglichkeit, einen Werttyp T bitweise zu vergleichen, muss ein viel langsamerer Vergleich verwendet werden, was die Leistung enorm beeinträchtigt.
  6. .NET scheint nicht in der Lage zu sein, Ad-hoc-Schnittstellen (wie die in Go oder Rust angebotenen) auf performante Weise zu unterstützen. Solche Schnittstellen hätten es Ihnen ermöglicht List<T>, eine Hypothese IListSource<U>(wobei T: U) zu erstellen, obwohl die Klasse diese Schnittstelle nicht explizit implementiert. Es gibt mindestens drei verschiedene Bibliotheken (unabhängig voneinander geschrieben), um diese Funktionalität bereitzustellen (natürlich mit Leistungsnachteilen - wenn eine perfekte Problemumgehung möglich wäre, wäre es nicht fair, sie als Fehler in .NET zu bezeichnen).
  7. Weitere Leistungsprobleme: IEnumerator erfordert zwei Schnittstellenaufrufe pro Iteration. Einfache Methodenzeiger (IntPtr-offene Delegaten) oder werttypisierte Delegaten (IntPtr * 2) sind nicht möglich. Arrays mit fester Größe (vom beliebigen Typ T) können nicht in Klassen eingebettet werden. Es gibt keine WeakReference<T>(Sie können leicht Ihre eigenen schreiben, aber es werden Abgüsse intern verwendet.)
  8. Die Tatsache, dass identische Delegatentypen als inkompatibel angesehen werden (keine implizite Konvertierung), war für mich gelegentlich ein Ärgernis (z . B. Predicate<T>vs Func<T,bool>). Ich wünschte oft, wir könnten eine strukturelle Typisierung für Schnittstellen und Delegaten haben, um eine lockerere Kopplung zwischen Komponenten zu erreichen, da es in .NET nicht ausreicht, dass Klassen in unabhängigen DLLs dieselbe Schnittstelle implementieren - sie müssen auch einen gemeinsamen Verweis auf eine dritte haben DLL, die die Schnittstelle definiert.
  9. DBNull.Valueexistiert, obwohl nullder gleiche Zweck gleich gut gedient hätte.
  10. C # hat keinen ?? = Operator; du musst schreiben variable = variable ?? value. In der Tat gibt es einige Stellen in C #, an denen es unnötig an Symmetrie mangelt. Zum Beispiel können Sie schreiben if (x) y(); else z();(ohne geschweifte Klammern), aber Sie können nicht schreiben try y(); finally z();.
  11. Beim Erstellen eines Threads kann der untergeordnete Thread nicht veranlassen, threadlokale Werte vom übergeordneten Thread zu erben. Die BCL unterstützt dies nicht nur nicht, sondern Sie können es auch nicht selbst implementieren, es sei denn, Sie erstellen alle Threads manuell. Selbst wenn es ein Thread-Erstellungsereignis gab, kann .NET Ihnen nicht die "Eltern" oder "Kinder" eines bestimmten Threads mitteilen .
  12. Die Tatsache, dass es zwei verschiedene Längenattribute für verschiedene Datentypen gibt, "Länge" und "Anzahl", ist ein kleines Ärgernis.
  13. Ich könnte für immer über das schlechte Design von WPF reden ... und WCF (obwohl es für einige Szenarien sehr nützlich ist) ist auch voller Warzen. Im Allgemeinen lässt mich die Blähung, Unintuitivität und eingeschränkte Dokumentation vieler neuerer BCL-Unterbibliotheken zögern, sie zu verwenden. Viele der neuen Inhalte hätten viel einfacher, kleiner, benutzerfreundlicher und verständlicher, lockerer gekoppelt, besser dokumentiert, für mehr Anwendungsfälle anwendbar, schneller und / oder stärker typisiert sein können.
  14. Ich bin oft von der unnötigen Kopplung zwischen Eigenschaftsträgern und Setzern gebissen worden: In einer abgeleiteten Klasse oder abgeleiteten Schnittstelle können Sie nicht einfach einen Setter hinzufügen, wenn die Basisklasse oder Basisschnittstelle nur einen Getter hat; Wenn Sie einen Getter überschreiben, dürfen Sie keinen Setter definieren. und Sie können den Setter nicht als virtuell definieren, sondern den Getter als nicht virtuell.
Qwertie
quelle
Ich stimme mit Ihnen über eine Teilmenge IList<T>, obwohl ich verwenden würde , IReadableByIndex<out T>und IAppendable<in T>. Viele deiner anderen Dinge sind Quietschgeräusche, denen ich auch zustimmen würde.
Supercat
Das ist ein sehr langer Name. Vielleicht könnten wir Kompromisse eingehen IListReader<T>;) - Ich verwende das Wort "Quelle" als Antonyme für "Senke" (eine Nur-Schreib-Schnittstelle).
Qwertie
Vielleicht IListSource<in T>oder IReadableList<out T>. Es kann von Wert sein, wenn Basisschnittstellentypen Methoden enthalten, die nicht in allen Derivaten vorhanden sind, obwohl ich denke, dass es oft gut ist, wenn Schnittstellen etwas spezialisiert sind. Zum Beispiel könnte man eine haben, IList<T>die Größenänderungsmethoden enthält, die möglicherweise funktionieren oder nicht, und eine IResizableList<T>, die dieselben Methoden implementiert, aber garantiert, dass sie funktionieren. Ein solcher Ansatz kann in Fällen nützlich sein, in denen ein Feld entweder den einzigen vorhandenen Verweis auf eine veränderbare Liste oder einen gemeinsamen Verweis auf eine unveränderliche Liste enthält.
Supercat
In einem solchen Fall würde Code, der den Inhalt der Liste ändern möchte, prüfen, ob es sich um einen veränderlichen Typ handelt, und andernfalls eine neue veränderbare Instanz generieren, die dieselben Elemente wie die unveränderliche Liste enthält, und diese dann verwenden. Es wäre lästig, wenn der Code das Feld jedes Mal, wenn er eine Mutationsmethode verwenden wollte, ständig typisieren müsste.
Supercat
@supercat Das ist nur lästig, weil C # keine wirklich einfache Möglichkeit bietet, zu überprüfen, ob eine Schnittstelle implementiert ist, und sie sofort zu verwenden. MS sollte eine Sprachfunktion hinzufügen, um dies zu vereinfachen. Meine bevorzugte Technik wäre ein verbindlicher Ausdruck if (rl:(list as IResizableList<T>) != null) rl.Add(...);, aber es gibt andere Vorschläge. Als Autor verschiedener Sammlungen und Sammlungsadapter ärgert mich das Schreiben vieler Dummy-Methoden, die Ausnahmen auslösen. Als Sicherheitslüfter möchte ich keine illegalen Methoden aufrufen dürfen. Als IntelliSense-Fan möchte ich nicht, dass sie aufgelistet werden.
Qwertie
2

Eine Sache , die mich in 1.x abgehakt war , als die Verwendung System.Xml.XmlValidatingReaderder ValidationEventHandler‚s ValidationEventArgsnicht der Basiswert aussetzt XmlSchemaException(markiert intern) , die alle nützlichen Informationen hat wie linenumberund position. Stattdessen wird erwartet, dass Sie dies aus der Message-String-Eigenschaft heraus analysieren oder mithilfe von Reflection ausgraben. Nicht so gut, wenn Sie dem Endbenutzer einen bereinigten Fehler zurückgeben möchten.

Kev
quelle
1

Es gefällt mir nicht, dass Sie die Werte einer Aufzählung nicht in einer anderen Aufzählung verwenden können, zum Beispiel:

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

    enum SpecialColors { Colors.blue, Colors.red, Colors.Yellow } 
tuinstoel
quelle
2
Dies macht jedoch nicht einmal Sinn. typeof(Color)! = typeof(SpecialColors).
Kirk Woll
10
Es ist einfach genug zu tun:enum SpecialColors { blue = Colors.blue, red = Colors.red, yellow = Colors.Yellow }
Trystan Spangler
0

Implizit typisierte Variablen wurden IMO schlecht implementiert. Ich weiß, dass Sie sie wirklich nur verwenden sollten, wenn Sie mit Linq-Ausdrücken arbeiten, aber es ist ärgerlich, dass Sie sie nicht außerhalb des lokalen Bereichs deklarieren können.

Von MSDN:

  • var kann nur verwendet werden, wenn eine lokale Variable in derselben Anweisung deklariert und initialisiert wird. Die Variable kann nicht mit null, einer Methodengruppe oder einer anonymen Funktion initialisiert werden.
  • var kann nicht für Felder im Klassenbereich verwendet werden.
  • Mit var deklarierte Variablen können nicht im Initialisierungsausdruck verwendet werden. Mit anderen Worten, dieser Ausdruck ist legal: int i = (i = 20); Dieser Ausdruck erzeugt jedoch einen Fehler bei der Kompilierung: var i = (i = 20);
  • Mehrere implizit typisierte Variablen können nicht in derselben Anweisung initialisiert werden.
  • Wenn sich ein Typ mit dem Namen var im Gültigkeitsbereich befindet, wird das Schlüsselwort var in diesen Typnamen aufgelöst und nicht als Teil einer implizit typisierten lokalen Variablendeklaration behandelt.

Der Grund, warum ich denke, dass es eine schlechte Implementierung ist, ist, dass sie es var nennen, aber es ist weit davon entfernt, eine Variante zu sein. Es ist wirklich nur eine Kurzsyntax, wenn Sie nicht den vollständigen Klassennamen eingeben müssen (außer bei Verwendung mit Linq).

Lomaxx
quelle
Definitiv anon Typen (neu {...}), nicht implizit typisiert (var)
Marc Gravell
Lies einfach noch einmal, was ich gepostet habe und es war falsch. Ich meinte implizit typisierte Variablen
5.
1
Eric Lippert erklärte, warum var nicht außerhalb von Methoden verwendet werden kann, weil es im Grunde genommen eine Black Box von Möglichkeiten schafft. blogs.msdn.com/ericlippert/archive/2009/01/26/…
Guvante
1
var soll keine Variante sein! es ist genau für die Kurzschrift (besonders anon Typen). Genießen Sie Dynamik, wenn es darum geht ...
ShuggyCoUk