studio - Colección de encapsulado en C#
windows forms c# visual studio 2017 (4)
Desde 3.0 C # tiene una gran sintaxis de azúcar como propiedades automáticas que simplifican mucho la implementación del principio de encapsulación. Esto es bueno si lo usa con valores atómicos, por lo que puede reemplazar un patrón de encapsulación como este:
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
con una sola línea:
public string FirstName { get; set; }
Me gusta mucho esta gran característica, ya que ahorra mucho tiempo a los desarrolladores.
Pero las cosas no son tan buenas cuando creas una propiedad que apunta a una colección. Por lo general, veo las propiedades de colección implementadas de una de dos maneras.
1) Sin propiedades automáticas en absoluto para poder utilizar el inicializador de campo:
private List<string> _names = new List<string>();
public List<string> Names
{
get { return _names; }
}
2) Usando auto-propiedades. Este enfoque está bien si la clase tiene un solo constructor:
public List<string> Names { get; private set; }
public .ctor()
{
Names = new List<string>();
}
Pero cuando trata con colecciones mutables como listas de tal manera, rompe la encapsulación, ya que el usuario de esta propiedad puede modificar la colección sin avisar al contenedor (o incluso reemplazar la colección si olvida hacer que el definidor sea privado).
En cuanto a mí, con respecto a la implementación correcta de Encapsulate Collection del encapsulado de colección debería tener este aspecto:
private readonly List<string> _names = new List<string>();
public ICollection<string> Names
{
get { return new ReadOnlyCollection<string>(_names); }
}
public void Add_Name(string name)
{
_names.Add(name);
}
public void Remove_Names(string name)
{
_names.Remove(name);
}
public void Clear_Names()
{
_names.Clear();
}
Honestamente, no recuerdo si he encontrado este tipo de implementación en el código real, incluso en las fuentes del marco. Creo que esto se debe a que las personas son perezosas y evitan escribir tanta cantidad de código solo para hacer que la encapsulación sea un poco más fuerte.
Me pregunto por qué el equipo de C # no proporciona una forma clara y fácil de definir las propiedades automáticas de la colección, de modo que los desarrolladores puedan complacer su pereza y crear un código robusto.
Cada vez que tengo que exponer una colección tiendo a usar IEnumerable<T>
pero no puede usar Auto-propiedades para hacer esto obviamente.
Una solución que realmente me gustaría ver implementada en .NET es el concepto de colecciones inmutables. Esto realmente resolvería el problema del que está hablando, ya que las propiedades automáticas funcionarían perfectamente bien de esta manera:
public ImmutableList<Foo> {get; private set; }
Cualquiera que modifique la lista de alguna manera obtendría una nueva instancia de ImmutableList
y no la lista original en sí.
Por supuesto, puede implementar su propia ImmutableList
pero me parece un poco extraño que .NET Framework no incluya este tipo de elemento.
Exponer ICollection<string>
no es una buena idea porque permite agregar y eliminar operaciones.
public sealed class CollectionHolderSample
{
private readonly List<string> names;
public CollectionHolderSample()
{
this.names = new List<string>();
}
public ReadOnlyCollection<string> Items
{
get
{
return this.names;
}
}
public void AddItem(string item)
{
this.names.Add(item);
}
}
EDITAR:
Como mencionó los métodos Add() y Remove() que provienen de la implementación ReadOnlyCollection<T>.ICollection<T>
explícita, se NotSupportedException
excepción NotSupportedException
por lo que la recopilación es de solo lectura.
Además, para asegurarse de que la recopilación bajo el capó es realmente de solo lectura, puede consultar la propiedad IsReadOnly
MSDN dice que:
ReadOnlyCollection.ICollection.IsReadOnly La propiedad es verdadera si la ICollection es de solo lectura; de lo contrario, falso. En la implementación predeterminada de ReadOnlyCollection, esta propiedad siempre devuelve true.
TL;DR , el compilador de C # no tiene colecciones automáticas porque hay muchas formas diferentes de exponer las colecciones. Al exponer una colección, debe pensar detenidamente cómo desea que se encapsule la colección y utilizar el método correcto.
La razón por la que el compilador de C # proporciona propiedades automáticas es porque son comunes y casi siempre funcionan de la misma manera, sin embargo, al descubrir que la situación rara vez es tan simple cuando se trata de colecciones, existen muchas formas diferentes de exponer una colección. El método correcto siempre depende de la situación, para nombrar algunos:
1) Una colección que puede ser cambiada.
A menudo no hay una necesidad real de colocar restricciones reales en la colección expuesta:
public List<T> Collection
{
get
{
return this.collection;
}
set
{
if (value == null)
{
throw new ArgumentNullException();
}
this.collection = value;
}
}
private List<T> collection = new List<T>();
Puede ser una buena idea asegurarse de que la colección nunca sea nula, de lo contrario, puede usar propiedades automáticas. A menos que tenga una buena razón para querer más encapsulación de mi colección, siempre uso este método por simplicidad.
2) Una colección que puede ser modificada, pero no intercambiada.
Puede codificar esto de la forma que desee, pero la idea es la misma: la colección expuesta permite modificar los elementos, pero la colección subyacente en sí no se puede reemplazar por otra colección. Por ejemplo:
public IList<T> Collection
{
get
{
return this.collection;
}
}
private ObservableCollection<T> collection = new ObservableCollection<T>();
Tiendo a usar este patrón simple cuando trato con cosas como colecciones observables cuando el consumidor debería poder modificar la colección, pero me he suscrito para cambiar las notificaciones: si permites que los consumidores intercambien toda la colección, solo tendrías problemas.
3) Exponer una copia de solo lectura de una colección
Con frecuencia, desea evitar que los consumidores modifiquen una colección expuesta, por lo general, sin embargo, sí desea que la clase expositora pueda modificar la colección. Una forma fácil de hacerlo es exponiendo una copia de solo lectura de su colección:
public ReadOnlyCollection<T> Collection
{
get
{
return new ReadOnlyCollection<T>(this.collection);
}
}
private List<T> collection = new List<T>();
Esto viene con la propiedad de que la colección devuelta nunca cambia, incluso si la colección subyacente cambia. A menudo, esto es bueno ya que permite a los consumidores recorrer la colección devuelta sin temor a que se pueda cambiar:
foreach (var item in MyClass.Collection)
{
// This is safe - even if MyClass changes the underlying collection
// we won''t be affected as we are working with a copy
}
Sin embargo, este no siempre es el comportamiento esperado (o deseado), por ejemplo, la propiedad Controls
no funciona de esta manera. También debe considerar que copiar muchas colecciones grandes de esta manera es potencialmente ineficiente.
Al exponer colecciones que solo son de lectura, siempre tenga en cuenta que los elementos en el control aún pueden modificarse. Una vez más, esto podría ser bueno, pero si desea que la colección expuesta sea "completamente" no modificable, asegúrese de que los elementos de la colección también sean de solo lectura / inmutables (por ejemplo, System.String
).
4) Colecciones que pueden modificarse, pero solo de una manera determinada
Supongamos que desea exponer una colección a la que se pueden agregar elementos, pero no eliminarlos. Podrías exponer propiedades en la propia clase de exposición:
public ReadOnlyCollection<T> Collection
{
get
{
return new ReadOnlyCollection<T>(this.collection);
}
}
private List<T> collection = new List<T>();
public AddItem(T item);
Sin embargo, si su objeto tiene muchas de estas colecciones, entonces su interfaz puede volverse confusa y desordenada rápidamente. También encuentro que este patrón es potencialmente contraintuitivo a veces:
var collection = MyClass.Collection;
int count = collection.Count;
MyClass.AddItem(item);
Debug.Assert(collection.Count > count, "huh?");
Es mucho más esfuerzo, pero IMO es un método mejor para exponer una colección personalizada que encapsula su colección "real" y las reglas sobre cómo la colección puede y no puede cambiarse, por ejemplo:
public sealed class CustomCollection<T> : IList<T>
{
private IList<T> wrappedCollection;
public CustomCollection(IList<T> wrappedCollection)
{
if (wrappedCollection == null)
{
throw new ArgumentNullException("wrappedCollection");
}
this.wrappedCollection = wrappedCollection;
}
// "hide" methods that don''t make sense by explicitly implementing them and
// throwing a NotSupportedException
void IList<T>.RemoveAt(int index)
{
throw new NotSupportedException();
}
// Implement methods that do make sense by passing the call to the wrapped collection
public void Add(T item)
{
this.wrappedCollection.Add(item);
}
}
Ejemplo de uso:
public MyClass()
{
this.wrappedCollection = new CustomCollection<T>(this.collection)
}
public CustomCollection<T> Collection
{
get
{
return this.wrappedCollection;
}
}
private CustomCollection<T> wrappedCollection;
private List<T> collection = new List<T>();
La colección expuesta ahora resume nuestras reglas sobre cómo la colección puede y no puede modificarse y también refleja inmediatamente los cambios realizados en la colección subyacente (lo que puede o no ser algo bueno). También es potencialmente más eficiente para grandes colecciones.
private IList<string> _list = new List<string>();
public IEnumerable<string> List
{
get
{
///return _list;
return _list.ToList();
}
}