我想估计包含泛型类型参数的结构体数组的大小,在本例中是字典条目结构体。要做到这一点,我需要结构体的大小。
struct Entry
{
int hash;
int next;
TKey key;
TValue value;
}
我怎样才能得到这个结构体的字节大小?
编辑
似乎使用Marshal.SizeOf
是有问题的。传递结构体的类型将引发异常,指出参数不能是泛型类型定义。
如果我调用接受实例的重载,例如Marshal.SizeOf(default(Entry))
,如果两个泛型参数都是值类型,它将工作。如果泛型参数为<int, object>
,则抛出此异常
[System.Int32、系统字典' 2 +条目。Object]'不能被封送为无管理的结构;无法计算有意义的大小或偏移量。
听起来IL sizeof
指令可能是您需要的。sizeof
指令由c# sizeof
操作符在幕后使用,但由于某些原因,IL版本的限制较少。
ECMA CLI规范(分区III,章节4.25)有sizeof
指令的描述:
返回类型的大小,以字节为单位。
typeTok
可以是一个泛型参数、引用类型或值类型对于引用类型,返回的大小是引用的大小对应类型的值,而不是存储在中的数据的大小引用值所引用的对象。
[基本原理:值类型的定义可以在生成CIL的时间和加载它的时间执行。因此,类型的大小并不总是已知的是生成的。
sizeof
指令允许CIL代码确定在运行时不需要调用到Framework类的大小图书馆。计算可以完全在运行时进行,也可以在从cil到本机代码的编译时间。sizeof
返回总大小类型为-的数组中的每个元素所占用包括实现选择添加的任何填充。具体来说,数组元素间隔sizeof
字节。<<em>结束原理/em>]
你应该能够得到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>();
}
近似的大小是hash
(4字节(32位体系结构))+ next
(4字节(32位体系结构))+ TKey
(如果引用类型为4字节的指针(32位体系结构),如果值类型是递归计算的值类型的大小))+ TValue
(与TKey
相同)
或
只是使用Marshal。SizeOf方法。
您也可以使用Marshal.ReadIntPtr(type.TypeHandle.Value, 4)
。它返回托管对象的基本实例大小。有关运行时内存布局的更多信息,请参阅http://msdn.microsoft.com/en-us/magazine/cc163791.aspx。
(在我写完这篇文章之后,我注意到这种方法在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
.