Skip to content

Commit 4dc90dc

Browse files
author
msftbot[bot]
authored
Add NullableHelper (#3389)
cc @Sergio0694 Introduce a `NullableHelper` type with `GetValueOrDefaultRef` which returns a `ref T` which points to a value that is equivalent to `GetValueOrDefault` TODO add tests
2 parents a66da6e + 7848c37 commit 4dc90dc

File tree

3 files changed

+98
-0
lines changed

3 files changed

+98
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
// This extension is restricted to the .NET 5 because it shares the same BCL
6+
// across all targets, ensuring that the layout of our Nullable<T> mapping type
7+
// will be correct. Exposing this API on older targets (especially .NET Standard)
8+
// is not guaranteed to be correct and could result in invalid memory accesses.
9+
#if NET5_0
10+
11+
using System;
12+
using System.Runtime.CompilerServices;
13+
14+
namespace Microsoft.Toolkit.HighPerformance.Extensions
15+
{
16+
/// <summary>
17+
/// Helpers for working with the <see cref="Nullable{T}"/> type.
18+
/// </summary>
19+
public static class NullableExtensions
20+
{
21+
/// <summary>
22+
/// Returns a reference to the value of the input <see cref="Nullable{T}"/> instance, regardless of whether
23+
/// the <see cref="Nullable{T}.HasValue"/> property is returning <see langword="true"/> or not. If that is not
24+
/// the case, this method will still return a reference to the underlying <see langword="default"/> value.
25+
/// </summary>
26+
/// <typeparam name="T">The type of the underlying value</typeparam>
27+
/// <param name="value">The <see cref="Nullable{T}"/></param>
28+
/// <returns>A reference to the underlying value from the input <see cref="Nullable{T}"/> instance.</returns>
29+
/// <remarks>
30+
/// Note that attempting to mutate the returned reference will not change the value returned by <see cref="Nullable{T}.HasValue"/>.
31+
/// That means that reassigning the value of an empty instance will not make <see cref="Nullable{T}.HasValue"/> return <see langword="true"/>.
32+
/// </remarks>
33+
public static ref T DangerousGetValueOrDefaultReference<T>(this ref T? value)
34+
where T : struct
35+
{
36+
return ref Unsafe.As<T?, RawNullableData<T>>(ref value).Value;
37+
}
38+
39+
/// <summary>
40+
/// Mapping type that reflects the internal layout of the <see cref="Nullable{T}"/> type.
41+
/// See https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Nullable.cs.
42+
/// </summary>
43+
/// <typeparam name="T">The value type wrapped by the current instance.</typeparam>
44+
private struct RawNullableData<T>
45+
where T : struct
46+
{
47+
#pragma warning disable CS0649 // Unassigned fields
48+
public bool HasValue;
49+
public T Value;
50+
#pragma warning restore CS0649
51+
}
52+
}
53+
}
54+
55+
#endif
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#if NET5_0
6+
7+
using System.Numerics;
8+
using Microsoft.Toolkit.HighPerformance.Extensions;
9+
using Microsoft.VisualStudio.TestTools.UnitTesting;
10+
11+
namespace UnitTests.HighPerformance.Extensions
12+
{
13+
[TestClass]
14+
public class Test_NullableExtensions
15+
{
16+
[TestCategory("NullableExtensions")]
17+
[TestMethod]
18+
public void Test_NullableExtensions_DangerousGetReference()
19+
{
20+
static void Test<T>(T before, T after)
21+
where T : struct
22+
{
23+
T? nullable = before;
24+
ref T reference = ref nullable.DangerousGetValueOrDefaultReference();
25+
26+
Assert.AreEqual(nullable.Value, before);
27+
28+
reference = after;
29+
30+
Assert.AreEqual(nullable.Value, after);
31+
}
32+
33+
Test(0, 42);
34+
Test(1.3f, 3.14f);
35+
Test(0.555, 8.49);
36+
Test(Vector4.Zero, new Vector4(1, 5.55f, 2, 3.14f));
37+
Test(Matrix4x4.Identity, Matrix4x4.CreateOrthographic(35, 88.34f, 9.99f, 24.6f));
38+
}
39+
}
40+
}
41+
42+
#endif

UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_BoolExtensions.cs" />
2929
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_HashCodeExtensions.cs" />
3030
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_IBufferWriterExtensions.cs" />
31+
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_NullableExtensions.cs" />
3132
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_StreamExtensions.cs" />
3233
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_SpanExtensions.cs" />
3334
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ReadOnlySpanExtensions.Count.cs" />

0 commit comments

Comments
 (0)