63

我有一个类,我想检查它的字段并最终报告每个字段占用多少字节。我假设所有字段都是 Int32、字节等类型。

如何轻松找出该字段占用多少字节?

我需要类似的东西:

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

9 回答 9

104

你不能,基本上。它将取决于填充,这很可能基于您正在使用的 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 个字节(考虑删除所有变量!)。单个变量的成本在这里并不是一个很有意义的概念。

于 2008-10-16T06:39:29.593 回答
17

根据受访者的需要,Marshal.SizeOf 可能会或可能不会给您想要的东西。(在 Jon Skeet 发布他的答案后编辑)。

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 是要走的路。

Jon Skeet 阐述了为什么 sizeof 和 Marshal.SizeOf 都不是完美的。我猜被问者需要决定他的问题是否可以接受。

于 2008-10-16T06:37:08.273 回答
10

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

用法:

应该使用生成 T 的新实例的 Func 创建。确保不会每次都返回相同的实例。例如,这会很好:

    public long SizeOfSomeObject()
    {
        var measure = new MeasureSize<SomeObject>(() => new SomeObject());
        return measure.GetByteSize();
    }
于 2012-10-25T13:09:23.663 回答
5

我不得不将其一直归结为 IL 级别,但我最终通过一个非常小的库将这个功能引入了 C#。

您可以在bitbucket获得它(BSD 许可)

示例代码:

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

基本上,我所做的是围绕sizeofIL 指令编写一个快速的类方法包装器。该指令将获取对对象的引用将使用的原始内存量。例如,如果您有一个 数组T,那么该sizeof指令会告诉您每个数组元素相隔多少字节。

这与 C# 的运算符极为不同sizeof。一方面,C# 只允许纯值类型,因为实际上不可能以静态方式获取其他任何东西的大小。相反,该sizeof指令在运行时级别工作。因此,无论在此特定实例期间对类型的引用将使用多少内存,都将返回。

您可以在我的博客上看到更多信息和更深入的示例代码

于 2012-11-14T03:33:19.237 回答
4

它可以间接完成,无需考虑对齐。引用类型实例的字节数等于服务字段大小 + 类型字段大小。服务字段(32x 每个占用 4 个字节,64x 8 个字节):

  1. 系统块索引
  2. 指向方法表的指针
  3. +可选(仅适用于数组)数组大小

因此,对于没有任何文件的类,他的实例在 32x 机器上占用 8 个字节。如果是具有一个字段的类,则在同一个类实例上引用,所以,这个类需要(64x):

Sysblockindex + pMthdTable + 类参考 = 8 + 8 + 8 = 24 字节

如果是值类型,它没有任何实例字段,因此 in 只取他的字段大小。例如,如果我们有一个带有一个 int 字段的结构,那么在 32x 机器上它只需要 4 个字节的内存。

于 2012-10-25T07:39:19.853 回答
1

如果您有类型,请使用 sizeof 运算符。它将以字节为单位返回类型的大小。例如

Console.WriteLine(sizeof(int));

将输出:

4

于 2008-10-16T06:36:31.393 回答
0

System.Runtime.CompilerServices.Unsafe

利用System.Runtime.CompilerServices.Unsafe.SizeOf<T>() where T: unmanaged

(当不在 .NET Core 中运行时,您需要安装该 NuGet 包)

文档指出:

返回给定类型参数的对象的大小。

它似乎sizeofEarlz 解决方案一样使用 IL 指令。(来源

unmanaged约束在 C# 7.3 中是新的

于 2019-11-07T17:02:08.920 回答
0

您可以使用方法重载作为确定字段大小的技巧:

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); }
于 2015-11-14T23:00:37.913 回答
0

最简单的方法是: int size = *((int*)type.TypeHandle.Value + 1)

我知道这是实现细节,但 GC 依赖于它,它需要尽可能接近方法表的开始以提高效率,并考虑到 GC 代码的复杂程度,以后没人敢更改它。事实上,它适用于 .net framework+.net core 的每个次要/主要版本。(目前无法测试 1.0)
如果您想要更可靠的方法,请在动态程序集中发出一个结构,该结构[StructLayout(LayoutKind.Auto)]具有完全相同的字段以相同的顺序,使用sizeof IL 指令获取其大小。您可能希望在 struct 中发出一个静态方法,该方法仅返回此值。然后为对象头添加 2*IntPtr.Size。这应该给你确切的价值。
但是,如果您的类派生自另一个类,则需要分别找到基类的每个大小并再次添加它们 + 2*Inptr.Size 作为标题。您可以通过获取带有BindingFlags.DeclaredOnly标志的字段来做到这一点。

于 2019-03-28T21:09:33.367 回答