Dlaczego występuje zarówno zwarcie OR, jak i nieokreślona odmiana tego operatora w języku C #?

9

Okresowo zastanawiam się nad tym:

Zwarcie OR zawsze zwróci tę samą wartość, co operator OR nieobwarty?

Oczekuję, że OR zwarcia zawsze będzie oceniać szybciej. Czy więc operator OR nieobwarty obwód został uwzględniony w języku C # dla zachowania spójności?

Czego mi brakowało?

CarneyCode
źródło
9
Ta sama wartość zwracana - tak. Te same skutki uboczne - nie.
Job
2
Załóżmy, że f()powstaje wyjątek, rozważ true || f()i true | f(). Czy widzisz różnicę? Pierwsze wyrażenie oznacza true, a ocena drugiego powoduje zgłoszenie wyjątku.
szalik

Odpowiedzi:

25

Oba operandy były przeznaczone do różnych rzeczy, pochodzących z C, które nie miały typu boolowskiego. Wersja zwarciowa || działa tylko z wartościami logicznymi, natomiast wersja bez zwarcia | działa z typami całkowymi, wykonując bitowe lub. Po prostu zdarzyło się, że działało jako logiczna operacja bez zwarcia dla logicznych wartości logicznych, które są reprezentowane przez pojedynczy bit, czyli 0 lub 1.

http://en.wikibooks.org/wiki/C_Sharp_Programming/Operators#Logical

Bezpieczne
źródło
10
+1 za link. Kiedy przeczytałem to pytanie, pomyślałem: „co?” ponieważ afaik ich oficjalne nazwy są logiczne i bitowe, a nie zwarcie i niezwarcie obwodu, które są efektem ubocznym ich działania.
stijn
1
+1 Widzę „działa tylko na operandach boolowskich” - prawdopodobnie nie zauważyłem różnicy, ponieważ i tak używam tylko wyrażeń, które oceniają na logiczne
CarneyCode
2
Musiałem sprawdzić, ale prawdą jest, że |operator zastosowany do dwóch wartości boolowskich jest skompilowany w tym samym oroperatorze w CIL, jak ma to miejsce w przypadku zastosowania do dwóch wartości całkowitych - w przeciwieństwie do tego, ||który jest kompilowany do CIL przy użyciu brtruewarunkowego skok.
quentin-starin
13

|(bitowy-lub) musi być odporny na zwarcie dla typów takich jak int. Jest tak, ponieważ w prawie wszystkich przypadkach należy obliczyć obie strony |wyrażenia, aby obliczyć poprawny wynik. Na przykład, co jest wynikiem 7 | f(x)?

Jeśli f(x)nie zostanie to ocenione, nie możesz powiedzieć.

Ponadto niespójne byłoby spowodowanie zwarcia tego operatora bool, gdy nie jest on zwarty int. Nawiasem mówiąc, myślę, że nigdy nie używałem |celowo porównań logicznych, ponieważ stwierdzenie, że jest |operatorem logicznym , jest bardzo niewygodne .

|| dotyczy to jednak logicznych porównań, w których ocena zwarcia działa dobrze.

To samo dotyczy nawet C i C ++, gdzie pochodzenie tych operatorów jest.

Doktor Brown
źródło
5

Jest to poprawne, operator OR zwarciowy (||) zawsze zwraca tę samą wartość, co operator OR zwarciowy (|). (*)

Jeśli jednak pierwszy argument jest prawdziwy, operator zwarcia nie spowoduje oceny drugiego argumentu, podczas gdy operator bez zwarcia zawsze spowoduje ocenę obu argumentów. Może to mieć wpływ na wydajność, a czasem na skutki uboczne.

Istnieje więc jedno i drugie: jeśli zależy ci na wydajności, a ocena drugiego operandu nie wywołuje żadnych skutków ubocznych (lub jeśli nie przejmujesz się nimi), to za wszelką cenę użyj operatora zwarcia . Ale jeśli z jakiegoś powodu potrzebujesz efektów ubocznych drugiego operandu, powinieneś użyć operatora bez zwarcia.

Przykład, w którym należy użyć operatora przeciwzwarciowego:

if( write_customer_to_database() != SUCCESS |
    write_supplier_to_database() != SUCCESS |
    write_order_to_database() != SUCCESS )
{
    transaction_rollback();
}

(*) Z wyjątkiem niektórych naprawdę zboczonych scenariuszy, w których ocena pierwszego operandu na fałsz powoduje efekt uboczny drugiego operandu, który ma być oceniany jako prawda zamiast fałszu.

Mike Nakis
źródło
2
+1 Ale chciałbym dodać, że oprócz używania funkcji do wskazywania sukcesu lub niepowodzenia (jak w twoim przykładzie): należy unikać funkcji z efektami ubocznymi ...
Marjan Venema
1
Tak, prawdopodobnie dlatego nigdy do tej pory nie korzystałem z żadnego z niezwarciowych operatorów, a szanse są niewielkie.
Mike Nakis,
Ten przykład zależy od oceny argumentów w ścisłej kolejności od lewej do prawej. C # gwarantuje to, ale wiele podobnych języków (w tym C i C ++) nie.
Keith Thompson,
Czy ocena zwarcia nie miałaby sensu w tym konkretnym przykładzie, ponieważ wszystkie transakcje zostaną wycofane, jeśli którakolwiek z nich zawiedzie? (Robię pewne założenia dotyczące semantyki wywołań).
Keith Thompson,
@KeithThompson masz rację, ocena braku zwarcia spowoduje niepotrzebne przetwarzanie, iw tym sensie przykład jest nieco kiepski, ale nie jest tak kiepski, że uzasadnia zmianę, ponieważ prawda pozostaje taka, że ​​w przypadku oceny zwarcia, jeśli write_customer_to_database () kończy się powodzeniem, wtedy pozostałe funkcje zapisu _... nigdy nie będą wywoływane, i lepiej jest działać poprawnie w przypadku sukcesu, niż wykonywać niepotrzebne operacje w przypadku awarii.
Mike Nakis,
4

Byłyby 2 powody, aby używać wariantu innego niż zwarcie:

  1. utrzymywanie efektów ubocznych (ale jest to łatwiejsze do kodowania za pomocą zmiennej tymczasowej)

  2. udaremnić ataki czasowe , unikając rozwarcia w zwarciu (może to nawet poprawić wydajność środowiska wykonawczego, jeśli drugi operand jest zmienny, ale jest to mikrooptymalizacja, którą kompilator może i tak wykonać)

maniak zapadkowy
źródło
Jest to jedyny słuszny podany powód, nie mam pojęcia, dlaczego nie jest to akceptowana odpowiedź z najwyższymi głosami ...
Behrooz
2

jeśli klauzula po prawej stronie operatora wywołuje efekt uboczny, a programista chciał, aby przed wystąpieniem wartości zwrotnej wystąpił efekt uboczny.

Lie Ryan
źródło
5
Eek. Proszę nie kodować w ten sposób ... jeśli polegasz na skutkach ubocznych, masz dwa różne wyrażenia, przypisujesz wyniki i testujesz je później .
Konrad Rudolph,