3

I'm developing a C# class wherein I need to be able to take in a byte array and copy it to a generic variable of the same size. In C/C++ such a thing (the copying) would be easy, but in C# not so much.

MyClass<T>
{
  public T Value = default(T);

  public MyClass(byte[] bytes)
  {
    // how to copy `bytes` into `Value`?
  }
}

I'd prefer not to use boxing. Is there a way to do this using marshaling, reflection, or unmanaged/unsafe code?


I did find this other post, but the only suggested answer won't work because it uses boxing.

Jim Fell
  • 13,750
  • 36
  • 127
  • 202
  • 1
    Do you control the process that creates the byte array? What's _in_ the byte array? Is `T` constrained in any way? Are you simply looking for (de)serialization? – CodeCaster Feb 05 '19 at 15:17
  • 1
    Whenever you write "doesn´t work for me" you should describe **precisely** what exactly does not work? – MakePeaceGreatAgain Feb 05 '19 at 15:18
  • 1
    Just a side note: When an object is created with new, all fields are initialized to their default value automatically. You can drop the `= default(T)`. – Olivier Jacot-Descombes Feb 05 '19 at 15:18
  • See for example [How to convert an object to a byte array in C#](https://stackoverflow.com/questions/1446547/how-to-convert-an-object-to-a-byte-array-in-c-sharp). – CodeCaster Feb 05 '19 at 15:18
  • 1
    I think you're misunderstanding the use of generics. Generics aren't meant to stay generic, your code needs to provide a type. Maybe you want to store the bytes array in an object type or dynamic type? – Daniel Feb 05 '19 at 15:20
  • @CodeCaster Yes, the process that creates the byte array is controlled by design. `T` will always be something like `float`, `uint`, `ushort`, `byte`, etc. – Jim Fell Feb 05 '19 at 15:20
  • question: are you using "recent" .NET versions? if so, we can use `Span` for this; otherwise, we'll probably need to use `Unsafe` - hence it matters :) – Marc Gravell Feb 05 '19 at 15:21
  • @HimBromBeere The answer in the other post uses boxing. Boxing doesn't work because of maintenance issues. – Jim Fell Feb 05 '19 at 15:22
  • If `Value` is a single float/uint/ushort/etc.. (a primitive value type), how would you transform a byte **array** into such a primitive value type. I can speculate of course about what you are trying to do, but then again, better you state it explicitly... (please **edit** and improve your question, don't explain here in the comments) –  Feb 05 '19 at 15:23
  • @elgonzo Going the other direction I do something like this `(byte[])typeof(BitConverter).GetMethod("GetBytes", new[] { typeof(T) }).Invoke(null, new[] { value })`, but that won't work starting with `byte[]` because `BitConverter` uses numerous methods to convert `byte[]` into the various primitive types. – Jim Fell Feb 05 '19 at 15:25
  • 1
    @elgonzo each type you mention uses more than one byte for storage. – CodeCaster Feb 05 '19 at 15:25
  • 1
    Without unsafe code/reflection, no, because any reflection-less code you're going to write here will need to first determine which type of `T` to use, and then execute specific code for that type, but that will leave you with a value of type `int`, not `T` (even though `T` = `int` in this case), and you're going to have to cast it via an object in order to get it stored, or use reflection. So no, this can't be done, at least not to my knowledge. (seeing the answer by Marc, then my knowledge is flawed :)) – Lasse V. Karlsen Feb 05 '19 at 15:26
  • @CodeCaster, i know that. What in my comment made you think i believe that float/short/uint... etc would have a byte size of 1? –  Feb 05 '19 at 15:27

1 Answers1

6

If you're using up-to-date .NET, you can use Span<T> (System.Buffers) for this:

class MyClass<T> where T : struct
{
    public T Value = default(T);

    public MyClass(byte[] bytes)
    {
        Value = MemoryMarshal.Cast<byte, T>(bytes)[0];
    }
}

You can also use unsafe in recent C# versions (for the T : unmanaged constraint):

class MyClass<T> where T : unmanaged
{
    public T Value = default(T);

    public unsafe MyClass(byte[] bytes)
    {
        fixed (byte* ptr = bytes)
        {
            Value = *(T*)ptr; // note: no out-of-range check here; dangerous
        }
    }
}

You can also do some things here using Unsafe.* methods (System.Runtime.CompilerServices.Unsafe); for example (note no constraints):

class MyClass<T>
{
    public T Value = default(T);

    public unsafe MyClass(byte[] bytes)
    {
        T local = default(T);
        fixed (byte* ptr = bytes)
        {
            Unsafe.Copy(ref local, ptr); // note: no out-of-range check here; dangerous
        }
        Value = local;
    }
}

If you want to check the out-of-range problem:

if (bytes.Length < Unsafe.SizeOf<T>())
    throw new InvalidOperationException("Not enough data, fool!");

or you can use sizeof(T) if you have the T : unmanaged constraint. You don't need this with the Span<T> solution (the first one), because the original Cast<byte, T> will yield a span of length zero in that scenario, and the [0] will throw appropriately.


I think this should work too!

public unsafe MyClass(byte[] bytes)
{
    Value = Unsafe.As<byte, T>(ref bytes[0]); // note: no out-of-range check here; dangerous
}

complete example (works on net462):

using System;
using System.Runtime.CompilerServices;


struct Foo
{
    public int x, y;
}
class MyClass<T>
{
    public T Value = default(T);

    public unsafe MyClass(byte[] bytes)
    {
        if (bytes.Length < Unsafe.SizeOf<T>())
            throw new InvalidOperationException("not enough data");
        Value = Unsafe.As<byte, T>(ref bytes[0]);
    }
}
static class P
{
    static void Main() {
        byte[] bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
        var obj = new MyClass<Foo>(bytes);
        var val = obj.Value;
        Console.WriteLine(val.x); // 67305985 = 0x04030201
        Console.WriteLine(val.y); // 134678021 = 0x08070605 
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • FYI: Whether this works as intended depends on the order/endianess of the bytes in byte array. So far it is not clear where OP gets the bytes from and in which order/endianess they are stored in the byte array. –  Feb 05 '19 at 15:30
  • @elgonzo oh, absolutely; frankly, **any serializer** (even perhaps `BinaryFormatter` on a good day) is preferable to brute-forcing bytes into structs – Marc Gravell Feb 05 '19 at 15:33
  • @MarcGravell My application is using .NET 4.6.2. I've added `using System.Runtime.InteropServices`, but MemoryMarshal isn't recognized. Is that what's you'd expect? I'll try your other suggestions as well. – Jim Fell Feb 05 '19 at 15:38
  • @Jim https://www.nuget.org/packages/System.Runtime.CompilerServices.Unsafe/ goes down to 4.5.2 - it might not have all the methods, though... and I doubt you can use the `Span` version; give me a sec, I'll check – Marc Gravell Feb 05 '19 at 15:40
  • @Jim https://www.nuget.org/packages/System.Buffers/ goes down to 4.5 - you should be golden either way – Marc Gravell Feb 05 '19 at 15:41
  • @JimFell yeah, I checked the last version (`Unsafe.As`) on net462 - worked fine – Marc Gravell Feb 05 '19 at 15:43
  • @JimFell full example added to the answer – Marc Gravell Feb 05 '19 at 15:46
  • @MarcGravell Nuget is new to me, but I finally got your example go compile. Thanks so much! – Jim Fell Feb 05 '19 at 15:59
  • 1
    @JimFell you might as well get familiar with nuget - most of the interesting Microsoft libraries now ship on nuget, as it allows more frequent updates - plus of course the *huge* range of open source libraries there – Marc Gravell Feb 05 '19 at 16:00