Skip to content

Add NullableHelper #3389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
5 commits merged into from
Mar 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions Microsoft.Toolkit.HighPerformance/Extensions/NullableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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.

// This extension is restricted to the .NET 5 because it shares the same BCL
// across all targets, ensuring that the layout of our Nullable<T> mapping type
// will be correct. Exposing this API on older targets (especially .NET Standard)
// is not guaranteed to be correct and could result in invalid memory accesses.
#if NET5_0

using System;
using System.Runtime.CompilerServices;

namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="Nullable{T}"/> type.
/// </summary>
public static class NullableExtensions
{
/// <summary>
/// Returns a reference to the value of the input <see cref="Nullable{T}"/> instance, regardless of whether
/// the <see cref="Nullable{T}.HasValue"/> property is returning <see langword="true"/> or not. If that is not
/// the case, this method will still return a reference to the underlying <see langword="default"/> value.
/// </summary>
/// <typeparam name="T">The type of the underlying value</typeparam>
/// <param name="value">The <see cref="Nullable{T}"/></param>
/// <returns>A reference to the underlying value from the input <see cref="Nullable{T}"/> instance.</returns>
/// <remarks>
/// Note that attempting to mutate the returned reference will not change the value returned by <see cref="Nullable{T}.HasValue"/>.
/// That means that reassigning the value of an empty instance will not make <see cref="Nullable{T}.HasValue"/> return <see langword="true"/>.
/// </remarks>
public static ref T DangerousGetValueOrDefaultReference<T>(this ref T? value)
where T : struct
{
return ref Unsafe.As<T?, RawNullableData<T>>(ref value).Value;
}

/// <summary>
/// Mapping type that reflects the internal layout of the <see cref="Nullable{T}"/> type.
/// See https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Nullable.cs.
/// </summary>
/// <typeparam name="T">The value type wrapped by the current instance.</typeparam>
private struct RawNullableData<T>
where T : struct
{
#pragma warning disable CS0649 // Unassigned fields
public bool HasValue;
public T Value;
#pragma warning restore CS0649
}
}
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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 NET5_0

using System.Numerics;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_NullableExtensions
{
[TestCategory("NullableExtensions")]
[TestMethod]
public void Test_NullableExtensions_DangerousGetReference()
{
static void Test<T>(T before, T after)
where T : struct
{
T? nullable = before;
ref T reference = ref nullable.DangerousGetValueOrDefaultReference();

Assert.AreEqual(nullable.Value, before);

reference = after;

Assert.AreEqual(nullable.Value, after);
}

Test(0, 42);
Test(1.3f, 3.14f);
Test(0.555, 8.49);
Test(Vector4.Zero, new Vector4(1, 5.55f, 2, 3.14f));
Test(Matrix4x4.Identity, Matrix4x4.CreateOrthographic(35, 88.34f, 9.99f, 24.6f));
}
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_BoolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_HashCodeExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_IBufferWriterExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_NullableExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_StreamExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_SpanExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ReadOnlySpanExtensions.Count.cs" />
Expand Down