Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting sbyte[] to bool[] and a char[] to short[]

Tags:

c#

casting

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.

like image 302
Nick Avatar asked May 23 '18 09:05

Nick


3 Answers

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).

like image 183
xanatos Avatar answered Sep 29 '22 03:09

xanatos


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
like image 27
Matthew Watson Avatar answered Sep 29 '22 02:09

Matthew Watson


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.

like image 37
Jeppe Stig Nielsen Avatar answered Sep 29 '22 03:09

Jeppe Stig Nielsen