Co to jest nadużycie generyczne?

35

Przeglądając jakiś kod, zauważyłem możliwość jego zmiany w celu użycia generycznych. Kod (zaciemniony) wygląda następująco:

public void DoAllTheThings(Type typeOfTarget, object[] possibleTargets)
{
    var someProperty = typeOfTarget.GetProperty(possibleTargets[0]);
    ...
}

Ten kod można zastąpić ogólnymi, takimi jak:

public void DoAllTheThings<T>(object[] possibleTargets[0])
{
    var someProperty = type(T).getProperty(possibleTargets[0]);
    ...
}

Badając zalety i wady tego podejścia, znalazłem termin zwany nadużyciem ogólnym . Widzieć:

Moje pytanie składa się z dwóch części:

  1. Czy są jakieś korzyści z przejścia na takie leki generyczne? (Wydajność? Czytelność?)
  2. Co to jest nadużycie generyczne? I czy używanie generycznych za każdym razem, gdy występuje parametr typu, stanowi nadużycie ?
Składnia Reguły
źródło
2
Jestem zmieszany. Wiesz wcześniej nazwę właściwości, ale nie typ obiektu, więc używasz odbicia, aby uzyskać wartość?
MetaFight,
3
@MetaFight Wiem, że to skomplikowany przykład, rzeczywisty kod byłby zbyt długi i trudny do umieszczenia tutaj. Niezależnie od tego, celem mojego pytania jest ustalenie, czy zamiana parametru Type na rodzajowy jest uważana za dobrą czy złą formę.
SyntaxRules
12
Zawsze uważałem to za typowy powód istnienia leków generycznych (pyszna gra słów nie zamierzona). Po co sam oszukiwać, kiedy można wykorzystać system typów, aby zrobić to za Ciebie?
MetaFight,
2
twój przykład jest dziwny, ale mam wrażenie, że to właśnie robi wiele bibliotek IoD
Ewan
14
Nie jest to odpowiedź, ale warto zauważyć, że drugi przykład jest mniej elastyczny niż pierwszy. Ujednolicenie funkcji eliminuje możliwość przejścia w trybie wykonawczym; Ogólne muszą znać typ w czasie kompilacji.
KChaloux,

Odpowiedzi:

87

Kiedy odpowiednio zastosowane są ogólne, usuwają kod zamiast po prostu go porządkuje. Przede wszystkim kodem, który generycy najlepiej usuwają, jest rzutowanie czcionek, odbicie i pisanie dynamiczne. Dlatego nadużycie generyczne można luźno zdefiniować jako tworzenie kodu ogólnego bez znacznego ograniczenia rzutowania, refleksji lub dynamicznego pisania w porównaniu z implementacją nie ogólną.

W odniesieniu do swojej przykład spodziewałbym odpowiednie stosowanie leków generycznych zmianę object[]Do T[]lub podobny i uniknąć Typelub typecałkowicie. Może to wymagać znacznego refaktoryzacji w innym miejscu, ale jeśli w tym przypadku właściwe jest użycie ogólnych, powinno to być prostsze, kiedy skończysz.

Karl Bielefeldt
źródło
3
To świetna uwaga. Przyznaję, że kod, który sprawił, że pomyślałem o tym, spowodował trudne do odczytania odbicie. Zobaczę, czy mogę się zmienić object[]na T[].
Składnia Reguły
3
Lubię charakterystykę usuwania kontra przestawianie.
copper.hat
13
Pominąłeś jedno kluczowe użycie leków generycznych - czyli ograniczenie duplikacji. (Chociaż możesz zmniejszyć powielanie, wprowadzając dowolny z innych grzechów, o których wspominasz). Jeśli zmniejszy to powielanie bez usuwania jakichkolwiek rzutów czcionki, odbicia lub dynamicznego pisania IMO, to jest OK.
Michael Anderson
4
Doskonała odpowiedź. Dodałbym, że w tym konkretnym przypadku OP może wymagać przełączenia na interfejs zamiast ogólnych. Wygląda na to, że odbicie służy do uzyskania dostępu do określonych właściwości obiektu; interfejs jest znacznie lepiej dostosowany, aby umożliwić kompilatorowi ustalenie, czy takie właściwości faktycznie istnieją.
jpmc26
4
@MichaelAnderson „usuń kod zamiast po prostu zmienić jego kolejność” obejmuje zmniejszenie duplikacji
Caleth
5

Użyłbym zasady non-nonsense: Generics, podobnie jak wszystkie inne konstrukcje programistyczne, istnieją, aby rozwiązać problem . Jeśli nie ma problemu do rozwiązania dla leków generycznych, użycie ich jest nadużyciem.

W konkretnym przypadku ogólnych, istnieją one głównie w celu oderwania się od konkretnych typów, umożliwiając złożenie implementacji kodu dla różnych typów w jeden ogólny szablon (lub jakkolwiek inny język to nazwie). Załóżmy, że masz kod, który używa typu Foo. Możesz zamienić ten typ na ogólny T, ale jeśli tylko potrzebujesz tego kodu do pracy Foo, po prostu nie ma innego kodu, za pomocą którego można go złożyć razem. W związku z tym nie ma problemu do rozwiązania, więc pośrednie dodanie nazwy ogólnej służyłoby po prostu zmniejszeniu czytelności.

W związku z tym sugerowałbym po prostu napisanie kodu bez użycia ogólnych, dopóki nie zobaczysz potrzeby ich wprowadzenia (ponieważ potrzebujesz drugiej instancji). Wtedy, i tylko wtedy, nadszedł czas na refaktoryzację kodu w celu użycia generycznych. Każde użycie przed tym punktem jest nadużyciem w moich oczach.


Brzmi to trochę zbyt pedantycznie, więc przypominam:
nie ma reguły bez wyjątków w programowaniu. Ta zasada obejmuje.

cmaster
źródło
Ciekawe myśli Jednak może pomóc niewielkie przeredagowanie kryteriów. Załóżmy, że OP potrzebuje nowego „komponentu”, który odwzorowuje liczbę całkowitą na ciąg znaków i odwrotnie. To oczywiste, że zaspokaja powszechną potrzebę i może być łatwo wykorzystana w przyszłości, jeśli stanie się ogólna. Ten przypadek mieści się w twojej definicji nadużyć generycznych. Jednak po zidentyfikowaniu bardzo ogólnej potrzeby, czy nie byłoby warte zainwestowania nieznacznego dodatkowego wysiłku w projekt bez tego nadużycia?
Christophe
@Christophe To rzeczywiście trudne pytanie. Tak, są sytuacje, w których rzeczywiście lepiej jest zignorować moją regułę (nie ma reguły bez wyjątku w programowaniu!). Jednak konkretny przypadek konwersji łańcucha jest w rzeczywistości raczej zaangażowany: 1. Musisz traktować osobno typy ze znakiem i bez znaku. 2. Musisz traktować konwersje zmienne oddzielnie od konwersji liczb całkowitych. 3. Możesz ponownie użyć implementacji dla największych dostępnych typów liczb całkowitych dla mniejszych typów. Możesz chcieć proaktywnie napisać ogólne opakowanie, aby wykonać casting, ale rdzeń byłby bez ogólnych.
cmaster
1
Argumentowałbym przeciwko proaktywnemu pisaniu generycznego (raczej przed ponownym użyciem niż ponownym użyciem) jako prawdopodobnego YAGNI. Kiedy potrzebujesz, refaktoryzuj.
Neil_UK
Pisząc to pytanie, przyjąłem bardziej stanowisko „napisz je jako ogólne, jeśli to możliwe, aby zaoszczędzić przyszłego ponownego użycia”. Wiedziałem, że to było trochę ekstremalne. Widzenie twojego punktu widzenia tutaj jest odświeżające. Dzięki!
SyntaxRules
0

Kod wygląda dziwnie. Ale jeśli go nazywamy, ładniej jest określić typ jako ogólny.

https://stackoverflow.com/questions/10955579/passing-just-a-type-as-a-parameter-in-c-sharp

Osobiście nie lubię tych zniekształceń, które sprawiają, że kod wywoławczy wygląda ładnie. na przykład cała „płynna” rzecz i metody rozszerzenia.

Ale musisz przyznać, że ma popularnych zwolenników. nawet Microsoft używa go na przykład w jedności

container.RegisterType<IInterface,Concrete>()
Ewan
źródło
0

Dla mnie wygląda to tak, jakbyś był na dobrej drodze, ale nie dokończył pracy.

Czy rozważałeś zmianę object[]parametru Func<T,object>[]na następny krok?

sq33G
źródło