@@ -19,6 +19,8 @@ public class Test_MemoryExtensions
1919 [ TestMethod ]
2020 public void Test_MemoryExtensions_Cast_Empty ( )
2121 {
22+ // Casting an empty memory of any size should always be valid
23+ // and result in another empty memory, regardless of types.
2224 Memory < byte > m1 = default ;
2325 Memory < byte > mc1 = m1 . Cast < byte , byte > ( ) ;
2426
@@ -34,11 +36,13 @@ public void Test_MemoryExtensions_Cast_Empty()
3436
3537 Assert . IsTrue ( mc3 . IsEmpty ) ;
3638
39+ // Same as above, but with a sliced memory (length 12, slide from 12, so length of 0)
3740 Memory < byte > m4 = new byte [ 12 ] . AsMemory ( 12 ) ;
3841 Memory < int > mc4 = m4 . Cast < byte , int > ( ) ;
3942
4043 Assert . IsTrue ( mc4 . IsEmpty ) ;
4144
45+ // Same as above, but slicing to 12 in two steps
4246 Memory < byte > m5 = new byte [ 12 ] . AsMemory ( ) . Slice ( 4 ) . Slice ( 8 ) ;
4347 Memory < int > mc5 = m5 . Cast < byte , int > ( ) ;
4448
@@ -49,16 +53,19 @@ public void Test_MemoryExtensions_Cast_Empty()
4953 [ TestMethod ]
5054 public void Test_MemoryExtensions_Cast_TooShort ( )
5155 {
56+ // One int is 4 bytes, so casting from 3 rounds down to 0
5257 Memory < byte > m1 = new byte [ 3 ] ;
5358 Memory < int > mc1 = m1 . Cast < byte , int > ( ) ;
5459
5560 Assert . IsTrue ( mc1 . IsEmpty ) ;
5661
62+ // Same as above, 13 / sizeof(int) == 3
5763 Memory < byte > m2 = new byte [ 13 ] ;
5864 Memory < float > mc2 = m2 . Cast < byte , float > ( ) ;
5965
6066 Assert . AreEqual ( mc2 . Length , 3 ) ;
6167
68+ // 16 - 5 = 11 ---> 11 / sizeof(int) = 2
6269 Memory < byte > m3 = new byte [ 16 ] . AsMemory ( 5 ) ;
6370 Memory < float > mc3 = m3 . Cast < byte , float > ( ) ;
6471
@@ -69,6 +76,7 @@ public void Test_MemoryExtensions_Cast_TooShort()
6976 [ TestMethod ]
7077 public void Test_MemoryExtensions_FromArray_CastFromByte ( )
7178 {
79+ // Test for a standard cast from bytes with an evenly divisible length
7280 Memory < byte > memoryOfBytes = new byte [ 128 ] ;
7381 Memory < float > memoryOfFloats = memoryOfBytes . Cast < byte , float > ( ) ;
7482
@@ -77,6 +85,9 @@ public void Test_MemoryExtensions_FromArray_CastFromByte()
7785 Span < byte > spanOfBytes = memoryOfBytes . Span ;
7886 Span < float > spanOfFloats = memoryOfFloats . Span ;
7987
88+ // We also need to check that the Span<T> returned from the caast memory
89+ // actually has the initial reference pointing to the same location as
90+ // the one to the same item in the span from the original memory.
8091 Assert . AreEqual ( memoryOfFloats . Length , spanOfFloats . Length ) ;
8192 Assert . IsTrue ( Unsafe . AreSame (
8293 ref spanOfBytes [ 0 ] ,
@@ -87,6 +98,8 @@ public void Test_MemoryExtensions_FromArray_CastFromByte()
8798 [ TestMethod ]
8899 public void Test_MemoryExtensions_FromArray_CastToByte ( )
89100 {
101+ // Cast from float to bytes to verify casting works when the target type
102+ // as a smaller byte size as well (so the resulting length will be larger).
90103 Memory < float > memoryOfFloats = new float [ 128 ] ;
91104 Memory < byte > memoryOfBytes = memoryOfFloats . Cast < float , byte > ( ) ;
92105
@@ -95,6 +108,8 @@ public void Test_MemoryExtensions_FromArray_CastToByte()
95108 Span < float > spanOfFloats = memoryOfFloats . Span ;
96109 Span < byte > spanOfBytes = memoryOfBytes . Span ;
97110
111+ // Same as above, we need to verify that the resulting span has matching
112+ // starting references with the one produced by the original memory.
98113 Assert . AreEqual ( memoryOfBytes . Length , spanOfBytes . Length ) ;
99114 Assert . IsTrue ( Unsafe . AreSame (
100115 ref spanOfFloats [ 0 ] ,
@@ -105,6 +120,7 @@ public void Test_MemoryExtensions_FromArray_CastToByte()
105120 [ TestMethod ]
106121 public void Test_MemoryExtensions_FromArray_CastToShort ( )
107122 {
123+ // Similar test as above, just with different types to double check
108124 Memory < float > memoryOfFloats = new float [ 128 ] ;
109125 Memory < short > memoryOfShorts = memoryOfFloats . Cast < float , short > ( ) ;
110126
@@ -123,13 +139,20 @@ public void Test_MemoryExtensions_FromArray_CastToShort()
123139 [ TestMethod ]
124140 public void Test_MemoryExtensions_FromArray_CastFromByteAndBack ( )
125141 {
142+ // Here we start from a byte array, get a memory, then cast to float and then
143+ // back to byte. We want to verify that the final memory is both valid and
144+ // consistent, as well that our internal optimization works and that the final
145+ // memory correctly skipped the indirect memory managed and just wrapped the original
146+ // array instead. This is documented in the custom array memory manager in the package.
126147 var data = new byte [ 128 ] ;
127148 Memory < byte > memoryOfBytes = data ;
128149 Memory < float > memoryOfFloats = memoryOfBytes . Cast < byte , float > ( ) ;
129150 Memory < byte > memoryBack = memoryOfFloats . Cast < float , byte > ( ) ;
130151
131152 Assert . AreEqual ( memoryOfBytes . Length , memoryBack . Length ) ;
132153
154+ // Here we get the array from the final memory and check that it does exist and
155+ // the associated parameters match the ones we'd expect here (same length, offset of 0).
133156 Assert . IsTrue ( MemoryMarshal . TryGetArray < byte > ( memoryBack , out var segment ) ) ;
134157 Assert . AreSame ( segment . Array ! , data ) ;
135158 Assert . AreEqual ( segment . Offset , 0 ) ;
@@ -140,13 +163,16 @@ public void Test_MemoryExtensions_FromArray_CastFromByteAndBack()
140163 Span < byte > span1 = memoryOfBytes . Span ;
141164 Span < byte > span2 = memoryBack . Span ;
142165
166+ // Also validate the initial and final spans for reference equality
143167 Assert . IsTrue ( span1 == span2 ) ;
144168 }
145169
146170 [ TestCategory ( "MemoryExtensions" ) ]
147171 [ TestMethod ]
148172 public void Test_MemoryExtensions_Cast_TooShort_WithSlice ( )
149173 {
174+ // Like we did above, we have some more tests where we slice an initial memory and
175+ // validate the length of the resulting, accounting for the expected rounding down.
150176 Memory < byte > m1 = new byte [ 8 ] . AsMemory ( ) . Slice ( 4 , 3 ) ;
151177 Memory < int > mc1 = m1 . Cast < byte , int > ( ) ;
152178
@@ -167,6 +193,8 @@ public void Test_MemoryExtensions_Cast_TooShort_WithSlice()
167193 [ TestMethod ]
168194 public void Test_MemoryExtensions_FromArray_CastFromByte_WithSlice ( )
169195 {
196+ // Same exact test as the cast from byte to float did above, but with a slice. This is done
197+ // to ensure the cast still works correctly when the memory is internally storing an offset.
170198 Memory < byte > memoryOfBytes = new byte [ 512 ] . AsMemory ( ) . Slice ( 128 , 128 ) ;
171199 Memory < float > memoryOfFloats = memoryOfBytes . Cast < byte , float > ( ) ;
172200
@@ -185,6 +213,7 @@ public void Test_MemoryExtensions_FromArray_CastFromByte_WithSlice()
185213 [ TestMethod ]
186214 public void Test_MemoryExtensions_FromArray_CastToByte_WithSlice ( )
187215 {
216+ // Same as above, just with inverted source and destination types
188217 Memory < float > memoryOfFloats = new float [ 512 ] . AsMemory ( ) . Slice ( 128 , 128 ) ;
189218 Memory < byte > memoryOfBytes = memoryOfFloats . Cast < float , byte > ( ) ;
190219
@@ -203,6 +232,8 @@ public void Test_MemoryExtensions_FromArray_CastToByte_WithSlice()
203232 [ TestMethod ]
204233 public void Test_MemoryExtensions_FromArray_CastToShort_WithSlice ( )
205234 {
235+ // Once again the same test but with types both different in size than 1. We're mostly
236+ // just testing the rounding logic in a number of different case to ensure it's correct.
206237 Memory < float > memoryOfFloats = new float [ 512 ] . AsMemory ( ) . Slice ( 128 , 128 ) ;
207238 Memory < short > memoryOfShorts = memoryOfFloats . Cast < float , short > ( ) ;
208239
@@ -221,13 +252,15 @@ public void Test_MemoryExtensions_FromArray_CastToShort_WithSlice()
221252 [ TestMethod ]
222253 public void Test_MemoryExtensions_FromArray_CastFromByteAndBack_WithSlice ( )
223254 {
255+ // Just like the equivalent test above, but with a slice thrown in too
224256 var data = new byte [ 512 ] ;
225257 Memory < byte > memoryOfBytes = data . AsMemory ( ) . Slice ( 128 , 128 ) ;
226258 Memory < float > memoryOfFloats = memoryOfBytes . Cast < byte , float > ( ) ;
227259 Memory < byte > memoryBack = memoryOfFloats . Cast < float , byte > ( ) ;
228260
229261 Assert . AreEqual ( memoryOfBytes . Length , memoryBack . Length ) ;
230262
263+ // Here we now also have to validate the starting offset from the extracted array
231264 Assert . IsTrue ( MemoryMarshal . TryGetArray < byte > ( memoryBack , out var segment ) ) ;
232265 Assert . AreSame ( segment . Array ! , data ) ;
233266 Assert . AreEqual ( segment . Offset , 128 ) ;
@@ -245,6 +278,10 @@ public void Test_MemoryExtensions_FromArray_CastFromByteAndBack_WithSlice()
245278 [ TestMethod ]
246279 public void Test_MemoryExtensions_FromMemoryManager_CastFromByte ( )
247280 {
281+ // This test is just like the ones above, but this time we're casting a memory
282+ // that wraps a custom memory manager and not an array. This is done to ensure
283+ // the casting logic works correctly in all cases, as it'll use a different
284+ // memory manager internally (a memory can wrap a string, an array or a manager).
248285 Memory < byte > memoryOfBytes = new ArrayMemoryManager < byte > ( 128 ) ;
249286 Memory < float > memoryOfFloats = memoryOfBytes . Cast < byte , float > ( ) ;
250287
@@ -263,6 +300,7 @@ public void Test_MemoryExtensions_FromMemoryManager_CastFromByte()
263300 [ TestMethod ]
264301 public void Test_MemoryExtensions_FromMemoryManager_CastToByte ( )
265302 {
303+ // Same as above, but with inverted types
266304 Memory < float > memoryOfFloats = new ArrayMemoryManager < float > ( 128 ) ;
267305 Memory < byte > memoryOfBytes = memoryOfFloats . Cast < float , byte > ( ) ;
268306
@@ -281,6 +319,7 @@ public void Test_MemoryExtensions_FromMemoryManager_CastToByte()
281319 [ TestMethod ]
282320 public void Test_MemoryExtensions_FromMemoryManager_CastToShort ( )
283321 {
322+ // Same as above, but with types different in size than 1, just in case
284323 Memory < float > memoryOfFloats = new ArrayMemoryManager < float > ( 128 ) ;
285324 Memory < short > memoryOfShorts = memoryOfFloats . Cast < float , short > ( ) ;
286325
@@ -299,13 +338,16 @@ public void Test_MemoryExtensions_FromMemoryManager_CastToShort()
299338 [ TestMethod ]
300339 public void Test_MemoryExtensions_FromMemoryManager_CastFromByteAndBack ( )
301340 {
341+ // Equivalent to the one with an array, but with a memory manager
302342 var data = new ArrayMemoryManager < byte > ( 128 ) ;
303343 Memory < byte > memoryOfBytes = data ;
304344 Memory < float > memoryOfFloats = memoryOfBytes . Cast < byte , float > ( ) ;
305345 Memory < byte > memoryBack = memoryOfFloats . Cast < float , byte > ( ) ;
306346
307347 Assert . AreEqual ( memoryOfBytes . Length , memoryBack . Length ) ;
308348
349+ // Here we expect to get back the original memory manager, due to the same optimization we
350+ // checked for when using an array. We need to check they're the same, and the other parameters.
309351 Assert . IsTrue ( MemoryMarshal . TryGetMemoryManager < byte , ArrayMemoryManager < byte > > ( memoryBack , out var manager , out var start , out var length ) ) ;
310352 Assert . AreSame ( manager ! , data ) ;
311353 Assert . AreEqual ( start , 0 ) ;
@@ -323,6 +365,7 @@ public void Test_MemoryExtensions_FromMemoryManager_CastFromByteAndBack()
323365 [ TestMethod ]
324366 public void Test_MemoryExtensions_FromMemoryManager_CastFromByte_WithSlice ( )
325367 {
368+ // Same as the ones with an array, but with an extra slice
326369 Memory < byte > memoryOfBytes = new ArrayMemoryManager < byte > ( 512 ) . Memory . Slice ( 128 , 128 ) ;
327370 Memory < float > memoryOfFloats = memoryOfBytes . Cast < byte , float > ( ) ;
328371
@@ -341,6 +384,7 @@ public void Test_MemoryExtensions_FromMemoryManager_CastFromByte_WithSlice()
341384 [ TestMethod ]
342385 public void Test_MemoryExtensions_FromMemoryManager_CastToByte_WithSlice ( )
343386 {
387+ // Same as above, but with inverted types
344388 Memory < float > memoryOfFloats = new ArrayMemoryManager < float > ( 512 ) . Memory . Slice ( 128 , 128 ) ;
345389 Memory < byte > memoryOfBytes = memoryOfFloats . Cast < float , byte > ( ) ;
346390
@@ -359,6 +403,7 @@ public void Test_MemoryExtensions_FromMemoryManager_CastToByte_WithSlice()
359403 [ TestMethod ]
360404 public void Test_MemoryExtensions_FromMemoryManager_CastToShort_WithSlice ( )
361405 {
406+ // Same as above but with different types
362407 Memory < float > memoryOfFloats = new ArrayMemoryManager < float > ( 512 ) . Memory . Slice ( 128 , 128 ) ;
363408 Memory < short > memoryOfShorts = memoryOfFloats . Cast < float , short > ( ) ;
364409
@@ -377,13 +422,15 @@ public void Test_MemoryExtensions_FromMemoryManager_CastToShort_WithSlice()
377422 [ TestMethod ]
378423 public void Test_MemoryExtensions_FromMemoryManager_CastFromByteAndBack_WithSlice ( )
379424 {
425+ // Just like the one above, but with the slice
380426 var data = new ArrayMemoryManager < byte > ( 512 ) ;
381427 Memory < byte > memoryOfBytes = data . Memory . Slice ( 128 , 128 ) ;
382428 Memory < float > memoryOfFloats = memoryOfBytes . Cast < byte , float > ( ) ;
383429 Memory < byte > memoryBack = memoryOfFloats . Cast < float , byte > ( ) ;
384430
385431 Assert . AreEqual ( memoryOfBytes . Length , memoryBack . Length ) ;
386432
433+ // Here we also need to validate that the offset was maintained
387434 Assert . IsTrue ( MemoryMarshal . TryGetMemoryManager < byte , ArrayMemoryManager < byte > > ( memoryBack , out var manager , out var start , out var length ) ) ;
388435 Assert . AreSame ( manager ! , data ) ;
389436 Assert . AreEqual ( start , 128 ) ;
@@ -410,6 +457,12 @@ public void Test_MemoryExtensions_FromMemoryManager_CastFromByteAndBack_WithSlic
410457 [ DataRow ( 256 , 64 , 8 ) ]
411458 public unsafe void Test_MemoryExtensions_FromArray_CastFromByte_Pin ( int size , int preOffset , int postOffset )
412459 {
460+ // Here we need to validate that pinning works correctly in a number of cases. First we allocate
461+ // an array of the requested size, then get a memory after slicing to a target position, then cast
462+ // and then slice again. We do so to ensure that pinning correctly tracks the correct index with
463+ // respect to the original array through a number of internal offsets. As in, when pinning the
464+ // final memory, our internal custom memory manager should be able to pin the item in the original
465+ // array at offset preOffset + (postOffset * sizeof(float)), accounting for the cast as well.
413466 var data = new byte [ size ] ;
414467 Memory < byte > memoryOfBytes = data . AsMemory ( preOffset ) ;
415468 Memory < float > memoryOfFloats = memoryOfBytes . Cast < byte , float > ( ) . Slice ( postOffset ) ;
@@ -435,6 +488,7 @@ public unsafe void Test_MemoryExtensions_FromArray_CastFromByte_Pin(int size, in
435488 [ DataRow ( 256 , 64 , 8 ) ]
436489 public unsafe void Test_MemoryExtensions_FromMemoryManager_CastFromByte_Pin ( int size , int preOffset , int postOffset )
437490 {
491+ // Just like the test above, but this type the initial memory wraps a memory manager
438492 var data = new ArrayMemoryManager < byte > ( size ) ;
439493 Memory < byte > memoryOfBytes = data . Memory . Slice ( preOffset ) ;
440494 Memory < float > memoryOfFloats = memoryOfBytes . Cast < byte , float > ( ) . Slice ( postOffset ) ;
@@ -451,13 +505,16 @@ public unsafe void Test_MemoryExtensions_FromMemoryManager_CastFromByte_Pin(int
451505 [ TestMethod ]
452506 public void Test_MemoryExtensions_FromString_CastFromByteAndBack ( )
453507 {
508+ // This is the same as the tests above, but here we're testing the
509+ // other remaining case, that is when a memory is wrapping a string.
454510 var data = new string ( 'a' , 128 ) ;
455511 Memory < char > memoryOfChars = MemoryMarshal . AsMemory ( data . AsMemory ( ) ) ;
456512 Memory < float > memoryOfFloats = memoryOfChars . Cast < char , float > ( ) ;
457513 Memory < char > memoryBack = memoryOfFloats . Cast < float , char > ( ) ;
458514
459515 Assert . AreEqual ( memoryOfChars . Length , memoryBack . Length ) ;
460516
517+ // Get the original string back (to validate the optimization too) and check the params
461518 Assert . IsTrue ( MemoryMarshal . TryGetString ( memoryOfChars , out var text , out int start , out int length ) ) ;
462519 Assert . AreSame ( text ! , data ) ;
463520 Assert . AreEqual ( start , 0 ) ;
@@ -484,6 +541,7 @@ public void Test_MemoryExtensions_FromString_CastFromByteAndBack()
484541 [ DataRow ( 256 , 64 , 8 ) ]
485542 public unsafe void Test_MemoryExtensions_FromString_CastAndPin ( int size , int preOffset , int postOffset )
486543 {
544+ // Same test as before to validate pinning, but starting from a string
487545 var data = new string ( 'a' , size ) ;
488546 Memory < char > memoryOfChars = MemoryMarshal . AsMemory ( data . AsMemory ( ) ) . Slice ( preOffset ) ;
489547 Memory < byte > memoryOfBytes = memoryOfChars . Cast < char , byte > ( ) . Slice ( postOffset ) ;
@@ -496,6 +554,8 @@ public unsafe void Test_MemoryExtensions_FromString_CastAndPin(int size, int pre
496554 Assert . IsTrue ( p1 == p2 ) ;
497555 }
498556
557+ // Here we also add an extra test just like the one above, but casting to a type
558+ // that is bigger in byte size than char. Just to double check the casting logic.
499559 Memory < int > memoryOfInts = memoryOfChars . Cast < char , int > ( ) . Slice ( postOffset ) ;
500560
501561 using ( var handle2 = memoryOfInts . Pin ( ) )
@@ -513,6 +573,7 @@ public void Test_MemoryExtensions_EmptyMemoryStream()
513573 {
514574 Memory < byte > memory = default ;
515575
576+ // Creating a stream from a default memory is valid, it's just empty
516577 Stream stream = memory . AsStream ( ) ;
517578
518579 Assert . IsNotNull ( stream ) ;
0 commit comments