Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)

like image 994
dajuric Avatar asked Aug 10 '13 23:08

dajuric


3 Answers

Taking the above thinking one step further, I arrived at:

public static class TypeSize<T>
{
    public readonly static int Size;

    static TypeSize()
    {
        var dm = new DynamicMethod("SizeOfType", typeof(int), new Type[] { });
        ILGenerator il = dm.GetILGenerator();
        il.Emit(OpCodes.Sizeof, typeof(T));
        il.Emit(OpCodes.Ret);
        Size = (int)dm.Invoke(null, null);
    }
}

...which I believe is the most efficient solution to the problem.

like image 136
Mark Avatar answered Oct 13 '22 12:10

Mark


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));
    }
like image 22
Mike Zboray Avatar answered Oct 13 '22 11:10

Mike Zboray


Now there is possibility for the unmanaged types in unsafe context to do this, if that is sufficient.

    private unsafe int MySizeOf<T>() where T : unmanaged
    {
        return sizeof(T);
    }
like image 4
Richard Vondracek Avatar answered Oct 13 '22 12:10

Richard Vondracek