2010-08-14 2 views
14

Мне кажется, что существует огромное количество безопасных, неизменных типов коллекций для .NET, в частности BCL, но я не видел много работы вне любой из них. У кого-нибудь есть указатели на (желательно) качество производства, быструю, неизменяемую библиотеку коллекций для .NET. Быстрый тип списка необходим. Я еще не готов переключиться на F #.Эффективные, неизменные, расширяемые коллекции для .NET.

* Edit: Обратите внимание на поисковиках, это перекатываться в BCL скоро: .NET immutable collections

ответ

19

Возможно, вы захотите взглянуть на пространство имен Microsoft.FSharp.Collections в сборке FSharp.Core. Вам не нужно программировать в F #, чтобы использовать эти типы.

Имейте в виду, что имена будут отличаться при использовании снаружи F #. Например, Map в F # известен как FSharpMap от C#.

+0

Привет, Ричард, я пробую это сейчас. Благодарю. –

+0

Богатые, я думаю, что они могут фактически работать практически изнутри C# с добавлением некоторых методов расширения для типичных операций, таких как consing и т. Д. –

+1

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

1

C5 приходит на ум, но я не уверен, насколько быстро он. Он существует уже много лет и очень стабилен.

Кроме того, List<T>.AsReadOnly() делает работу довольно хорошо ИМО, но, к сожалению, нет эквивалента для словарей или произвольных ICollection<T>.

+0

К сожалению, '.AsReadOnly() 'возвращает только обертку вокруг оригинальной коллекции; исходная ссылка все еще изменена, и поэтому так же доступна только для чтения оболочка. Кажется, что то же самое относится и к C5. – Timwi

+0

@Timwi Если вы обеспокоены тем, что исходная ссылка может быть изменена каким-либо другим кодом, просто сделайте ее копию: 'return new List (myList) .AsReadOnly()' –

+0

@romkyns Если вы всегда делаете копию списков, не было бы необходимости в неизменных списках :-) – drozzy

9
+1

Это больше похоже на это! Знаете ли вы, как эти коллекции складываются против Microsoft.FSharp.Collections, упомянутых выше, с точки зрения производительности? Спасибо за ссылку, Маурицио! –

+1

Эта библиотека Алексея выглядит очень ухоженной и чистой. Огромное спасибо от меня вам! :-) –

6

Я написал ImmutableList<T> класс некоторое время назад:

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 
} 

Все методы, которые изменяют коллекцию, возвращают измененную копию. Чтобы выполнить контракт с IList<T>, стандартные методы «Добавить/Удалить/Удалить/Очистить» реализованы явно, но они выбрасывают InvalidOperationException.

Этот класс использует несколько нестандартных методов расширения, вот они:

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

А вот вспомогательный класс для создания экземпляров из 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); 
    } 
} 

Вот пример использования:

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

Я не могу сказать, что это качество продукции, поскольку у меня нет тщательно изучил его, но пока это работает нормально ...

+3

Очень приятная реализация! Однако, если бы вы создали целевой массив напрямую и использовали 'Array.Copy', скорее всего, это было бы быстрее. 'IEnumerable ' приятно для читаемого функционального кода, но, честно говоря, он будет медленным. – Timwi

+0

@Timwi, действительно, эта реализация, безусловно, может быть оптимизирована. Мне не нужна была очень эффективная реализация, когда я ее написал, поэтому я сделал это способом Linq, потому что это более забавно;) –

+0

@Timwi. Этот подход более удобен для работы с памятью, хотя, по крайней мере, потому что содержимое списков повторное использование, хотя, как вы говорите, неэффективно - особенно для индексированного доступа к элементам. Что-то похожее на список Lisp с использованием неизменяемых cons-ячеек (http://en.wikipedia.org/wiki/Cons) обеспечит хорошую среднюю позицию - где можно оптимизировать индексированный доступ к последовательным членам (фиксированные затраты на выборку следующего элемента), где перечислимое решение имеет переменную стоимость, избегая при этом необходимости копировать все содержимое списка для сценариев, где хвост списка не изменяется. – Bittercoder

Смежные вопросы