// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
namespace CommunityToolkit.HighPerformance;
///
/// A that represents a boxed value on the managed heap.
/// This is a "shadow" type that can be used in place of a non-generic reference to a
/// boxed value type, to make the code more expressive and reduce the chances of errors.
/// Consider this example:
///
/// object obj = 42;
///
/// // Manual, error prone unboxing
/// int sum = (int)obj + 1;
///
/// In this example, it is not possible to know in advance what type is actually being boxed in a given
/// instance, making the code less robust at build time. The
/// type can be used as a drop-in replacement in this case, like so:
///
/// Box<int> box = 42;
///
/// // Build-time validation, automatic unboxing
/// int sum = box.Value + 1;
///
/// This type can also be useful when dealing with large custom value types that are also boxed, as
/// it allows to retrieve a mutable reference to the boxing value. This means that a given boxed
/// value can be mutated in-place, instead of having to allocate a new updated boxed instance.
///
/// The type of value being boxed.
[DebuggerDisplay("{ToString(),raw}")]
public sealed class Box
where T : struct
{
// Boxed value types in the CLR are represented in memory as simple objects that store the method table of
// the corresponding T value type being boxed, and then the data of the value being boxed:
// [ sync block || pMethodTable || boxed T value ]
// ^ ^
// | \-- Unsafe.Unbox(Box)
// \-- Box reference
// For more info, see: https://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/.
// Note that there might be some padding before the actual data representing the boxed value,
// which might depend on both the runtime and the exact CPU architecture.
// This is automatically handled by the unbox !!T instruction in IL, which
// unboxes a given value type T and returns a reference to its boxed data.
///
/// Initializes a new instance of the class.
///
///
/// This constructor is never used, it is only declared in order to mark it with
/// the visibility modifier and prevent direct use.
///
/// Always thrown when this constructor is used (eg. from reflection).
private Box()
{
throw new InvalidOperationException("The CommunityToolkit.HighPerformance.Box constructor should never be used.");
}
///
/// Returns a reference from the input instance.
///
/// The input instance, representing a boxed value.
/// A reference pointing to .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Box GetFrom(object obj)
{
if (obj.GetType() != typeof(T))
{
ThrowInvalidCastExceptionForGetFrom();
}
return Unsafe.As>(obj)!;
}
///
/// Returns a reference from the input instance.
///
/// The input instance, representing a boxed value.
/// A reference pointing to .
///
/// This method doesn't check the actual type of , so it is responsibility of the caller
/// to ensure it actually represents a boxed value and not some other instance.
///
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Box DangerousGetFrom(object obj)
{
return Unsafe.As>(obj)!;
}
///
/// Tries to get a reference from an input representing a boxed value.
///
/// The input instance to check.
/// The resulting reference, if was a boxed value.
/// if a instance was retrieved correctly, otherwise.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetFrom(object obj, [NotNullWhen(true)] out Box? box)
{
if (obj.GetType() == typeof(T))
{
box = Unsafe.As>(obj)!;
return true;
}
box = null;
return false;
}
///
/// Implicitly gets the value from a given instance.
///
/// The input instance.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator T(Box box)
{
return (T)(object)box;
}
///
/// Implicitly creates a new instance from a given value.
///
/// The input value to wrap.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Box(T value)
{
// The Box type is never actually instantiated.
// Here we are just boxing the input T value, and then reinterpreting
// that object reference as a Box reference. As such, the Box
// type is really only used as an interface to access the contents
// of a boxed value type. This also makes it so that additional methods
// like ToString() or GetHashCode() will automatically be referenced from
// the method table of the boxed object, meaning that they don't need to
// manually be implemented in the Box type. For instance, boxing a float
// and calling ToString() on it directly, on its boxed object or on a Box
// reference retrieved from it will produce the same result in all cases.
return Unsafe.As>(value)!;
}
///
public override string ToString()
{
// Here we're overriding the base object virtual methods to ensure
// calls to those methods have a correct results on all runtimes.
// For instance, not doing so is causing issue on .NET Core 2.1 Release
// due to how the runtime handles the Box reference to an actual
// boxed T value (not a concrete Box instance as it would expect).
// To fix that, the overrides will simply call the expected methods
// directly on the boxed T values. These methods will be directly
// invoked by the JIT compiler when using a Box reference. When
// an object reference is used instead, the call would be forwarded
// to those same methods anyway, since the method table for an object
// representing a T instance is the one of type T anyway.
return this.GetReference().ToString()!;
}
///
public override bool Equals(object? obj)
{
return Equals(this, obj);
}
///
public override int GetHashCode()
{
return this.GetReference().GetHashCode();
}
///
/// Throws an when a cast from an invalid is attempted.
///
private static void ThrowInvalidCastExceptionForGetFrom()
{
throw new InvalidCastException($"Can't cast the input object to the type Box<{typeof(T)}>");
}
}
///
/// Helpers for working with the type.
///
public static class BoxExtensions
{
///
/// Gets a reference from a instance.
///
/// The type of reference to retrieve.
/// The input instance.
/// A reference to the boxed value within .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReference(this Box box)
where T : struct
{
// The reason why this method is an extension and is not part of
// the Box type itself is that Box is really just a mask
// used over object references, but it is never actually instantiated.
// Because of this, the method table of the objects in the heap will
// be the one of type T created by the runtime, and not the one of
// the Box type. To avoid potential issues when invoking this method
// on different runtimes, which might handle that scenario differently,
// we use an extension method, which is just syntactic sugar for a static
// method belonging to another class. This isn't technically necessary,
// but it's just an extra precaution since the syntax for users remains
// exactly the same anyway. Here we just call the Unsafe.Unbox(object)
// API, which is hidden away for users of the type for simplicity.
// Note that this API will always actually involve a conditional
// branch, which is introduced by the JIT compiler to validate the
// object instance being unboxed. But since the alternative of
// manually tracking the offset to the boxed data would be both
// more error prone, and it would still introduce some overhead,
// this doesn't really matter in this case anyway.
return ref Unsafe.Unbox(box);
}
}