Size of generic structure

余生颓废 提交于 2019-11-30 20:27:50

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));
    }

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.

It would appear that your aim is to resolve the argument Type from your functions return type Func<Type, int> at compile time. This information is not known at compile time and there is no apparent way to resolve this information using reflection at runtime.

I do not see what benefit returning the the dynamic method serves instead of invoking the dynamic method and returning the result immediately. Given that you have not given any context I would propose the obvious solution, return the value immediately. If your concerns lie with performance then simply cache the results in a dictionary.

public static class GlobalExtensions
{

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

    public static int SizeOf(this Type type)
    {
        var dynamicMethod = new DynamicMethod("SizeOf", typeof(int), Type.EmptyTypes);
        var generator = dynamicMethod.GetILGenerator();

        generator.Emit(OpCodes.Sizeof, type);
        generator.Emit(OpCodes.Ret);

        var function = (Func<int>) dynamicMethod.CreateDelegate(typeof(Func<int>));
        return function();
    }
}

Using an extension method leverages some nice syntax. You can now proceed to obtain the size of a generic structure or class using the following code:

var size = TypeExtensions.SizeOf<Example<int>>();

//alternative syntax... 
size = typeof (Example<int>).SizeOf();

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);
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!