Jest to dozwolone:
int a, b, c;
a = b = c = 16;
string s = null;
while ((s = "Hello") != null) ;
W moim rozumieniu przypisanie s = ”Hello”;
powinno tylko powodować “Hello”
przypisanie s
, ale operacja nie powinna zwracać żadnej wartości. Gdyby to była prawda, ((s = "Hello") != null)
spowodowałoby to błąd, ponieważ null
zostałoby porównane z niczym.
Jakie jest uzasadnienie zezwalania instrukcjom przypisania na zwracanie wartości?
while ((s = GetWord()) != null) Process(s);
nie jest).Odpowiedzi:
Twoje rozumienie jest w 100% błędne. Czy możesz wyjaśnić, dlaczego wierzysz w tę fałszywą rzecz?
Po pierwsze, instrukcje przypisania nie generują wartości. Wyrażenia przypisania tworzą wartość. Wyrażenie przypisania to oświadczenie prawne; istnieje tylko kilka wyrażeń, które są instrukcjami prawnymi w C #: awaits of a expression, konstruowanie wystąpienia, inkrementacja, dekrementacja, wywołanie i wyrażenia przypisania mogą być używane tam, gdzie oczekiwana jest instrukcja.
W języku C # jest tylko jeden rodzaj wyrażenia, który nie generuje wartości, a mianowicie wywołanie czegoś, co zostało wpisane jako zwracające void. (Lub, równoważnie, oczekiwanie na zadanie bez skojarzonej wartości wyniku). Każdy inny rodzaj wyrażenia tworzy wartość, zmienną, odwołanie, dostęp do właściwości lub dostęp do zdarzenia i tak dalej.
Zauważ, że wszystkie wyrażenia, które są legalne jako oświadczenia, są przydatne ze względu na ich skutki uboczne . To jest tutaj kluczowy wgląd i myślę, że być może przyczyna twojej intuicji, że zadania powinny być stwierdzeniami, a nie wyrażeniami. Idealnie byłoby, gdybyśmy mieli dokładnie jeden efekt uboczny na instrukcję i żadnych skutków ubocznych w wyrażeniu. Jest to nieco dziwne, że boczne dokonania kod może być użyty w kontekście wyrażenie w ogóle.
Powodem dopuszczenia tej funkcji jest to, że (1) jest ona często wygodna i (2) jest idiomatyczna w językach typu C.
Można zauważyć, że postawiono pytanie: dlaczego jest to idiomatyczne w językach C-podobnych?
Dennisa Ritchiego nie można już zadawać, niestety, ale przypuszczam, że przypisanie prawie zawsze pozostawia wartość, która została właśnie przypisana w rejestrze. C jest językiem bardzo zbliżonym do maszyny. Wydaje się prawdopodobne i zgodne z projektem C, że istnieje funkcja języka, która zasadniczo oznacza „nadal używaj wartości, którą właśnie przypisałem”. Bardzo łatwo jest napisać generator kodu dla tej funkcji; po prostu nadal używasz rejestru, który przechował przypisaną wartość.
źródło
=
/==
literówka, którą można łatwo rozwiązać, uniemożliwiając użycie wartości,=
chyba że jest ona umieszczona w nawiasach. np.if ((x = y))
lubif ((x = y) == true)
jest dozwolone, aleif (x = y)
nie jest.Nie podałeś odpowiedzi? Ma to na celu włączenie dokładnie tego rodzaju konstrukcji, o których wspomniałeś.
Typowym przypadkiem, w którym używana jest ta właściwość operatora przypisania, jest odczytywanie wierszy z pliku ...
źródło
return _myBackingField ?? (_myBackingField = CalculateBackingField());
Dużo mniej plewy niż sprawdzanie wartości null i przypisywanie.Moim ulubionym zastosowaniem wyrażeń przypisania jest leniwie inicjowane właściwości.
źródło
??=
składnię.Po pierwsze, pozwala łączyć zadania w łańcuchy, jak w przykładzie:
Po drugie, pozwala przypisać i sprawdzić wynik w jednym wyrażeniu:
Obydwa są prawdopodobnie wątpliwe, ale z pewnością są ludzie, którzy lubią te konstrukcje.
źródło
return (HttpContext.Current.Items["x"] = myvar);
Oprócz wspomnianych już powodów (łańcuch przypisania, ustawianie i testowanie w pętli while itp.), Aby poprawnie używać
using
instrukcji, potrzebujesz tej funkcji:MSDN odradza deklarowanie jednorazowego obiektu poza instrukcją using, ponieważ pozostanie on w zakresie nawet po jego usunięciu (zobacz artykuł MSDN, do którego odsyłam).
źródło
using
. Wszystkie one są legalne:using (X x = new X())
,using (x = new X())
,using (x)
. Jednak w tym przykładzie zawartość instrukcji using to specjalna składnia, która w ogóle nie polega na przypisaniu zwracającym wartość -Font font3 = new Font("Arial", 10.0f)
nie jest wyrażeniem i nie jest poprawna w żadnym miejscu, w którym oczekuje się wyrażeń.Chciałbym rozwinąć konkretną kwestię, którą Eric Lippert poruszył w swojej odpowiedzi i skierować uwagę na konkretną okazję, która w ogóle nie została poruszona przez nikogo innego. Eric powiedział:
Chciałbym powiedzieć, że przypisanie zawsze pozostawi wartość, którą próbowaliśmy przypisać do naszego lewego operandu. Nie tylko „prawie zawsze”. Ale nie wiem, ponieważ nie znalazłem tego problemu skomentowanego w dokumentacji. Teoretycznie może to być bardzo skuteczna zaimplementowana procedura „pozostawienia” i nie ponownej oceny lewego operandu, ale czy jest wydajna?
„Wydajne” tak dla wszystkich przykładów, które zostały do tej pory skonstruowane w odpowiedziach w tym wątku. Ale wydajne w przypadku właściwości i indeksatorów, które używają metod dostępu get i set? Ani trochę. Rozważ ten kod:
Tutaj mamy właściwość, która nie jest nawet opakowaniem dla zmiennej prywatnej. Za każdym razem, gdy zostanie wezwany, zwróci prawdę, gdy ktoś spróbuje ustalić swoją wartość, nie powinien nic zrobić. Zatem ilekroć ta właściwość zostanie oceniona, będzie prawdomówny. Zobaczmy co się stanie:
Zgadnij, co to drukuje? To drukuje
Unexpected!!
. Jak się okazuje, faktycznie wywoływany jest set accessor, który nic nie robi. Ale potem metoda get nigdy nie jest w ogóle wywoływana. Przypisanie to po prostu pozostawiafalse
wartość, którą próbowaliśmy przypisać naszej własności. Tafalse
wartość jest obliczana przez instrukcję if.Skończę na przykładzie z prawdziwego świata , dzięki któremu zbadałem ten problem. Zrobiłem indeksator, który był wygodnym opakowaniem dla kolekcji (
List<string>
), którą moja klasa miała jako zmienną prywatną.Parametr wysyłany do indeksatora był łańcuchem, który miał być traktowany jako wartość w mojej kolekcji. Metoda dostępu get zwróciłaby po prostu prawdę lub fałsz, gdyby ta wartość istniała na liście, czy nie. Zatem
List<T>.Contains
metoda get była kolejnym sposobem wykorzystania metody.Jeśli metoda akcesora set indeksatora została wywołana z łańcuchem znaków jako argumentem, a prawy operand
true
byłby bool , dodałby ten parametr do listy. Ale jeśli ten sam parametrfalse
zostałby wysłany do metody dostępu, a właściwy operand byłby wartością logiczną , zamiast tego usunąłby element z listy. W ten sposób zestaw akcesorów został użyty jako wygodna alternatywa dla obuList<T>.Add
iList<T>.Remove
.Wydawało mi się, że mam zgrabne i kompaktowe „API” opakowujące listę moją własną logiką zaimplementowaną jako brama. Z pomocą samego indeksatora mogłem zrobić wiele rzeczy za pomocą kilku zestawów naciśnięć klawiszy. Na przykład, jak mogę spróbować dodać wartość do mojej listy i sprawdzić, czy się tam znajduje? Myślałem, że to jedyna niezbędna linia kodu:
Ale jak pokazał mój wcześniejszy przykład, metoda get, która powinna sprawdzić, czy wartość rzeczywiście znajduje się na liście, nie została nawet wywołana.
true
Wartość zawsze pozostawione skutecznie niszczyć cokolwiek logika miałem realizowany w moim akcesor get.źródło
Gdyby przypisanie nie zwróciło wartości, linia
a = b = c = 16
również nie zadziała.while ((s = readLine()) != null)
Czasami przydatna może być również umiejętność pisania takich rzeczy .Więc powodem, dla którego przypisanie zwraca przypisaną wartość, jest umożliwienie ci zrobienia tych rzeczy.
źródło
=
wystąpieniu w wyrażeniu, które nie jest ujęte w nawiasach (nie licząc parens samej instrukcji if / while). gcc daje takie ostrzeżenia i tym samym zasadniczo wyeliminował tę klasę błędów z programów C / C ++, które są z nim kompilowane. Szkoda, że inni twórcy kompilatorów poświęcili tak mało uwagi temu i kilku innym dobrym pomysłom w gcc.Myślę, że nie rozumiesz, jak parser będzie interpretował tę składnię. Przypisanie zostanie ocenione jako pierwsze , a wynik zostanie następnie porównany z wartością NULL, tj. Instrukcja jest równoważna z:
Jak wskazywali inni, wynikiem przypisania jest przypisana wartość. Trudno mi sobie wyobrazić korzyści z posiadania
((s = "Hello") != null)
i
nie są równoważne ...
źródło
Myślę, że głównym powodem jest (celowe) podobieństwo z C ++ i C.Sprawdzenie, że operator przypisania (i wiele innych konstrukcji językowych) zachowuje się tak, jak ich odpowiedniki w C ++, po prostu kieruje się zasadą najmniejszego zaskoczenia, a każdy programista pochodzący z innego curly- język nawiasów może ich używać bez poświęcania dużo uwagi. Łatwość przyswojenia przez programistów C ++ była jednym z głównych celów projektowych dla C #.
źródło
Z dwóch powodów, które umieścisz w swoim poście
1), więc możesz zrobić
a = b = c = 16
2), abyś mógł sprawdzić, czy zadanie się powiodło
if ((s = openSomeHandle()) != null)
źródło
Fakt, że „a ++” lub „printf („ foo ”)” może być użyteczny jako samodzielna instrukcja lub jako część większego wyrażenia oznacza, że C musi uwzględniać możliwość, że wynik wyrażenia może, ale nie musi, być używany. Biorąc to pod uwagę, istnieje ogólne przekonanie, że wyrażenia, które mogą pożytecznie „zwracać” wartość, równie dobrze mogą to robić. Tworzenie łańcuchów przypisań może być nieco „interesujące” w C, a nawet bardziej interesujące w C ++, jeśli wszystkie dane zmienne nie mają dokładnie tego samego typu. Takich zwyczajów prawdopodobnie najlepiej unikać.
źródło
Kolejny świetny przykład użycia, używam tego cały czas:
źródło
Dodatkową zaletą, której nie widzę w odpowiedziach tutaj, jest to, że składnia przypisania jest oparta na arytmetyce.
Teraz
x = y = b = c = 2 + 3
oznacza coś innego w arytmetyce niż język w stylu C; w arytmetycznej jego twierdzenia, możemy stwierdzić, że x jest równa y itd. oraz w języku C-stylu jest to instrukcja, która sprawia, że x równe y itd. Po to jest wykonywany.To powiedziawszy, wciąż istnieje wystarczająca relacja między arytmetyką a kodem, że nie ma sensu blokować tego, co jest naturalne w arytmetyce, chyba że istnieje dobry powód. (Inną rzeczą, którą języki w stylu C przejęły z użycia symbolu równości, jest użycie == do porównania równości. Tutaj jednak, ponieważ prawy most == zwraca wartość, tego rodzaju tworzenie łańcucha byłoby niemożliwe.)
źródło
a = b = c
oznacza, żea
ab
ic
to samo. Ucząc się języka C stylu dowiadujemy się, że poa = b = c
,a
ib
to samo jakc
. Z pewnością istnieje różnica w semantyce, jak sama moja odpowiedź mówi, ale mimo to, gdy jako dziecko po raz pierwszy nauczyłem się programować w języku, który używał=
do przydziału, ale nie pozwalałem naa = b = c
to, wydawało mi się to nierozsądne…=
do porównywania równości, więca = b = c
musiałoby to oznaczać, coa = b == c
oznacza w językach w stylu C. Okazało się, że tworzenie łańcuchów dozwolone w C jest o wiele bardziej intuicyjne, ponieważ mogłem narysować analogię do arytmetyki.Lubię używać wartości zwracanej przypisania, gdy muszę zaktualizować kilka rzeczy i zwrócić, czy były jakieś zmiany, czy nie:
Uważaj jednak. Możesz pomyśleć, że możesz to skrócić do tego:
Ale to faktycznie przestanie oceniać instrukcje lub po znalezieniu pierwszej prawdziwej. W tym przypadku oznacza to, że przestaje przypisywać kolejne wartości, gdy przypisze pierwszą inną wartość.
Zobacz https://dotnetfiddle.net/e05Rh8, aby się z tym pobawić
źródło