variable - seal c#
¿Pueden los parámetros ser constantes? (9)
Estoy buscando el equivalente de C # de la final
de Java. ¿Existe?
C tiene algo como lo siguiente:
public Foo(final int bar);
En el ejemplo anterior, la bar
es una variable de solo lectura y no puede ser modificada por Foo()
. ¿Hay alguna manera de hacer esto en C #?
Por ejemplo, tal vez tengo un método largo que trabajará con las coordenadas x
, y
y z
de algún objeto (ints). Quiero estar absolutamente seguro de que la función no altera estos valores de ninguna manera, con lo que corrompe los datos. Por lo tanto, me gustaría declararlos de manera 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.
}
Aquí hay una respuesta breve y dulce que probablemente obtendrá muchos votos abajo. No he leído todas las publicaciones y comentarios, así que, por favor, perdónenme si esto se sugirió 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 probablemente este sea un trabajo muy obvio que ya se ha considerado y el OP intenta evitar hacer esto al hacer esta pregunta, pero sentí que debería estar aquí, no obstante ...
Buena suerte :-)
Comenzaré con la porción int
. int
es un tipo de valor, y en .Net significa que realmente está tratando con una copia. Es una restricción de diseño realmente extraña decir un método "Puedes tener una copia de este valor. Es tu copia, no la mía; nunca más volveré a verla. 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 de manera segura al método. Si el método necesita el original, déjelo al implementador hacer una copia para guardarlo. O le da al método el valor o no le da el valor al método. No te vayas del todo insulso.
Pasemos a los tipos de referencia. Ahora se pone un poco confuso. ¿Quiere decir una referencia constante, donde la referencia en sí misma no puede ser cambiada, o un objeto completamente bloqueado e inmutable? Si el primero, las referencias en .Net por defecto pasan por valor. Es decir, obtienes una copia de la referencia. Entonces, esencialmente tenemos la misma situación que para los tipos de valores. Si el implementador necesitará la referencia original, pueden guardarla ellos mismos.
Eso simplemente nos deja con un objeto constante (bloqueado / inmutable). Esto puede parecer bien desde una perspectiva de tiempo de ejecución, pero ¿cómo es el compilador para hacer cumplir? Dado que las propiedades y los métodos pueden tener todos los efectos secundarios, esencialmente estarás limitado al acceso de campo de solo lectura. Tal objeto no es probable que sea muy interesante.
Cree una interfaz para su clase que solo tenga accesos de propiedad de solo lectura. Luego, haz que tu parámetro sea de esa interfaz en lugar de la clase en sí misma. 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 las estructuras, crea una envoltura const genérica.
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
}
Vale la pena señalar, sin embargo, que es extraño que quieras hacer lo que estás pidiendo con respecto a las estructuras, ya que el escritor del método que debes esperar saber 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 que se comporte de acuerdo con el método que está escribiendo. Llega un punto en que la vigilancia y el código limpio son la clave, más que imponer la const y otras reglas similares.
Desafortunadamente no puedes hacer esto en C #.
La palabra clave const
solo se puede usar para variables locales y campos.
La palabra clave de solo readonly
solo se puede usar en los campos.
NOTA: El lenguaje Java también admite tener parámetros finales para un método. Esta funcionalidad no existe en C #.
Esto ahora es posible en C # versión 7.2:
Puede usar la palabra clave in en la firma del método. Documentación de MSDN .
La palabra clave in
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 y
ya que están marcados con:
No se puede asignar a la variable ''in long'' porque es una variable de solo lectura
Marcar una discusión con in
significa:
Este método no modifica el valor del argumento utilizado como este parámetro.
La respuesta: C # no tiene la funcionalidad const como C ++.
Estoy de acuerdo con Bennett Dill.
La palabra clave const es muy útil. En el ejemplo, usaste un int y las personas no entienden tu punto. Pero, ¿por qué si el parámetro es un objeto enorme y complejo del usuario que no puede modificarse 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 el motivo aquí] que no importa para ese método. La palabra clave Const es muy poderosa y realmente la extraño en C #.
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 la conversión implícita al tipo base T. Pero solo la conversión explícita a la clase de contenedor <T>. Que impondrá los errores del compilador si el desarrollador prueba el conjunto implícito en el valor del tipo ReadOnly <T>. Como demostraré dos posibles usos a continuación.
El USO 1 requirió la definición de la persona que llama para cambiar. Este uso solo se usará para probar la corrección de su código de funciones "TestCalled". Mientras esté en el nivel / versiones de lanzamiento, no debería usarlo. Dado que en operaciones matemáticas a gran escala puede sobrepasarse en las conversiones, y hacer que su código sea lento. No lo usaría, pero solo para fines de demostración lo publiqué.
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 utiliza este enfoque, pero requiere un poco más de codificación de las definiciones de TestCaller2 utilizando el condicionamiento del compilador. Puede observar errores del compilador en la configuración de depuración, mientras que en la configuración del lanzamiento, todos los códigos en la función TestCalled2 se compilarán 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;
}
}
Si a menudo tiene problemas como este, debería considerar "aplicaciones húngaras". El buen tipo, a diferencia del mal tipo . Si bien esto normalmente no intenta expresar la constancia de un parámetro de método (que es demasiado inusual), no hay nada que le impida hacer una "c" adicional antes del nombre del identificador.
Para todos los que desean oprimir el botón de abajo, lea las opiniones de estas luminarias sobre el tema:
Si struct se pasa a un método, a menos que se pase por ref, no se cambiará por el método al que se transfiere. Entonces en ese sentido, sí.
¿Puedes 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é sucederá cuando el método salga.
La única vez que se alterarán los datos externos es si pasa una clase y cambia una de sus propiedades, o si pasa un valor usando la palabra clave ref. La situación que has delineado tampoco.