"Corrección constante" en C #

79

El punto de la corrección constante es poder proporcionar una vista de una instancia que el usuario no puede alterar ni eliminar. El compilador admite esto al señalar cuando se rompe la constness desde dentro de una función const, o se intenta usar una función no const de un objeto const. Entonces, sin copiar el enfoque const, ¿hay una metodología que pueda usar en C # que tenga los mismos fines?

Soy consciente de la inmutabilidad, pero eso realmente no se traslada a los objetos contenedores, por nombrar solo un ejemplo.

tenpn
fuente
Hay una buena discusión sobre posibles diseños de solo lectura en http://blogs.msdn.com/brada/archive/2004/02/04/67859.aspx
Asaf R
Entonces, ¿qué pasa con la implementación const_casten C #?
kerem

Respuestas:

64

También me he encontrado con este problema muchas veces y terminé usando interfaces.

Creo que es importante abandonar la idea de que C # es cualquier forma, o incluso una evolución de C ++. Son dos lenguajes diferentes que comparten casi la misma sintaxis.

Por lo general, expreso 'corrección constante' en C # definiendo una vista de solo lectura de una clase:

public interface IReadOnlyCustomer
{
    String Name { get; }
    int Age { get; }
}

public class Customer : IReadOnlyCustomer
{
    private string m_name;
    private int m_age;

    public string Name
    {
        get { return m_name; }
        set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        set { m_age = value; }
    }
}
Trampa
fuente
12
Claro, pero ¿qué pasa si uno de sus campos es una lista o un tipo enriquecido? Su solución se complica muy rápidamente.
Matt Cruikshank
12
@Trap: ese es el punto de la pregunta. Devolver una colección interna es una mala práctica porque el mundo exterior podría modificar tus internas, en C ++ eso se resuelve con el uso de const: proporcionas una vista constante de la colección (no puedes agregar / quitar elementos, solo ofrece una vista constante de su elementos) y, como tal, es seguro (y un idioma común). No es necesario trabajar en interfaces u otros trucos para evitar que el código externo cambie sus componentes internos.
David Rodríguez - dribeas
9
@Trap, estoy de acuerdo en que el mecanismo es una ayuda para el desarrollo que incluye la detección de errores de los desarrolladores, con experiencia o sin ella. No estoy de acuerdo con que tener que escribir interfaces de solo lectura sea una mejor solución por varios motivos. El primer punto es que siempre que está escribiendo una clase en C ++ está definiendo implícitamente la interfaz constante: el subconjunto de miembros declarados const, sin el costo adicional de definir una interfaz separada y requerir el envío en tiempo de ejecución. Es decir, el lenguaje proporciona una forma sencilla de implementar interfaces const en tiempo de compilación.
David Rodríguez - dribeas
10
Es importante tener en cuenta que el const-ness es parte del diseño y declara la intención con tanta claridad como escribir una interfaz constante externa. Además, proporcionar const-interfaces puede ser una tarea compleja si se tiene que abordar manualmente, y puede requerir escribir casi tantas interfaces como clases estén presentes en el tipo compuesto. Eso nos lleva a tu última frase: 'arruinar las cosas por olvidar agregar la constante'. Es más fácil acostumbrarse a agregar el en consttodas partes (el costo de desarrollo es pequeño) que escribir interfaces de solo lectura para cada clase.
David Rodríguez - dribeas
9
Una de las razones por las que realmente me gustó C # cuando salió fue que no descartaba todas las características pragmáticas de C ++ como lo hacía Java. Java intentó hacer todo con interfaces, y fue un desastre. La prueba está en el pudín. Con el tiempo, muchas características se han agregado "nuevamente" a Java que originalmente fueron denunciadas como herejías. El diseño basado en interfaces tiene su lugar, pero cuando se lleva a los extremos puede complicar innecesariamente la arquitectura de un programa. Termina pasando más tiempo escribiendo código repetitivo y menos tiempo haciendo cosas útiles.
kgriffs
29

Para obtener el beneficio de la locura constante (o pureza en términos de programación funcional), necesitará diseñar sus clases de manera que sean inmutables, tal como lo es la clase String de c #.

Este enfoque es mucho mejor que marcar un objeto como de solo lectura, ya que con las clases inmutables puede pasar datos fácilmente en entornos multitarea.

Sam
fuente
8
pero la inmutabilidad no se escala realmente a objetos complejos, ¿o no?
tenpn
8
Yo diría que si su objeto fuera tan complejo que la inmutabilidad fuera imposible, tendría un buen candidato para la refactorización.
Jim Burger
2
Creo que este es el mejor del grupo. Los objetos inmutables se utilizan con poca frecuencia.
3
No solo eso, sino que hay casos en los que desea que los objetos cambien (¡los objetos cambian!) Pero aún ofrecen una vista de solo lectura en la mayoría de los casos. Los objetos inmutables implican que siempre que necesite hacer un cambio (el cambio ocurre) deberá crear un nuevo objeto con todos los mismos datos además del cambio. Considere una escuela que tiene aulas, estudiantes ... ¿desea crear una nueva escuela cada vez que pasa la fecha de nacimiento de un estudiante y cambia su edad? ¿O simplemente puede cambiar la edad a nivel de estudiante, el estudiante a nivel de salón, tal vez el salón a nivel de escuela?
David Rodríguez - dribeas
8
@tenpn: La inmutabilidad en realidad se escala increíblemente bien, cuando se hace bien. Una cosa que realmente ayuda es el uso de Builderclases para grandes tipos inmutables (Java y .NET definen la StringBuilderclase, que es solo un ejemplo).
Konrad Rudolph
24

Solo quería señalarle que muchos de los contenedores System.Collections.Generics tienen un método AsReadOnly que le devolverá una colección inmutable.

Rick Minerich
fuente
4
Esto todavía tiene el problema de que los Ts son mutables incluso si ReadOnlyCollection <T> no lo es.
arynaq
4

C # no tiene esa característica. Puede pasar argumentos por valor o por referencia. La referencia en sí es inmutable a menos que especifique el modificador ref . Pero los datos referenciados no son inmutables. Por lo tanto, debe tener cuidado si desea evitar los efectos secundarios.

MSDN:

Pasar parámetros

aku
fuente
bueno, supongo que a eso se reduce la pregunta: ¿cuál es la mejor manera de evitar los efectos secundarios de no tener una estructura constante?
tenpn
Desafortunadamente, solo los tipos inmutables pueden ayudarlo. Puede echar un vistazo a Spec # - hay algunas comprobaciones interesantes del tiempo de compilación.
aku
3

Las interfaces son la respuesta, y en realidad son más poderosas que "const" en C ++. const es una solución única para todos los problemas donde "const" se define como "no establece miembros o llama a algo que establece miembros". Esa es una buena forma abreviada de const-ness en muchos escenarios, pero no en todos. Por ejemplo, considere una función que calcula un valor basado en algunos miembros pero que también almacena en caché los resultados. En C ++, eso se considera no constante, aunque desde la perspectiva del usuario es esencialmente constante.

Las interfaces le brindan más flexibilidad para definir el subconjunto específico de capacidades que desea brindar desde su clase. ¿Quieres constancia? Simplemente proporcione una interfaz sin métodos mutantes. ¿Quiere permitir configurar algunas cosas pero no otras? Proporcione una interfaz solo con esos métodos.

munificente
fuente
15
No es 100% correcto. Los métodos const de C ++ pueden mutar miembros marcados como mutable.
Constantin
3
Lo suficientemente justo. Y const-casting te permite deshacerte de la const-ness. Ambos implican que incluso los diseñadores de C ++ se dieron cuenta de que una talla única para todos es realmente una talla única.
generoso
8
La ventaja de C ++ es que tiene constante en la mayoría de los casos y puede implementar interfaces para otros. Ahora, además de const, C ++ tiene la palabra clave mutable para aplicar a atributos como cachés de datos o mecanismos de bloqueo (mutexes o similares). Const is not 'no cambiará ningún atributo interno) sino que no cambiará el estado del objeto tal como se percibe desde el exterior. Es decir, cualquier uso del objeto antes y después de llamar a un método const producirá el mismo resultado.
David Rodríguez - dribeas
9
desechar la constante para mutar comething en C ++ es un comportamiento indefenso (demonios nasales). const_cast es para interactuar con código heredado que, si bien es lógicamente const, no está marcado como const.
jk.
3

De acuerdo con algunos de los otros, miran el uso de campos de solo lectura que inicializa en el constructor para crear objetos inmutables.

    public class Customer
    {
    private readonly string m_name;
    private readonly int m_age;

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
    }

    public int Age
    {
        get { return m_age; }
    }
  }

Alternativamente, también podría agregar un alcance de acceso en las propiedades, es decir, ¿public get y protected set?

    public class Customer
    {
    private string m_name;
    private int m_age;

    protected Customer() 
    {}

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
        protected set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        protected set { m_age = value; }
    }
  }
Stuart McConnell
fuente
3
Estos son enfoques diferentes que realmente no se ajustan a todo el problema. Con el nivel de control de acceso, solo permite derivar clases de cambios, en muchos casos esto no modelará el mundo real de manera apropiada. Un maestro no se deriva de un registro de estudiante, pero puede querer cambiar las calificaciones del estudiante, aunque el estudiante no puede cambiar las calificaciones pero puede leerlas ... solo por nombrar un ejemplo simple.
David Rodríguez - dribeas
2
  • La constante palabra clave se puede utilizar para compilar constantes de tiempo como tipos primitivos y cadenas
  • La palabra clave readonly se puede utilizar para constantes de tiempo de ejecución como tipos de referencia

El problema con readonly es que solo permite que la referencia (puntero) sea constante. La cosa referenciada (señalada) todavía se puede modificar. Esta es la parte complicada, pero no hay forma de evitarlo. Implementar objetos constantes significa hacer que no expongan ningún método o propiedad mutable, pero esto es incómodo.

Consulte también C # efectivo: 50 formas específicas de mejorar su C # (elemento 2: prefiera solo lectura a const.)

Thomas Bratt
fuente