Search code examples
c#genericsmarshallingunsafe

Copy Byte Array into Generic Type without Boxing


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.


Solution

  • 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 
        }
    }