Skip to content
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
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