Size of generic structure
Asked Answered
G

4

13

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)

Goby answered 10/8, 2013 at 23:15 Comment(2)
Can your questions be an example of XY-Problem? (this and #18166929)Oestrin
I stated that I want to find out a size of a generic structure (I need to allocate an unmanaged memory of that size) The problem is that those structures can be generic so the known methods such as Marshal.SizeOf(...) and sizeof() operator do not work.Goby
C
9

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));
    }
Calends answered 11/8, 2013 at 0:22 Comment(5)
If a program will create many large but short-lived data structures, there can be major advantages to subdividing them into arrays which are no more than 85,000 bytes each. What's needed is fundamentally a means of finding the size of an array of N elements; that value is well-defined for all .NET types, even though neither C# nor VB.NET exposes any convenient way of getting it.Gatefold
@Gatefold I assume your trying to avoid the large object heap. I'd be interested in an example where that actually works. I suspect the runtime authors want to keep that number an implementation detail though. Also, there are special cases. For example, arrays of doubles end up on the LOH at above 1000 elements.Calends
Thank you. But my original question is: can I cache a method (created delegate) instead of result ?Goby
@Goby See my edit. I do not think you can create a non-generic delegate to do so.Calends
If you generate dynamic assembly and emit a generic method into dynamic module you could cache it. Then get reference to generic method info, then MakeGeneric(type).Invoke(null, null) ...Mackenziemackerel
S
16

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.

Stgermain answered 24/2, 2017 at 11:28 Comment(6)
+1 Not sure why this got downvoted. This is the most efficient and elegant solution presented here IMO. The static constructor/field combination give you the caching "for free". I conducted some sanity checks and could not find any obvious flaws. Can someone comment if there are serious shortcomings with this approach?Streamlined
Indeed very elegant!Cyanogen
new Type[] { } can be replaced with Array.Empty<Type> or Type.EmptyTypes It's not that much, but may make solution slightly betterSwithbart
@Streamlined According to the docs for OpCodes.Sizeof, the result is always equal to IntPtr.Size when T is a reference-type, so it might make sense to add a constraint: class TypeSize<T> where T : struct to avoid wasting cycles generating dynamic-methods for reference-types.Dartmouth
nah, IntPtr.Size is the correct answer for reference types. We're also allowed to call .ToString() on String objects, right?Streamlined
Great solution! You might consider replacing new Type[] { } with Type.EmptyTypes.Hoarhound
C
9

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));
    }
Calends answered 11/8, 2013 at 0:22 Comment(5)
If a program will create many large but short-lived data structures, there can be major advantages to subdividing them into arrays which are no more than 85,000 bytes each. What's needed is fundamentally a means of finding the size of an array of N elements; that value is well-defined for all .NET types, even though neither C# nor VB.NET exposes any convenient way of getting it.Gatefold
@Gatefold I assume your trying to avoid the large object heap. I'd be interested in an example where that actually works. I suspect the runtime authors want to keep that number an implementation detail though. Also, there are special cases. For example, arrays of doubles end up on the LOH at above 1000 elements.Calends
Thank you. But my original question is: can I cache a method (created delegate) instead of result ?Goby
@Goby See my edit. I do not think you can create a non-generic delegate to do so.Calends
If you generate dynamic assembly and emit a generic method into dynamic module you could cache it. Then get reference to generic method info, then MakeGeneric(type).Invoke(null, null) ...Mackenziemackerel
U
6

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);
    }
Uziel answered 29/12, 2018 at 18:54 Comment(0)
R
4

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();
Riles answered 11/8, 2013 at 0:27 Comment(3)
Thank you. I did that (first attempt). But would not be faster if I create delegate method at then use it instead creating method every time ?Goby
@Goby Well the most optimal way to do this is to cache your results. This sounds complicated but in reality all you need to do is define a Dictionary<Type, Int32 and every time you call SizeOf store the type and the calculated size in the dictionary. Next time you call SizeOf check to see if the type already exists in the dictionary and if it does return the value from the dictionary instead of creating and invoking the delegate. I can update my answer to include an example if you would like :)Riles
Thank you, but there is no need.Goby

© 2022 - 2024 — McMap. All rights reserved.