C#: Getting size of a value-type variable at runtime?
Asked Answered
C

8

29

I know languages such as C and C++ allow determining the size of data (structs, arrays, variables...) at runtime using sizeof() function. I tried that in C# and apparently it does not allow putting variables into the sizeof() function, but type defintions only (float, byte, Int32, uint, etc...), how am I supposed to do that?

Practically, I want this to happen:

int x;
Console.WriteLine(sizeof(x));   // Output: 4

AND NOT:

Console.WriteLine(sizeof(int)); // Output: 4

I'm sure there's some normal way to get the size of data at runtime in C#, yet google didn't give much help.. Here it is my last hope

Cleric answered 17/11, 2011 at 19:41 Comment(4)
Why would you need that?Formalism
Wouldn't you already know that, because you're declaring the variable?Crisper
@delnan: The usecase in C is that, if you change the type of x from int to say long long, you don't have to replace every occurrence of sizeof(int) with sizeof(long long) where you need the size of x. However, I can't think of many cases where one needs the size of a type (or variable) in C#.Impenitent
Plus think var...Cesarcesare
E
30

To find the size of an arbitrary variable, x, at runtime you can use Marshal.SizeOf:

System.Runtime.InteropServices.Marshal.SizeOf(x)

As mentioned by dtb, this function returns the size of the variable after marshalling, but in my experience that is usually the size you want, as in a pure managed environment the size of a variable is of little interest.

Elkeelkhound answered 17/11, 2011 at 19:47 Comment(4)
Not really... Marshal.SizeOf returns the size after marshalling. For example, Marshal.SizeOf('x') returns 1, whereas sizeof(char) returns 2.Impenitent
- System.Runtime.InteropServices.Marshal.SizeOf(myObject) 'System.Runtime.InteropServices.Marshal.SizeOf(myObject)' threw an exception of type 'System.ArgumentException' int {System.ArgumentException}Bywaters
Not a good answer; try doing this on a bool and see what you get.Frenum
@Frenum yeh but... who is out here testing the size of a bool?????? It can literally only be 1 sizeSuccessful
S
33

Following on from Cory's answer, if performance is important and you need to hit this code a lot then you could cache the size so that the dynamic method only needs to be built and executed once per type:

int x = 42;
Console.WriteLine(Utils.SizeOf(x));    // Output: 4

// ...

public static class Utils
{
    public static int SizeOf<T>(T obj)
    {
        return SizeOfCache<T>.SizeOf;
    }

    private static class SizeOfCache<T>
    {
        public static readonly int SizeOf;

        static SizeOfCache()
        {
            var dm = new DynamicMethod("func", typeof(int),
                                       Type.EmptyTypes, typeof(Utils));

            ILGenerator il = dm.GetILGenerator();
            il.Emit(OpCodes.Sizeof, typeof(T));
            il.Emit(OpCodes.Ret);

            var func = (Func<int>)dm.CreateDelegate(typeof(Func<int>));
            SizeOf = func();
        }
    }
}
Shroud answered 17/11, 2011 at 22:48 Comment(4)
I actually think this is the best answer; sizes won't change and marshal is just incorrect as multiple people pointed out. IMO this is an under-rated answer, so +1 from me.Alarum
This is a beaut. Thanks muchly :)Redshank
Strangely this code indeed gives an answer even in those cases where the compiler refuses to emit a sizeof on because they are "managed". I wonder what that means.Isaacisaacs
This code is so confusing, it should be used in the interviews... I like it.Pendant
E
30

To find the size of an arbitrary variable, x, at runtime you can use Marshal.SizeOf:

System.Runtime.InteropServices.Marshal.SizeOf(x)

As mentioned by dtb, this function returns the size of the variable after marshalling, but in my experience that is usually the size you want, as in a pure managed environment the size of a variable is of little interest.

Elkeelkhound answered 17/11, 2011 at 19:47 Comment(4)
Not really... Marshal.SizeOf returns the size after marshalling. For example, Marshal.SizeOf('x') returns 1, whereas sizeof(char) returns 2.Impenitent
- System.Runtime.InteropServices.Marshal.SizeOf(myObject) 'System.Runtime.InteropServices.Marshal.SizeOf(myObject)' threw an exception of type 'System.ArgumentException' int {System.ArgumentException}Bywaters
Not a good answer; try doing this on a bool and see what you get.Frenum
@Frenum yeh but... who is out here testing the size of a bool?????? It can literally only be 1 sizeSuccessful
R
17

The size of int is always going to be 32 bits. Why would you need to get the size at runtime?

With that said, you could use Marshal.SizeOf(), but that's really intended for unmanaged code only.

I stumbled upon some code that apparently will give you the size of a value type. It uses reflection and would be quite an expensive method call compared to the functionality you wanted to use (sizeof()):

using System;
using System.Reflection;
using System.Reflection.Emit;

...

// GetManagedSize() returns the size of a structure whose type
// is 'type', as stored in managed memory. For any reference type
// this will simply return the size of a pointer (4 or 8).
public static int GetManagedSize(Type type)
{
    // all this just to invoke one opcode with no arguments!
    var method = new DynamicMethod("GetManagedSizeImpl", typeof(uint), new Type[0], typeof(TypeExtensions), false);

    ILGenerator gen = method.GetILGenerator();

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

    var func = (Func<uint>)method.CreateDelegate(typeof(Func<uint>));
    return checked((int)func());
}
Rosco answered 17/11, 2011 at 19:46 Comment(3)
I wouldn't say Marshal.SizeOf is intended only unmanaged code. It's intended for interop, hence its namespace. Sometimes interop requires things like this.Elkeelkhound
Remember, marshal.sizeof tells you the size of the thing on the marshalled side not how much memory a thing takes up on the managed side. Those two values can be very different.Reverberatory
Link is dead, and typeof(TypeExtensions) fails to compile =(Berserker
S
9

If you are doing something like building data packets to send to a device, try this:

byte[] dataBytes = BitConverter.GetBytes(x);
int dataLength = dataBytes.Length;

Now you can, for example, copy the dataBytes array to the Payload section of the dataPacket array, and dataLength will tell you how many bytes to copy, and let you validate or set the PayloadLength value in your data packet.

Salinometer answered 28/3, 2014 at 14:11 Comment(0)
V
3

I was going to say use type inference to meet your requirement ("if you change the type of x from int to say long long, you don't have to replace every occurrence of sizeof(int) with sizeof(long long)"):

public unsafe void GetSizeOf<T>(T exemplar)
    where T : struct
{
    return sizeof(T);
}

But you can't do that, because T might be a "managed type" -- it might be a struct with an object reference field. There doesn't seem to be a way to constrain T to only unmanaged types.

You could use a static helper class:

public static class Size
{
    public int Of(int x)
    {
        return sizeof(int);
    }

    public int Of(long x)
    {
        return sizeof(long);
    }

    public unsafe int Of(MyStruct x)
    {
        //only works if MyStruct is unmanaged
        return sizeof(MyStruct);
    }
}
public class Program
{
    public void Main()
    {
        int x = 0;
        Console.WriteLine(Size.Of(x));
    }
    public void OldMain()
    {
        long x = 0;
        Console.WriteLine(Size.Of(x));
    }
}
Vaginate answered 17/11, 2011 at 22:22 Comment(1)
Nice. Note that your methods also need to be static to call them that way: static public int Of(int x).Yaron
U
3

Went ahead and added some safety/performance/convenience features to the code CORY posted, for the less paranoid LukeH's code should suffice.

In short this class returns type sizes, ensuring a cache is used whenever possible, wrapping up exceptions from external classes as it goes.

You may want to rewrite the catch-all blocks to better suit your project.

/* A class for finding the sizes of types and variables */
public static class Sizes
{
    /* Retrieves the size of the generic type T
        Returns the size of 'T' on success, 0 otherwise */
    public static int SizeOf<T>()
    {
        return FetchSizeOf(typeof(T));
    }

    /* Retrieves the size of the type of obj
        Returns the size of 'obj' on success, 0 otherwise */
    public static int SizeOf<T>(T obj)
    {
        return FetchSizeOf(typeof(T));
    }

    /* Retrieves the size of 'type'
        Returns the size of 'type' on success, 0 otherwise */
    public static int SizeOf(this Type type)
    {
        return FetchSizeOf(type);
    }

    /* Gets the size of the specified type
        Returns the size of 'type' on success, 0 otherwise*/
    private static int FetchSizeOf(this Type type)
    {
        if ( typeSizeCache == null )
            CreateCache();

        if ( typeSizeCache != null )
        {
            int size = 0;
            if ( GetCachedSizeOf(type, out size) )
                return size;
            else
                return CalcAndCacheSizeOf(type);
        }
        else
            return CalcSizeOf(type);
    }

    /* Attempts to get the size of type from the cache
        Returns true and sets size on success, returns
        false and sets size to 0 otherwise. */
    private static bool GetCachedSizeOf(Type type, out int size)
    {
        size = 0;
        try
        {
            if ( type != null )
            {
                if ( !typeSizeCache.TryGetValue(type, out size) )
                    size = 0;
            }
        }
        catch
        {
            /*  - Documented: ArgumentNullException
                - No critical exceptions. */
            size = 0;
        }
        return size > 0;
    }

    /* Attempts to calculate the size of 'type', and caches
        the size if it is valid (size > 0)
        Returns the calclated size on success, 0 otherwise */
    private static int CalcAndCacheSizeOf(Type type)
    {
        int typeSize = 0;
        try
        {
            typeSize = CalcSizeOf(type);
            if ( typeSize > 0 )
                typeSizeCache.Add(type, typeSize);
        }
        catch
        {
            /*  - Documented: ArgumentException, ArgumentNullException,
                - Additionally Expected: OutOfMemoryException
                - No critical exceptions documented. */
        }
        return typeSize;
    }

    /* Calculates the size of a type using dynamic methods
        Return the type's size on success, 0 otherwise */
    private static int CalcSizeOf(this Type type)
    {
        try
        {
            var sizeOfMethod = new DynamicMethod("SizeOf", typeof(int), Type.EmptyTypes);
            var generator = sizeOfMethod.GetILGenerator();
            generator.Emit(OpCodes.Sizeof, type);
            generator.Emit(OpCodes.Ret);

            var sizeFunction = (Func<int>)sizeOfMethod.CreateDelegate(typeof(Func<int>));
            return sizeFunction();
        }
        catch
        {
            /*  - Documented: OutOfMemoryException, ArgumentNullException,
                              ArgumentException, MissingMethodException,
                              MethodAccessException
                - No critical exceptions documented. */
        }
        return 0;
    }

    /* Attempts to allocate the typeSizesCache
        returns whether the cache is allocated*/
    private static bool CreateCache()
    {
        if ( typeSizeCache == null )
        {
            try
            {
                typeSizeCache = new Dictionary<Type, int>();
            }
            catch
            {
                /*  - Documented: OutOfMemoryException
                    - No critical exceptions documented. */
                typeSizeCache = null;
            }
        }
        return typeSizeCache != null;
    }

    /* Static constructor for Sizes, sets typeSizeCache to null */
    static Sizes()
    {
        CreateCache();
    }

    /* Caches the calculated size of various types */
    private static Dictionary<Type, int> typeSizeCache;
}
Unification answered 23/9, 2015 at 21:44 Comment(0)
A
3
public static class TypeSize
{
    public static int GetSize<T>(this T value)
    {
        if (typeof(T).IsArray)
        {
            var elementSize = GetTypeSize(typeof(T).GetElementType());
            var length = (value as Array)?.GetLength(0);
            return length.GetValueOrDefault(0) * elementSize;
        }
        return GetTypeSize(typeof(T));
    }

    static ConcurrentDictionary<Type, int> _cache = new ConcurrentDictionary<Type, int>();

    static int GetTypeSize(Type type)
    {
        return _cache.GetOrAdd(type, _ =>
        {
            var dm = new DynamicMethod("SizeOfType", typeof(int), new Type[0]);
            ILGenerator il = dm.GetILGenerator();
            il.Emit(OpCodes.Sizeof, type);
            il.Emit(OpCodes.Ret);
            return (int)dm.Invoke(null, null);
        });
    }
}
Airdrie answered 24/2, 2017 at 11:44 Comment(0)
J
1

Starting with netcore1.0 there is System.Runtime.CompilerServices.Unsafe.SizeOf method that gives you size of any object. Method is implemented in the runtime so it should be very fast.

Note: seems that method returns pointer size (sizeof(IntPtr)) for reference type instead of actual size

Usage:

Console.WriteLine(Unsafe.SizeOf<System.Guid>()); // 16
Jubal answered 2/6, 2021 at 4:5 Comment(2)
I don't like the name "Unsafe", can you explain if there is any possible danger in using this method?, the docs didn't mention anything.Brieta
@Eboubaker, this particular method is safe and can't cause anything bad. Internally it's just sizeof type; ret, see source. This method is located in Unsafe class because C# doesn't allow usage of sizeof on arbitrary type outside of unsafe context. Maybe it's because of hand-written serialization that developer may accidentally do wrongJubal

© 2022 - 2024 — McMap. All rights reserved.