typeparam returns remarks c# function scope nested c#-7.0

returns - summary function c#



¿Por qué una función local no siempre está oculta en C#7? (4)

Lo que estoy mostrando a continuación, es más bien una pregunta teórica. Pero me interesa cómo funciona el nuevo compilador de C # 7 y cómo resuelve las funciones locales.

En C # 7 puedo usar funciones locales. Por ejemplo (puedes probar estos ejemplos en LinqPad beta ):

Ejemplo 1: Principal anidado ()

void Main() { void Main() { Console.WriteLine("Hello!"); } Main(); }

En lugar de llamar a Main() de forma recursiva, se llama una vez a la función local Main() , por lo que la salida de esto es:

¡Hola!

El compilador acepta esto sin advertencias y errores.

Ejemplo 2: Aquí voy un nivel más profundo, como:

void Main() { void Main() { void Main() { Console.WriteLine("Hello!"); } Main(); } Main(); }

En este caso, también esperaría la misma salida, ya que la función local más interna se llama, luego un nivel más arriba, Main() es solo otra función local con un alcance local, por lo que no debería ser muy diferente del primer ejemplo.

Pero aquí, para mi sorpresa, estoy recibiendo un error:

CS0136 No se puede declarar un local o parámetro llamado ''Principal'' en este ámbito porque ese nombre se usa en un ámbito local adjunto para definir un local o parámetro

Pregunta: ¿Puede explicar por qué este error ocurre en el Ejemplo 2, pero no en el Ejemplo 1?

Pensé, cada Main() interno Main() tendría un alcance local y se oculta afuera.

Actualización: Gracias a todos los que han hecho contribuciones hasta ahora (ya sea respuestas o comentarios), vale la pena lo que escribió para comprender el comportamiento del compilador de C #.

Por lo que leí, y después de considerar las posibilidades, lo que descubrí con su ayuda es que puede ser un error del compilador o un comportamiento por diseño.

Recuerde que C # tenía algunos objetivos de diseño que lo diferencian de lenguajes como C ++.

Si le interesa lo que he hecho para investigar más a fondo: he cambiado el nombre de la función más interna a MainL como:

Ejemplo 2b:

void Main() { void Main() { void MainL() { Console.WriteLine("Hello!"); } MainL(); } Main(); }

Este ejemplo modificado compila y ejecuta con éxito.

Ahora cuando compilas esto con LinqPad y luego cambias a la pestaña IL puedes ver lo que hizo el compilador:

Creó la función MainL más MainL como g__MainL0_1 , la función Main g__Main0_0 tiene la etiqueta g__Main0_0 .

Eso significa que, si elimina la L de MainL , notará que el compilador ya le cambia el nombre de una manera única, porque entonces el código parece:

IL_0000: call UserQuery.<Main>g__Main0_0 IL_0005: ret <Main>g__Main0_0: IL_0000: call UserQuery.<Main>g__Main0_1 IL_0005: ret <Main>g__Main0_1: IL_0000: ldstr "Hello!" IL_0005: call System.Console.WriteLine IL_000A: ret

que aún se resolvería correctamente. Dado que el código no se ve así en el Ejemplo 2, ya que el compilador se detiene con un error, ahora asumo que el comportamiento es por diseño, no es probable que sea un error del compilador.

Conclusión: Algunos de ustedes escribieron que en C ++ la resolución recursiva de las funciones locales puede llevar a problemas de refactorización, y otros escribieron que este tipo de comportamiento en C # es lo que hace el compilador con las variables locales (tenga en cuenta que el mensaje de error es el mismo). Eso incluso me confirma que pensé que se hizo así por diseño y no es ningún error.


Como no permite respuestas múltiples, pensé cuál sería la forma más justa. Creé esta respuesta como wiki de la comunidad, realicé las dos respuestas a continuación y las agregué como enlace para su referencia:

  • Respuesta 1: v-andrew
  • Respuesta 2: Jon Hanna

Y creé un resumen en la pregunta que contiene toda la información que recibí de usted a partir de los comentarios y de las respuestas:

  • Resumen: en la pregunta

Dado que c # es un lenguaje estáticamente compilado, creo que todas las funciones se compilan antes de que se ejecute su alcance global y, por lo tanto, el Main más interno no se puede declarar porque un Main ya existe desde su punto de vista de cierre (un nivel hacia arriba).

Tenga en cuenta que esto no se basa en evidencia objetiva, sino en mis pensamientos iniciales sobre el asunto.

Después de algunas investigaciones ... estaba equivocado, en su mayoría. Mi intuición parece implicar el mismo comportamiento, pero no por las razones que inicialmente pensé.

@PetSerAl ya se ha explicado en forma de comentario mejor de lo que podría haberlo copiado de los manuales, por lo que cedo a esa respuesta.


Los parámetros y las variables locales del ámbito adjunto están disponibles dentro de una función local.

Pensé, cada Main interno () tendría un alcance local y se oculta afuera.

C # no sobrescribe los nombres del ámbito principal, por lo que existe una ambigüedad para el nombre Main que se define en los ámbitos actual y principal.

Entonces, en el segundo ejemplo, ambas declaraciones de void Main() están disponibles para el ámbito interno y el compilador muestra un error.

Aquí hay un ejemplo con variables y local functions que podrían ayudarlo a ver el problema en el entorno familiar. Para aclarar que solo es cuestión de alcance, modifiqué el ejemplo y agregué funciones a las variables para aclararlo:

class Test { int MainVar = 0; public void Main() { if (this.MainVar++ > 10) return; int MainVar = 10; Console.WriteLine($"Instance Main, this.MainVar=${this.MainVar}, MainVar={MainVar}"); void Main() { if (MainVar++ > 14) return; Console.WriteLine($"Local Main, this.MainVar=${this.MainVar}, MainVar={MainVar}"); // Here is a recursion you were looking for, in Example 1 this.Main(); // Let''s try some errors! int MainVar = 110; /* Error! Local MainVar is already declared in a parent scope. // Error CS0136 A local or parameter named ''MainVar'' cannot be declared in this scope // because that name is used in an enclosing local scope to define a local or parameter */ void Main() { } /* Error! The same problem with Main available on the parent scope. // Error CS0136 A local or parameter named ''Main'' cannot be declared in this scope // because that name is used in an enclosing local scope to define a local or parameter */ } Main(); // Local Main() this.Main(); // Instance Main() // You can have another instance method with a different parameters this.Main(99); // But you can''t have a local function with the same name and parameters do not matter void Main(int y) { } // Error! Error CS0128 A local variable or function named ''Main'' is already defined in this scope } void Main(int x) { Console.WriteLine($"Another Main but with a different parameter x={x}"); } }

Incluso hay los mismos errores cuando intenta sobrescribir la función local y la variable local.

Entonces, como puede ver, es una cuestión de ámbitos y no puede sobrescribir la función o variable local.

Por cierto, en un primer ejemplo puedes hacer una llamada recursiva usando this.Main(); :

void Main() { void Main() { Console.WriteLine("Hello!"); } this.Main(); // call instance method }

Nota al pie: las funciones locales no se representan como delegados, como sugieren algunos comentaristas, lo que hace que local functions sean mucho más ágiles en la memoria y la CPU.


Para ampliar un poco la respuesta de v-andrew, es análogo a tener dos variables con el mismo nombre. Tenga en cuenta que lo siguiente está permitido:

void Main() { { void Main() { Console.WriteLine("Hello!"); } Main(); } { void Main() { Console.WriteLine("GoodBye!"); } Main(); } }

Aquí tenemos dos ámbitos y podemos tener dos funciones locales del mismo nombre en el mismo método.

También para combinar la respuesta de v-andrew y su pregunta, tenga en cuenta que puede (y siempre puede) tener una variable llamada Main dentro de Main() pero no puede tener una variable y una función local del mismo nombre en el mismo ámbito ya sea.

Por otro lado, no puede sobrecargar a los locales como los miembros puede tener diferentes parámetros.

Realmente, todo está más cerca de las reglas existentes para los locales que de las reglas existentes para los métodos. De hecho, son las mismas reglas. Considera que no puedes hacer:

void Main() { { void Main() { int Main = 3; Console.WriteLine(Main); } Main(); } }

Pensé, cada Main interno () tendría un alcance local y se oculta afuera.

Es, pero el ámbito incluye el nombre de la función local. Cf que no puede redefinir el nombre de una variable de for , foreach o using dentro de su alcance.

Mientras tanto, creo que es un error de compilación.

Es una característica del compilador.

Eso significa que eliminar la L de MainL no debería ser perjudicial porque el compilador ya la renombra de una manera única, debe dar como resultado un código IL.

Eso significa que es posible introducir un error en el compilador donde funcionaría el código que tiene en su pregunta. Eso infringiría las reglas de C # para los nombres de los locales.

es confuso en C #, pero lógico en C ++

Bloquea algo que ha sido conocido como una fuente de errores por algún tiempo. Del mismo modo, en C # no se le permite usar valores enteros con if() y tiene que caer explícitamente en las instrucciones de switch . Todos estos son cambios que C # realizó en comparación con C ++ desde el principio y todos eliminan cierta conveniencia, pero todos ellos son cosas que la gente realmente encontró que causaron errores y con frecuencia están prohibidos en las convenciones de codificación.