4

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.

Anders Forsgren
  • 10,827
  • 4
  • 40
  • 77
  • Is it even possible; surely depending on the types of `TKey` and `TValue` the size will change. – George Duckett May 13 '13 at 09:54
  • If TKey and TValue are generics, you can't now their type upfront so I don't think it's possible to calculate the size. – Wouter de Kort May 13 '13 at 09:54
  • "size of the struct". If you work with generics, the compiler basically creates as much different structs/classes, as you use different TKey/TValue combinations. So there isn't *one* struct with *one* size, but (possibly) many differents structs each with their own individual size. So an `Entry` will have a different size than `Entry`. – Corak May 13 '13 at 10:02
  • Better yet `Marshal.SizeOf(new Entry())` throws an ArgumentException: "Type 'Entry`2[System.String,System.Decimal]' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed." – Corak May 13 '13 at 10:14
  • Yes, I want to get the size of the struct WITH the type known arguments, that is, at runtime. – Anders Forsgren May 13 '13 at 10:45
  • It actually seems like a .NET error, since the exception shows that the type cannot be generic type definition. If type parameters are defined, it is clearly already not a definition. (`Type.IsGenericTypeDefinition` is false.) – IS4 May 13 '13 at 11:20
  • Read the answer by Jon Skeet: http://stackoverflow.com/questions/207592/getting-the-size-of-a-field-in-bytes-with-c-sharp – Luis Filipe May 13 '13 at 11:34
  • He suggests allocating N items in an array and getting the memory use and dividing by N? Surely there has to be a simpler way? Also, his answer seems to be related to the size of a class instance (not a struct) including the size of the fields? – Anders Forsgren May 13 '13 at 11:36

4 Answers4

11

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>();
}
LukeH
  • 263,068
  • 57
  • 365
  • 409
  • Note that this won't help to calculate the storage space required by a class instance; it will just give the size of the reference that would point to it (8 bytes on a 64-bit execution). – Oliver Bock Jan 19 '16 at 04:58
4

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.

Tigran
  • 61,654
  • 8
  • 86
  • 123
  • 3
    4 bytes for the pointer? It's 2013, and you're still assuming a 32-bit architecture? – Damien_The_Unbeliever May 13 '13 at 09:55
  • @Damien_The_Unbeliever: you will not beleive, but we are still using XP in corporate, so... I believe also many others too. – Tigran May 13 '13 at 09:56
  • @Tigran can you show this with an example? I can't seem to get the sizeof/Marshal.SizeOf to work with the generic types. – Anders Forsgren May 13 '13 at 10:55
  • @AndersForsgren: you *should* be able to do it after generic type assigment, so when *real* type is generted by calling: var runtimeType = typeof(struct_instance); and SizeOf on that type. – Tigran May 13 '13 at 11:02
  • Yes, turns out on a struct instance it is possible, but on the struct type it is not (even if I use CreateGenericType and pass in the type args). – Anders Forsgren May 13 '13 at 11:06
  • @AndersForsgren: because the *type* in case of generics depends on runtime type assigned to it, it's not something you cn figure out before actual asignment. – Tigran May 13 '13 at 11:46
  • Yes, but it feels it should be possible to get the size of a struct instance (the sum of its field sizes) *at runtime* when the types are bound. – Anders Forsgren May 13 '13 at 11:50
  • @AndersForsgren: sure, that's why I suugest to use typeof on *instance* of a struct, so you get a runtime type of an instance. – Tigran May 13 '13 at 13:05
0

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.

IS4
  • 11,945
  • 2
  • 47
  • 86
0

(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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720