¿Pueden los parámetros ser constantes?

83

Estoy buscando el equivalente en C # de Java final. ¿Existe?

¿C # tiene algo parecido a lo siguiente:

public Foo(final int bar);

En el ejemplo anterior, bar es una variable de solo lectura y no se puede cambiar por Foo(). ¿Hay alguna forma de hacer esto en C #?

Por ejemplo, tal vez tengo un método mucho tiempo que va a trabajar con x, yy zlas coordenadas de un objeto (enteros). Quiero estar absolutamente seguro de que la función no altera estos valores de ninguna manera, corrompiendo así los datos. Por lo tanto, me gustaría declararlos de solo lectura.

public Foo(int x, int y, int z) {
     // do stuff
     x++; // oops. This corrupts the data. Can this be caught at compile time?
     // do more stuff, assuming x is still the original value.
}
Nick Heiner
fuente
Duplicado de stackoverflow.com/questions/2125591/… (y tiene una gran respuesta de Eric Lippert, por cierto)
casperOne
12
No creo que sea un duplicado exacto. Esa pregunta trata sobre la diferencia entre pasar por referencia y pasar por valor. Creo que Rosarch pudo haber usado un código de ejemplo incorrecto para el punto que estaba tratando de transmitir.
Corey Sunwold
@Rosarch: De lo que entiendo por la palabra "final", es que no puedes hacer más acciones con el objeto, sea lo que sea. Entiendo que "final" aplicado a una clase sería la equivalencia de "sellado" en C #. Pero, ¿cuál es el beneficio o el uso real de esta palabra clave "final" de todos modos? Lo he visto casi en cualquier lugar algún día en un código fuente JAVA.
Will Marcouiller
3
@Will Marcouiller Con la palabra clave final, no es el objeto que no puede cambiar, porque puede usar el método que cambia en las partes internas del objeto, es la referencia al objeto que no puede cambiar. En el caso de una situación de paso por valor, como el ejemplo que se dio, evitaría que x ++ sea una operación válida porque el valor estaría cambiando. La ventaja sería que obtiene una verificación de cordura del tiempo de compilación para asegurarse de que nada establece el valor para que sea diferente. Sin embargo, en mi experiencia, nunca he necesitado tal característica.
Corey Sunwold
+1 @Corey Sunwold: Gracias por esta precisión. Por ahora, solo conozco similitudes entre C # y JAVA, sabiendo que C # "heredó" de JAVA en sus conceptos. Dicho esto, me anima a aprender más sobre JAVA, ya que sé que está bien difundido en todo el mundo.
Will Marcouiller

Respuestas:

60

Desafortunadamente, no puede hacer esto en C #.

La constpalabra clave solo se puede utilizar para campos y variables locales.

La readonlypalabra clave solo se puede utilizar en campos.

NOTA: El lenguaje Java también admite tener parámetros finales para un método. Esta funcionalidad no existe en C #.

desde http://www.25hoursaday.com/CsharpVsJava.html

EDITAR (2019/08/13): Estoy lanzando esto para mayor visibilidad, ya que se acepta y es el más alto de la lista. Ahora es posible con inparámetros. Vea la respuesta debajo de esta para más detalles.

Corey Sunwold
fuente
1
"Desafortunadamente"? ¿En qué se diferenciaría eso de la capacidad actual?
John Saunders
31
@John Saunders Desafortunado porque Rosarch está buscando una función que no existe.
Corey Sunwold
7
@John: No es constante dentro del método. Ese es el problema.
Mediodía Silk
6
Su única constante fuera del alcance de este método. Ignorando si se pasó por referencia o por valor, Rosarch no quiere que el parámetro cambie dentro del alcance del método. Esa es la diferencia clave.
Corey Sunwold
5
@silky: leyendo su publicación, eso no es lo que está pidiendo. Pide asegurarse de que el método no cambie los parámetros reales.
John Saunders
30

Esto ahora es posible en C # versión 7.2:

Puede utilizar la inpalabra clave en la firma del método. Documentación de MSDN .

los in palabra clave debe agregarse antes de especificar el argumento de un método.

Ejemplo, un método válido en C # 7.2:

public long Add(in long x, in long y)
{
    return x + y;
}

Si bien lo siguiente no está permitido:

public long Add(in long x, in long y)
{
    x = 10; // It is not allowed to modify an in-argument.
    return x + y;
}

Se mostrará el siguiente error al intentar modificar cualquiera xo yya que están marcados con in:

No se puede asignar a la variable 'en largo' porque es una variable de solo lectura

Marcar un argumento con inmedios:

Este método no modifica el valor del argumento utilizado como este parámetro.

Max
fuente
3
Por supuesto, no es útil para tipos de referencia. El uso de inpalabras clave para parámetros con esos tipos, solo evita asignarles. ¡No evite modificar sus campos o propiedades accesibles! Estoy en lo cierto?
ABS
5
Tenga en cuenta que solo funciona bien con estructuras / valores y NO con clases, donde puede modificar la instancia que es por referencia, por lo que es mucho peor. Y no recibe ninguna advertencia. Úselo con precaución. blog.dunnhq.com/index.php/2017/12/15/…
jeromej
8

Aquí hay una respuesta corta y dulce que probablemente obtendrá muchos votos negativos. No he leído todas las publicaciones y comentarios, así que perdóneme si esto ha sido sugerido anteriormente.

¿Por qué no tomar sus parámetros y pasarlos a un objeto que los expone como inmutables y luego usar ese objeto en su método?

Me doy cuenta de que esta es probablemente una solución muy obvia que ya se ha considerado y el OP está tratando de evitar hacer esto haciendo esta pregunta, pero sentí que debería estar aquí de todos modos ...

Buena suerte :-)

Eneldo Bennett
fuente
1
Porque los miembros todavía se pueden modificar. ++ Este código C muestra que: int* p; *p = 0;. Eso se compilará y se ejecutará hasta la falla de segmentación.
Cole Johnson
Voté en contra porque no es una solución al problema. También puede guardar los argumentos en el encabezado de la función y compararlos al final, y lanzar una excepción al cambio. Lo guardaré en mi bolsillo trasero si alguna vez hay un concurso de la peor solución :)
Rick O'Shea
8

La respuesta: C # no tiene la funcionalidad constante como C ++.

Estoy de acuerdo con Bennett Dill.

La palabra clave const es muy útil. En el ejemplo, usaste un int y la gente no entiende tu punto. Pero, ¿por qué si su parámetro es un objeto enorme y complejo de usuario que no se puede cambiar dentro de esa función? Ese es el uso de la palabra clave const: el parámetro no puede cambiar dentro de ese método porque [cualquiera sea la razón aquí] eso no importa para ese método. La palabra clave Const es muy poderosa y realmente la extraño en C #.

Michel Vaz Ramos
fuente
7

Empezaré por la intporción. intes un tipo de valor, y en .Net eso significa que realmente se trata de una copia. Es una restricción de diseño realmente extraña decirle a un método "Puedes tener una copia de este valor. Es tu copia, no la mía; nunca la volveré a ver. Pero no puedes cambiar la copia". Está implícito en la llamada al método que copiar este valor está bien, de lo contrario no podríamos haber llamado al método de forma segura. Si el método necesita el original, deje que el implementador haga una copia para guardarlo. O dé el valor al método o no dé el valor al método. No te vuelvas loco en el medio.

Pasemos a los tipos de referencia. Ahora se vuelve un poco confuso. ¿Te refieres a una referencia constante, donde la referencia en sí no se puede cambiar, o un objeto completamente bloqueado e inmutable? Si es el primero, las referencias en .Net por defecto se pasan por valor. Es decir, obtienes una copia de la referencia. Entonces, tenemos esencialmente la misma situación que para los tipos de valor. Si el implementador necesita la referencia original, puede conservarla él mismo.

Eso solo nos deja con un objeto constante (bloqueado / inmutable). Esto puede parecer correcto desde la perspectiva del tiempo de ejecución, pero ¿cómo va a hacer cumplir el compilador? Dado que las propiedades y los métodos pueden tener efectos secundarios, esencialmente estaría limitado al acceso de campo de solo lectura. No es probable que un objeto así sea muy interesante.

Joel Coehoorn
fuente
1
Te voté en contra por malinterpretar la pregunta; no se trata de cambiar el valor DESPUÉS de la llamada, sino de cambiarlo DENTRO.
Mediodía Silk
2
@silky - No entendí mal la pregunta. También estoy hablando de la función. Estoy diciendo que es una restricción extraña para enviar a una función, porque realmente no detiene nada. Si alguien olvida el parámetro original modificado, es muy probable que olvide una copia modificada.
Joel Coehoorn
1
Estoy en desacuerdo. Simplemente proporcionando un comentario que explique el voto negativo.
Seda del mediodía
DateTime tiene acceso de campo de solo lectura, pero sigue siendo interesante. Tiene muchos métodos que devuelven nuevas instancias. Muchos lenguajes funcionales evitan métodos con efectos secundarios y prefieren tipos inmutables, en mi opinión esto hace que el código sea más fácil de seguir.
Devin Garner
DateTime también sigue siendo un tipo de valor, no un tipo de referencia.
Joel Coehoorn
5

Cree una interfaz para su clase que solo tenga accesores de propiedad de solo lectura. Luego, haga que su parámetro sea de esa interfaz en lugar de la clase en sí. Ejemplo:

public interface IExample
{
    int ReadonlyValue { get; }
}

public class Example : IExample
{
    public int Value { get; set; }
    public int ReadonlyValue { get { return this.Value; } }
}


public void Foo(IExample example)
{
    // Now only has access to the get accessors for the properties
}

Para estructuras, cree un contenedor const genérico.

public struct Const<T>
{
    public T Value { get; private set; }

    public Const(T value)
    {
        this.Value = value;
    }
}

public Foo(Const<float> X, Const<float> Y, Const<float> Z)
{
// Can only read these values
}

Sin embargo, vale la pena señalar que es extraño que quieras hacer lo que estás pidiendo con respecto a las estructuras, como autor del método, debes esperar saber qué está sucediendo en ese método. No afectará los valores pasados ​​para modificarlos dentro del método, por lo que su única preocupación es asegurarse de comportarse en el método que está escribiendo. Llega un punto en el que la vigilancia y el código limpio son la clave, por encima de la aplicación de reglas y otras reglas similares.

Steve Lillis
fuente
Eso es bastante inteligente. También quiero señalar que puede heredar varias interfaces al mismo tiempo, pero solo puede heredar una clase.
Natalie Adams
Esto es útil ... y de la misma manera alivia la molestia de que falta en c #. Gracias
Jimmyt1988
3

Si a menudo tiene problemas como este, entonces debería considerar "aplicaciones húngaras". La buena, en contraposición a la mala . Si bien esto normalmente no intenta expresar la constancia de un parámetro de método (eso es demasiado inusual), ciertamente no hay nada que le impida agregar una "c" adicional antes del nombre del identificador.

Para todos aquellos que ansían presionar el botón de voto negativo ahora, lean las opiniones de estas luminarias sobre el tema:

Hans Passant
fuente
Tenga en cuenta que no necesita una convención de nomenclatura para hacer cumplir esto; siempre puedes hacerlo usando una herramienta de análisis después del hecho (no es genial, pero no obstante). Creo que Gendarme ( mono-project.com/Gendarme ) tiene una regla para ello, y probablemente StyleCop / FxCop también.
Mediodía Silk
Fácilmente el peor intento (fallido) de solución. Entonces, ahora su parámetro no solo se puede escribir, sino que lo está preparando para fallar al mentirle sobre que no ha cambiado.
Rick O'Shea
Dos usuarios de SO doloridos hasta ahora, podrían haber sido peores.
Hans Passant
2

Sé que esto podría ser un poco tarde. Pero para las personas que todavía están buscando otras formas de hacerlo, podría haber otra forma de evitar esta limitación del estándar C #. Podríamos escribir la clase contenedora ReadOnly <T> donde T: struct. Con conversión implícita al tipo base T. Pero solo conversión explícita a la clase contenedora <T>. Lo que hará cumplir los errores del compilador si el desarrollador intenta establecer implícitamente un valor de tipo ReadOnly <T>. Como demostraré a continuación dos posibles usos.

USAGE 1 requería cambiar la definición de la persona que llama. Este uso solo tendrá uso para probar la exactitud de su código de funciones "TestCalled". Mientras esté en el nivel de lanzamiento / compilaciones, no debe usarlo. Dado que en las operaciones matemáticas a gran escala pueden ser excesivas en las conversiones y hacer que su código sea lento. No lo usaría, pero solo lo he publicado para fines de demostración.

USAGE 2 que sugeriría, tiene el uso de Debug vs Release demostrado en la función TestCalled2. Además, no habría conversión en la función TestCaller cuando se usa este enfoque, pero requiere un poco más de codificación de las definiciones de TestCaller2 usando el acondicionamiento del compilador. Puede notar errores del compilador en la configuración de depuración, mientras que en la configuración de lanzamiento todo el código en la función TestCalled2 se compilará correctamente.

using System;
using System.Collections.Generic;

public class ReadOnly<VT>
  where VT : struct
{
  private VT value;
  public ReadOnly(VT value)
  {
    this.value = value;
  }
  public static implicit operator VT(ReadOnly<VT> rvalue)
  {
    return rvalue.value;
  }
  public static explicit operator ReadOnly<VT>(VT rvalue)
  {
    return new ReadOnly<VT>(rvalue);
  }
}

public static class TestFunctionArguments
{
  static void TestCall()
  {
    long a = 0;

    // CALL USAGE 1.
    // explicite cast must exist in call to this function
    // and clearly states it will be readonly inside TestCalled function.
    TestCalled(a);                  // invalid call, we must explicit cast to ReadOnly<T>
    TestCalled((ReadOnly<long>)a);  // explicit cast to ReadOnly<T>

    // CALL USAGE 2.
    // Debug vs Release call has no difference - no compiler errors
    TestCalled2(a);

  }

  // ARG USAGE 1.
  static void TestCalled(ReadOnly<long> a)
  {
    // invalid operations, compiler errors
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  }


  // ARG USAGE 2.
#if DEBUG
  static void TestCalled2(long a2_writable)
  {
    ReadOnly<long> a = new ReadOnly<long>(a2_writable);
#else
  static void TestCalled2(long a)
  {
#endif
    // invalid operations
    // compiler will have errors in debug configuration
    // compiler will compile in release
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    // compiler will compile in both, debug and release configurations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  }

}
Solar
fuente
Sería mejor si ReadOnly fuera una estructura y VT podría ser una estructura y una clase, de esa manera si VT es una estructura, el valor se pasará por valor y si es una clase, el valor se pasará por referencia y si quieres que sea como Java's final, el operador ReadOnly debería estar implícito.
Tomer Wolberg
0

Si la estructura se pasa a un método, a menos que sea pasada por ref, no será cambiada por el método al que se pasa. Entonces, en ese sentido, sí.

¿Puede crear un parámetro cuyo valor no se pueda asignar dentro del método o cuyas propiedades no se puedan establecer dentro del método? No. No puede evitar que se asigne el valor dentro del método, pero puede evitar que se establezcan sus propiedades creando un tipo inmutable.

La pregunta no es si el parámetro o sus propiedades se pueden asignar dentro del método. La pregunta es qué será cuando salga el método.

La única vez que se modificará cualquier dato externo es si pasa una clase y cambia una de sus propiedades, o si pasa un valor utilizando la palabra clave ref. La situación que ha delineado no lo hace.

David Morton
fuente
Haría +1 excepto por el comentario sobre la inmutabilidad ... tener un tipo inmutable no lograría lo que está buscando.
Adam Robinson
Seguro que lo haría, por defecto. Un tipo inmutable, no pasado por referencia, asegurará que el valor, fuera del método, no cambie.
David Morton
1
Es cierto, pero su ejemplo trata de cambiar el valor dentro del método. En su código de ejemplo, x es un Int32, que es inmutable, pero todavía se le permite escribir x++. Es decir, está tratando de evitar la reasignación del parámetro, que es ortogonal a la mutabilidad del valor del parámetro.
itowlson
1
@silky Es un poco extraño rechazar tres respuestas que no entendieron la misma pregunta. Quizás no sea culpa del lector que la pregunta haya sido mal entendida. Sabes, cuando un hombre se divorcia de su tercera esposa, normalmente no es culpa de las esposas.
David Morton
2
@David: Es el propósito del sistema de votación. Ordenar por relevancia. No veo por qué debería preocuparte que te corrijan.
Seda del mediodía