Возможны ли неизменяемые массивы в .NET? - PullRequest
27 голосов
/ 17 октября 2008

Можно ли как-то пометить System.Array как неизменный. Когда они помещены за public-get / private-set, они не могут быть добавлены, так как это требует перераспределения и переназначения, но потребитель все еще может установить любой индекс, который он пожелает:

public class Immy
{
    public string[] { get; private set; }
}

Я думал, что ключевое слово readonly могло бы сработать, но не повезло.

Ответы [ 10 ]

28 голосов
/ 17 октября 2008

Руководство по разработке структуры предлагает вернуть копию массива. Таким образом, потребители не могут изменять элементы из массива.

// bad code
// could still do Path.InvalidPathChars[0] = 'A';
public sealed class Path {
   public static readonly char[] InvalidPathChars = 
      { '\"', '<', '>', '|' };
}

это лучше:

public static ReadOnlyCollection<char> GetInvalidPathChars(){
   return Array.AsReadOnly(InvalidPathChars);
}

public static char[] GetInvalidPathChars(){
   return (char[])InvalidPathChars.Clone();
}

Примеры прямо из книги.

26 голосов
/ 17 октября 2008

ReadOnlyCollection<T>, вероятно, то, что вы ищете. У него нет Add() метода.

13 голосов
/ 02 января 2013

См. Доступные неизменяемые коллекции в библиотеке базовых классов (в настоящее время в режиме предварительного просмотра).

9 голосов
/ 17 октября 2008

Вы можете использовать метод Array.AsReadOnly для возврата.

2 голосов
/ 17 октября 2008

Я считаю, что по этой причине лучше использовать IList , а не массивы в открытых API. readonly не позволит установить переменную-член вне конструктора, но, как вы обнаружили, не помешает людям назначать элементы в массиве.

См. Массивы, считающиеся несколько вредными для получения дополнительной информации.

Редактировать: Массивы не могут быть только для чтения, но их можно преобразовать в реализации IList только для чтения через Array.AsReadOnly (), как указывает @shahkalpesh.

1 голос
/ 25 июля 2013

.NET имеет тенденцию держаться подальше от массивов для всех, кроме самых простых и традиционных случаев использования. Для всего остального существуют различные реализации перечислимых / коллекций.

Если вы хотите пометить набор данных как неизменяемый, вы выходите за рамки возможностей, предоставляемых традиционным массивом. .NET предоставляет эквивалентные возможности, но технически не в форме массива. Чтобы получить неизменяемую коллекцию из массива, используйте ">Array.AsReadOnly<T>:

var mutable = new[]
{
    'a', 'A',
    'b', 'B',
    'c', 'C',
};

var immutable = Array.AsReadOnly(mutable);

immutable будет ">ReadOnlyCollection<char> экземпляром. В более общем случае вы можете создать ">ReadOnlyCollection<T> из любой общей реализации ">IList<T>.

var immutable = new ReadOnlyCollection<char>(new List<char>(mutable));

Обратите внимание, что это должна быть общая реализация; обычный старый IList не будет работать, а это означает, что вы не можете использовать этот метод в традиционном массиве, который реализует только IList. Это раскрывает возможность использования ">Array.AsReadOnly<T> в качестве быстрого средства получения доступа к универсальным реализациям, которые обычно недоступны через традиционный массив.

">ReadOnlyCollection<T> предоставит вам доступ ко всем функциям, которые можно ожидать от неизменяемого массива:

// Note that .NET favors Count over Length; all but traditional arrays use Count:
for (var i = 0; i < immutable.Count; i++)
{
    // this[] { get } is present, as ReadOnlyCollection<T> implements IList<T>:
    var element = immutable[i]; // Works

    // this[] { set } has to be present, as it is required by IList<T>, but it
    // will throw a NotSupportedException:
    immutable[i] = element; // Exception!
}

// ReadOnlyCollection<T> implements IEnumerable<T>, of course:
foreach (var character in immutable)
{
}

// LINQ works fine; idem
var lowercase =
    from c in immutable
    where c >= 'a' && c <= 'z'
    select c;

// You can always evaluate IEnumerable<T> implementations to arrays with LINQ:
var mutableCopy = immutable.ToArray();
// mutableCopy is: new[] { 'a', 'A', 'b', 'B', 'c', 'C' }
var lowercaseArray = lowercase.ToArray();
// lowercaseArray is: new[] { 'a', 'b', 'c' }
0 голосов
/ 20 октября 2008

Возможно, вы захотите проверить мой ответ на похожий вопрос, чтобы получить больше идей по демонстрации коллекций на объекте.

0 голосов
/ 17 октября 2008

Лучшее, на что вы можете надеяться, это расширить существующую коллекцию, чтобы создать свою собственную. Большая проблема заключается в том, что он должен работать иначе, чем каждый существующий тип коллекции, поскольку каждый вызов должен возвращать новую коллекцию.

0 голосов
/ 17 октября 2008

В дополнение к ответу Мэтта, IList является полным абстрактным интерфейсом для массива, поэтому он позволяет добавлять, удалять и т. Д. Я не уверен, почему Липперт, кажется, предлагает его как альтернативу IEnumerable, где необходима неизменность. ( Edit: , потому что реализация IList может генерировать исключения для этих методов мутирования, если вам нравится такая вещь).

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

Убедитесь, что элементы в списке неизменны (как в вашем примере: строка неизменна).

Возвращает глубокий клон всего, так что в этом случае вы можете использовать массив в любом случае.

Возвращает интерфейс, который дает доступ только для чтения к элементу:

interface IImmutable
{
    public string ValuableCustomerData { get; }
}

class Mutable, IImmutable
{
    public string ValuableCustomerData { get; set; }
}

public class Immy
{
    private List<Mutable> _mutableList = new List<Mutable>();

    public IEnumerable<IImmutable> ImmutableItems
    {
        get { return _mutableList.Cast<IMutable>(); }
    }
}

Обратите внимание, что каждое значение, доступное из интерфейса IImmutable, само по себе должно быть неизменным (например, строка) или же быть копией, которую вы делаете на лету.

0 голосов
/ 17 октября 2008

Единственное, что нужно добавить, это то, что массивы подразумевают изменчивость. Когда вы возвращаете массив из функции, вы предлагаете клиентскому программисту, что он может / должен что-то изменить.

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