Por que existe uma variação de curto-circuito OU ou de curto-circuito desse operador em C #?

9

Periodicamente, eu me pergunto sobre isso:

O OR de curto-circuito sempre retornaria o mesmo valor que o operador OU de curto-circuito.

Eu espero que o curto-circuito OU sempre avalie mais rapidamente. Então, o operador OR sem curtos-circuitos foi incluído na linguagem C # para consistência?

O que eu perdi?

CarneyCode
fonte
9
Mesmo valor de retorno - sim. Mesmos efeitos colaterais - não.
Job
2
Suponha que f()gera uma exceção, considere true || f()e true | f(). Você vê a diferença? A expressão anterior é avaliada como true, a avaliação dos últimos resulta em uma exceção sendo lançada.
scarfridge

Respostas:

25

Ambos os operandos foram feitos para coisas diferentes, vindos de C, que não tinham um tipo booleano. A versão de curto-circuito || só funciona com booleanos, enquanto a versão sem curto-circuito | trabalha com tipos integrais, executando um bit a bit ou. Por acaso, funcionou como uma operação lógica de curto-circuito para booleanos, representados por um único bit, sendo 0 ou 1.

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

Seguro
fonte
10
+1 para o link. Quando li essa pergunta, fiquei tipo "o quê?" uma vez que o nome oficial deles é lógico e bit a bit ou, e não um curto-circuito e um curto-circuito, que são efeitos colaterais de como eles funcionam.
25711
11
+1 Eu vejo "opera apenas em operandos booleanos" - possivelmente não percebi a diferença porque só uso expressões que são avaliadas como booleanas de qualquer maneira
CarneyCode 25/12/11
2
Eu tive que verificar, mas é verdade que o |operador, quando aplicado a dois valores booleanos, é compilado no mesmo oroperador no CIL e quando aplicado a dois valores inteiros - ao contrário do ||que é compilado no CIL usando brtruepara uma condição saltar.
Quentin-starin
13

|(o bit a bit ou) deve ser sem curto-circuito para tipos como int. Isso ocorre porque em quase todos os casos você precisa calcular os dois lados de uma |expressão para calcular o resultado correto. Por exemplo, qual é o resultado 7 | f(x)?

Se f(x)não é avaliado, você não pode dizer.

Além disso, seria inconsistente fazer com que este operador sofra um curto-circuito bool, quando não for um curto-circuito int. A propósito, acho que nunca usei |intencionalmente comparações lógicas, achando muito inconveniente falar |como um operador lógico.

|| no entanto, é para comparações lógicas, onde a avaliação de curto-circuito funciona bem.

O mesmo vale para C e C ++, onde está a origem desses operadores.

Doc Brown
fonte
5

Isso está correto, o operador OR de curto-circuito (||) sempre retornará o mesmo valor que o operador OR de não-curto-circuito (|). (*)

No entanto, se o primeiro operando for verdadeiro, o operador de curto-circuito não causará avaliação do segundo operando, enquanto o operador sem curto-circuito sempre causará avaliação de ambos os operandos. Isso pode ter um impacto no desempenho e, às vezes, em efeitos colaterais.

Portanto, existe uma utilidade para ambos: se você se importa com o desempenho e a avaliação do segundo operando não produz efeitos colaterais (ou se você não se importa com eles), então use o operador de curto-circuito . Mas se, por algum motivo, você precisar dos efeitos colaterais do segundo operando, use o operador que não seja de curto-circuito.

Um exemplo em que você deve usar o operador que não seja de curto-circuito:

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

(*) Exceto por um cenário realmente pervertido, em que a avaliação do primeiro operando como falso causa por efeito colateral o segundo operando a ser avaliado como verdadeiro em vez de falso.

Mike Nakis
fonte
2
+1 Mas eu gostaria de acrescentar que além de utilizar funções para indicar sucesso ou fracasso (como no seu exemplo): funções com efeitos colaterais geralmente deve ser evitado ...
Marjan Venema
11
Sim, é provavelmente por isso que nunca usei nenhum dos operadores que não são de curto-circuito até hoje, e as chances são pequenas de que alguma vez o usarei.
quer
Esse exemplo depende dos operandos que estão sendo avaliados em ordem estrita da esquerda para a direita. O C # garante isso, mas muitas linguagens semelhantes (incluindo C e C ++) não.
Keith Thompson
A avaliação de curto-circuito não faria sentido para esse exemplo específico, pois todas as transações serão revertidas se alguma delas falhar? (Estou fazendo algumas suposições sobre a semântica das chamadas.)
Keith Thompson
@KeithThompson você está certo, a avaliação sem curto-circuito causará processamento desnecessário e, nesse sentido, o exemplo é um pouco esfarrapado, mas não é tão esfarrapado que justifique uma mudança, porque a verdade é que, na avaliação de curtos-circuitos, se write_customer_to_database () é bem-sucedido, então o restante das funções write _... nunca será chamado, e é melhor funcionar corretamente no caso de êxito do que executar algumas operações desnecessárias no caso de falha.
quer
4

Existem dois motivos para usar a variante não-curto-circuito:

  1. mantendo efeitos colaterais (mas é mais claro codificar com uma variável temporária)

  2. frustrar ataques de tempo evitando a bifurcação no curto-circuito (isso pode até melhorar a eficiência do tempo de execução se o segundo operando for uma variável, mas isso for uma micro-otimização que o compilador possa fazer de qualquer maneira)

catraca arrepiante
fonte
Esta é a única razão direito previsto, eu não tenho idéia porque esta não é a resposta aceita com maior número de votos ...
Behrooz
2

se a cláusula direita do operador tiver efeito colateral, e o programador pretender que ele deseje que ambos os efeitos colaterais aconteçam antes de verificar seu valor de retorno.

Lie Ryan
fonte
5
Eek. Por favor, não codifique assim ... se você confiar nos efeitos colaterais, tiver duas expressões diferentes, atribuir os resultados e testá-los depois .
Konrad Rudolph