如此一来,我们解决了存储和释放的问题,使用时只需要简单的:
NativeBuffer<int> buf = new(new[] { 1, 2, 3, 4, 5 });// ...buf.Dispose();
或者让它在作用域结束时自动释放:
using NativeBuffer<int> buf = new(new[] { 1, 2, 3, 4, 5 });
或者干脆不管了,等待 GC 回收时自动调用我们的编写的析构函数,这个时候就会从 ~NativeBuffer
调用 Dispose
方法 。
紧接着,为了能够使用 foreach
进行迭代,我们还需要实现一个 Enumerator
,但是为了提升效率并且支持引用,此时我们选择实现自己的 GetEnumerator
。
首先我们实现一个 NativeBufferEnumerator
:
public ref struct NativeBufferEnumerator{private unsafe readonly ref T* pointer;private readonly nuint length;private ref T current;private nuint index;public ref T Current{get{unsafe{// 确保指向的内存仍然有效if (pointer == (T*)0){return ref Unsafe.NullRef<T>();}else return ref current;}}}public unsafe NativeBufferEnumerator(ref T* pointer, nuint length){this.pointer = ref pointer;this.length = length;this.index = 0;this.current = ref Unsafe.NullRef<T>();}public bool MoveNext(){unsafe{// 确保没有越界并且指向的内存仍然有效if (index >= length || pointer == (T*)0){return false;}if (Unsafe.IsNullRef(ref current)) current = ref *pointer;else current = ref Unsafe.Add(ref current, 1);}index++;return true;}}
然后只需要让 NativeBuffer.GetEnumerator
方法返回我们的实现好的迭代器即可:
public NativeBufferEnumerator GetEnumerator(){unsafe{return new(ref pointer, Length);}}
从此,我们便可以轻松零分配地迭代我们的 NativeBuffer
了:
int[] buffer = new[] { 1, 2, 3, 4, 5 };using NativeBuffer<int> nb = new(buffer);foreach (int i in nb) Console.WriteLine(i); // 1 2 3 4 5foreach (ref int i in nb) i++;foreach (int i in nb) Console.WriteLine(i); // 2 3 4 5 6
并且由于我们的迭代器中保存着对 NativeBuffer.pointer
的引用,如果 NativeBuffer
被释放了,运行了一半的迭代器也能及时发现并终止迭代:
int[] buffer = new[] { 1, 2, 3, 4, 5 };NativeBuffer<int> nb = new(buffer);foreach (int i in nb){Console.WriteLine(i); // 1nb.Dispose();}
结构化数据我们经常会需要存储结构化数据,例如在进行图片处理时,我们经常需要保存颜色信息 。这个颜色可能是直接从文件数据中读取得到的 。那么此时我们便可以封装一个 Color
来代表颜色数据 RGBA:
[StructLayout(LayoutKind.Sequential)]public struct Color : IEquatable<Color>{public byte R, G, B, A;public Color(byte r, byte g, byte b, byte a = 0){R = r;G = g;B = b;A = a;}public override int GetHashCode() => HashCode.Combine(R, G, B, A);public override string ToString() => $"Color {{ R = {R}, G = {G}, B = {B}, A = {A} }}";public override bool Equals(object? other) => other is Color color ? Equals(color) : false;public bool Equals(Color other) => (R, G, B, A) == (other.R, other.G, other.B, other.A);}
这么一来我们就有能表示颜色数据的类型了 。但是这么做还不够,我们需要能够和二进制数据或者字符串编写的颜色值相互转换,因此我们编写 Serialize
、Deserialize
和 Parse
方法来进行这样的事情:
[StructLayout(LayoutKind.Sequential)]public struct Color : IParsable<Color>, IEquatable<Color>{public static byte[] Serialize(Color color){unsafe{byte[] buffer = new byte[sizeof(Color)];MemoryMarshal.Write(buffer, ref color);return buffer;}}public static Color Deserialize(ReadOnlySpan<byte> data){return MemoryMarshal.Read<Color>(data);}[DoesNotReturn] private static void ThrowInvalid() => throw new InvalidDataException("Invalid color string.");public static Color Parse(string s, IFormatProvider? provider){if (s.Length is not 7 and not 9 || (s.Length > 0 && s[0] != '#')){ThrowInvalid();}return new(){R = byte.Parse(s[1..3], NumberStyles.HexNumber, provider),G = byte.Parse(s[3..5], NumberStyles.HexNumber, provider),B = byte.Parse(s[5..7], NumberStyles.HexNumber, provider),A = s.Length is 9 ? byte.Parse(s[7..9], NumberStyles.HexNumber, provider) : default};}public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Color result){result = default;if (s?.Length is not 7 and not 9 || (s.Length > 0 && s[0] != '#')){return false;}Color color = new Color();return byte.TryParse(s[1..3], NumberStyles.HexNumber, provider, out color.R)&& byte.TryParse(s[3..5], NumberStyles.HexNumber, provider, out color.G)&& byte.TryParse(s[5..7], NumberStyles.HexNumber, provider, out color.B)&& (s.Length is 9 ? byte.TryParse(s[7..9], NumberStyles.HexNumber, provider, out color.B) : true);}}
经验总结扩展阅读
- 二、.Net Core搭建Ocelot
- 一千零一夜柏海的结局是什么?
- 创建.NET程序Dump的几种姿势
- 中国6大奇葩零食排行榜
- 干无花果怎么吃
- 零食可以托运吗
- C# 8.0 添加和增强的功能【基础篇】
- .NET性能系列文章二:Newtonsoft.Json vs. System.Text.Json
- 某 .NET RabbitMQ SDK 有采集行为,你怎么看?
- .net core Blazor+自定义日志提供器实现实时日志查看器