Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

?id=03dbbec0-55ea-40db-9832-8516e37944a4

¿Qué diferencia existe entre const y readonly en el lenguaje C#? - Más de la que crees...

Una pregunta muy típica que se suele hacer la gente que empieza con .NET es la que da título a este artículo. Y es que, si una variable solamente se puede leer y por lo tanto no se puede cambiar... en realidad eso es la definición de una constante ¿no?

Sí y no. En realidad hay algunas sutiles diferencias que voy a explicar y que son básicas, pero también existe una diferencia enorme, mucho menos evidente, que mostraré al final y que ni siquiera muchos programadores experimentados conocen.

Vale, vamos a empezar por lo básico.

Constantes vs variables de solo lectura

Una constante se define en C# de la siguiente manera:

public const double PI = 3.14;

El modificador const le indica al compilador que esta variable va a ser constante, y solo la podemos inicializar al mismo tiempo que la declaramos. Si en cualquier otra parte del código la intentamos modificar, el compilador se quejará diciendo que el lado izquierdo de una asignación debe ser una variable, una propiedad o un indexador:

Error al intentar asignar una constanrte

Una variable de solo lectura, se parece mucho a una constante y se declara de manera parecida:

public readonly double PI = 3.14;

y si luego la intentamos modificarla en casi cualquier otra parte del código, veremos el siguiente error:

Error al intentar asignar una variable de solo lectura

Y es que, siendo de solo lectura, no se puede asignar tampoco. Sin embargo, hay algo en el error del compilador que nos da una pista de una de las diferencias con una constante, y la he destacado en el mensaje de error: excepto en un constructor.

Y es que los miembros de una clase que sean readonly pueden ser modificados mientras no termine de ser ejecutado el constructor de la clase. Es decir, dentro del constructor podemos modificarlos a voluntad, por ejemplo, obteniendo su valor a partir de los parámetros que se le pasen al constructor, algo que no es posible en el caso de una constante.

Esto nos da una importante ventaja a la hora de crear estructuras de datos inmutables, que son aquellas que una vez establecidas podemos tener la seguridad de que no van a cambiar. Estas estructuras tienen muchas aplicaciones en programación y son especialmente útiles al ser inherentemente seguras para uso multi-hilo, ya que al no poder cambiar sus datos no hay peligros de interbloqueos.

En el caso de que nuestra variable de solo lectura apunte a un tipo por valor, sabemos que el valor no va cambiar, y si apunta a un tipo por referencia (otra clase), aunque la clase y sus datos cambien podemos tener la seguridad de que nuestra variable apuntará siempre a dicha clase.

Miembros estáticos

Otra importante diferencia entre una constante y una variable de solo lectura es que las constantes son siempre miembros estáticos de la clase en la que se declaran.

Es decir, si escribimos:

public class Clase01
{
public const double PI = 3.14;
}

luego podemos acceder a ese valor PI sin necesidad de crear un objeto de la clase con new, así:

Console.Write("El valor de Pi en nuestro programa es " + Clase01.PI);

ya que es un miembro estático, común a todos los objetos de la clase y existente en la propia definición de ésta, por eso no necesitamos instanciar nada.

Para conseguir lo mismo con una variable de solo lectura tendríamos que añadir explícitamente el modificador static, así:

public class Clase01
{
public static readonly double PI = 3.14;
}

y ya podríamos usarla de la misma manera.

La diferencia menos evidente es a la hora de compilar

Vale, hasta aquí lo básico todo claro: las constantes son estáticas y solo se pueden asignar al declararlas, y las de solo lectura no son estáticas por defecto y se pueden asignar también en el constructor de las clases. Perfecto, ya sabemos lo mínimo que hay que saber.

Ahora vamos a hilar un poco más fino.

Supón que tienes una clase súper-básica definida de esta manera en una biblioteca de código (que genera una DLL al final):

public class Clase01 {     public static readonly double roPI = 3.14;     public const double cPI = 3.14; }

Tan solo define una clase con dos miembros estáticos: una variable de solo lectura y una constante para el número Pi.

Evidentemente en la realidad sería algo mucho más complejo con funcionalidad que quieres exponer para que luego tú o tus compañeros la utilicen como apoyo en sus desarrollos. Puede que incluso la distribuyas como un paquete NuGet y que llegue a mucha gente. Lo importante es ver el funcionamiento de las constantes y las variables de solo lectura, así que lo hacemos así de sencillo para eso.

Ahora generas un archivo .dll que distribuyes para que pueda ser utilizado para dotar de funcionalidad a otras aplicaciones.

A continuación creas un nuevo proyecto y le añades una referencia a esta DLL. Agregas el espacio de nombres y empiezas a utilizarla. Para seguir con código muy sencillo que nos permita ver entender a dónde quiero llegar, haces esto en un programa de consola:

Console.WriteLine("La constante PI vale:" + Clase01.cPI + " mientras que la readonly vale: " + Clase01.roPI);

O sea, simplemente usas la constante y la variable de solo lectura para mostrar por pantalla sus valores. En una aplicación real operarías con ellos, pero para lo que persigo me vale así.

Al ejecutarla se verá esto:

Ambos valores muestran 3.14

Bien, con tu nueva aplicación de consola lista, la compilas y la distribuyes junto con la DLL a tus "clientes" para que la usen. Al cabo de unos días decides que deberías darle más decimales a Pi, así que cambias sus definiciones y le asignas el valor 3.14156, con más decimales. Cambias el código de la DLL, la recompilas y la distribuyes.

En condiciones normales, con tan solo copiar la DLL por encima de la vieja, te funcionará todo perfectamente, ya que el código que nos interesa está en dicha DLL y el ejecutable tan solo hace uso de ella para esa funcionalidad. Sin embargo, si la ejecutas de nuevo, verás esto por pantalla:

Se ve 3.14 y 3.14156, dos valores diferentes

WTF? ¿Cómo es posible?: sólo ha cambiado el segundo valor, el de la variable de solo lectura, pero no la constante.

El motivo lo encontramos si examinamos con un decompilador el código generado para el ejecutable es:

Código generado por el compilador en el que se ve que las constantes se sustituyen por sus literales

Este es el código inverso obtenido a partir del código intermedio del ensamblado final que se distribuye. Lo pongo en C# para que sea más fácil de ver y que se observe lo que hace el compilador, pero podrías verlo directamente como código IL y comprobar allí el mismo efecto.

Lo que vemos es que, aunque son un miembro en una clase que pertenece a una DLL externa, ¡las constantes se compilan como literales!.

Es decir, el compilador, cuando analiza el código ve que se está utilizando una constante, y como esta no puede cambiar, directamente lo que hace es sustituirla en todas partes por su valor literal. Así que, las constantes, ¡incluso cuando residen en una DLL externa se compilan con su valor en todos los sitios en los que se utilicen! 

Sin embargo, en el caso de la variable de solo lectura, como puede depender de cuándo se instancie la clase, no puede hacerlo, y se sigue utilizando desde la DLL.

Por eso, cuando copiamos una nueva versión de la DLL por encima de la anterior, aunque cambiemos alguna constante no nos valdrá solo con eso. Si queremos que "pillen" los nuevos valores de las constantes debemos recompilar de nuevo las aplicaciones que hagan uso de la biblioteca.

Y este es quizá el efecto más importante entre usar una constante y una variable de solo lectura. Y también el menos evidente y más complicado de ver, que nos puede traer por la calle de la amargura si no lo tenemos claro.

¡Espero que te resulte interesante! (y útil)!

José Manuel Alarcón Fundador de campusMVP, es ingeniero industrial y especialista en consultoría de empresa. Ha escrito diversos libros, habiendo publicado hasta la fecha cientos de artículos sobre informática e ingeniería en publicaciones especializadas. Microsoft lo ha reconocido como MVP (Most Valuable Professional) en desarrollo web desde el año 2004 hasta la actualidad. Puedes seguirlo en Twitter en @jm_alarcon o leer sus blog técnico o personal. Ver todos los posts de José Manuel Alarcón
Archivado en: Lenguajes y plataformas

¿Te ha gustado este post?
Pues espera a ver nuestro boletín mensual...

Suscríbete a la newsletter

La mejor formación online para desarrolladores como tú

Comentarios (2) -

¡Y luego decís que C++ es más complejo que C#!

Me llama la atención, y no poco, que el compilador "coja" el PI const de la DLL como "número mágico" viniendo de un ensamblado diferente.

Por lo tanto, la constante no es más que un reemplazo de un #define de C++, con control de tipos (cosa que es cojonuda), y no realmente una "variable". Ojo entonces con declarar mega estructuras como const.

Por cierto, en C++ ahora tenemos "constexpr", que es equivalente al const de C#.

Responder

Buen artículo.

Responder

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.