Skip to content

Commit

Permalink
Merge pull request #499 from CommunityToolkit/dev/nullable-get-ref
Browse files Browse the repository at this point in the history
Use Nullable.GetValueRefOrDefaultRef where possible
  • Loading branch information
Sergio0694 authored Nov 15, 2022
2 parents 9fc1027 + 418af71 commit eec1618
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ public static class NullableExtensions
public static ref T DangerousGetValueOrDefaultReference<T>(this ref T? value)
where T : struct
{
#if NET7_0_OR_GREATER
return ref Unsafe.AsRef(in Nullable.GetValueRefOrDefaultRef(in value));
#else
return ref Unsafe.As<T?, RawNullableData<T>>(ref value).Value;
#endif
}

/// <summary>
Expand All @@ -46,17 +50,41 @@ public static ref T DangerousGetValueOrDefaultReference<T>(this ref T? value)
/// <returns>A reference to the value of the input <see cref="Nullable{T}"/> instance, or a <see langword="null"/> <typeparamref name="T"/> reference.</returns>
/// <remarks>The returned reference can be tested for <see langword="null"/> using <see cref="Unsafe.IsNullRef{T}(ref T)"/>.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T DangerousGetValueOrNullReference<T>(ref this T? value)
public static ref T DangerousGetValueOrNullReference<T>(ref this T? value)
where T : struct
{
#if NET7_0_OR_GREATER
ref T resultRef = ref Unsafe.NullRef<T>();

// This pattern ensures that the resulting code ends up having a single return, and a single
// forward branch (the one where the value is null) that is predicted non taken. That is,
// the initial null ref is very cheap as it's just clearing a register, and the rest of the
// code is a single assignment (lea on x86-64) that should always be taken. This results in:
// =============================
// L0000: xor eax, eax
// L0002: cmp byte ptr[rcx], 0
// L0005: je short L000b
// L0007: lea rax, [rcx + 4]
// L000b: ret
// =============================
// This is better than what the code would've been with two separate returns in the method.
if (value.HasValue)
{
resultRef = ref Unsafe.AsRef(in Nullable.GetValueRefOrDefaultRef(in value));
}

return ref resultRef;
#else
if (value.HasValue)
{
return ref Unsafe.As<T?, RawNullableData<T>>(ref value).Value;
}

return ref Unsafe.NullRef<T>();
#endif
}

#if !NET7_0_OR_GREATER
/// <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.
Expand All @@ -70,6 +98,7 @@ private struct RawNullableData<T>
public T Value;
#pragma warning restore CS0649
}
#endif
}

#endif
1 change: 0 additions & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ jobs:
displayName: Install the .NET 7 SDK
inputs:
version: $(DotNet.Version)
includePreviewVersions: true
performMultiLevelLookup: true

# Set Build Version
Expand Down

0 comments on commit eec1618

Please sign in to comment.