Getting the size of a field in bytes with C#

51,016

Solution 1

You can't, basically. It will depend on padding, which may well be based on the CLR version you're using and the processor etc. It's easier to work out the total size of an object, assuming it has no references to other objects: create a big array, use GC.GetTotalMemory for a base point, fill the array with references to new instances of your type, and then call GetTotalMemory again. Take one value away from the other, and divide by the number of instances. You should probably create a single instance beforehand to make sure that no new JITted code contributes to the number. Yes, it's as hacky as it sounds - but I've used it to good effect before now.

Just yesterday I was thinking it would be a good idea to write a little helper class for this. Let me know if you'd be interested.

EDIT: There are two other suggestions, and I'd like to address them both.

Firstly, the sizeof operator: this only shows how much space the type takes up in the abstract, with no padding applied round it. (It includes padding within a structure, but not padding applied to a variable of that type within another type.)

Next, Marshal.SizeOf: this only shows the unmanaged size after marshalling, not the actual size in memory. As the documentation explicitly states:

The size returned is the actually the size of the unmanaged type. The unmanaged and managed sizes of an object can differ. For character types, the size is affected by the CharSet value applied to that class.

And again, padding can make a difference.

Just to clarify what I mean about padding being relevant, consider these two classes:

class FourBytes { byte a, b, c, d; }
class FiveBytes { byte a, b, c, d, e; }

On my x86 box, an instance of FourBytes takes 12 bytes (including overhead). An instance of FiveBytes takes 16 bytes. The only difference is the "e" variable - so does that take 4 bytes? Well, sort of... and sort of not. Fairly obviously, you could remove any single variable from FiveBytes to get the size back down to 12 bytes, but that doesn't mean that each of the variables takes up 4 bytes (think about removing all of them!). The cost of a single variable just isn't a concept which makes a lot of sense here.

Solution 2

Depending on the needs of the questionee, Marshal.SizeOf might or might not give you what you want. (Edited after Jon Skeet posted his answer).

using System;
using System.Runtime.InteropServices;

public class MyClass
{
    public static void Main()
    {
        Int32 a = 10;
        Console.WriteLine(Marshal.SizeOf(a));
        Console.ReadLine();
    }
}

Note that, as jkersch says, sizeof can be used, but unfortunately only with value types. If you need the size of a class, Marshal.SizeOf is the way to go.

Jon Skeet has laid out why neither sizeof nor Marshal.SizeOf is perfect. I guess the questionee needs to decide wether either is acceptable to his problem.

Solution 3

From Jon Skeets recipe in his answer I tried to make the helper class he was refering to. Suggestions for improvements are welcome.

public class MeasureSize<T>
{
    private readonly Func<T> _generator;
    private const int NumberOfInstances = 10000;
    private readonly T[] _memArray;

    public MeasureSize(Func<T> generator)
    {
        _generator = generator;
        _memArray = new T[NumberOfInstances];
    }

    public long GetByteSize()
    {
        //Make one to make sure it is jitted
        _generator();

        long oldSize = GC.GetTotalMemory(false);
        for(int i=0; i < NumberOfInstances; i++)
        {
            _memArray[i] = _generator();
        }
        long newSize = GC.GetTotalMemory(false);
        return (newSize - oldSize) / NumberOfInstances;
    }
}

Usage:

Should be created with a Func that generates new Instances of T. Make sure the same instance is not returned everytime. E.g. This would be fine:

    public long SizeOfSomeObject()
    {
        var measure = new MeasureSize<SomeObject>(() => new SomeObject());
        return measure.GetByteSize();
    }

Solution 4

I had to boil this down all the way to IL level, but I finally got this functionality into C# with a very tiny library.

You can get it (BSD licensed) at bitbucket

Example code:

using Earlz.BareMetal;

...
Console.WriteLine(BareMetal.SizeOf<int>()); //returns 4 everywhere I've tested
Console.WriteLine(BareMetal.SizeOf<string>()); //returns 8 on 64-bit platforms and 4 on 32-bit
Console.WriteLine(BareMetal.SizeOf<Foo>()); //returns 16 in some places, 24 in others. Varies by platform and framework version

...

struct Foo
{
  int a, b;
  byte c;
  object foo;
}

Basically, what I did was write a quick class-method wrapper around the sizeof IL instruction. This instruction will get the raw amount of memory a reference to an object will use. For instance, if you had an array of T, then the sizeof instruction would tell you how many bytes apart each array element is.

This is extremely different from C#'s sizeof operator. For one, C# only allows pure value types because it's not really possible to get the size of anything else in a static manner. In contrast, the sizeof instruction works at a runtime level. So, however much memory a reference to a type would use during this particular instance would be returned.

You can see some more info and a bit more in-depth sample code at my blog

Solution 5

It can be done indirectly, without considering the alignment. The number of bytes that reference type instance is equal service fields size + type fields size. Service fields(in 32x takes 4 bytes each, 64x 8 bytes):

  1. Sysblockindex
  2. Pointer to methods table
  3. +Optional(only for arrays) array size

So, for class without any fileds, his instance takes 8 bytes on 32x machine. If it is class with one field, reference on the same class instance, so, this class takes(64x):

Sysblockindex + pMthdTable + reference on class = 8 + 8 + 8 = 24 bytes

If it is value type, it does not have any instance fields, therefore in takes only his fileds size. For example if we have struct with one int field, then on 32x machine it takes only 4 bytes memory.

Share:
51,016
Admin
Author by

Admin

Updated on December 30, 2020

Comments

  • Admin
    Admin over 3 years

    I have a class, and I want to inspect its fields and report eventually how many bytes each field takes. I assume all fields are of type Int32, byte, etc.

    How can I find out easily how many bytes does the field take?

    I need something like:

    Int32 a;
    // int a_size = a.GetSizeInBytes;
    // a_size should be 4
    
  • Philip Fourie
    Philip Fourie over 15 years
    MSDN:Although you can use the SizeOf method, the value returned by this method is not always the same as the value returned by sizeof. Marshal.SizeOf returns the size after the type has been marshaled whereas sizeof returns thesize as it has been allocated by the CLR, including any padding.
  • Philip Fourie
    Philip Fourie over 15 years
    Link to MSDN: ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_csref/html/c54859‌​2c-677c-4f40-a4ce-e6‌​13f7529141.htm
  • Windows programmer
    Windows programmer over 15 years
    The question says "I want to inspect it's fields, and report eventually how much bytes does each field take. I assume all fields are of types as Int32, byte etc." so I don't think Jon Skeet was thinking of class overhead.
  • Jon Skeet
    Jon Skeet over 15 years
    It's not the class overhead - it's that after marshalling, an object can look very different to how it looks in memory. Marshalling is black magic as far as I'm concerned... but the marshalled size may well be different to the size in memory. I'll edit my answer.
  • Lasse V. Karlsen
    Lasse V. Karlsen over 15 years
    I guess only the original poster can determine wether either is acceptable, or not. Revised my answer and wikied it.
  • gap
    gap about 12 years
    @jon - I'd be interested in your helper class... available?
  • Jon Skeet
    Jon Skeet about 12 years
    @gap: No, I never wrote the helper class, I'm afraid.
  • Joshua Belden
    Joshua Belden about 12 years
    Sorry, I duplicated the question in different words. For furture reference: stackoverflow.com/questions/11126375/….
  • Earlz
    Earlz over 11 years
    Are there any new developments on this answer? Surely there is a more pretty way than relying on the garbage collector. Also, what about the ldelema. If you go down to IL level, wouldn't ldelema be reliable in determining the size of a given object? (if you took the address of two side-by-side elements in an array of T, then got the difference)... Or for that matter the sizeof instruction should do exactly what this question is asking
  • Jon Skeet
    Jon Skeet over 11 years
    @Earlz: I explained why sizeof isn't ideal in the answer. Note that my GC-based answer is really to determine the size of an object rather than the size of a field.
  • Earlz
    Earlz over 11 years
    @JonSkeet I mean that sizeof as a C# construct acts completely differently from the sizeof instruction in IL
  • Earlz
    Earlz over 11 years
    @JonSkeet see my answer. It's completely possible to get this information if you drop down to IL level
  • Jon Skeet
    Jon Skeet over 11 years
    That doesn't actually tell you how much space a field will take up though. That's contextual. For example, what's the result for byte? It will depend on whether there are other fields it can fit in with, in terms of padding.
  • Earlz
    Earlz over 11 years
    @JonSkeet of course, but it will work on structs as well. There basically is no possible way to get "what will byte be in every single context" It's different in different contexts. But, this will help I'm sure. (if all else fails, you can add fields to a struct one field at a time taking a measure with this to determine the size of each field)
  • Jon Skeet
    Jon Skeet over 11 years
    You can't add the structs one at a time, because then you get into padding and "minimum size" issues - for example, a class with a single int field takes no more space than a class with no fields at all. Then adding another int field will make it take 4 (or 8) more bytes. That doesn't mean there's anything special about the second field - removing the first field would do just as well as removing the second field. Fundamentally I'm not convinced the field size is really useful to the OP anyway. I know it's what was requested, but I think it's very rarely genuinely useful.
  • redcalx
    redcalx over 11 years
    I'd change 'generator' to the more standard term 'factory'.
  • Earlz
    Earlz over 11 years
    @JonSkeet well, the original reason for me creating this was to determine the size of arrays containing structs in raw bytes as a way to avoid the large object heap. Beyond that, you're correct, I can't see any good use for it unless you're doing some weird pointer arithmetic or some other stuff that's frowned upon in C#
  • Qwertie
    Qwertie about 11 years
    This library could be useful for diagnostic/benchmarking/profiling purposes. For instance, I have written several generic collection classes. These classes could provide a "TotalSize" property if they had access to the true size of T and to the true size of their own class. And while there is still no "sizeof" for a heap object, I can measure the size of a collection class empirically and then hard-coded as a constant for this purpose (actually I just rely on the known padding rules and the fact that an object header is 2 words [8/16 bytes] or 3-4 words for an array.)
  • Qwertie
    Qwertie about 11 years
    The only thing that stops me from using it is the fact that it's klunky to distribute a separate DLL for one minor diagnostic feature :(
  • Earlz
    Earlz about 11 years
    @Qwertie unfortunately it's not possible otherwise :( The reflection APIs don't expose the sizeof opcode for structs. If you want though, you can use something like ILMerge as a post-build step to keep from having to distribute a separate assembly
  • Alexey Khoroshikh
    Alexey Khoroshikh about 11 years
    The approach doesn't take into account the initial memory allocation for _memArray. I.e., it makes sense to measure oldSize before _memArray = new T[NumberOfInstances];
  • Jesper Niedermann
    Jesper Niedermann about 11 years
    @Alexey But you do not want to measure _memArray ! It is just a helper array to contain the stuff you want to measure i.e. the size of T. That is the whole point of this class.
  • Alexey Khoroshikh
    Alexey Khoroshikh about 11 years
    Yes, @Jesper, you're definitely right theoretically, meaning the "pure" class instance size. However, if talking about real world examples (e.g. "how many instances could be loaded into the free memory", the issue I had) one has to take the reference to this instance into account. So my estimation is just more useful despite its theoretical incorectness. Obviously, I had to include this in the previous comment. Sorry for misunderstanding I brought.
  • Jesper Niedermann
    Jesper Niedermann about 11 years
    @Alexey OK I see your point. But I guess the arrays size would be miniscule compared to the content of the array. Would it not ? Besides the original question was more about measuring the size of a single object. But of course feel free to change the code as needed :)
  • yoyo
    yoyo over 7 years
    I suggest you pass true to GetTotalMemory, to (try and) force garbage collection before measuring in order to get the most accurate possible value for currently allocated memory.
  • Jesper Niedermann
    Jesper Niedermann over 7 years
    @yoyo to my knowledge passing true to GetTotalMemory would be a bad idea. You suddenly make the whole thing (more) imprecise and non-determinististic because the GC can be partial. I would assume that using GetTotalMemory(false) makes it deterministic if the process is one-threaded, and T is a valuetype this is because I do not use the heap between the two calls thus GC cannot occur between the calls. In other cases it might require a couple of runs to be absolutely sure that you get the correct result. Because GC can occur then. Not absolutely sure.
  • yoyo
    yoyo over 7 years
    @JesperNiedermann you are correct to raise the caution, but I would say whether to pass true or false depends on your environment and your intent. I was trying to measure allocations as accurately as possible, and passing true provided more accurate and granular results. At least on Windows using .NET. Under Unity and Mono, GC collection is too slow, so passing true makes performance unusable. Try both?
  • Glenn Slayden
    Glenn Slayden over 6 years
    @Qwertie It's trivial to implement simple IL using DynamicMethod; You don't need a whole separate library/dll. For example, check this answer, which also happens to provide another approach to the ValueType—or more interestingly, "Formatted Class"—*in-situ* size question. In that answer, I give a general purpose managed pointer subtraction function in IL. It returns a byte-offset and thus supports use with disjoint types, so it can also measure the size of any struct given something that's known to be pack=1 adjacent.
  • Glenn Slayden
    Glenn Slayden over 6 years
    @Earlz Re: "...reason for... creating this was to determine the size of arrays containing structs... as a way to avoid the [LOH]..." OMG me too! I now wonder, given that .NET has sealed up this area of "runtime struct layout" so thoroughly and well, whether this precise use-case—the one that we both exactly hit upon—is the only one they didn't happen to foresee.
  • Glenn Slayden
    Glenn Slayden over 6 years
    @Earlz Ok, so I made a DynamicMethod version of your Opcodes.SizeOf idea and the problem is that the sizeof IL opcode fails for (non-blittable) structs (and 'formatted types') which contain managed fields. The error is "CLR has detected an Invalid program." That stricter interpretation is maybe leftover from prior to the advent of the formatted class concept in .NET 4.0. I was hoping to have an alternative to my managed pointer subtraction approach, but alas, it appears the functionality available from the sizeof opcode is a proper subset of the method I outlined.
  • Glenn Slayden
    Glenn Slayden over 6 years
    Another update: although my managed pointer subtraction doesn't crash with non-blittable structs, it only measures the stored handle size itself, always returning IntPtr.Size. From here it appears that the only way to get the actual layout size of a managed class is via Marshal.ReadInt32(typeof(T).​TypeHandle.Value, 4) At this point, however, note that our "arrays containing structs" scenario is moot, so it's truly unclear if there's any legitimate use for this specific piece of information.