You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中递归计算复杂对象的大小(不使用GC)

如何在不触发GC的前提下计算复杂对象的内存占用

你需要计算包含值类型、字符串、List、Dictionary等成员的复杂对象总内存大小,且全程不能触发GC。先明确你的对象结构(已整理为规范C#代码):

class Student
{
    public string Name;
    public int Age;
}

class Section
{
    public int SectionNumber;
    public double SectionAverage;
    public string SectionName;
    public List<Student> Students;
    public List<string> Subjects;
    public Dictionary<string, dynamic> StudentMapping;
}

下面给出具体实现思路和代码:

核心思路

要在无GC的前提下计算内存,需递归遍历所有成员的实际占用,同时避免任何托管内存分配:

  1. 基础值类型直接用sizeof()计算,无GC触发
  2. 引用类型需计算对象本身大小+其指向的实例内存
  3. 内置集合(List、Dictionary)需读取私有内部字段来计算,避免调用会分配内存的公共方法
  4. 提前缓存反射字段信息,避免重复反射分配对象

关键实现细节

1. 固定大小的基础单元

  • 64位系统下,对象头占24字节,数组头占24字节,指针占8字节(32位系统需对应改为12、12、4字节)
  • 字符串内存=对象头+字符数组大小(每个char占2字节)
  • 值类型直接用Marshal.SizeOf()获取大小

2. 自定义类型与集合的递归计算

  • 自定义类:计算对象头+所有字段大小(含对齐),再递归计算每个引用类型字段的实例内存
  • List:读取私有字段_items(内部数组),计算List自身大小+数组及元素的总内存
  • Dictionary<K,V>:读取私有字段_entries和_buckets数组,计算Dictionary自身大小+两个数组及内部元素的总内存

完整实现代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;

public static class MemorySizeCalculator
{
    private static readonly Dictionary<Type, FieldInfo[]> _fieldCache = new();
    // 64位系统参数,32位需修改对应值
    private const int ObjectHeaderSize = 24;
    private const int ArrayHeaderSize = 24;
    private const int PointerSize = 8;

    static MemorySizeCalculator()
    {
        // 提前缓存需要计算的类型字段
        CacheTypeFields(typeof(Student));
        CacheTypeFields(typeof(Section));
        CacheTypeFields(typeof(List<>));
        CacheTypeFields(typeof(Dictionary<,>));
    }

    private static void CacheTypeFields(Type type)
    {
        if (!_fieldCache.ContainsKey(type))
        {
            _fieldCache[type] = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        }
    }

    public static long CalculateSize(object obj)
    {
        if (obj == null) return 0;
        Type type = obj.GetType();

        // 处理值类型
        if (type.IsValueType)
        {
            return Marshal.SizeOf(obj);
        }

        // 处理字符串
        if (type == typeof(string))
        {
            var str = (string)obj;
            return ObjectHeaderSize + str.Length * 2;
        }

        // 处理数组
        if (type.IsArray)
        {
            var arr = (Array)obj;
            int elementCount = arr.Length;
            Type elementType = type.GetElementType();
            long elementSize = elementType.IsValueType ? Marshal.SizeOf(elementType) : PointerSize;
            long total = ArrayHeaderSize + elementCount * elementSize;

            // 递归计算引用类型元素的内存
            if (!elementType.IsValueType)
            {
                for (int i = 0; i < elementCount; i++)
                {
                    total += CalculateSize(arr.GetValue(i));
                }
            }
            return total;
        }

        // 处理List<T>
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
        {
            var itemsField = _fieldCache[type].First(f => f.Name == "_items");
            var items = (Array)itemsField.GetValue(obj);
            // List自身大小:对象头+3个指针/值类型字段(_items, _size, _version)
            long total = ObjectHeaderSize + PointerSize * 3;
            total += CalculateSize(items);
            return total;
        }

        // 处理Dictionary<K,V>
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
        {
            var entriesField = _fieldCache[type].First(f => f.Name == "_entries");
            var bucketsField = _fieldCache[type].First(f => f.Name == "_buckets");
            var entries = (Array)entriesField.GetValue(obj);
            var buckets = (Array)bucketsField.GetValue(obj);

            // Dictionary自身大小:对象头+8个指针/值类型字段(简化计算,可按实际字段数调整)
            long total = ObjectHeaderSize + PointerSize * 8;
            total += CalculateSize(entries);
            total += CalculateSize(buckets);
            return total;
        }

        // 处理自定义引用类型
        long totalSize = ObjectHeaderSize;
        long currentOffset = 0;

        foreach (var field in _fieldCache[type])
        {
            Type fieldType = field.FieldType;
            long fieldSize;

            if (fieldType.IsValueType)
            {
                fieldSize = Marshal.SizeOf(fieldType);
            }
            else
            {
                fieldSize = PointerSize;
                // 递归计算引用指向的对象内存
                totalSize += CalculateSize(field.GetValue(obj));
            }

            // 字段对齐(按8字节对齐)
            currentOffset = (currentOffset + 7) / 8 * 8;
            currentOffset += fieldSize;
        }

        // 对象总大小按8字节对齐
        totalSize = (totalSize + 7) / 8 * 8;
        return totalSize;
    }
}

注意事项

  • 代码默认适配64位.NET环境,32位系统需修改ObjectHeaderSize、ArrayHeaderSize、PointerSize的对应值
  • dynamic类型实际运行时为object,会按引用类型递归计算其实际实例大小
  • 字符串如果是 interned 状态,实际内存会被共享,但代码计算的是当前对象引用的实例大小,符合常规业务需求
  • 字段缓存仅需初始化一次,避免重复反射产生的内存分配

内容的提问来源于stack exchange,提问作者Curry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 09:53:07