C#中的reinterpret_cast

时间:2009-01-26 12:58:47

标签: c# arrays casting

我正在寻找一种方法来重新解释byte []类型的数组作为不同的类型,比如short []。在C ++中,这可以通过简单的转换来实现,但在C#中我没有找到实现这一目的的方法而不需要复制整个缓冲区。

有什么想法吗?

9 个答案:

答案 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)

这个问题有四个很好的答案。每个都有不同的缺点。当然,要注意字节序,并意识到所有这些答案都是类型系统中的漏洞,而不是特别危险的漏洞。简而言之,不要做太多,只有当你真的需要时才这样做。

  1. Sander的回答。使用不安全的代码重新解释指针。这是最快的解决方案,但它使用不安全的代码。并不总是一种选择。

  2. Leonidas'回答。使用StructLayoutFieldOffset(0)将结构转换为联合。其缺点是某些(罕见)环境不支持StructLayout(例如Unity3D中的Flash构建),并且StructLayout不能与泛型一起使用。

  3. ljs'回答。使用BitConverter方法。这样做的缺点是大多数方法都会分配内存,这在低级代码中并不是很好。此外,还没有完整的这些方法,所以你不能真正使用它。

  4. Buffer.BlockCopy两个不同类型的数组。唯一的缺点是你需要两个缓冲区,这在转换数组时是完美的,但在投射单个值时会很痛苦。请注意,长度以字节为单位,而不是元素。 Buffer.ByteLength有帮助。此外,它只适用于原语,如整数,浮点数和布尔值,而不是结构或枚举。

  5. 但你可以用它做一些巧妙的事情。

    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)

你可以将你的短裤/字节包装成一个允许你访问这两个值的结构:

另见:C++ union in C#

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