Jakie są niektóre z największych błędów projektowych w C # lub ogólnie w .NET Framework?
Przykład: nie ma typu łańcucha niedopuszczającego wartości null i musisz sprawdzić DBNull podczas pobierania wartości z IDataReader.
Jakie są niektóre z największych błędów projektowych w C # lub ogólnie w .NET Framework?
Przykład: nie ma typu łańcucha niedopuszczającego wartości null i musisz sprawdzić DBNull podczas pobierania wartości z IDataReader.
Odpowiedzi:
Zdecydowanie zgadzam się z tym postem (dla tych, którzy odrzucają brak ToString, istnieje atrybut debuggera, który zapewnia niestandardowy format dla twojej klasy).
Na szczycie powyższej listy dodałbym również następujące uzasadnione żądania:
T : new(string)lub gdzieT : new(string, int)var e = new Foo(); e { Bar = baz };Either<T>", nie jest, więc chciałbym w jakiś sposób zadeklarować zamknięty typ algebraiczny i wymusić na nim wyczerpujące dopasowywanie wzorców (w zasadzie pierwszorzędne wsparcie dla wzorca gości, ale znacznie bardziej wydajne); więc po prostu weź wyliczenia, rozszerz je o wyczerpującą obsługę dopasowywania wzorców i nie zezwalaj na nieprawidłowe przypadki,System.IOzajęcia Streamsą trochę źle zaprojektowane; każdy interfejs, który wymaga pewnych implementacji, NotSupportedExceptionjest złym projektem,IListpowinno być znacznie prostsze niż jest; w rzeczywistości może to być prawdą dla wielu interfejsów zbiórki beton, jak ICollection,INotifyPropertyChanged, które pobierają nazwę pola jako ciąg; możesz to zrobić, używając metody rozszerzającej, która przyjmuje lambdę z MemberExpression, tj. () => Fooale to nie jest zbyt wydajne,
nameof()operator dla nazw pojedynczych elementów członkowskich, ale nie działa w nameof(T) == "T"typach ogólnych ( zamiast rzeczywistej nazwy argumentu typu: nadal musisz to zrobić typeof(T).Name)) - ani nie pozwala uzyskać ciągu „ścieżki” np. nameof(this.ComplexProperty.Value) == "Value"ograniczenie jego możliwych zastosowań.IArithmetic; możliwe są również inne przydatne współdzielone interfejsy operatora,readonlysłowo kluczowe, a C # 6,0 dodał właściwości automatyczne tylko do odczytu, chociaż nie jest tak rygorystyczne, jak obsługa prawdziwego języka dla niezmiennych typów i wartości.Myślę, że na razie wystarczy. To wszystkie irytacje, na które napotkałem w zeszłym tygodniu. Prawdopodobnie mógłbym trwać godzinami, gdybym naprawdę się nad tym zastanowił. C # 4.0 już dodaje argumenty nazwane, opcjonalne i domyślne, za co zdecydowanie się zgadzam.
A teraz jedna nieuzasadniona prośba:
Pięknie proszę? :-)
List<T>milion Ts. Jak proponujesz sprawne wykonanie migawki? # 21: Użyj readonlysłowa kluczowego .... chociaż jest tu kilka dobrych sugestii, są one głównie takie - sugestie, a nie wady projektowe.
Reset()metoda na IEnumerator<T>to pomyłka (iterator dla bloków, spec język nawet wymaga , że ta zgłasza wyjątek)IEnumerable<out T>i Func<in T, out TResult>, ale nie konkretne typy (takie jak List<T>).ApplicationException raczej wypadł z łask - czy to był błąd?Containsa Addwięc), więc zbiór, który synchronizuje różne operacje, nie jest aż tak przydatny
System.Collections.Concurrenttypy , z TryAdd, GetOrAdd, TryRemoveitp dodano w .NET Framework 4.0 - choć metody, które akceptują delegata fabrycznego nie gwarantują fabryka zostanie wywołany tylko raz na klucz.using/ lock- być może umożliwiając im współdzielenie składni wielokrotnego użytku (rozszerzalnej?); możesz to zasymulować, powracając IDisposablei używając using, ale mogłoby to być jaśniejszeFoo(SqlConnection! connection)(która wprowadza kontrolę null / throw) byłaby fajna (w przeciwieństwie do int?itp.)
dynamiclub możesz to włączyć w ten sposóbforeachinterpretacji, co oznacza, że metody anon / lambdy przechwytują pojedynczą zmienną, a nie jedną na iterację (bolesne z wątkami / asynchronicznymi / itp.)
ApplicationExceptionto błąd - nieprzydatny, tak jak mieli nadzieję. Powiedzieli też, że System.Exceptionpowinno abstract.
TextWriter jest klasą bazową StreamWriter. wtf?
To zawsze wprawia mnie w skrajne zamieszanie.
Mały peev pet w języku C # - konstruktorzy używają składni C ++ / Java, w której konstruktor ma taką samą nazwę jak klasa.
New()lub ctor()byłoby znacznie ładniej.
I oczywiście, narzędzia takie jak coderush zmniejszają ten problem przy zmianie nazw klas, ale z punktu widzenia czytelności, New () zapewnia dużą przejrzystość.
class Foo { new(int j) {i = j} int i; }
Newsłowa kluczowe - nie - wielkie litery są sprzeczne z konwencją), waham się, czy nazwać to błędem projektowym. Chcieli przyciągnąć istniejących programistów C ++ / Java, a pożyczenie wielu głupich starych konwencji składniowych prawdopodobnie pomogło im osiągnąć cel.
Nie rozumiem, że nie możesz tego zrobić
gdzie T: nowy (U)
Więc deklarujesz, że typ ogólny T ma konstruktora innego niż domyślny.
edytować:
Chce to zrobić:
public class A
{
public A(string text)
{
}
}
public class Gen<T> where T : new(string text)
{
}
Jestem naprawdę zaskoczony, że jako pierwszy o tym wspomniałem:
Zestawy danych typu ADO.NET nie ujawniają kolumn dopuszczających wartość null jako właściwości typów dopuszczających wartość null. Powinieneś umieć napisać to:
int? i = myRec.Field;
myRec.Field = null;
Zamiast tego musisz napisać to, co jest po prostu głupie:
int? i = (int?)myRec.IsFieldNull() ? (int?)null : myRec.Field;
myRec.SetFieldNull();
To było denerwujące w .NET 2.0, a teraz jest jeszcze bardziej irytujące, kiedy musisz używać jiggery-pokery, jak powyżej, w ładnych, schludnych zapytaniach LINQ.
Irytujące jest również to, że wygenerowana Add<TableName>Rowmetoda jest podobnie niewrażliwa na pojęcie typów dopuszczających wartość null. Tym bardziej, że wygenerowane TableAdaptermetody nie są.
W .NET nie ma zbyt wiele rzeczy, które sprawiają, że czuję się tak, jakby zespół programistów powiedział: „Dobra, chłopcy, jesteśmy wystarczająco blisko - wysyłajcie to!” Ale to z pewnością tak.
DBNull.Value, kiedy nullsama byłaby całkowicie odpowiednia do reprezentowania NULL. Na szczęście LINQ-to-SQL po prostu używa null dla NULL.
Edycja
5. Inną moją irytacją jest to, że System.Reflection.BindingFlags ma różne zastosowania w zależności od używanej metody. Na przykład w FindFields, co oznacza CreateInstance lub SetField? Jest to przypadek, w którym przeładowali znaczenie tego wyliczenia, które jest mylące.
Nie wiem, czy posunąłbym się nawet do stwierdzenia, że to wada projektowa, ale byłoby naprawdę fajnie, gdybyś mógł wywnioskować wyrażenie lambda w taki sam sposób, jak w VB:
VB:
Dim a = Function(x) x * (x - 1)
DO#
Byłoby miło, gdyby można było to zrobić:
var a = x => x * (x - 1);
Zamiast tego robić:
Func<int, int> a = x => x * (x - 1);
Zdaję sobie sprawę, że to niewiele dłużej, ale w Code Golf każda postać się liczy! Czy nie biorą tego pod uwagę podczas projektowania tych języków programowania? :)
(int x) => x * (x -1);może znaczyć Func<int, int>lub może znaczyćExpression<Func<int, int>>
+nie jest zdefiniowany dla object.
System.Object klasa:
Equals i GetHashCode - nie wszystkie klasy są porównywalne lub haszowane, należy je przenieść do interfejsu. Przychodzi mi na myśl IEquatable lub IComparable (lub podobny).
ToString - nie wszystkie klasy można przekonwertować na ciąg znaków, należy je przenieść do interfejsu. Przychodzi mi na myśl IFormattable (lub podobny).
Właściwość ICollection.SyncRoot :
Leki generyczne powinny być tam od początku:
EqualityComparer<T>.Defaultpoprawna aktualizacja . Wtedy obie var dict = new Dictionary<object, string>(EqualityComparer<object>.Default)i var dict = new Dictionary<object, string>()użyją porównania referencyjnego / równości.
EqualityComparer<T>.Defaultrobi. Nie musisz sprawdzać przy każdym wyszukiwaniu. Funkcja porównująca jest właściwością Dictionaryinstancji i każdy Dictionarywie, której z nich używa.
Jedną z rzeczy, która mnie irytuje, jest Predicate<T> != Func<T, bool>paradoks. Obaj są delegatami typu, T -> boolale nie są zgodni z przypisaniem.
Niektórzy ludzie (ISV) chcą, abyś mógł skompilować go do kodu maszynowego w czasie kompilacji i połączyć go, aby utworzyć natywny plik wykonywalny, który nie potrzebuje czasu wykonywania dotNet.
Wiemy tak dużo o właściwych technikach OO. Oddzielenie, programowanie na podstawie umowy, unikanie niewłaściwego dziedziczenia, właściwe użycie wyjątków, otwarty / zamknięty podmiot, zastępowalność Liskova i tak dalej. Jak dotąd frameworki .Net nie stosują najlepszych praktyk.
Dla mnie największą wadą w projektowaniu .Net jest nie stanie na barkach gigantów; promowanie mniej niż idealnych paradygmatów programowania wśród mas programistów, którzy używają ich frameworków .
Gdyby MS zwróciło na to uwagę, świat inżynierii oprogramowania mógł dokonać wielkich skoków pod względem jakości, stabilności i skalowalności w tej dekadzie, ale niestety wydaje się, że sytuacja się cofa.
Nie podoba mi się instrukcja przełączania języka C #.
Chciałbym coś takiego
switch (a) {
1 : do_something;
2 : do_something_else;
3,4 : do_something_different;
else : do_something_weird;
}
Więc koniec z przerwami (łatwe do zapomnienia) i możliwość oddzielenia przecinkami różnych wartości.
switchjest zasadniczo uszkodzony we wszystkich językach, które naśladują celowo uszkodzoną wersję C (zoptymalizowaną pod kątem szybkości!). VB wypada znacznie lepiej, ale wciąż jest lata świetlne za językami z dopasowywaniem wzorców (Haskell, F #…).
Zdarzenia w języku C #, w których musisz jawnie sprawdzić słuchaczy. Czy nie o to chodziło w przypadku wydarzeń, aby transmitować je każdemu, kto się tam znalazł? Nawet jeśli nie ma?
Okropne (i całkiem niewidoczne dla większości ludzi) zachowanie O (N ^ 2) zagnieżdżonych / rekurencyjnych iteratorów .
Jestem dość wypatroszony, że wiedzą o tym, wiedzą, jak to naprawić, ale nie jest to postrzegane jako mające wystarczający priorytet, aby zasługiwać na włączenie.
Cały czas pracuję ze strukturami podobnymi do drzewa i muszę poprawiać kod, który w innym przypadku jest mądry, gdy nieumyślnie wprowadzają w ten sposób bardzo kosztowne operacje.
Piękno „yield foreach” polega na tym, że prostsza, łatwiejsza składnia zachęca do poprawnego, wydajnego kodu. Jest to „dół sukcesu” , do którego powinni dążyć przed dodaniem nowych funkcji zapewniających długoterminowy sukces platformy.
Niektóre klasy implementują interfejsy, ale nie implementują wielu metod tego interfejsu, na przykład Array implementuje IList, ale 4 z 9 metod generują NotSupportedException http://msdn.microsoft.com/en-us/library/system.array_members .aspx
Statyczne elementy członkowskie i typy zagnieżdżone w interfejsach.
Jest to szczególnie przydatne, gdy człon interfejs ma parametr typu, który jest specyficzny dla złącza ( np się enum). Byłoby miło zagnieździć typ wyliczenia w typie interfejsu.
Strasznie niebezpieczny domyślny charakter wydarzeń. Fakt, że możesz wywołać wydarzenie i być w stanie niespójnym z powodu usuwania subskrybentów, jest po prostu okropny. Zobacz doskonałe artykuły Jona Skeeta i Erica Lipperta, aby dowiedzieć się więcej na ten temat.
null wszędzie.
const nigdzie.
Interfejsy API są niespójne, np. Mutują zwracaną tablicę, voidale dołączają do niej StringBuffertę samą zmienną StringBuffer.
Kolekcja interfejsy są niezgodne z niezmiennych struktur danych, na przykład Addw System.Collections.Generic.IList<_>nie może zwrócić wynik.
Bez pisania strukturalnego, więc piszesz System.Windows.Media.Effects.SamplingMode.Bilinearzamiast tylko Bilinear.
Mutowalny IEnumeratorinterfejs implementowany przez klasy, gdy powinien być niezmienny struct.
Równość i porównania są bałagan: masz System.IComparablei Equalsale potem już też dostał System.IComparable<_>, System.IEquatable, System.Collections.IComparer, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable, System.Collections.Generic.ICompareri System.Collections.Generic.IEqualityComparer.
Krotki powinny być strukturami, ale struktury niepotrzebnie uniemożliwiają eliminację wywołań końcowych, więc jeden z najbardziej powszechnych i podstawowych typów danych będzie niepotrzebnie alokować i zniszczyć skalowalny równoległość.
IEnumerator?
0 moonlighting as enum
osobliwości enum: http://blogs.msdn.com/abhinaba/archive/2007/01/09/more-peculiarites-of-enum.aspx
jak ilustruje ten dobry przykład: http://plus.kaist.ac.kr/~shoh/postgresql/Npgsql/apidocs/Npgsql.NpgsqlParameterCollection.Add_overload_3.html
moja sugestia, dobrze wykorzystaj znak „@”:
zamiast:
if ((myVar & MyEnumName.ColorRed)! = 0)
Użyj tego:
if ((myVar & MyEnumName.ColorRed)! = @ 0)
Aby dodać do długiej listy dobrych punktów już przez innych:
DateTime.Now == DateTime.Now w większości, ale nie we wszystkich przypadkach.
Stringktóry jest niezmienny ma wiele opcji konstrukcji i manipulacji, ale StringBuilder(który jest zmienny) nie.
Monitor.Enteri Monitor.Exitpowinny być metodami instancji, więc zamiast tworzyć nowy obiekt do blokowania, można dodać nowe a Monitori zablokować na tym.
Destruktory nigdy nie powinny być nazywane destruktorami. Specyfikacja ECMA nazywa je finalizatorami, co jest znacznie mniej zagmatwane dla tłumu C ++, ale specyfikacja języka nadal określa je jako destruktory.
DateTime.NowJeden jest na świecie najbardziej oczywistym warunkiem wyścig, ale +1 dla reszty
Sposób, w jaki używamy właściwości, czasami mnie irytuje. Lubię myśleć o nich jako o odpowiednikach metod Java getFoo () i setFoo (). Ale tak nie jest.
Jeśli wytyczne dotyczące użycia właściwości określają, że właściwości powinny być ustawiane w dowolnej kolejności, aby serializacja działała, wówczas są one bezużyteczne do sprawdzania poprawności w czasie ustawiania. Jeśli pochodzisz z tła, w którym lubisz zapobiegać temu, by obiekt kiedykolwiek znalazł się w nieprawidłowym stanie, to właściwości nie są twoim rozwiązaniem. Czasami nie widzę, jak są one lepsze niż członkowie publicznych, ponieważ jesteśmy tak ograniczone w jakiego rodzaju rzeczy jesteśmy powinien zrobić w nieruchomości.
W tym celu zawsze chciałem (jest to głównie głośne myślenie, po prostu chciałbym zrobić coś takiego), żebym mógł jakoś rozszerzyć składnię właściwości. Wyobraź sobie coś takiego:
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;
}
}
Nie jestem pewien, czy to jest przydatne, ani jak wyglądałoby uzyskiwanie do nich dostępu. Ale chciałbym po prostu zrobić więcej z właściwościami i naprawdę traktować je jak metody get and set.
Metody rozszerzeń są fajne, ale są brzydkim sposobem rozwiązywania problemów, które można by było rozwiązać za pomocą prawdziwych mikserów (spójrz na ruby, aby zobaczyć, o czym mówię), na temat miksów. Naprawdę fajnym sposobem na dodanie ich do języka byłoby zezwolenie na używanie typów generycznych do dziedziczenia. Pozwala to na rozszerzenie istniejących klas w przyjemny sposób zorientowany obiektowo:
public class MyMixin<T> : T
{
// etc...
}
można tego użyć w ten sposób, aby rozszerzyć ciąg, na przykład:
var newMixin = new MyMixin<string>();
Jest o wiele potężniejszy niż metody rozszerzające, ponieważ pozwala na przesłonięcie metod, na przykład zawinięcie ich, umożliwiając funkcjonalność podobną do AOP w języku.
Przepraszam za rant :-)
Microsoft nie naprawi oczywistych błędów we frameworku i nie zapewni podpórek, aby użytkownicy końcowi mogli je naprawić.
Ponadto nie ma sposobu na binarną poprawkę plików wykonywalnych .NET w czasie wykonywania i nie ma możliwości określenia prywatnych wersji bibliotek .NET Framework bez binarnego łatania bibliotek natywnych (w celu przechwycenia wywołania ładowania), a ILDASM nie podlega redystrybucji, więc nie mogę zautomatyzować mimo to łatka.
Możliwość wywołania metody rozszerzenia na zmiennej null jest dyskusyjna, np
obiekt a = null; a.MyExtMethod (); // to jest wywoływalne, załóżmy, że gdzieś zdefiniowano MyExtMethod
Może to być przydatne, ale jest niejednoznaczne w tematach wyjątków odniesienia o wartości zerowej.
Jedna wada nazewnicza. „C” elementu „configuration” w pliku System.configuration.dll powinno być pisane wielkimi literami.
Obsługa wyjątków. Wyjątek powinien być wychwycony lub wyrzucony na siłę, jak w Javie, kompilator powinien to sprawdzić w czasie kompilacji. Użytkownicy nie powinni polegać na komentarzach w przypadku informacji o wyjątkach w wywołaniu docelowym.
Metoda .Parameters.Add () w SqlCommand w V1 frameworka została okropnie zaprojektowana - jedno z przeciążeń w zasadzie nie działałoby, gdybyś przekazał parametr o wartości (int) 0 - to doprowadziło do utworzenia metoda .Parameters.AddWithValue () w klasie SqlCommand.
ICollection<T>i IList<T>; co najmniej kowariantny interfejs kolekcji tylko do odczytu IListSource<out T>(z modułem wyliczającym, indeksatorem i Count) byłby niezwykle przydatny.Transform(Sequence<T>, Func<T,T>)funkcję, która musiała szybko określić, czy funkcja zwraca tę samą wartość, czy inną. Jeśli funkcja nie modyfikuje większości / wszystkich swoich argumentów, sekwencja wyjściowa może współdzielić część / całą pamięć z sekwencji wejściowej. Bez możliwości bitowego porównywania wartości typu T należy użyć znacznie wolniejszego porównania, co ogromnie obniża wydajność.List<T>na hipotetyczny IListSource<U>(gdzie T: U), nawet jeśli klasa nie implementuje jawnie tego interfejsu. Istnieją co najmniej trzy różne biblioteki (napisane niezależnie), które dostarczają tę funkcjonalność (oczywiście z wadami wydajnościowymi - gdyby możliwe było idealne obejście, nie byłoby w porządku nazywanie tego wadą w .NET).WeakReference<T>(możesz łatwo napisać własne, ale wewnętrznie użyje rzutów).Predicate<T>Vs Func<T,bool>). Często żałuję, że nie możemy mieć strukturalnego typowania dla interfejsów i delegatów, aby uzyskać luźniejsze sprzężenie między komponentami, ponieważ w .NET nie wystarczy, aby klasy w niezależnych bibliotekach DLL implementowały ten sam interfejs - muszą również mieć wspólne odniesienie do trzeciego DLL, który definiuje interfejs.DBNull.Valueistnieje, chociaż nullrównie dobrze służyłby temu samemu celowi.variable = variable ?? value. Rzeczywiście, jest kilka miejsc w C #, w których niepotrzebnie brakuje symetrii. Na przykład możesz pisać if (x) y(); else z();(bez nawiasów klamrowych), ale nie możesz pisać try y(); finally z();.IList<T>, chociaż użyłbym IReadableByIndex<out T>i IAppendable<in T>. Wiele z twoich innych rzeczy to skrzeczenia, z którymi też się zgadzam.
IListReader<T>;) - Używam słowa „source” jako antonimu „sink” (interfejs tylko do zapisu).
IListSource<in T>lub IReadableList<out T>. Posiadanie podstawowych typów interfejsów zawierających metody, które nie istnieją we wszystkich pochodnych może mieć wartość, chociaż myślę, że często dobrze jest mieć nieco wyspecjalizowane interfejsy. Na przykład można mieć metodę, IList<T>która zawiera metody zmiany rozmiaru, które mogą działać lub nie, i IResizableList<T>która implementuje te same metody, ale gwarantuje, że powinny działać. Takie podejście może być przydatne w przypadkach, gdy pole może zawierać jedyne istniejące odwołanie do listy zmiennej lub wspólne odwołanie do niezmiennej.
if (rl:(list as IResizableList<T>) != null) rl.Add(...);, ale są inne propozycje. Jako autor różnych kolekcji i adapterów do kolekcji irytuje mnie pisanie wielu fikcyjnych metod, które rzucają wyjątki. Jako fan bezpieczeństwa typu nie chcę, aby można było nazywać nielegalne metody. Jestem fanem IntelliSense, nie chcę ich widzieć na liście.
Jedna rzecz, która mnie wkurzyla w 1.x był przy korzystaniu z System.Xml.XmlValidatingReaderThe ValidationEventHandler„s ValidationEventArgsnie odsłaniające XmlSchemaException(oznaczony wewnętrznym), który posiada wszystkie przydatne informacje, jak linenumberi position. Zamiast tego oczekuje się, że przeanalizujesz to z właściwości ciągu komunikatu lub użyj odbicia, aby go wykopać. Niezbyt dobre, gdy chcesz zwrócić bardziej oczyszczony błąd użytkownikowi końcowemu.
Nie podoba mi się to, że nie możesz użyć wartości jednego wyliczenia w innym wyliczeniu, na przykład:
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 }
Zmienne z niejawnym typowaniem zostały zaimplementowane słabo IMO. Wiem, że naprawdę powinieneś ich używać tylko podczas pracy z wyrażeniami Linq, ale denerwujące jest to, że nie możesz ich zadeklarować poza lokalnym zakresem.
Z MSDN:
Powodem, dla którego uważam, że jest to kiepska implementacja, jest to, że nazywają ją var, ale daleko jej do bycia wariantem. To naprawdę tylko skrócona składnia, która nie wymaga wpisywania pełnej nazwy klasy (z wyjątkiem sytuacji, gdy jest używana z Linq)