平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“C# 中的 sizeof 与非托管类型约束全解析”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。
结合项目来看,它的作用是在编译时拿到非托管类型所占用的字节数。它就像是内存的“空间丈量尺”,告诉你这个类型在内存里到底占了多大的地儿。
在 C# 里,sizeof 是一个用来拿到非托管类型(Unmanaged Type)在内存中所占字节数(Byte)的运算符。它的本质是编译器指令,在编译阶段就能确定数值。
sizeof 的核心在于内存布局(Memory Layout)理解这一步时,。计算机存储数据时,不同的数据类型需占用不同大小的连续空间。sizeof 告诉程序:如果要为一个变量分配空间,到底需切出多大的一块内存。
sizeof 的用法很轻松,但受限于 C# 的安全机制:
sizeof(int)。unsafe(不安全)上下下文采用。class,因为引用类型的大小在堆栈上只是一个指针大小,其实际内容在堆中分配,且受垃圾回收(GC)影响,布局不固定。
对于内置类型,sizeof 得到的是可预见的结果:
Console.WriteLine(sizeof(byte)); // 输出: 1
Console.WriteLine(sizeof(int)); // 输出: 4
Console.WriteLine(sizeof(long)); // 输出: 8
Console.WriteLine(sizeof(double)); // 输出: 8
当处理自定义结构时,需考虑内存对齐(Memory Alignment)。
using System;
struct MyStruct
{
public byte A; // 1 byte
public int B; // 4 bytes
}
class Program
{
static void Main()
{
// 必须开启 unsafe 编译选项
unsafe
{
// 结果是 8,而不是 5。
// 因为编译器为了 CPU 读取效率,会对字段进行填充(Padding)。
Console.WriteLine($"结构体大小: {sizeof(MyStruct)}");
}
}
}
byte[] 数组时,借助 sizeof 确定偏移量。sizeof 观察结构体对齐情况,减少内存碎片的浪费。从实现思路看,除了基础的内存分配,在资深软件工程师眼中,sizeof 更多用来底层优化和精密控制。
从实现思路看,在追求极致性能的场景(如解析高频数据),我们避开堆内存,直接在栈上开辟空间。
// 申请 100 个 int 大小的栈空间
Span<int> numbers = stackalloc int[100];
// 在底层逻辑中,它等同于申请了 100 * sizeof(int) 字节
从实现思路看,在 C# 7.3 之后,能够采用 where T : unmanaged 约束。配合 sizeof,能够编写极其通用的高性能代码。
public unsafe void CopyData<T>(T* source, T* destination, int count) where T : unmanaged
{
// 自动根据 T 的类型计算需要复制的字节总数
long totalBytes = (long)count * sizeof(T);
Buffer.MemoryCopy(source, destination, totalBytes, totalBytes);
}
从实现思路看,你在处理复杂的二进制协议(如网络封包、视频解码)时,sizeof 是计算偏移量的唯一准绳。
| sizeof(sbyte) | 1 |
|---|---|
| sizeof(byte) | 1 |
| sizeof(short) | 2 |
| sizeof(ushort) | 2 |
| sizeof(int) | 4 |
| sizeof(uint) | 4 |
| sizeof(long) | 8 |
| sizeof(ulong) | 8 |
| sizeof(char) | 2 |
| sizeof(float) | 4 |
| sizeof(double) | 8 |
| sizeof(decimal) | 16 |
| sizeof(bool) | 1 |
在真实项目中,Marshal.SizeOf 最经典的舞台就是 Win32 API 调用。
结合项目来看,很多 Windows 底层的函数要求你传入一个结构体,同时且在这个结构体的第一个字段里,你必须明确告诉系统:“这个结构体本人占用了多少字节”。如果这个数字填错了,系统为了安全会直接拒绝执行。
Windows API 设计时为了向前兼容结合项目来看,,经常会在同一个结构体的后续版本中增加字段。系统借助检查你传入的 Size(大小)来判断你采用的是哪一个版本的结构体,从而决定如何处理内存。
实际处理时,假设我们要拿到 Windows 任务栏通知区域(托盘)的图标信息,会用到 NOTIFYICONDATA 结构体。
using System;
using System.Runtime.InteropServices;
// 定义符合 Win32 布局的结构体
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct NOTIFYICONDATA
{
public int cbSize; // 结构体本身的大小(关键!)
public IntPtr hWnd; // 窗口句柄
public uint uID; // 图标 ID
public uint uFlags; // 标志位
public uint uCallbackMessage;
public IntPtr hIcon; // 图标句柄
// ... 其他字段省略
}
class Program
{
static void Main()
{
NOTIFYICONDATA data = new NOTIFYICONDATA();
// 场景:初始化结构体时,必须告知系统该结构体的大小
// 这里不能用 sizeof(NOTIFYICONDATA),因为在没有 unsafe 的环境下无法编译
// 且 Marshal.SizeOf 会考虑 CharSet 带来的字符编码宽度差异
data.cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA));
Console.WriteLine($"结构体在托管内存中的大小推算为: {data.cbSize} 字节");
// 模拟调用 Win32 API
// Shell_NotifyIcon(NIM_ADD, ref data);
}
}
Marshal.SizeOf 能够在普通的托管代码中运行,不需开启项目的“允许不安全代码”开关。Marshal.SizeOf 会考虑 StructLayout 属性。比如,如果结构体里有 string,sizeof 根本无法处理,而 Marshal.SizeOf 会根据你指定的 CharSet(如 Unicode 占 2 字节,Ansi 占 1 字节)来计算它转化成 C 语言格式后的大小。
结合项目来看,它位于 System.Runtime.InteropServices 命名空间下。只要你安装了 .NET SDK,就能够直接借助 using 引用它。
struct 实例,也能处理一些被装箱的对象,但性能开销比 sizeof 大。| 特性 | sizeof | Marshal.SizeOf |
|---|---|---|
| 性能 | 极高(等同于直接写数字) | 一般(有运行时开销) |
| 上下文要求 | 涉及自定义结构需 unsafe | 无需 unsafe |
| 灵活性 | 只能用类型名,不能用变量名 | 能够直接传入变量实例 |
| 主要目的 | 算偏移量、算指针位移 | 调用 Win32 API、处理复杂封送 |
long(8 字节)。unsafe 关键字,你能够在特定区域内像 C 语言一样直接操作指针和查看原始内存布局。到此这篇关于C# 中的 sizeof 与非托管类型约束全解析的文章就介绍到这了,更多相关C# sizeof 与非托管类型约束内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多兼容脚本之家!