I want to estimate the size of an array of structs containing generic type parameters, in this case a dictionary entry struct. To do that I need the size of the struct.
struct Entry
{
int hash;
int next;
TKey key;
TValue value;
}
How can I get the size in bytes of this struct?
Edit
It seems using Marshal.SizeOf
is problematic. Passing the type of the struct will raise an exception saying that the argument can't be a generic type definition.
If I instead call the overload that takes an instance, e.g. Marshal.SizeOf(default(Entry))
it will work if both generic type arguments are value types. If the generic arguments are e.g. <int, object>
then this exception is thrown
Dictionary`2+Entry[System.Int32,System.Object]' cannot be marshaled as
an unmanaged structure; no meaningful size or offset can be computed.
It sounds like the IL sizeof
instruction could be what you need. The sizeof
instruction is used by the C# sizeof
operator behind-the-scenes, but the IL version has fewer restrictions for some reason.
The ECMA CLI specification (partition III, section 4.25) has this description of the sizeof
instruction:
Returns the size, in bytes, of a type. typeTok
can be a generic
parameter, a reference type or a value type.
For a reference type, the size returned is the size of a reference
value of the corresponding type, not the size of the data stored in
objects referred to by a reference value.
[Rationale: The definition of a value type can change between the
time the CIL is generated and the time that it is loaded for
execution. Thus, the size of the type is not always known when the CIL
is generated. The sizeof
instruction allows CIL code to determine
the size at runtime without the need to call into the Framework class
library. The computation can occur entirely at runtime or at
CIL-to-native-code compilation time. sizeof
returns the total size
that would be occupied by each element in an array of this type –
including any padding the implementation chooses to add. Specifically,
array elements lie sizeof
bytes apart. end rationale]
You should be able to get at the sizeof
instruction with a bit of simple runtime codegen:
Console.WriteLine("Entry is " + TypeHelper.SizeOf(typeof(Entry)) + " bytes.");
// ...
public static class TypeHelper
{
public static int SizeOf<T>(T? obj) where T : struct
{
if (obj == null) throw new ArgumentNullException("obj");
return SizeOf(typeof(T?));
}
public static int SizeOf<T>(T obj)
{
if (obj == null) throw new ArgumentNullException("obj");
return SizeOf(obj.GetType());
}
public static int SizeOf(Type t)
{
if (t == null) throw new ArgumentNullException("t");
return _cache.GetOrAdd(t, t2 =>
{
var dm = new DynamicMethod("$", typeof(int), Type.EmptyTypes);
ILGenerator il = dm.GetILGenerator();
il.Emit(OpCodes.Sizeof, t2);
il.Emit(OpCodes.Ret);
var func = (Func<int>)dm.CreateDelegate(typeof(Func<int>));
return func();
});
}
private static readonly ConcurrentDictionary<Type, int>
_cache = new ConcurrentDictionary<Type, int>();
}
The approximated size would sum of the hash
( 4 bytes (32 bit architecture)) + next
(4 bytes (32 bit architecture)) + TKey
(if reference type 4 bytes for the pointer (32 bit architecture), if value type the size of that value type calculated in recursion)) + TValue
(the same as TKey
)
or
simply using Marshal.SizeOf method.
You may also use Marshal.ReadIntPtr(type.TypeHandle.Value, 4)
. It returns basic instance size of the managed object. See http://msdn.microsoft.com/en-us/magazine/cc163791.aspx for more information about runtime memory layout.
(After I wrote this, I noticed that the approach is anticipated in the rationale LukeH quoted)
struct Pin : IDisposable
{
public GCHandle pinHandle;
public Pin(object o) { pinHandle = GCHandle.Alloc(o, GCHandleType.Pinned); }
public void Dispose()
{
pinHandle.Free();
}
}
static class ElementSize<T>
{
private static int CalcSize(T[] testarray)
{
using (Pin p = new Pin(testarray))
return (int)(Marshal.UnsafeAddrOfPinnedArrayElement(testarray, 1).ToInt64()
- Marshal.UnsafeAddrOfPinnedArrayElement(testarray, 0).ToInt64());
}
static public readonly int Bytes = CalcSize(new T[2]);
}
I'm fairly sure that pinning and throwing away a tiny array is cheaper than dynamic compilation. Plus a static field in a generic class is a great way to have type-safe per-type data... no need for a ConcurrentDictionary
.