tags:

views:

677

answers:

6

Hi

I'm looking for a way to reinterpret an array of type byte[] as a different type, say short[]. In C++ this would be achieved by a simple cast but in C# I haven't found a way to achieve this without resorting to duplicating the entire buffer.

Any ideas?

+1  A: 

Casting like this is fundamentally unsafe and not permitted in a managed language. That's also why C# doesn't support unions. Yes, the workaround is to use the Marshal class.

Hans Passant
C# supports unions just fine - take a look at StructLayout and LayoutKind.Explict
ShuggyCoUk
Yes but that's hardly idiomatic is it?!
kronoz
What do you mean not idiomatic - This is the [prescribed](http://msdn.microsoft.com/en-us/library/acxa5b99(VS.80).aspx) way to achieve unions in c#. you can't get much more idiomatic than that. I fear you don't understand what idiomatic actually **means**
ShuggyCoUk
As can be seen in the answer by Sander, you *can* do this in C#. The trick/trap is that pointer arithmetic is restricted to within unsafe{} bocks.
Bevan
+1  A: 

This kind of behaviour would result in C# being rather type-unsafe. You can easily achieve this in a type-safe manner, however (though of course you are copying the array in doing so).

If you want one byte to map to one short then it's simple using ConvertAll, e.g.:-

short[] shorts = Array.ConvertAll(bytes, b => (short)b);

If you want to simply map every 2 bytes to a short then the following should work:-

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

It may be possible to use the marshaller to do some weird bit-twiddling to achieve this, probably using an unsafe { ... } code block, though this would be prone to errors and make your code unverifiable.

I suspect what you're trying to do can be achieved better using a type-safe idiom rather than a type-unsafe C/C++ one!

Update: Updated to take into account comment.

kronoz
Wrong. This will convert EACH byte to a short, not interpret the array as a short array (2 bytes per short).
Sander
Good point - However if he did mean to do so, then that is the solution though of course as you rightly point out that isn't what a reinterpret_cast would do. Have added code to do the re-interpreted conversion.
kronoz
Well this is less efficient than Sander's unsafe version, however it is type-safe...!
kronoz
+2  A: 

c# supports this so long as you are willing to use unsafe code but only on structs.

for example : (The framework provides this for you but you could extend this to int <-> uint conversion

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

Since the arrays are reference types and hold their own metadata about their type you cannot reinterpret them without overwriting the metadata header on the instance as well (an operation likely to fail).

You can howveer take a foo* from a foo[] and cast that to a bar* (via the technique above) and use that to iterate over the array. Doing this will require you pin the original array for the lifetime of the reinterpreted pointer's use.

ShuggyCoUk
how are you going to interpret a pair of bytes using this method? I don't think it's possible.
kronoz
you must reinterpret a pair of bytes into a short, ushort or other struct of size 2. Obviously they must be adjacent in memory for this.
ShuggyCoUk
+6  A: 

You can achieve this but this is a relatively bad idea. Raw memory access like this is not type-safe and can only be done under a full trust security environment. You should never do this in a properly designed managed application. If your data is masquerading under two different forms, perhaps you actually have two separate data sets?

In any case, here is a quick and simple code snippet to accomplish what you asked:

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]);
     }
    }
}
Sander
This solution is probably the most efficient approach if you're willing to use unverifiable code. However as you say, this is not a good idea.
kronoz
A: 

Wouldn't it be possible to create a collection class that implements an interface for both bytes and shorts? Maybe implement both IList< byte > and IList< short >? Then you can have your underlying collection contain bytes, but implement IList< short > functions that work on byte pairs.

Joe
+1  A: 

You could wrap your shorts/bytes into a structure which allows you to access both values:

See also here: 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();
        }
    }
}
Leonidas