Por que o C # limita o conjunto de tipos que podem ser declarados como const?

O erro do compilador CS0283 indica que somente os tipos básicos de POD (assim como strings, enums e referências nulas) podem ser declarados como const . Alguém tem uma teoria sobre a lógica dessa limitação? Por exemplo, seria bom poder declarar valores constantes de outros tipos, como IntPtr.

Eu acredito que o conceito de const é realmente açúcar sintático em C #, e que apenas substitui qualquer uso do nome com o valor literal. Por exemplo, dada a seguinte declaração, qualquer referência a Foo seria substituída por “foo” em tempo de compilation.

 const string Foo = "foo"; 

Isso excluiria qualquer tipo mutável, então talvez eles tenham escolhido essa limitação em vez de precisar determinar em tempo de compilation se um determinado tipo é mutável?

Da especificação C #, capítulo 10.4 – constantes :
(10.4 na especificação C # 3.0, 10.3 na versão online para 2.0)

Uma constante é um membro de class que representa um valor constante: um valor que pode ser calculado em tempo de compilation.

Isso basicamente diz que você só pode usar expressões que consistem apenas em literais. Quaisquer chamadas a quaisquer methods, construtores (que não podem ser representados como literais IL puros) não podem ser usadas, pois não há como o compilador fazer essa execução e, assim, calcular os resultados, em tempo de compilation. Além disso, como não há como rotular um método como invariante (ou seja, há um mapeamento um-para-um entre input e saída), a única maneira de o compilador fazer isso seria analisar o IL para ver se isso depende de outras coisas que não sejam os parâmetros de input, o caso especial manipula alguns tipos (como IntPtr) ou simplesmente desabilita todas as chamadas para qualquer código.

IntPtr, por exemplo, apesar de ser um tipo de valor, ainda é uma estrutura e não um dos literais incorporados. Como tal, qualquer expressão usando um IntPtr precisará chamar o código na estrutura IntPtr, e isso é o que não é legal para uma declaração constante.

O único exemplo de tipo de valor constante legal que eu posso pensar seria um que é inicializado com zeros apenas declarando, e isso é pouco útil.

Quanto ao modo como o compilador trata / usa constantes, ele usará o valor computado no lugar do nome da constante no código.

Assim, você tem o seguinte efeito:

  • Nenhuma referência ao nome da constante original, class em que ele foi declarado ou namespace, é compilada no código neste local
  • Se você descompilar o código, ele terá números mágicos, simplesmente porque a “referência” original à constante é, como mencionado acima, não presente, somente o valor da constante
  • O compilador pode usar isso para otimizar ou até mesmo remover código desnecessário. Por exemplo, if (SomeClass.Version == 1) , quando SomeClass.Version tiver o valor de 1, de fato removerá a instrução if e manterá o bloco de código sendo executado. Se o valor da constante não for 1, toda a instrução if e seu bloco serão removidos.
  • Como o valor de uma constante é compilado no código, e não uma referência à constante, o uso de constantes de outros assemblies não atualizará automaticamente o código compilado de qualquer forma se o valor da constante for alterado (o que não deve acontecer!)

Em outras palavras, com o seguinte cenário:

  1. Assembly A, contém uma constante denominada “Version”, com um valor de 1
  2. Assembly B, contém uma expressão que analisa o número da versão do assembly A dessa constante e a compara a 1, para garantir que ele possa trabalhar com a assembly
  3. Alguém modifica o assembly A, aumentando o valor da constante para 2 e recria A (mas não B)

Nesse caso, o assembly B, em sua forma compilada, ainda irá comparar o valor de 1 para 1, porque quando B foi compilado, a constante teve o valor 1.

Na verdade, se esse é o único uso de qualquer coisa do assembly A no assembly B, o assembly B será compilado sem uma dependência no assembly A. Executando o código que contém essa expressão no assembly B não carregará o assembly a.

Portanto, as constantes só devem ser usadas para coisas que nunca serão alteradas. Se for um valor que pode ou vai mudar em algum momento no futuro, e você não pode garantir que todos os outros conjuntos sejam recriados simultaneamente, um campo somente leitura é mais apropriado que uma constante.

Então está tudo bem:

  • public const Int32 NumberOfDaysInAWeekInGregorianCalendar = 7;
  • public const Int32 NumberOfHoursInADayOnEarth = 24;

enquanto isso não é:

  • public const Int32 AgeOfProgrammer = 25;
  • public const Cadeia NameOfLastProgrammerThatModifiedAssembly = “Programador Joe”;

Editar 27 de maio de 2016

Ok, acabei de ganhar um voto positivo, então eu releio minha resposta aqui e isso está um pouco errado.

Agora, a intenção da especificação da linguagem C # é tudo o que escrevi acima. Você não deveria usar algo que não pode ser representado com um literal como um const .

Mas você consegue? Bem, sim….

Vamos dar uma olhada no tipo decimal .

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

Vamos ver como essa class se parece quando se olha com 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 ) 

Deixe-me quebrar isto para você:

 .field public static initonly 

corresponde a:

 public static readonly 

Isso mesmo, um const decimal é na verdade um readonly decimal .

O negócio real aqui é que o compilador usará esse DecimalConstantAttribute para trabalhar sua mágica.

Agora, essa é a única mágica que conheço com o compilador C #, mas achei que valeu a pena mencionar.

Alguém tem uma teoria sobre a lógica dessa limitação?

Se for permitido ser apenas uma teoria, minha teoria é que valores constantes de tipos primitivos podem ser expressos em parâmetros opcode literais no MSIL … mas os valores de outros tipos não-primitivos não podem, porque MSIL não tem o syntax para expressar o valor de um tipo definido pelo usuário como um literal.

Eu acredito que o conceito de const é realmente açúcar sintático em C #, e que apenas substitui qualquer uso do nome com o valor literal

O que o compilador faz com objects const em outras linguagens?

Você pode usar readonly para tipos mutáveis ​​que podem ser avaliados em tempo de execução. Veja este artigo para as diferenças.

Consts são limitados a números e seqüências de caracteres em C # porque o compilador substitui a variável com o valor literal no MSIL. Em outras palavras, quando você escreve:

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

é realmente tratado como

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

e sim, o compilador C # é inteligente o suficiente para tratar o operador de igualdade (==) em seqüências de caracteres como

 string1.Equals(string2) 

Parece-me que apenas os tipos de valor podem ser expressos como uma constante (com exceção de strings, que fica em algum lugar entre valor e tipo de object).

Está tudo bem para mim: objects (referências) devem ser alocados no heap, mas as constantes não são alocadas (já que são substituídas em tempo de compilation).

Em suma, todos os tipos simples, enums e strings são imutáveis, mas um Struct, por exemplo, não é. Você pode ter uma estrutura com estado mutável (campos, propriedades e até referências a tipos de referência). Portanto, o compilador não pode garantir (em tempo de compilation) que o estado interno de uma variável Struct não pode ser alterado. Portanto, o compilador precisa ter certeza de que um tipo é, por definição, imutável para ser usado em uma expressão constante.