具有泛型类型字段的结构的大小
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]' 不能编组为 非托管结构;无法计算出有意义的大小或偏移量。
【问题讨论】:
有可能吗?当然取决于TKey
和TValue
的类型,大小会改变。
如果 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
。
【讨论】:
以上是关于具有泛型类型字段的结构的大小的主要内容,如果未能解决你的问题,请参考以下文章