如何无需ToArray()将只读集合转换为ReadOnlySpan<T>/ReadOnlyMemory<T>
如何不调用ToArray()将IReadOnlyList/ReadOnlyCollection转换为ReadOnlySpan/ReadOnlyMemory
在C#开发中,ReadOnlySpan<T>和ReadOnlyMemory<T>对数组T[]的支持很直接,但IReadOnlyList<T>、ReadOnlyCollection<T>这类只读集合也很常用。我们希望不用调用ToArray()(避免额外的内存分配和拷贝),直接完成这类集合到ReadOnlySpan<T>/ReadOnlyMemory<T>的转换,具体该怎么做?
你的使用场景示例
Model类
public class Model { private readonly byte[]? _data; // 数据仅需生成一次,缓存后供外部多次通过GetData()访问 ... private byte[] GenerateData() => ...; public IReadOnlyList<byte> GetData() => _data ??= GenerateData(); ... }
NetworkSender类(存在语法错误的调用)
public class NetworkSender { ... public async Task SendModelData(Model model) { ... using var client = new UdpClient(); await client.SendAsync(new ReadOnlyMemory(model.GetData())); // 这里出现语法错误.. ... } }
解决方案
针对你的场景的快速处理
你的Model类内部实际缓存的是byte[],只是对外暴露成IReadOnlyList<byte>接口。这种情况下可以直接做类型判断和转换,完全避免额外内存分配:
public async Task SendModelData(Model model) { ... using var client = new UdpClient(); var data = model.GetData(); // 直接转换为数组,因为Model内部实际存储的就是byte[] if (data is byte[] byteArray) { await client.SendAsync(new ReadOnlyMemory<byte>(byteArray)); } // 兜底逻辑:如果后续Model内部存储类型改变,兼容其他IReadOnlyList<byte>实现 else { await client.SendAsync(new ReadOnlyMemory<byte>(data.ToArray())); } ... }
如果不想在调用方做类型判断,也可以修改Model类,新增一个直接返回ReadOnlyMemory<byte>的方法,让调用更简洁:
public class Model { private readonly byte[]? _data; ... public IReadOnlyList<byte> GetData() => _data ??= GenerateData(); // 新增方法,直接返回ReadOnlyMemory<byte> public ReadOnlyMemory<byte> GetDataMemory() => (_data ??= GenerateData()).AsMemory(); }
调用方就可以直接使用:
await client.SendAsync(model.GetDataMemory());
通用场景的处理方式
如果你的IReadOnlyList<T>不是基于数组实现的(比如真正的ReadOnlyCollection<T>包装了非数组集合),想要无分配转换需要注意:
ReadOnlySpan<T>要求内存连续,只有当集合的底层存储是连续内存(比如数组)时,才能无分配转换。如果是链表这类非连续存储的集合,根本无法直接转成Span,必须拷贝到数组。- 对于基于数组的
ReadOnlyCollection<T>,可以在创建时保留对原数组的引用,后续直接用原数组转换:
// 创建ReadOnlyCollection时保留原数组引用 byte[] originalArray = GenerateData(); ReadOnlyCollection<byte> readOnlyCollection = new ReadOnlyCollection<byte>(originalArray); // 直接用原数组生成ReadOnlyMemory,无额外分配 ReadOnlyMemory<byte> memory = originalArray.AsMemory();
另外,.NET 5及以上提供了MemoryMarshal.CreateReadOnlySpan方法,可在确定集合底层是连续内存时使用,但需要用unsafe代码,且存在风险:
// 仅当你100%确定IReadOnlyList<T>的底层是连续内存时使用 if (data is IReadOnlyList<byte> list && list.Count > 0) { unsafe { fixed (byte* ptr = &list[0]) { ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(ptr, list.Count); // 使用span处理数据 } } }
⚠️ 注意:这种unsafe方式有风险,如果集合底层不是连续内存,或者在Span使用期间原集合被修改(即便包装成只读集合,原集合若可修改也会出问题),会导致未定义行为,谨慎使用。
内容的提问来源于stack exchange,提问作者huang
相关产品推荐
相关产品推荐

