Em C#, a palavra reservada readonly só pode ser utilizada com construções de campos. A proposta 115, Readonly for Locals and Parameterers (somente leitura para variáveis locais e parâmetros) define extensões de uso da palavra reservada readonly para cobrir muitos outros cenários.
A proposta começa definindo a capacidade de criar variáveis locais readonly. O primeiro caso de uso observado é uma melhoria semântica para documentação. Ao modificar uma variável local para readonly, indica-se que esta variável não é e nunca deve ser alterada em nenhum outro lugar da função. Esta característica é especialmente útil para funções mais longas e complexas, que não podem ser examinadas em sua totalidade em uma única visão.
O segundo caso de uso é maior segurança ao trabalhar com closures e múltiplas threads. Ao executar Parallel.ForEach na closure, pode-se facilmente introduzir uma condição de corrida (race condition). Ao modificar as variáveis locais com readonly por padrão, qualquer variável local mutável vai ser apontada pelo compilador como uma condição a ser revisada.
Preocupações com a sintaxe
Variáveis locais readonly apresentam maior valor quando utilizadas por padrão. Mas, para que isso aconteça, a sintaxe não pode ser onerosa. Considere as linhas a seguir:
var gravity = 9.780327; double gravity = 9.780327; const double gravity = 9.780327;
Mesmo que a intenção seja de declarar a gravidade como uma constante, a maioria dos desenvolvedores prefere usar a primeira versão apresentada. Eles não fazem "a coisa certa" por que a alternativa adotada é mais simples de se digitar e, em um caso isolado, não apresenta tanta diferença.
Essa preferência de facilidade sobre corretude também ocorre nas operações de coerção de tipos. O código a seguir apresenta um erro muito comum mesmo para programadores experientes:
var button = sender as Button; button.Enabled = false;
O código deveria ser "button = (Button)sender", mas novamente o código correto é ligeiramente mais trabalhoso de se digitar.
Para endereçar essas preocupações, a proposta sugere um atalho ao utilizar variáveis locais de tipos implícitos. Duas construções são consideradas:
val gravity = 9.780327; let gravity = 9.780327;
Entre as duas opções, "let" está atualmente em preferência, pois ela já é utilizada em expressões LINQ e é simples de diferenciar visualmente de "var". Ela também é uma melhor opção para pessoas que não falam inglês e cuja língua nativa não distingue entre r e l.
Parâmetros de somente leitura
O próximo tema envolve a possibilidade de alterar parâmetros com a palavra reservada readonly. Usuários das ferramentas de análise de código do Visual Studio provavelmente considerarão isto redundante, uma vez que as ferramentas previnem a alteração de valor de um parâmetro normal. Ainda assim, existe um caso de uso que estas ferramentas não cobrem.
Ao trabalhar com código de alto desempenho é comum preferir o uso de estruturas (structs) ao invés de classes, mesmo quando estas estruturas são grandes. Para evitar os custos associados a cópias, estas estruturas são passadas para funções usando parâmetros por referência (ref parameters).
Do ponto de vista de documentação, não há nada na assinatura da função que deixe claro para o código que a invocou que o parâmetro não vá ser modificado. A possibilidade de marcar o parâmetro como "readonly ref" cobriria esta lacuna.
Nem todos os envolvidos estão contentes com a sintaxe, pois ela requer que o ponto de invocação seja decorado com a palavra reservada ref, o que pode ser de alguma ilusório. Ao invés disso, Porges sugere usar a palavra reservada "in":
void DoSomething(readonly ref LargeStruct value) DoSomething(ref myLocal); void DoSomething(in LargeStruct value) DoSomething(myLocal);
Readonly e const
Enquanto a utilização de readonly previne a substituição direta de um valor, ela usualmente não previne a possibilidade de modificar um membro de um objeto. Desta forma, uma extensão a essa proposta é a capacidade de aumentar a proteção oferecida por readonly.
Existem linguagens como C++ que já suportam esse conceito. Apesar de funcionar corretamente, ele pode ser difícil de usar, pois os desenvolvedores frequentemente se confundem sobre qual é o contexto em que const está sendo aplicado: sobre a própria variável ou sobre o conteúdo da variável. Para evitar esta ambiguidade é importante considerar aspectos da sintaxe. Uma sugestão é usar readonly para a variável e const para seu conteúdo.
C++ também suporta o conceito de função const. Estas são funções que podem ser invocadas por meio de uma variável readonly/const pois isto garante que o estado do objeto não será alterado. .NET também suporta este conceito por meio do atributo Pure, mas ele não é atualmente considerado pelo compilador de C#.
Notas: readonly, structs e fields
Apesar de atualmente não fazer parte da proposta, a interação entre readonly, structs e fields deve ser levada em conta. Considere a linha a seguir:
private readonly Foo _foo = new Foo(1, 2, 3);
Se Foo for completamente imutável, então este campo readonly funciona conforme esperado. Mas, se existir qualquer setter em Foo, então cada vez que acessar este campo o compilador criará uma cópia para que não possa ser modificada acidentalmente. Ao contrário dos campos referenciados como readonly, estruturas readonly realmente são somente leitura. Mas, como existe um custo e inconsistência de desempenho de forma oculta, encontrar uma melhor semântica para expressar esse conceito seria benéfico.
Para mais informações, veja Mutating Readonly Structs de Eric Libbert.