JBlam.Collections.ValueArray 0.0.2

dotnet add package JBlam.Collections.ValueArray --version 0.0.2                
NuGet\Install-Package JBlam.Collections.ValueArray -Version 0.0.2                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="JBlam.Collections.ValueArray" Version="0.0.2" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add JBlam.Collections.ValueArray --version 0.0.2                
#r "nuget: JBlam.Collections.ValueArray, 0.0.2"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install JBlam.Collections.ValueArray as a Cake Addin
#addin nuget:?package=JBlam.Collections.ValueArray&version=0.0.2

// Install JBlam.Collections.ValueArray as a Cake Tool
#tool nuget:?package=JBlam.Collections.ValueArray&version=0.0.2                

JBlam.Collections.ValueArray

An immutable array wrapper with value equality semantics.

Usage

using JBlam.Collections.ValueArray;

// Initialise with a collection expression.
ValueArray<int> arr = [1, 2, 3];

// Iterate like any other IEnumerable.
foreach (var value in arr)
	Console.WriteLine(value);

// Collect IEnumerables into a ValueArray instance.
var second = Enumerable.Range(1, 3).ToValueArray();

// Value equality!
Console.WriteLine($"ValueArrays are equal? {arr == second}");

// Default value does not throw!
var empty = default(ValueArray<int>);
Console.WriteLine($"Am I safe from NullReferenceExceptions in value types now? {empty.Length == 0}");

// Implicit conversion to/from ImmutableArray<T> means you can use ImmutableArray.Builder.
var builder = System.Collections.Immutable.ImmutableArray.CreateBuilder<int>();
builder.Add(1);
builder.Add(2);
builder.Add(3);
System.Collections.Immutable.ImmutableArray<int> immutableArray = builder.ToImmutable();
// implicit conversion happens here ↓
ValueArray<int> third = immutableArray;
Console.WriteLine($"Converted from ImmutableArray? {arr == third}");

Why not ImmutableArray?

This is a drop in replacement for System.Collections.Immutable.ImmutableArray<T> which aims to resolve three issues:

  1. The default value is logically empty, rather than exploding with null reference exceptions.
  2. It implements value equality, and is therefore suitable to use in record types.
  3. It overrides the default string representation to print at least some of the contents, similar to the generated ToString for record types.

Platform Support

ValueArray targets net8.0 which at time of writing was the LTS release. I've also multitargeted netstandard2.0 because I want to use it in Roslyn analysers and source-generators; and also net6.0 because I have some work projects which use that.

The System.Text.Json integration is not available for netstandard2.0 because it's outside my use-case. Minor bits of API are also excluded, but mostly I'm providing some shims.

Implementation details

The struct layout of ValueArray is identical to ImmutableArray. The type design is similar, except it special-cases the default value, provides value equality, and overrides ToString to print its contents.

Converting to and from ImmutableArray<T> is effectively free. (Roughly the cost of copying a single reference.)

I will release this with minimal API around it, on the understanding that if you want "immutable mutations" you can easily convert to the equivalent ImmutableArray, do your business, and convert back.

Rants

ImmutableArray<T> is a value type which wraps a hidden reference to a reference type, and as such default contains a null reference. This is a massive footgun, because the only safe way to interact with an ImmutableArray is to inspect it for IsDefault; that's incredibly unexpected because C# programmers don't normally inspect value types for null. It also hides the null reference from the nullable type annotation system, which otherwise makes NullReferenceException more-or-less a solved problem.

ValueArray<T> solves this issue by making default logically equal to an empty array.

But wait! if default is equal to [], doesn't that mean equality is broken? Well, have I got a story to tell you ...

ImmutableArray<T> is a value type which wraps a hidden reference to a reference type and doesn't manage equality meaning that ImmutableArray<T>.Equals is effectively reference-equality on the hidden reference. This is again highly surprising for the C# developer who assumes that value types support value equality, because they are values.

ValueArray<T> solves this issue by making Equals return sequence equality. Yes, that's O(n). I suggest not making ValueArray<T> wrap multiple gigabytes of data.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.0.2 108 5/20/2024

0.0.2; Initial release as an independent package