Are immutable arrays possible in .NET?

2019-02-05 15:27发布

Is it possible to somehow mark a System.Array as immutable. When put behind a public-get/private-set they can't be added to, since it requires re-allocation and re-assignment, but a consumer can still set any subscript they wish:

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

I thought the readonly keyword might do the trick, but no such luck.

10条回答
放我归山
2楼-- · 2019-02-05 15:43

.NET tends to steer away from arrays for all but the simplest and most traditional use cases. For everything else, there are various enumerable/collection implementations.

When you want to mark a set of data as immutable, you're going beyond the capability provided by a traditional array. .NET provides equivalent capability, but not technically in the form of an array. To get an immutable collection from an array, use Array.AsReadOnly<T>:

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

var immutable = Array.AsReadOnly(mutable);

immutable will be a ReadOnlyCollection<char> instance. As a more general use case, you can create a ReadOnlyCollection<T> from any generic IList<T> implementation.

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

Note that it has to be a generic implementation; plain old IList won't work, meaning that you can't use this method on a traditional array, which only implements IList. This brings to light the possibility of using Array.AsReadOnly<T> as a quick means of obtaining access to generic implementations that are normally inaccessible via a traditional array.

ReadOnlyCollection<T> will give you access to all of the features that you would expect from an immutable array:

// 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' }
查看更多
做自己的国王
3楼-- · 2019-02-05 15:45

The Framework Design Guidelines suggest returning a copy of the Array. That way, consumers can't change items from the array.

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

these are better:

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

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

The examples are straight from the book.

查看更多
我只想做你的唯一
4楼-- · 2019-02-05 15:48

You could use Array.AsReadOnly method to return.

查看更多
何必那么认真
5楼-- · 2019-02-05 15:48

Further to Matt's answer, IList is a complete abstract interface to an array, so it allows add, remove, etc. I'm not sure why Lippert appears to suggest it as an alternative to IEnumerable where immutability is needed. (Edit: because the IList implementation can throw exceptions for those mutating methods, if you like that kind of thing).

Maybe another thing to bear in mind that the items on the list may also have mutable state. If you really don't want the caller to modify such state, you have some options:

Make sure the items on the list are immutable (as in your example: string is immutable).

Return a deep clone of everything, so in that case you could use an array anyway.

Return an interface that gives readonly access to an item:

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

Note that every value accessible from the IImmutable interface must itself be immutable (e.g. string), or else be a copy that you make on-the-fly.

查看更多
干净又极端
6楼-- · 2019-02-05 15:48

ReadOnlyCollection<T> is probably what you are looking for. It doesn't have an Add() method.

查看更多
三岁会撩人
7楼-- · 2019-02-05 15:50

The only thing to add is that Arrays imply mutability. When you return an Array from a function, you are suggesting to the client programmer that they can/should change things.

查看更多
登录 后发表回答