Получение размера поля в байтах с помощью C # - PullRequest
59 голосов
/ 16 октября 2008

У меня есть класс, и я хочу проверить его поля и в конце концов сообщить, сколько байтов занимает каждое поле. Я предполагаю, что все поля имеют тип Int32, байт и т. Д.

Как я могу легко узнать, сколько байт занимает поле?

Мне нужно что-то вроде:

Int32 a;
// int a_size = a.GetSizeInBytes;
// a_size should be 4

Ответы [ 8 ]

98 голосов
/ 16 октября 2008

Вы не можете, в основном. Это будет зависеть от заполнения, которое вполне может основываться на используемой вами версии CLR, процессоре и т. Д. Проще определить общий размер объекта, предполагая, что он не имеет ссылок на другие объекты: создайте большой массив, используйте GC.GetTotalMemory для базовой точки, заполните массив ссылками на новые экземпляры вашего типа, а затем снова вызовите GetTotalMemory. Отнимите одно значение от другого и разделите на количество экземпляров. Возможно, вам следует заранее создать один экземпляр, чтобы убедиться, что новый код JITted не влияет на число. Да, это так же хакерски, как и звучит, - но до сих пор я использовал это с хорошим эффектом.

Буквально вчера я думал, что было бы неплохо написать для этого небольшой класс помощников. Дайте мне знать, если вам будет интересно.

РЕДАКТИРОВАТЬ: Есть два других предложения, и я хотел бы обратиться к ним обоим.

Во-первых, оператор sizeof : он показывает только то, сколько места занимает тип в аннотации, без дополнения вокруг него. (Он включает отступы внутри структуры, но не отступы, применяемые к переменной этого типа внутри другого типа.)

Далее, Marshal.SizeOf : показывает только неуправляемый размер после сортировки, а не фактический размер в памяти. В документации прямо говорится:

Возвращенный размер на самом деле размер неуправляемого типа. неуправляемые и управляемые размеры объект может отличаться. Для персонажа типы, размер зависит от Значение CharSet, примененное к этому классу.

И снова, отступы могут иметь значение.

Просто чтобы прояснить, что я имею в виду, что отступы актуальны, рассмотрим эти два класса:

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

На моем компьютере x86 экземпляр FourBytes занимает 12 байтов (включая служебные данные). Экземпляр FiveBytes занимает 16 байтов. Единственная разница - это переменная "e" - так что, это занимает 4 байта? Ну вроде ... и вроде нет. Совершенно очевидно, что вы можете удалить любую переменную из FiveBytes, чтобы уменьшить размер до 12 байт, но это не означает, что каждая переменных занимает 4 байта (подумайте об удалении всех из них! ). Стоимость одной переменной просто не является концепцией, которая имеет здесь большой смысл.

17 голосов
/ 16 октября 2008

В зависимости от потребностей опрашиваемого Marshal.SizeOf может дать или не дать вам то, что вы хотите. (Отредактировано после того, как Джон Скит опубликовал свой ответ).

using System;
using System.Runtime.InteropServices;

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

Обратите внимание, что, как говорит jkersch, sizeof может использоваться, но, к сожалению, только с типами значений. Если вам нужен размер класса, лучше всего использовать Marshal.SizeOf.

Джон Скит объяснил, почему ни sizeof, ни Marshal.SizeOf не идеальны. Я полагаю, что респондент должен решить, является ли вопрос приемлемым для его проблемы.

8 голосов
/ 25 октября 2012

По рецепту Джона Скитса в его ответе я попытался создать класс помощника, к которому он обращался. Предложения по улучшению приветствуются.

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

Использование:

Должен быть создан с помощью Func, который генерирует новые экземпляры T. Убедитесь, что один и тот же экземпляр не возвращается каждый раз. Например. Это было бы хорошо:

    public long SizeOfSomeObject()
    {
        var measure = new MeasureSize<SomeObject>(() => new SomeObject());
        return measure.GetByteSize();
    }
5 голосов
/ 14 ноября 2012

Мне пришлось довести это до уровня IL, но я наконец-то включил эту функциональность в C # с очень маленькой библиотекой.

Вы можете получить его (по лицензии BSD) по адресу bitbucket

Пример кода:

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

По сути, я написал короткую оболочку для метода класса вокруг инструкции sizeof IL. Эта инструкция получит необработанный объем памяти, который будет использовать ссылка на объект. Например, если у вас есть массив T, то инструкция sizeof сообщит вам, сколько байтов разделено на каждый элемент массива.

Это очень отличается от оператора C # sizeof. Во-первых, C # допускает только типы с чистыми значениями, потому что на самом деле невозможно получить размер чего-либо еще в статическом виде. Напротив, инструкция sizeof работает на уровне времени выполнения. Таким образом, сколько бы памяти не использовалась ссылка на тип во время этого конкретного экземпляра.

Вы можете увидеть больше информации и более детальный пример кода в моем блоге

4 голосов
/ 25 октября 2012

Это может быть сделано косвенно, без учета выравнивания. Число байтов, на которые ссылается экземпляр типа, равно размеру служебных полей + размеру полей типа. Служебные поля (в 32x занимает 4 байта каждое, 64x 8 байтов):

  1. Sysblockindex
  2. Указатель на таблицу методов
  3. + Необязательный (только для массивов) размер массива

Итак, для класса без каких-либо полей его экземпляр занимает 8 байт на 32-разрядной машине. Если это класс с одним полем, ссылка на тот же экземпляр класса, поэтому этот класс принимает (64x):

Sysblockindex + pMthdTable + ссылка на класс = 8 + 8 + 8 = 24 байта

Если это тип значения, у него нет полей экземпляра, поэтому он принимает только размер своего файла. Например, если у нас есть структура с одним полем типа int, то на 32-разрядной машине она занимает всего 4 байта памяти.

1 голос
/ 16 октября 2008

если у вас есть тип, используйте оператор sizeof. он вернет размер шрифта в байтах. например,

Console.WriteLine (SizeOf (INT));

выведет:

4

0 голосов
/ 29 марта 2019

Самый простой способ: int size = *((int*)type.TypeHandle.Value + 1)

Я знаю, что это детали реализации, но GC полагается на нее, и она должна быть как можно ближе к началу методической таблицы для эффективности, а также с учетом того, насколько сложен код GC, никто не посмеет изменить его в будущем. На самом деле это работает для всех минорных / основных версий .net framework + .net core. (В настоящее время не может проверить на 1.0)
Если вы хотите более надежный способ, создайте структуру в динамической сборке с [StructLayout(LayoutKind.Auto)] с точно такими же полями в том же порядке, возьмите ее размер с помощью инструкции sizeof IL. Вы можете использовать статический метод внутри структуры, который просто возвращает это значение. Затем добавьте 2 * IntPtr.Size для заголовка объекта. Это должно дать вам точное значение.
Но если ваш класс является производным от другого класса, вам нужно отдельно найти каждый размер базового класса и снова добавить их + 2 * Inptr.Size для заголовка. Вы можете сделать это, получив поля с флагом BindingFlags.DeclaredOnly.

0 голосов
/ 15 ноября 2015

Вы можете использовать перегрузку метода в качестве трюка для определения размера поля:

public static int FieldSize(int Field) { return sizeof(int); }
public static int FieldSize(bool Field) { return sizeof(bool); }
public static int FieldSize(SomeStructType Field) { return sizeof(SomeStructType); }
...