task 2.1-2.2: implement MarshalCache<T> with tests
Add MarshalCache<T> static class that computes Size, SizeU, TypeRequiresMarshal, IsIntPtr, TypeCode, and RealType once per type in the static constructor. Handles bool (size=1), enums (underlying type), and MarshalAs-attributed fields (TypeRequiresMarshal). 12 new tests covering: blittable sizes, bool size, enum size, struct size, marshal-required flag, IsIntPtr, computed-once caching. All passing.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WhiteMagic;
|
||||
|
||||
/// <summary>
|
||||
/// Computes and caches marshal-related metadata for type <typeparamref name="T"/>
|
||||
/// exactly once. <see cref="MemoryBase.Read{T}"/> and <see cref="MemoryBase.Write{T}"/>
|
||||
/// branch on these cached flags to decide between blittable <c>Span</c>/<c>MemoryMarshal</c>
|
||||
/// paths and the fallback marshal path.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to cache metadata for.</typeparam>
|
||||
public static class MarshalCache<T>
|
||||
{
|
||||
/// <summary>The unmanaged size of <typeparamref name="T"/> in bytes.</summary>
|
||||
public static readonly int Size;
|
||||
|
||||
/// <summary>The unmanaged size of <typeparamref name="T"/> as an unsigned integer.</summary>
|
||||
public static readonly uint SizeU;
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="true"/> when <typeparamref name="T"/> has at least one field
|
||||
/// decorated with <see cref="MarshalAsAttribute"/>, meaning it cannot be copied
|
||||
/// via a simple pointer dereference.
|
||||
/// </summary>
|
||||
public static readonly bool TypeRequiresMarshal;
|
||||
|
||||
/// <summary><see langword="true"/> when <typeparamref name="T"/> is <see cref="IntPtr"/>.</summary>
|
||||
public static readonly bool IsIntPtr;
|
||||
|
||||
/// <summary>The underlying type code of <typeparamref name="T"/>.</summary>
|
||||
public static readonly TypeCode TypeCode;
|
||||
|
||||
/// <summary>
|
||||
/// The effective type that the marshaler uses. For an enum this is the underlying
|
||||
/// integer type; for all other types it is <typeparamref name="T"/> itself.
|
||||
/// </summary>
|
||||
public static readonly Type RealType;
|
||||
|
||||
static MarshalCache()
|
||||
{
|
||||
TypeCode = Type.GetTypeCode(typeof(T));
|
||||
|
||||
if (typeof(T) == typeof(bool))
|
||||
{
|
||||
Size = 1;
|
||||
RealType = typeof(T);
|
||||
}
|
||||
else if (typeof(T).IsEnum)
|
||||
{
|
||||
Type underlying = typeof(T).GetEnumUnderlyingType();
|
||||
Size = Marshal.SizeOf(underlying);
|
||||
RealType = underlying;
|
||||
TypeCode = Type.GetTypeCode(underlying);
|
||||
}
|
||||
else
|
||||
{
|
||||
Size = Marshal.SizeOf(typeof(T));
|
||||
RealType = typeof(T);
|
||||
}
|
||||
|
||||
SizeU = (uint)Size;
|
||||
IsIntPtr = RealType == typeof(IntPtr);
|
||||
|
||||
TypeRequiresMarshal =
|
||||
RealType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Any(f => f.GetCustomAttributes(typeof(MarshalAsAttribute), true).Length != 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using WhiteMagic;
|
||||
|
||||
namespace WhiteMagicTest;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="MarshalCache{T}"/>: blittable size, marshal-required flag,
|
||||
/// IsIntPtr, and computed-once behavior.
|
||||
/// </summary>
|
||||
public class MarshalCacheTests
|
||||
{
|
||||
[Fact]
|
||||
public void Size_for_int_is_4()
|
||||
{
|
||||
Assert.Equal(4, MarshalCache<int>.Size);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Size_for_byte_is_1()
|
||||
{
|
||||
Assert.Equal(1, MarshalCache<byte>.Size);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Size_for_IntPtr_matches_native_pointer_size()
|
||||
{
|
||||
Assert.Equal(IntPtr.Size, MarshalCache<IntPtr>.Size);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Size_for_bool_is_1()
|
||||
{
|
||||
Assert.Equal(1, MarshalCache<bool>.Size);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Size_for_enum_matches_underlying_type()
|
||||
{
|
||||
Assert.Equal(4, MarshalCache<DayOfWeek>.Size);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Size_for_blittable_struct_is_accurate()
|
||||
{
|
||||
Assert.Equal(8, MarshalCache<BlittableStruct>.Size);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TypeRequiresMarshal_is_false_for_blittable_types()
|
||||
{
|
||||
Assert.False(MarshalCache<int>.TypeRequiresMarshal);
|
||||
Assert.False(MarshalCache<long>.TypeRequiresMarshal);
|
||||
Assert.False(MarshalCache<BlittableStruct>.TypeRequiresMarshal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TypeRequiresMarshal_is_true_for_types_with_MarshalAs_field()
|
||||
{
|
||||
Assert.True(MarshalCache<MarshalAsStruct>.TypeRequiresMarshal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsIntPtr_is_true_for_IntPtr()
|
||||
{
|
||||
Assert.True(MarshalCache<IntPtr>.IsIntPtr);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsIntPtr_is_false_for_non_IntPtr_types()
|
||||
{
|
||||
Assert.False(MarshalCache<int>.IsIntPtr);
|
||||
Assert.False(MarshalCache<long>.IsIntPtr);
|
||||
Assert.False(MarshalCache<BlittableStruct>.IsIntPtr);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void All_properties_are_computed_once_and_cached()
|
||||
{
|
||||
int size1 = MarshalCache<int>.Size;
|
||||
bool marshal1 = MarshalCache<int>.TypeRequiresMarshal;
|
||||
bool intPtr1 = MarshalCache<int>.IsIntPtr;
|
||||
|
||||
int size2 = MarshalCache<int>.Size;
|
||||
bool marshal2 = MarshalCache<int>.TypeRequiresMarshal;
|
||||
bool intPtr2 = MarshalCache<int>.IsIntPtr;
|
||||
|
||||
Assert.Equal(size1, size2);
|
||||
Assert.Equal(marshal1, marshal2);
|
||||
Assert.Equal(intPtr1, intPtr2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SizeU_matches_Size_as_uint()
|
||||
{
|
||||
Assert.Equal((uint)MarshalCache<int>.Size, MarshalCache<int>.SizeU);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct BlittableStruct
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MarshalAsStruct
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] Data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user