Неизменяемые массивы в .net (c #): разумный подход? - PullRequest
3 голосов
/ 16 сентября 2009

Вам, вероятно, известна следующая проблема: вы хотите разделить коллекцию между двумя объектами A и B (или аналогичным образом хотите представить коллекцию по свойству) и ... ну, вы понимаете, что на самом деле это не очень хорошая идея поскольку и А, и В теперь могут модифицировать коллекцию и бла-бла-армагеддон ...

Но часто вы понимаете, что не хотите делить коллекцию как целое, а хотите только инициализировать объект с элементами коллекции или передать элементы в метод объекта. Кроме того, предметы объекта не меняются в течение жизни объекта, поэтому вам часто приходится делать такие вещи:

public class Foo
{
  private List<int> items;

  public Foo(IEnumerable<int> items)
  {
    this.items = items.ToList();
  }

  public ReadOnlyCollection<int> Items
  {
    get { return new ReadOnlyCollection(this.items); }
  }
}

И .ToList (), и ReadonlyCollection-Wrapper обеспечивают разумное решение проблемы, но также страдают от некоторых проблем: .ToList () копирует весь список - так что в итоге я получаю x экземпляров одной коллекции, в то время как ReadOnlyCollection гарантирует, что клиенты не изменят коллекцию, но не дадут клиентам гарантии того, что коллекция не изменится.

На самом деле, с такой ситуацией я часто сталкиваюсь, потому что большинство классов, которые я пишу (предположительно: 80-90%), являются эффективно неизменяемыми и часто собирают коллекции, которые в основном представляют неизменяемые массивы (что означает в данном контексте: фиксированный размер неизменяемой последовательности предметов). С моей точки зрения, хорошее решение этой проблемы довольно простое: создайте класс-оболочку для массива, который гарантированно будет правильно инициализирован в конструкторе, и предоставьте только методы / свойства, которые не изменяют базовый массив.

Короче говоря: есть ли что-то не так со следующей (тривиальной) реализацией концепции неизменяемого массива?

Кстати: я решил, что Sequence - подходящее имя, так как а) он достаточно хорошо описывает намерение (неизменяемая последовательность предметов фиксированного размера) б) это не имя, используемое в настоящее время .net и в) он очень короткий, что важно, потому что я планирую широко его использовать.

public sealed class Sequence<T> : IEnumerable<T>
    {
        private readonly T[] items;

        public Sequence(IEnumerable<T> items)
        {
            this.items = items.ToArray();
        }

        public Sequence(int size, Func<int, T> producer)
        {
            this.items = new T[size];

            for (int i = 0; i < size; i++)
            {
                this.items[i] = producer(i);
            }
        }

        public T this[int i]
        {
            get { return this.items[i]; }
        }

        public int Length
        {
            get { return this.items.Length; }
        }

        public bool Contains(T item)
        {
            for (int i = 0; i < this.items.Length; i++)
            {
                if (this.items[i].Equals(item))
                    return true;
            }

            return false;
        }

        public Enumerator<T> GetEnumerator()
        {
            return new Enumerator<T>(this);
        }

        IEnumerator<T> System.Collections.Generic.IEnumerable<T>.GetEnumerator()
        {
            return GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public struct Enumerator<T> : IEnumerator<T>
        {
            private T[] items;
            private int nextIndex;
            private T current;

            internal Enumerator(Sequence<T> immutableArray)
            {
                this.items = immutableArray.items;
                this.nextIndex = 0;
                this.current = default(T);
            }

            public T Current
            {
                get { return this.current; }
            }

            object System.Collections.IEnumerator.Current
            {
                get { return this.Current; }
            }

            public void Dispose()
            {
            }

            public bool MoveNext()
            {
                if (this.nextIndex < this.items.Length)
                {
                    this.current = this.items[this.nextIndex];
                    this.nextIndex++;
                    return true;
                }
                else
                {
                    return false;
                }
            }

            public void Reset()
            {
                this.nextIndex = 0;
                this.current = default(T);
            }
        }
    }

Ответы [ 3 ]

3 голосов
/ 16 сентября 2009

Что бы ваш Sequence класс дал вам, что ReadOnlyCollection не даст? Не могли бы вы просто использовать простое наследование для предоставления нужных вам конструкторов?

public class Sequence<T> : ReadOnlyCollection<T>
{
    public Sequence(IEnumerable<T> items)
        : base(items.ToList()) { }

    public Sequence(int size, Func<int, T> generator)
        : base(Enumerable.Range(0, size).Select(generator).ToList()) { }

    // properties, methods etc provided by the ReadOnlyCollection base class
}
2 голосов
/ 16 сентября 2009

Кажется, все в порядке. Обратите внимание, что вы можете сжать весь код IEnumerable в несколько строк, используя yield return или просто возвращая Items.GetEnumerator().

1 голос
/ 24 декабря 2012

.NET только что отправил свои первые неизменные коллекции , которые я предлагаю вам попробовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...