¿Cómo creo una clase inmutable?

113

Estoy trabajando para crear una clase inmutable.
He marcado todas las propiedades como de solo lectura.

Tengo una lista de elementos de la clase.
Aunque si la propiedad es de solo lectura, la lista se puede modificar.

Exponer el IEnumerable de la lista lo hace inmutable.
Quería saber cuáles son las reglas básicas que se deben seguir para que una clase sea inmutable.

Biswanath
fuente
2
Le recomiendo encarecidamente que lea la serie de blogs de Eric Lippert sobre la inmutabilidad , en particular la entrada sobre "tipos de inmutabilidad" . Su comentario de que "Exponer el IEnumerable de la lista lo hace inmutable" me parece algo extraño. ¿Qué quieres decir con eso?
Jon Skeet
Lo que quise decir fue, en lugar de permitir el acceso a la lista (si el objeto tiene una lista de algunos otros objetos), permitir al usuario acceder a los miembros con IEnumerable. La lista parlante aquí como en un ejemplo específico, pero puede ser cualquier estructura de datos.
Biswanath
1
Los enlaces de Jon Skeet a la serie de blogs de Eric Lippert se rompieron cuando MSDN archivó estos blogs. Consulte "tipos de inmutabilidad" . El resto de la serie parece estar por debajo de diciembre de 2007 + enero de 2008 en el panel de la izquierda.
John Doe
Por favor, vea el blog de la serie Eric Lippert sobre cómo la gente suele confundir los términos atomicity, volatilityy immutability: Primera parte , Segunda Parte y la Parte Tres . Estos son de su blog personal y, creo, más amigables para los novatos que sus publicaciones de MSDN.
John Doe

Respuestas:

121

Creo que estás en el camino correcto

  • toda la información inyectada en la clase debe proporcionarse en el constructor
  • todas las propiedades deben ser solo captadores
  • si se pasa una colección (o matriz) al constructor, se debe copiar para evitar que la persona que llama la modifique más tarde
  • si va a devolver su colección, devuelva una copia o una versión de solo lectura (por ejemplo, usando ArrayList.ReadOnly o similar; puede combinar esto con el punto anterior y almacenar una copia de solo lectura para ser devuelta cuando las personas que llaman acceden a él), devuelven un enumerador o usan algún otro método / propiedad que permita el acceso de solo lectura a la colección
  • tenga en cuenta que aún puede tener la apariencia de una clase mutable si alguno de sus miembros es mutable; si este es el caso, debe copiar cualquier estado que desee conservar y evitar devolver objetos mutables completos, a menos que los copie antes de devolverlos a la persona que llama, otra opción es devolver solo las "secciones" inmutables del objeto mutable, gracias a @Brian Rasmussen por animarme a expandir este punto
Blair Conrad
fuente
¿Debo escribir una propiedad de búsqueda de contenedor, que le permita solo realizar la búsqueda? Y gracias por la respuesta.
Biswanath
5
Se debe copiar cualquier tipo de referencia mutable pasado como argumento al constructor. De lo contrario, la persona que llama seguirá teniendo una referencia al estado.
Brian Rasmussen
@Brian Rasmussen, tienes razón, pero incluso si está copiado, es posible que cualquier persona que llama acceda al objeto mutable, dependiendo de los accesorios proporcionados. En el caso de que se le pase un objeto mutable, es mejor que la clase devuelva siempre una copia diferente o secciones inmutables del objeto
Blair Conrad
@Blair: si tengo un diccionario en la clase, solo quiero usarlo para la búsqueda. ¿Es una propiedad de solo lectura que debe estar bien con una búsqueda?
Biswanath
@Biswanath - sí, con la salvedad de que si los valores en el diccionario son mutables, deberá protegerlos antes de regresar, si no quiere que muten
Blair Conrad
18

Para ser inmutable, todas sus propiedades y campos deben ser de solo lectura. Y los elementos de cualquier lista deberían ser inmutables.

Puede crear una propiedad de lista de solo lectura de la siguiente manera:

public class MyClass
{
    public MyClass(..., IList<MyType> items)
    {
        ...
        _myReadOnlyList = new List<MyType>(items).AsReadOnly();
    }

    public IList<MyType> MyReadOnlyList
    {
        get { return _myReadOnlyList; }
    }
    private IList<MyType> _myReadOnlyList

}
Joe
fuente
1
Creo que ImmutableList sería mejor.
Kevin Wong
use ImmutableList, también considere sellar la clase
kofifus
No estoy convencido de que ImmutableList es una buena opción para este caso de uso: basic rules to make a class (that contains a list) immutable. La semántica ImmutableList permite un uso de memoria más eficiente al agregar elementos a una copia de la lista, pero me parece que ese es un caso de uso más específico.
Joe
11

Además, tenga en cuenta que:

public readonly object[] MyObjects;

no es inmutable incluso si está marcado con una palabra clave de solo lectura. Aún puede cambiar referencias / valores de matriz individuales por medio de acceso de índice.

lubos hasko
fuente
5

Usa la ReadOnlyCollectionclase. Está situado en el System.Collections.ObjectModelespacio de nombres.

En cualquier cosa que devuelva su lista (o en el constructor), configure la lista como una colección de solo lectura.

using System.Collections.ObjectModel;

...

public MyClass(..., List<ListItemType> theList, ...)
{
    ...
    this.myListItemCollection= theList.AsReadOnly();
    ...
}

public ReadOnlyCollection<ListItemType> ListItems
{
     get { return this.myListItemCollection; }
}
mbillard
fuente
1

Otra opción sería utilizar un patrón de visitante en lugar de exponer ninguna colección interna.

Andrés
fuente