Skip to content

Instantly share code, notes, and snippets.

@mburbea
Last active November 24, 2023 00:10
Show Gist options
  • Save mburbea/7164f02722c5ac3a311a72a76c73c0e5 to your computer and use it in GitHub Desktop.
Save mburbea/7164f02722c5ac3a311a72a76c73c0e5 to your computer and use it in GitHub Desktop.
An attempt at creating a variant type that avoids boxing.
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))!;
}
}
@NWoodsman
Copy link

@mburbea Could we get this as MIT License?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment