// 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); } }