Search code examples
c#optimizationimmutable-collections

Create an ImmutableArray without copying


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


Solution

  • 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.