Wady projektu C # (.NET) [zamknięte]


84

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.


W jakim sensie są te wady projektowe?
Julia

Dzięki IDataReader możesz używać IsDBNull zamiast sprawdzać ręcznie
Marc Gravell

9
Cue Jon Skeet, aby porozmawiać o klasach zamkniętych;)
johnc

3
Naprawienie IDataReader za pomocą metody rozszerzenia jest dość łatwe: patrz weblogs.asp.net/skillet/archive/2008/06/18/… .
Robert Rossney,

@lagerdalek - dałbym +1 temu komentarzowi, gdybym mógł; dobrze zapamiętany
Marc Gravell

Odpowiedzi:


39

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:

  1. typy odwołań nie dopuszczające wartości null jako uzupełnienie typów wartości dopuszczających wartość null,
  2. pozwalają na nadpisanie pustego konstruktora struktury,
  3. zezwalaj na ogólne ograniczenia typu na określenie klas zapieczętowanych,
  4. Zgadzam się z innym plakatem, który żądał dowolnych podpisów konstruktorów, gdy są używane jako ograniczenia, tj. gdzie T : new(string)lub gdzieT : new(string, int)
  5. Zgadzam się również z innym postem o naprawianiu wydarzeń, zarówno dla pustych list wydarzeń, jak i w ustawieniu równoległym (choć to drugie jest trudne),
  6. operatory powinny być definiowane jako metody rozszerzające, a nie jako metody statyczne klasy (lub przynajmniej jako metody statyczne),
  7. zezwalaj na statyczne właściwości i metody dla interfejsów (Java to ma, ale C # nie),
  8. zezwolić na inicjalizację zdarzeń w inicjalizatorach obiektów (obecnie dozwolone są tylko pola i właściwości),
  9. dlaczego składnia „inicjatora obiektu” jest użyteczna tylko podczas tworzenia obiektu? Dlaczego nie udostępnić go w dowolnym momencie, tj.var e = new Foo(); e { Bar = baz };
  10. naprawić kwadratowe wyliczalne zachowanie ,
  11. wszystkie kolekcje powinny mieć niezmienne migawki do iteracji (tj. mutacja kolekcji nie powinna unieważniać iteratora),
  12. krotki są łatwe do dodania, ale efektywny zamknięty typ algebraiczny, taki jak " 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,
  13. Bardzo chciałbym w ogóle wspierać dopasowywanie wzorców, ale przynajmniej testowanie typów obiektów; Podoba mi się też składnia przełącznika zaproponowana w innym poście tutaj,
  14. Zgadzam się z innym postem, że System.IOzajęcia Streamsą trochę źle zaprojektowane; każdy interfejs, który wymaga pewnych implementacji, NotSupportedExceptionjest złym projektem,
  15. IListpowinno być znacznie prostsze niż jest; w rzeczywistości może to być prawdą dla wielu interfejsów zbiórki beton, jak ICollection,
  16. zbyt wiele metod generuje wyjątki, na przykład IDictionary,
  17. Wolałbym formę sprawdzonych wyjątków lepszą niż ta dostępna w Javie (zobacz badania nad systemami typów i efektów, aby dowiedzieć się, jak to zrobić),
  18. naprawić różne irytujące przypadki narożne w ogólnej metodzie rozwiązywania problemu; na przykład spróbuj podać dwie przeciążone metody rozszerzające, jedną działającą na typach referencyjnych, a drugą na typach struktur dopuszczających wartość null, i zobacz, jak podoba się to Twojemu wnioskowi o typie,
  19. zapewniają sposób bezpiecznej refleksji nad nazwami pól i elementów członkowskich dla interfejsów, takich jak 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,
    • Aktualizacja: C # 6.0 dodał 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ń.
  20. zezwolić operatorom na interfejsy i zaimplementować wszystkie podstawowe typy liczb IArithmetic; możliwe są również inne przydatne współdzielone interfejsy operatora,
  21. utrudnić mutację pól / właściwości obiektów lub przynajmniej zezwolić na dodawanie adnotacji do pól niezmiennych i sprawić, by sprawdzanie typów wymusiło to (potraktuj to tylko jako właściwość pobierającą tylko dla chrissakes, to nie jest trudne!); w rzeczywistości ujednolicaj pola i właściwości w bardziej rozsądny sposób, ponieważ nie ma sensu mieć obu; Automatyczne właściwości języka C # 3.0 to pierwszy krok w tym kierunku, ale nie idą wystarczająco daleko,
    • Aktualizacja: chociaż C # miał 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.
  22. uprościć deklarowanie konstruktorów; Podoba mi się podejście F #, ale inny post tutaj, który wymaga po prostu „nowego” zamiast nazwy klasy, jest przynajmniej lepszy,

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:

  1. byłoby naprawdę fajnie, gdyby C # / CLR mógł obsługiwać polimorfizm konstruktora typu, tj. typy generyczne nad rodzajami,

Pięknie proszę? :-)


1
W odniesieniu do # 1, każdy typ musi mieć jakąś wartość domyślną lub system musi zapewnić sposób uruchamiania konstruktora za każdym razem, gdy zostanie przydzielona zmienna lub pole określonego typu. Wolałbym to drugie (odgałęzienie nr 2), ale nr 1 mógłby zostać uwzględniony, gdyby można było ozdobić metody / właściwości niewirtualne, aby określić, że powinny być wywoływane bez sprawdzania wartości null. Pozwoliłoby to polom typu „String” zachowywać się tak, jakby domyślnie były pustym łańcuchem zamiast null (ponieważ statyczna funkcja „length” String mogłaby zwrócić 0, gdyby została wywołana na łańcuchu zerowym).
supercat

1
W odniesieniu do # 2 byłoby przydatne, gdyby struktury mogły określać nie tylko konstruktor inny niż wypełnij zerem, ale także konstruktor kopiujący inny niż kopia bajt po bajcie. Właściwie chciałbym zobaczyć strukturę podobną do .net, która mogłaby wykonać dobrą robotę rozpoznawania, że ​​encje mogą mieć semantykę wartości lub referencji i pozwalać na oznaczanie obiektów sterty typu wartości jako mutowalne, współużytkowane-niezmienne lub niezatwierdzone (niezatwierdzony obiekt wartości może zostać zmutowany, jeśli jego tryb jest pierwszy CompareExchange na mutable, lub może być współużytkowany, jeśli jego tryb jest pierwszy CompareExchangeed to shared).
supercat

1
Doskonałe punkty! W przypadku # 1 typowym rozwiązaniem jest adnotacja systemu typów, ale wolę propagować ograniczenia konstruktora za pomocą zmiennych typu, takich jak T: new (). Re: # 2, dobra uwaga na temat konstruktorów kopiujących, ale byłbym zadowolony z bardziej ogólnych konstruktorów zgodnie z liniami, które opisałem powyżej. Jeszcze lepiej byłoby całkowicie wyeliminować konstruktory jako wyróżnione metody i po prostu uczynić je metodami statycznymi. Pozwala to na prostsze i bardziej ogólne wzorce konstrukcyjne, szczególnie jeśli dopuszczamy metody statyczne na interfejsach. Constructors-as-static-methods + static-methods-in-interfaces rozwiązuje również # 1.
naasking

3
# 3: Jaki jest sens używania zapieczętowanej klasy jako parametru typu ogólnego, np. Foo <T> gdzie T: string? # 11: OK, więc mam 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.
Qwertie

2
To bardzo interesująca odpowiedź, ale myślę, że powinniśmy zaktualizować funkcje C # 6. Przykład: Elementy 19 i 21 zostały zaimplementowane =)
eduardobr

72
  • Reset()metoda na IEnumerator<T>to pomyłka (iterator dla bloków, spec język nawet wymaga , że ta zgłasza wyjątek)
  • metody odbicia zwracające tablice były zdaniem Erica błędem
  • kowariancja tablic była i pozostaje dziwactwem
    • Aktualizacja: C # 4.0 z .NET 4.0 dodał obsługę kowariantów / kontrawariancji do interfejsów ogólnych (takich jak 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?
  • kolekcje zsynchronizowane - fajny pomysł, ale niekoniecznie przydatny w rzeczywistości: zwykle trzeba zsynchronizować wiele operacji ( Containsa Addwięc), więc zbiór, który synchronizuje różne operacje, nie jest aż tak przydatny
    • Aktualizacja: W 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.
  • można by bardziej wykorzystać wzorzec 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śniejsze
  • bloki iteratorów: brak prostego sposobu sprawdzania argumentów z wyprzedzeniem (raczej niż leniwie). Jasne, możesz napisać dwie metody łańcuchowe, ale to jest brzydkie
  • prostsza niezmienność byłaby miła; C # 4.0 trochę pomaga , ale niewystarczająco
  • brak obsługi „tego parametru typu ref nie może mieć wartości null” - chociaż kontrakty (w wersji 4.0) nieco w tym pomagają. Ale składnia taka jak Foo(SqlConnection! connection)(która wprowadza kontrolę null / throw) byłaby fajna (w przeciwieństwie do int?itp.)
  • brak obsługi operatorów i konstruktorów innych niż domyślne z generycznymi; C # 4.0 rozwiązuje to trochę za pomocą dynamiclub możesz to włączyć w ten sposób
  • zmienna iteratora jest zadeklarowana poza while w foreachinterpretacji, co oznacza, że ​​metody anon / lambdy przechwytują pojedynczą zmienną, a nie jedną na iterację (bolesne z wątkami / asynchronicznymi / itp.)

IEnumerable! = IEnumerable <object> jest rzeczywiście dziwny
Rauhotz

2
Cóż, IEnumerable to kac 1.1; możesz użyć .Cast <object> () z LINQ, przynajmniej
Marc Gravell

8
Ludzie z BCL powiedzieli, że ApplicationExceptionto błąd - nieprzydatny, tak jak mieli nadzieję. Powiedzieli też, że System.Exceptionpowinno abstract.
Jay Bazuzi

2
Nie dopuszcza wartości null: powinno być błędem kompilacji, aby przekazać zwykły typ odwołania T do czegoś, co przyjmuje wartość T nie dopuszczającą wartości null! (tak jak nie możesz przekazać int? do int). Oczywiście przejście w drugą stronę jest w porządku.
Jay Bazuzi,

1
@Jon Harrop: IMHO, powinno istnieć wsparcie dla niezmiennych tablic i odwołań do tablic tylko do odczytu. Prawdopodobnie także dla innych wariantów tablicy (np. "Tablica o zmiennym rozmiarze" (odwołanie pośrednie) lub tablica z odniesieniem do tablicy z przesunięciem i powiązaniem).
supercat

60

TextWriter jest klasą bazową StreamWriter. wtf?

To zawsze wprawia mnie w skrajne zamieszanie.


19
+1 Muszę to sprawdzić za każdym razem. (Co znaczy, że nie mogę nowego TextWriter ()?)
Nicholas Piasecki

1
Dzięki Bogu ... myślałem, że to tylko ja.
IJ Kennedy,

? To zawsze coś, co zapisuje tekst, ale tylko StreamWriter robi to w strumieniu. Wydaje się całkiem proste.
Jon Hanna

4
Nazwa StreamWriter nie sprawia, że ​​fakt, że pisze tekst jest wystarczająco oczywisty IMO. Brzmi to w izolacji, jakby po prostu zapisywał bajty, a TextWriter byłby fantazyjną implementacją, która przekształciłaby api (string toWrite) na bajty. Teraz, gdyby nazywało się StreamTextWriter, to na pewno byłoby od razu oczywiste, ale trochę długie :(
Quibblesome

44

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ść.


Erm, skąd miałoby to wiedzieć, czego próbujesz utworzyć nową instancję?
BlueRaja - Danny Pflughoeft

4
@BlueRaja: Scott odnosi się do nazewnictwa konstruktorów w klasach. class Foo { new(int j) {i = j} int i; }
dalle

Chociaż zgadzam się w 100%, że ctor () lub constructor () byłyby lepsze ( 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.
Qwertie

związane z tym pytaniem: stackoverflow.com/questions/32101993/c-sharp-sorted-linkedlist nie ma sposobu (używając tylko .NET), aby po prostu posortować LinkedList za pomocą MergeSort, bucketsort lub innego algorytmu sortowania, ale używając linq, który jest o wiele wolniejszy niż wdrożenie ad hoc.
CoffeDeveloper

29

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

}

Typ ogólny deklaruje, w jaki sposób będziesz używać obiektów tego typu, czyli ich interfejs. Konstruktor to szczegół implementacji tego interfejsu, który nie jest problemem konsumenta. Jeśli chcesz utworzyć sparametryzowaną instancję, użyj fabryki.
Bryan Watts

Dla informacji, chociaż nie może on sprawdzać czasu kompilacji, istnieje kod w MiscUtil do efektywnego używania konstruktorów innych niż domyślne (w typach generycznych), tj. Bez Activator.CreateInstance lub odbicia.
Marc Gravell

Ponieważ nie ma to sensu i jest mylące w niektórych zastosowaniach. Niemniej jednak może być przydatny podczas pracy z niezmiennymi obiektami.
Pop Catalin

7
Ogólnie rzecz biorąc, brak ograniczeń członków jest denerwujący, tak.
MichaelGG

3
amen, zawsze tego chciałem
Steve,

20

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.


Z całego serca się zgadzam! Wkurza mnie to za każdym razem, gdy muszę go używać (co jest często). Argh! +1
Eyvind,

2
Mogliby przynajmniej wymyślić nową klasę DataSetV2 (zła nazwa - tylko dla dobra argumentów), która używałaby typów wartości dopuszczających wartość null zamiast DBNull w całym tekście.
Christian Hayter,

Nie zapominajmy o absurdalności wymagania specjalnej wartości 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.
Qwertie

W rzeczywistości ten absurd jest skałą, na której zbudowano cały absurdalny gmach.
Robert Rossney,

20
  1. Nie jestem wielkim fanem klas Stream, StringWriter, StringReader, TextReader, TextWriter ... po prostu nie jest to intuicyjne.
  2. IEnumerable.Reset zgłasza wyjątek dla iteratorów. Mam kilka komponentów innych firm, które zawsze wywołują resetowanie, gdy są związane z danymi, wymagają ode mnie najpierw rzutowania na listę, aby ich użyć.
  3. Xml Serializer powinien mieć zserializowane elementy IDictionary
  4. Zupełnie zapomniałem o HttpWebRequest i FTP API, co za ból w moim ... (dzięki za komentarz Mikołaja, który mi o tym przypomniał :-)

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.


1
+1 Muszę za każdym razem wyszukać dowolną z klas XmlTextWriter, TextWriter itp. To samo dotyczy rzeczy HttpWebRequest / Response. Całkowicie nieintuicyjny interfejs API.
Nicholas Piasecki

+ 1-1 = 0: XmlTextWriter itp., Zgadzam się, że niemożliwe jest wywnioskowanie, jakie są one na podstawie nazwy. HttpWebRequest Nie zgadzam się. Uważam to za dość intuicyjne.
AnthonyWJones

Przypuszczam, że każdy ma swój własny. Myślę, że po FTP spodziewałbym się wyższego poziomu abstrakcji niż to, co mają.
JoshBerke

15

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? :)


3
Microsoft powinien brać pod uwagę Code Golf podczas projektowania języków?
jrcs3

3
@Ray Burns: Skąd to wie w VB? VB to obsługuje, więc jaka jest różnica?
BenAlabaster

3
@RayBurns Wnioskowanie o typie? Używam tego od 1989 roku.
RD1,

3
Lamdy są homoikoniczne w C #. fragment (int x) => x * (x -1);może znaczyć Func<int, int>lub może znaczyćExpression<Func<int, int>>
Scott Weinstein

3
@BenAlabaster: VB obsługuje operatory arytmetyczne z późnym wiązaniem. C # musi je rozwiązać w czasie kompilacji. To różnica językowa. Na przykład VB może dodać do siebie dwa obiekty. C # nie może, ponieważ +nie jest zdefiniowany dla object.
rekurencyjne

14
  1. 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).

  2. Właściwość ICollection.SyncRoot :

    • Promuje kiepski projekt, zamek zewnętrzny jest prawie zawsze bardziej przydatny.
  3. Leki generyczne powinny być tam od początku:

    • Przestrzeń nazw System.Collections zawiera wiele mniej lub bardziej przestarzałych klas i interfejsów.

1
1. Te metody są tak powszechne, że zdecydowano, że dziwne przypadki są w porządku, czy ma znaczenie, czy ktoś może wywołać Object.Equals w Twojej klasie? Wiadomo, że może istnieć implementacja lub nie, a wymaganie: IEquatable, IFormattable na 99% klas jest nieparzyste.
Guvante

1
Przydatna jest możliwość np. Skonstruowania słownika z obiektami zdefiniowanymi przez użytkownika jako kluczami, przy użyciu domyślnej równości odwołań, bez konieczności jawnego dodawania w tym celu kodu do obiektów zdefiniowanych przez użytkownika. Uznałbym Finalize za znacznie większe marnotrawstwo (lepszą alternatywą byłoby posiadanie obiektów, które będą wymagały finalizacji, zaimplementować iFinalizable i jawnie zarejestrować się do finalizacji). OTOH, powinno być bardziej nieodłączne wsparcie dla iDisposable, w tym wywoływanie Dispose, jeśli konstruktor zgłosi wyjątek.
supercat

1
@supercat: Wszystko, co jest potrzebne, to 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.
dalle

1
@supercat: To, co opisujesz, jest dokładnie tym, co 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.
dalle

1
@supercat: słownik musi używać najbardziej ogólnego typu (tj. wspólnej klasy bazowej) jako klucza, użycie zarówno ciągów znaków, jak i daty i godziny w tym samym słowniku nie spowoduje żadnego sensu, chyba że użycie porównania referencyjnego, chyba że zostanie udowodnione porównanie zdefiniowane przez użytkownika jest. Pamiętaj, że nazwa tego tematu to „C # (.NET) Wady projektowe”.
dalle

12

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.


Jest pewna sztuczka z użyciem Delegate.Create i trochę rzutowania, aby wykonać konwersję, ale przynajmniej możliwość wykonania jawnego rzutowania byłaby miła (rozumiem jednak brak wsparcia dla implicit)
Guvante,

Konstrukcja delegatów jest ogólnie wadliwa; na przykład brak słabych zdarzeń (implementacja słabych zdarzeń po stronie źródła bez specjalnego wysiłku ze strony abonenta może być wykonana tylko z garścią refleksji i ReflectionPermission, patrz codeproject.com/Articles/29922/Weak-Events-in-C ), i nieefektywność, która wynika z wymogu, że delegaci muszą być typami referencyjnymi (delegaci byliby szybsi i zużyliby 1/3 pamięci w wielu przypadkach, gdyby byli typami wartości - wtedy byliby po prostu parą wskaźniki, które możesz przekazać na stosie.)
Qwertie

11

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.


Powinieneś być w stanie uruchomić program NGEN przed uruchomieniem.
Otávio Décio

To nie to samo, co usuwanie zależności środowiska wykonawczego. Zapisujesz tylko pierwszy przebieg JIT na swoim kodzie.
Ed S.,


Czy nie ma narzędzia zaciemniającego, które to robi? Osadzi framework w twoim exe, więc nie musisz go wdrażać. Nie mogę sobie przypomnieć jego nazwy ... to nie PreEmptive's ...
JoshBerke

2
Czy Postbuild Xenocode tego nie robi? Byłoby miło, gdyby Visual Studio miało na to sposób ...
BenAlabaster,

11

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.


4
+1 za rant celu; Dodałbym do tej niezdolności do podklasy każdej klasy, brak interfejsów dla podstawowych klas, a niechęć do błędów ramowych naprawić nawet po kilku latach
Steven A. Lowe

1
Jeśli przejdziemy do sedna, czy mówimy, że frameworki .Net są tak złe, że trudno jest zdecydować, która wada jest najgorsza? Czuję się lepiej po wentylacji i doceniam pozytywne głosy, ponieważ spodziewałem się, że będę krzyczeć przez chłopców-fanów MS.
Daniel Paull,

I tak nie głosowałem, a poza tym jestem bardzo niechętny do wydawania głosów negatywnych, ale próbuję dowiedzieć się, dlaczego PO PROSTU MNIE NIE ZALEŻY.
Mike Dunlavey

2
Myślę, że mówisz „nie jest tak dobre, jak mogłoby być”, co jest brakiem odpowiedzi. Nic nie jest doskonałe. Podaj szczegóły.
jcollum

3
Nie, mówię, że jest wiele przypadków, w których projekt jest ewidentnie wadliwy, a kiedy myślisz, że robią to dobrze, nadal źle to rozumieją. Na przykład mój post na forach MSDN tutaj: social.msdn.microsoft.com/forums/en-US/wpf/thread/…
Daniel Paull

11

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.


Właściwie myślę, że byłoby jeszcze lepiej, gdyby wymagało to pojedynczej instrukcji lub bloku w nawiasach klamrowych - jak wszystko inne w C #. Bez możliwości przejścia, obecna składnia przerwania jest nieco podejrzana i nie ogranicza też zakresu. (AFAIK, może, nie wiem)
Tamas Czinege

Nie rozumiem, co masz na myśli. Zamieść tutaj swoje własne „idealne” oświadczenie dotyczące przełączania. Nie chcę przechodzić przez wartości oddzielone przecinkami.
tuinstoel

8
Nawiasem mówiąc, zgadzam się z OP. 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 #…).
Konrad Rudolph

1
tuinostel: coś takiego jak przełącznik (a) {przypadek 1 {do_something; } przypadek 2 {do_something_else; }} - czyli pozbycie się instrukcji break i wymaganie odpowiednich bloków kodu dla każdego przypadku
Tamas Czinege

2
Przerwa wydaje się ogólnie błędem, czy nie jest to błąd kompilacji, aby ją wyemitować? Wydaje się, że istnieje tylko po to, aby ułatwić przejście z C do C # (aka uczenie programistów, że nie mogą automatycznie przejść)
Guvante

10

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?


1
To denerwujące, że nie ma do tego cukru, kiedy tego chcesz, rozumiem, dlaczego robią to ciężko, zachęca to do niepotrzebnego tworzenia instancji argumentów wydarzenia
ShuggyCoUk

Hm. Albo nie rozumiem, albo się nie zgadzam, albo jedno i drugie :-). Nie mogę powiedzieć, że kiedykolwiek czułam się zniechęcona do tworzenia instancji. Dla mnie to pachnie jak przedwczesna optymalizacja.
Thomas Eyde

W niektórych przypadkach metody częściowe są realną alternatywą.
Robert Harvey

PLUS całe sprzężenie, które powoduje ... MS ma CAB, który rozwiązuje oba te problemy, ale CAB ma wiele własnych problemów z powodu ograniczeń w C # (np. Ciągi znaków zamiast wyliczeń jako tematy zdarzeń) - dlaczego po prostu nie tworzyć luźno -sprzężona część wydarzeń w języku !?
BlueRaja - Danny Pflughoeft

9

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.


Jest to szczególnie wadliwe, ponieważ jest sprzeczne z intuicyjnymi oczekiwaniami dotyczącymi zachowania O, a więc będzie pułapką dla wielu ludzi
oefe,

7

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


Cóż, nie możesz zmienić liczby elementów w tablicy, więc nie ma nic, co Add, Clear, Insert i Remove (At) nie mogą zrobić, ale wrzucić NotSupported ... W rzeczywistości spodziewałbym się KAŻDEJ implementacji IList, która zwraca prawdę dla IsFixedSize rzuci na nie.
CB

Wydaje mi się, że @CB trochę spóźniłem się na imprezę :) Ale jeśli Array nie może spełnić "IList", po co go zrealizować? To jest pogwałcenie L w zasadzie SOLID.
Skrzydłowy Sendon

7

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.


1
Czy to nie jest bardzo podobne do Twojej innej sugestii?
RCIX

1
Nie, ten dotyczy języka C #, a drugi frameworka. Nie każdemu zależy na rozróżnieniu, więc powiem, że ten dotyczy tego, co jest dozwolone, a drugi tego, co jest zapewnione.
Jay Bazuzi

6

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.


Nie miałbym nic przeciwko, gdyby zdarzenia nie były domyślnie bezpieczne dla wątków (mogłoby to zwiększyć wydajność w kodzie jednowątkowym); głupie jest to, że dodawanie / usuwanie jest domyślnie bezpieczne, ale naturalny sposób wywołania zdarzenia jest niebezpieczny i nie ma możliwości łatwego zabezpieczenia go.
Qwertie

@Qwertie: Głupsze jest to, że przez jakiś czas dodawanie / usuwanie używałoby blokowania, a mimo to nie byłby bezpieczny dla wątków.
supercat

6
  • 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ść.


W środowisku CLR nie ma typowania strukturalnego, ale wydaje się, że typowanie strukturalne jest pomieszane z wnioskami o typie lub z funkcją języka Ruby znaną jako „symbole”. Typowanie strukturalne byłoby, gdyby środowisko CLR uznało Func <int, bool> i Predicate <int> za tego samego typu lub przynajmniej niejawnie konwertowalne.
Qwertie

Mówiąc o porównaniu, nie zapomnij o Comparer <T>!
Qwertie

@Qwertie Odnosiłem się do funkcji języka programowania, takich jak warianty polimorficzne w OCaml. Biblioteka OCaml LablGL zawiera wiele interesujących przykładów strukturalnego typowania użytecznych w kontekście grafiki. Nie ma to nic wspólnego z wnioskami o typie i tylko stycznie odnosi się do symboli.
JD

1
Jak można użyć niezmiennej struktury IEnumerator?
supercat

5

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)


1
+1 wyliczenie to jedna z niewielu rzeczy, które Java zrobiła dobrze, a C # nie
BlueRaja - Danny Pflughoeft

5

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.


3
DateTime.NowJeden jest na świecie najbardziej oczywistym warunkiem wyścig, ale +1 dla reszty
BlueRaja - Danny Pflughoeft

Nie chodzi o to, że jest to stan wyścigu, ale o to, że uczynili z tego własność. Właściwości wyglądają dokładnie jak pola, więc jest to raczej zaskakujące zachowanie IMO.
Brian Rasmussen

4
@Brian Rasmussen: DateTime.Now jest właściwie właściwością, ponieważ zmienia się nie przez jej odczyt, ale przez czynniki zewnętrzne. Jeśli odczytuje się właściwość taką jak SomeForm.Width, a następnie - po zmianie rozmiaru formularza przez użytkownika - odczytuje się ją ponownie, wartość przy drugim odczycie będzie inna. Chociaż możliwe jest, że wykonanie pierwszej DateTime.Now może zająć wystarczająco dużo czasu, aby wpłynąć na wartość odczytywaną przez drugą, efekt taki nie różni się od innych funkcji, których wykonanie zajęło tyle samo czasu.
supercat

4

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.


3
Uważam, że zapewniają interfejs ISerializable i niejawny konstruktor do robienia takich rzeczy, czyli gdy nie chcesz, aby serializator po prostu wywoływał właściwości. Chociaż trochę więcej pracy, wygląda na to, że większość tej pracy wykonujesz już swoją metodą.
Guvante

4

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


5
Ciekawe, ale wolę metody rozszerzające. Jeśli otrzymam bibliotekę, która zawiera kilka metod rozszerzających dla ciągów, nie chcę zmieniać wszystkich odniesień do ciągów na MyMixin <string>, aby uzyskać nowe rzeczy. To trochę drobne, jasne, ale to przejrzyste dodawanie metod sprawia, że ​​metody rozszerzające są tak ładne.
RCIX

A tak przy okazji, czy wiesz, że to już działa?
RCIX

2
Nie widzę, jak LINQ mógłby działać w ten sposób
BlueRaja - Danny Pflughoeft

2
@RCIX: Mixiny brzmią tak, jak myślałem, że powinny działać metody rozszerzające. Problem z utajnieniem metod rozszerzających polega na tym, że oznacza to, że prawdziwi członkowie klasy muszą mieć pierwszeństwo przed metodami rozszerzającymi. Jeśli zdefiniowano metodę rozszerzającą Graphics.DrawParallelogram (Pen p, Point v1, Point v2, Point v3), a później do System.Graphics dodawana jest funkcja DrawParallelogram, która używa punktów w innej kolejności, kod używający metody rozszerzającej zostanie przerwany bez ostrzeżenie. BTW, czy byłby jakiś problem z użyciem dwóch kropek dla metod rozszerzających (np. Object..method ()?)
supercat

3

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.


1
Jakie oczywiste błędy frameworka masz na myśli?
Robert Rossney,

1
# 1 Kliknij częściowo widoczną kontrolkę podrzędną kontrolki przewijanej. Kontrolka jest przenoszona do widoku przed odebraniem zdarzenia MouseDown, powodując, że kliknięcie znajduje się w innym miejscu kontrolki niż oczekiwano. Gorzej jest w widokach drzewa, w których również wyzwala operację przeciągania.
Joshua,


3
  • 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.


3
Jednak bardzo przydatne - mam metodę rozszerzenia „ThrowIfNull” do sprawdzania parametrów ;-p
Marc Gravell

2
możesz to zrobić? ugh ThrowIfNull to interesujące rozszerzenie, ale to po prostu wydaje się błędne.
JoshBerke,

1
W zwykłym środowisku CLR można wywołać metody instancji dla odwołań o wartości null, a jeśli metoda nie ma dostępu do obiektu lub jego pól, wywołanie nie zgłasza wyjątku odwołania o wartości null. (Nie możesz tego zrobić w C #, ponieważ używa callvirt nawet dla metod niewirtualnych)
Pop Catalin,

7
wyjątek jest bardzo zły. Nie możesz szybko zawieść, jeśli MUSISZ złapać każdy cholerny wyjątek, który może zostać wrzucony na stos wywołań. Żałuję jednak, że nie było łatwiej zidentyfikować wszystkie wyjątki, które mogą zostać rzucone w danym wywołaniu i wynikający z niego

3
@Will: Obsługa wyjątków jest okropna zarówno w Javie, jak i .net, ponieważ zastosowany mechanizm ściśle wiąże ze sobą trzy koncepcje, które są nieco powiązane, ale także nieco ortogonalne: (1) Jaki typ rzeczy poszedł nie tak (błąd granic tablicy, przekroczenie czasu I / O itp.); (2) Czy określony kod powinien w rezultacie wykonać akcję; (3) W którym momencie problem należy uznać za „rozwiązany”. Rozważmy procedurę, która ma mutować obiekt z danymi odczytanymi z IEnumerable. Co powinno się stać, jeśli wystąpi wyjątek podczas przetwarzania tego IEnumerable?
supercat

3

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.


Zgadzam się, ale myślę, że masz na myśli metodę SqlCommand.Parameters.Add ().
Matt Peterson,

3
  1. Nie ma podzbiorów 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.
  2. NET nie obsługuje słabych delegatów . Obejścia są w najlepszym razie niezdarne, a obejścia po stronie słuchacza są niemożliwe w przypadku częściowego zaufania (wymagana jest opcja ReflectionPermission).
  3. Ujednolicenie ogólnego interfejsu jest zabronione, nawet jeśli ma sens i nie powoduje problemów.
  4. W przeciwieństwie do C ++ kowariantne typy zwracane nie są dozwolone w .NET
  5. Nie jest możliwe bitowe porównywanie dwóch typów wartości dla równości. W funkcjonalnej „ trwałej ” strukturze danych pisałem 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ść.
  6. Wydaje się, że .NET nie jest w stanie obsługiwać interfejsów ad-hoc (takich jak te oferowane w Go lub Rust) w wydajny sposób. Takie interfejsy pozwoliłyby na rzutowanie 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).
  7. Inne problemy z wydajnością: IEnumerator wymaga dwóch wywołań interfejsu na iterację. Zwykłe wskaźniki metody (otwarte delegaty o rozmiarze IntPtr) lub delegaci o typie wartości (IntPtr * 2) nie są możliwe. Tablice o stałym rozmiarze (dowolnego typu T) nie mogą być osadzane wewnątrz klas. Nie ma WeakReference<T>(możesz łatwo napisać własne, ale wewnętrznie użyje rzutów).
  8. Fakt, że identyczne typy delegatów są uważane za niekompatybilne (brak niejawnej konwersji), był dla mnie czasami dokuczliwy (np. 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.
  9. DBNull.Valueistnieje, chociaż nullrównie dobrze służyłby temu samemu celowi.
  10. C # nie ma operatora ?? =; musisz napisać 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();.
  11. Podczas tworzenia wątku nie można spowodować, aby wątek podrzędny dziedziczył wartości lokalne wątku z wątku nadrzędnego. BCL nie tylko tego nie obsługuje, ale nie możesz tego zaimplementować samodzielnie, chyba że ręcznie utworzysz wszystkie wątki; nawet jeśli wystąpiło zdarzenie związane z tworzeniem wątku, .NET nie może podać „rodziców” lub „dzieci” danego wątku.
  12. Fakt, że istnieją dwa różne atrybuty długości dla różnych typów danych, „Długość” i „Liczba”, jest niewielką uciążliwością.
  13. Mógłbym bez końca mówić o złym projekcie WPF ... a WCF (choć całkiem przydatne w niektórych scenariuszach) jest również pełne brodawek. Ogólnie rzecz biorąc, wzdęcia, nieintuicyjność i ograniczona dokumentacja wielu nowszych bibliotek podrzędnych BCL sprawiają, że niechętnie ich używam. Wiele nowych rzeczy mogło być znacznie prostszych, mniejszych, łatwiejszych w użyciu i zrozumieniu, luźniej powiązanych, lepiej udokumentowanych, nadających się do większej liczby przypadków użycia, szybszych i / lub silniej wpisanych na maszynie.
  14. Często gryzie mnie niepotrzebne sprzężenie między metodami pobierającymi i ustawiającymi właściwości: w klasie pochodnej lub interfejsie pochodnym nie można po prostu dodać metody ustawiającej, gdy klasa bazowa lub interfejs bazowy ma tylko metodę pobierającą; jeśli nadpisujesz metodę pobierającą, nie możesz zdefiniować metody ustawiającej; i nie możesz zdefiniować ustawiającego jako wirtualnego, ale pobierającego jako niewirtualnego.

Zgadzam się z tobą co do podzbiorów 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.
supercat

To naprawdę długie imię. Może moglibyśmy pójść na kompromis IListReader<T>;) - Używam słowa „source” jako antonimu „sink” (interfejs tylko do zapisu).
Qwertie

Może 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.
supercat

W takim przypadku kod, który chce zmienić zawartość listy, sprawdzi, czy jest to typ zmienny, a jeśli nie, wygeneruje nową mutowalną instancję zawierającą te same elementy co niezmienna lista, a następnie zacznie jej używać. Byłoby uciążliwe, gdyby kod musiał stale typować pole za każdym razem, gdy chciał użyć na nim metody mutacji.
supercat

@supercat To tylko irytujące, ponieważ C # nie zapewnia naprawdę łatwego sposobu sprawdzenia, czy interfejs jest zaimplementowany i natychmiastowego użycia go. MS powinno dodać funkcję językową, aby było to łatwiejsze. Moja preferowana technika byłaby wiążącym wyrażeniem 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.
Qwertie

2

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.


1

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 } 

2
To nawet nie ma sensu. typeof(Color)! = typeof(SpecialColors).
Kirk Woll

10
To dość proste:enum SpecialColors { blue = Colors.blue, red = Colors.red, yellow = Colors.Yellow }
Trystan Spangler

0

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:

  • var można używać tylko wtedy, gdy zmienna lokalna jest zadeklarowana i zainicjowana w tej samej instrukcji; zmienna nie może zostać zainicjowana do wartości null ani do grupy metod lub funkcji anonimowej.
  • var nie może być używany w polach w zakresie klasy.
  • Zmiennych zadeklarowanych przy użyciu var nie można używać w wyrażeniu inicjującym. Innymi słowy, to wyrażenie jest poprawne: int i = (i = 20); ale to wyrażenie powoduje błąd w czasie kompilacji: var i = (i = 20);
  • W tej samej instrukcji nie można zainicjować wielu zmiennych wpisanych niejawnie.
  • Jeśli typ o nazwie var znajduje się w zakresie, słowo kluczowe var będzie rozpoznawać tę nazwę typu i nie będzie traktowane jako część niejawnie wpisanej deklaracji zmiennej lokalnej.

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)


Zdecydowanie anon typy (nowe {...}), nie wpisane niejawnie (zmienna)
Marc Gravell

Po prostu przeczytaj ponownie to, co opublikowałem i było źle. Miałem na myśli niejawnie wpisane zmienne, chociaż
lomaxx,

1
Eric Lippert wyjaśnił, dlaczego var nie może być używany poza metodami, po prostu dlatego, że tworzy czarną skrzynkę możliwości. blogs.msdn.com/ericlippert/archive/2009/01/26/…
Guvante

1
var nie ma być wariantem! to jest właśnie dla stenografii (zwłaszcza anonowych typów). ciesz się dynamiką, gdy się pojawi ...
ShuggyCoUk,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.