【问题标题】:Getting the size of a field in bytes with C#使用 C# 获取字段的大小(以字节为单位)
【发布时间】:2010-09-17 11:32:07
【问题描述】:

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

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

我需要类似的东西:

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

【问题讨论】:

    标签: c# reflection byte


    【解决方案1】:

    System.Runtime.CompilerServices.Unsafe

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

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

    Documentation 状态:

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

    它似乎使用sizeof IL 指令,就像Earlz solution 一样。 (source)

    unmanaged 约束是 C# 7.3 中的新内容

    【讨论】:

      【解决方案2】:

      最简单的方法是: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 标志的字段来做到这一点。

      【讨论】:

        【解决方案3】:

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

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

        【讨论】:

          【解决方案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 指令在运行时级别工作。因此,无论在此特定实例期间对类型的引用将使用多少内存,都将返回。

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

          【讨论】:

          • 这实际上并不能告诉您一个字段将占用多少空间。那是上下文的。例如,byte 的结果是什么?就填充而言,这将取决于它是否可以适应其他字段。
          • @JonSkeet 当然,但它也适用于结构。基本上没有可能的方法来获得“byte 在每个单独的上下文中会是什么”它在不同的上下文中是不同的。但是,我敢肯定这会有所帮助。 (如果所有其他方法都失败了,您可以一次将字段添加到结构中一个字段,以此来衡量每个字段的大小)
          • 你不能一次添加一个结构,因为这样你就会遇到填充和“最小大小”问题 - 例如,一个具有单个 int 字段的类占用的空间不超过一个完全没有字段的类。然后添加另一个 int 字段将使其多占用 4(或 8)个字节。这并不意味着第二个字段有什么特别之处 - 删除第一个字段与删除第二个字段一样。从根本上说,我不相信字段大小对 OP 来说 真的 有用。我知道这是要求的,但我认为它很少真正有用。
          • @Qwertie 不幸的是,否则不可能 :( 反射 API 不会公开结构的 sizeof 操作码。如果你愿意,你可以使用类似 ILMerge 的东西作为构建后的步骤不必分发单独的程序集
          • @Qwertie 使用DynamicMethod 实现简单的IL 很简单;您不需要一个完整的单独的库/dll。例如,检查this answer,它也恰好为ValueType 提供了另一种方法——或者更有趣的是,“格式化类”——*in-situ* 大小问题。在那个答案中,我在IL 中给出了一个通用的托管指针 减法函数。它返回一个字节偏移量,因此支持与不相交的类型一起使用,因此它还可以测量给定已知为pack=1 相邻的任何结构的大小。
          【解决方案5】:

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

          【讨论】:

          • 我会将“生成器”更改为更标准的术语“工厂”。
          • 该方法不考虑_memArray 的初始内存分配。即,测量oldSize before _memArray = new T[NumberOfInstances]; 是有意义的
          • @Alexey 但是你不想测量 _memArray !它只是一个辅助数组,用于包含您要测量的内容,即 T 的大小。这就是这个类的重点。
          • 是的,@Jesper,理论上你绝对是对的,这意味着“纯”类实例大小。但是,如果谈论现实世界的示例(例如“可以将多少个实例加载到可用内存中”,我遇到的问题)必须考虑对这个实例的引用。因此,尽管理论上不正确,但我的估计更有用。显然,我必须在之前的评论中包含这一点。很抱歉给我带来了误解。
          • @Alexey 好的,我明白你的意思。但我猜与数组的内容相比,数组的大小会很小。不会吗?除了最初的问题更多的是关于测量单个物体的大小。但当然可以根据需要随意更改代码:)
          【解决方案6】:

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

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

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

          Sysblockindex + pMthdTable + 1234562 上的引用 = 8 + 8 + 8 = 24 字节

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

          【讨论】:

            【解决方案7】:

            根据受访者的需求,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 都不是完美的。我猜被问者需要决定他的问题是否可以接受。

            【讨论】:

            • 问题是“我想检查它的字段,并最终报告每个字段占用多少字节。我假设所有字段的类型都是 Int32、字节等。”所以我认为 Jon Skeet 并没有考虑课堂开销。
            • 这不是类的开销——而是在编组之后,一个对象看起来可能与它在内存中的样子非常不同。就我而言,编组是黑魔法......但编组的大小很可能与内存中的大小不同。我将编辑我的答案。
            • 我想只有原始发布者才能确定是否可以接受。修改了我的答案并加入了维基百科。
            【解决方案8】:

            你不能,基本上。它将取决于填充,这很可能基于您正在使用的 CLR 版本和处理器等。假设对象没有对其他对象的引用,更容易计算出对象的总大小:创建一个大数组,使用GC.GetTotalMemory 作为基点,用对您的类型的新实例的引用填充数组,然后再次调用GetTotalMemory。将一个值与另一个值分开,然后除以实例数。您可能应该事先创建一个实例,以确保没有新的 JITted 代码对数字有贡献。是的,它就像听起来一样 hacky - 但我以前用过它,效果很好。

            就在昨天,我还在想为此编写一个小助手类是个好主意。如果您有兴趣,请告诉我。

            编辑:还有另外两个建议,我想同时解决它们。

            首先,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 个字节(考虑删除所有变量!)。单个变量的成本在这里并不是一个很有意义的概念。

            【讨论】:

            • @jon - 我会对你的助手类感兴趣...可用吗?
            • @gap:不,恐怕我从来没有写过助手类。
            • 对不起,我用不同的词重复了这个问题。未来参考:stackoverflow.com/questions/11126375/….
            • 这个答案有什么新进展吗?肯定有比依赖垃圾收集器更漂亮的方法。另外,ldelema 呢?如果您下降到 IL 级别,ldelema 在确定给定对象的大小时是否可靠? (如果您在 T 的数组中获取两个并排元素的地址,那么就会得到差异)......或者就此而言,sizeof 指令应该完全按照这个问题的要求进行
            • @Earlz:我解释了为什么sizeof 在答案中并不理想。请注意,我基于 GC 的答案实际上是确定 object 的大小而不是 field 的大小。
            【解决方案9】:

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

            Console.WriteLine(sizeof(int));

            将输出:

            4

            【讨论】:

            • MSDN:虽然可以使用SizeOf方法,但是这个方法返回的值并不总是和sizeof返回的值一样。 Marshal.SizeOf 返回类型被编组后的大小,而 sizeof 返回由 CLR 分配的大小,包括任何填充。
            • 链接到 MSDN:ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_csref/html/c548592c-677c-4f40-a4ce-e613f7529141.htm
            猜你喜欢
            • 2016-04-08
            • 1970-01-01
            • 2013-02-01
            • 2013-02-06
            • 2011-09-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多