2009-01-14 10 views
26

El error del compilador 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.¿Por qué C# limita el conjunto de tipos que se pueden declarar como const?

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 declaración, cualquier referencia a Foo sería reemplazada por "foo" en tiempo de compilación.

const string Foo = "foo"; 

Esto descarta cualquier tipo mutables, así que tal vez eligieron esta limitación en lugar de tener que determinar en tiempo de compilación si un tipo dado es mutable?

Respuesta

26

Desde el C# specification, chapter 10.4 - Constants:
(10,4 en la especificación C# 3.0, 10.3 en la versión en línea de 2,0)

Una constante es un miembro de la clase que representa un valor constante: un valor que puede ser computado en tiempo de compilació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 compilador realice esa ejecución y, por lo tanto, calcule los resultados, en tiempo de compilación. Además, dado que no hay forma de etiquetar un método como invariante (es decir,existe un mapeo uno a uno entre entrada y salida), la única forma para que el compilador haga esto sería analizar el IL para ver si depende de cosas que no sean los parámetros de entrada, caso especial manejar algunos tipos (como IntPtr), o simplemente no permitir todas las llamadas 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 declaración constante.

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

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

Por lo tanto, tiene el siguiente efecto:

  • No se hace referencia al nombre de la constante original, la clase fue declarado en, o espacio de nombres, se compila en el código en esta ubicación
  • Si descompilación código, tendrá números mágicos, simplemente porque la "referencia" original a la constante es, como se mencionó anteriormente, no presente, solo el valor de la constante
  • El compilador 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 compila en el código y no es una referencia a la constante, el uso de constantes de otros ensambles no actualizará automágicamente el código compilado de ninguna manera si el valor de la constante cambiara (lo cual debería no)

En otras palabras, con el siguiente escenario:

  1. Asamblea a, contiene una constante llamada "Versión", tiene un valor de 1
  2. Asamblea B, contiene una expresión que analiza el número de versión del conjunto A a partir de esa constante a nd compara con 1, para asegurarse de que puede trabajar con el conjunto
  3. Alguien modifica conjunto A, aumentando el valor de la constante a 2, y reconstruye A (pero no B)

En este caso, El ensamblado B, en su forma compilada, 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 del ensamble A en conjunto B, conjunto B se compilará sin dependencia del conjunto A. La ejecución del código que contiene esa expresión en el conjunto B no cargará el conjunto A.

Las constantes solo deben 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.

Así que esto es aceptable:

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

mientras que esto no es:

  • const pública Int32 AgeOfProgrammer = 25;
  • public const String NameOfLastProgrammerThatModifiedAssembly = "Joe Programmer";

edición puede 27o el año 2016

bien, acaba de recibir un upvote, por lo que volver a leer mi respuesta aquí y esto es en realidad un poco mal.

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

¿Pero puede? Bueno, sí ...

Echemos un vistazo al tipo decimal.

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

Echemos un vistazo a lo que esta clase se parece realmente si 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) 

Vamos a dividirla para usted:

.field public static initonly 

corresponde a:

public static readonly 

Correcto, un const decimal es en realidad un readonly decimal.

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

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

+1

Obviamente, no puede hacer esto, pero * hablando hipotéticamente *, supongamos que teníamos un método estático de devolución de valor que estaba marcado con el atributo [Puro] y el compilador realmente lo impuso (no lo hace actualmente). ¿Podrían los diseñadores del lenguaje al menos * teóricamente * permitir que se inicialice una const con método puro de retorno de tipo de valor estático (con la aplicación real de atributo [Pure]) siempre que los argumentos no sean más que literales? Parece que podrían simplemente ejecutarlo con argumentos literalmente, obtener el resultado que obviamente no va a cambiar y sub en el valor. ¿Estoy pasando por alto algo? –

+0

'En este caso, el conjunto B, en su forma compilada, aún comparará el valor de 1 a 1, porque cuando B se compiló, la constante tenía el valor 1' - aún puede hacer lo mismo con' const int público AssemblyVersion = 1' en cualquier versión de C# en la actualidad, por lo que no hay nada malo con * structures * en sí mismos –

1

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

Lo que hace el compilador ver con objetos const en otra idiomas?

Puede usar readonly para los tipos mutables que me serán evaluados en tiempo de ejecución. Ver this article para las diferencias.

+0

Supongo que podría verificar esto usando Reflector. – BuddyJoe

+0

La palabra clave const puede tener diferentes efectos en diferentes idiomas, como en C++, donde se puede usar para garantizar que un método no cambie el estado de un objeto, por ejemplo. –

+0

@ LasseV.Karlsen, pero 'const' significa lo mismo en los tipos C (++) y C# para * no referencia *. Entonces esta respuesta es correcta cuando se habla de eso. Otros idiomas tienen este concepto de "const correctness". El análogo más cercano en C# es el patrón inmutable combinado con los miembros de la clase 'readonly' inicializados por el constructor. – binki

0

consts se limitan a números y cadenas en C# porque el compilador reemplaza la variable con el valor literal en el MSIL. En otras palabras, cuando escribe:

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

se trata realmente tan

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

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

string1.Equals(string2) 
+0

Correcto, lo que no entiendo es por qué no pudo declarar, p. a const IntPtr, que también podría ser reemplazado en los puntos de uso. Quizás la diferencia es que tendría que construirse un IntPtr, mientras que los POD y las cadenas se pueden representar directamente en el IL. – Charlie

+0

El compilador tendría que ejecutar código en IntPtr para obtener el valor final, y esta parte "ejecutar código para obtener el valor" tendría que compilarse en el código donde sea que se use la constante, y esta es la parte que no está permitida . –

1

¿Alguien tiene una teoría sobre los motivos de esta limitación?

Si se le permite ser sólo una teoría, mi teoría es que los valores const de tipos primitivos se pueden expresar en los parámetros de código de operación literales en el MSIL ... pero los valores de otros tipos no primitivos, no puede, porque MSIL no tiene la sintaxis para expresar el valor de un tipo definido por el usuario como literal.

+0

Eso parece posible, sí (ver también mi comentario a @IAmCodeMonkey) – Charlie

+0

El MSIL define las constantes en términos de una secuencia de bytes. Dado un tipo como, por ejemplo, 'struct Vector2d {public double X, Y;}', un compilador podría permitir la declaración de una constante 'const Vector2d UnitXVector = {X = 1.0, Y = 0.0};', ya que podría determinar la secuencia exacta de bytes representados por ese estructura. Eso solo funcionaría, sin embargo, para una estructura sin campos privados; si una estructura usa propiedades públicas y campos de respaldo privados, un compilador solo podría construirlo averiguando qué hicieron exactamente las propiedades. – supercat

+0

@supercat, ¿está implicando que MSIL puede confiar en que los objetos se distribuyan de cierta manera en la memoria? ¿La existencia de ['StructLayoutAttribute'] (https://msdn.microsoft.com/en-us/library/System.Runtime.InteropServices.StructLayoutAttribute%28v=vs.100%29.aspx) no implica que pueda ' t? – binki

0

Me parece que solo los tipos de valores se pueden expresar como una constante (con la excepción de las 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 compilación).

+1

Se pueden usar cadenas porque el compilador puede insertar código IL para cargar una cadena con un valor dado. Como esta es la magia de IL especialmente agregada para cuerdas, se pueden usar. –

0

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 compilador no puede asegurarse (en tiempo de compilación) de que el estado interno de una variable Struct no se puede cambiar.Por lo tanto, el compilador debe asegurarse de que un tipo sea, por definición, inmutable para ser utilizado en una expresión constante.

Cuestiones relacionadas