-
-
Save mburbea/7164f02722c5ac3a311a72a76c73c0e5 to your computer and use it in GitHub Desktop.
An attempt at creating a variant type that avoids boxing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Diagnostics.CodeAnalysis; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
namespace ValueMarkerPrototype; | |
[StructLayout(LayoutKind.Sequential)] | |
public readonly partial struct ValueMarker | |
{ | |
private readonly nint _storage; | |
private readonly object _object; | |
private static readonly int s_MaxNonboxingSize = Unsafe.SizeOf<nint>(); | |
// sizeof(nint?) is 2*sizeof(nint) and since we store T? as a T, we can eliminate large structs quickly. | |
// It is possible to construct a struct S where sizeof(S) > sizeof(nint) && sizeof(S?) < sizeof(nint?) | |
// So this is a coarse filter. Inside the helper we will compare against the s_NonBoxingSize. | |
private static readonly int s_MaxNullableNonboxingSize = Unsafe.SizeOf<nint>() * 2; | |
private ValueMarker(nint storage, object obj) | |
{ | |
_storage = storage; | |
_object = obj; | |
} | |
private ValueMarker(object obj) | |
{ | |
Unsafe.SkipInit(out _storage); | |
_object = obj; | |
} | |
public bool HasValue => _object != null; | |
public Type? Type => _object is IMarker m ? m.Type : _object?.GetType(); | |
public object? Value => _object is IMarker m ? m.Box(this) : _object; | |
public static ValueMarker Create<T>(T? value) | |
{ | |
if (value != null) | |
{ | |
if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>()) | |
{ | |
if (default(T) != null) | |
{ | |
if (Unsafe.SizeOf<T>() <= s_MaxNonboxingSize) | |
{ | |
return new(Unsafe.As<T, nint>(ref value), IMarker.Cache<T>.Instance); | |
} | |
} | |
else | |
{ | |
if (Unsafe.SizeOf<T>() <= s_MaxNullableNonboxingSize) | |
{ | |
return INullableHelpers<T>.Instance.Create(value); | |
} | |
} | |
} | |
return new(value); | |
} | |
return default; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public readonly bool TryGetValue<T>([NotNullWhen(true)] out T? value) | |
{ | |
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>() | |
|| (default(T) == null && Unsafe.SizeOf<T>() > s_MaxNullableNonboxingSize) | |
|| (default(T) != null && Unsafe.SizeOf<T>() > s_MaxNonboxingSize)) | |
{ | |
if (_object is T v) | |
{ | |
value = v; | |
return true; | |
} | |
} | |
else if (default(T) == null) | |
{ | |
return INullableHelpers<T>.Instance.TryGetValue(this, out value); | |
} | |
else if (default(T) != null) | |
{ | |
if (_object == IMarker.Cache<T>.Instance) | |
{ | |
value = Unsafe.As<nint, T>(ref Unsafe.AsRef(_storage))!; | |
return true; | |
} | |
} | |
value = default; | |
return default(T) is null && _object is null; | |
} | |
#region Nullable struct specializations | |
/* Specialize for nullable structs. In non-generic contexts or when T has a struct constraint | |
* these direct methods will be resolved. This avoids the redirection through the Helper instance. */ | |
public static ValueMarker Create<T>(T? value) | |
where T : struct | |
{ | |
if (value.HasValue) | |
{ | |
return !RuntimeHelpers.IsReferenceOrContainsReferences<T>() && Unsafe.SizeOf<T>() <= s_MaxNonboxingSize | |
? (new(Unsafe.As<T, nint>(ref Unsafe.AsRef(value.GetValueOrDefault())), IMarker.Cache<T>.Instance)) | |
: (new(value)); | |
} | |
return default; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public readonly bool TryGetValue<T>([NotNullWhen(true)] out T? value) | |
where T : struct | |
{ | |
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>() || Unsafe.SizeOf<T>() > s_MaxNonboxingSize) | |
{ | |
if (_object is T v) | |
{ | |
value = v; | |
return true; | |
} | |
} | |
else | |
{ | |
if (_object == IMarker.Cache<T>.Instance) | |
{ | |
value = Unsafe.As<nint, T>(ref Unsafe.AsRef(_storage)); | |
return true; | |
} | |
} | |
value = default; | |
return _object == null; | |
} | |
#endregion | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public T As<T>() | |
{ | |
if (!TryGetValue<T>(out T? value)) | |
{ | |
ThrowInvalidCast(); | |
} | |
return value; | |
} | |
[DoesNotReturn] | |
private static void ThrowInvalidCast() => throw new InvalidCastException(); | |
private interface IMarker | |
{ | |
internal static class Cache<T> | |
{ | |
internal static IMarker Instance = typeof(T).IsValueType && default(T) == null | |
? (IMarker)Activator.CreateInstance(typeof(Marker<>).MakeGenericType(typeof(T).GetGenericArguments()[0]))! | |
: new Marker<T>(); | |
} | |
public abstract Type Type { get; } | |
public abstract object Box(in ValueMarker v); | |
} | |
private interface INullableHelpers<T> | |
{ | |
public static INullableHelpers<T> Instance = (Activator.CreateInstance(typeof(NullableHelpers<>) | |
.MakeGenericType(typeof(T).GetGenericArguments()[0])) | |
as INullableHelpers<T>)!; | |
public abstract bool TryGetValue(ValueMarker v, [NotNullWhen(true)] out T? value); | |
public abstract ValueMarker Create(T value); | |
} | |
private sealed class NullableHelpers<T> : INullableHelpers<T?> | |
where T : struct | |
{ | |
public ValueMarker Create(T? value) => ValueMarker.Create<T>(value); | |
public bool TryGetValue(ValueMarker v, [NotNullWhen(true)] out T? value) => v.TryGetValue<T>(out value); | |
} | |
private sealed class Marker<T> : IMarker | |
{ | |
public Type Type => typeof(T); | |
public object Box(in ValueMarker v) => Unsafe.As<nint, T>(ref Unsafe.AsRef(v._storage))!; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@mburbea Could we get this as MIT License?