Skip to content

Commit eec1618

Browse files
authored
Merge pull request #499 from CommunityToolkit/dev/nullable-get-ref
Use Nullable.GetValueRefOrDefaultRef where possible
2 parents 9fc1027 + 418af71 commit eec1618

File tree

2 files changed

+30
-2
lines changed

2 files changed

+30
-2
lines changed

CommunityToolkit.HighPerformance/Extensions/NullableExtensions.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ public static class NullableExtensions
3535
public static ref T DangerousGetValueOrDefaultReference<T>(this ref T? value)
3636
where T : struct
3737
{
38+
#if NET7_0_OR_GREATER
39+
return ref Unsafe.AsRef(in Nullable.GetValueRefOrDefaultRef(in value));
40+
#else
3841
return ref Unsafe.As<T?, RawNullableData<T>>(ref value).Value;
42+
#endif
3943
}
4044

4145
/// <summary>
@@ -46,17 +50,41 @@ public static ref T DangerousGetValueOrDefaultReference<T>(this ref T? value)
4650
/// <returns>A reference to the value of the input <see cref="Nullable{T}"/> instance, or a <see langword="null"/> <typeparamref name="T"/> reference.</returns>
4751
/// <remarks>The returned reference can be tested for <see langword="null"/> using <see cref="Unsafe.IsNullRef{T}(ref T)"/>.</remarks>
4852
[MethodImpl(MethodImplOptions.AggressiveInlining)]
49-
public static unsafe ref T DangerousGetValueOrNullReference<T>(ref this T? value)
53+
public static ref T DangerousGetValueOrNullReference<T>(ref this T? value)
5054
where T : struct
5155
{
56+
#if NET7_0_OR_GREATER
57+
ref T resultRef = ref Unsafe.NullRef<T>();
58+
59+
// This pattern ensures that the resulting code ends up having a single return, and a single
60+
// forward branch (the one where the value is null) that is predicted non taken. That is,
61+
// the initial null ref is very cheap as it's just clearing a register, and the rest of the
62+
// code is a single assignment (lea on x86-64) that should always be taken. This results in:
63+
// =============================
64+
// L0000: xor eax, eax
65+
// L0002: cmp byte ptr[rcx], 0
66+
// L0005: je short L000b
67+
// L0007: lea rax, [rcx + 4]
68+
// L000b: ret
69+
// =============================
70+
// This is better than what the code would've been with two separate returns in the method.
71+
if (value.HasValue)
72+
{
73+
resultRef = ref Unsafe.AsRef(in Nullable.GetValueRefOrDefaultRef(in value));
74+
}
75+
76+
return ref resultRef;
77+
#else
5278
if (value.HasValue)
5379
{
5480
return ref Unsafe.As<T?, RawNullableData<T>>(ref value).Value;
5581
}
5682

5783
return ref Unsafe.NullRef<T>();
84+
#endif
5885
}
5986

87+
#if !NET7_0_OR_GREATER
6088
/// <summary>
6189
/// Mapping type that reflects the internal layout of the <see cref="Nullable{T}"/> type.
6290
/// See https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Nullable.cs.
@@ -70,6 +98,7 @@ private struct RawNullableData<T>
7098
public T Value;
7199
#pragma warning restore CS0649
72100
}
101+
#endif
73102
}
74103

75104
#endif

azure-pipelines.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ jobs:
3737
displayName: Install the .NET 7 SDK
3838
inputs:
3939
version: $(DotNet.Version)
40-
includePreviewVersions: true
4140
performMultiLevelLookup: true
4241

4342
# Set Build Version

0 commit comments

Comments
 (0)