Кастинг sbyte [] на bool [] и char [] на короткий []
Есть ли в любом случае явно литье/принуждение
-
sbyte[]
или byte[]
в bool[]
-
char[]
на short[]
/ushort[]
В CIL вы регулярно видите что-то вроде
stelem Type sbyte (ldloc pArray) ldc_i4 1 ldc_i4 0
который выполняет pArray[1] = true
где pArray
- это одномерный массив типа bool[]
. Я хочу воспроизвести это в С#, выполнив
(sbyte[])pArray[1] = 1;
К сожалению, это не допускается компилятором С#.
Ответы
Ответ 1
Недокументированный трюк, играйте на свой страх и риск:
(например, здесь и во многих других местах)
[StructLayout(LayoutKind.Explicit)]
public struct ConvSByteBool
{
[FieldOffset(0)]
public sbyte[] In;
[FieldOffset(0)]
public bool[] Out;
}
а потом:
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
Обратите внимание, что этот трюк несовместим с дженериками. Нет Conv<T, U>
!
Трюк лучше всего работает, когда размер элемента в источнике и цели одинаковый (sizeof(sbyte) == sizeof(bool)
). В противном случае существуют некоторые ограничения (описанные в связанном вопросе выше).
Ответ 2
Для этого вы можете использовать новые типы Span<T>
и MemoryMarshal
.
Обратите внимание, что это доступно только в последних версиях С#, и вы должны использовать пакет NuGet для предоставления библиотеки на данный момент, но это изменится.
Так, например, чтобы "бросить" массив символов в короткий массив, вы можете написать код следующим образом:
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'
Этот подход документирован и не делает никаких копий каких-либо данных - и он также чрезвычайно эффективен (по дизайну).
Обратите внимание, что это также работает с 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
Также обратите внимание, что это позволяет вам делать некоторые подозрительные вещи, например:
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
Ответ 3
Это частичный ответ.
Hack:
Компилятор С# и время выполнения полностью не согласуются с тем, какие типы массивов конвертируются друг в друга (как вы намекаете на свой вопрос). Поэтому вы можете избежать запроса компилятора и отложить приведение к времени выполнения, пройдя через System.Array
(или System.Object
или System.Collections.IEnumerable
и т.д.).
Пример:
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));
}
}
Попробуйте онлайн (tio.run)
Выход:
249
253
8
11
Если бы вы избежали промежуточного (Array)
в коде выше, компилятор С# сказал бы вам, что литье невозможно.
Ответ 4
Вы можете использовать метод расширения, например:
namespace ExtensionMethods
{
public static class ByteExt
{
public static bool ToBool(this byte b) => b != 0;
public static bool[] ToBoolArray(this byte[] bytes)
{
bool[] returnValues = new bool[bytes.Length];
for (int i = 0; i < bytes.Length; i++)
returnValues[i] = bytes[i].ToBool();
return returnValues;
}
// Do same for sbyte
}
public static class CharExt
{
public static short[] ToBoolArray(this char[] chars)
{
short[] returnValues = new bool[chars.Length];
for (int i = 0; i < chars.Length; i++)
returnValues[0] = (short)chars[0];
return returnValues;
}
}
}
Тогда просто используйте его так:
byte[] myBytes = new[] {1, 2, 5};
bool[] myBools = myBytes.ToBoolArray();
В С# 8, вероятно, будет то, что называется "Extension Everything", где вы сможете определить свои собственные расширения, как явные, так и неявные.
Синтаксис будет примерно таким:
public extension class ByteExt extends Byte[]
{
public static explicit operator bool[](byte[] bytes)
{
// Same implementation as before
}
}
И может использовать его вот так:
byte[] myBytes = new[] {1, 2, 5};
bool[] myBools = (bool[])myBytes;
Ответ 5
Используйте Array.ConvertAll
следующим образом:
// Input
sbyte[] sbyteArray = { 0, 1, 2, 0 };
byte[] byteArray = { 0, 1, 2, 0 };
// Result
bool[] boolArray1 = Array.ConvertAll(sbyteArray, (item) => Convert.ToBoolean(item));
bool[] boolArray2 = Array.ConvertAll(byteArray, (item) => Convert.ToBoolean(item));
// Input
char[] charArray = { 'A', 'B', 'C' };
// Result
short[] shortArray = Array.ConvertAll(charArray, (item) => Convert.ToInt16(item));
ushort[] ushortArray = Array.ConvertAll(charArray, (item) => Convert.ToUInt16(item));