1
0
mirror of https://github.com/chylex/.NET-Community-Toolkit.git synced 2024-07-25 01:28:50 +02:00
.NET-Community-Toolkit/CommunityToolkit.HighPerformance/Box{T}.cs
2021-12-29 18:06:54 +01:00

222 lines
10 KiB
C#

// 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.Runtime.CompilerServices;
namespace CommunityToolkit.HighPerformance;
/// <summary>
/// A <see langword="class"/> that represents a boxed <typeparamref name="T"/> value on the managed heap.
/// This is a "shadow" type that can be used in place of a non-generic <see cref="object"/> reference to a
/// boxed value type, to make the code more expressive and reduce the chances of errors.
/// Consider this example:
/// <code>
/// object obj = 42;
///
/// // Manual, error prone unboxing
/// int sum = (int)obj + 1;
/// </code>
/// In this example, it is not possible to know in advance what type is actually being boxed in a given
/// <see cref="object"/> instance, making the code less robust at build time. The <see cref="Box{T}"/>
/// type can be used as a drop-in replacement in this case, like so:
/// <code>
/// Box&lt;int> box = 42;
///
/// // Build-time validation, automatic unboxing
/// int sum = box.Value + 1;
/// </code>
/// 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.
/// </summary>
/// <typeparam name="T">The type of value being boxed.</typeparam>
[DebuggerDisplay("{ToString(),raw}")]
public sealed class Box<T>
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<T>(Box<T>)
// \-- Box<T> 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.
/// <summary>
/// Initializes a new instance of the <see cref="Box{T}"/> class.
/// </summary>
/// <remarks>
/// This constructor is never used, it is only declared in order to mark it with
/// the <see langword="private"/> visibility modifier and prevent direct use.
/// </remarks>
/// <exception cref="InvalidOperationException">Always thrown when this constructor is used (eg. from reflection).</exception>
private Box()
{
throw new InvalidOperationException("The CommunityToolkit.HighPerformance.Box<T> constructor should never be used.");
}
/// <summary>
/// Returns a <see cref="Box{T}"/> reference from the input <see cref="object"/> instance.
/// </summary>
/// <param name="obj">The input <see cref="object"/> instance, representing a boxed <typeparamref name="T"/> value.</param>
/// <returns>A <see cref="Box{T}"/> reference pointing to <paramref name="obj"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Box<T> GetFrom(object obj)
{
if (obj.GetType() != typeof(T))
{
ThrowInvalidCastExceptionForGetFrom();
}
return Unsafe.As<Box<T>>(obj)!;
}
/// <summary>
/// Returns a <see cref="Box{T}"/> reference from the input <see cref="object"/> instance.
/// </summary>
/// <param name="obj">The input <see cref="object"/> instance, representing a boxed <typeparamref name="T"/> value.</param>
/// <returns>A <see cref="Box{T}"/> reference pointing to <paramref name="obj"/>.</returns>
/// <remarks>
/// This method doesn't check the actual type of <paramref name="obj"/>, so it is responsibility of the caller
/// to ensure it actually represents a boxed <typeparamref name="T"/> value and not some other instance.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Box<T> DangerousGetFrom(object obj)
{
return Unsafe.As<Box<T>>(obj)!;
}
/// <summary>
/// Tries to get a <see cref="Box{T}"/> reference from an input <see cref="object"/> representing a boxed <typeparamref name="T"/> value.
/// </summary>
/// <param name="obj">The input <see cref="object"/> instance to check.</param>
/// <param name="box">The resulting <see cref="Box{T}"/> reference, if <paramref name="obj"/> was a boxed <typeparamref name="T"/> value.</param>
/// <returns><see langword="true"/> if a <see cref="Box{T}"/> instance was retrieved correctly, <see langword="false"/> otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetFrom(object obj, [NotNullWhen(true)] out Box<T>? box)
{
if (obj.GetType() == typeof(T))
{
box = Unsafe.As<Box<T>>(obj)!;
return true;
}
box = null;
return false;
}
/// <summary>
/// Implicitly gets the <typeparamref name="T"/> value from a given <see cref="Box{T}"/> instance.
/// </summary>
/// <param name="box">The input <see cref="Box{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator T(Box<T> box)
{
return (T)(object)box;
}
/// <summary>
/// Implicitly creates a new <see cref="Box{T}"/> instance from a given <typeparamref name="T"/> value.
/// </summary>
/// <param name="value">The input <typeparamref name="T"/> value to wrap.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Box<T>(T value)
{
// The Box<T> type is never actually instantiated.
// Here we are just boxing the input T value, and then reinterpreting
// that object reference as a Box<T> reference. As such, the Box<T>
// 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<T> type. For instance, boxing a float
// and calling ToString() on it directly, on its boxed object or on a Box<T>
// reference retrieved from it will produce the same result in all cases.
return Unsafe.As<Box<T>>(value)!;
}
/// <inheritdoc/>
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<T> reference to an actual
// boxed T value (not a concrete Box<T> 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<T> 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()!;
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return Equals(this, obj);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetReference().GetHashCode();
}
/// <summary>
/// Throws an <see cref="InvalidCastException"/> when a cast from an invalid <see cref="object"/> is attempted.
/// </summary>
private static void ThrowInvalidCastExceptionForGetFrom()
{
throw new InvalidCastException($"Can't cast the input object to the type Box<{typeof(T)}>");
}
}
/// <summary>
/// Helpers for working with the <see cref="Box{T}"/> type.
/// </summary>
public static class BoxExtensions
{
/// <summary>
/// Gets a <typeparamref name="T"/> reference from a <see cref="Box{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of reference to retrieve.</typeparam>
/// <param name="box">The input <see cref="Box{T}"/> instance.</param>
/// <returns>A <typeparamref name="T"/> reference to the boxed value within <paramref name="box"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReference<T>(this Box<T> box)
where T : struct
{
// The reason why this method is an extension and is not part of
// the Box<T> type itself is that Box<T> 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<T> 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<T>(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<T>(box);
}
}