Desde que empecé con el desarrollo en .NET, en su versión 1.1, he hechado de menos que además de las colecciones por defecto, existiera la posibilidad de trabajar con colecciones inmutables.

¿Cuando queremos una colección inmutable? En mi caso, hecho de menos las colecciones inmutables cuando:

  • Queremos compartir datos entre varios hilos, y para obtener el máximo rendimiento queremos evitar el uso de locks.
  • Queremos asegurarnos que los resultados de una API no pueden ser modificados, ni por el cliente de la API y no por la API en sí.

Este tweet me ha hecho recordar un caso típico dónde puede interersarnos utilizar una colección Inmutable:

class Example
        {
            private List notes;

            public Example()
            {
                this.notes = new List();
            }

            public async Task AddNotes(int note, int number)
            {
                for (int i = 0; i < number; i++)
                {
                    await Task.Factory.StartNew(() => this.notes.Add(note + i));
                }
            }

            public List Notes
            {
                get
                {
                    return notes;
                }
            }
}

En el anterior ejemplo, se expone una colección Notes, como ejemplo de lista compartida, que permite a quienes usen esta clase recorrer la colección. El primero defecto, es que, aunque nuestra intención es exponer la lista como sólo lectura, en realidad si permitimos añadir nuevos valores a la lista, ya que List<T> sigue siendo mutable.

Podríamos mitigar este efecto, exponiendo un IEnumerable<T>, sin embargo nada impide a los usuarios de nuestra clase convertir a List y añadir igualmente valores a nuestra lista, aunque seguramente este uso sólo nos preocuparía en caso de ser una API pública.

Podemos exponer, en lugar de la lista List; una colección de sólo lectura. De esta forma garantizamos que nuestros usuarios no pueden realizar modificaciones:

public IReadOnlyCollection Notes
            {
                get
                {
                    return new ReadOnlyCollection(this.notes);
                }
            }

Sin embargo este código presenta un problema. Si os fijáis en el método AddNotes de la clase Example, veréis que añade valores a la lista en paralelo. Por tanto si mientras añadimos notas estamos a su vez enumerando la colección Notes, obtendremos una excepción ya que la colección fue modificada durante la enumeración, como ocurre en el siguiente test:

        [TestMethod]
        public async Task CollectionModifiedOverReadOnly()
        {
            var example = new Example();

            AddNotes(example, 5);

            await ReadNotes(example.NotesReadOnly);

        }

        private async void AddNotes(Example example, int note)
        {
            await example.AddNotes(note, 1000);
        }

        private async Task ReadNotes(IEnumerable<int> notes)
        {
            await Task.Factory.StartNew(() =>
            {
                foreach (var note in notes)
                {
                    Console.WriteLine(note);
                }
            });
        }

Es aquí donde las colecciones Inmutables cobran su importancia, por un lado nos permiten asegurar que el contrato entre nuestra clase y los usuarios de nuestra API no permite modificaciones en las colecciones, y por otro, nos ayuda enormemente en la programación concurrente, permitiéndonos crear aplicaciones que no necesitan del uso de bloqueos y tienen como consecuencia un mayor rendimiento.

En el siguiente ejemplo, podéis ver como el test no falla al enumerar la colección inmutable, ya que al añadir elementos en realidad se crean nuevas instancias de la misma, y el uso de un Builder para obtener un rendimiento óptimo mientras se añaden valores de forma masiva:

[TestClass]
    public class InmutableExamples
    {
        public class Example
        {
            private ImmutableList notes;

            public Example()
            {
                this.notes = ImmutableList.Empty;
            }

            public async Task AddNotes(int note, int number)
            {
                var builder = this.notes.ToBuilder();

                for (int i = 0; i < number; i++)
                {
                    await Task.Factory.StartNew(() => 
                        {
                            builder.Add(note + i);

                            this.notes = builder.ToImmutable();
                        });
                }
            }

            public ImmutableList Notes
            {
                get
                {
                    return notes;
                }
            }

        }

        [TestMethod]
        public async Task RunsToEnd()
        {
            var example = new Example();

            AddNotes(example, 5);

            await ReadNotes(example.Notes);

        }

Sinceramente, es un nuevo conjunto de colecciones que hace tiempo que buscaba. Hasta que se publique su versión definitiva, podeis encontrar el paquete en Nuget buscando por Microsoft.Bcl.Immutable

Hasta la próxima!