diff --git a/Microsoft.Toolkit.HighPerformance/Attributes/IgnoresAccessChecksToAttribute.cs b/Microsoft.Toolkit.HighPerformance/Attributes/IgnoresAccessChecksToAttribute.cs
new file mode 100644
index 00000000000..a813d3b2a24
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Attributes/IgnoresAccessChecksToAttribute.cs
@@ -0,0 +1,31 @@
+// 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)
+ {
+ AssemblyName = assemblyName;
+ }
+
+ ///
+ /// Gets the assembly name to use.
+ ///
+ public string AssemblyName { get; }
+ }
+}
+
+#endif
diff --git a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj
index 06c9e584da5..047743a0086 100644
--- a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj
+++ b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj
@@ -76,6 +76,16 @@
+
+
+
+
+
+ true
+ SilverlightPlatformPublicKey.snk
+ true
+
+
+ false
+
+
+
diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{T}.cs
index 97b00fe1d48..09a8fa14a30 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{T}.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_NullableReadOnlyRef{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;
@@ -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 106b6c95892..37d09ce93e8 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_ReadOnlyRef{T}.cs
@@ -2,21 +2,29 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance;
using Microsoft.VisualStudio.TestTools.UnitTesting;
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
{
+#if WINDOWS_UWP
[TestCategory("ReadOnlyRefOfT")]
[TestMethod]
-#if WINDOWS_UWP
- public void Test_RefOfT_CreateRefOfT()
+ public void Test_ReadOnlyRefOfT_CreateReadOnlyRefOfT()
{
var model = new ReadOnlyFieldOwner();
var reference = new ReadOnlyRef(model, model.Value);
@@ -32,14 +40,62 @@ private sealed class ReadOnlyFieldOwner
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "Ref readonly access for tests")]
public readonly int Value = 1;
}
+
+ [Pure]
+ private static ReadOnlyRef CreateReadOnlyRefFromArray(T[] array)
+ {
+ return new ReadOnlyRef(array, array[0]);
+ }
#else
- public void Test_RefOfT_CreateRefOfT()
+ [TestCategory("ReadOnlyRefOfT")]
+ [TestMethod]
+ public void Test_ReadOnlyRefOfT_CreateReadOnlyRefOfT()
{
int value = 1;
var reference = new ReadOnlyRef(value);
Assert.IsTrue(Unsafe.AreSame(ref value, ref Unsafe.AsRef(reference.Value)));
}
+
+ [Pure]
+ private static ReadOnlyRef CreateReadOnlyRefFromArray(T[] array)
+ {
+ return new ReadOnlyRef(array[0]);
+ }
+
+ [TestCategory("ReadOnlyRefOfT")]
+ [TestMethod]
+ public unsafe void Test_ReadOnlyRefOfT_CreateReadOnlyRefOfTFromPointer_Ok()
+ {
+ int value = 1;
+ var reference = new ReadOnlyRef(&value);
+
+ Assert.IsTrue(Unsafe.AreSame(ref value, ref Unsafe.AsRef(reference.Value)));
+ }
+
+ [TestCategory("ReadOnlyRefOfT")]
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentException))]
+ public unsafe void Test_ReadOnlyRefOfT_CreateReadOnlyRefOfTFrompointer_Fail()
+ {
+ var reference = new ReadOnlyRef((void*)0);
+
+ Assert.Fail();
+ }
#endif
+
+ [TestCategory("ReadOnlyRefOfT")]
+ [TestMethod]
+ public void Test_ReadOnlyRefOfT_DangerousGetReferenceAt()
+ {
+ int[] array = { 1, 2, 3, 4, 5 };
+ 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 556649c3cf6..29dc5bfab31 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Test_Ref{T}.cs
@@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -13,18 +15,21 @@ namespace UnitTests.HighPerformance
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_RefOfT
{
+#if WINDOWS_UWP
[TestCategory("RefOfT")]
[TestMethod]
-#if WINDOWS_UWP
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);
}
@@ -36,18 +41,81 @@ private sealed class FieldOwner
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "Quick ref access for tests")]
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)
+ {
+ return new Ref(array, ref array[0]);
+ }
#else
+ [TestCategory("RefOfT")]
+ [TestMethod]
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++;
+
+ Assert.AreEqual(value, 2);
+ }
+
+ // Same as above, but here we can directly wrap the target ref
+ [Pure]
+ private static Ref CreateRefFromArray(T[] array)
+ {
+ return new Ref(ref array[0]);
+ }
+
+ [TestCategory("RefOfT")]
+ [TestMethod]
+ 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++;
Assert.AreEqual(value, 2);
}
+
+ [TestCategory("RefOfT")]
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentException))]
+ 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
+
+ [TestCategory("RefOfT")]
+ [TestMethod]
+ 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]));
+ }
}
}
diff --git a/Windows Community Toolkit.sln b/Windows Community Toolkit.sln
index dbb8ef27886..db16d9d9f79 100644
--- a/Windows Community Toolkit.sln
+++ b/Windows Community Toolkit.sln
@@ -1,4 +1,3 @@
-
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29230.61
@@ -112,6 +111,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{88C6FFBE-3
ThirdPartyNotices.txt = ThirdPartyNotices.txt
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Private.CoreLib", "System.Private.CoreLib\System.Private.CoreLib.csproj", "{5F66DDCC-2F40-43C1-902E-2C1F887DB650}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Controls.Markdown", "Microsoft.Toolkit.Uwp.UI.Controls.Markdown\Microsoft.Toolkit.Uwp.UI.Controls.Markdown.csproj", "{6FEDF199-B052-49DD-8F3E-2A9224998E0F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design", "Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design\Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design.csproj", "{67FE47A0-CA93-4680-B770-A0A48C1DBC40}"
@@ -851,6 +852,36 @@ Global
{804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.ActiveCfg = Release|x86
{804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.Build.0 = Release|x86
{804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.Deploy.0 = Release|x86
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|ARM.Build.0 = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|x64.Build.0 = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Debug|x86.Build.0 = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|Any CPU.ActiveCfg = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|Any CPU.Build.0 = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|ARM.ActiveCfg = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|ARM.Build.0 = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|ARM64.ActiveCfg = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|ARM64.Build.0 = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|x64.ActiveCfg = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|x64.Build.0 = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|x86.ActiveCfg = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Native|x86.Build.0 = Debug|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|ARM.ActiveCfg = Release|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|ARM.Build.0 = Release|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|ARM64.Build.0 = Release|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|x64.ActiveCfg = Release|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|x64.Build.0 = Release|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|x86.ActiveCfg = Release|Any CPU
+ {5F66DDCC-2F40-43C1-902E-2C1F887DB650}.Release|x86.Build.0 = Release|Any CPU
{6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|ARM.ActiveCfg = Debug|Any CPU