Marshal.SizeOf<>
方法 重载的行为不一致。为了简洁起见,我在这里仅考虑通用重载。
已在 .NET 8 运行时尝试过此操作。
问题1:
Marshal.SizeOf<T>()
允许太少
考虑这段代码:
using System.Collections.Generic;
using System.Runtime.InteropServices;
...
int size1a = Marshal.SizeOf<KeyValuePair<int, int>>(KeyValuePair.Create(123, 456)); // good, gives 8
int size1b = Marshal.SizeOf<KeyValuePair<int, int>>(default); // good, gives 8
int size2 = Marshal.SizeOf<KeyValuePair<int, int>>(); // throws?!?
解释:
KeyValuePair<TKey, TValue>
是一个struct
,它分别包含两个类型为TKey
和TValue
的非静态字段。当我在 int
和 struct
两个位置上使用 TKey
(TValue
,没有托管字段)构造它时,我有一个类型 KeyValuePair<int, int>
,它不包含引用类型成员,因此是“非托管”。
预期:
size2
可以被确定(如8
),就像size1a
和size1b
一样
实际:用
ArgumentException
爆炸,说 T
不能是泛型类型
问题:当使用arg的重载没有问题时发现大小没有问题,这不是完全不一致吗
structure
?
请注意,封闭类型
KeyValuePair<int, int>
将很高兴满足自 C# 7.3(从 2018 年起)以来存在的通用约束 where TSomething : unmanaged
。这也使得我们很自然地期望它的大小是可以确定的。
问题2:
Marshal.SizeOf<T>(T structure)
允许太多!
让我们在
KeyValuePair<,>
中放入托管类型:
using System.Collections.Generic;
using System.Runtime.InteropServices;
...
int size3 = Marshal.SizeOf<KeyValuePair<string, string>>(KeyValuePair.Create("arbitrarily long text", "can go in here"));
预期:调用应导致异常,因为类型/实例保存对托管对象的引用(在此示例中为类型
System.String
(类))。
实际:调用成功,返回值取决于构建平台。例如,当为
x86构建时,
size3
变为 8
(== 4+4
),为 x64构建时,
16
(== 8+8
),依此类推。
问题:为什么?我看到运行时引用的宽度是两倍。
这在 .NET Framework 4.8 上可以正常工作。在这里,尝试找到
size3
(必须显式使用 new KeyValuePair<string, string>(...)
,因为静态 Create
方法不存在)会导致:
ArgumentException:类型“System.Collections.Generic.KeyValuePair`2[System.String,System.String]”无法封送为非托管结构;无法计算出有意义的大小或偏移量。
因此,问题 2 在 .NET Core 上一定是新的。正如我所说,我正在使用 .NET 8。
我应该将此报告为 .NET 中的错误,还是我忽略了某些内容?
替代方案:
对于问题 1:处理非托管类型时,可以在调用 Marshal.SizeOf 时显式指定类型。例如,您可以执行以下操作,而不是 Marshal.SizeOf
int size2 = Marshal.SizeOf(typeof(KeyValuePair<int, int>));
通过这种方式,您可以显式提供类型信息,并且它应该可以正常工作而不会引发异常。
对于问题 2:当需要计算大小时,可以通过不将 KeyValuePair 与引用类型一起使用来避免该问题。相反,您可以创建一个自定义结构来表示您要使用的数据,确保它仅包含值类型(而不是引用类型)。例如:
public struct MyKeyValuePair
{
public string Key;
public string Value;
}
然后,您可以计算此自定义结构体的大小,而不会遇到与引用类型相关的问题:
int size3 = Marshal.SizeOf(typeof(MyKeyValuePair));
By using a custom struct that only contains value types, you avoid the problem of Marshal.SizeOf incorrectly calculating the size based on reference types.
请记住,这些解决方法可能需要重构您的代码,尤其是在问题 2 的情况下,您需要将 KeyValuePair 的用法替换为自定义结构。这些解决方法针对您所描述的特定场景解决了 .NET Core/.NET 5+ 中 Marshal.SizeOf 方法的限制。