Get unsafe pointer to array KeyValuePair <DateTime, decimal> in C #
I have large arrays of KeyValuePair<DateTime,decimal> . I know that in memory the array is contiguous, since KVP is a value type, DateTime is actually Int64, and decimal is an array of 4 ints (and this will not change). However, DateTime is not blittable, and decimal is not primitive.
Is there any way to abuse the type system and get an unsafe array pointer and work with it like bytes? ( GCHandle.Alloc cannot work with these two types when they are part of the structure, but it works fine with arrays of this type.)
(If this interests you, I now convert the array manually to what, in my opinion, is a 1-by-1 byte [], and it's slow)
Finally, there is a publicly available tool: System.Runtime.CompilerServices.Unsafe package.
Below is the test:
using System.Runtime.CompilerServices.Unsafe; [Test] public unsafe void CouldUseNewUnsafePackage() { var dt = new KeyValuePair<DateTime, decimal>[2]; dt[0] = new KeyValuePair<DateTime, decimal>(DateTime.UtcNow.Date, 123.456M); dt[1] = new KeyValuePair<DateTime, decimal>(DateTime.UtcNow.Date.AddDays(1), 789.101M); var obj = (object)dt; byte[] asBytes = Unsafe.As<byte[]>(obj); //Console.WriteLine(asBytes.Length); // prints 2 fixed (byte* ptr = &asBytes[0]) { // reading this: https://github.com/dotnet/coreclr/issues/5870 // it looks like we could fix byte[] and actually KeyValuePair<DateTime, decimal> will be fixed // because: // "GC does not care about the exact types, eg if type of local object // reference variable is not compatible with what is actually stored in it, // the GC will still track it fine." for (int i = 0; i < (8 + 16) * 2; i++) { Console.WriteLine(*(ptr + i)); } var firstDate = *(DateTime*)ptr; Assert.AreEqual(DateTime.UtcNow.Date, firstDate); Console.WriteLine(firstDate); var firstDecimal = *(decimal*)(ptr + 8); Assert.AreEqual(123.456M, firstDecimal); Console.WriteLine(firstDecimal); var secondDate = *(DateTime*)(ptr + 8 + 16); Assert.AreEqual(DateTime.UtcNow.Date.AddDays(1), secondDate); Console.WriteLine(secondDate); var secondDecimal = *(decimal*)(ptr + 8 + 16 + 8); Assert.AreEqual(789.101M, secondDecimal); Console.WriteLine(secondDecimal); } } I just tested that unsafe and GCHandle.Alloc not working (as you said). There is a terribly unsafe hack to still do this. I do not know if this is safe with the current CLR. In the future, of course, work is not guaranteed.
You can convert a reference to an object of any type to any other reference type in IL. This IL is not subject to verification. JIT tends to accept many non-verifiable designs. Perhaps this is because they wanted to support Managed C ++.
So, you need to create a DynamicMethod that roughly has the following IL:
static T UnsafeCast(object value) { ldarg.1 //load type object ret //return type T } I think this should work ...
Or you can call System.Runtime.CompilerServices.JitHelpers.UnsafeCast<T> using Reflection.
This is a dangerous tool ... I would not use it in production code.