¿Por qué puedo declarar una variable secundaria con el mismo nombre que una variable en el ámbito primario?

23

Recientemente escribí un código en el que involuntariamente reutilicé un nombre de variable como parámetro de una acción declarada dentro de una función que ya tiene una variable del mismo nombre. Por ejemplo:

var x = 1;
Action<int> myAction = (x) => { Console.WriteLine(x); };

Cuando descubrí la duplicación, me sorprendió ver que el código se compilaba y se ejecutaba perfectamente, lo cual no es un comportamiento que esperaría según lo que sé sobre el alcance en C #. Algunos rápida búsqueda en Google se presentó lo que las preguntas que se quejan de que un código similar no producir un error, como Lambda Ámbito Aclaración . (Pegué ese código de muestra en mi IDE para ver si se ejecutaría, solo para asegurarme; funciona perfectamente.) Además, cuando entro en el cuadro de diálogo Cambiar nombre en Visual Studio, el primero xse resalta como un conflicto de nombres.

¿Por qué funciona este código? Estoy usando C # 8 con Visual Studio 2019.

stellr42
fuente
1
La lambda se mueve a un método en una clase que genera el compilador y, por lo tanto, todo el xparámetro de ese método se mueve fuera del alcance. Ver sharplab para un ejemplo.
Lasse V. Karlsen
66
Es probable que valga la pena señalar aquí que esto no se compilará cuando apunte a C # 7.3, por lo que parece ser exclusivo de C # 8.
Jonathon Chase
El código en la pregunta vinculada también se compila bien en sharplab . Esto podría ser un cambio reciente.
Lasse V. Karlsen
2
encontrado un engañado (sin respuesta): stackoverflow.com/questions/58639477/…
bolov

Respuestas:

26

¿Por qué funciona este código? Estoy usando C # 8 con Visual Studio 2019.

¡Has respondido tu propia pregunta! Es porque estás usando C # 8.

La regla de C # 1 a 7 fue: un nombre simple no puede usarse para significar dos cosas diferentes en el mismo ámbito local. (La regla real era un poco más compleja que eso, pero describía cómo es tedioso; consulte la especificación de C # para más detalles).

La intención de esta regla era evitar el tipo de situación de la que está hablando en su ejemplo, donde resulta muy fácil confundirse sobre el significado de lo local. En particular, esta regla fue diseñada para evitar confusiones como:

class C 
{
  int x;
  void M()
  {
    x = 123;
    if (whatever)
    {
      int x = 356;
      ...

Y ahora tenemos una situación en la que dentro del cuerpo de M, xsignifica ambos this.xy lo local x.

Aunque bien intencionado, hubo una serie de problemas con esta regla:

  • No se implementó según las especificaciones. Hubo situaciones en las que se podía usar un nombre simple como, por ejemplo, un tipo y una propiedad, pero no siempre se marcaron como errores porque la lógica de detección de errores era defectuosa. (Vea abajo)
  • Los mensajes de error fueron redactados de manera confusa e informados de manera inconsistente. Hubo múltiples mensajes de error diferentes para esta situación. Inconsistentemente identificaron al delincuente; es decir, a veces se llamaba el uso interno , a veces el externo , y a veces era simplemente confuso.

Hice un esfuerzo en la reescritura de Roslyn para resolver esto; Agregué algunos mensajes de error nuevos e hice que los viejos fueran consistentes con respecto a dónde se informó el error. Sin embargo, este esfuerzo fue muy poco, demasiado tarde.

El equipo de C # decidió para C # 8 que toda la regla estaba causando más confusión de la que impedía, y la regla se retiró del lenguaje. (Gracias Jonathon Chase por determinar cuándo ocurrió la jubilación).

Si está interesado en conocer la historia de este problema y cómo intenté solucionarlo, vea estos artículos que escribí al respecto:

https://ericlippert.com/2009/11/02/simple-names-are-not-so-simple/

https://ericlippert.com/2009/11/05/simple-names-are-not-so-simple-part-two/

https://ericlippert.com/2014/09/25/confusing-errors-for-a-confusing-feature-part-one/

https://ericlippert.com/2014/09/29/confusing-errors-for-a-confusing-feature-part-two/

https://ericlippert.com/2014/10/03/confusing-errors-for-a-confusing-feature-part-three/

Al final de la tercera parte, noté que también había una interacción entre esta función y la función "Color Color", es decir, la función que permite:

class C
{
  Color Color { get; set; }
  void M()
  {
    Color = Color.Red;
  }
}

Aquí hemos usado el nombre simple Colorpara referirnos a ambos this.Colory al tipo enumerado Color; de acuerdo con una lectura estricta de la especificación, esto debería ser un error, pero en este caso la especificación era incorrecta y la intención era permitirla, ya que este código no es ambiguo y sería molesto hacer que el desarrollador lo cambie.

Nunca escribí ese artículo describiendo todas las extrañas interacciones entre estas dos reglas, ¡y sería un poco inútil hacerlo ahora!

Eric Lippert
fuente
El código en la pregunta no se compila para C # 6, 7, 7.1, 7.2 y 7.3, dando "CS0136: Un local o parámetro llamado 'x' no se puede declarar en este ámbito porque ese nombre ...". Parece que la regla aún se aplica hasta C # 8.
Jonathon Chase
@JonathonChase: ¡Gracias!
Eric Lippert