visual valid remarks net name generate example comment code c# post-increment pre-increment

c# - valid - Incremento posterior dentro de una autoasignación



summary c# documentation (6)

... ya que esto es asignar la variable a sí mismo y luego incrementar el valor ...

No, no es lo que hace.

El operador postincidente incrementa la variable y devuelve el valor anterior . El operador de preincremento incrementa la variable y devuelve el nuevo valor .

Entonces su c++ incrementa c a 43, pero devuelve 42, que luego se asigna a c nuevamente.

Entiendo las diferencias entre i++ and ++i , pero no estoy muy seguro de por qué obtengo los resultados a continuación:

static void Main(string[] args) { int c = 42; c = c++; Console.WriteLine(c); //Output: 42 }

En el código anterior, como esto es asignar la variable a sí mismo y luego incrementar el valor, esperaría que el resultado fuera 43 . Sin embargo, está regresando 42 . Obtengo el mismo resultado cuando uso c = c--; también.

Me doy cuenta de que simplemente podría usar c++; y termine con esto, pero tengo más curiosidad de por qué se comporta de la manera que es. ¿Alguien puede explicar lo que está pasando aquí?


Creo que veo lo que el entrevistador original estaba pensando. Pensaban (creo) que postincremento significa incrementar la variable después de la evaluación de la expresión completa, por ejemplo,

x = a[i++] + a[j++]; // (0)

es lo mismo que

{ x = a[i] + a[j] ; i += 1 ; j += 1; } // (1)

(y ciertamente estos son equivalentes) y que

c = c++; // (2)

medio

{ c = c ; c +=1 ; } // (3)

y eso

x = a[i++] + a[i++]; // (4)

medio

{ x = a[i] + a[i] ; i += 2 ; } // (5)

Pero este no es el caso. v++ significa incrementar v inmediato, pero usa el valor anterior como el valor de la expresión. Entonces en el caso de (4) una declaración realmente equivalente es

{int t0 = a[i] ; i += 1 ; int t1 = a[i] ; i += 1 ; x = t0 + t1 ; } // (6)

Como otros han notado, las afirmaciones como (2) y (4) están bien definidas en C # (y Java), pero no están bien definidas en C y C ++.

En las expresiones C y C ++ como (2) y (4) que cambian una variable y también la usan de alguna otra manera, generalmente no están definidas, lo que significa que el compilador es bienvenido (en lo que respecta a las leyes de lenguaje) para traducirlas de alguna manera a todo, por ejemplo, para transferir dinero de su cuenta bancaria al escritor del compilador.


De acuerdo con la página MSDN en operadores C #, el operador de asignación ( = ) tiene una precedencia menor que cualquier operador primario, como ++x o x++ . Eso significa que en la línea

c = c++;

el lado derecho se evalúa primero. La expresión c++ incrementa c a 43, luego devuelve el valor original 42 como resultado, que se usa para la asignación.

Como la documentación que vinculó a los estados,

[La segunda forma es una] operación de incremento de postfijo. El resultado de la operación es el valor del operando antes de que se haya incrementado.

En otras palabras, tu código es equivalente a

// Evaluate the right hand side: int incrementResult = c; // Store the original value, int incrementResult = 42 c = c + 1; // Increment c, i.e. c = 43 // Perform the assignment: c = incrementResult; // Assign c to the "result of the operation", i.e. c = 42

Compare esto con el formulario de prefijo

c = ++c;

que evaluaría como

// Evaluate the right hand side: c = c + 1; // Increment c, i.e. c = 43 int incrementResult = c; // Store the new value, i.e. int incrementResult = 43 // Perform the assignment: c = incrementResult; // Assign c to the "result of the operation", i.e. c = 43


Echemos un vistazo al código de idioma intermediario para eso:

IL_0000: nop IL_0001: ldc.i4.s 2A IL_0003: stloc.0 // c IL_0004: ldloc.0 // c

Esto carga el entero constante 42 en la pila, luego lo almacena en la variable c , y lo carga inmediatamente otra vez en la pila.

IL_0005: stloc.1 IL_0006: ldloc.1

Esto copia el valor en otro registro y también lo carga de nuevo.

IL_0007: ldc.i4.1 IL_0008: add

Esto agrega la constante 1 al valor cargado

IL_0009: stloc.0 // c

... y almacena el resultado (43) en la variable c .

IL_000A: ldloc.1 IL_000B: stloc.0 // c

Luego se carga el valor del otro registro (¡aún 42!) Y se almacena en la variable c .

IL_000C: ldloc.0 // c IL_000D: call System.Console.WriteLine IL_0012: nop IL_0013: ret

Luego, el valor (42) se carga desde la variable y se imprime.

Entonces, lo que se puede ver a partir de esto es que mientras c++ incrementa la variable en uno después de que se devolvió el resultado, ese incremento ocurre aún antes de asignar el valor a la variable. Entonces la secuencia es más como esto:

  1. Obtener valor de c
  2. Incremento posterior c
  3. Asignar valor leído previamente a c

Y eso debería explicar por qué obtienes ese resultado :)

Para agregar un ejemplo más, ya que esto se mencionó en un comentario que se eliminó desde entonces:

c = c++ + c;

Esto funciona de manera muy similar: asumiendo un valor inicial de 2 nuevamente, el lado izquierdo de la suma se evalúa primero. Entonces, el valor se lee de la variable (2), luego c se incrementa ( c convierte en 3). Luego se evalúa el lado derecho de la adición. El valor de c es leído (ahora 3). Luego, la adición tiene lugar (2 + 3) y el resultado (5) se asigna a la variable.

De esto se desprende que debe evitar mezclar operaciones de incremento y decremento en expresiones normales. Si bien el comportamiento está muy bien definido y tiene mucho sentido (como se muestra arriba), a veces es difícil abarcarlo. Especialmente cuando asigna algo a la misma variable que incrementa en la expresión, esto se vuelve confuso rápidamente. Así que hágalo a usted mismo y a los demás y evite las operaciones de incremento / disminución cuando no están completamente solos :)


La expresión en el lado derecho de una tarea se evalúa por completo, luego se realiza la asignación.

c = c++;

Es lo mismo que

// Right hand side is calculated first. _tmp = c; c = c + 1; // Then the assignment is performed c = _tmp;


Los documentos dicen para el estado de postfix:

El resultado de la operación es el valor del operando antes de que se haya incrementado.

Esto significa que cuando lo haga:

c = c++;

En realidad, está reasignando 42 a c , y es por eso que está viendo la impresión de la consola 42 . Pero si lo haces:

static void Main(string[] args) { int c = 42; c++; Console.WriteLine(c); }

Lo verá salida 43 .

Si observa lo que el compilador genera (en modo Debug), verá:

private static void Main(string[] args) { int num = 42; int num2 = num; num = num2 + 1; num = num2; Console.WriteLine(num); }

Lo cual muestra la sobrescritura más claramente. Si miras el modo Release, verás que el compilador optimiza la llamada completa a:

private static void Main(string[] args) { Console.WriteLine(42); }