No hay duda de que conoces estos operadores: && y ||, sabes lo que hacen e inclusive los has usado más veces de las que lo recuerdas en tus aplicaciones. Pero, probablemente no sabes que cuando las usas, el lenguaje hace de las suyas tratando de mejorar el desempeño de tu aplicación. Considera el siguiente par de ejemplos:

if(true || false) { } 
if(false && true) { } 

¿Qué sentido tendría evaluar el lado derecho de ambos condicionales si ya sabemos que nada de lo que está ahí modificará el resultado? entonces en tiempo de ejecución sucede algo como lo siguiente:

El programa reconoce que nada de lo que ocurra después del operador cambiará el resultado, entonces evita evaluarlo, ahorrando así un poco de tiempo y recursos. Este comportamiento se debe a los operadores lógicos && y ||, que en realidad su nombre real es: operadores lógicos corto-circuitados (short-circuiting logical operators).

Esto sin duda es algo de lo que muy pocos tendrían queja, pero para aquellos casos en los que quisiéramos que los operadores lógicos evalúen todos los operandos, C# nos tiene una solución: los operadores lógicos no corto-circuitados: & y |, que en pocas palabras evalúan todos los operandos, sin importar que estos no vayan a modificar el resultado de la operación.

Para poner un ejemplo que puedes descargar, considera el siguiente par de métodos que imprimen un mensaje y retornan un valor constante booleano:

static bool AlwaysTrue(string label)
{   
    Console.Write(label + ": " + true);
    return true;
}

static bool AlwaysFalse(string label)
{
    Console.Write(label + ": " + false);
    return false;
}

Entonces, al ejecutar el siguiente código:

Console.WriteLine("Ifs");
if (AlwaysFalse("If 1") && AlwaysTrue("If 1")) ;
Console.WriteLine();
if (AlwaysFalse("If 2") & AlwaysTrue("If 2")) ;
Console.WriteLine();
if (AlwaysTrue("If 3") || AlwaysTrue("If 3")) ;
Console.WriteLine();
if (AlwaysTrue("If 4") | AlwaysTrue("If 4")) ;
Console.WriteLine();
if (AlwaysTrue("If 5") | AlwaysTrue("If 5") && (AlwaysFalse("If 5") & AlwaysTrue("If 5")));

Obtendremos como resultado:

Ifs
If 1: False

If 2: False
If 2: True

If 3: True

If 4: True
If 4: True

If 5: True
If 5: True
If 5: False
If 5: True

¿Por qué?

La razón de la existencia de ambos operadores radica en que los no corto-circuitados tienen un propósito más allá de operaciones de lógica condicional, son operadores de bit a bit, que están pensados para funcionar con valores enteros, en estas operaciones siempre se deben tomar en cuenta ambos operandos. El hecho de que funcionen también con valores booleanos solamente es un extra.