I have two structs with arrays of bytes and booleans:
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct1
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] values;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct2
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public bool[] values;
}
And the following code:
class main
{
public static void Main()
{
Console.WriteLine("sizeof array of bytes: "+Marshal.SizeOf(typeof(struct1)));
Console.WriteLine("sizeof array of bools: " + Marshal.SizeOf(typeof(struct2)));
Console.ReadKey();
}
}
That gives me the following output:
sizeof array of bytes: 3
sizeof array of bools: 12
It seems to be that a boolean
takes 4 bytes of storage. Ideally a boolean
would only take one bit (false
or true
, 0
or 1
, etc..).
What is happening here? Is the boolean
type really so inefficient?
Firstly, this is only the size for interop. It doesn't represent the size in managed code of the array. That's 1 byte per bool
- at least on my machine. You can test it for yourself with this code:
using System;
class Program
{
static void Main(string[] args)
{
int size = 10000000;
object array = null;
long before = GC.GetTotalMemory(true);
array = new bool[size];
long after = GC.GetTotalMemory(true);
double diff = after - before;
Console.WriteLine("Per value: " + diff / size);
// Stop the GC from messing up our measurements
GC.KeepAlive(array);
}
}
Now, for marshalling arrays by value, as you are, the documentation says:
When the MarshalAsAttribute.Value property is set to
ByValArray
, the SizeConst field must be set to indicate the number of elements in the array. TheArraySubType
field can optionally contain theUnmanagedType
of the array elements when it is necessary to differentiate among string types. You can use thisUnmanagedType
only on an array that whose elements appear as fields in a structure.
So we look at ArraySubType
, and that has documentation of:
You can set this parameter to a value from the
UnmanagedType
enumeration to specify the type of the array's elements. If a type is not specified, the default unmanaged type corresponding to the managed array's element type is used.
Now looking at UnmanagedType
, there's:
Bool
A 4-byte Boolean value (true != 0, false = 0). This is the Win32 BOOL type.
So that's the default for bool
, and it's 4 bytes because that corresponds to the Win32 BOOL type - so if you're interoperating with code expecting a BOOL
array, it does exactly what you want.
Now you can specify the ArraySubType
as I1
instead, which is documented as:
A 1-byte signed integer. You can use this member to transform a Boolean value into a 1-byte, C-style bool (true = 1, false = 0).
So if the code you're interoperating with expects 1 byte per value, just use:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I1)]
public bool[] values;
Your code will then show that as taking up 1 byte per value, as expected.
See more on this question at Stackoverflow