Is there anyway to explicitly cast/coerce
sbyte[]
or byte[]
to a bool[]
char[]
to a short[]
/ushort[]
In CIL you regularly see something such as
stelem Type sbyte (ldloc pArray) ldc_i4 1 ldc_i4 0
which is doing pArray[1] = true
where pArray
is a one-dimensional array of type bool[]
. I want to replicate this in c# by doing
(sbyte[])pArray[1] = 1;
Unfortunately this is not allowed by the C# compiler.
Undocumented trick, play at your own risk:
(shown for example here and in many other places)
[StructLayout(LayoutKind.Explicit)]
public struct ConvSByteBool
{
[FieldOffset(0)]
public sbyte[] In;
[FieldOffset(0)]
public bool[] Out;
}
and then:
var bytes = new sbyte[] { -2, -1, 0, 1, 2 };
var conv = new ConvSByteBool { In = bytes }.Out;
bool b1 = conv[0]; // true
bool b2 = conv[1]; // true
bool b3 = conv[2]; // false
bool b4 = conv[3]; // true
bool b5 = conv[4]; // true
Note that this trick is tottally incompatible with generics. No Conv<T, U>
!
The trick works best when the size of the element in source and target is the same (sizeof(sbyte) == sizeof(bool)
). Otherwise there are some limitations (described in the linked question above).
You can use the new Span<T>
and MemoryMarshal
types to do this.
Note that this is only available with recent versions of C#, and you have to use a NuGet package to provide the library at the moment, but that will change.
So for example to "cast" a char array to a short array, you can write code like this:
var charArray = new char[100];
Span<short> shortArray = MemoryMarshal.Cast<char, short>(charArray);
charArray[0] = 'X';
Console.WriteLine(charArray[0]); // Prints 'X'
++shortArray[0];
Console.WriteLine(charArray[0]); // Prints 'Y'
This approach is documented and does not make any copies of any data - and it's also extremely performant (by design).
Note that this also works with structs:
struct Test
{
public int X;
public int Y;
public override string ToString()
{
return $"X={X}, Y={Y}";
}
}
...
var testArray = new Test[100];
Span<long> longArray = MemoryMarshal.Cast<Test, long>(testArray);
testArray[0].X = 1;
testArray[0].Y = 2;
Console.WriteLine(testArray[0]); // Prints X=1, Y=2
longArray[0] = 0x0000000300000004;
Console.WriteLine(testArray[0]); // Prints X=4, Y=3
Also note that this allows you to do some suspect things, like this:
struct Test1
{
public int X;
public int Y;
public override string ToString()
{
return $"X={X}, Y={Y}";
}
}
struct Test2
{
public int X;
public int Y;
public int Z;
public override string ToString()
{
return $"X={X}, Y={Y}, Z={Z}";
}
}
...
var test1 = new Test1[100];
Span<Test2> test2 = MemoryMarshal.Cast<Test1, Test2>(test1);
test1[1].X = 1;
test1[1].Y = 2;
Console.WriteLine(test1[1]); // Prints X=1, Y=2
test2[0].Z = 10; // Actually sets test1[1].X.
Console.WriteLine(test1[1]); // Prints X=10, Y=2
This is a partial answer only.
Hack:
The C# compiler and the run-time do not agree entirely on what array types are convertible to each other (as you are hinting at in your question). So you can avoid asking the compiler and defer the cast to run-time, by going through System.Array
(or System.Object
or System.Collections.IEnumerable
etc.).
An example:
using System;
static class P
{
static void Main()
{
var a = new sbyte[] { -7, -3, 8, 11, };
var hack = (byte[])(Array)a;
Console.WriteLine(string.Join("\r\n", hack));
}
}
Try it online (tio.run)
Output:
249 253 8 11
If you were to avoid the intermediate (Array)
in the code above, the C# compiler would tell you that the cast is impossible.
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