2010-08-14 9 views
14

Me parece que hay una gran falta de tipos de colecciones inmutables e inseguras para .NET, en particular BCL, pero tampoco he visto mucho trabajo realizado fuera. ¿Alguien tiene alguna sugerencia para una biblioteca de colecciones (preferiblemente) de calidad de producción, inmutables para .NET. Un tipo de lista rápida es esencial. Todavía no estoy preparado para cambiar a F #.Colecciones Eficientes, Inmutables, Extensibles para .NET

* Editar: Nota a los buscadores, esto se está implementando en el BCL pronto: .NET immutable collections

Respuesta

19

Es posible que desee echar un vistazo al espacio de nombres Microsoft.FSharp.Collections en el ensamblaje FSharp.Core. Usted no tiene que programar en F # para hacer uso de estos tipos.

Tenga en cuenta que los nombres serán diferentes cuando se utilicen desde fuera de F #. Por ejemplo, el Map en F # se conoce como FSharpMap de C#.

+0

Hola rico, estoy probando esto ahora. Gracias. –

+0

Rico, creo que esto realmente puede funcionar prácticamente desde C# con la adición de algunos métodos de extensión para operaciones típicas como consultar, etc. –

+1

relacionado: http://stackoverflow.com/questions/8238757/using-f-datatypes-in- c-sharp –

1

C5 viene a la mente, pero no estoy seguro de lo rápido que es. Ha existido por años, y es muy estable.

Además, List<T>.AsReadOnly() hace el trabajo bastante bien IMO, pero desafortunadamente no hay equivalente para diccionarios o arbitraria ICollection<T> 's.

+0

Desafortunadamente, '.AsReadOnly() 'solo devuelve un contenedor alrededor de la colección original; la referencia original sigue siendo mutable y, por lo tanto, también lo es el contenedor de solo lectura. Parece que lo mismo es cierto de C5. – Timwi

+0

@Timwi Si le preocupa que la referencia original pueda ser modificada por algún otro código, simplemente haga una copia: 'return new List (myList) .AsReadOnly()' –

+0

@romkyns Si uno siempre hace una copia de listas, no habría necesidad de listas inmutables :-) – drozzy

9
+1

¡Esto es más parecido! ¿Sabe cómo estas colecciones se comparan con las colecciones de Microsoft.FSharp, que Rich mencionó anteriormente, en términos de rendimiento? Gracias por la referencia, Mauricio! –

+1

Esta biblioteca, por Alexey, parece ser muy bien factorizada y limpia. ¡Muchas gracias de mi parte! :-) –

6

escribí una clase ImmutableList<T> hace algún tiempo:

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Linq; 

public class ImmutableList<T> : IList<T>, IEquatable<ImmutableList<T>> 
{ 
    #region Private data 

    private readonly IList<T> _items; 
    private readonly int _hashCode; 

    #endregion 

    #region Constructor 

    public ImmutableList(IEnumerable<T> items) 
    { 
     _items = items.ToArray(); 
     _hashCode = ComputeHash(); 
    } 

    #endregion 

    #region Public members 

    public ImmutableList<T> Add(T item) 
    { 
     return this 
       .Append(item) 
       .AsImmutable(); 
    } 

    public ImmutableList<T> Remove(T item) 
    { 
     return this 
       .SkipFirst(it => object.Equals(it, item)) 
       .AsImmutable(); 
    } 

    public ImmutableList<T> Insert(int index, T item) 
    { 
     return this 
       .InsertAt(index, item) 
       .AsImmutable(); 
    } 

    public ImmutableList<T> RemoveAt(int index) 
    { 
     return this 
       .SkipAt(index) 
       .AsImmutable(); 
    } 

    public ImmutableList<T> Replace(int index, T item) 
    { 
     return this 
       .ReplaceAt(index, item) 
       .AsImmutable(); 
    } 

    #endregion 

    #region Interface implementations 

    public int IndexOf(T item) 
    { 
     if (_items == null) 
      return -1; 

     return _items.IndexOf(item); 
    } 

    public bool Contains(T item) 
    { 
     if (_items == null) 
      return false; 

     return _items.Contains(item); 
    } 

    public void CopyTo(T[] array, int arrayIndex) 
    { 
     if (_items == null) 
      return; 

     _items.CopyTo(array, arrayIndex); 
    } 

    public int Count 
    { 
     get 
     { 
      if (_items == null) 
       return 0; 

      return _items.Count; 
     } 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     if (_items == null) 
      return Enumerable.Empty<T>().GetEnumerator(); 

     return _items.GetEnumerator(); 
    } 

    public bool Equals(ImmutableList<T> other) 
    { 
     if (other == null || this._hashCode != other._hashCode) 
      return false; 
     return this.SequenceEqual(other); 
    } 

    #endregion 

    #region Explicit interface implementations 

    void IList<T>.Insert(int index, T item) 
    { 
     throw new InvalidOperationException(); 
    } 

    void IList<T>.RemoveAt(int index) 
    { 
     throw new InvalidOperationException(); 
    } 

    T IList<T>.this[int index] 
    { 
     get 
     { 
      if (_items == null) 
       throw new IndexOutOfRangeException(); 

      return _items[index]; 
     } 
     set 
     { 
      throw new InvalidOperationException(); 
     } 
    } 

    void ICollection<T>.Add(T item) 
    { 
     throw new InvalidOperationException(); 
    } 

    void ICollection<T>.Clear() 
    { 
     throw new InvalidOperationException(); 
    } 

    bool ICollection<T>.IsReadOnly 
    { 
     get { return true; } 
    } 

    bool ICollection<T>.Remove(T item) 
    { 
     throw new InvalidOperationException(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 

    #endregion 

    #region Overrides 

    public override bool Equals(object obj) 
    { 
     if (obj is ImmutableList<T>) 
     { 
      var other = (ImmutableList<T>)obj; 
      return this.Equals(other); 
     } 
     return false; 
    } 

    public override int GetHashCode() 
    { 
     return _hashCode; 
    } 

    #endregion 

    #region Private methods 

    private int ComputeHash() 
    { 
     if (_items == null) 
      return 0; 

     return _items 
      .Aggregate(
       983, 
       (hash, item) => 
        item != null 
         ? 457 * hash^item.GetHashCode() 
         : hash); 
    } 

    #endregion 
} 

Todos los métodos que modifican la colección devuelven una copia modificada. Para cumplir con el contrato de interfaz IList<T>, los métodos estándar Agregar/Eliminar/Eliminar/Borrar se implementan explícitamente, pero arrojan un InvalidOperationException.

Esta clase utiliza unos métodos de extensión no estándar, aquí están:

public static class ExtensionMethods 
{ 
    public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T item) 
    { 
     return source.Concat(new[] { item }); 
    } 

    public static IEnumerable<T> SkipFirst<T>(this IEnumerable<T> source, Func<T, bool> predicate) 
    { 
     bool skipped = false; 
     foreach (var item in source) 
     { 
      if (!skipped && predicate(item)) 
      { 
       skipped = true; 
       continue; 
      } 

      yield return item; 
     } 
    } 

    public static IEnumerable<T> SkipAt<T>(this IEnumerable<T> source, int index) 
    { 
     return source.Where((it, i) => i != index); 
    } 

    public static IEnumerable<T> InsertAt<T>(this IEnumerable<T> source, int index, T item) 
    { 
     int i = 0; 
     foreach (var it in source) 
     { 
      if (i++ == index) 
       yield return item; 

      yield return it; 
     } 
    } 

    public static IEnumerable<T> ReplaceAt<T>(this IEnumerable<T> source, int index, T item) 
    { 
     return source.Select((it, i) => i == index ? item : it); 
    } 
} 

Y aquí es una clase de ayuda para crear instancias de ImmutableList<T>:

public static class ImmutableList 
{ 
    public static ImmutableList<T> CreateFrom<T>(IEnumerable<T> source) 
    { 
     return new ImmutableList<T>(source); 
    } 

    public static ImmutableList<T> Create<T>(params T[] items) 
    { 
     return new ImmutableList<T>(items); 
    } 

    public static ImmutableList<T> AsImmutable<T>(this IEnumerable<T> source) 
    { 
     return new ImmutableList<T>(source); 
    } 
} 

Aquí está un ejemplo de uso:

[Test] 
    public void Test_ImmutableList() 
    { 
     var expected = ImmutableList.Create("zoo", "bar", "foo"); 
     var input = ImmutableList.Create("foo", "bar", "baz"); 
     var inputSave = input.AsImmutable(); 
     var actual = input 
       .Add("foo") 
       .RemoveAt(0) 
       .Replace(0, "zoo") 
       .Insert(1, "bar") 
       .Remove("baz"); 

     Assert.AreEqual(inputSave, input, "Input collection was modified"); 
     Assert.AreEqual(expected, actual); 
    } 

No puedo decir que sea de calidad de producción, ya que no tengo lo dijimos a fondo, pero hasta ahora parece funcionar bien ...

+3

Muy buena implementación! Sin embargo, probablemente sería más rápido bajo estrés si creara la matriz objetivo directamente y usara 'Array.Copy'. 'IEnumerable ' es bueno para código funcional legible, pero sinceramente, va a ser lento. – Timwi

+0

@Timwi, de hecho, esta implementación ciertamente podría optimizarse. No necesitaba una implementación muy eficiente cuando lo escribí, así que lo hice de la manera Linq porque es más divertido;) –

+0

@Timwi - Este enfoque es más amigable con la memoria, al menos, porque el contenido de las listas está siendo reutilizado, aunque como dices ineficiente, especialmente para el acceso indexado a los elementos. Algo similar a una lista de Lisp que usa celdas de cons inmutables (http://en.wikipedia.org/wiki/Cons) proporcionaría un buen término medio, donde el acceso indexado a los miembros secuenciales se puede optimizar (costo fijo para ir a buscar el siguiente elemento), donde la solución enumerable tiene un costo variable, mientras se evita la necesidad de copiar todo el contenido de la lista para escenarios donde la cola de la lista no se modifica. – Bittercoder

Cuestiones relacionadas