qué objetos método mutables inmutables inmutabilidad clases c# functional-programming design-patterns immutability

objetos - Patrón de objeto inmutable en C#: ¿qué piensas?



objetos inmutables c# (15)

¿Qué tal tener una clase abstracta ThingBase, con subclases MutableThing e ImmutableThing? ThingBase contendría todos los datos en una estructura protegida, proporcionando propiedades públicas de solo lectura para los campos y propiedad protegida de solo lectura para su estructura. También proporcionaría un método reemplazable AsImmutable que devolvería un InmutableThing.

MutableThing sombreará las propiedades con propiedades de lectura / escritura y proporcionará tanto un constructor predeterminado como un constructor que acepte una ThingBase.

Algo inmutable sería una clase sellada que anula a AsImmutable para que simplemente se devuelva a sí misma. También proporcionaría un constructor que acepta una ThingBase.

A lo largo de algunos proyectos he desarrollado un patrón para crear objetos inmutables (de solo lectura) y gráficos de objetos inmutables. Los objetos inmutables tienen el beneficio de ser 100% seguros para hilos y, por lo tanto, pueden reutilizarse a través de hilos. En mi trabajo, muy a menudo uso este patrón en las aplicaciones web para las configuraciones y otros objetos que cargo y almaceno en la memoria. Los objetos en caché siempre deben ser inmutables, ya que quiere garantizar que no se cambien inesperadamente.

Ahora, por supuesto, puede diseñar fácilmente objetos inmutables como en el siguiente ejemplo:

public class SampleElement { private Guid id; private string name; public SampleElement(Guid id, string name) { this.id = id; this.name = name; } public Guid Id { get { return id; } } public string Name { get { return name; } } }

Esto está bien para clases simples, pero para clases más complejas no me gusta el concepto de pasar todos los valores a través de un constructor. Tener decoradores en las propiedades es más deseable y su código para construir un nuevo objeto es más fácil de leer.

Entonces, ¿cómo se crean objetos inmutables con setters?

Bueno, en mi patrón, los objetos comienzan siendo completamente mutables hasta que los congelas con una sola llamada a un método. Una vez que un objeto está congelado, permanecerá inmutable para siempre: no se puede volver a convertir en un objeto mutable. Si necesita una versión mutable del objeto, simplemente clonarlo.

Ok, ahora a algún código. En los siguientes fragmentos de código he tratado de reducir el patrón a su forma más simple. IElement es la interfaz base que todos los objetos inmutables deben implementar en última instancia.

public interface IElement : ICloneable { bool IsReadOnly { get; } void MakeReadOnly(); }

La clase Element es la implementación predeterminada de la interfaz IElement:

public abstract class Element : IElement { private bool immutable; public bool IsReadOnly { get { return immutable; } } public virtual void MakeReadOnly() { immutable = true; } protected virtual void FailIfImmutable() { if (immutable) throw new ImmutableElementException(this); } ... }

Reorganicemos la clase SampleElement anterior para implementar el patrón de objetos inmutables:

public class SampleElement : Element { private Guid id; private string name; public SampleElement() {} public Guid Id { get { return id; } set { FailIfImmutable(); id = value; } } public string Name { get { return name; } set { FailIfImmutable(); name = value; } } }

Ahora puede cambiar la propiedad Id y la propiedad Name siempre que el objeto no se haya marcado como inmutable llamando al método MakeReadOnly (). Una vez que es inmutable, llamar a un setter arrojará una ImmutableElementException.

Nota final: el patrón completo es más complejo que los fragmentos de código que se muestran aquí. También contiene soporte para colecciones de objetos inmutables y gráficos de objetos completos de gráficos de objetos inmutables. El patrón completo le permite convertir un objeto entero en gráfico inmutable llamando al método MakeReadOnly () en el objeto más externo. Una vez que comienza a crear modelos de objetos más grandes utilizando este patrón, aumenta el riesgo de objetos con fugas. Un objeto con fugas es un objeto que no puede llamar al método FailIfImmutable () antes de realizar un cambio en el objeto. Para evaluar fugas, también he desarrollado una clase de detector de fugas genérico para usar en pruebas unitarias. Utiliza la reflexión para probar si todas las propiedades y métodos arrojan la ImmutableElementException en el estado inmutable. En otras palabras, TDD se usa aquí.

Me ha gustado mucho este patrón y encuentro grandes beneficios en él. Entonces, ¿qué me gustaría saber si alguno de ustedes está usando patrones similares? En caso afirmativo, ¿conoce algún buen recurso que lo documente? En esencia, busco mejoras potenciales y cualquier estándar que ya exista sobre este tema.


Aquí hay un nuevo video en Channel 9 donde Anders Hejlsberg de 36:30 en la entrevista comienza a hablar de inmutabilidad en C #. Le da un muy buen caso de uso para la inmutabilidad de las paletas y explica cómo esto es algo que actualmente se requiere que implemente. Fue música para mis oídos escucharlo decir que vale la pena pensar en un mejor soporte para crear gráficos de objetos inmutables en futuras versiones de C #

Experto a experto: Anders Hejlsberg - El futuro de C #


Después de mi incomodidad inicial por el hecho de que tuve que crear un nuevo System.Drawing.Point en cada modificación, hace unos años adopté totalmente el concepto. De hecho, ahora creo cada campo como readonly de forma predeterminada y solo lo modifico para que sea mutable si hay una razón de peso, que sorprendentemente rara vez.

Sin embargo, no me preocupan mucho los problemas de cross-threading (rara vez uso código donde esto es relevante). Lo encuentro mucho, mucho mejor debido a la expresividad semántica. La inmutabilidad es el epítome de una interfaz que es difícil de usar de forma incorrecta.


El (relativamente) nuevo paradigma de Diseño de Software llamado Domain Driven design, hace la distinción entre objetos de entidad y objetos de valor.

Los objetos de entidad se definen como cualquier cosa que se debe asignar a un objeto controlado por clave en un almacén de datos persistente, como un empleado, o un cliente, o una factura, etc., donde cambiar las propiedades del objeto implica que necesita guarde el cambio en un almacén de datos en algún lugar, y la existencia de múltiples instancias de una clase con la misma "clave" implica la necesidad de sincronizarlas, o coordine su persistencia al almacén de datos para que los cambios de una instancia no sobrescriban a los demás . Cambiar las propiedades de un objeto de entidad implica que está cambiando algo sobre el objeto, sin cambiar A QUÉ objeto está haciendo referencia ...

Los objetos de valor otoh, son objetos que se pueden considerar inmutables, cuya utilidad se define estrictamente por sus valores de propiedad, y para los que no se necesita coordinar de ninguna manera varias instancias ... como direcciones o números de teléfono o las ruedas en un automóvil, o las letras en un documento ... estas cosas están totalmente definidas por sus propiedades ... un objeto ''A'' en mayúsculas en un editor de texto se puede intercambiar de forma transparente con cualquier otro objeto ''A'' en mayúsculas en todo el documento, no necesita una clave para distinguirla de todas las otras ''A''. En este sentido, es inmutable, porque si la cambia a ''B'' (como cambiar el número de teléfono en un número de teléfono, no está cambiando los datos asociados con alguna entidad mutable, está cambiando de un valor a otro ... al igual que cuando cambia el valor de una cadena ...


Este es un problema importante, y me encanta ver un soporte más directo de framework / language para resolverlo. La solución que tiene requiere una gran cantidad de repetición. Puede ser simple automatizar algunos de los estándares usando la generación de código.

Generarías una clase parcial que contiene todas las propiedades freezables. Sería bastante simple hacer una plantilla T4 reutilizable para esto.

La plantilla tomaría esto como aporte:

  • espacio de nombres
  • nombre de la clase
  • lista de nombre de propiedad / tipo de tuplas

Y generaría un archivo C #, que contiene:

  • declaración de espacio de nombres
  • clase parcial
  • cada una de las propiedades, con los tipos correspondientes, un campo de respaldo, un getter y un setter que invoca el método FailIfFrozen

Las etiquetas AOP en las propiedades freezable también podrían funcionar, pero requeriría más dependencias, mientras que T4 está integrado en las versiones más nuevas de Visual Studio.

Otro escenario que se parece mucho a esto es la interfaz INotifyPropertyChanged . Es probable que las soluciones para ese problema sean aplicables a este problema.


Expandiendo el punto por @Cory Foy y @Charles Bretana donde hay una diferencia entre entidades y valores. Mientras que los objetos de valor siempre deben ser inmutables, realmente no creo que un objeto pueda congelarse o dejarse congelar arbitrariamente en la base de código. Tiene un mal olor, y me preocupa que pueda ser difícil rastrear exactamente dónde se ha congelado un objeto, y por qué estaba congelado, y el hecho de que entre llamadas a un objeto podría cambiar el estado de descongelado a congelado. .

Eso no quiere decir que a veces quiera dar una entidad (mutable) a algo y asegurarse de que no vaya a cambiarse.

Entonces, en lugar de congelar el objeto en sí, otra posibilidad es copiar la semántica de ReadOnlyCollection <T>

List<int> list = new List<int> { 1, 2, 3}; ReadOnlyCollection<int> readOnlyList = list.AsReadOnly();

Su objeto puede tomar una parte como mutable cuando lo necesite, y luego ser inmutable cuando lo desee.

Tenga en cuenta que ReadOnlyCollection <T> también implementa ICollection <T> que tiene un método Add( T item) en la interfaz. Sin embargo, también hay bool IsReadOnly { get; } bool IsReadOnly { get; } definido en la interfaz para que los consumidores puedan verificar antes de llamar a un método que lanzará una excepción.

La diferencia es que no puedes simplemente configurar IsReadOnly en falso. Una colección es o no solo de lectura, y nunca cambia durante el tiempo de vida de la colección.

Sería bueno a la hora de tener la const-corrección que C ++ te da en tiempo de compilación, pero eso comienza a tener su propio conjunto de problemas y me alegro de que C # no vaya allí.

ICloneable - Pensé que me referiría a lo siguiente:

No implemente ICloneable

No use ICloneable en API públicas

Brad Abrams - Pautas de diseño, código administrado y .NET Framework


Mi problema con este patrón es que no estás imponiendo ningún tipo de restricciones de compilación sobre la inmutabilidad. El codificador es responsable de asegurarse de que un objeto esté configurado como inmutable antes, por ejemplo, agregarlo a un caché u otra estructura no segura para subprocesos.

Es por eso que ampliaría este patrón de codificación con una restricción en tiempo de compilación en forma de una clase genérica, como esta:

public class Immutable<T> where T : IElement { private T value; public Immutable(T mutable) { this.value = (T) mutable.Clone(); this.value.MakeReadOnly(); } public T Value { get { return this.value; } } public static implicit operator Immutable<T>(T mutable) { return new Immutable<T>(mutable); } public static implicit operator T(Immutable<T> immutable) { return immutable.value; } }

Aquí hay una muestra de cómo usaría esto:

// All elements of this list are guaranteed to be immutable List<Immutable<SampleElement>> elements = new List<Immutable<SampleElement>>(); for (int i = 1; i < 10; i++) { SampleElement newElement = new SampleElement(); newElement.Id = Guid.NewGuid(); newElement.Name = "Sample" + i.ToString(); // The compiler will automatically convert to Immutable<SampleElement> for you // because of the implicit conversion operator elements.Add(newElement); } foreach (SampleElement element in elements) Console.Out.WriteLine(element.Name); elements[3].Value.Id = Guid.NewGuid(); // This will throw an ImmutableElementException


No me gusta la idea de poder cambiar un objeto de un estado mutable a un estado inmutable, ese tipo de parece derrotar el punto de diseño para mí. ¿Cuándo necesitas hacerlo? Solo los objetos que representan VALORES deben ser inmutables


Otra opción sería crear algún tipo de clase de Constructor.

Por ejemplo, en Java (y C # y muchos otros idiomas) String es inmutable. Si desea realizar varias operaciones para crear una Cadena, utilice un StringBuilder. Esto es mutable, y luego, una vez que terminas, te devuelve el objeto String final. Desde ese momento, es inmutable.

Podrías hacer algo similar para tus otras clases. Usted tiene su Elemento inmutable, y luego un ElementBuilder. Todo lo que el constructor haría es almacenar las opciones que establezca, luego, cuando lo finalice, construirá y devolverá el elemento inmutable.

Es un poco más de código, pero creo que es más limpio que tener setters en una clase que se supone que es inmutable.


Otras dos opciones para su problema particular que no se han discutido:

  1. Crea tu propio deserializador, uno que pueda llamar a un establecedor de propiedad privada. Si bien el esfuerzo en la construcción del deserializador al principio será mucho más, hace las cosas más limpias. El compilador evitará que intente llamar a los instaladores y el código en sus clases será más fácil de leer.

  2. Ponga un constructor en cada clase que tome un elemento XElement (u otro sabor del modelo de objetos XML) y se rellene desde él. Obviamente, a medida que aumenta el número de clases, esto rápidamente se vuelve menos deseable como solución.


Para información, el segundo enfoque se llama "inmutabilidad de paletas".

Eric Lippert tiene una serie de entradas de blog sobre la inmutabilidad que comienza here . Todavía me estoy familiarizando con el CTP (C # 4.0), pero parece interesante lo que los parámetros opcionales / nombrados (al .ctor) pueden hacer aquí (cuando se asignan a campos de solo lectura) ... [actualización: he blogueado en esto here ]

Para obtener información, probablemente no convertiría esos métodos en virtual ; probablemente no queremos que las subclases puedan hacerlo no congelable. Si desea que puedan agregar código adicional, sugeriría algo como:

[public|protected] void Freeze() { if(!frozen) { frozen = true; OnFrozen(); } } protected virtual void OnFrozen() {} // subclass can add code here.

Además, AOP (como PostSharp) podría ser una opción viable para agregar todos los controles ThrowIfFrozen ().

(me disculpo si cambié los nombres de terminología / método - SO no mantiene visible la publicación original al redactar las respuestas)


Puede usar argumentos nombrados opcionales junto con nulables para hacer un setter inmutable con muy poco texto estándar. Si realmente desea establecer una propiedad como nula, entonces puede tener más problemas.

class Foo{ ... public Foo Set ( double? majorBar=null , double? minorBar=null , int? cats=null , double? dogs=null) { return new Foo ( majorBar ?? MajorBar , minorBar ?? MinorBar , cats ?? Cats , dogs ?? Dogs); } public Foo ( double R , double r , int l , double e ) { .... } }

Lo usarías así

var f = new Foo(10,20,30,40); var g = f.Set(cat:99);


Solo un consejo para simplificar las propiedades del elemento: use las propiedades automáticas con private set y evite declarar explícitamente el campo de datos. p.ej

public class SampleElement { public SampleElement(Guid id, string name) { Id = id; Name = name; } public Guid Id { get; private set; } public string Name { get; private set; } }


System.String es un buen ejemplo de una clase inmutable con setters y métodos de mutación, solo que cada método de mutación devuelve una nueva instancia.


Todavía está lidiando con el estado, y por lo tanto todavía puede ser mordido si sus objetos se paralelizan antes de hacerse inmutables.

Una forma más funcional podría ser devolver una nueva instancia del objeto con cada setter. O cree un objeto mutable y páselo al constructor.