¿Por qué C # limita el conjunto de tipos que pueden declararse como const?

El error del comstackdor CS0283 indica que solo los tipos básicos de POD (así como las cadenas, las enumeraciones y las referencias nulas) se pueden declarar como const . ¿Alguien tiene una teoría sobre el fundamento de esta limitación? Por ejemplo, sería bueno poder declarar valores const de otros tipos, como IntPtr.

Creo que el concepto de const es en realidad azúcar sintáctico en C #, y que simplemente reemplaza cualquier uso del nombre con el valor literal. Por ejemplo, dada la siguiente statement, cualquier referencia a Foo sería reemplazada por “foo” en tiempo de comstackción.

 const string Foo = "foo"; 

Esto descartaría cualquier tipo mutable, ¿entonces tal vez eligieron esta limitación en lugar de tener que determinar en tiempo de comstackción si un tipo dado es mutable?

De la especificación C #, capítulo 10.4 – Constantes :
(10.4 en la especificación C # 3.0, 10.3 en la versión en línea para 2.0)

Una constante es un miembro de clase que representa un valor constante: un valor que puede calcularse en tiempo de comstackción.

Esto básicamente dice que solo puede usar expresiones que constan únicamente de literales. Cualquier llamada a cualquier método, constructores (que no pueden ser representados como literales puros de IL) no pueden ser utilizados, ya que no hay forma de que el comstackdor realice esa ejecución y, por lo tanto, calcule los resultados, en tiempo de comstackción. Además, dado que no hay forma de etiquetar un método como invariante (es decir, hay una correspondencia de uno a uno entre la entrada y la salida), la única forma de que el comstackdor haga esto sería analizar el IL para ver si depende de cosas que no sean los parámetros de entrada, casos especiales manejan algunos tipos (como IntPtr), o simplemente no permiten cada llamada a cualquier código.

IntPtr, por ejemplo, aunque es un tipo de valor, sigue siendo una estructura y no uno de los literales incorporados. Como tal, cualquier expresión que use un IntPtr necesitará llamar al código en la estructura IntPtr, y esto es lo que no es legal para una statement constante.

El único ejemplo legal de tipo de valor constante que se me ocurre sería uno que se inicializa con ceros con solo declararlo, y eso no es muy útil.

En cuanto a cómo el comstackdor trata / usa las constantes, utilizará el valor calculado en lugar del nombre constante en el código.

Por lo tanto, tiene el siguiente efecto:

  • No se comstack ninguna referencia al nombre de la constante original, la clase en la que se declaró o el espacio de nombres en el código de esta ubicación.
  • Si descomstacks el código, tendrá números mágicos, simplemente porque la “referencia” original a la constante no está presente, como se mencionó anteriormente, solo el valor de la constante
  • El comstackdor puede usar esto para optimizar, o incluso eliminar, código innecesario. Por ejemplo, if (SomeClass.Version == 1) , cuando SomeClass.Version tiene el valor de 1, de hecho eliminará el enunciado if y mantendrá el bloque de código en ejecución. Si el valor de la constante no es 1, entonces toda la instrucción if y su bloque serán eliminados.
  • Dado que el valor de una constante se comstack en el código, y no una referencia a la constante, el uso de constantes de otros ensambles no actualizará automágicamente el código comstackdo de ninguna manera si el valor de la constante cambia (¡lo cual no debería ser así!)

En otras palabras, con la siguiente situación:

  1. El ensamblaje A contiene una constante llamada “Versión”, que tiene un valor de 1
  2. El ensamblado B contiene una expresión que analiza el número de versión del ensamblaje A de esa constante y lo compara con 1, para asegurarse de que pueda funcionar con el ensamblaje
  3. Alguien modifica el ensamblaje A, incrementa el valor de la constante a 2 y reconstruye A (pero no B)

En este caso, el conjunto B, en su forma comstackda, aún comparará el valor de 1 a 1, porque cuando se compiló B, la constante tenía el valor 1.

De hecho, si ese es el único uso de algo del ensamblaje A en el ensamblaje B, el ensamblado B se comstackrá sin una dependencia en el ensamblaje A. La ejecución del código que contiene esa expresión en el ensamblaje B no cargará el ensamblaje A.

Las constantes solo deberían usarse para cosas que nunca cambiarán. Si se trata de un valor que podría o va a cambiar en algún momento en el futuro, y no puede garantizar que todos los demás conjuntos se reconstruyan simultáneamente, un campo de solo lectura es más apropiado que una constante.

Entonces esto está bien:

  • Const pública Int32 NumberOfDaysInAWeekInGregorianCalendar = 7;
  • const público Int32 NumberOfHoursInADayOnEarth = 24;

mientras que esto no es:

  • public const Int32 AgeOfProgrammer = 25;
  • public const String NameOfLastProgrammerThatModifiedAssembly = “Joe Programmer”;

Editar 27 de mayo de 2016

OK, acabo de recibir un voto positivo, así que volví a leer mi respuesta aquí y esto en realidad es un poco incorrecto.

Ahora, la intención de la especificación del lenguaje C # es todo lo que escribí arriba. Se supone que no debes usar algo que no se pueda representar con un literal como const .

Pero puedes? Bueno, sí….

Echemos un vistazo al tipo decimal .

 public class Test { public const decimal Value = 10.123M; } 

Veamos cómo es realmente esta clase cuando se mira con ildasm:

 .field public static initonly valuetype [mscorlib]System.Decimal X .custom instance void [mscorlib]System.Runtime.CompilerServices.DecimalConstantAttribute::.ctor(int8, uint8, uint32, uint32, uint32) = ( 01 00 01 00 00 00 00 00 00 00 00 00 64 00 00 00 00 00 ) 

Déjame desglosarlo por ti:

 .field public static initonly 

corresponde a:

 public static readonly 

Así es, una const decimal es en realidad un readonly decimal .

El verdadero problema aquí es que el comstackdor usará ese DecimalConstantAttribute para trabajar su magia.

Ahora, esta es la única magia que conozco con el comstackdor de C #, pero pensé que valía la pena mencionarla.

¿Alguien tiene una teoría sobre el fundamento de esta limitación?

Si se permite que sea solo una teoría, mi teoría es que los valores de const de los tipos primitivos pueden expressse en parámetros de código de operación literales en el MSIL … pero los valores de otros tipos no primitivos no pueden, porque MSIL no tiene el syntax para express el valor de un tipo definido por el usuario como un literal.

Creo que el concepto de const es en realidad azúcar sintáctico en C #, y que simplemente reemplaza cualquier uso del nombre con el valor literal

¿Qué hace el comstackdor con objetos const en otros idiomas?

Puede usar readonly para tipos mutables que me serán evaluados en tiempo de ejecución. Ver este artículo para las diferencias.

las conste se limitan a números y cadenas en C # porque el comstackdor reemplaza la variable con el valor literal en MSIL. En otras palabras, cuando escribes:

 const string myName = "Bruce Wayne"; if (someVar == myName) { ... } 

en realidad es tratado como

 if (someVar == "Bruce Wayne") { ... } 

y sí, el comstackdor de C # es lo suficientemente inteligente como para tratar al operador de igualdad (==) en cadenas como

 string1.Equals(string2) 

Me parece que solo los tipos de valores se pueden express como una constante (con la excepción de cadenas, que se encuentra entre el valor y el tipo de objeto).

Está bien para mí: los objetos (referencias) deben asignarse en el montón, pero las constantes no se asignan en absoluto (ya que se reemplazan en tiempo de comstackción).

En resumen, todos los tipos simples, enumeraciones y cadenas son inmutables pero un Struct por ejemplo no lo es. Puede tener un Struct con estado mutable (campos, propiedades, incluso referencias a Tipos de Referencia). Por lo tanto, el comstackdor no puede asegurarse (en tiempo de comstackción) de que el estado interno de una variable Struct no se puede cambiar. Por lo tanto, el comstackdor debe asegurarse de que un tipo sea, por definición, inmutable para ser utilizado en una expresión constante.