Jestem doświadczonym programistą C ++, znam język bardzo szczegółowo i intensywnie korzystałem z niektórych jego specyficznych funkcji. Znam też zasady OOD i wzorce projektowe. Uczę się teraz C #, ale nie mogę przestać czuć, że nie mogę pozbyć się sposobu myślenia w C ++. Tak mocno związałem się z mocnymi stronami C ++, że nie mogę żyć bez niektórych funkcji. I nie mogę znaleźć żadnych dobrych obejść lub zamienników dla nich w języku C #.
Jakie dobre praktyki , wzorce projektowe , idiomy , które różnią się w C # od perspektywy C ++, możesz zasugerować? Jak uzyskać idealny projekt w C ++, który nie wygląda głupio w C #?
W szczególności nie mogę znaleźć dobrego sposobu radzenia sobie w języku C # (ostatnie przykłady):
- Kontrolowanie czasu życia zasobów, które wymagają deterministycznego czyszczenia (np. Plików). Łatwo jest to mieć
using
pod ręką, ale jak właściwie z niego korzystać, gdy własność zasobu jest przenoszona [... między wątkami]? W C ++ po prostu używałbym wspólnych wskaźników i pozwalał zająć się „odśmiecaniem” w odpowiednim czasie. - Ciągłe zmaganie się z nadpisywaniem funkcji dla określonych generycznych (uwielbiam takie rzeczy, jak częściowa specjalizacja szablonów w C ++). Czy powinienem po prostu zrezygnować z jakichkolwiek prób programowania ogólnego w języku C #? Może generyczne są celowo ograniczone i używanie ich nie jest C # poza konkretną domeną problemów?
- Funkcjonalność przypominająca makro. Chociaż ogólnie jest to zły pomysł, w przypadku niektórych domen problemów nie ma innego obejścia (np. Warunkowa ocena instrukcji, podobnie jak w przypadku dzienników, które powinny przejść tylko do wersji debugowania). Brak ich oznacza, że muszę umieścić więcej
if (condition) {...}
płyt kotłowych i nadal nie jest to równe pod względem wywoływania efektów ubocznych.
C#
do wygenerowania innego kodu. Możesz odczytać plikCSV
lubXML
plik, który zapisałeś jako dane wejściowe i wygenerować plikC#
lubSQL
plik. Może to być bardziej wydajne niż używanie makr funkcjonalnych.Odpowiedzi:
Z mojego doświadczenia wynika, że nie ma na to książki. Fora pomagają w posługiwaniu się idiomami i najlepszymi praktykami, ale w końcu będziesz musiał poświęcić czas. Ćwicz, rozwiązując wiele różnych problemów w języku C #. Spójrz na to, co było trudne / niezręczne i spróbuj sprawić, by następnym razem były mniej trudne i mniej niezręczne. Dla mnie ten proces trwał około miesiąca, zanim go „dostałem”.
Najważniejszą rzeczą, na której należy się skupić, jest brak zarządzania pamięcią. W C ++ włada każdą decyzją dotyczącą projektu, ale w C # przynosi efekty odwrotne do zamierzonych. W języku C # często chcesz zignorować śmierć lub inne zmiany stanu swoich obiektów. Ten rodzaj powiązania dodaje niepotrzebne połączenie.
Przeważnie skup się na jak najskuteczniejszym korzystaniu z nowych narzędzi, a nie na walce o przywiązanie do starych.
Uświadamiając sobie, że różne idiomy dążą do różnych podejść projektowych. Nie możesz po prostu przenieść czegoś 1: 1 między (większością) języków i oczekiwać czegoś pięknego i idiomatycznego na drugim końcu.
Ale w odpowiedzi na konkretne scenariusze:
Współużytkowane niezarządzane zasoby - To był zły pomysł w C ++, a tak samo zły pomysł w C #. Jeśli masz plik, otwórz go, użyj go i zamknij. Udostępnianie go między wątkami wymaga tylko kłopotów, a jeśli tylko jeden wątek naprawdę go używa, niech ten wątek otworzy / będzie miał / zamknie go. Jeśli z jakiegoś powodu naprawdę masz plik o długim czasie życia, stwórz klasę, która będzie go reprezentować i pozwól aplikacji zarządzać nim w razie potrzeby (zamknij przed wyjściem, powiązaj zdarzenia związane z zamykaniem aplikacji itp.)
Częściowa specjalizacja szablonów - Nie masz tu szczęścia, chociaż im więcej użyłem C #, tym bardziej znajduję użycie szablonu, które zrobiłem w C ++, by wpaść w YAGNI. Po co tworzyć coś ogólnego, gdy T jest zawsze int? Inne scenariusze najlepiej rozwiązywać w języku C # przez swobodne stosowanie interfejsów. Nie potrzebujesz ogólnych, gdy typ interfejsu lub delegata może zdecydowanie wpisać to, co chcesz zmienić.
Warunki wstępne preprocesora - C # ma
#if
, zwariuj. Co więcej, ma atrybuty warunkowe , które po prostu usuwają tę funkcję z kodu, jeśli nie jest zdefiniowany symbol kompilatora.źródło
O ile widzę, jedyną rzeczą, która pozwala na deterministyczne zarządzanie czasem życia, jest
IDisposable
awaria (jak w: brak obsługi języka lub systemu typów), kiedy, jak zauważyłeś, musisz przenieść własność takiego zasobu.Będąc w tej samej łodzi co Ty - programista C ++ robi bankomat C #. - brak wsparcia dla modelu przenoszenia własności (plików, połączeń db ...) w typach / podpisach C # był dla mnie bardzo denerwujący, ale muszę przyznać, że „OK” działa całkiem dobrze w sprawie konwencji i dokumentów API, aby to zrobić poprawnie.
IDisposable
do wykonywania deterministycznego czyszczenia, jeśli jest to konieczne.IDisposable
, po prostu upewnij się, że interfejs API jest jasny i nie używajusing
w tym przypadku, ponieważusing
nie można tak powiedzieć „anulować”.Tak, nie jest tak elegancki, jak to, co można wyrazić w systemie czcionek za pomocą nowoczesnego C ++ (przenosić sematykę itp.), Ale wykonuje to zadanie i (o dziwo) społeczność C # jako całość nie wydaje się nadmierna opieka, AFAICT.
źródło
Wspomniałeś o dwóch konkretnych funkcjach C ++. Omówię RAII:
Zwykle nie ma zastosowania, ponieważ C # jest śmieciami zbierane. Najbliższą konstrukcją C # jest prawdopodobnie
IDisposable
interfejs, używany w następujący sposób:Nie należy spodziewać się
IDisposable
częstego wdrażania (i jest to nieco zaawansowane), ponieważ nie jest to konieczne w przypadku zasobów zarządzanych. Jednak powinieneś użyćusing
konstruktora (lub zadzwonićDispose
, jeśliusing
jest to niemożliwe) do wszystkiego, co implementujeIDisposable
. Nawet jeśli to zepsujesz, dobrze zaprojektowane klasy C # spróbują sobie z tym poradzić (używając finalizatorów). Jednak w języku zbierającym śmieci wzorzec RAII nie działa w pełni (ponieważ gromadzenie nie jest deterministyczne), więc czyszczenie zasobów będzie opóźnione (czasem katastroficzne).źródło
Disposing()
plik i prawdopodobnie powinien go użyćusing
.To bardzo dobre pytanie, ponieważ widziałem wielu programistów C ++ piszących okropny kod C #.
Najlepiej nie myśleć o C # jako „C ++ z ładniejszą składnią”. Są to różne języki, które wymagają odmiennego podejścia do myślenia. C ++ zmusza cię do ciągłego myślenia o tym, co zrobi procesor i pamięć. C # nie jest taki. C # jest specjalnie zaprojektowany, abyś nie myślał o procesorze i pamięci, a zamiast tego myślisz o domenie biznesowej, dla której piszesz .
Jednym z przykładów tego, co widziałem, jest to, że wielu programistów C ++ lubi używać pętli, ponieważ są szybsze niż foreach. Jest to zwykle zły pomysł w języku C #, ponieważ ogranicza możliwe typy iteracji kolekcji (a tym samym możliwość ponownego użycia i elastyczność kodu).
Myślę, że najlepszym sposobem na dostosowanie od C ++ do C # jest próba podejścia do kodowania z innej perspektywy. Na początku będzie to trudne, ponieważ z biegiem lat będziesz przyzwyczajony do używania w mózgu wątku „co robi procesor i pamięć” do filtrowania pisanego kodu. Ale w języku C # powinieneś zamiast tego myśleć o relacjach między obiektami w domenie biznesowej. „Co chcę robić”, a nie „co robi komputer”.
Jeśli chcesz coś zrobić na liście obiektów, zamiast pisać na liście pętlę for, utwórz metodę, która pobiera pętlę
IEnumerable<MyObject>
i używaforeach
.Twoje konkretne przykłady:
Nigdy nie powinieneś tego robić w języku C # (szczególnie między wątkami). Jeśli chcesz coś zrobić z plikiem, zrób to w jednym miejscu na raz. Jest ok (i rzeczywiście dobra praktyka), aby napisać klasę opakowania, która zarządza niezarządzanym zasobem, który jest przekazywany między twoimi klasami, ale nie próbuj przekazywać pliku między wątkami i mieć osobne klasy, które zapisują / czytają / zamykają / otwierają. Nie dziel się własnością niezarządzanych zasobów. Użyj
Dispose
wzoru, aby poradzić sobie z porządkiem.Ogólne w C # są zaprojektowane, aby być ogólne. Specjalizacje generyczne powinny być obsługiwane przez klasy pochodne. Dlaczego warto
List<T>
zachowywać się inaczej, jeśli jest toList<int>
aList<string>
? Wszystkie operacje naList<T>
są ogólne, aby można je było zastosować do dowolnejList<T>
. Jeśli chcesz zmienić zachowanie.Add
metody naList<string>
, a następnie utwórz klasę pochodnąMySpecializedStringCollection : List<string>
lub klasę złożoną,MySpecializedStringCollection : IList<string>
która korzysta z tego, co ogólne, ale robi różne rzeczy. Pomoże to uniknąć naruszenia zasady substytucji Liskowa i po prostu rujnować innych, którzy korzystają z twojej klasy.Jak powiedzieli inni, możesz to zrobić za pomocą poleceń preprocesora. Atrybuty są jeszcze lepsze. Ogólnie atrybuty są najlepszym sposobem obsługi rzeczy, które nie są funkcjami „rdzenia” dla klasy.
Krótko mówiąc, pisząc C #, pamiętaj, że powinieneś myśleć wyłącznie o domenie biznesowej, a nie o procesorze i pamięci. W razie potrzeby możesz zoptymalizować później , ale kod powinien odzwierciedlać relacje między zasadami biznesowymi, które próbujesz zmapować.
źródło
IDisposable
( nie surowego zasobu) z jednej klasy do drugiej: wystarczy zobaczyć API StreamWriter, gdzie ma to sens dlaDispose()
przekazywanego strumienia. I jest dziura w języku C # - nie można tego wyrazić w systemie pisma.Najbardziej odmienna była dla mnie tendencja do wszystkiego nowego . Jeśli chcesz obiekt, zapomnij o próbie przekazania go do rutyny i współdzielenia podstawowych danych, wygląda na to, że wzorzec C # służy tylko do stworzenia nowego obiektu. Zasadniczo wydaje się, że jest to znacznie więcej w języku C #. Na początku ten wzorzec wydawał się bardzo nieefektywny, ale myślę, że tworzenie większości obiektów w C # to szybka operacja.
Jednak są też klasy statyczne, które naprawdę mnie denerwują, ponieważ od czasu do czasu próbujesz stworzyć jedną tylko po to, by odkryć, że musisz jej po prostu użyć. Nie jest tak łatwo wiedzieć, którego użyć.
Jestem całkiem szczęśliwy w programie C #, że po prostu go ignoruję, gdy już go nie potrzebuję, ale pamiętaj tylko, co zawiera ten obiekt - generalnie musisz tylko pomyśleć, jeśli ma jakieś „niezwykłe” dane, obiekty składające się tylko z pamięci po prostu odejdźcie sami, inni będą musieli się pozbyć lub będziesz musiał spojrzeć na to, jak je zniszczyć - ręcznie lub za pomocą bloku użycia, jeśli nie.
bardzo szybko go podniesiesz, C # prawie nie różni się od VB, więc możesz traktować to jako to samo narzędzie RAD (jeśli pomaga myśleć o C # jak VB.NET, zrób to, składnia jest tylko nieznacznie inne, a moje dawne czasy używania VB doprowadziły mnie do właściwego sposobu myślenia o rozwoju RAD). Jedynym trudnym bitem jest określenie, którego z wielkich bibliotek .net należy użyć. Google jest tutaj zdecydowanie Twoim przyjacielem!
źródło