De modo que todos nos damos cuenta de los beneficios de los tipos inmutables, particularmente en escenarios multiproceso. (O al menos todos deberíamos darnos cuenta, ver por ejemplo System.String.)Diseño de construcción de clase inmutable
Sin embargo, lo que no he visto es mucha discusión para creando instancias inmutables, específicamente las pautas de diseño.
Por ejemplo, supongamos que queremos tener la siguiente clase inmutable:
class ParagraphStyle {
public TextAlignment Alignment {get;}
public float FirstLineHeadIndent {get;}
// ...
}
El enfoque más común que he visto es tener "pares" mutables/inmutables de tipos, por ejemplo los tipos mutables List<T> e inmutables ReadOnlyCollection<T> o los tipos mutables StringBuilder e inmutables String.
Para imitar este patrón existente requeriría la introducción de algún tipo de "mutable" ParagraphStyle
tipo que "duplicados" los miembros (para proporcionar fijadores), y luego proporcionar un constructor ParagraphStyle
que acepta el tipo mutable como un argumento
// Option 1:
class ParagraphStyleCreator {
public TextAlignment {get; set;}
public float FirstLineIndent {get; set;}
// ...
}
class ParagraphStyle {
// ... as before...
public ParagraphStyle (ParagraphStyleCreator value) {...}
}
// Usage:
var paragraphStyle = new ParagraphStyle (new ParagraphStyleCreator {
TextAlignment = ...,
FirstLineIndent = ...,
});
Por lo tanto, esto funciona, admite la finalización de código dentro del IDE, y hace las cosas razonablemente obvias sobre cómo construir cosas ... pero parece bastante duplicativo.
¿Hay una manera mejor?
Por ejemplo, C# tipos anónimos son inmutables, y permitir el uso de fijadores "normales" de propiedad de inicialización:
var anonymousTypeInstance = new {
Foo = "string",
Bar = 42;
};
anonymousTypeInstance.Foo = "another-value"; // compiler error
Por desgracia, el camino más cercano para duplicar esta semántica en C# es el uso de parámetros del constructor:
// Option 2:
class ParagraphStyle {
public ParagraphStyle (TextAlignment alignment, float firstLineHeadIndent,
/* ... */) {...}
}
Pero esto no se "escala" bien; si su tipo tiene, p. 15 propiedades, un constructor con 15 parámetros es cualquier cosa menos amigable, y proporcionar sobrecargas "útiles" para las 15 propiedades es una receta para una pesadilla. Estoy rechazando esto directamente.
Si tratamos de imitar los tipos anónimos, parece que podríamos usar "set-una vez" propiedades de tipo "inmutable", y por lo tanto dejar caer la "mutable" variante:
// Option 3:
class ParagraphStyle {
bool alignmentSet;
TextAlignment alignment;
public TextAlignment Alignment {
get {return alignment;}
set {
if (alignmentSet) throw new InvalidOperationException();
alignment = value;
alignmentSet = true;
}
}
// ...
}
El problema con esto es que no es obvio que las propiedades se puedan establecer solo una vez (el compilador ciertamente no se quejará), y la inicialización no es segura para subprocesos. Por lo tanto, resulta tentador agregar un método Commit()
para que el objeto sepa que el desarrollador finalizó la configuración de las propiedades (lo que provoca que todas las propiedades que no se hayan configurado previamente arrojen si se invoca su definidor), pero esto parece ser una manera de empeorar las cosas, no mejor.
¿Hay un mejor diseño que la división de clase mutable/inmutable? ¿O estoy condenado a lidiar con la duplicación de miembros?
Me doy cuenta de que esto puede ser difícil de creer, pero estoy trabajando con una base de código C# existente. Transmitir todo el elemento a F # no es una opción, e incluso si fuera una opción, la licencia MSR-SSLA que se publica en las bibliotecas F # no parece adecuada para mi uso, específicamente (ii) (c). – jonp
Esto parece una duplicación de: http://stackoverflow.com/questions/263585/immutable-object-pattern-in-c-what-do-you-think – jonp
Puede que no sea una respuesta, pero he tratado con requisitos similares en el pasado mediante la implementación de ISupportInitialize. Después de una llamada a EndInit() te vuelves inmutable, y throw cuando se llaman setters. No es seguro en tiempo de compilación, pero la documentación y el lanzamiento temprano y a menudo ayuda. – Will