1+ using System . ComponentModel ;
12using System . Diagnostics . CodeAnalysis ;
23using System . Numerics ;
4+ using System . Reflection ;
35using System . Runtime . CompilerServices ;
46using System . Runtime . InteropServices ;
57
@@ -12,6 +14,7 @@ namespace DotNext.Runtime;
1214/// <param name="fieldRef">The reference to the field.</param>
1315/// <typeparam name="T">The type of the field.</typeparam>
1416[ StructLayout ( LayoutKind . Auto ) ]
17+ [ EditorBrowsable ( EditorBrowsableState . Advanced ) ]
1518public readonly struct ValueReference < T > ( object owner , ref T fieldRef ) :
1619 IEquatable < ValueReference < T > > ,
1720 IEqualityOperators < ValueReference < T > , ValueReference < T > , bool >
@@ -23,11 +26,46 @@ public readonly struct ValueReference<T>(object owner, ref T fieldRef) :
2326 /// </summary>
2427 /// <param name="array">The array.</param>
2528 /// <param name="index">The index of the array element.</param>
29+ /// <exception cref="ArrayTypeMismatchException">
30+ /// <typeparamref name="T"/> is a reference type, and <paramref name="array" /> is not an array of type <typeparamref name="T"/>.
31+ /// </exception>
2632 public ValueReference ( T [ ] array , int index )
2733 : this ( array , ref array [ index ] )
2834 {
2935 }
3036
37+ private ValueReference ( StrongBox < T > box )
38+ : this ( box , ref box . Value ! )
39+ {
40+ }
41+
42+ /// <summary>
43+ /// Creates a reference to the anonymous value.
44+ /// </summary>
45+ /// <param name="value">The anonymous value.</param>
46+ public ValueReference ( T value )
47+ : this ( new StrongBox < T > { Value = value } )
48+ {
49+ }
50+
51+ /// <summary>
52+ /// Creates a reference to a static field.
53+ /// </summary>
54+ /// <remarks>
55+ /// If <typeparamref name="T"/> is a value type then your static field MUST be marked
56+ /// with <see cref="FixedAddressValueTypeAttribute"/>. Otherwise, the behavior is unpredictable.
57+ /// Correctness of this constructor is based on the fact that static fields are stored
58+ /// as elements of <see cref="object"/> array allocated by the runtime in the Pinned Object Heap.
59+ /// It means that the address of the field cannot be changed by GC.
60+ /// </remarks>
61+ /// <param name="staticFieldRef">A reference to the static field.</param>
62+ /// <seealso href="https://devblogs.microsoft.com/dotnet/internals-of-the-poh/">Internals of the POH</seealso>
63+ [ CLSCompliant ( false ) ]
64+ public ValueReference ( ref T staticFieldRef )
65+ : this ( Sentinel . Instance , ref staticFieldRef )
66+ {
67+ }
68+
3169 /// <summary>
3270 /// Gets a value indicating that is reference is empty.
3371 /// </summary>
@@ -80,21 +118,57 @@ public bool Equals(ValueReference<T> reference)
80118 /// <returns>The immutable field reference.</returns>
81119 public static implicit operator ReadOnlyValueReference < T > ( ValueReference < T > reference )
82120 => Unsafe . BitCast < ValueReference < T > , ReadOnlyValueReference < T > > ( reference ) ;
121+
122+ /// <summary>
123+ /// Gets a span over the referenced value.
124+ /// </summary>
125+ /// <param name="reference">The value reference.</param>
126+ /// <returns>The span that contains <see cref="Value"/>; or empty span if <paramref name="reference"/> is empty.</returns>
127+ public static implicit operator Span < T > ( ValueReference < T > reference )
128+ => reference . IsEmpty ? new ( ) : new ( ref reference . Value ) ;
83129}
84130
85131/// <summary>
86- /// Represents a mutable reference to the field.
132+ /// Represents an immutable reference to the field.
87133/// </summary>
88134/// <param name="owner">An object that owns the field.</param>
89135/// <param name="fieldRef">The reference to the field.</param>
90136/// <typeparam name="T">The type of the field.</typeparam>
91137[ StructLayout ( LayoutKind . Auto ) ]
138+ [ EditorBrowsable ( EditorBrowsableState . Advanced ) ]
92139public readonly struct ReadOnlyValueReference < T > ( object owner , ref readonly T fieldRef ) :
93140 IEquatable < ReadOnlyValueReference < T > > ,
94141 IEqualityOperators < ReadOnlyValueReference < T > , ReadOnlyValueReference < T > , bool >
95142{
96143 private readonly nint offset = RawData . GetOffset ( owner , in fieldRef ) ;
97144
145+ /// <summary>
146+ /// Creates a reference to an array element.
147+ /// </summary>
148+ /// <param name="array">The array.</param>
149+ /// <param name="index">The index of the array element.</param>
150+ public ReadOnlyValueReference ( T [ ] array , int index )
151+ : this ( array , in array [ index ] )
152+ {
153+ }
154+
155+ /// <summary>
156+ /// Creates a reference to a static field.
157+ /// </summary>
158+ /// <remarks>
159+ /// If <typeparamref name="T"/> is a value type then your static field MUST be marked
160+ /// with <see cref="FixedAddressValueTypeAttribute"/>. Otherwise, the behavior is unpredictable.
161+ /// Correctness of this constructor is based on the fact that static fields are stored
162+ /// as elements of <see cref="object"/> array allocated by the runtime in the Pinned Object Heap.
163+ /// It means that the address of the field cannot be changed by GC.
164+ /// </remarks>
165+ /// <param name="staticFieldRef">A reference to the static field.</param>
166+ /// <seealso href="https://devblogs.microsoft.com/dotnet/internals-of-the-poh/">Internals of the POH</seealso>
167+ public ReadOnlyValueReference ( ref readonly T staticFieldRef )
168+ : this ( Sentinel . Instance , in staticFieldRef )
169+ {
170+ }
171+
98172 /// <summary>
99173 /// Gets a value indicating that is reference is empty.
100174 /// </summary>
@@ -139,19 +213,45 @@ public bool Equals(ReadOnlyValueReference<T> reference)
139213 /// <returns><see langword="true"/> if both references are not equal; otherwise, <see langword="false"/>.</returns>
140214 public static bool operator != ( ReadOnlyValueReference < T > x , ReadOnlyValueReference < T > y )
141215 => x . Equals ( y ) is false ;
216+
217+ /// <summary>
218+ /// Gets a span over the referenced value.
219+ /// </summary>
220+ /// <param name="reference">The value reference.</param>
221+ /// <returns>The span that contains <see cref="Value"/>; or empty span if <paramref name="reference"/> is empty.</returns>
222+ public static implicit operator ReadOnlySpan < T > ( ReadOnlyValueReference < T > reference )
223+ => reference . IsEmpty ? new ( ) : new ( in reference . Value ) ;
142224}
143225
144226[ SuppressMessage ( "Performance" , "CA1812" , Justification = "Used for reinterpret cast" ) ]
145- file sealed class RawData
227+ file abstract class RawData
146228{
229+ // TODO: Replace with public counterpart in future
230+ private static readonly Func < object , nuint > ? GetRawObjectDataSize ;
231+
232+ [ DynamicDependency ( DynamicallyAccessedMemberTypes . NonPublicMethods , typeof ( RuntimeHelpers ) ) ]
233+ static RawData ( )
234+ {
235+ const BindingFlags flags = BindingFlags . DeclaredOnly | BindingFlags . NonPublic | BindingFlags . Static ;
236+ GetRawObjectDataSize = typeof ( RuntimeHelpers )
237+ . GetMethod ( nameof ( GetRawObjectDataSize ) , flags , [ typeof ( object ) ] )
238+ ? . CreateDelegate < Func < object , nuint > > ( ) ;
239+ }
240+
147241 private byte data ;
148242
149243 private RawData ( ) => throw new NotImplementedException ( ) ;
150244
151- internal static nint GetOffset < T > ( object owner , ref readonly T field )
245+ internal static nint GetOffset < T > ( object owner , ref readonly T field , [ CallerArgumentExpression ( nameof ( field ) ) ] string ? paramName = null )
152246 {
153247 ref var rawData = ref Unsafe . As < RawData > ( owner ) . data ;
154- return Unsafe . ByteOffset ( in rawData , in Intrinsics . ChangeType < T , byte > ( in field ) ) ;
248+ var offset = Unsafe . ByteOffset ( in rawData , in Intrinsics . ChangeType < T , byte > ( in field ) ) ;
249+
250+ // Ensure that the reference is an interior pointer to the field inside the object
251+ if ( GetRawObjectDataSize is not null && owner != Sentinel . Instance && ( nuint ) ( offset + Unsafe . SizeOf < T > ( ) ) > GetRawObjectDataSize ( owner ) )
252+ throw new ArgumentOutOfRangeException ( paramName ) ;
253+
254+ return offset ;
155255 }
156256
157257 internal static ref T GetObjectData < T > ( object owner , nint offset )
0 commit comments