Вопрос

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)

Это было полезно?

Решение 2

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.

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

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();
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top