C#中结构数组如何分配?如何实现结构内数组的内嵌存储?
你的假设完全正确。
C#中所有Array类型都属于引用类型,即便数组元素是struct值类型,数组实例本身也会存储在托管堆中,结构内仅保存指向该数组实例的引用。你直接初始化FrameTraffic[]结构数组时,数组内的每个FrameTraffic实例会执行默认的按位零初始化,不会调用你定义的带默认参数的自定义构造函数,因此Channels字段默认值就是null,和你观察到的现象完全一致。
如果要实现结构内嵌数组的效果,有以下几个可行方案:
方案1:使用unsafe固定大小缓冲区(兼容所有.NET版本)
这是最接近C/C++中结构内嵌固定长度数组的实现,数组空间直接分配在结构的内存布局中,不会产生额外的堆分配。仅要求你的自定义Channel结构是unmanaged类型(所有字段都是值类型,无引用类型字段),且数组长度为编译期常量。
public struct Channel { public int ChannelId; // 其余字段均为值类型即可 } public unsafe struct FrameTraffic { public int FrameId; public int MaxChannels; // 直接定义长度为16的固定大小Channel数组,长度需为常量 public fixed Channel Channels[16]; public FrameTraffic(int dummyCS0568 = 0) { this.FrameId = 0; MaxChannels = 16; // 需和上面定义的固定长度保持一致 // 无需额外分配数组,空间已直接内嵌在结构中 } }
使用时需要在unsafe上下文中访问数组元素,比如将访问逻辑包裹在unsafe { }块中,或者给对应方法添加unsafe修饰符。
方案2:使用.NET 5+ 提供的InlineArray特性(无需unsafe)
如果你使用的是.NET 5及以上版本,可以使用官方提供的InlineArray特性实现类型安全的内嵌数组,不需要编写unsafe代码,数组长度同样要求为编译期常量。
// 先定义指定长度的内嵌数组类型 [System.Runtime.CompilerServices.InlineArray(16)] public struct ChannelArray { private Channel _element0; } public struct Channel { public int ChannelId; } public struct FrameTraffic { public int FrameId; public int MaxChannels; // 直接使用定义好的InlineArray类型作为字段 public ChannelArray Channels; public FrameTraffic(int dummyCS0568 = 0) { this.FrameId = 0; MaxChannels = 16; // 无需额外分配数组,空间已直接内嵌在FrameTraffic结构中 } }
使用时可以直接像普通数组一样通过索引访问元素:frame.Channels[0].ChannelId = 1,完全类型安全。
方案3:无法固定数组长度时手动初始化结构数组
如果你业务上要求Channels长度是动态的,无法在编译期确定,只能使用普通数组引用的话,可以在初始化FrameTraffic数组后遍历所有元素,手动调用自定义构造函数解决Channels为null的问题:
// 初始化结构数组 int frameCount = 10; FrameTraffic[] trafficArray = new FrameTraffic[frameCount]; // 遍历每个元素调用构造函数初始化Channels for(int i = 0; i < frameCount; i++) { trafficArray[i] = new FrameTraffic(); }
这种方案数组仍存储在托管堆中,没有实现内嵌效果,仅解决Channels为null的问题。
内容的提问来源于stack exchange,提问作者Greggy

