2010-08-14 10 views
14

Es scheint mir, es gibt einen extremen Mangel an sicheren, unveränderlichen Sammlungstypen für .NET, insbesondere BCL, aber ich habe nicht viel Arbeit außerhalb gesehen. Gibt es irgendwelche Hinweise auf eine (vorzugsweise) schnelle, unveränderbare Bibliothek für .NET in Bezug auf Produktionsqualität? Ein schneller Listentyp ist wichtig. Ich bin noch nicht bereit, zu F # zu wechseln.Effiziente, unveränderbare, erweiterbare Sammlungen für. NET

* Edit: für Suchende Hinweis, diese bald in den BCL gerollt werden: .NET immutable collections

Antwort

19

Sie könnten sich den Microsoft.FSharp.Collections Namensraum in der FSharp.Core Baugruppe ansehen. Sie müssen nicht in F # programmieren, um diese Typen zu verwenden.

Beachten Sie, dass die Namen bei Verwendung außerhalb von F # anders lauten. Zum Beispiel ist die Map in F # als FSharpMap von C# bekannt.

+0

Hi Rich, ich probiere das jetzt aus. Vielen Dank. –

+0

Rich, ich denke, diese können tatsächlich praktisch aus C# mit dem Zusatz von einigen Erweiterungsmethoden für typische Operationen wie Consing etc. –

+1

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

1

C5 in dem Sinne, aber ich bin nicht sicher, wie schnell es ist. Es gibt es seit Jahren und es ist sehr stabil.

Darüber hinaus macht List<T>.AsReadOnly() die Arbeit eher gut IMO, aber leider gibt es keine Entsprechung für Wörterbücher oder beliebig ICollection<T> 's.

+0

Leider 'veröffentlicht.AsReadOnly() 'gibt nur einen Wrapper um die ursprüngliche Sammlung zurück; Der ursprüngliche Verweis ist immer noch änderbar, und daher ist auch der schreibgeschützte Wrapper. Es scheint, dass das Gleiche für C5 gilt. – Timwi

+0

@Timwi Wenn Sie besorgt sind, dass die ursprüngliche Referenz durch einen anderen Code geändert werden kann, dann erstellen Sie einfach eine Kopie davon: 'return new Liste (myList) .AsReadOnly()' –

+0

@romkyns Wenn man immer eine Kopie von Listen, es gäbe keine Notwendigkeit für unveränderliche Listen :-) – drozzy

9
+1

Das ist mehr wie es! Weißt du, wie diese Sammlungen in Bezug auf die Leistung gegen die Microsoft.FSharp.Collections, die Rich oben erwähnt, stapeln? Danke für die Referenz, Mauricio! –

+1

Diese Bibliothek, von Alexey, scheint sehr gut und sauber zu sein. Ein großer Dank von mir an dich! :-) –

6

Ich schrieb einen ImmutableList<T> einige Zeit Klasse vor:

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 
} 

Alle Methoden, die die Auflistung ändern, geben eine geänderte Kopie zurück. Um den Schnittstellenvertrag IList<T> zu erfüllen, werden die Standardmethoden Hinzufügen/Entfernen/Löschen/Löschen explizit implementiert, aber sie werfen eine InvalidOperationException.

Diese Klasse verwendet ein paar Nicht-Standardmethoden Erweiterung, hier sind sie:

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); 
    } 
} 

Und hier ist eine Hilfsklasse zu Instanzen von ImmutableList<T> zu erstellen:

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); 
    } 
} 

Hier ist ein Anwendungsbeispiel:

[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); 
    } 

Ich kann nicht sagen, dass es Produktionsqualität ist, wie ich nicht habe stated es gründlich, aber so weit scheint es gut zu funktionieren ...

+3

Sehr schöne Umsetzung! Es wäre jedoch wahrscheinlich schneller unter Stress, wenn Sie das Ziel-Array direkt erstellt und "Array.Copy" verwendet. 'IEnumerable ' ist schön für lesbaren Funktionscode, aber ehrlich gesagt wird es langsam sein. – Timwi

+0

@Timwi, tatsächlich könnte diese Implementierung sicherlich optimiert werden. Ich brauchte keine sehr effiziente Implementierung, als ich sie schrieb, also habe ich es auf Linq-Art gemacht, weil es mehr Spaß macht;) –

+0

@Timwi - Dieser Ansatz ist zumindest speicherfreundlicher, weil der Inhalt der Listen gerade ist wiederverwendet, obwohl Sie sagen, ineffizient - vor allem für indizierten Zugriff auf Elemente. Etwas Ähnliches wie eine Lisp-Liste mit unveränderlichen Cons-Zellen (http://en.wikipedia.org/wiki/Cons) würde einen netten Mittelweg bieten - wo indizierter Zugriff auf sequentielle Elemente optimiert werden kann (Fixkosten beim Abrufen des nächsten Elements), wobei die aufzählbare Lösung variable Kosten hat, während die Notwendigkeit vermieden wird, den gesamten Inhalt der Liste für Szenarien zu kopieren, in denen das Ende der Liste unverändert ist. – Bittercoder

0

Sie könnten BclExtras von JaredPar versuchen.