Reputation: 6841
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
Upvotes: 9
Views: 4033
Reputation: 1
private static readonly ImmutableArray<Key> FinisherKeys = new[]
{
Key.Down,
Key.Return,
Key.Enter,
}.ToImmutableArray();
or
private static readonly ImmutableArray<Key> FinisherKeys = [
..new[]
{
Key.Down,
Key.Return,
Key.Enter,
},
];
Upvotes: -2
Reputation: 9666
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);
Upvotes: 9
Reputation: 13581
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:
ReadOnlyCollection<T>
is a class wrapping an IList<T>
, while ImmutableArray<T>
is a struct wrapping an T[]
.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.
Upvotes: 1
Reputation: 111890
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.
Upvotes: 17
Reputation: 189
There is also another two hacky approaches, both suggested here: https://stackoverflow.com/a/3799030/4418060 (one in answer, one in comment).
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;
}
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;
}
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.
Upvotes: 5
Reputation: 6841
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);
Upvotes: 4
Reputation: 32770
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.
Upvotes: 0
Reputation: 2204
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.
Upvotes: 1