c# - net - ¿Cómo exponer una propiedad de la colección?
net core architecture (7)
Cada vez que creo un objeto que tiene una propiedad de colección, ¿voy y viene de la mejor manera para hacerlo?
- propiedad pública con un getter que devuelve una referencia a variable privada
- métodos explícitos get_ObjList y set_ObjList que devuelven y crean objetos nuevos o clonados cada vez
- explícita get_ObjList que devuelve un IEnumerator y un set_ObjList que toma IEnumerator
¿Hace una diferencia si la colección es una matriz (es decir, objList.Clone ()) versus una lista?
Si devolver la colección real como referencia es tan mala porque crea dependencias, ¿por qué devolver alguna propiedad como referencia? Cada vez que expone un objeto hijo como referencia, las partes internas de ese niño pueden cambiarse sin que el padre "sepa", a menos que el niño tenga un evento modificado por la propiedad. ¿Hay riesgo de fugas de memoria?
Y, ¿las opciones 2 y 3 no interrumpen la serialización? ¿Es esto una trampa 22 o tiene que implementar una serialización personalizada cada vez que tenga una propiedad de colección?
El ReadOnlyCollection genérico parece un buen compromiso para el uso general. Envuelve un IList y restringe el acceso a él. Tal vez esto ayude con fugas de memoria y serialización. Sin embargo, todavía tiene preocupaciones de enumeración
Quizás solo depende. Si no le importa que la colección se modifique, simplemente exhíbalo como un acceso público sobre una variable privada por # 1. Si no desea que otros programas modifiquen la colección, entonces # 2 y / o # 3 es mejor.
Implícito en la pregunta, ¿por qué debería usarse un método sobre otro y cuáles son las ramificaciones en seguridad, memoria, serialización, etc.?
Normalmente voy por esto, un getter público que devuelve System.Collections.ObjectModel.ReadOnlyCollection:
public ReadOnlyCollection<SomeClass> Collection
{
get
{
return new ReadOnlyCollection<SomeClass>(myList);
}
}
Y métodos públicos sobre el objeto para modificar la colección.
Clear();
Add(SomeClass class);
Si se supone que la clase es un repositorio con el que otras personas pueden meterse, entonces expongo la variable privada según el método n. ° 1, ya que guarda la escritura de su propia API, pero tiendo a apartarme de eso en el código de producción.
Si simplemente busca exponer una colección en su instancia, entonces usar un getter / setter a una variable de miembro privada parece ser la solución más sensata para mí (su primera opción propuesta).
¿Por qué sugieres que usar ReadOnlyCollection (T) sea un compromiso? Si aún necesita obtener notificaciones de cambio realizadas en el IList envuelto original, también podría usar un ReadOnlyObservableCollection (T) para envolver su colección. ¿Esto sería menos comprometedor en su escenario?
Soy un desarrollador de Java pero creo que esto es lo mismo para c #.
Nunca expongo una propiedad de colección privada porque otras partes del programa pueden cambiarla sin que los padres se den cuenta, de modo que en el método getter devuelvo una matriz con los objetos de la colección y en el método setter llamo a clearAll()
sobre la colección y luego addAll()
ReadOnlyCollection todavía tiene la desventaja de que el consumidor no puede estar seguro de que la colección original no se cambiará en un momento inoportuno. En su lugar, puede usar Colecciones Inmutables . Si necesita hacer un cambio, en lugar de cambiar el original, recibirá una copia modificada. La forma en que se implementa es competitivo con el rendimiento de las colecciones mutables. O incluso mejor si no tiene que copiar el original varias veces para hacer una serie de cambios diferentes (incompatibles) después de cada copia.
Recomiendo usar las nuevas IReadOnlyList<T>
e IReadOnlyCollection<T>
para exponer una colección (requiere .NET 4.5).
Ejemplo:
public class AddressBook
{
private readonly List<Contact> contacts;
public AddressBook()
{
this.contacts = new List<Contact>();
}
public IReadOnlyList<Contact> Contacts { get { return contacts; } }
public void AddContact(Contact contact)
{
contacts.Add(contact);
}
public void RemoveContact(Contact contact)
{
contacts.Remove(contact);
}
}
Si necesita garantizar que la colección no se puede manipular desde el exterior, considere ReadOnlyCollection<T>
o las nuevas colecciones Inmutables.
Evite utilizar la interfaz IEnumerable<T>
para exponer una colección. Esta interfaz no define ninguna garantía de que las enumeraciones múltiples funcionen bien. Si IEnumerable representa una consulta, cada enumeración ejecutará la consulta nuevamente. Los desarrolladores que obtienen una instancia de IEnumerable no saben si representa una colección o una consulta.
Se puede leer más sobre este tema en esta página Wiki .
La forma de exponer una colección depende completamente de cómo los usuarios intenten interactuar con ella.
1) Si los usuarios agregarán y eliminarán elementos de la colección de un objeto, entonces es mejor una simple propiedad de recolección solo para obtener (opción # 1 de la pregunta original):
private readonly Collection<T> myCollection_ = new ...;
public Collection<T> MyCollection {
get { return this.myCollection_; }
}
Esta estrategia se utiliza para las colecciones de Items
en los controles de WindowsForms y WPF ItemsControl
, donde los usuarios agregan y eliminan los elementos que desean que se muestre en el control. Estos controles publican la recopilación real y utilizan devoluciones de llamada u oyentes de eventos para realizar un seguimiento de los elementos.
WPF también expone algunas colecciones configurables para permitir a los usuarios mostrar una colección de elementos que controlan, como la propiedad ItemsControl
en ItemsControl
(opción # 3 de la pregunta original). Sin embargo, este no es un caso de uso común.
2) Si los usuarios solo leerán datos mantenidos por el objeto, entonces puedes usar una colección de solo lectura, como sugería Quibblesome :
private readonly List<T> myPrivateCollection_ = new ...;
private ReadOnlyCollection<T> myPrivateCollectionView_;
public ReadOnlyCollection<T> MyCollection {
get {
if( this.myPrivateCollectionView_ == null ) { /* lazily initialize view */ }
return this.myPrivateCollectionView_;
}
}
Tenga en cuenta que ReadOnlyCollection<T>
proporciona una vista en vivo de la colección subyacente, por lo que solo necesita crear la vista una vez.
Si la colección interna no implementa IList<T>
, o si desea restringir el acceso a usuarios más avanzados, puede en cambio ajustar el acceso a la colección a través de un enumerador:
public IEnumerable<T> MyCollection {
get {
foreach( T item in this.myPrivateCollection_ )
yield return item;
}
}
Este enfoque es simple de implementar y también proporciona acceso a todos los miembros sin exponer la colección interna. Sin embargo, sí requiere que la colección permanezca sin modificaciones, ya que las clases de recopilación de BCL generarán una excepción si intenta enumerar una colección después de que se haya modificado. Si es probable que la colección subyacente cambie, puede crear un contenedor de luz que enumere la colección de forma segura o devolver una copia de la colección.
3) Finalmente, si necesita exponer matrices en lugar de colecciones de nivel superior, debe devolver una copia de la matriz para evitar que los usuarios la modifiquen (opción # 2 de la pregunta original):
private T[] myArray_;
public T[] GetMyArray( ) {
T[] copy = new T[this.myArray_.Length];
this.myArray_.CopyTo( copy, 0 );
return copy;
// Note: if you are using LINQ, calling the ''ToArray( )''
// extension method will create a copy for you.
}
No debe exponer la matriz subyacente a través de una propiedad, ya que no podrá saber cuándo los usuarios la modifican. Para permitir la modificación de la matriz, puede agregar el SetMyArray( T[] array )
o utilizar un indexador personalizado:
public T this[int index] {
get { return this.myArray_[index]; }
set {
// TODO: validate new value; raise change event; etc.
this.myArray_[index] = value;
}
}
(por supuesto, implementando un indexador personalizado, estará duplicando el trabajo de las clases BCL :)