Search code examples
c#genericsstructuresizeofil

Size of generic structure


I need to find out a size of a generic structure (I can not do it like sizeof(T) or using Marshal.SizeOf(...) 0> gives me an error)

So I wrote:

public static class HelperMethods
{
    static HelperMethods()
    {
        SizeOfType = createSizeOfFunc();
    }

    public static int SizeOf<T>()
    {
        return SizeOfType(typeof(T));
    }

    public static readonly Func<Type, int> SizeOfType = null;

    private static Func<Type, int> createSizeOfFunc()
    {
        var dm = new DynamicMethod("SizeOfType", typeof(int), new Type[] { typeof(Type) });

        ILGenerator il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Sizeof); //needs to be il.Emit(OpCodes.Sizeof, typeof(something))
        il.Emit(OpCodes.Ret);

        var func = (Func<Type, int>)dm.CreateDelegate(typeof(Func<Type, int>));
        return func;
    }
}

A diffuclty is that il.Emit(OpCodes.Sizeof) needs an argument which I can not pass it during the method (SizeOfType) creation. How can I pass a parameter which is on stack to il.Emit(OpCodes.Sizeof) using IL ? (or a different solution but I want to cache a function (delegate) not a result what is proposed in the 2nd answer)


Solution

  • Computing size is something that is fraught with problems because you need to know what is meaningful in the context you are using it. I'd assume there is a good reason for Marshal.SizeOf to throw when the argument is a generic struct, but I don't know what it is.

    With that caveat, this code seems to work and gives similar results to Marshal.SizeOf for non-generic structs. It generates a new dynamic method that gets the size via the sizeof IL opcode for the type. It then caches the result (since generating a dynamic method is some what expensive) for future use.

    public class A { int x,y,z; }
    public struct B { int x,y,z,w,a,b; }
    public struct C<T> { Guid g; T b,c,d,e,f; }
    
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine(IntPtr.Size); // on x86 == 4
            Console.WriteLine(SizeHelper.SizeOf(typeof(C<double>))); // prints 56 on x86
            Console.WriteLine(SizeHelper.SizeOf(typeof(C<int>))); // prints 36 on x86
        }
    }
    
    static class SizeHelper
    {
        private static Dictionary<Type, int> sizes = new Dictionary<Type, int>();
    
        public static int SizeOf(Type type)
        {
            int size;
            if (sizes.TryGetValue(type, out size))
            {
                return size;
            }
    
            size = SizeOfType(type);
            sizes.Add(type, size);
            return size;            
        }
    
        private static int SizeOfType(Type type)
        {
            var dm = new DynamicMethod("SizeOfType", typeof(int), new Type[] { });
            ILGenerator il = dm.GetILGenerator();
            il.Emit(OpCodes.Sizeof, type);
            il.Emit(OpCodes.Ret);
            return (int)dm.Invoke(null, null);
        }
    }
    

    Edit

    As far as I can tell there is no way to make non-generic delegate that you can cache. The SizeOf opcode requires a metadata token. It does not take a value from the evaluation stack.

    Actually the code below works as well. I'm not sure why Marshal.SizeOf(Type) throws an argument exception when the type is generic structure but Marshal.SizeOf(Object) does not.

        public static int SizeOf<T>() where T : struct
        {
            return Marshal.SizeOf(default(T));
        }