Получение размера поля в байтах с помощью С#

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

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

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

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

Ответ 1

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

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

EDIT: Есть еще два предложения, и я хотел бы обратиться к ним обоим.

Во-первых, оператор 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 байта (подумайте об удалении всех из них!). Стоимость одной переменной просто не является концепцией, которая здесь имеет большой смысл.

Ответ 2

В зависимости от потребностей опроса, 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 не идеальны. Я думаю, что вопросник должен решить, приемлемо ли для его проблемы.

Ответ 3

Из рецепта Jon Skeets в его ответе я попытался сделать вспомогательный класс, на который он ссылался. Предложения по улучшению приветствуются.

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

Ответ 4

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

Вы можете получить его (лицензированный 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 сообщит вам, сколько байтов разделить каждый элемент массива.

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

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

Ответ 5

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

  • Sysblockindex
  • Таблица указателей на методы
  • + Необязательный (только для массивов) размер массива

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

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

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

Ответ 6

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

еЫп (SizeOf (INT));

выведет:

4

Ответ 7

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

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

Ответ 8

int size = *((int*)type.TypeHandle.Value + 1) способ: int size = *((int*)type.TypeHandle.Value + 1)

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