To complement Mathias R. Jessen's helpful answer with background information and more convenient solutions:
C# structs (.NET value types) do not support members that are embedded arrays of fixed length:
It is only ever a reference (pointer) to an array (of unspecified size) that is stored in the struct itself, and that reference is null when an instance of the struct is created by default.
An alternative to allocating and assigning a fixed-size array to your .thresholds field after construction of an instance is to declare a public constructor that performs the array initialization.
In C# 10+ / PowerShell (Core) 7.2.+, you can do this as follows:
$typeCode = @'
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[Serializable]
public struct MyConfig
{
// Explicit default (parameterless) constructor that initializes the fields.
// Note: ALL fields must be initialized.
public MyConfig() { level = 0; thresholds = new Int32[16]; }
public Int16 level;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public Int32[] thresholds;
}
'@
Add-Type -TypeDefinition $typeCode
# Construct an instance.
[MyConfig]::new()
The above prints the following to the display; the {0, 0, 0, 0…} part implies that the array was allocated:
{0, 0, 0, 0…}
In C# 9- / Windows PowerShell and earlier PowerShell (Core) versions), parameterless constructors aren't supported, so a workaround is needed:
Declare a constructor with a dummy parameter that has a default value:
$typeCode = @'
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[Serializable]
public struct MyConfig
{
// Note the `int unused = 0` dummy parameter.
public MyConfig(int unused = 0) { level = 0; thresholds = new Int32[16]; }
public Int16 level;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public Int32[] thresholds;
}
'@
Add-Type -TypeDefinition $typeCode
# Construct an instance.
# Do NOT use New-Object MyConfig (see below).
[MyConfig]::new()
Caveats:
The constructor with the optional parameter is only called if you use [MyConfig]::new() to construct an instance; New-Object MyConfig does not do that, even though you'd expect the two command forms to be equivalent. The problem has been reported in GitHub issue #18049
In C# code, using default(MyConfig) does not call either constructor, because the purpose of default() is simply to zero out the structs members.