Podczas nauki języka C # okazało się, że C # obsługuje przeciążanie operatora. Mam problem z dobrym przykładem, który:
- Ma sens (np. Dodanie klasy o nazwie owca i krowa)
- Nie jest przykładem łączenia dwóch łańcuchów
Przykłady z biblioteki klas podstawowych są mile widziane.
==
robi mnożenie, ma to dla mnie sens, ale może nie mieć sensu dla innych! Czy to pytanie dotyczy zasadności języków programowania w obiektach, czy mówimy o „kodowaniu najlepszych praktyk”?Odpowiedzi:
Oczywistym przykładem odpowiedniego przeciążenia operatora są dowolne klasy, które zachowują się tak samo, jak działają liczby. Tak więc klasy BigInt (jak sugeruje Jalayn ), liczby zespolone lub klasy macierzy (jak sugeruje Superbest ) wszystkie mają te same operacje, które zwykłe liczby tak dobrze odwzorowują na operatory matematyczne, podczas gdy operacje czasowe (jak sugeruje svick ) ładnie odwzorowują podzbiór tych operacji.
Nieco bardziej abstrakcyjnie, operatory mogą być używane podczas wykonywania operacji typu set , więc
operator+
może być związkiem ,operator-
może być uzupełnieniem itp. To jednak zaczyna rozciągać paradygmat, szczególnie jeśli używasz operatora dodawania lub mnożenia dla operacji, która nie jest t przemienny , jak można się spodziewać.Sam C # ma doskonały przykład przeciążenia operatora nienumerycznego . Wykorzystuje
+=
i-=
do dodawania i odejmowania delegatów , tj. Rejestrowania i wyrejestrowywania ich. Działa to dobrze, ponieważ operatory+=
i-=
działają tak, jak byś tego oczekiwał, a to skutkuje znacznie bardziej zwięzłym kodem.Dla purysty jednym z problemów z
+
operatorem łańcucha jest to, że nie jest on przemienny."a"+"b"
to nie to samo co"b"+"a"
. Rozumiemy ten wyjątek dla ciągów, ponieważ jest tak powszechny, ale jak możemy stwierdzić, czy użycieoperator+
na innych typach będzie przemienne, czy nie? Większość ludzi zakłada, że tak jest, chyba że obiekt jest podobny do łańcucha , ale tak naprawdę nigdy nie wiadomo, co ludzie przyjmą.Podobnie jak w przypadku łańcuchów, wątki matryc są również dość dobrze znane. Oczywiste jest, że
Matrix operator* (double, Matrix)
jest to mnożenie skalarne, podczas gdy na przykładMatrix operator* (Matrix, Matrix)
byłoby mnożeniem macierzowym (tj. Macierzą mnożenia iloczynu iloczynowego).Podobnie użycie operatorów z delegatami jest tak bardzo dalekie od matematyki, że jest mało prawdopodobne, aby popełniono te błędy.
Nawiasem mówiąc, na konferencji ACCU w 2011 r. Roger Orr i Steve Love przedstawili sesję „ Niektóre przedmioty są bardziej równe niż inne” - spojrzenie na wiele znaczeń równości, wartości i tożsamości . Ich slajdy można pobrać , podobnie jak dodatek Richarda Harrisa o równości zmiennoprzecinkowej . Podsumowanie: Należy być bardzo ostrożnym z
operator==
tu być smoki!Przeciążenie operatora jest bardzo potężną techniką semantyczną, ale jest łatwe do nadmiernego użycia. Idealnie powinieneś go używać tylko w sytuacjach, gdy z kontekstu jasno wynika, jaki jest efekt przeciążenia operatora. Pod wieloma względami
a.union(b)
jest jaśniejsze niża+b
ia*b
jest o wiele bardziej niejasne niża.cartesianProduct(b)
, zwłaszcza że wynik działania kartezjańskiego byłbySetLike<Tuple<T,T>>
raczej wynikiem niżSetLike<T>
.Prawdziwe problemy z przeciążeniem operatora pojawiają się, gdy programista zakłada, że klasa będzie zachowywać się w jeden sposób, ale w rzeczywistości zachowuje się w inny sposób. Sugeruję, że tego rodzaju starcie semantyczne należy unikać.
źródło
d1 + d2
dla dowolnych dwóch delegatów tego samego typu.Dziwię się, że nikt nie wspomniał o jednym z bardziej interesujących przypadków w BCL:
DateTime
iTimeSpan
. Możesz:TimeSpan
s, aby uzyskać innyTimeSpan
TimeSpan
aby uzyskać negacjęTimeSpan
DateTime
s, aby uzyskaćTimeSpan
TimeSpan
od,DateTime
aby uzyskać innyDateTime
Inny zestaw operatorów, które mogłyby spowodować poczucie na wiele typów są
<
,>
,<=
,>=
. Na przykład w BCLVersion
implementuje je.źródło
Pierwszym przykładem, który przychodzi mi na myśl, jest implementacja BigInteger , która pozwala na pracę z dużymi liczbami całkowitymi ze znakiem . Sprawdź link MSDN, aby zobaczyć, ilu operatorów zostało przeciążonych (to znaczy, istnieje duża lista, a ja nie sprawdziłem, czy wszyscy operatorzy zostali przeciążeni, ale na pewno tak jest)
Ponadto, ponieważ robię również Javę, a Java nie pozwala na przeciążanie operatorów, pisanie jest niesamowicie słodsze
Następnie w Javie:
źródło
Cieszę się, że to widziałem, ponieważ wygłupiałem się z Irony i WIELKIE zastosowanie przeciążenia operatora. Oto próbka tego, co może zrobić.
Irony jest więc „zestawem implementacyjnym języka .NET” i generatorem analizatora składni (generującym analizator składni LALR). Zamiast nauczyć się nowej składni / języka, takiego jak generatory parsera, takie jak yacc / lex, piszesz gramatykę w języku C # z przeciążeniem operatora. Oto prosta gramatyka BNF
Jest to więc prosta gramatyka (przepraszam, jeśli występują niespójności, ponieważ uczę się BNF i gramatyki). Teraz spójrzmy na C #:
Jak widać, przy przeciążeniu operatora pisanie gramatyki w języku C # jest prawie dokładnie pisaniem gramatyki w języku BNF. Dla mnie to nie tylko ma sens, ale jest świetnym zastosowaniem przeciążenia operatora.
źródło
Kluczowym przykładem jest operator == / operator! =.
Jeśli chcesz łatwo porównać dwa obiekty według wartości danych zamiast przez odniesienie, będziesz chciał przeciążać .Equals (i.GetHashCode!), A dla zachowania spójności możesz również chcieć wykonać operatory! = I ==.
Nigdy nie widziałem żadnych dzikich przeciążeń innych operatorów w C # (wyobrażam sobie, że istnieją skrajne przypadki, w których może to być przydatne).
źródło
Ten przykład z MSDN pokazuje, jak zaimplementować liczby zespolone i pozwolić, aby używały normalnego operatora +.
Kolejny przykład pokazuje, jak to zrobić w celu dodania macierzy, a także wyjaśnia, jak nie używać go do dodawania samochodu do garażu (przeczytaj link).
źródło
Dobre wykorzystanie przeciążenia może być rzadkie, ale tak się dzieje.
przeciążanie operatora == i operatora! = pokaż dwie szkoły myślenia: te za powiedzenie, że to ułatwia, a te przeciwko temu, że uniemożliwiają porównywanie adresów (tj. czy wskazuję dokładnie to samo miejsce w pamięci, a nie tylko kopię tego samego obiekt).
Uważam, że przeciążenia operatora rzutowania są przydatne w określonych sytuacjach. Na przykład musiałem serializować / deserializować w formacie XML wartość logiczną reprezentowaną przez 0 lub 1. Właściwy (domyślny lub jawny, zapominam) operator rzutowania z wartości logicznej na int i odwrotnie.
źródło
object.ReferenceEquals()
.==
przez rzutowanie:(object)foo == (object)bar
zawsze porównuje odniesienia. Ale wolałbymReferenceEquals()
, jak wspomina @ dan04, ponieważ jest wyraźniejsze, co robi.Nie należą do kategorii rzeczy, które ludzie zwykle myślą o przeciążeniu operatora, ale myślę, że jednym z najważniejszych operatorów, którzy mogą przeciążać, jest operator konwersji .
Operatory konwersji są szczególnie przydatne w przypadku typów wartości, które mogą „oddzielić cukier” od typu liczbowego lub mogą działać jak typ liczbowy w niektórych kontekstach. Na przykład, można zdefiniować specjalny
Id
rodzaj, który reprezentuje pewien identyfikator, i można zapewnić niejawna konwersja doint
tak, że można przekazaćId
do metody, która trwaint
, ale explict konwersji odint
doId
tak nikt nie może przekazaćint
do metoda, która wymagaId
najpierw bez rzutowania.Na przykład poza C # język Python zawiera wiele specjalnych zachowań, które są implementowane jako przeciążalne operatory. Należą do nich
in
operator do testowania członkostwa,()
operator do wywoływania obiektu tak, jakby to była funkcja, orazlen
operator do określania długości lub wielkości obiektu.A potem masz języki takie jak Haskell, Scala i wiele innych języków funkcjonalnych, w których nazwy
+
są zwykłymi funkcjami, a nie operatorami w ogóle (i istnieje obsługa języków dla używania funkcji w pozycji infiksowej).źródło
Struktura punktów w przestrzeni nazw System.Drawing wykorzystuje przeciążenie, aby porównać dwie różne lokalizacje za pomocą przeciążenia operatora.
Jak widać, o wiele łatwiej jest porównać współrzędne X i Y dwóch lokalizacji za pomocą przeciążenia.
źródło
Jeśli znasz wektor matematyczny, możesz zobaczyć zastosowanie w przeciążeniu
+
operatora. Możesz dodać wektor zaa=[1,3]
pomocąb=[2,-1]
i uzyskaćc=[3,2]
.Przeciążenie równości (==) może być również przydatne (chociaż prawdopodobnie lepiej jest zaimplementować
equals()
metodę). Aby kontynuować przykłady wektorowe:źródło
Wyobraź sobie kawałek kodu do rysowania na formularzu
Innym częstym przykładem jest użycie struktury do przechowywania informacji o pozycji w postaci wektora.
do wykorzystania później jako
źródło
operator+
powinno nie być przeciążone (można wdrożyć punkt pod względem wektora, ale nie powinien być w stanie dodać dwa punkty)p1+((p2-p1)+(p3-p1)+(p4-p1))/4
, ale wydaje się to nieco niezręczne.