// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace CommunityToolkit.HighPerformance.Helpers;
///
/// Helpers for working with instances.
///
public static class ObjectMarshal
{
///
/// Calculates the byte offset to a specific field within a given .
///
/// The type of field being referenced.
/// The input hosting the target field.
/// A reference to a target field of type within .
///
/// The value representing the offset to the target field from the start of the object data
/// for the parameter . The offset is in relation to the first usable byte after the method table.
///
/// The input parameters are not validated, and it's responsibility of the caller to ensure that
/// the reference is actually pointing to a memory location within .
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr DangerousGetObjectDataByteOffset(object obj, ref T data)
{
RawObjectData? rawObj = Unsafe.As(obj)!;
ref byte r0 = ref rawObj.Data;
ref byte r1 = ref Unsafe.As(ref data);
return Unsafe.ByteOffset(ref r0, ref r1);
}
///
/// Gets a reference to data within a given at a specified offset.
///
/// The type of reference to retrieve.
/// The input hosting the target field.
/// The input byte offset for the reference to retrieve.
/// A reference at a specified offset within .
///
/// None of the input arguments is validated, and it is responsibility of the caller to ensure they are valid.
/// In particular, using an invalid offset might cause the retrieved reference to be misaligned with the
/// desired data, which would break the type system. Or, if the offset causes the retrieved reference to point
/// to a memory location outside of the input instance, that might lead to runtime crashes.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetObjectDataReferenceAt(object obj, IntPtr offset)
{
RawObjectData? rawObj = Unsafe.As(obj)!;
ref byte r0 = ref rawObj.Data;
ref byte r1 = ref Unsafe.AddByteOffset(ref r0, offset);
ref T r2 = ref Unsafe.As(ref r1);
return ref r2;
}
// Description adapted from CoreCLR, see:
// https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,301.
// CLR objects are laid out in memory as follows:
// [ sync block || pMethodTable || raw data .. ]
// ^ ^
// | \-- ref Unsafe.As(owner).Data
// \-- object
// The reference to RawObjectData.Data points to the first data byte in the
// target object, skipping over the sync block, method table and string length.
// Even though the description above links to the CoreCLR source, this approach
// can actually work on any .NET runtime, as it doesn't rely on a specific memory
// layout. Even if some 3rd party .NET runtime had some additional fields in the
// object header, before the field being referenced, the returned offset would still
// be valid when used on instances of that particular type, as it's only being
// used as a relative offset from the location pointed by the object reference.
[StructLayout(LayoutKind.Explicit)]
private sealed class RawObjectData
{
[FieldOffset(0)]
public byte Data;
}
///
/// Tries to get a boxed value from an input instance.
///
/// The type of value to try to unbox.
/// The input instance to check.
/// The resulting value, if was in fact a boxed value.
/// if a value was retrieved correctly, otherwise.
///
/// This extension behaves just like the following method:
///
/// public static bool TryUnbox<T>(object obj, out T value)
/// {
/// if (obj is T)
/// {
/// value = (T)obj;
///
/// return true;
/// }
///
/// value = default;
///
/// return false;
/// }
///
/// But in a more efficient way, and with the ability to also assign the unboxed value
/// directly on an existing T variable, which is not possible with the code above.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryUnbox(this object obj, out T value)
where T : struct
{
if (obj.GetType() == typeof(T))
{
value = Unsafe.Unbox(obj);
return true;
}
value = default;
return false;
}
///
/// Unboxes a value from an input instance.
///
/// The type of value to unbox.
/// The input instance, representing a boxed value.
/// The value boxed in .
/// Thrown when is not of type .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousUnbox(object obj)
where T : struct
{
return ref Unsafe.Unbox(obj);
}
}