具有泛型类型字段的结构的大小

Posted

技术标签:

【中文标题】具有泛型类型字段的结构的大小【英文标题】:Size of struct with generic type fields 【发布时间】:2013-05-13 09:51:46 【问题描述】:

我想估计包含泛型类型参数的结构数组的大小,在本例中为字典条目结构。为此,我需要结构的大小。

struct Entry

   int hash;
   int next;
   TKey key;
   TValue value;

我怎样才能得到这个结构的字节大小?

编辑

似乎使用Marshal.SizeOf 是有问题的。传递结构的 type 会引发异常,指出参数不能是泛型类型定义。

如果我改为调用采用实例的重载,例如Marshal.SizeOf(default(Entry)) 如果 both 泛型类型参数都是值类型,它将起作用。如果通用参数是例如<int, object> 则抛出此异常

Dictionary`2+Entry[System.Int32,System.Object]' 不能编组为 非托管结构;无法计算出有意义的大小或偏移量。

【问题讨论】:

有可能吗?当然取决于TKeyTValue 的类型,大小会改变。 如果 TKey 和 TValue 是泛型,你现在不能预先输入它们的类型,所以我认为无法计算大小。 “结构的大小”。如果您使用泛型,编译器基本上会创建尽可能多的不同结构/类,因为您使用不同的 TKey/TValue 组合。因此,没有 one 结构具有 one 大小,但(可能)有许多不同的结构,每个结构都有自己的大小。所以Entry<char, bool> 的大小将与Entry<string, decimal> 不同。 更好的是 Marshal.SizeOf(new Entry<string, decimal>()) 抛出 ArgumentException:“类型 'Entry`2[System.String,System.Decimal]' 无法作为非托管结构进行封送;无法计算有意义的大小或偏移量。 " 是的,我想通过类型已知参数获取结构体的大小,也就是在运行时。 【参考方案1】:

听起来 IL sizeof 指令可能是您所需要的。 sizeof 指令被 C# sizeof 运算符在幕后使用,但 IL 版本由于某种原因限制较少。

ECMA CLI specification(第 III 部分,第 4.25 节)对 sizeof 指令有以下描述:

返回类型的大小(以字节为单位)。 typeTok 可以是泛型 参数、引用类型或值类型。

对于引用类型,返回的大小是引用的大小 对应类型的值,而不是存储的数据的大小 引用值引用的对象。

[基本原理:值类型的定义可以在 生成 CIL 的时间和加载它的时间 执行。因此,当 CIL 时,类型的大小并不总是已知的 生成。 sizeof 指令允许 CIL 代码确定 运行时的大小,无需调用 Framework 类 图书馆。计算可以完全在运行时或在 CIL 到本机代码的编译时间。 sizeof 返回总大小 这将被这种类型的数组中的每个元素占用—— 包括实现选择添加的任何填充。具体来说, 数组元素相隔sizeof 个字节。 结束理由]

您应该能够通过一些简单的运行时代码生成来获取sizeof 指令:

Console.WriteLine("Entry is " + TypeHelper.SizeOf(typeof(Entry)) + " bytes.");

// ...

public static class TypeHelper

    public static int SizeOf<T>(T? obj) where T : struct
    
        if (obj == null) throw new ArgumentNullException("obj");
        return SizeOf(typeof(T?));
    

    public static int SizeOf<T>(T obj)
    
        if (obj == null) throw new ArgumentNullException("obj");
        return SizeOf(obj.GetType());
    

    public static int SizeOf(Type t)
    
        if (t == null) throw new ArgumentNullException("t");

        return _cache.GetOrAdd(t, t2 =>
            
                var dm = new DynamicMethod("$", typeof(int), Type.EmptyTypes);
                ILGenerator il = dm.GetILGenerator();
                il.Emit(OpCodes.Sizeof, t2);
                il.Emit(OpCodes.Ret);

                var func = (Func<int>)dm.CreateDelegate(typeof(Func<int>));
                return func();
            );
    

    private static readonly ConcurrentDictionary<Type, int>
        _cache = new ConcurrentDictionary<Type, int>();

【讨论】:

请注意,这无助于计算类实例所需的存储空间;它只会给出指向它的引用的大小(64 位执行时为 8 个字节)。【参考方案2】:

近似大小是hash(4 字节(32 位架构))+next(4 字节(32 位架构))+TKey(如果引用类型)的总和4 个字节用于指针(32 位架构),如果值类型是递归计算的该值类型的大小)+TValue(与TKey 相同)

只需使用Marshal.SizeOf 方法。

【讨论】:

指针的 4 个字节?现在是 2013 年,你还在假设 32 位架构? @Damien_The_Unbeliever:你不会相信,但我们仍在企业中使用 XP,所以......我也相信很多其他人。 @Tigran 你能举个例子吗?我似乎无法让 sizeof/Marshal.SizeOf 与泛型类型一起使用。 @AndersForsgren:你应该能够在泛型类型分配之后做到这一点,所以当 real 类型通过调用生成时: var runtimeType = typeof (结构实例);和 SizeOf 在该类型上。 是的,结果在结构实例上是可能的,但在结构类型上却不是(即使我使用 CreateGenericType 并传入类型参数)。【参考方案3】:

您也可以使用Marshal.ReadIntPtr(type.TypeHandle.Value, 4)。它返回托管对象的基本实例大小。有关运行时内存布局的更多信息,请参阅http://msdn.microsoft.com/en-us/magazine/cc163791.aspx。

【讨论】:

【参考方案4】:

(在我写完这篇文章后,我注意到该方法在 rationale LukeH 引用中有所预料)

struct Pin : IDisposable

    public GCHandle pinHandle;
    public Pin(object o)  pinHandle = GCHandle.Alloc(o, GCHandleType.Pinned); 

    public void Dispose()
    
        pinHandle.Free();
    


static class ElementSize<T>

    private static int CalcSize(T[] testarray)
    
      using (Pin p = new Pin(testarray))
        return (int)(Marshal.UnsafeAddrOfPinnedArrayElement(testarray, 1).ToInt64()
                   - Marshal.UnsafeAddrOfPinnedArrayElement(testarray, 0).ToInt64());
    

    static public readonly int Bytes = CalcSize(new T[2]);

我相当确定固定和丢弃一个小数组比动态编译便宜。另外,泛型类中的静态字段是获得类型安全的每种类型数据的好方法......不需要ConcurrentDictionary

【讨论】:

以上是关于具有泛型类型字段的结构的大小的主要内容,如果未能解决你的问题,请参考以下文章

自定义泛型结构:泛型类泛型接口泛型方法

TS泛型类、泛型接口、泛型函数

泛型类的可选方法参数的类型

泛型类派生子类

升级到 Java 7 后,泛型类的类型参数化字段变得不可见

20165315 第四次考试课下补做