From 8cb7f187534f372df2d45c4243384ddb64b9e736 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 27 Jun 2020 15:27:27 +0200 Subject: [PATCH 01/32] Enabled hack to use ByReference from the BCL Huge thanks to @ltrzesniewski, see comment here: https://github.com/ltrzesniewski/InlineIL.Fody/issues/20#issuecomment-650423640 --- .../IgnoresAccessChecksToAttribute.cs | 25 ++++++++++++ .../Microsoft.Toolkit.HighPerformance.csproj | 6 +++ .../Properties/AssemblyInfo.cs | 11 ++++++ System.Private.CoreLib/ByReference{T}.cs | 29 ++++++++++++++ .../SilverlightPlatformPublicKey.snk | Bin 0 -> 160 bytes .../System.Private.CoreLib.csproj | 14 +++++++ Windows Community Toolkit.sln | 37 ++++++++++++++++-- 7 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 Microsoft.Toolkit.HighPerformance/Attributes/IgnoresAccessChecksToAttribute.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Properties/AssemblyInfo.cs create mode 100644 System.Private.CoreLib/ByReference{T}.cs create mode 100644 System.Private.CoreLib/SilverlightPlatformPublicKey.snk create mode 100644 System.Private.CoreLib/System.Private.CoreLib.csproj diff --git a/Microsoft.Toolkit.HighPerformance/Attributes/IgnoresAccessChecksToAttribute.cs b/Microsoft.Toolkit.HighPerformance/Attributes/IgnoresAccessChecksToAttribute.cs new file mode 100644 index 00000000000..fadf3249d08 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Attributes/IgnoresAccessChecksToAttribute.cs @@ -0,0 +1,25 @@ +// 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. + +#if NETCORE_RUNTIME + +namespace System.Runtime.CompilerServices +{ + /// + /// A special attribute recognized by CoreCLR that allows to ignore visibility checks for internal APIs. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class IgnoresAccessChecksToAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The assembly name to use. + public IgnoresAccessChecksToAttribute(string assemblyName) + { + } + } +} + +#endif diff --git a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj index f1d961a73c2..07604d48534 100644 --- a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj +++ b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj @@ -74,6 +74,9 @@ + + + diff --git a/Microsoft.Toolkit.HighPerformance/Properties/AssemblyInfo.cs b/Microsoft.Toolkit.HighPerformance/Properties/AssemblyInfo.cs index ae433591c22..7ed1b82ac31 100644 --- a/Microsoft.Toolkit.HighPerformance/Properties/AssemblyInfo.cs +++ b/Microsoft.Toolkit.HighPerformance/Properties/AssemblyInfo.cs @@ -6,6 +6,15 @@ using System.Runtime.CompilerServices; +// This is needed so that the runtime will actually let us access the internal +// ByReference type exposed through the fake System.Private.CoreLib assembly +// that is referenced by this project. The reason for this is that while the proxy +// type we declared belongs to a project we have internals access to, the real +// ByReference from the actual CoreLib assembly does not, so the runtime would +// normally fail to load the type if this attribute wasn't present. +// Note that while this attribute is internally recognized by the runtime, we still +// need the fake CoreLib assembly to be able to use the type when building from source, +// as Roslyn itself does not actually recognize and/or respect it. [assembly: IgnoresAccessChecksTo("System.Private.CoreLib")] #endif diff --git a/System.Private.CoreLib/Properties/AssemblyInfo.cs b/System.Private.CoreLib/Properties/AssemblyInfo.cs index 6025dad95e0..134b5aa54a7 100644 --- a/System.Private.CoreLib/Properties/AssemblyInfo.cs +++ b/System.Private.CoreLib/Properties/AssemblyInfo.cs @@ -4,4 +4,11 @@ using System.Runtime.CompilerServices; +// The ByReference type included in this project is kept internal, so +// that users referencing the Microsoft.Toolkit.HighPerformance package will +// need to go through the (overhead free) abstraction of the Ref types +// present in that assembly. This makes codebases using this package more +// resilient to changes in the runtime, and the code itself more portable. +// We need to provide the full public key here, as this assembly is signed. +// See the .csproj file for more info on why this is needed. [assembly: InternalsVisibleTo("Microsoft.Toolkit.HighPerformance, PublicKey=002400000480000094000000060200000024000052534131000400000100010041753af735ae6140c9508567666c51c6ab929806adb0d210694b30ab142a060237bc741f9682e7d8d4310364b4bba4ee89cc9d3d5ce7e5583587e8ea44dca09977996582875e71fb54fa7b170798d853d5d8010b07219633bdb761d01ac924da44576d6180cdceae537973982bb461c541541d58417a3794e34f45e6f2d129e2")] diff --git a/System.Private.CoreLib/System.Private.CoreLib.csproj b/System.Private.CoreLib/System.Private.CoreLib.csproj index 07d54144f3f..a4d1ac05777 100644 --- a/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -6,9 +6,20 @@ 4.0.0.0 System true + + true SilverlightPlatformPublicKey.snk true + + false From 333d447199d6de0f6d7e6fcd9e8acd6f732432f3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 23 Aug 2020 20:40:44 +0200 Subject: [PATCH 30/32] Added [ReadOnly]Ref indexers, code refactoring --- .../ReadOnlyRef{T}.cs | 84 +++++++++++++------ Microsoft.Toolkit.HighPerformance/Ref{T}.cs | 51 +++++------ .../Test_ReadOnlyRef{T}.cs | 2 + .../Test_Ref{T}.cs | 4 +- 4 files changed, 89 insertions(+), 52 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs b/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs index 1b08608a2bf..05f161737a9 100644 --- a/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs @@ -158,6 +158,58 @@ public ref readonly T Value } } + /// + /// Gets a reference to an element at a specified offset with respect to . + /// + /// The offset of the element to retrieve, starting from the reference provided by . + /// + /// This indexer offers a layer of abstraction over , and similarly it does + /// not do any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. + /// + public unsafe ref readonly T this[int offset] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if NETCORE_RUNTIME + return ref Unsafe.Add(ref ByReference.Value, (IntPtr)(void*)(uint)offset); +#elif SPAN_RUNTIME_SUPPORT + return ref Unsafe.Add(ref MemoryMarshal.GetReference(Span), (IntPtr)(void*)(uint)offset); +#else + ref T r0 = ref this.owner.DangerousGetObjectDataReferenceAt(this.offset); + ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); + + return ref r1; +#endif + } + } + + /// + /// Gets a reference to an element at a specified offset with respect to . + /// + /// The offset of the element to retrieve, starting from the reference provided by . + /// + /// This indexer offers a layer of abstraction over , and similarly it does + /// not do any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. + /// + public ref readonly T this[IntPtr offset] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if NETCORE_RUNTIME + return ref Unsafe.Add(ref ByReference.Value, offset); +#elif SPAN_RUNTIME_SUPPORT + return ref Unsafe.Add(ref MemoryMarshal.GetReference(Span), offset); +#else + ref T r0 = ref this.owner.DangerousGetObjectDataReferenceAt(this.offset); + ref T r1 = ref Unsafe.Add(ref r0, offset); + + return ref r1; +#endif + } + } + /// /// Returns a mutable reference with the same target as . /// @@ -175,23 +227,14 @@ public ref T DangerousGetReference() /// The offset of the element to retrieve, starting from the reference provided by . /// A reference to the element at the specified offset from . /// - /// This method offers a layer of abstraction over , and similarly it does not does not do - /// any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. + /// This method offers a layer of abstraction over , and similarly it does + /// not do any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe ref T DangerousGetReferenceAt(int offset) + public ref T DangerousGetReferenceAt(int offset) { -#if NETCORE_RUNTIME - return ref Unsafe.Add(ref ByReference.Value, (IntPtr)(void*)(uint)offset); -#elif SPAN_RUNTIME_SUPPORT - return ref Unsafe.Add(ref MemoryMarshal.GetReference(Span), (IntPtr)(void*)(uint)offset); -#else - ref T r0 = ref this.owner.DangerousGetObjectDataReferenceAt(this.offset); - ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); - - return ref r1; -#endif + return ref Unsafe.AsRef(this[offset]); } /// @@ -200,23 +243,14 @@ public unsafe ref T DangerousGetReferenceAt(int offset) /// The offset of the element to retrieve, starting from the reference provided by . /// A reference to the element at the specified offset from . /// - /// This method offers a layer of abstraction over , and similarly it does not does not do - /// any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. + /// This method offers a layer of abstraction over , and similarly it does + /// not do any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T DangerousGetReferenceAt(IntPtr offset) { -#if NETCORE_RUNTIME - return ref Unsafe.Add(ref ByReference.Value, offset); -#elif SPAN_RUNTIME_SUPPORT - return ref Unsafe.Add(ref MemoryMarshal.GetReference(Span), offset); -#else - ref T r0 = ref this.owner.DangerousGetObjectDataReferenceAt(this.offset); - ref T r1 = ref Unsafe.Add(ref r0, offset); - - return ref r1; -#endif + return ref Unsafe.AsRef(this[offset]); } /// diff --git a/Microsoft.Toolkit.HighPerformance/Ref{T}.cs b/Microsoft.Toolkit.HighPerformance/Ref{T}.cs index 1728934ea5d..a5ae9e7a2c9 100644 --- a/Microsoft.Toolkit.HighPerformance/Ref{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Ref{T}.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; #if NETCORE_RUNTIME #elif SPAN_RUNTIME_SUPPORT @@ -125,53 +124,55 @@ public ref T Value } /// - /// Returns a reference to an element at a specified offset with respect to . + /// Gets a reference to an element at a specified offset with respect to . /// /// The offset of the element to retrieve, starting from the reference provided by . - /// A reference to the element at the specified offset from . /// - /// This method offers a layer of abstraction over , and similarly it does not does not do - /// any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. + /// This indexer offers a layer of abstraction over , and similarly it does + /// not do any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. /// - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe ref T DangerousGetReferenceAt(int offset) + public unsafe ref T this[int offset] { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { #if NETCORE_RUNTIME - return ref Unsafe.Add(ref ByReference.Value, (IntPtr)(void*)(uint)offset); + return ref Unsafe.Add(ref ByReference.Value, (IntPtr)(void*)(uint)offset); #elif SPAN_RUNTIME_SUPPORT - return ref Unsafe.Add(ref MemoryMarshal.GetReference(Span), (IntPtr)(void*)(uint)offset); + return ref Unsafe.Add(ref MemoryMarshal.GetReference(Span), (IntPtr)(void*)(uint)offset); #else - ref T r0 = ref Owner.DangerousGetObjectDataReferenceAt(Offset); - ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); + ref T r0 = ref Owner.DangerousGetObjectDataReferenceAt(Offset); + ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset); - return ref r1; + return ref r1; #endif + } } /// - /// Returns a reference to an element at a specified offset with respect to . + /// Gets a reference to an element at a specified offset with respect to . /// /// The offset of the element to retrieve, starting from the reference provided by . - /// A reference to the element at the specified offset from . /// - /// This method offers a layer of abstraction over , and similarly it does not does not do - /// any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. + /// This indexer offers a layer of abstraction over , and similarly it does + /// not do any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. /// - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T DangerousGetReferenceAt(IntPtr offset) + public ref T this[IntPtr offset] { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { #if NETCORE_RUNTIME - return ref Unsafe.Add(ref ByReference.Value, offset); + return ref Unsafe.Add(ref ByReference.Value, offset); #elif SPAN_RUNTIME_SUPPORT - return ref Unsafe.Add(ref MemoryMarshal.GetReference(Span), offset); + return ref Unsafe.Add(ref MemoryMarshal.GetReference(Span), offset); #else - ref T r0 = ref Owner.DangerousGetObjectDataReferenceAt(Offset); - ref T r1 = ref Unsafe.Add(ref r0, offset); + ref T r0 = ref Owner.DangerousGetObjectDataReferenceAt(Offset); + ref T r1 = ref Unsafe.Add(ref r0, offset); - return ref r1; + return ref r1; #endif + } } #if SPAN_RUNTIME_SUPPORT diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs index 6ef61b89aed..8651f5c402c 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs @@ -86,6 +86,8 @@ public void Test_ReadOnlyRefOfT_DangerousGetReferenceAt() ReadOnlyRef reference = CreateReadOnlyRefFromArray(array); Assert.IsTrue(Unsafe.AreSame(ref array[0], ref reference.DangerousGetReference())); + Assert.IsTrue(Unsafe.AreSame(ref array[3], ref Unsafe.AsRef(reference[3]))); + Assert.IsTrue(Unsafe.AreSame(ref array[3], ref Unsafe.AsRef(reference[(IntPtr)3]))); Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference.DangerousGetReferenceAt(3))); Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference.DangerousGetReferenceAt((IntPtr)3))); } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs index e00490ab7e6..d9687b28f9e 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs @@ -98,8 +98,8 @@ public void Test_RefOfT_DangerousGetReferenceAt() Ref reference = CreateRefFromArray(array); Assert.IsTrue(Unsafe.AreSame(ref array[0], ref reference.Value)); - Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference.DangerousGetReferenceAt(3))); - Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference.DangerousGetReferenceAt((IntPtr)3))); + Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference[3])); + Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference[(IntPtr)3])); } } } From c7cb273823571138effab7299f6aa30235462818 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 30 Oct 2020 14:14:27 +0100 Subject: [PATCH 31/32] Added comments to unit tests --- .../Test_NullableReadOnlyRef{T}.cs | 6 ++++++ .../Test_NullableRef{T}.cs | 20 ++++++++++++++++++- .../Test_ReadOnlyRef{T}.cs | 6 ++++++ .../Test_Ref{T}.cs | 16 +++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{T}.cs index baa546dc814..09a8fa14a30 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{T}.cs @@ -12,6 +12,12 @@ namespace UnitTests.HighPerformance { + /* ==================================================================== + * NOTE + * ==================================================================== + * All thests here mirror the ones for NullableRef, as the two types + * are the same except for the fact that this wraps a readonly ref. See + * comments in the NullableRef test file for more info on these tests. */ [TestClass] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")] public class Test_NullableReadOnlyRefOfT diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableRef{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableRef{T}.cs index cc86c5bb3eb..f9f737eca5e 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableRef{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableRef{T}.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if NETCOREAPP3_1 +#if NETCOREAPP2_1 || NETCOREAPP3_1 using System; using System.Diagnostics.CodeAnalysis; @@ -23,6 +23,9 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_Ok() int value = 1; var reference = new NullableRef(ref value); + // Create a NullableRef to a local, then validate that the ref + // has value (as in, the ref T is not null), and that the reference + // does match and correctly points to the local variable above. Assert.IsTrue(reference.HasValue); Assert.IsTrue(Unsafe.AreSame(ref value, ref reference.Value)); @@ -35,6 +38,8 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_Ok() [TestMethod] public void Test_NullableRefOfT_CreateNullableRefOfT_Null() { + // Validate that different methods of creating a nullable ref + // are all correctly reported as not having a valid value. Assert.IsFalse(default(NullableRef).HasValue); Assert.IsFalse(NullableRef.Null.HasValue); @@ -49,6 +54,11 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_Null_Exception() { NullableRef reference = default; + // We try to access the value to trigger the null reference check within the + // NullableRef type. The type should correctly detect that a null reference + // is wrapped, and throw an InvalidOperationException (not a NullReferenceException). + // This is consistent with how Nullable works in the BCL as well (the type that + // is used to represent nullable value types). _ = reference.Value; } @@ -60,6 +70,8 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_ImplicitRefCast() var reference = new Ref(ref value); NullableRef nullableRef = reference; + // Verify that the inplicit Ref ==> NullableRef conversion works properly. + // The value should be available, and the internal ref should remain the same. Assert.IsTrue(nullableRef.HasValue); Assert.IsTrue(Unsafe.AreSame(ref reference.Value, ref nullableRef.Value)); } @@ -71,6 +83,9 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_ExplicitCastOfT() int value = 42; var reference = new NullableRef(ref value); + // Test the value equality by using the implicit T operator for NullableRef. + // As in, just like with normal refs, NullableRef has automatic dereferencing, + // and when cast to T it will dereference the internal ref and return th T value. Assert.AreEqual(value, (int)reference); } @@ -81,6 +96,9 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_ExplicitCastOfT_Exception() { NullableRef invalid = default; + // Same as above, but trying to dereference should throw an InvalidOperationException + // since our NullableRef here wraps a null reference. The value is discarded because + // here we just need to trigger the implicit operator to verify the exception is thrown. _ = (int)invalid; } } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs index 8651f5c402c..37d09ce93e8 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs @@ -11,6 +11,12 @@ namespace UnitTests.HighPerformance { + /* ================================================================== + * NOTE + * ================================================================== + * All thests here mirror the ones for Ref, as the two types are + * the same except for the fact that this wraps a readonly ref. See + * comments in the Ref test file for more info on these tests. */ [TestClass] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")] public class Test_ReadOnlyRefOfT diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs index d9687b28f9e..29dc5bfab31 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs @@ -23,10 +23,13 @@ public void Test_RefOfT_CreateRefOfT() var model = new FieldOwner { Value = 1 }; var reference = new Ref(model, ref model.Value); + // Create a Ref wrapping a ref to a field in an object and validate that + // the returned ref does indeed match one to that field directly. Assert.IsTrue(Unsafe.AreSame(ref model.Value, ref reference.Value)); reference.Value++; + // We increment the ref, and then verify the target field was updated correctly Assert.AreEqual(model.Value, 2); } @@ -39,6 +42,8 @@ private sealed class FieldOwner public int Value; } + // Helper method that creates a Ref to the first item of a T[] array, on UWP. In this + // case (portable version) we need to use both the target object, and an interior reference. [Pure] private static Ref CreateRefFromArray(T[] array) { @@ -52,6 +57,7 @@ public void Test_RefOfT_CreateRefOfT() int value = 1; var reference = new Ref(ref value); + // Same as the UWP version above, but directly wrapping the target ref Assert.IsTrue(Unsafe.AreSame(ref value, ref reference.Value)); reference.Value++; @@ -59,6 +65,7 @@ public void Test_RefOfT_CreateRefOfT() Assert.AreEqual(value, 2); } + // Same as above, but here we can directly wrap the target ref [Pure] private static Ref CreateRefFromArray(T[] array) { @@ -72,6 +79,7 @@ public unsafe void Test_RefOfT_CreateRefOfTFromPointer_Ok() int value = 1; var reference = new Ref(&value); + // Same as above, but this time we wrap a raw pointer to a local instead Assert.IsTrue(Unsafe.AreSame(ref value, ref reference.Value)); reference.Value++; @@ -86,6 +94,9 @@ public unsafe void Test_RefOfT_CreateRefOfTFrompointer_Fail() { var reference = new Ref((void*)0); + // Creating a Ref from a pointer when T is a managed type (eg. string) is not + // allowed (this is consistent with APIs from the BCL such as Span). So the + // constructor above should always throw an ArgumentException. Assert.Fail(); } #endif @@ -97,6 +108,11 @@ public void Test_RefOfT_DangerousGetReferenceAt() int[] array = { 1, 2, 3, 4, 5 }; Ref reference = CreateRefFromArray(array); + // We created a Ref pointing to the first item of the target array, so here + // we test a number of ref accesses (both directly to the target ref, as well as + // using the helper indexers to do pointer arithmetic) to validate their results. + // Doing Ref[i] is conceptually eqivalent to doing p[i] on a given T* pointer, + // so here we compare these offsetting operations with refs from the target array. Assert.IsTrue(Unsafe.AreSame(ref array[0], ref reference.Value)); Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference[3])); Assert.IsTrue(Unsafe.AreSame(ref array[3], ref reference[(IntPtr)3])); From 1d1bb80f2386a13ac552cc965e9077eab8489606 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 6 Nov 2020 01:25:05 +0100 Subject: [PATCH 32/32] Refactored IntPtr indexer to nint --- Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs | 2 +- Microsoft.Toolkit.HighPerformance/Ref{T}.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs b/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs index 05f161737a9..d5d965fe158 100644 --- a/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/ReadOnlyRef{T}.cs @@ -192,7 +192,7 @@ public unsafe ref readonly T this[int offset] /// This indexer offers a layer of abstraction over , and similarly it does /// not do any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. /// - public ref readonly T this[IntPtr offset] + public ref readonly T this[nint offset] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get diff --git a/Microsoft.Toolkit.HighPerformance/Ref{T}.cs b/Microsoft.Toolkit.HighPerformance/Ref{T}.cs index a5ae9e7a2c9..35fb353f3da 100644 --- a/Microsoft.Toolkit.HighPerformance/Ref{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Ref{T}.cs @@ -157,7 +157,7 @@ public unsafe ref T this[int offset] /// This indexer offers a layer of abstraction over , and similarly it does /// not do any kind of input validation. It is responsability of the caller to ensure the supplied offset is valid. /// - public ref T this[IntPtr offset] + public ref T this[nint offset] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get