English 中文(简体)
使用C#获取字段大小(以字节为单位)
原标题:
  • 时间:2008-10-16 06:31:12
  •  标签:

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

我怎样能方便地找出该字段所占的字节数?

我需要像这样的东西:

Int32 a;
// int a_size = a.GetSizeInBytes;
// a_size should be 4
问题回答

基本上你无法做到。这取决于填充,它可能基于您正在使用的CLR版本和处理器等。假设对象没有引用到其他对象,计算对象的总大小要更容易一些:创建一个大数组,使用 GC.GetTotalMemory 作为基准点,将数组填充为对您类型的新实例的引用,然后再次调用GetTotalMemory。从其中一个值减去另一个值,然后除以实例的数量。您可能应该预先创建一个单一实例,以确保没有新的JIT代码对该数字产生贡献。是的,它像听起来的那样笨拙 - 但我以前使用过它并且效果不错。

就在昨天,我在想写一个小辅助类会是个好主意。如果你感兴趣的话,我可以告诉你。

编辑:还有另外两个建议,我想都处理一下。

首先,sizeof 运算符:它只显示类型在抽象中占用的空间大小,没有应用到它周围的填充。 (它包括结构内部的填充,但不包括应用于另一种类型内的该类型变量的填充。)

接下来是 Marshal.SizeOf 函数:它只显示序列化后的非托管大小,而不是内存中的实际大小。正如文档明确说明的那样:

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.

再次强调,垫子可以起到作用。

只是为了澄清我的意思,关于填充的相关性,考虑这两个类:

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 个字节(考虑删除它们中的所有变量!)。单个变量的成本在这里并没有什么意义。

根据提问者的需求,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都不是完美的。我猜问问题的人需要决定其中哪一个对他的问题是可以接受的。

从Jon Skeet的答案中的配方中,我尝试制作他所提到的助手类。欢迎提出改进意见。

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

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

因此,在没有任何属性的类中,他的实例在32位机器上占用8个字节。如果这是一个具有一个属性的类,引用相同的类实例,那么这个类将占用(64x):

Sysblockindex + pMthdTable + class的引用 = 8 + 8 + 8 = 24字节

如果它是值类型,它就没有任何实例字段,因此它只占用它的字段大小。例如,如果我们有一个只有一个int字段的结构,在32位机器上它只占用4个字节的内存。

我必须将其精简到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;
}

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

这与C#的sizeof运算符完全不同。首先,C#仅允许纯值类型,因为以静态方式获取其他任何内容的大小实际上不太可能。相比之下,sizeof指令在运行时级别上起作用。因此,引用类型所使用的内存量取决于特定实例期间的返回值。

你可以在我的博客上看到更多信息和稍微深入一些的示例代码。

if you have the type, use the sizeof operator. it will return the type`s size in byte. e.g.

控制台.写行(sizeof(int));

将产生输出:

你可以使用方法重载作为一个判断字段大小的技巧:

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

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

I know this is implementation detail but GC relies on it and it needs to be as close to start of the methodtable for efficiency plus taking into consideration how GC code complex is nobody will dare to change it in future. In fact it works for every minor/major versions of .net framework+.net core. (Currently unable to test for 1.0)
If you want more reliable way, emit a struct in a dynamic assembly with [StructLayout(LayoutKind.Auto)] with exact same fields in same order, take its size with sizeof IL instruction. You may want to emit a static method within struct which simply returns this value. Then add 2*IntPtr.Size for object header. This should give you exact value.
But if your class derives from another class, you need to find each size of base class seperatly and add them + 2*Inptr.Size again for header. You can do this by getting fields with BindingFlags.DeclaredOnly flag.

System.Runtime.CompilerServices.Unsafe

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

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

“文档”声明:

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

它似乎与Earlz的解决方案一样使用sizeof IL指令。 (来源)

unmanaged 约束是 C# 7.3 中的新特性。





相关问题
热门标签