|
| 1 | +// Licensed to the .NET Foundation under one or more agreements. |
| 2 | +// The .NET Foundation licenses this file to you under the MIT license. |
| 3 | +// See the LICENSE file in the project root for more information. |
| 4 | + |
| 5 | +using System; |
| 6 | +using System.Diagnostics; |
| 7 | +using System.Diagnostics.CodeAnalysis; |
| 8 | +using System.Diagnostics.Contracts; |
| 9 | +using System.Runtime.CompilerServices; |
| 10 | + |
| 11 | +#nullable enable |
| 12 | + |
| 13 | +namespace Microsoft.Toolkit.HighPerformance |
| 14 | +{ |
| 15 | + /// <summary> |
| 16 | + /// A <see langword="class"/> that represents a boxed <typeparamref name="T"/> value on the managed heap. |
| 17 | + /// </summary> |
| 18 | + /// <typeparam name="T">The type of value being boxed.</typeparam> |
| 19 | + [DebuggerDisplay("{ToString(),raw}")] |
| 20 | + public sealed class Box<T> |
| 21 | + where T : struct |
| 22 | + { |
| 23 | + // Boxed value types in the CLR are represented in memory as simple objects that store the method table of |
| 24 | + // the corresponding T value type being boxed, and then the data of the value being boxed: |
| 25 | + // [ sync block || pMethodTable || boxed T value ] |
| 26 | + // ^ ^ |
| 27 | + // | \-- Unsafe.Unbox<T>(Box<T>) |
| 28 | + // \-- Box<T> reference |
| 29 | + // For more info, see: https://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/. |
| 30 | + // Note that there might be some padding before the actual data representing the boxed value, |
| 31 | + // which might depend on both the runtime and the exact CPU architecture. |
| 32 | + // This is automatically handled by the unbox !!T instruction in IL, which |
| 33 | + // unboxes a given value type T and returns a reference to its boxed data. |
| 34 | + |
| 35 | + /// <summary> |
| 36 | + /// Initializes a new instance of the <see cref="Box{T}"/> class. |
| 37 | + /// </summary> |
| 38 | + /// <remarks> |
| 39 | + /// This constructor is never used, it is only declared in order to mark it with |
| 40 | + /// the <see langword="private"/> visibility modifier and prevent direct use. |
| 41 | + /// </remarks> |
| 42 | + private Box() |
| 43 | + { |
| 44 | + } |
| 45 | + |
| 46 | + /// <summary> |
| 47 | + /// Returns a <see cref="Box{T}"/> reference from the input <see cref="object"/> instance. |
| 48 | + /// </summary> |
| 49 | + /// <param name="obj">The input <see cref="object"/> instance, representing a boxed <typeparamref name="T"/> value.</param> |
| 50 | + /// <returns>A <see cref="Box{T}"/> reference pointing to <paramref name="obj"/>.</returns> |
| 51 | + [Pure] |
| 52 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 53 | + public static Box<T> GetFrom(object obj) |
| 54 | + { |
| 55 | + if (obj.GetType() != typeof(T)) |
| 56 | + { |
| 57 | + ThrowInvalidCastExceptionForGetFrom(); |
| 58 | + } |
| 59 | + |
| 60 | + return Unsafe.As<Box<T>>(obj); |
| 61 | + } |
| 62 | + |
| 63 | + /// <summary> |
| 64 | + /// Returns a <see cref="Box{T}"/> reference from the input <see cref="object"/> instance. |
| 65 | + /// </summary> |
| 66 | + /// <param name="obj">The input <see cref="object"/> instance, representing a boxed <typeparamref name="T"/> value.</param> |
| 67 | + /// <returns>A <see cref="Box{T}"/> reference pointing to <paramref name="obj"/>.</returns> |
| 68 | + /// <remarks> |
| 69 | + /// This method doesn't check the actual type of <paramref name="obj"/>, so it is responsability of the caller |
| 70 | + /// to ensure it actually represents a boxed <typeparamref name="T"/> value and not some other instance. |
| 71 | + /// </remarks> |
| 72 | + [Pure] |
| 73 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 74 | + public static Box<T> DangerousGetFrom(object obj) |
| 75 | + { |
| 76 | + return Unsafe.As<Box<T>>(obj); |
| 77 | + } |
| 78 | + |
| 79 | + /// <summary> |
| 80 | + /// Tries to get a <see cref="Box{T}"/> reference from an input <see cref="object"/> representing a boxed <typeparamref name="T"/> value. |
| 81 | + /// </summary> |
| 82 | + /// <param name="obj">The input <see cref="object"/> instance to check.</param> |
| 83 | + /// <param name="box">The resulting <see cref="Box{T}"/> reference, if <paramref name="obj"/> was a boxed <typeparamref name="T"/> value.</param> |
| 84 | + /// <returns><see langword="true"/> if a <see cref="Box{T}"/> instance was retrieved correctly, <see langword="false"/> otherwise.</returns> |
| 85 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 86 | + [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1115", Justification = "Comment for [NotNullWhen] attribute")] |
| 87 | + public static bool TryGetFrom( |
| 88 | + object obj, |
| 89 | +#if NETSTANDARD2_1 |
| 90 | + /* On .NET Standard 2.1, we can add the [NotNullWhen] attribute |
| 91 | + * to let the code analysis engine know that whenever this method |
| 92 | + * returns true, box will always be assigned to a non-null value. |
| 93 | + * This will eliminate the null warnings when in a branch that |
| 94 | + * is only taken when this method returns true. */ |
| 95 | + [NotNullWhen(true)] |
| 96 | +#endif |
| 97 | + out Box<T>? box) |
| 98 | + { |
| 99 | + if (obj.GetType() == typeof(T)) |
| 100 | + { |
| 101 | + box = Unsafe.As<Box<T>>(obj); |
| 102 | + |
| 103 | + return true; |
| 104 | + } |
| 105 | + |
| 106 | + box = null; |
| 107 | + |
| 108 | + return false; |
| 109 | + } |
| 110 | + |
| 111 | + /// <summary> |
| 112 | + /// Implicitly gets the <typeparamref name="T"/> value from a given <see cref="Box{T}"/> instance. |
| 113 | + /// </summary> |
| 114 | + /// <param name="box">The input <see cref="Box{T}"/> instance.</param> |
| 115 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 116 | + public static implicit operator T(Box<T> box) |
| 117 | + { |
| 118 | + return Unsafe.Unbox<T>(box); |
| 119 | + } |
| 120 | + |
| 121 | + /// <summary> |
| 122 | + /// Implicitly creates a new <see cref="Box{T}"/> instance from a given <typeparamref name="T"/> value. |
| 123 | + /// </summary> |
| 124 | + /// <param name="value">The input <typeparamref name="T"/> value to wrap.</param> |
| 125 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 126 | + public static implicit operator Box<T>(T value) |
| 127 | + { |
| 128 | + /* The Box<T> type is never actually instantiated. |
| 129 | + * Here we are just boxing the input T value, and then reinterpreting |
| 130 | + * that object reference as a Box<T> reference. As such, the Box<T> |
| 131 | + * type is really only used as an interface to access the contents |
| 132 | + * of a boxed value type. This also makes it so that additional methods |
| 133 | + * like ToString() or GetHashCode() will automatically be referenced from |
| 134 | + * the method table of the boxed object, meaning that they don't need to |
| 135 | + * manually be implemented in the Box<T> type. For instance, boxing a float |
| 136 | + * and calling ToString() on it directly, on its boxed object or on a Box<T> |
| 137 | + * reference retrieved from it will produce the same result in all cases. */ |
| 138 | + return Unsafe.As<Box<T>>(value); |
| 139 | + } |
| 140 | + |
| 141 | + /// <inheritdoc/> |
| 142 | + public override string ToString() |
| 143 | + { |
| 144 | + /* Here we're overriding the base object virtual methods to ensure |
| 145 | + * calls to those methods have a correct results on all runtimes. |
| 146 | + * For instance, not doing so is causing issue on .NET Core 2.1 Release |
| 147 | + * due to how the runtime handles the Box<T> reference to an actual |
| 148 | + * boxed T value (not a concrete Box<T> instance as it would expect). |
| 149 | + * To fix that, the overrides will simply call the expected methods |
| 150 | + * directly on the boxed T values. These methods will be directly |
| 151 | + * invoked by the JIT compiler when using a Box<T> reference. When |
| 152 | + * an object reference is used instead, the call would be forwarded |
| 153 | + * to those same methods anyway, since the method table for an object |
| 154 | + * representing a T instance is the one of type T anyway. */ |
| 155 | + return this.GetReference().ToString(); |
| 156 | + } |
| 157 | + |
| 158 | + /// <inheritdoc/> |
| 159 | + public override bool Equals(object obj) |
| 160 | + { |
| 161 | + return Equals(this, obj); |
| 162 | + } |
| 163 | + |
| 164 | + /// <inheritdoc/> |
| 165 | + public override int GetHashCode() |
| 166 | + { |
| 167 | + return this.GetReference().GetHashCode(); |
| 168 | + } |
| 169 | + |
| 170 | + /// <summary> |
| 171 | + /// Throws an <see cref="InvalidCastException"/> when a cast from an invalid <see cref="object"/> is attempted. |
| 172 | + /// </summary> |
| 173 | + [MethodImpl(MethodImplOptions.NoInlining)] |
| 174 | + private static void ThrowInvalidCastExceptionForGetFrom() |
| 175 | + { |
| 176 | + throw new InvalidCastException($"Can't cast the input object to the type Box<{typeof(T)}>"); |
| 177 | + } |
| 178 | + } |
| 179 | + |
| 180 | + /// <summary> |
| 181 | + /// Helpers for working with the <see cref="Box{T}"/> type. |
| 182 | + /// </summary> |
| 183 | + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402", Justification = "Extension class to replace instance methods for Box<T>")] |
| 184 | + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Extensions being declared after the type they apply to")] |
| 185 | + public static class BoxExtensions |
| 186 | + { |
| 187 | + /// <summary> |
| 188 | + /// Gets a <typeparamref name="T"/> reference from a <see cref="Box{T}"/> instance. |
| 189 | + /// </summary> |
| 190 | + /// <typeparam name="T">The type of reference to retrieve.</typeparam> |
| 191 | + /// <param name="box">The input <see cref="Box{T}"/> instance.</param> |
| 192 | + /// <returns>A <typeparamref name="T"/> reference to the boxed value within <paramref name="box"/>.</returns> |
| 193 | + [Pure] |
| 194 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 195 | + public static ref T GetReference<T>(this Box<T> box) |
| 196 | + where T : struct |
| 197 | + { |
| 198 | + /* The reason why this method is an extension and is not part of |
| 199 | + * the Box<T> type itself is that Box<T> is really just a mask |
| 200 | + * used over object references, but it is never actually instantiated. |
| 201 | + * Because of this, the method table of the objects in the heap will |
| 202 | + * be the one of type T created by the runtime, and not the one of |
| 203 | + * the Box<T> type. To avoid potential issues when invoking this method |
| 204 | + * on different runtimes, which might handle that scenario differently, |
| 205 | + * we use an extension method, which is just syntactic sugar for a static |
| 206 | + * method belonging to another class. This isn't technically necessary, |
| 207 | + * but it's just an extra precaution since the syntax for users remains |
| 208 | + * exactly the same anyway. Here we just call the Unsafe.Unbox<T>(object) |
| 209 | + * API, which is hidden away for users of the type for simplicity. |
| 210 | + * Note that this API will always actually involve a conditional |
| 211 | + * branch, which is introduced by the JIT compiler to validate the |
| 212 | + * object instance being unboxed. But since the alternative of |
| 213 | + * manually tracking the offset to the boxed data would be both |
| 214 | + * more error prone, and it would still introduce some overhead, |
| 215 | + * this doesn't really matter in this case anyway. */ |
| 216 | + return ref Unsafe.Unbox<T>(box); |
| 217 | + } |
| 218 | + } |
| 219 | +} |
0 commit comments