8

Is there any way (possibly a dirty hack) to create an ImmutableArray which will just use a specified array, instead of copying it over?

I have an array which I know won't change, and I want to create an ImmutableArray to allow clients to access my array safely.

Looking at the source code for ImmutableArray, I see the Create method will not help:

public static ImmutableArray<T> Create<T>(params T[] items)
{
    if (items == null)
    {
        return Create<T>();
    }

    // We can't trust that the array passed in will never be mutated by the caller.
    // The caller may have passed in an array explicitly (not relying on compiler params keyword)
    // and could then change the array after the call, thereby violating the immutable
    // guarantee provided by this struct. So we always copy the array to ensure it won't ever change.
    return CreateDefensiveCopy(items);
}

Edit: There is a request for exactly this feature on GitHub, which also gives the quickest hack to use: https://github.com/dotnet/corefx/issues/28064

7
  • 1
    Why not simply use a usual array if you know your element won´t change? Commented Jul 12, 2018 at 7:51
  • Why are you trying to avoid copying? Is this casting performance problems? are you optimising prematurely?
    – Sweeper
    Commented Jul 12, 2018 at 7:54
  • 1
    @sweeper. performance. It's for a vector library, and so has to be fast. An extra iteration through an array in vector.Add would about halve performance Commented Jul 12, 2018 at 7:58
  • 1
    @HimBromBeere. Because I need to give readonly access to the array to client code Commented Jul 12, 2018 at 7:59
  • What about returning an IReadOnlyList<T>?
    – MineR
    Commented Jul 12, 2018 at 8:06

7 Answers 7

14

If you know the exact length of the array, you can use the ImmutableArray.CreateBuilder<> plus the .MoveToImmutable() that will create an ImmutableArray<> from the internals of the Builder without copying it:

var builder = ImmutableArray.CreateBuilder<int>(4);
builder.Add(1);
builder.Add(2);
builder.Add(3);
builder.Add(4);
ImmutableArray<int> array = builder.MoveToImmutable();

The method .MoveToImmutable() will throw an exception if builder.Capacity != builder.Count

Note that other methods of the builder (like .ToImmutable()) will create a copy of the array.

1
  • 2
    When building a new immutable array from scratch, this is the best solution. When there is already an array that just needs to be made immutable, this does not help, though, as it still requires copying the array. Might be worth including this info in the answer.
    – Palec
    Commented Sep 15, 2021 at 18:29
9

In .NET 8 preview no need to use Unsafe anymore.

They exposed some additional methods to access the internal constructor.

Use ImmutableCollectionsMarshal.AsImmutableArray<T>:

ImmutableArray<T> im = ImmutableCollectionsMarshal.AsImmutableArray(array);
5

There is also another two hacky approaches, both suggested here: https://stackoverflow.com/a/3799030/4418060 (one in answer, one in comment).

  1. Marshal one struct type to another.
  2. Unsafely cast one to another.

First one involves creating a new struct type that mirrors layout of ImmutableArray (which is a single T[] field) and changing the type of that struct as seen by CLR (runtime). The struct would look like this:

public struct HackImmutableArray<T>
{
    public T[] Array;
}
  1. Marshalling:

    static ImmutableArray<T> HackyMakeImmutable<T>(T[] array)
    {
        var arrayObject = (object)new HackImmutableArray<T> { Array = array };
        var handle = GCHandle.Alloc(arrayObject, GCHandleType.Pinned);
        var immutable = (ImmutableArray<T>)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
        handle.Free();
        return immutable;
    }
    
  2. Unsafe casting (nice helpers written here, found in this blog post). Casting uses Unsafe static class available in System.Runtime.CompilerServices.Unsafe NuGet

    using System.Runtime.CompilerServices;
    
    static ImmutableArray<T> HackyMakeImmutable<T>(T[] array)
    {
        return Unsafe.As<T[], ImmutableArray<T>>(ref array);
    }
    

The second option is "not safe" but quite safe, as we can with certainty assume ImmutableArray's struct layout not to change, being a defining feature, and it'll also be probably much faster than any other solution.

4

At https://github.com/dotnet/corefx/issues/28064 They recommend the fastest way is using System.Runtime.CompilerServices.Unsafe:

ImmutableArray<T> im = Unsafe.As<T[], ImmutableArray<T>>(ref array);
1

This is probably a bad idea, and they can use the same trick against you, but you can cheat with reflection:

public static ImmutableArray<T> GetImmutableArray<T>(T[] arr)
{
    var immutableArray = ImmutableArray.Create(new T[0]);
    var boxed = ((object) immutableArray);
    var t = boxed.GetType();
    var fi = t.GetField("array", BindingFlags.NonPublic | BindingFlags.Instance);
    fi.SetValue(boxed, arr);
    return (ImmutableArray<T>)boxed;
}

And call it like this:

var arr = new int[] { 1, 2, 3 };
Console.WriteLine("Arr: " + string.Join(",", arr)); //Arr: 1,2,3
var imm = GetImmutableArray(arr);
Console.WriteLine("ImmutableArray: " + string.Join(",", imm)); //ImmutableArray: 1,2,3
arr[0] = 234;
imm[0] = 235; //Compile Error
Console.WriteLine("ImmutableArray: " + string.Join(",", imm)); //ImmutableArray: 234,2,3

The reflection cost would have to be weighed against the Array.Copy cost.

1
  • 1
    @ MineR I love bad ideas. Always makes me feel like more of a programmer ;-). Thanks for this. I'll hold back on it unless really necessary, but it might just come in useful. Commented Jul 12, 2018 at 8:33
0

ReadOnlyCollection<T> could be used to achieve the same purpose in many cases. It is not true it gives access to the original array -- the Items property is protected.

There are two downsides to using ReadOnlyCollection<T> instead of ImmutableArray<T>, though:

  1. An extra allocation takes place. ReadOnlyCollection<T> is a class wrapping an IList<T>, while ImmutableArray<T> is a struct wrapping an T[].
  2. The receiver of the collection has weaker guarantees about immutability. The collection cannot be modified from the outside, but whoever created it may still hold a reference to the original array and may use it to modify the collection.

The guarantee that no such modifications will take place can be provided by documentation, but it is still a somewhat weaker guarantee than the technically enforced one.

4
  • 1
    "The receiver of the collection has weaker guarantees about immutability." It's more like "the receiver of the collection has no guarantees about immutability" IMHO. Commented Sep 15, 2021 at 19:50
  • There are many types of immutability, @TheodorZoulias. learn.microsoft.com/en-us/archive/blogs/ericlippert/…
    – Palec
    Commented Sep 15, 2021 at 19:56
  • 1
    Eric probably forgot to mention two more kinds of immutability. Conventional immutability: you append the suffix Immutable to the name of the array, and hope that the caller will get the memo. Remarked immutability: You write in the <remarks> section that the array will not be changed by the callee, and should not be changed by the caller, otherwise puppies and kittens are going to die. Commented Sep 15, 2021 at 20:41
  • Back to seriousness, Eric talked about kinds of immutability, not degrees of immutability. But now that I am thinking of it, it makes some sense to talk about weaker and stronger guarantees about immutability. A collection that's passed through an obfuscator is probably more immutable than an unobfuscated collection, because it's harder to mutate it through reflection. And a collection contained in a machine covered by a thick lead shield is more immutable than a collection in my PC, because it's harder to get mutated by high energy cosmic rays. Commented Sep 15, 2021 at 20:42
-1

Do you need an ImmutableArray<T> or is an IReadOnlyList<T> enough? If its the latter, you can always implement a very lightweight array wrapper that meets your needs:

public class ImmutableArrayWrapper<T>: IReadOnlyList<T>
{
     public static ImmutableArrayWrapper<T> Wrap(T[] array)
         => new ImmutableArrayWrapper(array);

    private readonly T[] innerArray;

    private ImmutableArrayWrapper(T[] arr) {
         if (arr == null)
             throw new ArgumentNullException();

         innerArray = arr; }

    public int Count => innerArray.Count();
    public T this[int index] => innerArray[index];

    //IEnumerable<T>...
}

And now you can pass the wrapper to your client safely.

4
  • 1
    I would advise using a struct like ImmutableArray, as that means it can have zero overhead compared to an array Commented Jul 12, 2018 at 8:49
  • @YairHalberstadt The problem using an ImmutableArray is that Create copies the array contents; thats the overhead the OP wants to avoid.
    – InBetween
    Commented Jul 12, 2018 at 8:58
  • I am the op ;-). I meant use a struct instead of a class, like immutable array does Commented Jul 12, 2018 at 9:02
  • @YairHalberstadt lol, sorry. Well, if using a struct is a must performance wise then this solution won't do; changing the wrapper to a struct doesn't help if you are using IReadOnlyListz<T> references.
    – InBetween
    Commented Jul 12, 2018 at 9:04

Not the answer you're looking for? Browse other questions tagged or ask your own question.