runtime: Error in `ImmutableArray` initialization using C#12 collection literals

Description

ImmutableArrays are throwing an exception when being initialized at runtime in some circumstances:

  • When debugging the project from Visual Studio
  • When publishing the project (self-contained, trimmed, single-file in my case).

However, they work from CLI

This can be especially painful because tests pass CI but then everything collapses at runtime initialization. Real life CI example:

Simplified example below:

Reproduction Steps

dotnet new console -n immutable

Program.cs

using System.Collections.Immutable;

Console.WriteLine($"Hello, World from {Constants.Coordinates[0]}");


public static class Constants
{
    public static readonly ImmutableArray<string> Coordinates =
    [
        "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8",
        "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7",
        "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6",
        "a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5",
        "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4",
        "a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3",
        "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2",
        "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1"
    ];
}

immutable.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <LangVersion>preview</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

Expected behavior

Runs normally, prints this in all the cases

Hello, World from a8

Actual behavior

Failes from Visual Studio or when the project is published:

$> dotnet run -c Release
Hello, World from a8
$> dotnet run -c Debug
Hello, World from a8
$> dotnet publish -c Release --self-contained /p:PublishSingleFile=true /p:PublishTrimmed=true --runtime win-x64 -o ./output && output/immutable.exe
Unhandled exception. System.TypeInitializationException: The type initializer for 'Constants' threw an exception.
 ---> System.TypeLoadException
   at Constants..cctor()
   --- End of inner exception stack trace ---
   at Program.<Main>$(String[]) in C:\dev\tmp\immutable\Program.cs:line 3
C:\dev\tmp\immutable>

From Visual Studio, while debugging:

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=System.Collections.Immutable
  StackTrace:
   at System.Collections.Immutable.ImmutableArray`1.Add(T item)
   at Constants..cctor() in C:\dev\tmp\immutable\Program.cs:line 8

Regression?

No response

Known Workarounds

No response

Configuration

.NET SDK:
 Version:   8.0.100-rc.1.23455.8
 Commit:    e14caf947f

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19045
 OS Platform: Windows
 RID:         win-x64
 Base Path:   C:\Program Files\dotnet\sdk\8.0.100-rc.1.23455.8\

.NET workloads installed:
There are no installed workloads to display.

Host:
  Version:      8.0.0-rc.1.23419.4
  Architecture: x64
  Commit:       92959931a3
  RID:          win-x64

Microsoft Visual Studio Enterprise 2022 (64-bit) - Version 17.7.0

Other information

No response

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Comments: 19 (18 by maintainers)

Most upvoted comments

The problem when trimming is now fixed in both main and net8.0 branches, so it should ship with 8 RC2 I think.

I can repro a similar problem as @lsoft … I’ll follow up on the debugger part.

@eduherminio I can’t repro the app’s crash with F5.

The debugger differences are potentially explainable though. It seems the debugger doesn’t run static constructors just to show the right values for static fields.

For example:

Console.WriteLine("Break here"); // Stopping here in the debugger Constants.V shows 0

Console.WriteLine(Constants.V); // This prints out 42

class Constants
{
    public static readonly int V = 42;
}

Runtime will run the static constructor (which is what actually assigns the value 42 into the field) before any access to that field, but it does so as late as possible - so in this case it does it right before it reads the value of the field to pass it to WriteLine. The debugger doesn’t seem to trigger it on its own, so it sees the “uninitialized” values before that happens. In the case of ImmutableArray it’s possible that there’s something inside the array which is a reference and when it’s not initialized it’s null -> causing NullRef trying to use it from the debugger. But that should never happen at runtime.

https://github.com/dotnet/runtime/pull/92060 should fix the problem with trimmed app using these.

@agocke

Probably, I see something related when debugging immutables inside VS:

image

image

image

image

Unfortunately, I can’t repro this inside a small project, and can’t share the big project. The things above is everything I can share. Hope this helps.