tipos tipo objetos funciones float ejemplos datos dato clases c# syntax block

objetos - tipos de funciones en c#



¿Cuándo usas el alcance sin una declaración en C#? (5)

Recientemente descubrí que puedes hacer esto en C #:

{ // google string url = "#"; if ( value > 5 ) url = "http://google.com"; menu.Add( new MenuItem(url) ); } { // cheese string url = "#"; // url has to be redefined again, // so it can''t accidently leak into the new menu item if ( value > 45 ) url = "http://cheese.com"; menu.Add( new MenuItem(url) ); }

en lugar de es decir:

string url = "#"; // google if ( value > 5 ) url = "http://google.com"; menu.Add( new MenuItem(url) ); // cheese url = "#"; // now I need to remember to reset the url if ( value > 45 ) url = "http://cheese.com"; menu.Add( new MenuItem(url) );

Este podría ser un mal ejemplo que puede resolverse de muchas otras maneras.

¿Hay algún patrón en el que la característica ''alcance sin declaración'' sea una buena práctica?


Ese patrón tiene poco o ningún efecto en el tiempo de ejecución de C #, por lo que es puramente una cosa estética (compárese con C ++, donde usamos este patrón regularmente con RAII para detectar cosas como bloqueos).

Si tengo dos bloques de código completamente no relacionados, a veces los ubicaré de esta manera, para dejar en un 100% las variables que un programador debe mantener en su cabeza como "potencialmente modificado en el bloque anterior. Llena una brecha entre Bloque de código grande y funciones aisladas; puedo compartir algunas variables, no otras.

También usaré esto alrededor del código generado automáticamente. A menudo, puede ser MUCHO más fácil trabajar con tales bloques conectables sin preocuparse por las interacciones.

Cuando uso esto, me gusta estilísticamente poner un comentario antes de cada bloque, aproximadamente donde iría una declaración if , explicando lo que hará el bloque. Creo que es útil para evitar que otros desarrolladores piensen que "parece que solía haber un flujo de control, pero alguien lo molestó". En este caso, podría ser un poco excesivo, pero obtendrás la idea:

// Add a menu item for Google, if value is high enough. { string url = "#"; if ( value > 5 ) url = "http://google.com"; menu.Add( new MenuItem(url) ); } // Add a menu item for Cheese, if the value is high enough { // cheese string url = "#"; if ( value > 45 ) url = "http://cheese.com"; menu.Add( new MenuItem(url) ); }

Como se dijo antes, esto es puramente estilístico en C #. Siéntase libre de usar su propio estilo donde tenga sentido.


Podría usar {} para reutilizar nombres de variables (incluso para diferentes tipos):

{ var foo = new SomeClass(); } { Bar foo = new Bar(); // impairs readability }

Sin embargo, la reutilización de las variables de esta manera confundirá la legibilidad.

Por lo tanto, en lugar de usar un bloque de alcance "no solicitado" sin una declaración anterior, en la mayoría de los casos, el código debe ser refactorizado en funciones separadas en consecuencia.

Editar

En mi opinión, siempre que sea necesario restablecer de forma imperativa los valores de las variables mutables locales o reutilizarlos para preocupaciones adicionales o alternativas es un signo de olor. Por ejemplo, el código original puede ser refactorizado como:

menu.Add( value > 5 ? new MenuItem("http://google.com") : new MenuItem("#")); menu.Add( value > 45 ? new MenuItem("http://cheese.com") : new MenuItem("#"));

Lo cual creo que transmite la intención, sin el riesgo de que no se aplique el # reserva, y sin la necesidad de variables mutables locales explícitas para mantener el estado.

(o el new MenuItem(value > 45 ? "http://cheese.com" : "#") , o cree una sobrecarga de MenuItem con un valor predeterminado de # , o mueva la creación de MenuItem a un método de fábrica, etc.)

Editar
Re: Scope no tiene efecto en la vida útil

El alcance se puede usar dentro de un método para limitar la vida útil de objetos costosos

Mi publicación inicial declaró incorrectamente que el alcance local podría usarse para influir en la vida útil de los objetos. Esto es incorrecto, tanto para las compilaciones DEBUG como para RELEASE , y sin importar si el nombre de la variable se reasigna o no, como lo demuestra el desensamblaje de IL de Jeppe, y estas pruebas de unidad aquí . Gracias a Jeppe por señalar esto. Además, Lasse señala que, incluso sin salirse explícitamente del ámbito, las variables que ya no están en uso serán elegibles para la recolección de basura en las versiones de lanzamiento.

TL; DR Aunque el uso de un alcance no solicitado puede ayudar a transmitir el uso lógico de un alcance variable a un ser humano, hacerlo no influye en si el objeto es elegible para la recolección, dentro del mismo método.

es decir, en el código a continuación, el alcance, e incluso la reutilización de la variable foo continuación no tiene ningún efecto en la vida útil.

void MyMethod() { // Gratuituous braces: { var foo = new ExpensiveClass(); } { Bar foo = new Bar(); // Don''t repurpose, it impairs readability! } Thread.Sleep(TimeSpan.FromSeconds(10)); GC.Collect(); GC.WaitForPendingFinalizers(); <-- Point "X" }

En el punto X:

  • En una compilación DEBUG , ninguna de las variables foo se habrá recopilado a pesar de los intentos de pirateo para atraer al GC para que lo haga.
  • En una versión RELEASE , ambos foos habrían sido elegibles para la recolección tan pronto como no fueran necesarios, independientemente del alcance. El momento de la recogida, por supuesto, debe dejarse a sus propios dispositivos.

Si preguntas por qué implementaron la función?

He desarrollado mi propio lenguaje, y lo que encontré fue que la sintaxis de ''alcance sin una declaración'' se introduce en la gramática muy fácilmente. Es un efecto secundario si lo hace, cuando escribe una gramática que funciona bien y es fácil de leer .

En mi idioma, tengo la misma funcionalidad y nunca la "diseñé" explícitamente, la obtuve de forma gratuita . Nunca me senté en mi escritorio y pensé "oh, ¿no sería genial tener esa ''característica''?". Diablos, al principio, ni siquiera sabía que mi gramática lo permitía.

''{}'' es una "declaración compuesta" porque simplifica la sintaxis de todos los lugares en los que querría usarla (condicionales, cuerpos de bucles, etc.) ... y porque le permite dejar de lado las llaves cuando una sola declaración está siendo controlado (''si (a <10) ++ a;'' y similares).

El hecho de que se pueda utilizar en cualquier lugar en que pueda aparecer una afirmación se deriva directamente de eso; Es inofensivo, y en ocasiones es útil como han dicho otras respuestas. "Si no está roto, no lo arregles. - Keshlam.

Entonces, la pregunta no es tanto acerca de "por qué lo implementaron", sino más bien, "¿por qué no lo prohibieron / por qué permitieron esto?"

¿Podría prohibir esta funcionalidad específica en mi idioma? Claro, pero no veo ninguna razón para que sea un costo adicional para la compañía.

Ahora, la historia anterior podría, o podría no ser cierta para C #, pero no diseñé el lenguaje (ni soy realmente un diseñador de idiomas), por lo que es difícil decir exactamente por qué, pero pensé que lo mencionaría de todos modos.

La misma funcionalidad está en C ++, que en realidad tiene algunos casos de uso: permite que un finalizador de un objeto se ejecute de manera determinista, si el objeto se sale del ámbito, aunque este no es el caso de C #.

Dicho esto, no he usado esa sintaxis específica en mis 4 años de C # ( incrustación de declaración -> bloque , cuando se habla de términos concretos), ni he visto que se use en ninguna parte. Tu ejemplo está pidiendo ser refactorizado a los métodos.

Mire la gramática de C #: http://msdn.microsoft.com/en-us/library/aa664812%28v=vs.71%29.aspx

Además, como Jeppe ha dicho, he usado la sintaxis ''{}'' para asegurarme de que cada bloque de caso en la construcción de ''switch'' tenga un alcance local separado:

int a = 10; switch(a) { case 1: { int b = 10; break; } case 2: { int b = 10; break; } }


Un uso que considero aceptable en muchos casos, es incluir cada sección de switch de una declaración de switch en un ámbito local.

Adición tardía:

Los bloques de alcance local { ... } presentes en la fuente de C # no parecen ser relevantes para el bytecode IL resultante. Probé este simple ejemplo:

static void A() { { var o = new object(); Console.WriteLine(o); } var p = new object(); Console.WriteLine(p); } static void B() { var o = new object(); Console.WriteLine(o); var p = new object(); Console.WriteLine(p); } static void C() { { var o = new object(); Console.WriteLine(o); } { var o = new object(); Console.WriteLine(o); } }

Esto fue compilado en modo Release (optimizaciones habilitadas). La IL resultante de acuerdo con IL DASM es:

.method private hidebysig static void A() cil managed { // Code size 25 (0x19) .maxstack 1 .locals init ([0] object o, [1] object p) IL_0000: newobj instance void [mscorlib]System.Object::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: call void [mscorlib]System.Console::WriteLine(object) IL_000c: newobj instance void [mscorlib]System.Object::.ctor() IL_0011: stloc.1 IL_0012: ldloc.1 IL_0013: call void [mscorlib]System.Console::WriteLine(object) IL_0018: ret } // end of method LocalScopeExamples::A

.method private hidebysig static void B() cil managed { // Code size 25 (0x19) .maxstack 1 .locals init ([0] object o, [1] object p) IL_0000: newobj instance void [mscorlib]System.Object::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: call void [mscorlib]System.Console::WriteLine(object) IL_000c: newobj instance void [mscorlib]System.Object::.ctor() IL_0011: stloc.1 IL_0012: ldloc.1 IL_0013: call void [mscorlib]System.Console::WriteLine(object) IL_0018: ret } // end of method LocalScopeExamples::B

.method private hidebysig static void C() cil managed { // Code size 25 (0x19) .maxstack 1 .locals init ([0] object o, [1] object V_1) IL_0000: newobj instance void [mscorlib]System.Object::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: call void [mscorlib]System.Console::WriteLine(object) IL_000c: newobj instance void [mscorlib]System.Object::.ctor() IL_0011: stloc.1 IL_0012: ldloc.1 IL_0013: call void [mscorlib]System.Console::WriteLine(object) IL_0018: ret } // end of method LocalScopeExamples::C

Conclusiones:

  • Las declaraciones de alcance local no existen en ninguna forma en el bytecode de IL.
  • El compilador de C # elegirá nuevos nombres para las variables locales que de lo contrario estarían en conflicto (la segunda de las variables o en el método C ).
  • Las personas que sienten (ver otras respuestas y comentarios a la pregunta) que el recolector de basura podría hacer su trabajo antes cuando introducen el alcance local en la fuente C #, están equivocados.

También intenté compilar esto en modo de depuración (sin optimizaciones). El inicio y el final de un ámbito local parecen aparecer solo como una instrucción nop ("Sin operación"). En algunos casos, dos variables locales con nombres idénticos de distintos ámbitos locales se asignaron a la misma variable local en la IL, como con el método C # llamado C arriba. Tal "unificación" de dos variables solo es posible si sus tipos son compatibles.


{ string f = "hello"; }

Sólo se ve raro.

Obviamente, los métodos los necesitan:

private void hmm() {}

Y cambiar declaraciones:

switch(msg) { case "hi": // do something break; // ... }

E incluso si, para, foreach, mientras que las declaraciones ...

if(a == 1) { bool varIsEqual = true; isBusy = true; // do more work }

Pero si solo tienes 1 instrucción en un bucle o if, no necesitas las llaves:

if("Samantha" != "Man") msg = "Phew!";