2010-02-26 12 views
71

Estoy buscando el equivalente C# de Java final. ¿Existe?¿Pueden los parámetros ser constantes?

¿El C# tienen algo como lo siguiente:

public Foo(final int bar); 

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

Por ejemplo, tal vez tienen un método de largo que va a trabajar con x, y y z coordenadas de un objeto (enteros). 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. 
} 
+0

duplicados de http://stackoverflow.com/questions/2125591/net-parameter-passing-by-reference-vs-by-value (y tiene una gran respuesta de Eric Lippert, por cierto) – casperOne

+10

no creo su duplicado exacto. Esa pregunta es sobre la diferencia entre pasar por referencia y pasar por valor. Creo que Rosarch pudo haber usado un mal código de ejemplo para el punto que intentaba transmitir. –

+0

@Rosarch: De lo que estoy entendiendo por la palabra "final", es que puedes hacer una acción nomore 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 de JAVA. –

Respuesta

53

Lamentablemente no se puede hacer esto en C#.

La palabra clave const solo se puede usar para variables locales y campos.

La palabra clave readonly solo se puede utilizar en los campos.

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

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

+1

"Desafortunadamente"? ¿Cómo difería eso de la capacidad actual? –

+30

@ John Saunders Desafortunado porque Rosarch está buscando una característica que no existe. –

+0

@Corey: está buscando una función sin efecto. En su ejemplo, el parámetro sería constante sin el 'const'. –

0

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í.

¿Se puede crear un parámetro cuyo valor no se puede asignar dentro del método o cuyas propiedades no se pueden 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 utilizando la palabra clave ref. La situación que has delineado tampoco.

+0

Haría +1 excepto por el comentario sobre la inmutabilidad ... tener un tipo inmutable no lograría lo que está buscando. –

+0

Claro que lo haría, por defecto. Un tipo inmutable, no pasado por referencia, garantizará que el valor, fuera del método, no cambie. –

+1

Es cierto, pero su ejemplo se trata de cambiar el valor * dentro * del método. En su código de ejemplo, x es un Int32, que es inmutable, pero aún puede 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

8

Comenzaré con la parte 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.

+1

Lo rechacé por malinterpretar la pregunta; no se trata de cambiar el valor DESPUÉS de la llamada, sino de cambiarlo DENTRO de ella. –

+2

@silky - No entendí mal la pregunta. Estoy hablando dentro de la función también. Estoy diciendo que es una restricción extraña 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. –

+1

No estoy de acuerdo. Simplemente dando un comentario explicando el voto a la baja. –

5

Si a menudo se meten en problemas como éste, entonces debería considerar "aplicaciones húngaro". El buen tipo, a diferencia del bad kind. 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 aquellos dolor para cerrar de golpe el botón downvote ahora, por favor leer las opiniones de estas luminarias sobre el tema:

+0

Tenga en cuenta que no necesita una convención de nomenclatura para hacer cumplir esto, siempre se puede utilizar una herramienta de análisis después del hecho (no es genial, pero sin embargo). Creo que Gendarme (http: //www.mono- project.com/Gendarme) tiene una regla para ello, y probablemente StyleCop/FxCop también. –

9

He aquí una breve y dulce respuesta 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 toma sus parámetros y los pasa a un objeto que los expone como inmutables y luego usa ese objeto en su método?

Me doy cuenta de que este es probablemente un trabajo muy obvio que ya se ha considerado y el OP está tratando de evitar hacer esta pregunta, pero sentí que debería estar aquí ...

Buena suerte :-)

+0

Porque los miembros todavía se pueden modificar. Este código de C++ muestra que: 'int * p; * p = 0;'. Eso compilará y ejecutará hasta la falla de segmentación –

7

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#.

3

Crea una interfaz para tu clase que solo tiene accesadores 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, cree una envoltura de 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 su extraño que usted quiere hacer lo que preguntas que hacer con respecto a estructuras, como el escritor del método que debe esperar para saber que hay de nuevo 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.

+0

Eso es realmente muy inteligente. También quiero señalar que puede heredar múltiples interfaces al mismo tiempo, pero solo puede heredar una clase. –

+0

Esto es útil ... y de la misma manera alivia la molestia de que falta de C#. Gracias – Jimmyt1988

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 a tipo base T. Pero solo conversión explícita a envoltorio < T> clase. Que impondrá los errores del compilador si el desarrollador intenta establecer implícitamente el valor de ReadOnly < T> type. Como demostraré dos posibles usos a continuación.

USO 1 se requiere una definición de llamante 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; 
    } 

} 
0

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

Puede utilizar la palabra clave in en la firma del método. MSDN documentation.

La palabra clave in se debe agregar 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; 
} 

Mientras que el no está permitido siguiente: se mostrará

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

error siguiente al intentar modificar ya sea x o y ya que son marcado con in:

No se puede asignar a variab le 'largo' porque es una variable de sólo lectura

Marcado una discusión con in significa:

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

Cuestiones relacionadas