¿Cómo es nulo + verdadero una cadena?

112

Dado trueque no es un tipo de cadena, ¿cómo es null + trueuna cadena?

string s = true;  //Cannot implicitly convert type 'bool' to 'string'   
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'

Cuál es la razón detrás de esto?

Javed Akram
fuente
27
¿Haces algo que no tiene sentido y luego no te gusta el mensaje que genera el compilador? Este código C # no es válido ... ¿qué es exactamente lo que quería que hiciera?
Hogan
19
@Hogan: El código parece lo suficientemente aleatorio y académico como para que la pregunta se formule por pura curiosidad. Solo mi suposición ...
BoltClock
8
null + true devuelve 1 en JavaScript.
Fatih Acet

Respuestas:

147

Por extraño que parezca, se trata simplemente de seguir las reglas de la especificación del lenguaje C #.

De la sección 7.3.4:

Una operación de la forma x op y, donde op es un operador binario sobrecargable, x es una expresión de tipo X e y es una expresión de tipo Y, se procesa de la siguiente manera:

  • Se determina el conjunto de operadores candidatos definidos por el usuario proporcionados por X e Y para el operador de operación op (x, y). El conjunto consiste en la unión de los operadores candidatos proporcionados por X y los operadores candidatos proporcionados por Y, cada uno determinado usando las reglas de §7.3.5. Si X e Y son del mismo tipo, o si X e Y se derivan de un tipo de base común, los operadores candidatos compartidos solo se producen en el conjunto combinado una vez.
  • Si el conjunto de operadores candidatos definidos por el usuario no está vacío, este se convierte en el conjunto de operadores candidatos para la operación. De lo contrario, las implementaciones de operaciones de operador binario predefinidas, incluidas sus formas elevadas, se convierten en el conjunto de operadores candidatos para la operación. Las implementaciones predefinidas de un operador dado se especifican en la descripción del operador (§7.8 a §7.12).
  • Las reglas de resolución de sobrecarga de §7.5.3 se aplican al conjunto de operadores candidatos para seleccionar el mejor operador con respecto a la lista de argumentos (x, y), y este operador se convierte en el resultado del proceso de resolución de sobrecarga. Si la resolución de sobrecarga no selecciona el mejor operador, se produce un error de tiempo de enlace.

Entonces, repasemos esto a su vez.

X es el tipo nulo aquí, o no es un tipo en absoluto, si quieres pensarlo de esa manera. No proporciona ningún candidato. Y es bool, que no proporciona ningún +operador definido por el usuario . Entonces, el primer paso no encuentra operadores definidos por el usuario.

El compilador luego pasa al segundo punto, mirando a través de las implementaciones de operadores binarios predefinidos y sus formas levantadas. Estos se enumeran en la sección 7.8.4 de la especificación.

Si observa esos operadores predefinidos, el único que es aplicable es string operator +(string x, object y). Entonces, el conjunto de candidatos tiene una sola entrada. Eso hace que el punto final sea muy simple ... la resolución de sobrecarga elige ese operador, dando un tipo de expresión general de string.

Un punto interesante es que esto ocurrirá incluso si hay otros operadores definidos por el usuario disponibles en tipos no mencionados. Por ejemplo:

// Foo defined Foo operator+(Foo foo, bool b)
Foo f = null;
Foo g = f + true;

Eso está bien, pero no se usa para un literal nulo, porque el compilador no sabe buscar Foo. Solo sabe considerar stringporque es un operador predefinido que se enumera explícitamente en la especificación. (De hecho, es no un operador definido por el tipo de cadena ... 1 ) Esto significa que este dejará de compilar:

// Error: Cannot implicitly convert type 'string' to 'Foo'
Foo f = null + true;

Otros tipos de segundo operando usarán algunos otros operadores, por supuesto:

var x = null + 0; // x is Nullable<int>
var y = null + 0L; // y is Nullable<long>
var z = null + DayOfWeek.Sunday; // z is Nullable<DayOfWeek>

1 Puede que se pregunte por qué no hay un operador + cadena. Es una pregunta razonable, y solo estoy adivinando la respuesta, pero considere esta expresión:

string x = a + b + c + d;

Si stringno tuviera una carcasa especial en el compilador de C #, esto terminaría con la misma eficacia:

string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;

Entonces eso creó dos cadenas intermedias innecesarias. Sin embargo, debido a que hay un soporte especial dentro del compilador, en realidad es capaz de compilar lo anterior como:

string x = string.Concat(a, b, c, d);

que puede crear una sola cadena de exactamente la longitud correcta, copiando todos los datos exactamente una vez. Agradable.

Jon Skeet
fuente
'dando un tipo de expresión general de cadena' simplemente no estoy de acuerdo. El error proviene de trueno ser convertible a string. Si la expresión fuera válida, el tipo lo sería string, pero en este caso, la falta de conversión a cadena hace que toda la expresión sea un error y, por lo tanto, no tiene tipo.
leppie
6
@leppie: la expresión es válida y su tipo es cadena. Pruebe "var x = null + true;" - compila y xes de tipo string. Tenga en cuenta que la firma utilizada aquí es string operator+(string, object): se está convirtiendo boola object(lo cual está bien), no a string.
Jon Skeet
Gracias Jon, entiende ahora. BTW sección 14.7.4 en la versión 2 de la especificación.
leppie
@leppie: ¿Está eso en la numeración ECMA? Eso siempre ha sido muy diferente :(
Jon Skeet
47
en 20 minutos. Escribió todo esto en 20 minutos. Debería escribir un libro o algo así ... oh, espera.
Epaga
44

La razón es porque una vez que introduce el +operador C #, las reglas de enlace entran en juego. Considerará el conjunto de +operadores disponibles y seleccionará la mejor sobrecarga. Uno de esos operadores es el siguiente

string operator +(string x, object y)

Esta sobrecarga es compatible con los tipos de argumentos de la expresión null + true. Por lo tanto, se selecciona como operador y se evalúa como esencialmente lo ((string)null) + trueque evalúa el valor "True".

La sección 7.7.4 de la especificación del lenguaje C # contiene los detalles sobre esta resolución.

JaredPar
fuente
1
Noto que el IL generado va directamente a llamar a Concat. Estaba pensando que vería una llamada a la función del operador + allí. ¿Eso sería simplemente optimización en línea?
quentin-starin
1
@qstarin creo que la razón es que realmente no hay un operator+para string. En cambio, solo existe en la mente del compilador y simplemente lo traduce en llamadas parastring.Concat
JaredPar
1
@qstarin @JaredPar: Me he referido un poco más a eso en mi respuesta.
Jon Skeet
11

El compilador busca un operador + () que pueda tomar un argumento nulo primero. Ninguno de los tipos de valor estándar califica, nulo no es un valor válido para ellos. La única coincidencia es System.String.operator + (), no hay ambigüedad.

El segundo argumento de ese operador también es una cadena. Eso va kapooey, no puede convertir implícitamente bool en cadena.

Hans Passant
fuente
10

Curiosamente, usando Reflector para inspeccionar lo que se genera, el siguiente código:

string b = null + true;
Console.WriteLine(b);

es transformado en esto por el compilador:

Console.WriteLine(true);

El razonamiento detrás de esta "optimización" es un poco extraño, debo decirlo, y no rima con la selección del operador que esperaría.

Además, el siguiente código:

var b = null + true; 
var sb = new StringBuilder(b);

se transforma en

string b = true; 
StringBuilder sb = new StringBuilder(b);

donde en string b = true;realidad no es aceptado por el compilador.

Peter Lillevold
fuente
8

nullse convertirá en una cadena nula, y hay un convertidor implícito de bool a cadena, por lo trueque se convertirá en cadena y luego +se aplicará el operador: es como: cadena str = "" + true.ToString ();

si lo verifica con Ildasm:

string str = null + true;

es como abajo:

.locals init ([0] string str)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  box        [mscorlib]System.Boolean
  IL_0007:  call       string [mscorlib]System.String::Concat(object)
  IL_000c:  stloc.0
Saeed Amiri
fuente
5
var b = (null + DateTime.Now); // String
var b = (null + 1);            // System.Nullable<Int32> | same with System.Single, System.Double, System.Decimal, System.TimeSpan etc
var b = (null + new Object()); // String | same with any ref type

¿¿Loco?? No, debe haber una razón detrás de esto.

Alguien llame Eric Lippert...

deciclón
fuente
5

La razón de esto es la conveniencia (la concatenación de cadenas es una tarea común).

Como dijo BoltClock, el operador '+' se define en tipos numéricos, cadenas y también se puede definir para nuestros propios tipos (sobrecarga del operador).

Si no hay un operador '+' sobrecargado en los tipos del argumento y no son tipos numéricos, el compilador usa por defecto la concatenación de cadenas.

El compilador inserta una llamada a String.Concat(...)cuando concatenas usando '+', y la implementación de Concat llama a ToString en cada objeto que se le pasa.

quentin-starin
fuente