¿Por qué no se permiten los parámetros const en C #?

102

Parece extraño especialmente para los desarrolladores de C ++. En C ++, solíamos marcar un parámetro como constpara estar seguros de que su estado no cambiará en el método. También hay otras razones específicas de C ++, como pasar const refpara pasar por ref y estar seguro de que el estado no cambiará. Pero, ¿por qué no podemos marcar como parámetros de método const en C #?

¿Por qué no puedo declarar mi método de la siguiente manera?

    ....
    static void TestMethod1(const MyClass val)
    {}
    ....
    static void TestMethod2(const int val)
    {}
    ....
Incógnito
fuente
Siempre fue solo una característica de seguridad en C ++, y nunca realmente integral al lenguaje como lo vi. Evidentemente, los desarrolladores de C # no pensaron que añadiera mucho valor.
Noldorin
3
Una discusión más amplia en esta pregunta: "Const-corrección en c #": stackoverflow.com/questions/114149/const-correctness-in-c
tenpn
Hay tipos de valor [out / ref], pero no tipos de referencia. Es una pregunta interesante.
Kenny
4
¿Cómo podría esta pregunta tener las etiquetas C # y las etiquetas independientes del idioma?
CJ7
1
Los datos inmutables y las funciones sin efectos secundarios son más fáciles de razonar. Puede disfrutar de F # fsharpforfunandprofit.com/posts/is-your-language-unreasonable
Colonel Panic

Respuestas:

59

Además de las otras buenas respuestas, agregaré otra razón más por la que no poner la constidad de estilo C en C #. Tu dijiste:

marcamos el parámetro como constante para estar seguros de que su estado no cambiará en el método.

Si const realmente hiciera eso, sería genial. Const no hace eso. ¡La constante es mentira!

Const no ofrece ninguna garantía de que realmente pueda usar. Suponga que tiene un método que toma una constante. Hay dos autores de código: la persona que escribe la persona que llama y la persona que escribe la persona que llama . El autor de la llamada ha hecho que el método tome una constante. ¿Qué pueden asumir los dos autores que es invariante con respecto al objeto?

Nada. La persona que llama es libre de deshacerse de la constante y mutar el objeto, por lo que la persona que llama no tiene garantía de que llamar a un método que toma una constante no lo mutará. Del mismo modo, el destinatario no puede asumir que el contenido del objeto no cambiará durante la acción del destinatario; el llamado podría llamar a algún método mutante en un alias no constante del objeto const, y ahora el llamado objeto const ha cambiado .

Const estilo C no ofrece garantía de que el objeto no cambiará y, por lo tanto, está roto. Ahora, C ya tiene un sistema de tipos débil en el que puede hacer una reinterpretación de un doble en un int si realmente lo desea, por lo que no debería sorprender que también tenga un sistema de tipos débil con respecto a const. Pero C # fue diseñado para tener un buen sistema de tipos, un sistema de tipos en el que cuando dices "esta variable contiene una cadena", la variable en realidad contiene una referencia a una cadena (o nulo). No queremos en absoluto poner un modificador "const" de estilo C en el sistema de tipos porque no queremos que el sistema de tipos sea una mentira . Queremos que el sistema de tipos sea sólido para que pueda razonar correctamente sobre su código.

Const en C es una guía ; básicamente significa "puedes confiar en que no intentaré mutar esta cosa". Eso no debería estar en el sistema de tipos ; lo que hay en el sistema de tipos debe ser un hecho sobre el objeto sobre el que pueda razonar, no una guía para su uso.

Ahora, no me malinterpretes; el hecho de que la constante en C esté profundamente rota no significa que todo el concepto sea inútil. Lo que me encantaría ver es una forma realmente correcta y útil de anotación "const" en C #, una anotación que tanto los humanos como los compiladores podrían usar para ayudarlos a comprender el código, y que el tiempo de ejecución podría usar para hacer cosas como la paralelización automática y otras optimizaciones avanzadas.

Por ejemplo, imagina si pudieras "dibujar un cuadro" alrededor de un trozo de código y decir "Te garantizo que este trozo de código no realiza mutaciones en ningún campo de esta clase" de una manera que el compilador pueda verificar. O dibuje una caja que diga "este método puro muta el estado interno del objeto pero no de ninguna manera que sea observable fuera de la caja". Un objeto de este tipo no podría ser multiproceso automático de forma segura, pero podría memorizarse automáticamente . Hay todo tipo de anotaciones interesantes que podríamos poner en el código que permitirían optimizaciones enriquecidas y una comprensión más profunda. Podemos hacerlo mucho mejor que la débil anotación constante de estilo C.

Sin embargo, enfatizo que esto es solo una especulación . No tenemos planes firmes para poner este tipo de característica en ninguna versión futura hipotética de C #, si es que existe una, que no hayamos anunciado de una forma u otra. Es algo que me encantaría ver, y algo que el próximo énfasis en la computación de múltiples núcleos podría requerir, pero nada de esto debe interpretarse de ninguna manera como una predicción o garantía de alguna característica en particular o dirección futura para C #.

Ahora, si lo que quieres es simplemente una anotación en la variable local que es un parámetro que dice "el valor de este parámetro no cambia en todo el método", entonces, claro, eso se haría fácilmente. Podríamos admitir locales y parámetros de "solo lectura" que se inicializarían una vez, y un error en tiempo de compilación para cambiar el método. La variable declarada por la instrucción "using" ya es local; podríamos agregar una anotación opcional a todos los locales y parámetros para que actúen como "usando" variables. Nunca ha sido una característica de muy alta prioridad, por lo que nunca se ha implementado.

Eric Lippert
fuente
34
Lo siento, pero encuentro este argumento muy débil, el hecho de que un desarrollador pueda decir una mentira no se puede evitar en ningún idioma. void foo (const A & a) que modifica el objeto a no es diferente de int countApples (Basket b) que devuelve el número de peras. Es solo un error, un error que se compila en cualquier idioma.
Alessandro Teruzzi
55
“El destinatario es libre de deshacerse de la constante…” uhhhhh… (° _ °) Este es un argumento bastante superficial que estás haciendo aquí. El destinatario también es libre de comenzar a escribir ubicaciones de memoria aleatorias con 0xDEADBEEF. Pero ambos serían una programación muy mala , y cualquier compilador moderno los captaría temprano y conmovedoramente. En el futuro, cuando escriba respuestas, no confunda lo que es técnicamente posible mediante el uso de las puertas traseras de un lenguaje con lo que es factible en el código real del mundo real. Información sesgada como esta confunde a los lectores menos experimentados y degrada la calidad de la información en SO.
Slipp D. Thompson
8
"Const en C es una guía"; Citar una fuente para algunas de las cosas que está diciendo realmente ayudaría en su caso aquí. En mi educación y experiencia en la industria, la declaración citada es una tontería: la const en C es tanto una característica incorporada obligatoria como el sistema de tipos en sí mismo, ambos tienen puertas traseras para engañarlos cuando sea necesario (como realmente todo en C) pero se espera para ser utilizado por el libro en el 99,99% del código.
Slipp D. Thompson
29
Esta respuesta es la razón por la que a veces odio el diseño de C #. Los diseñadores de lenguajes están absolutamente seguros de que son mucho más inteligentes que otros desarrolladores y tratan de protegerlos excesivamente de los errores. Es obvio que la palabra clave const funciona solo en tiempo de compilación, porque en C ++ tenemos acceso directo a la memoria y podemos hacer lo que queramos y tenemos varias formas de evitar la declaración const. Pero nadie hace esto en situaciones normales. Por cierto, 'privado' y 'protegido' en C # tampoco ofrecen garantía, ya que podemos acceder a estos métodos o campos mediante la reflexión.
BlackOverlord
13
@BlackOverlord: (1) El deseo de diseñar un lenguaje cuyas propiedades conduzcan naturalmente a los desarrolladores al éxito en lugar del fracaso está motivado por el deseo de construir herramientas útiles , no por la creencia de que los diseñadores son más inteligentes que sus clientes, como usted conjetura. (2) La reflexión no es parte del lenguaje C #; es parte del tiempo de ejecución; el acceso a la reflexión está limitado por el sistema de seguridad del tiempo de ejecución. El código de baja confianza no tiene la capacidad de realizar una reflexión privada. Las garantías proporcionadas por los modificadores de acceso son para códigos de baja confianza ; el código de alta confianza puede hacer cualquier cosa.
Eric Lippert
24

Una de las razones por las que no hay una corrección constante en C # es porque no existe en el nivel de tiempo de ejecución. Recuerde que C # 1.0 no tenía ninguna característica a menos que fuera parte del tiempo de ejecución.

Y varias razones por las que CLR no tiene una noción de corrección constante son, por ejemplo:

  1. Complica el tiempo de ejecución; además, la JVM tampoco lo tenía, y CLR básicamente comenzó como un proyecto para crear un tiempo de ejecución similar a JVM, no un tiempo de ejecución similar a C ++.
  2. Si hay una corrección constante en el nivel de tiempo de ejecución, debería haber una corrección constante en el BCL; de lo contrario, la característica no tiene sentido en lo que respecta a .NET Framework.
  3. Pero si el BCL requiere corrección constante, todos los lenguajes en la parte superior de CLR deben admitir corrección constante (VB, JavaScript, Python, Ruby, F #, etc.). Eso no va a suceder.

La corrección de la constante es prácticamente una característica del lenguaje que solo está presente en C ++. Por lo tanto, se reduce prácticamente a la misma argumentación de por qué CLR no requiere excepciones marcadas (que es una característica exclusiva del lenguaje Java).

Además, no creo que pueda introducir una característica de sistema de tipo tan fundamental en un entorno administrado sin romper la compatibilidad con versiones anteriores. Así que no cuente con que la corrección constante entre en el mundo de C #.

Ruben
fuente
¿Estás seguro de que JVM no lo tiene? Como yo sé lo tiene. Puede probar TestMethod vacío estático público (final int val) en Java.
Incógnito
2
Final no es realmente constante. Aún puede cambiar campos en la referencia IIRC, pero no el valor / referencia en sí. (Que se pasa por valor de todos modos, por lo que es más un truco del compilador para evitar que cambie el valor del parámetro).
Ruben
1
tu tercera viñeta es sólo parcialmente cierta. tener parámetros constantes para las llamadas BCL no sería un cambio rotundo, ya que simplemente describen el contrato con mayor precisión. "Este método no cambiará el estado del objeto" en contraste con "Este método requiere que pase un valor constante" que se rompería
Rune FS
2
Se aplica dentro del método, por lo que introducirlo en el BCL no sería un cambio importante.
Rune FS
1
Incluso si el tiempo de ejecución no pudiera imponerlo, sería útil tener un atributo que especificara si se debe permitir / esperar que una función escriba en un refparámetro (especialmente, en el caso de métodos / propiedades de estructura this). Si un método indica específicamente que no escribirá en un refparámetro, entonces el compilador debería permitir que se pasen valores de solo lectura. Por el contrario, si un método indica que va a escribir this, el compilador no debe permitir que dicho método sea invocado en valores de solo lectura.
supercat
12

Creo que hay dos razones por las que C # no es constante.

La primera es la comprensión . Pocos programadores de C ++ entienden la corrección constante. El ejemplo simple de const int arges lindo, pero también lo he visto char * const * const arg: un puntero constante a punteros constantes a caracteres no constantes. La corrección constante de los punteros a las funciones es un nivel completamente nuevo de ofuscación.

La segunda es porque los argumentos de clase son referencias pasadas por valor. Esto significa que ya hay dos niveles de constancia con los que lidiar, sin una sintaxis obviamente clara . Un punto de tropiezo similar son las colecciones (y colecciones de colecciones, etc.).

La corrección de constantes es una parte importante del sistema de tipos de C ++. En teoría, podría agregarse a C # como algo que solo se verifica en tiempo de compilación (no es necesario agregarlo al CLR y no afectaría al BCL a menos que se incluyera la noción de métodos de miembros const) .

Sin embargo, creo que esto es poco probable: la segunda razón (sintaxis) sería bastante difícil de resolver, lo que haría que la primera razón (comprensión) fuera aún más problemática.

Stephen Cleary
fuente
1
Tiene razón en que CLR realmente no necesita preocuparse por esto, ya que uno puede usar modopty modreqen el nivel de metadatos para almacenar esa información (como C + / CLI ya lo hace - ver System.Runtime.CompilerServices.IsConst); y esto también podría extenderse trivialmente a los métodos const.
Pavel Minaev
Vale la pena, pero debe hacerse de forma segura. La constante Deep / Shallow que mencionas abre una lata completa de gusanos.
user1496062
En un c ++ donde las referencias son realmente manipuladas, puedo ver su argumento sobre tener dos tipos de const. Sin embargo, en C # (donde tienes que atravesar puertas traseras para usar "punteros") me parece lo suficientemente claro que hay un significado bien definido para los tipos de referencia (es decir, clases) y un significado bien definido aunque diferente para el valor tipos (es decir, primitivas, estructuras)
Asimilador
2
Los programadores que no pueden entender la const-ness no deberían programar en C ++.
Steve White
5

constsignifica "constante en tiempo de compilación" en C #, no "de solo lectura pero posiblemente mutable por otro código" como en C ++. Un análogo aproximado de C ++ consten C # es readonly, pero ese solo es aplicable a los campos. Aparte de eso, no existe una noción similar a C ++ de corrección constante en C #.

El fundamento es difícil de decir con certeza, porque hay muchas razones potenciales, pero mi suposición sería el deseo de mantener el lenguaje simple en primer lugar, y las ventajas inciertas de implementar este segundo.

Pavel Minaev
fuente
Entonces crees que MS considera como 'ruido' tener parámetros constantes en los métodos.
Incógnito
1
No estoy seguro de qué es "ruido"; prefiero llamarlo "relación costo / beneficio alta". Pero, por supuesto, necesitará que alguien del equipo de C # se lo diga con seguridad.
Pavel Minaev
Entonces, como lo entiendo, su argumento es que la constness se dejó fuera de C # para mantenerlo simple. Piénsalo.
Steve White
2

Esto es posible desde C # 7.2 (diciembre de 2017 con Visual Studio 2017 15.5).

¡Solo para estructuras y tipos básicos ! , no para miembros de clases.

Debe usar inpara enviar el argumento como entrada por referencia. Ver:

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

Por tu ejemplo:

....
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
}
....
se ha ido
fuente