88using System . Diagnostics . Contracts ;
99using System . Runtime . CompilerServices ;
1010
11- #nullable enable
12-
1311namespace Microsoft . Toolkit . HighPerformance
1412{
1513 /// <summary>
1614 /// A <see langword="class"/> that represents a boxed <typeparamref name="T"/> value on the managed heap.
15+ /// This is a "shadow" type that can be used in place of a non-generic <see cref="object"/> reference to a
16+ /// boxed value type, to make the code more expressive and reduce the chances of errors.
17+ /// Consider this example:
18+ /// <code>
19+ /// object obj = 42;
20+ ///
21+ /// // Manual, error prone unboxing
22+ /// int sum = (int)obj + 1;
23+ /// </code>
24+ /// In this example, it is not possible to know in advance what type is actually being boxed in a given
25+ /// <see cref="object"/> instance, making the code less robust at build time. The <see cref="Box{T}"/>
26+ /// type can be used as a drop-in replacement in this case, like so:
27+ /// <code>
28+ /// Box<int> box = 42;
29+ ///
30+ /// // Build-time validation, automatic unboxing
31+ /// int sum = box.Value + 1;
32+ /// </code>
33+ /// This type can also be useful when dealing with large custom value types that are also boxed, as
34+ /// it allows to retrieve a mutable reference to the boxing value. This means that a given boxed
35+ /// value can be mutated in-place, instead of having to allocate a new updated boxed instance.
1736 /// </summary>
1837 /// <typeparam name="T">The type of value being boxed.</typeparam>
1938 [ DebuggerDisplay ( "{ToString(),raw}" ) ]
@@ -39,8 +58,10 @@ public sealed class Box<T>
3958 /// This constructor is never used, it is only declared in order to mark it with
4059 /// the <see langword="private"/> visibility modifier and prevent direct use.
4160 /// </remarks>
61+ /// <exception cref="InvalidOperationException">Always thrown when this constructor is used (eg. from reflection).</exception>
4262 private Box ( )
4363 {
64+ throw new InvalidOperationException ( "The Microsoft.Toolkit.HighPerformance.Box<T> constructor should never be used" ) ;
4465 }
4566
4667 /// <summary>
@@ -83,18 +104,7 @@ public static Box<T> DangerousGetFrom(object obj)
83104 /// <param name="box">The resulting <see cref="Box{T}"/> reference, if <paramref name="obj"/> was a boxed <typeparamref name="T"/> value.</param>
84105 /// <returns><see langword="true"/> if a <see cref="Box{T}"/> instance was retrieved correctly, <see langword="false"/> otherwise.</returns>
85106 [ 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 )
107+ public static bool TryGetFrom ( object obj , [ NotNullWhen ( true ) ] out Box < T > ? box )
98108 {
99109 if ( obj . GetType ( ) == typeof ( T ) )
100110 {
@@ -125,38 +135,38 @@ public static implicit operator T(Box<T> box)
125135 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
126136 public static implicit operator Box < T > ( T value )
127137 {
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+ // The Box<T> type is never actually instantiated.
139+ // Here we are just boxing the input T value, and then reinterpreting
140+ // that object reference as a Box<T> reference. As such, the Box<T>
141+ // type is really only used as an interface to access the contents
142+ // of a boxed value type. This also makes it so that additional methods
143+ // like ToString() or GetHashCode() will automatically be referenced from
144+ // the method table of the boxed object, meaning that they don't need to
145+ // manually be implemented in the Box<T> type. For instance, boxing a float
146+ // and calling ToString() on it directly, on its boxed object or on a Box<T>
147+ // reference retrieved from it will produce the same result in all cases.
138148 return Unsafe . As < Box < T > > ( value ) ;
139149 }
140150
141151 /// <inheritdoc/>
142152 public override string ToString ( )
143153 {
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 ( ) ;
154+ // Here we're overriding the base object virtual methods to ensure
155+ // calls to those methods have a correct results on all runtimes.
156+ // For instance, not doing so is causing issue on .NET Core 2.1 Release
157+ // due to how the runtime handles the Box<T> reference to an actual
158+ // boxed T value (not a concrete Box<T> instance as it would expect).
159+ // To fix that, the overrides will simply call the expected methods
160+ // directly on the boxed T values. These methods will be directly
161+ // invoked by the JIT compiler when using a Box<T> reference. When
162+ // an object reference is used instead, the call would be forwarded
163+ // to those same methods anyway, since the method table for an object
164+ // representing a T instance is the one of type T anyway.
165+ return this . GetReference ( ) . ToString ( ) ! ;
156166 }
157167
158168 /// <inheritdoc/>
159- public override bool Equals ( object obj )
169+ public override bool Equals ( object ? obj )
160170 {
161171 return Equals ( this , obj ) ;
162172 }
@@ -177,11 +187,12 @@ private static void ThrowInvalidCastExceptionForGetFrom()
177187 }
178188 }
179189
190+ #pragma warning disable SA1402 // Extensions being declared after the type they apply to
191+ #pragma warning disable SA1204 // Extension class to replace instance methods for Box<T>
192+
180193 /// <summary>
181194 /// Helpers for working with the <see cref="Box{T}"/> type.
182195 /// </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" ) ]
185196 public static class BoxExtensions
186197 {
187198 /// <summary>
@@ -195,24 +206,24 @@ public static class BoxExtensions
195206 public static ref T GetReference < T > ( this Box < T > box )
196207 where T : struct
197208 {
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. */
209+ // The reason why this method is an extension and is not part of
210+ // the Box<T> type itself is that Box<T> is really just a mask
211+ // used over object references, but it is never actually instantiated.
212+ // Because of this, the method table of the objects in the heap will
213+ // be the one of type T created by the runtime, and not the one of
214+ // the Box<T> type. To avoid potential issues when invoking this method
215+ // on different runtimes, which might handle that scenario differently,
216+ // we use an extension method, which is just syntactic sugar for a static
217+ // method belonging to another class. This isn't technically necessary,
218+ // but it's just an extra precaution since the syntax for users remains
219+ // exactly the same anyway. Here we just call the Unsafe.Unbox<T>(object)
220+ // API, which is hidden away for users of the type for simplicity.
221+ // Note that this API will always actually involve a conditional
222+ // branch, which is introduced by the JIT compiler to validate the
223+ // object instance being unboxed. But since the alternative of
224+ // manually tracking the offset to the boxed data would be both
225+ // more error prone, and it would still introduce some overhead,
226+ // this doesn't really matter in this case anyway.
216227 return ref Unsafe . Unbox < T > ( box ) ;
217228 }
218229 }
0 commit comments