reinterpret_cast в C #

Я ищу способ переинтерпретировать массив типа byte [] как другой тип, например short []. В C ++ это было бы достигнуто простым актом, но в C # я не нашел способ достичь этого, не прибегая к дублированию всего буфера.

Есть идеи?

Вы можете добиться этого, но это относительно плохая идея. Такой доступ к необработанной памяти не является безопасным для типов и может выполняться только в полной безопасности. Вы никогда не должны делать этого в правильно разработанном управляемом приложении. Если ваши данные маскируются под двумя разными формами, возможно, у вас на самом деле есть два отдельных набора данных?

В любом случае, вот быстрый и простой fragment кода, чтобы выполнить то, что вы просили:

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

c # поддерживает это, пока вы готовы использовать небезопасный код, но только для структур.

например: (Framework предоставляет это для вас, но вы можете расширить его до преобразования int <-> uint

 public unsafe long DoubleToLongBits(double d) { return *((long*) (void*) &d); } 

Поскольку массивы являются ссылочными типами и содержат свои собственные метаданные об их типе, вы не можете переинтерпретировать их, не перезаписывая также заголовок метаданных на экземпляре (операция может завершиться неудачей).

Вы можете вывести foo * из foo [] и отбросить его на бар * (используя вышеприведенную технику) и использовать его для итерации по массиву. Для этого потребуется привязать исходный массив к времени жизни использования интерпретируемого указателя.

На этот вопрос есть четыре хороших ответа. У каждого есть разные недостатки. Конечно, остерегайтесь энтузиазма и понимайте, что все эти ответы – это дыры в системе типов, а не только предательские дыры. Короче говоря, не делайте этого много, и только тогда, когда вам действительно нужно.

  1. Ответ Сандера . Используйте небезопасный код для повторной интерпретации указателей. Это самое быстрое решение, но оно использует небезопасный код. Не всегда вариант.

  2. Ответ Леонидаса . Используйте StructLayout и FieldOffset(0) чтобы превратить структуру в объединение. Недостатком этого является то, что некоторые (редкие) среды не поддерживают StructLayout (например, Flash-сборки в Unity3D) и что StructLayout не может использоваться с дженериками.

  3. Ответ. Используйте методы BitConverter . Это имеет тот недостаток, что большинство методов выделяют память, которая невелика в низкоуровневом коде. Кроме того, не существует полного набора этих методов, поэтому вы не можете использовать его в общих чертах.

  4. Buffer.BlockCopy два массива разных типов. Единственным недостатком является то, что вам нужны два буфера, что идеально подходит при преобразовании массивов, но боль при бросании одного значения. Остерегайтесь того, что длина указана в байтах, а не в элементах. Buffer.ByteLength помогает. Кроме того, он работает только с примитивами, такими как ints, float и bools, а не с строками или enumsми.

Но вы можете сделать с ним аккуратные вещи.

 public static class Cast { private static class ThreadLocalType { [ThreadStatic] private static T[] buffer; public static T[] Buffer { get { if (buffer == null) { buffer = new T[1]; } return buffer; } } } public static TTarget Reinterpret(TSource source) { TSource[] sourceBuffer = ThreadLocalType.Buffer; TTarget[] targetBuffer = ThreadLocalType.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(100)); Console.ReadKey(); } } 

Вы можете обернуть ваши шорты / байты в структуру, которая позволит вам получить доступ к обоим значениям:

См. Также: Объединение C ++ в C #

 использование системы;
 используя System.Collections.Generic;
 используя System.Runtime.InteropServices;

 namespace TestShortUnion {
     [StructLayout (LayoutKind.Explicit)]
     public struct shortbyte {
         открытый статический неявный оператор shortbyte (ввод int) {
             если (вход> короткий.MaxValue)
                 throw new ArgumentOutOfRangeException («ввод», «shortbyte принимает только значения в ближнем»);
             return new shortbyte ((короткий) ввод);
         }

         открытый shortbyte (байтовый ввод) {
             shortval = 0;
             byteval = input;
         }

         открытый shortbyte (короткий ввод) {
             byteval = 0;
             shortval = input;
         }

         [FieldOffset (0)]
         public byte byteval;
         [FieldOffset (0)]
         открытый короткий короткий срок;
     }

     classа Программа {
         static void Main (string [] args) {
             shortbyte [] testarray = новый 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 ();
         }
     }
 }

Кастинг, подобный этому, принципиально небезопасен и не разрешен на управляемом языке. Вот почему C # не поддерживает союзы. Да, обходным путем является использование classа маршала.

Такое поведение приведет к тому, что C # будет скорее небезопасным по типу. Однако вы можете легко достичь этого безопасным типом, хотя (конечно, вы копируете массив при этом).

Если вы хотите, чтобы один байт отображался на один короткий, это просто, используя 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); } 

Возможно, можно использовать маршаллера, чтобы сделать что-то странное, чтобы достичь этого, возможно, используя небезопасный блок кода {...}, хотя это будет подвержено ошибкам и сделает ваш код непроверенным.

Я подозреваю, что то, что вы пытаетесь сделать, может быть достигнуто лучше, используя идиому типа безопасного, а не тип-небезопасный C / C ++ один!

Обновление : обновлено, чтобы принять во внимание комментарий.

Разве не удалось бы создать class коллекции, который реализует интерфейс для байтов и шорт? Может быть, реализовать как IList , так и IList ? Затем вы можете иметь свою базовую коллекцию, содержащую байты, но реализовать функции IList , которые работают с байтовыми парами.

Я использовал код из 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