It appears that C# 12's collection initializers don't produce the same result as a ImmutableArray's Empty property; for example:
using System;
using System.Collections.Immutable;
public class Program
{
public static void Main()
{
ImmutableArray<object> a = ImmutableArray<object>.Empty;
ImmutableArray<object> b = ImmutableArray<object>.Empty;
ImmutableArray<object> c = [];
ImmutableArray<object> d = [];
Console.WriteLine($"a == b: {a == b}");
Console.WriteLine($"a == c: {a == c}");
Console.WriteLine($"c == d: {c == d}");
}
}
Run with .NET Fiddle
a == b: True
a == c: False
c == d: True
What's the reason for this?
Some notes: On further reflection, I've updated the post, as changing the code to use ImmutableList<T> yields true in all cases.
TL;DR
ImmutableArray<T>.Equals compares underlying arrays:
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is IImmutableArray other && this.array == other.Array;
}
public bool Equals(ImmutableArray<T> other)
{
return this.array == other.array;
}
And all instances of ImmutableArray<object>.Empty will point to the same array and all instances of ImmutableArray<object> _ = []; will point to the same array but those would be 2 different empty arrays.
Details
While specification explicitly mentions that:
The empty collection expression is permitted to be a singleton if used to construct a final collection value that is known to not be mutable.
And ImmutableArray<T> having create method proxied with CollectionBuilderAttribute(source code) to ImmutableArray.Create:
The method must have a single parameter of type
System.ReadOnlySpan<E>, passed by value,
[CollectionBuilder(typeof(ImmutableArray), nameof(ImmutableArray.Create))]
public readonly partial struct ImmutableArray<T> :
public static ImmutableArray<T> Create<T>(ReadOnlySpan<T> items)
{
if (items.IsEmpty)
{
return ImmutableArray<T>.Empty;
}
T[] array = items.ToArray();
return new ImmutableArray<T>(array);
}
And ImmutableArray<T>.Empty is:
#pragma warning disable CA1825
// Array.Empty<T>() doesn't exist in all configurations
// Switching to Array.Empty also has a non-negligible impact on the working set memory
public static readonly ImmutableArray<T> Empty = new ImmutableArray<T>(new T[0]);
#pragma warning restore CA1825
But as you can see in the actual decompilation of empty collection expression results in:
ImmutableCollectionsMarshal.AsImmutableArray(Array.Empty<object>())
Which just creates a new ImmutableArray:
public static ImmutableArray<T> AsImmutableArray<T>(T[]? array)
{
return new(array);
}
So ImmutableArray<object>.Empty and ImmutableArray<object> c = [] will point to different locations in memory (but they will be the same for the same expressions used). I.e. all ImmutableArray<object>.Empty will be equal and all ImmutableArray<object> _ = [] will be equal but not equal to each other. Note that in general it does not break any guarantees made by specification (since it is not even guaranteed to be a singleton).
Submitted issue @gitub
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With