Are immutable arrays possible in .NET?
Asked Answered
S

10

29

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.

Stpeter answered 16/10, 2008 at 21:49 Comment(6)
That property is missing a name, isn't it?Ungracious
The property is called "immutable" - it's in this sense that strings are immutable (unless you access the char pointer in unsafe code, which you really shouldn't do because they're interned.)Overcharge
Keep in mind that an immutable array is only as immutable as its members. @PhilWhittington He meant in the code, the property is missing a name. It's syntactically incorrect. It's not really meant to be a working example, though.Toadstool
I know this was a few years ago, but Svish was pointing out, you should have public string[] variablename { ... } He's saying, that property is missing a name...Eudoxia
Maybe its not actual anymore but there is a System.Collections.Immutable.ImmutableArray in.netUntruthful
Please be aware that immutability and read-only aren't the same. You can read more about it here.Dispensatory
H
27

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

Heraclitean answered 16/10, 2008 at 21:53 Comment(5)
More accurately, it doesn't have a publicly exposed Add() method. It does have one, because the interfaces require it. But good interface code will check .ReadOnly first before calling it, because doing so would cause an exception.Unthrone
This isn't really the same thing as an immutable array -- ReadOnlyCollection doesn't allow any modifications, but it just wraps a regular List, which can still be changed by the creator.Splenetic
@HenryJackson In the end all data in .NET is mutable via Reflection. So meaningful immutability is simply a matter of making the mutability inaccessible. A read-only wrapper to a mutable collection certainly counts as immutable if the reference to the mutable collection is then discarded, making mutation impossible.Kedgeree
@AndrewArnott: Unfortunately, neither ReadOnlyCollection nor any other similar Framework type provides any means by which an instance can promise that its backing store is immutable (e.g. because the wrapper created a clone of the original array, to which it holds the only reference anywhere in the universe). There are many situations in which code receiving a collection should make a snapshot unless it's immutable, in which case it shouldn't bother, but there's no way to find out whether a snapshot is necessary.Spradlin
If I'm not wrong, Immutability and read-only aren't the same.Dispensatory
C
29

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.

Cris answered 16/10, 2008 at 22:11 Comment(1)
The second option (char[])InvalidPathChars.Clone(); does return a copy but it is modifiable by the client i.e. he can use his copy for his own purpose and the original array remains unaffected.Dispensatory
H
27

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

Heraclitean answered 16/10, 2008 at 21:53 Comment(5)
More accurately, it doesn't have a publicly exposed Add() method. It does have one, because the interfaces require it. But good interface code will check .ReadOnly first before calling it, because doing so would cause an exception.Unthrone
This isn't really the same thing as an immutable array -- ReadOnlyCollection doesn't allow any modifications, but it just wraps a regular List, which can still be changed by the creator.Splenetic
@HenryJackson In the end all data in .NET is mutable via Reflection. So meaningful immutability is simply a matter of making the mutability inaccessible. A read-only wrapper to a mutable collection certainly counts as immutable if the reference to the mutable collection is then discarded, making mutation impossible.Kedgeree
@AndrewArnott: Unfortunately, neither ReadOnlyCollection nor any other similar Framework type provides any means by which an instance can promise that its backing store is immutable (e.g. because the wrapper created a clone of the original array, to which it holds the only reference anywhere in the universe). There are many situations in which code receiving a collection should make a snapshot unless it's immutable, in which case it shouldn't bother, but there's no way to find out whether a snapshot is necessary.Spradlin
If I'm not wrong, Immutability and read-only aren't the same.Dispensatory
M
14

Please see Immutable Collections Now Available in the base class library.

Melliemelliferous answered 2/1, 2013 at 19:35 Comment(0)
M
8

You could use Array.AsReadOnly method to return.

Mousebird answered 16/10, 2008 at 21:57 Comment(1)
Just to add an information - ReadOnlyCollection class doesn't have API like Add, Add<T>, Remove or Remove<T> to ensure read-only nature. At the same time their indexers are also read-only. If we try using set indexer, it results in compile time error - char[] myCharArray = { '\"', '<', '>', '|' }; var b1 = Array.AsReadOnly(myCharArray); b1[0] = '\0'; //results in compile time error.Dispensatory
S
2

I believe best practice is to use IList<> rather than arrays in public APIs for this exact reason. readonly will prevent a member variable from being set outside of the constructor, but as you discovered, won't prevent people from assigning elements in the array.

See Eric Lippert's article Arrays Considered Somewhat Harmful for more information.

Edit: Arrays can't be read only, but they can be converted to read-only IList implementations via Array.AsReadOnly() as @shahkalpesh points out.

Sottish answered 16/10, 2008 at 21:52 Comment(0)
M
0

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.

Maxon answered 16/10, 2008 at 23:26 Comment(1)
Is this a convention or is there a more specific reason for clients expecting this behaviour?Ho
C
0

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.

Complete answered 17/10, 2008 at 11:49 Comment(0)
B
0

The best you can hope to do is extend an existing collection to build your own. The big issue is that it would have to work differently than every existing collection type because every call would have to return a new collection.

Biltong answered 17/10, 2008 at 15:34 Comment(0)
T
0

.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' }
Toadstool answered 25/7, 2013 at 8:15 Comment(0)
S
0

Yes, You can now use ImmutableArray with .NET Core 1.0+

using System.Collections.Immutable;

var arr1 = new [] {1,2,3}.ToImmutableArray();
var arr2 = ImmutableArray.Create(new [] {1,2,3});

Demo in DotNetFiddle

See Also: Please welcome ImmutableArray by Immo Landwerth

Sain answered 14/9, 2022 at 12:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.