C#: Los peligros de las variables estáticas en librerías

Estamos acostumbrados a usar variables estáticas para valores “constantes”, que no cambiarán de valor independientemente del estado de la aplicación. El ejemplo más típico es el valor de PI. Lo cierto es que suena tan sencillo que rara vez nos paramos a pensar si estamos usando las variables estáticas donde debemos o no. Sin embargo, esto puede traer bastantes quebraderos de cabeza si estamos creando código que será usado por otros…

Supongamos que hemos desarrollado una librería que se encarga de gestionar conexiones para algún servicio (IncredibleService) que tiene un número máximo de llamadas a la API por hora, 150. Dado que ese límite es constante y no cambiará independientemente del estado de la aplicación, podríamos escribir algo así:

Esa librería podría ser usada por un programa como este:

using System;

public class IncredibleService
{
        public const int MaxApiCalls = 150;
        // Here we'd have the rest of the implementation
}

Esa librería podría ser usada por un programa como este:

using System;

public class Program
{
        public static void Main()
        {
                Console.WriteLine("Maximum number of calls to the API: " + IncredibleService.MaxApiCalls);
        }
}

Si compilamos la librería, compilamos el programa y lo ejecutamos, nos mostraría por pantalla el texto “Maximum number of calls to the API: 150”, que es precisamente lo que debería pasar, así que hasta aquí, todo bien.

Pablos-MacBook-Pro:csharp carballude$ dmcs -target:library IncredibleService.cs 
Pablos-MacBook-Pro:csharp carballude$ dmcs -reference:IncredibleService.dll Program.cs 
Pablos-MacBook-Pro:csharp carballude$ mono Program.exe 
Maximum number of calls to the API: 150

Supongamos que tres meses más tarde, el numero de usuarios del servicio crece y la empresa decide realizar una inversión y mejorar la experiencia de los usuarios permitiendo el doble de consultas a la API por hora, es decir, 300. Nosotros, como desarrolladores de la librería IncredibleService deberíamos cambiar el valor 150 por 300, compilar la librería y distribuirla a nuestros clientes:

using System;

public class IncredibleService
{
        public const int MaxApiCalls = 300;
        // Here we'd have the rest of the implementation
}

La mayoría de nosotros esperaríamos que una vez que Program.exe haya recibido la nueva dll, mostraría por pantalla el valor 300… sin embargo:

Pablos-MacBook-Pro:csharp carballude$ dmcs -target:library IncredibleService.cs 
Pablos-MacBook-Pro:csharp carballude$ mono Program.exe 
Maximum number of calls to the API: 150

¿Cómo es posible que IncredibleService.dll tenga el valor 300 para MaxApiCalls, Program.exe llame a IncredibleService.MaxApiCalls y este siga mostrando el valor 150? Yo también me quedé con un palmo la primera vez que lo vi xD Examinemos lo que ha generado el compilador:

Pablos-MacBook-Pro:csharp carballude$ mono disassembler.exe --disassemble-all --methods-matching Main Program.exe 

DotNet disassembler v1.0 - Pablo Carballude

Program

	Public Void Main()

		-- Local variables
		-- End of local variables

		IL_0: ldstr
		IL_1: break
		IL_2: nop
		IL_3: nop
		IL_4: cpobj
		IL_5: ldc.i4
		IL_6: ldelem.i8
		IL_7: nop
		IL_8: nop
		IL_9: nop
		IL_10: box
		IL_11: ldarg.0
		IL_12: nop
		IL_13: nop
		IL_14: break
		IL_15: call System.String::Concat
		IL_20: call System.Console::WriteLine
		IL_25: ret

Si observamos el código, veremos que el compilador ha generado “ldc.i4” en el IL_5, es decir, está guardando el valor directamente en nuestro código y no mirando la librería. ¿Por qué? Fácil, el compilador se ha dado cuenta de que estamos llamando a IncredibleService para leer una constante, dado que esa constante no va a cambiar de valor (porque para eso es constante) en vez de cargar la dll en memoria y hacer la llamada, ahorra memoria y tiempo incrustando el valor en nuestra código directamente. El problema es que si actualizamos el valor de la constante en la dll, el programa no recibirá el nuevo valor hasta que la recompilemos contra la nueva versión ¡porque la librería ni siquiera se está cargando en memoria!

Arreglando el entuerto

Cuando tengamos que fijar un valor que va a ser constante respecto del estado de la aplicación, pero susceptible de ser modificado (como el caso del ejemplo) es aconsejable hacer uso de variables “readonly”. El código quedaría así:

using System;

public class IncredibleService
{
        public static readonly int MaxApiCalls = 150;
        // Here we'd have the rest of the implementation
}

Ahora la variable no es “const” sino “static readonly”, pero esto no produce ningún cambio sintáctico en los clientes, así que podemos compilar Program sin hacerle ningún cambio:

Pablos-MacBook-Pro:csharp carballude$ dmcs -target:library IncredibleService.cs 
Pablos-MacBook-Pro:csharp carballude$ dmcs -reference:IncredibleService.dll Program.cs 
Pablos-MacBook-Pro:csharp carballude$ mono Program.exe 
Maximum number of calls to the API: 150

Si ahora actualizamos el código de IncredibleService.dll para que tenga el valor 300 y compilamos, Program sí mostrará el resultado esperado:

Pablos-MacBook-Pro:csharp carballude$ dmcs -target:library IncredibleService.cs 
Pablos-MacBook-Pro:csharp carballude$ mono Program.exe 
Maximum number of calls to the API: 300

Podemos comprobar además que ahora el código generado por el compilador sí llama a nuestra dll:

Pablos-MacBook-Pro:csharp carballude$ mono disassembler.exe --disassemble-all --methods-matching Main Program.exe 

DotNet disassembler v1.0 - Pablo Carballude

Program

	Public Void Main()

		-- Local variables
		-- End of local variables

		IL_0: ldstr
		IL_1: break
		IL_2: nop
		IL_3: nop
		IL_4: cpobj
		IL_5: ldsfld
		IL_6: ldarg.0
		IL_7: nop
		IL_8: nop
		IL_9: stloc.0
		IL_10: box
		IL_11: ldarg.1
		IL_12: nop
		IL_13: nop
		IL_14: break
		IL_15: call System.String::Concat
		IL_20: call System.Console::WriteLine
		IL_25: ret

Ahora el compilador sí ha generado código para mirar el campo estático «ldsfld» y no está incrustándolo en nuestra aplicación :)

Por Carballude

Me llamo Pablo Carballude González, soy graduado en computación con master en HCI y Seguridad Informática. Actualmente trabajo para Amazon en Seattle como Software Developer Engineer. Soy de esas personas que no saben si los textos autobiográficos deben ser en primera o tercera persona. Lo intenté en segunda, pero no le entendí nada :P

12 comentarios

  1. Pingback: Bitacoras.com
  2. No conocía el modificador «readonly» :-)
    Ya que estás, me podrías decir cual es la diferencia entre una variable read-only y una constante? Me cederás que a nivel conceptual, una variable que solo puede leerse es… una constante :P

  3. Amigo

    Tengo un servicio de tipo Windows Aplication
    quisiera saber como puedo hacer para obtener el numero de usuarios que
    estan conectados en un determinado tiempo.

    Por favor ayudame. Gracias

  4. Much of state Security comes from the lack need. Health of the populace cbeoinmd with opportunity and hope create an easily governable nation. If It is more fruitful to work within the system to build a future then laws will be followed.“Poverty in all its forms is the greatest single threat to peace, security, democracy, human rights and the environment.”-Mike Moore, former Director-General World Trade Organisation (WTO) We therefore have to…fight poverty not only for moral and humanitarian reasons, but also as an integral part of the fight against terrorism.”-Jan Kavan, former President of the General Assembly of the United Nations

  5. Kedves Hajnalka!Igen, folyamatos bővül a kutatásokat megrendelő cégek száma is. Egyre többféle termékre lehet beváltani a vásárlási utalványokat is. ÉS rohamosan növekszik az EU-ban élő felhasználók száma is. Valóban különleges és sokrétű lehetőség a ProDm Szövetség által kínált szortiment. Sok sikert kívánunk önnek is!ProDm Admin Hungary

  6. Completely agree. It seems they are addicted to dramatising politics. Making soap opera. As soon as the PM gets a boost in the polls, Marius Benson will whip out a story about Kevin Rudd coming back to invent some leadership challenge. Malcolm Turnbull’s ridiculous lies creating FUD about the NBN are never reported in the MSM, but get a big run on the 5th estate. A news limited journalist on “Insiders” practically admitted that the Margie Abbott coverage was a coordinated campaign with News. If the MSM journalists can’t present information fairly, then what are they for?

  7. zegt:ze zijn leuk, maarre even van een babyleek, hoelang kan je zwanger zijn dan? dacht dat het met 9.1/2 maand toch wel klaar zou zijn! heb echt geen idee,hoe dat werkt, wanneer gaan ze het anders halen dan?Hoop voor je dat je morgen een verse mama mag zijn:-)groetjes

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *