我正在寻找一种方法来重新解释byte []类型的数组作为不同的类型,比如short []。在C ++中,这可以通过简单的转换来实现,但在C#中我没有找到实现这一目的的方法而不需要复制整个缓冲区。
有什么想法吗?
答案 0 :(得分:10)
你可以做到这一点,但这是一个相对糟糕的主意。像这样的原始内存访问不是类型安全的,只能在完全信任的安全环境下完成。您绝不应该在设计合理的托管应用程序中执行此操作。如果您的数据伪装成两种不同的形式,也许您实际上有两个独立的数据集?
无论如何,这里有一个快速简单的代码片段来完成你的要求:
byte[] bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int byteCount = bytes.Length;
unsafe
{
// By using the fixed keyword, we fix the array in a static memory location.
// Otherwise, the garbage collector might move it while we are still using it!
fixed (byte* bytePointer = bytes)
{
short* shortPointer = (short*)bytePointer;
for (int index = 0; index < byteCount / 2; index++)
{
Console.WriteLine("Short {0}: {1}", index, shortPointer[index]);
}
}
}
答案 1 :(得分:4)
这个问题有四个很好的答案。每个都有不同的缺点。当然,要注意字节序,并意识到所有这些答案都是类型系统中的漏洞,而不是特别危险的漏洞。简而言之,不要做太多,只有当你真的需要时才这样做。
Sander的回答。使用不安全的代码重新解释指针。这是最快的解决方案,但它使用不安全的代码。并不总是一种选择。
Leonidas'回答。使用StructLayout
和FieldOffset(0)
将结构转换为联合。其缺点是某些(罕见)环境不支持StructLayout(例如Unity3D中的Flash构建),并且StructLayout不能与泛型一起使用。
ljs'回答。使用BitConverter
方法。这样做的缺点是大多数方法都会分配内存,这在低级代码中并不是很好。此外,还没有完整的这些方法,所以你不能真正使用它。
Buffer.BlockCopy
两个不同类型的数组。唯一的缺点是你需要两个缓冲区,这在转换数组时是完美的,但在投射单个值时会很痛苦。请注意,长度以字节为单位,而不是元素。 Buffer.ByteLength
有帮助。此外,它只适用于原语,如整数,浮点数和布尔值,而不是结构或枚举。
但你可以用它做一些巧妙的事情。
public static class Cast {
private static class ThreadLocalType<T> {
[ThreadStatic]
private static T[] buffer;
public static T[] Buffer
{
get
{
if (buffer == null) {
buffer = new T[1];
}
return buffer;
}
}
}
public static TTarget Reinterpret<TTarget, TSource>(TSource source)
{
TSource[] sourceBuffer = ThreadLocalType<TSource>.Buffer;
TTarget[] targetBuffer = ThreadLocalType<TTarget>.Buffer;
int sourceSize = Buffer.ByteLength(sourceBuffer);
int destSize = Buffer.ByteLength(targetBuffer);
if (sourceSize != destSize) {
throw new ArgumentException("Cannot convert " + typeof(TSource).FullName + " to " + typeof(TTarget).FullName + ". Data types are of different sizes.");
}
sourceBuffer[0] = source;
Buffer.BlockCopy(sourceBuffer, 0, targetBuffer, 0, sourceSize);
return targetBuffer[0];
}
}
class Program {
static void Main(string[] args)
{
Console.WriteLine("Float: " + Cast.Reinterpret<int, float>(100));
Console.ReadKey();
}
}
答案 2 :(得分:3)
c#支持这个。
例如:(框架为您提供此功能,但您可以将其扩展为int&lt; - &gt; uint转换
public unsafe long DoubleToLongBits(double d)
{
return *((long*) (void*) &d);
}
由于数组是引用类型并保存有关其类型的自己的元数据,因此无法在不覆盖实例上的元数据头的情况下重新解释它们(操作可能会失败)。
你可以如何从foo []中获取foo *并将其转换为bar *(通过上面的技术)并使用它来迭代数组。执行此操作将要求您在重新解释指针的使用期限内固定原始数组。
答案 3 :(得分:2)
你可以将你的短裤/字节包装成一个允许你访问这两个值的结构:
using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace TestShortUnion { [StructLayout(LayoutKind.Explicit)] public struct shortbyte { public static implicit operator shortbyte(int input) { if (input > short.MaxValue) throw new ArgumentOutOfRangeException("input", "shortbyte only accepts values in the short-range"); return new shortbyte((short)input); } public shortbyte(byte input) { shortval = 0; byteval = input; } public shortbyte(short input) { byteval = 0; shortval = input; } [FieldOffset(0)] public byte byteval; [FieldOffset(0)] public short shortval; } class Program { static void Main(string[] args) { shortbyte[] testarray = new shortbyte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1111 }; foreach (shortbyte singleval in testarray) { Console.WriteLine("Byte {0}: Short {1}", singleval.byteval, singleval.shortval); } System.Console.ReadLine(); } } }
答案 4 :(得分:1)
这样的转换从根本上说是不安全的,不允许使用托管语言。这也是C#不支持工会的原因。是的,解决方法是使用Marshal类。
答案 5 :(得分:1)
这种行为会导致C#相当类型不安全。但是,您可以通过类型安全的方式轻松实现此目的(当然,您正在复制数组)。
如果你想要一个字节映射到一个short,那么使用ConvertAll很简单,例如: -
short[] shorts = Array.ConvertAll(bytes, b => (short)b);
如果您想简单地将每2个字节映射到一个短片,那么以下内容应该有效: -
if (bytes.Length % 2 != 0)
{
throw new ArgumentException("Byte array must have even rank.");
}
short[] shorts = new short[bytes.Length / 2];
for (int i = 0; i < bytes.Length / 2; ++i)
{
shorts[i] = BitConverter.ToInt16(bytes, 2*i);
}
有可能使用marshaller做一些奇怪的比特来实现这一点,可能使用了一个不安全的{...}代码块,尽管这会容易出错并使你的代码无法验证。
我怀疑使用类型安全的习惯而不是类型不安全的C / C ++,可以更好地实现你想要做的事情!
更新:已更新,以考虑评论。
答案 6 :(得分:0)
是不是可以创建一个实现字节和短路接口的集合类?也许实现IList&lt;字节&gt;和IList&lt;短&gt;?然后,您可以让您的底层集合包含字节,但实现IList&lt;简短&gt;在字节对上工作的函数。
答案 7 :(得分:0)
我使用FastArraySerializer中的代码创建了一个类型转换器,以便从SByte []到Double []
public unsafe class ConvertArrayType
{
[StructLayout(LayoutKind.Explicit)]
private struct Union
{
[FieldOffset(0)] public sbyte[] sbytes;
[FieldOffset(0)] public double[] doubles;
}
private Union _union;
public double[] doubles {
get { return _union.doubles; }
}
public sbyte[] sbytes
{
get { return _union.sbytes; }
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct ArrayHeader
{
public UIntPtr type;
public UIntPtr length;
}
private readonly UIntPtr SBYTE_ARRAY_TYPE;
private readonly UIntPtr DOUBLE_ARRAY_TYPE;
public ConvertArrayType(Array ary, Type newType)
{
fixed (void* pBytes = new sbyte[1])
fixed (void* pDoubles = new double[1])
{
SBYTE_ARRAY_TYPE = getHeader(pBytes)->type;
DOUBLE_ARRAY_TYPE = getHeader(pDoubles)->type;
}
Type typAry = ary.GetType();
if (typAry == newType)
throw new Exception("No Type change specified");
if (!(typAry == typeof(SByte[]) || typAry == typeof(double[])))
throw new Exception("Type Not supported");
if (newType == typeof(Double[]))
{
ConvertToArrayDbl((SByte[])ary);
}
else if (newType == typeof(SByte[]))
{
ConvertToArraySByte((Double[])ary);
}
else
{
throw new Exception("Type Not supported");
}
}
private void ConvertToArraySByte(double[] ary)
{
_union = new Union { doubles = ary };
toArySByte(_union.doubles);
}
private void ConvertToArrayDbl(sbyte[] ary)
{
_union = new Union { sbytes = ary };
toAryDouble(_union.sbytes);
}
private ArrayHeader* getHeader(void* pBytes)
{
return (ArrayHeader*)pBytes - 1;
}
private void toAryDouble(sbyte[] ary)
{
fixed (void* pArray = ary)
{
var pHeader = getHeader(pArray);
pHeader->type = DOUBLE_ARRAY_TYPE;
pHeader->length = (UIntPtr)(ary.Length / sizeof(double));
}
}
private void toArySByte(double[] ary)
{
fixed (void* pArray = ary)
{
var pHeader = getHeader(pArray);
pHeader->type = SBYTE_ARRAY_TYPE;
pHeader->length = (UIntPtr)(ary.Length * sizeof(double));
}
}
} // ConvertArrayType{}
这是VB用法:
Dim adDataYgch As Double() = Nothing
Try
Dim nGch As GCHandle = GetGch(myTag)
If GCHandle.ToIntPtr(nGch) <> IntPtr.Zero AndAlso nGch.IsAllocated Then
Dim asb As SByte()
asb = CType(nGch.Target, SByte())
Dim cvt As New ConvertArrayType(asb, GetType(Double()))
adDataYgch = cvt.doubles
End If
Catch ex As Exception
Debug.WriteLine(ex.ToString)
End Try
答案 8 :(得分:0)
您可以使用System.Memory安全地执行此操作。
public static TTo[] Cast<TFrom, TTo>(this TFrom[] source) where TTo : struct where TFrom : struct =>
MemoryMarshal.Cast<TFrom, TTo>(source).ToArray();
private byte[] CastToBytes(int[] foo) => foo.Cast<int, byte>(foo);