Por que os parâmetros const não são permitidos em C #?

102

Parece estranho, especialmente para desenvolvedores C ++. Em C ++ costumávamos marcar um parâmetro como constpara ter certeza de que seu estado não será alterado no método. Existem também outros motivos específicos do C ++, como passar const refpara passar por ref e ter certeza de que o estado não será alterado. Mas por que não podemos marcar como parâmetros de método const em C #?

Por que não posso declarar meu método como o seguinte?

    ....
    static void TestMethod1(const MyClass val)
    {}
    ....
    static void TestMethod2(const int val)
    {}
    ....
Incógnito
fonte
Sempre foi apenas um recurso de captura de segurança em C ++, e nunca realmente parte integrante da linguagem como eu a via. Os desenvolvedores de C # não achavam que agregava muito valor, evidentemente.
Noldorin
3
Uma discussão maior nesta questão: "Const-correção em c #": stackoverflow.com/questions/114149/const-correctness-in-c
tenpn
Existe para tipos de valor [out / ref], mas não para tipos de referência. É uma pergunta interessante.
Kenny
4
Como essa pergunta pode ter as tags C # e agnósticas de linguagem?
CJ7 de
1
Dados imutáveis ​​e funções sem efeitos colaterais são mais fáceis de raciocinar. Você pode gostar de F # fsharpforfunandprofit.com/posts/is-your-language-unreasonable
Coronel Panic

Respostas:

59

Além de outras boas respostas, acrescentarei outra razão para não colocar constância do estilo C no C #. Você disse:

marcamos o parâmetro como const para ter certeza de que seu estado não será alterado no método.

Se const realmente fizesse isso, seria ótimo. Const não faz isso. O const é uma mentira!

Const não oferece nenhuma garantia de que eu possa realmente usar. Suponha que você tenha um método que usa uma coisa const. Existem dois autores de código: a pessoa que escreve o chamador e a pessoa que escreve o receptor . O autor do chamado fez com que o método assumisse uma const. O que os dois autores podem assumir é invariável em relação ao objeto?

Nada. O callee está livre para rejeitar const e transformar o objeto, portanto, o chamador não tem garantia de que chamar um método que usa const realmente não o fará sofrer mutação. Da mesma forma, o receptor não pode assumir que o conteúdo do objeto não mudará durante a ação do receptor; o callee pode chamar algum método mutante em um alias não const do objeto const, e agora o chamado objeto const mudou .

Const de estilo C não fornece garantia de que o objeto não será alterado e, portanto, está quebrado. Agora, C já tem um sistema de tipo fraco no qual você pode fazer uma reinterpretação de um duplo em um int se realmente quiser, então não deve ser uma surpresa que ele tenha um sistema de tipo fraco com respeito a const também. Mas C # foi projetado para ter um bom sistema de tipos, um sistema de tipos onde, quando você diz "esta variável contém uma string", a variável realmente contém uma referência a uma string (ou nula). Não queremos absolutamente colocar um modificador "const" no estilo C no sistema de tipos porque não queremos que o sistema de tipos seja uma mentira . Queremos que o sistema de tipos seja forte para que você possa raciocinar corretamente sobre o seu código.

Const em C é uma diretriz ; basicamente significa "você pode confiar em mim para não tentar transformar essa coisa". Isso não deveria estar no sistema de tipos ; as coisas no sistema de tipos devem ser um fato sobre o objeto sobre o qual você pode raciocinar, não uma diretriz para seu uso.

Agora, não me entenda mal; só porque const em C está profundamente quebrado não significa que todo o conceito seja inútil. O que eu adoraria ver é alguma forma realmente correta e útil de anotação "const" em C #, uma anotação que humanos e compiladores podem usar para ajudá-los a entender o código, e que o tempo de execução pode usar para fazer coisas como paralelização automática e outras otimizações avançadas.

Por exemplo, imagine se você pudesse "desenhar uma caixa" em torno de um pedaço de código e dizer " Garanto que esse pedaço de código não executa mutações em nenhum campo desta classe" de uma forma que pudesse ser verificada pelo compilador. Ou desenhe uma caixa que diga "este método puro altera o estado interno do objeto, mas não de qualquer maneira que seja observável fora da caixa". Esse objeto não poderia ser multithread automaticamente com segurança, mas poderia ser memoized automaticamente . Existem todos os tipos de anotações interessantes que poderíamos colocar no código que permitiriam otimizações ricas e um entendimento mais profundo. Podemos fazer muito melhor do que a fraca anotação const do estilo C.

No entanto, enfatizo que isso é apenas especulação . Não temos planos firmes de colocar esse tipo de recurso em qualquer versão futura hipotética do C #, se é que existe uma, que não anunciamos de uma forma ou de outra. É algo que eu adoraria ver e algo que a próxima ênfase na computação de vários núcleos pode exigir, mas nada disso deve ser interpretado de forma alguma como uma previsão ou uma garantia de qualquer recurso particular ou direção futura para C #.

Agora, se o que você deseja é apenas uma anotação na variável local que é um parâmetro que diz "o valor deste parâmetro não muda ao longo do método", então, claro, isso seria feito facilmente. Poderíamos suportar locais e parâmetros "somente leitura" que seriam inicializados uma vez e um erro em tempo de compilação para alterar o método. A variável declarada pela instrução "using" já é um local; poderíamos adicionar uma anotação opcional a todos os locais e parâmetros para fazê-los agir como variáveis ​​"usando". Nunca foi um recurso de prioridade muito alta, então nunca foi implementado.

Eric Lippert
fonte
34
Desculpe, mas acho este argumento muito fraco, o fato de um desenvolvedor poder dizer uma mentira não pode ser evitado em nenhum idioma. void foo (const A & a) que modifica o objeto a não é diferente de int countApples (Basket b) que retorna o número de peras. É apenas um bug, um bug que pode ser compilado em qualquer linguagem.
Alessandro Teruzzi
55
“O chamado está livre para jogar fora o const…” uhhhhh… (° _ °) Este é um argumento muito superficial que você está fazendo aqui. O receptor também está livre para começar a escrever localizações de memória aleatórias com 0xDEADBEEF. Mas ambos seriam uma programação muito ruim , e detectados cedo e de forma pungente por qualquer compilador moderno. No futuro, ao escrever as respostas, por favor, não confunda o que é tecnicamente possível usando backdoors de uma linguagem com o que é viável no código do mundo real. Informações distorcidas como essa confundem leitores menos experientes e degradam a qualidade das informações no SO.
Slipp D. Thompson
8
“Const em C é uma diretriz;” Citar uma fonte para algumas das coisas que você está dizendo realmente ajudaria em seu caso aqui. Na minha experiência educacional e da indústria, a declaração citada é besteira - const em C é um recurso integrado obrigatório tanto quanto o próprio sistema de tipos - ambos têm portas dos fundos para enganá-los quando necessário (como realmente tudo em C), mas são esperados a ser usado pelo livro em 99,99% do código.
Slipp D. Thompson
29
Essa resposta é a razão pela qual às vezes odeio o design C #. Os designers de linguagem têm absoluta certeza de que são muito mais inteligentes do que outros desenvolvedores e tentam protegê-los excessivamente de bugs. Obviamente, a palavra-chave const funciona apenas em tempo de compilação, porque em C ++ temos acesso direto à memória e podemos fazer o que quisermos e temos várias maneiras de contornar a declaração const. Mas ninguém faz isso em situações normais. A propósito, 'privado' e 'protegido' em C # também não fornecem nenhuma garantia, porque podemos acessar esses métodos ou campos usando reflexão.
BlackOverlord
13
@BlackOverlord: (1) O desejo de projetar uma linguagem cujas propriedades naturalmente levem os desenvolvedores ao sucesso, ao invés do fracasso, é motivado pelo desejo de construir ferramentas úteis , não pela crença de que os designers são mais espertos do que seus clientes, como você conjectura. (2) A reflexão não faz parte da linguagem C #; faz parte do tempo de execução; o acesso à reflexão é limitado pelo sistema de segurança do tempo de execução. O código de baixa confiança não tem a capacidade de fazer reflexão privada. As garantias fornecidas pelos modificadores de acesso são para código de baixa confiança ; o código de alta confiança pode fazer qualquer coisa.
Eric Lippert
24

Um dos motivos pelos quais não há correção const em C # é porque ele não existe no nível de tempo de execução. Lembre-se de que C # 1.0 não tinha nenhum recurso, a menos que fizesse parte do tempo de execução.

E várias razões pelas quais o CLR não tem uma noção de correção const são, por exemplo:

  1. Isso complica o tempo de execução; além disso, a JVM também não tinha, e o CLR basicamente começou como um projeto para criar um tempo de execução do tipo JVM, não um tempo de execução do tipo C ++.
  2. Se houver correção const no nível de tempo de execução, deve haver correção const no BCL, caso contrário, o recurso é praticamente inútil no que diz respeito ao .NET Framework.
  3. Mas se o BCL requer correção const, toda linguagem no topo do CLR deve suportar correção const (VB, JavaScript, Python, Ruby, F #, etc.) Isso não vai acontecer.

A correção Const é basicamente um recurso de linguagem presente apenas em C ++. Portanto, tudo se resume à mesma argumentação sobre por que o CLR não exige exceções verificadas (que é um recurso exclusivo da linguagem Java).

Além disso, não acho que você pode introduzir um recurso de sistema de tipo fundamental em um ambiente gerenciado sem quebrar a compatibilidade com versões anteriores. Portanto, não conte com a correção const entrando no mundo do C #.

Ruben
fonte
Tem certeza de que a JVM não tem. Como eu sei, tem. Você pode tentar public static void TestMethod (final int val) em Java.
Incógnito de
2
Final não é realmente constante. Você ainda pode alterar os campos no IIRC de referência, mas não o valor / referência em si. (Que é passado por valor de qualquer maneira, então é mais um truque do compilador para evitar que você altere o valor do parâmetro.)
Ruben
1
seu terceiro ponto é apenas parcialmente verdadeiro. ter parâmetros const para chamadas BCL não seria uma alteração decisiva, pois eles apenas descrevem o contrato com mais precisão. "Este método não mudará o estado do objeto" em contraste com "Este método requer que você passe um valor const" que seria quebrado
Rune FS
2
Ele é imposto dentro do método para que não seja uma alteração importante introduzi-lo no BCL.
Rune FS de
1
Mesmo se o tempo de execução não pudesse aplicá-lo, seria útil ter um atributo que especifica se uma função deve ser permitida / esperada para gravar em um refparâmetro (especialmente, no caso de métodos / propriedades de estrutura this). Se um método declara especificamente que não gravará em um refparâmetro, o compilador deve permitir que valores somente leitura sejam passados. Por outro lado, se um método afirma que gravará this, o compilador não deve permitir que tal método seja chamado em valores somente leitura.
supercat de
12

Eu acredito que há duas razões pelas quais C # não é constante.

O primeiro é a compreensibilidade . Poucos programadores C ++ entendem correção const. O exemplo simples de const int argé bonito, mas eu também vi char * const * const arg- um ponteiro constante para ponteiros constantes para caracteres não constantes. A correção constante de ponteiros para funções é um nível totalmente novo de ofuscação.

A segunda é porque os argumentos da classe são referências passadas por valor. Isso significa que já existem dois níveis de constância para lidar, sem uma sintaxe obviamente clara . Um ponto de tropeço semelhante são as coleções (e coleções de coleções, etc.).

A correção constante é uma parte importante do sistema de tipos C ++. Ele poderia - em teoria - ser adicionado ao C # como algo que só é verificado em tempo de compilação (não precisa ser adicionado ao CLR e não afetaria o BCL a menos que a noção de métodos de membro const fossem incluídos) .

No entanto, acredito que isso seja improvável: o segundo motivo (sintaxe) seria bastante difícil de resolver, o que tornaria o primeiro motivo (compreensibilidade) ainda mais problemático.

Stephen Cleary
fonte
1
Você está certo que o CLR realmente não precisa se preocupar com isso, pois pode-se usar modopte modreqno nível de metadados para armazenar essa informação (como C + / CLI já faz - veja System.Runtime.CompilerServices.IsConst); e isso poderia ser estendido trivialmente aos métodos const também.
Pavel Minaev
Vale a pena, mas precisa ser feito de maneira segura. O const Profundo / Raso que você mencionou abre uma lata inteira de vermes.
user1496062 01 de
Em um c ++ onde as referências são realmente manipuladas, posso ver seu argumento sobre ter dois tipos de const. No entanto, em C # (onde você tem que passar pelas portas dos fundos para usar "ponteiros"), parece claro o suficiente para mim que há um significado bem definido para tipos de referência (ou seja, classes) e um significado bem definido, embora diferente, para valor tipos (ou seja, primitivos, estruturas)
Assimilador de
2
Programadores que não conseguem entender a constância não deveriam programar em C ++.
Steve White de
5

constsignifica "constante de tempo de compilação" em C #, não "somente leitura, mas possivelmente mutável por outro código" como em C ++. Um análogo aproximado de C ++ constem C # é readonly, mas só se aplica a campos. Além disso, não há nenhuma noção semelhante à do C ++ de correção const em C #.

O raciocínio é difícil de dizer com certeza, porque há muitos motivos potenciais, mas meu palpite seria o desejo de manter a linguagem simples em primeiro lugar e as vantagens incertas de implementar isso em segundo lugar.

Pavel Minaev
fonte
Então você acha que o MS considera 'ruído' ter parâmetros constantes nos métodos.
Incógnito de
1
Não tenho certeza do que é "ruído" - prefiro chamá-lo de "relação custo / benefício elevada". Mas, é claro, você precisará de alguém da equipe C # para lhe dizer com certeza.
Pavel Minaev
Então, pelo que entendi, seu argumento é que a constância foi deixada de fora do C # para mantê-lo simples. Pense sobre isso.
Steve White,
2

Isso é possível desde C # 7.2 (dezembro de 2017 com Visual Studio 2017 15.5).

Apenas para estruturas e tipos básicos ! , não para membros de classes.

Você deve usar inpara enviar o argumento como uma entrada por referência. Vejo:

Consulte: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/in-parameter-modifier

Para seu exemplo:

....
static void TestMethod1(in MyStruct val)
{
    val = new MyStruct;  // Error CS8331 Cannot assign to variable 'in MyStruct' because it is a readonly variable
    val.member = 0;  // Error CS8332 Cannot assign to a member of variable 'MyStruct' because it is a readonly variable
}
....
static void TestMethod2(in int val)
{
    val = 0;  // Error CS8331 Cannot assign to variable 'in int' because it is a readonly variable
}
....
isgoed
fonte