Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Size of struct with generic type fields

Tags:

c#

.net

struct

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.

like image 400
Anders Forsgren Avatar asked Dec 06 '22 07:12

Anders Forsgren


1 Answers

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>();
}
like image 160
LukeH Avatar answered Dec 20 '22 23:12

LukeH