-Colecciones del ObjectModel- Coleccionando objetos en .NET Framework (III)

Introducción

Esta serie de artículos pretende mostrar las características y peculiaridades de las colecciones en .NET. El objetivo no es mostrar las situaciones en las que cada una de los diferentes tipos de colecciones pueden utilizarse en cada contexto sino la de conocer las mismas, ventajas y desventajas, para poder seleccionar el tipo de colección más apropiado en cada momento.

La serie cubre (entre corchetes el estado):

  • Colecciones No Genericas
  • Colecciones Genéricas System.Collections.Generic
  • Colecciones de System.Collections.ObjectModel (este post)
  • Colecciones nuevas en CLR 4.0 (no es definitivo además solo hay una)
  • Cota superior asintótica de las colecciones del CLI. aka Notación de Landau – O Grande- (en desarrollo)
  • Power Collections y C5 (por ahí andan)
  • Rx  (si hay ganas y tiempo)

Colecciones System.Collections.ObjectModel

Todas las colecciones agrupadas bajo System.Collections.ObjectModel tiene como denominador común que todas y cada una de ellas pueden ser utilizadas como modelo de objetos en librerias públicas con la intención de ser extendidas.

Krzysztof Cwalina expone sus razones por las cuales algunas de estas clases han sido clasificadas bajo dicho espacio de nombres en el siguiente post. Básicamente, la gran mayoria de desarrolladores nos conformaremos con utilizar las colecciones expuestas tanto en System.Collections como en System.Collections.Generics dejando las expuestas en ObjectModel las utilizadas en menor medidad para otros usos no tan habituales como es  la generacción de colecciones propietarias.

Dicho esto, lo que pretende este post es introducir las 5 clases con las que se presenta .NET Framework 4.0 y que son:

  • System.Collections.ObjectModel.Collection<T>
  • System.Collections.ObjectModel.KeyedCollection<TKey,TItem>
  • System.Collections.ObjectModel.ObservableCollection<T>
  • System.Collections.ObjectModel.ReadOnlyCollection<T>
  • System.Collections.ObjectModel.ReadOnlyObservableCollection<T>

System.Collections.ObjectModel.Collection<T>

Collection<T> se utiliza como clase base (pese a que no es abstracta) para la implementación de colecciones personalizadas. Para ello expone parte de sus métodos como virtuales, tales como ClearItems, InsertItem, RemoveItem y SetItem. Por lo tanto se recomienda que todo los desarroladores que pretendan generar una colección personalizada extiendan Collection<T> en lugar de crear una desde zero.

El propio .NET Framework hace un uso extensivo de Collection<T> en gran parte de las colecciones utilizadas:

System.Object
  System.Collections.ObjectModel.Collection<T>
    System.Collections.ObjectModel.KeyedCollection<TKey, TItem>
    System.Collections.ObjectModel.ObservableCollection<T>
    System.ComponentModel.BindingList<T>
    System.ComponentModel.SortDescriptionCollection
    System.Net.IPEndPointCollection
    System.Net.Mail.AlternateViewCollection
    System.Net.Mail.AttachmentCollection
    System.Net.Mail.LinkedResourceCollection
    System.Net.Mail.MailAddressCollection
    System.Net.PeerToPeer.CloudCollection
    System.Net.PeerToPeer.Collaboration.PeerApplicationCollection
    System.Net.PeerToPeer.Collaboration.PeerContactCollection
    System.Net.PeerToPeer.Collaboration.PeerEndPointCollection
    System.Net.PeerToPeer.Collaboration.PeerNearMeCollection
    System.Net.PeerToPeer.Collaboration.PeerObjectCollection
    System.Net.PeerToPeer.PeerNameRecordCollection
    System.Security.Cryptography.CngPropertyCollection
    System.ServiceModel.Channels.BindingElementCollection
    System.ServiceModel.Channels.ChannelParameterCollection
    System.ServiceModel.Description.FaultDescriptionCollection
    System.ServiceModel.Description.MessageDescriptionCollection
    System.ServiceModel.Description.OperationDescriptionCollection
    System.ServiceModel.Description.PolicyAssertionCollection
    System.ServiceModel.Description.ServiceEndpointCollection
    System.ServiceModel.Dispatcher.MessageQueryCollection
    System.ServiceModel.Syndication.SyndicationElementExtensionCollection
    System.Web.DynamicData.DataControlReferenceCollection
    System.Web.Routing.RouteCollection
    System.Web.UI.DataVisualization.Charting.ChartElementCollection<T>
    System.Web.UI.ScriptReferenceCollection
    System.Web.UI.ServiceReferenceCollection
    System.Web.UI.UpdatePanelTriggerCollection
    System.Windows.ConditionCollection
    System.Windows.Forms.DataVisualization.Charting.ChartElementCollection<T>
    System.Windows.Forms.FileDialogCustomPlacesCollection
    System.Windows.Ink.StrokeCollection
    System.Windows.Input.StylusPlugIns.StylusPlugInCollection
    System.Windows.Input.StylusPointCollection
    System.Windows.Input.TouchPointCollection
    System.Windows.SetterBaseCollection
    System.Windows.TriggerCollection
    System.Workflow.ComponentModel.Compiler.ValidationErrorCollection

System.Collections.ObjectModel.KeyedCollection<TKey,TItem>

Se trata de una clases abstracta que como peculiaridad las claves estan incrustadas en sus valores. Tal y como se define en el MSDN Library, KeyedCollection<TKey,TItem> es un conjunto infinito de tipos abstractos ya que la implementación dependerá en gran medida de los tipos genericos representados por TKey y TItem.

La clase KeyedCollection<TKey, TItem> es un híbrido entre una colección basada en IList<T> y una colección basada en IDictionary<TKey, TValue> y por tanto puede ser accedida mediante índice y clave. La diferencia fundamental es que ésta no es una par clave/valor ya que el propio valor agrupa tanto el valor en sí como la clave. Su firma es:

public abstract class KeyedCollection<TKey, TItem> : Collection<TItem>
{
    protected KeyedCollection();
    protected KeyedCollection(IEqualityComparer<TKey> comparer);
    protected KeyedCollection(IEqualityComparer<TKey> comparer, int dictionaryCreationThreshold);
    public IEqualityComparer<TKey> Comparer { get; }
    public TItem this[TKey key] { get; }
    protected IDictionary<TKey, TItem> Dictionary { get; }
    public bool Contains(TKey key);
    public bool Remove(TKey key);
    protected void ChangeItemKey(TItem item, TKey newKey);
    protected override void ClearItems();
    protected abstract TKey GetKeyForItem(TItem item);
    protected override void InsertItem(int index, TItem item);
    protected override void RemoveItem(int index);
    protected override void SetItem(int index, TItem item);
}

Por ejemplo, KeyedCollection<int, Empleado> puede contener una clave “234449” que pertenece como propiedad al propio elemento Empleado (como por ejemplo el código del mismo) que se obtendría a través del método abstracto GetKeyForItem() de la siguiente forma:

public class Departamento : KeyedCollection<int, Empleado>
{
    public Departamento () : base(null, 0) {}
 
    protected override int GetKeyForItem(Empleado empleado)
    {
        return empleado.Codigo;
    }
 
     //Clase incompleta
}

 

System.Collections.ObjectModel.ObservableCollection<T>

 http://msdn.microsoft.com/en-us/library/ms668604.aspx 

Las colecciones ObservableCollection<T> fueron una de las caracterísitcas más importantes introducidas a raíz de WPF y Silverlight posteriormente debido a la capacidad que tiene de propagar una modificación sobre el conjunto de elementos de la colección, a través de un par de interfaces que veremos a continuación, y que sirve como base para el Data Binding con los elementos o vistas WPF/Silverlight, por ejemplo.

Si observamos la firma de ObservableCollection<T> vemos que además de implementar la clase abstracta Collection<T>, implementa INotifyCollectionChanged e INotifyPropertyChanged.

[Serializable]
public class ObservableCollection<T> 
    : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
...
}

Si vemos la definición de INotifyCollectionChanged:

public interface INotifyCollectionChanged
{
    event NotifyCollectionChangedEventHandler CollectionChanged;
}

Vemos que implementa un evento del tipo NotifyCollectionChangedEventHandler el cual notificará a los “subscriptores” – o listeners en inglés- ante cualquier modificación sobre el conjunto de elementos. Ojo, se utiliza la palabra subscriptores entre comillas pues no es una entorno publicador – subscriptor real. (Véase Observer Design Pattern with .NET)

Por otro lado, la interfaz INotifyPropertyChanged:

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

Es la que notificará las modificaciones, no sobre el conjunto de elementos, sino sobre los datos –Propiedades- de cualquier elemento de la colección. Las interfaces WPF/Silverlight basados en Data Bindings son los típicos clientes de esta interfaz y son los que representan la modificación de cualquier propiedad de cualquier elemento de una colección ObservableCollection<T> en el elemento enlazado de la interfaz.

Por ejemplo supongamos que tenemos la clase Empleado que pertenecerá a una ObservableCollection<T> y que posteriormente será enlazada a la interfaz de una aplicación WPF. En primer lugar debemos implementar la interfaz INotifyPropertyChanged para proveer de un mecanismo de notificación sobre las propiedades de la clase. Así, si la clase Empleado tiene dos propiedades llamadas Login y Nombre en ambos setters de la propiedad deberíamos ejecutar el evento PropertyChanged() tal y como se muestra a continuación:

public class Empleado : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        
        private string _login;
        private string _nombre;
        
        public string Login
        {
            get { return _login; }
            set
            {
                _login = value;
                PropertyChanged(this, 
                    new PropertyChangedEventArgs("Login"));
            }
        }
 
        public string Nombre
        {
            get { return _nombre; }
            set
            {
                _nombre = value;
                PropertyChanged(this, 
                    new PropertyChangedEventArgs("Nombre"));
            }
        }
    }

A partir de aquí, la clase Empleado debe residir dentro de una colección ObservableCollection<Empleado> y ser enlazada a un elemento cualquier, por ejemplo un datagrid. Una ejemplo trivial con una única grid y un boton que agregaría un nuevo empleado a la colección seria:

public partial class MainWindow : Window
{
    private readonly ObservableCollection<Empleado> users 
        = new ObservableCollection<Empleado>();
 
    public MainWindow()
    {
        InitializeComponent();
            
        datagrid1.DataContext = users;
    }
 
    private void button1_Click(object sender, RoutedEventArgs e)
    {
        users.Add(new Empleado()
        {
            Login = "jmtorres",
            Nombre = "José Miguel"
        });
    }
}

En el XAML únicamente deberiamos hacer uso de los Data Bindings de WPF:

<Grid>
    <DataGrid Name="dataGrid1" ItemsSource="{Binding}" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Login}" Header="Login" />
            <DataGridTextColumn Binding="{Binding Nombre}" Header="Nombre" />
        </DataGrid.Columns>
    </DataGrid>
    <Canvas>
        <Button Click="button1_Click" ></Button>
    </Canvas>
</Grid>

A continuación detallo enlaces que tratan de forma más específica los Data Bindings tanto en WPF como en Silverlight utilizando colecciones ObservableCollection<T>:

 

System.Collections.ObjectModel.ReadOnlyCollection<T>

ReadOnlyCollection<T> es la versión Solo Lectura de la colección Collection<T>. Está especialmente para ser extendida en lugar de crear colecciones personalizadas desde zero y su peculiaridad, como su propio nombre indica es que carece de los 4 métodos que en la clase Collection<T> estaban declarados como virtuales y que se detallan a continuación:

protected virtual void ClearItems();
protected virtual void InsertItem(int index, T item);
protected virtual void RemoveItem(int index);
protected virtual void SetItem(int index, T item);

Por ultimo se detalla una relación de clases de .NET Framework que extienden las funcionalidades de ReadOnlyCollection<T>

System.Object
  System.Collections.ObjectModel.ReadOnlyCollection<T>

    System.Collections.ObjectModel.ReadOnlyObservableCollection<T>
    System.Data.Metadata.Edm.ReadOnlyMetadataCollection<T>
    System.Security.Cryptography.ManifestSignatureInformationCollection
    System.ServiceModel.Channels.AddressHeaderCollection
    System.Windows.Input.StylusButtonCollection
    System.Windows.Input.StylusDeviceCollection

System.Collections.ObjectModel.ReadOnlyObservableCollection<T>

La descripción oficial que hace el MSDN Library acerca de esta clase es que: “ReadOnlyObservableCollection es un wrapper –una envoltura- de sólo lectura sobre una colección ObservableCollection”. La percepción que tengo yo al respecto, siempre desde el respeto y la ignorancia especialmente sobre esta clase, es que no tengo ni pajolera idea de cual es su utilidad real llegando incluso a dudar de que realmente la tenga, con lo que no indagaré más al respecto.