Dlaczego warto korzystać z klas cząstkowych?

72

W moim rozumieniu partialsłowo kluczowe nie pozwala na podział klasy na kilka plików źródłowych. Czy jest jakiś inny powód do tego niż organizacja kodu? Widziałem to używane w generowanych klasach interfejsu użytkownika.

Wydaje się, że to zły powód, aby utworzyć całe słowo kluczowe. Jeśli klasa jest wystarczająco duża, aby wymagać wielu plików, prawdopodobnie robi zbyt wiele. Pomyślałem, że być może można go użyć do częściowego zdefiniowania klasy, którą inny programista może dokończyć, ale lepiej byłoby stworzyć klasę abstrakcyjną.

Michael K.
źródło
10
Nie jest to tak naprawdę „całe słowo kluczowe”. To kontekstowe słowo kluczowe . partialoznacza tylko coś, co nastąpi wcześniej class. Możesz użyć go jako nazwy identyfikatora w innych częściach kodu itp.
Dean Harding
4
Jeśli ich używasz i nie jest to generowany kod, nienawidzę cię. Nie ty osobiście, ale ogólnie. Nienawidzę polowania na kod poprzez częściowe klasy.
Steven Evers,
3
„Wyszukanie kodu” nie jest trudne, wszystkie metody klas nadal pojawiają się w menu rozwijanym VS, niezależnie od tego, na którą część części patrzysz. Inna nawigacja kodu też działa dobrze (albo „sprytna” nawigacja w stylu R #, albo stary dobry shift-ctrl-f
Steve
2
To nieporozumienie, że klasy częściowe muszą znajdować się w osobnych plikach.
Bart

Odpowiedzi:

110

Jest to bardzo przydatne w każdym scenariuszu, w którym jedna część klasy jest generowana przez jakieś niestandardowe narzędzie, ponieważ pozwala na dodanie niestandardowej logiki do generowanego kodu bez dziedziczenia wygenerowanej klasy. Btw. istnieją również częściowe metody z tego samego powodu.

Nie chodzi tylko o interfejs użytkownika, ale także o inne technologie, takie jak Linq-To-Sql lub Entity Framework.

Ladislav Mrnka
źródło
44
Pozwala także zachować kod pod kontrolą wersji, pozostawiając wygenerowany kod poza kontrolą wersji.
Frank Shearar,
3
Dobra uwaga, nigdy o tym nie myślałem. Jednak widziałem to nadużywane tylko w praktyce. Sposób (kiepskich) programistów na utrzymanie zarządzanego rozmiaru pliku.
Martin Wickman,
1
Najlepsze przykłady, których użyłem to: a) dla kontekstów danych Linq do SQL, w których chcesz rozszerzyć klasy; b) rozszerzać klasy przekazywane z serwisów internetowych. W żadnym wypadku nie jest to nadużycie, ani nie jest środkiem do utrzymania rozmiarów plików zarządzalnych ... Byłbym radykalnie niezadowolony z tego, że jest używany w ten sposób (i podejmowałbym kroki ...)
Murph
3
Klasy WinForm używają go do ukrywania wszystkich kontrolek tworzących kod projektanta (szczególnie ważne, ponieważ projektant lubi losowo zmieniać kolejność całego tworzonego kodu, co powoduje ogromne bałagany podczas różnicowania / obwiniania), a jeśli pracujesz z XML, narzędzie XSD może ponownie utwórz częściowe klasy z pliku schematu, umożliwiając oddzielenie kodu od sortowania generowanego automatycznie.
Dan Neely,
2
@MartinWickman niekoniecznie jest kiepski. To dobry punkt wyjścia do refaktoryzacji obiektów Boga w starszym kodzie. Spróbuj oczyścić krajobraz, najpierw dzieląc duże klasy na osobne pliki. (Wiem, że twój komentarz jest bardzo stary)
Konrad Morawski
26

Jak mówisz, często służy do oddzielania generowanego kodu. Często nie ma to nic wspólnego z rozmiarem klas / plików.

Zaletą oddzielania generowanego kodu jest styl. Wygenerowany kod może być dość brzydki i nieczytelny i nie spełniłby wielu standardów kodowania (i kontroli StyleCop), ale to jest OK, nikt nie musi go czytać ani utrzymywać bezpośrednio. Tak więc, jeśli „ukryjesz” go w innym pliku, możesz skoncentrować się na upewnieniu się, że reszta klasy jest zgodna ze standardem, przejdzie testy StyleCop i tak dalej.

Innym obszarem, w którym go używałem, jest klasa, w której implementuje się wiele interfejsów, całkiem fajnie może być rozdzielenie implementacji na osobne pliki, chociaż jest to raczej kwestia osobistych preferencji, nigdy nie widziałem, żeby wymagały to jakiekolwiek standardy kodowania ( lub zapobiec).

Steve
źródło
9
Nigdy nie myślałem o użyciu częściowych klas do oddzielenia implementacji interfejsu, to doskonały pomysł.
Mark Booth,
Chodzi o coś więcej niż styl. Programiści generatorów kodów musieliby przeskakiwać przez obręcze, aby umożliwić edytowanie kodu w pliku zarządzanym przez generator, a nawet wtedy byłoby to problematyczne. Zaletą nr 1 jest dla mnie miejsce na napisanie kodu, którego generator nie może / nie może dotknąć. To bardzo namacalna korzyść.
Daniel
19

Jeden z programistów, w którym pracuję, wpadł na całkiem niezłe zastosowanie kilka tygodni temu w refaktoryzacji wielkich klas Bożych, które wymknęły się spod kontroli i mają wiele publicznych metod: oddzielając logiczne funkcje każdego elementu klasę na osobne klasy cząstkowe. Można fizycznie oddzielić klasę na bardziej atomowe jednostki, które powinny być klasami, bez przerywania istniejącej funkcjonalności, pozwalając zobaczyć, co jest wspólne, a co nie. Dzięki temu jako pierwszemu etapowi możesz łatwiej rozdzielić częściowe na ich własne niezależne klasy i zaimplementować je w całej bazie kodu. Myślałem, że to fajny pomysł.

Ogólnie jednak myślę, że powinny być one używane tylko do rozszerzania klas generowanych maszynowo podczas pisania nowego kodu.


źródło
1
Widzę, jak to może być przydatne. Czy to jest uzasadnienie tego, aby być w języku, czy nie ... Nie wiem. Kod generowany maszynowo to jednak inna historia.
Michael K
2
Tak, nie jest uzasadnieniem, aby być w języku, ale jest to ciekawy sposób na ich użycie.
18

Mogę wymyślić kilka przydatnych scenariuszy, w których częściowe mają sens, większość z nich wykorzystuję w swoich projektach:

  • Aby oddzielić wygenerowany kod Tool / IDE / Designer od kodu, który utrzymujesz. Dobrym przykładem jest Form.Designer.csplik zawierający kod wygenerowany przez projektanta dla aplikacji formularzy Windows. Wiele innych formatów .NET ma również kod generowany przez narzędzie, który może zostać potencjalnie zregenerowany podczas tworzenia projektu, a zatem wszystkie niestandardowe zmiany zostaną usunięte. Rozdzielenie pomoże ci zachować kod i zmiany przed takimi automatycznymi modyfikacjami.

  • Podczas implementowania wielu interfejsów z dużą ilością kodu w implementacji. I mają tendencję do korzystania z oddzielnego pliku częściowe dla każdego takiego interfejsu, nazywając to tak: {Class}.{Interface}.cs. Łatwo jest mi zobaczyć w IDE, które interfejs {Class}implementuje i jak.

  • Gdy klasa musi zawierać jedną lub więcej klas zagnieżdżonych , zwłaszcza z wystarczającą ilością kodu, aby można je było umieścić w osobnym pliku. Trzymałbym się powyższego wzoru i używałbym {Class}.{NestedClass}.cskonwencji nazewnictwa dla każdej zagnieżdżonej klasy. Podobna praktyka jest już wspomniana w odpowiedzi Virtlink .

  • Kiedy piszę staticklasy, w których będą przechowywane metody rozszerzeń . Często zdarza się, że udostępnia się tę samą logikę metody rozszerzenia podobnym klasom lub interfejsom - na przykład Reversew kolekcjach ogólnych i nie-ogólnych. Umieściłbym wszystkie metody rozszerzenia dla pojedynczej klasy lub interfejsu w osobnej części klasy statycznej. Na przykład miałbym wszystkie metody rozszerzenia IListinterfejsu w jednym miejscu i te same metody, które są IList<T>w innym pliku. Innym podejściem byłoby umieszczenie tej samej metody (wszystkich jej przeciążeń) w tej samej częściowej, ze wszystkimi możliwymi klasami dla thisparametru - na przykład mając wszystkieReverseimplementacje w jednym pliku. Zależy to od tego, który z nich lepiej uzasadniałby separację pod względem objętości kodu lub pewnych wewnętrznych konwencji, których ty lub twoja organizacja możecie się trzymać.

  • Nie używam tego, ale widziałem ludzi z C / C ++, którzy lubią podejście, które tu opiszę: utwórz częściowe dla klasy tylko z częściowymi metodami . Przypomina to sposób C / C ++ do definiowania interfejsów i oddzielania deklaracji metod od implementacji.

  • Separacja według czysto logicznych problemów . Jeśli zdarzy ci się pracować z dużą klasą, która łączy w sobie więcej niż jeden logiczny zestaw operacji, możesz rozdzielić każdy związany z logiką kod na osobną część. Zwykle istnienie takich klas jest sprzeczne z zasadą rozdzielania obaw , ale często obserwuje się rzeczywisty przypadek, szczególnie w przypadku starszych baz kodowych. Inny użytkownik Steve Evers wspomniał o nich w swojej odpowiedzi na to pytanie , odnosząc się do nich pod nazwą bóg obiektów. Osobiście zastosowałbym podejście klas cząstkowych do podzielenia kodu przed dokonaniem refaktoryzacji tak naprawdę dużych plików, aby ułatwić mi pracę i uczynić refaktoryzację bardziej przejrzystą. Zmniejszy to również konflikty, które potencjalnie spowoduję, gdy zaangażowane będą systemy kontroli wersji, takie jak SVN.

Ivaylo Slavov
źródło
14

Nikt o tym nie wspominał: używam partialdo umieszczania zagnieżdżonych klas we własnych plikach.

Wszystkie moje pliki kodu zawierają tylko jedną klasę, strukturę, interfejs lub wyliczenie. Znacznie łatwiej jest znaleźć definicję obiektu, gdy nazwy plików pokazują nazwę rzeczy, której szukasz. A ponieważ Visual Studio próbuje dopasować foldery projektu do przestrzeni nazw, nazwy plików powinny pasować do klas.

Oznacza to również, że klasa NestedClasszagnieżdżona wewnątrz MyClassbędzie mieć swój własny plik w moich projektach: MyClass.NestedClass.cs.

partial class MyClass
{
    private class NestedClass
    {
        // ...
    }
}
Daniel AA Pelsmaeker
źródło
1
Zastanawiam się, dlaczego ktoś głosowałby za odrzuceniem tej odpowiedzi. Wspomniana praktyka nie jest ani anty-wzorem, ani nie spowodowałaby żadnych problemów, o których wiem.
Ivaylo Slavov
1
Rozwiązuje to również problem polegający na utrzymywaniu małych plików klas (pod względem linii kodu), jednocześnie umożliwiając właściwe enkapsulowanie kodu należącego do klasy wewnętrznej.
redcalx,
10

Z wyjątkiem przy użyciu kodu wygenerowanego, mam tylko widział je stosować w celu ukrycia dla obiektów boga . Próba zrozumienia nowej bazy kodu lub poruszania się po wielu plikach źródłowych, które są tym samym obiektem, jest bardzo denerwująca.

Więc kiedy pytasz Why use partial classes?, odpowiadam: chyba że używasz wygenerowanego kodu, nie rób tego.

Steven Evers
źródło
+1. Po raz pierwszy widzę, że tego typu obiekty nazywane są nazwami (boskimi przedmiotami) i jest to nawet oficjalne. Na naukę czegoś nowego nigdy nie jest za późno.
Ivaylo Slavov
6

Organizacja kodu jest jedynym powodem, ale idzie głębiej, niż się wydaje. Jeśli masz częściowe klasy, w których części są generowane, możesz łatwo:

  • Ponownie wygeneruj kod bez konieczności wykrywania ręcznych zmian w klasach, w których piszesz (aby nie zastąpić tych części).
  • Wyklucz wygenerowane klasy cząstkowe z pokrycia testowego, kontroli źródła, metryk itp.
  • Użyj wygenerowanego kodu bez zmuszania Cię do oparcia wokół niego strategii dziedziczenia (nadal możesz dziedziczyć z innych klas).
Deckard
źródło
1

Istnieje również kilka miejsc, w których generowane / dorozumiane klasy są zadeklarowane jako częściowe, więc jeśli Dev musi je rozszerzyć, mają pełny dostęp bez konieczności bałagania się o dziedziczenie i nadpisywanie w całym miejscu. Spójrz na wygenerowane klasy w obszarze temp asp.net po uruchomieniu witryny sieci Web po raz pierwszy w IIS, jeśli chcesz spróbować złapać kilka przykładów.

Rachunek
źródło
1

Mogę wymyślić dla nich brudne zastosowanie.

Załóżmy, że masz klasy, które potrzebują wspólnej funkcjonalności, ale nie chcesz wstawiać tej funkcji do łańcucha dziedziczenia nad nimi. Lub masz zestaw klas, które używają wspólnej klasy pomocniczej.

Zasadniczo chcesz zrobić wielokrotne dziedziczenie, ale C # nie lubię. Więc używasz częściowego, aby stworzyć coś, co zasadniczo jest #include w C / C ++;

Umieść całą funkcjonalność w klasie lub użyj klasy pomocnika. Następnie skopiuj pomocnika X razy i zmień jego nazwę na częściową klasę A, B, C. i częściowo ułóż na swoich klasach A, B, C.

Zastrzeżenie: Tak, to jest złe. Nigdy tego nie rób - zwłaszcza jeśli spowoduje to, że inni będą na ciebie źli.

znak
źródło
Mixiny Można to połączyć z T4, aby uniknąć ręcznego kopiowania ...
Peter Taylor
Jak wspomniano, to bardzo przypomina mixiny. Jednak zbliżasz się do koncepcji znanej jako „cechy”, a oni bezpiecznie poradzą sobie z tym, co próbujesz osiągnąć. Mam opis zależny od języka na publius-ovidius.livejournal.com/314737.html Szukałem czystej implementacji dla C #, ale jej nie widziałem.
Owidiusz,