C#从List或Dictionary获取结构体ref返回的可行替代方案有哪些?
解决方案总览
1. 无需包装类的原生方案(推荐)
针对List<T>
使用System.Runtime.InteropServices.CollectionsMarshal.AsSpan()方法将List转换为Span<T>,即可通过ref访问内部元素,全程无拷贝:
List<MyStruct> structList = new List<MyStruct>(); structList.Add(new MyStruct { foo = 42 }); // 直接获取索引0元素的引用 ref MyStruct itemRef = ref CollectionsMarshal.AsSpan(structList)[0]; itemRef.foo = 43; // 直接修改List内的原始结构体,无拷贝
针对Dictionary<TKey, TValue>
使用CollectionsMarshal.GetValueRefOrNullRef或GetValueRefOrAddDefault方法直接获取值的引用:
Dictionary<DateTime, MyStruct> timeStructMap = new Dictionary<DateTime, MyStruct>(); timeStructMap.Add(DateTime.Today, new MyStruct { foo = 42 }); // 获取对应键的值引用,键不存在时返回空引用 ref MyStruct msRef = ref CollectionsMarshal.GetValueRefOrNullRef(timeStructMap, DateTime.Today); if (!System.Runtime.CompilerServices.Unsafe.IsNullRef(ref msRef)) { msRef.foo = 43; // 直接修改字典内的原始结构体,无拷贝 }
2. 通用泛型包装类方案
如果你确实需要跨作用域持有结构体引用、或者需要多集合共享同一个结构体实例,可以写一个通用的泛型包装类,无需为每个结构体单独定义:
public class ValueWrapper<T> where T : struct { public T Value; public ValueWrapper(T value) => Value = value; }
使用方式和你现有的自定义包装类一致:
Dictionary<DateTime, ValueWrapper<MyStruct>> timeWrapMap = new(); timeWrapMap.Add(DateTime.Today, new ValueWrapper<MyStruct>(new MyStruct { foo = 42 })); var wrap = timeWrapMap[DateTime.Today]; wrap.Value.foo = 43; // 修改生效
注意:.NET 官方并没有提供内置的通用值类型包装类,上述通用实现足以覆盖绝大多数包装场景。
注意事项
- 使用
CollectionsMarshal系列API拿到结构体引用后,不要对原集合执行添加/删除/扩容操作,否则会导致引用失效,产生未定义行为 - 如果你的结构体大小小于16字节,直接拷贝替换的开销几乎可以忽略,优先用更安全的全值替换写法:
timeStructMap[DateTime.Today] = new MyStruct { foo = 43 }; - 可变结构体本身确实存在易误用的问题,如果没有强制性能要求,优先考虑将结构体设计为不可变,从根源避免意外拷贝修改失效的问题
内容的提问来源于stack exchange,提问作者Michael Covelli
相关产品推荐
相关产品推荐

