Skip to content

Commit 2f5beb8

Browse files
authored
CSHARP-4807: Support serialization of Memory<T> and ReadOnlyMemory<T> (#1330)
1 parent a5aadd5 commit 2f5beb8

File tree

6 files changed

+1035
-2
lines changed

6 files changed

+1035
-2
lines changed

src/MongoDB.Bson/Serialization/CollectionsSerializationProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
using System.Dynamic;
2121
using System.Linq;
2222
using System.Reflection;
23-
using System.Runtime.InteropServices;
2423
using MongoDB.Bson.Serialization.Serializers;
2524

2625
namespace MongoDB.Bson.Serialization
@@ -43,6 +42,8 @@ static CollectionsSerializationProvider()
4342
{ typeof(Queue<>), typeof(QueueSerializer<>) },
4443
{ typeof(ReadOnlyCollection<>), typeof(ReadOnlyCollectionSerializer<>) },
4544
{ typeof(Stack<>), typeof(StackSerializer<>) },
45+
{ typeof(Memory<>), typeof(MemorySerializer<>) },
46+
{ typeof(ReadOnlyMemory<>), typeof(ReadonlyMemorySerializer<>) }
4647
};
4748
}
4849

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Runtime.CompilerServices;
18+
using System.Runtime.InteropServices;
19+
using MongoDB.Bson.IO;
20+
21+
namespace MongoDB.Bson.Serialization.Serializers
22+
{
23+
/// <summary>
24+
/// Represents a serializer for <see cref="ReadOnlyMemory{TItem}"/>.
25+
/// </summary>
26+
/// <typeparam name="TItem">The type of the item. Only primitive numeric types are supported.</typeparam>
27+
public sealed class ReadonlyMemorySerializer<TItem> : MemorySerializerBase<TItem, ReadOnlyMemory<TItem>>
28+
{
29+
/// <summary>
30+
/// Initializes a new instance of the <see cref="ReadonlyMemorySerializer{TItem}" /> class.
31+
/// </summary>
32+
public ReadonlyMemorySerializer() : base()
33+
{
34+
}
35+
36+
/// <summary>
37+
/// Initializes a new instance of the <see cref="ReadonlyMemorySerializer{TItem}" /> class.
38+
/// </summary>
39+
public ReadonlyMemorySerializer(BsonType representation) : base(representation)
40+
{
41+
}
42+
43+
/// <summary>
44+
/// Returns a serializer that has been reconfigured with the specified representation.
45+
/// </summary>
46+
/// <param name="representation">The representation.</param>
47+
/// <returns>The reconfigured serializer.</returns>
48+
public override MemorySerializerBase<TItem, ReadOnlyMemory<TItem>> WithRepresentation(BsonType representation)
49+
{
50+
if (representation == Representation)
51+
{
52+
return this;
53+
}
54+
else
55+
{
56+
return new ReadonlyMemorySerializer<TItem>(representation);
57+
}
58+
}
59+
60+
/// <inheritdoc/>
61+
protected override ReadOnlyMemory<TItem> CreateMemory(TItem[] items) => items;
62+
63+
/// <inheritdoc/>
64+
protected override Memory<TItem> GetMemory(ReadOnlyMemory<TItem> memory) => MemoryMarshal.AsMemory(memory);
65+
}
66+
67+
/// <summary>
68+
/// Represents a serializer for <see cref="Memory{TItem}"/>.
69+
/// </summary>
70+
/// <typeparam name="TItem">The type of the item. Only primitive numeric types are supported.</typeparam>
71+
public sealed class MemorySerializer<TItem> : MemorySerializerBase<TItem, Memory<TItem>>
72+
{
73+
/// <summary>
74+
/// Initializes a new instance of the <see cref="MemorySerializer{TItem}" /> class.
75+
/// </summary>
76+
public MemorySerializer() : base()
77+
{
78+
}
79+
80+
/// <summary>
81+
/// Initializes a new instance of the <see cref="MemorySerializer{TItem}" /> class.
82+
/// </summary>
83+
public MemorySerializer(BsonType representation) : base(representation)
84+
{
85+
}
86+
87+
/// <summary>
88+
/// Returns a serializer that has been reconfigured with the specified representation.
89+
/// </summary>
90+
/// <param name="representation">The representation.</param>
91+
/// <returns>The reconfigured serializer.</returns>
92+
public override MemorySerializerBase<TItem, Memory<TItem>> WithRepresentation(BsonType representation)
93+
{
94+
if (representation == Representation)
95+
{
96+
return this;
97+
}
98+
else
99+
{
100+
return new MemorySerializer<TItem>(representation);
101+
}
102+
}
103+
104+
/// <inheritdoc/>
105+
protected override Memory<TItem> CreateMemory(TItem[] items) => items;
106+
107+
/// <inheritdoc/>
108+
protected override Memory<TItem> GetMemory(Memory<TItem> memory) => memory;
109+
}
110+
111+
/// <summary>
112+
/// Represents an abstract base class for <see cref="Memory{TItem}"/> and <see cref="ReadOnlyMemory{TItem}"/> serializers.
113+
/// </summary>
114+
/// <typeparam name="TItem">The type of the item. Only primitive numeric types are supported.</typeparam>
115+
/// <typeparam name="TMemory">The type of the memory struct.</typeparam>
116+
public abstract class MemorySerializerBase<TItem, TMemory> : StructSerializerBase<TMemory>, IRepresentationConfigurable<MemorySerializerBase<TItem, TMemory>>
117+
where TMemory : struct
118+
{
119+
private static readonly bool __isByte = (typeof(TItem) == typeof(byte));
120+
121+
private readonly Func<IBsonReader, TItem[]> _readItems;
122+
private readonly Action<IBsonWriter, Memory<TItem>> _writeItems;
123+
124+
/// <inheritdoc/>
125+
public BsonType Representation { get; }
126+
127+
// constructors
128+
/// <summary>
129+
/// Initializes a new instance of the <see cref="MemorySerializerBase{TItem, TMemory}" /> class.
130+
/// </summary>
131+
public MemorySerializerBase(BsonType representation)
132+
{
133+
if (representation != BsonType.Array &&
134+
!(__isByte && representation == BsonType.Binary))
135+
{
136+
throw new ArgumentOutOfRangeException(nameof(representation));
137+
}
138+
139+
(_readItems, _writeItems) = GetReaderAndWriter();
140+
Representation = representation;
141+
}
142+
143+
/// <summary>
144+
/// Initializes a new instance of the <see cref="MemorySerializerBase{TItem, TMemory}" /> class.
145+
/// </summary>
146+
public MemorySerializerBase() :
147+
this(__isByte ? BsonType.Binary : BsonType.Array) // Match the serialization behavior for arrays
148+
{
149+
}
150+
151+
// public methods
152+
/// <inheritdoc/>
153+
public override TMemory Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
154+
{
155+
var reader = context.Reader;
156+
157+
var bsonType = reader.GetCurrentBsonType();
158+
switch (bsonType)
159+
{
160+
case BsonType.Array:
161+
var items = _readItems(reader);
162+
163+
return CreateMemory(items);
164+
case BsonType.Binary:
165+
if (!__isByte)
166+
{
167+
throw CreateCannotDeserializeFromBsonTypeException(bsonType);
168+
}
169+
var bytes = reader.ReadBytes();
170+
return CreateMemory(bytes as TItem[]);
171+
default:
172+
throw CreateCannotDeserializeFromBsonTypeException(bsonType);
173+
}
174+
}
175+
176+
/// <inheritdoc/>
177+
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TMemory value)
178+
{
179+
var memory = GetMemory(value);
180+
181+
switch (Representation)
182+
{
183+
case BsonType.Array:
184+
_writeItems(context.Writer, memory);
185+
break;
186+
case BsonType.Binary:
187+
var bytesMemory = Unsafe.As<Memory<TItem>, Memory<byte>>(ref memory);
188+
var bytes = MemoryMarshal.AsBytes(bytesMemory.Span);
189+
context.Writer.WriteBytes(bytes.ToArray());
190+
break;
191+
default:
192+
throw new NotSupportedException(nameof(Representation));
193+
}
194+
}
195+
196+
/// <inheritdoc/>
197+
public abstract MemorySerializerBase<TItem, TMemory> WithRepresentation(BsonType representation);
198+
199+
// explicit interface implementations
200+
IBsonSerializer IRepresentationConfigurable.WithRepresentation(BsonType representation) =>
201+
WithRepresentation(representation);
202+
203+
/// <summary>
204+
/// Creates the Memory{TITem} structure.
205+
/// </summary>
206+
/// <param name="items">The items to initialize the resulting instance with.</param>
207+
/// <returns>The created memory structure.</returns>
208+
protected abstract TMemory CreateMemory(TItem[] items);
209+
210+
/// <summary>
211+
/// Get the memory structure from TMemory instance.
212+
/// </summary>
213+
/// <returns>The Memory{TITem} structure.</returns>
214+
protected abstract Memory<TItem> GetMemory(TMemory memory);
215+
216+
private static (Func<IBsonReader, TItem[]> reader, Action<IBsonWriter, Memory<TItem>> writer) GetReaderAndWriter()
217+
{
218+
Func<IBsonReader, TItem[]> readItems;
219+
Action<IBsonWriter, Memory<TItem>> writeItems;
220+
221+
switch (typeof(TItem))
222+
{
223+
case var t when t == typeof(bool):
224+
readItems = reader => PrimitivesArrayReader.ReadBool(reader) as TItem[];
225+
writeItems = (writer, memory) =>
226+
{
227+
var span = Unsafe.As<Memory<TItem>, Memory<bool>>(ref memory).Span;
228+
PrimitivesArrayWriter.WriteBool(writer, span);
229+
};
230+
break;
231+
case var t when t == typeof(sbyte):
232+
readItems = reader => PrimitivesArrayReader.ReadInt8(reader) as TItem[];
233+
writeItems = (writer, memory) =>
234+
{
235+
var span = Unsafe.As<Memory<TItem>, Memory<sbyte>>(ref memory).Span;
236+
PrimitivesArrayWriter.WriteInt8(writer, span);
237+
};
238+
break;
239+
case var t when t == typeof(byte):
240+
readItems = reader => PrimitivesArrayReader.ReadUInt8(reader) as TItem[];
241+
writeItems = (writer, memory) =>
242+
{
243+
var span = Unsafe.As<Memory<TItem>, Memory<byte>>(ref memory).Span;
244+
PrimitivesArrayWriter.WriteUInt8(writer, span);
245+
};
246+
break;
247+
case var t when t == typeof(char):
248+
readItems = reader => PrimitivesArrayReader.ReadChar(reader) as TItem[];
249+
writeItems = (writer, memory) =>
250+
{
251+
var span = Unsafe.As<Memory<TItem>, Memory<char>>(ref memory).Span;
252+
PrimitivesArrayWriter.WriteChar(writer, span);
253+
};
254+
break;
255+
case var t when t == typeof(short):
256+
readItems = reader => PrimitivesArrayReader.ReadInt16(reader) as TItem[];
257+
writeItems = (writer, memory) =>
258+
{
259+
var span = Unsafe.As<Memory<TItem>, Memory<short>>(ref memory).Span;
260+
PrimitivesArrayWriter.WriteInt16(writer, span);
261+
};
262+
break;
263+
case var t when t == typeof(ushort):
264+
readItems = reader => PrimitivesArrayReader.ReadUInt16(reader) as TItem[];
265+
writeItems = (writer, memory) =>
266+
{
267+
var span = Unsafe.As<Memory<TItem>, Memory<ushort>>(ref memory).Span;
268+
PrimitivesArrayWriter.WriteUInt16(writer, span);
269+
};
270+
break;
271+
case var t when t == typeof(int):
272+
readItems = reader => PrimitivesArrayReader.ReadInt32(reader) as TItem[];
273+
writeItems = (writer, memory) =>
274+
{
275+
var span = Unsafe.As<Memory<TItem>, Memory<int>>(ref memory).Span;
276+
PrimitivesArrayWriter.WriteInt32(writer, span);
277+
};
278+
break;
279+
case var t when t == typeof(uint):
280+
readItems = reader => PrimitivesArrayReader.ReadUInt32(reader) as TItem[];
281+
writeItems = (writer, memory) =>
282+
{
283+
var span = Unsafe.As<Memory<TItem>, Memory<uint>>(ref memory).Span;
284+
PrimitivesArrayWriter.WriteUInt32(writer, span);
285+
};
286+
break;
287+
case var t when t == typeof(long):
288+
readItems = reader => PrimitivesArrayReader.ReadInt64(reader) as TItem[];
289+
writeItems = (writer, memory) =>
290+
{
291+
var span = Unsafe.As<Memory<TItem>, Memory<long>>(ref memory).Span;
292+
PrimitivesArrayWriter.WriteInt64(writer, span);
293+
};
294+
break;
295+
case var t when t == typeof(ulong):
296+
readItems = reader => PrimitivesArrayReader.ReadUInt64(reader) as TItem[];
297+
writeItems = (writer, memory) =>
298+
{
299+
var span = Unsafe.As<Memory<TItem>, Memory<ulong>>(ref memory).Span;
300+
PrimitivesArrayWriter.WriteUInt64(writer, span);
301+
};
302+
break;
303+
case var t when t == typeof(float):
304+
readItems = reader => PrimitivesArrayReader.ReadSingles(reader) as TItem[];
305+
writeItems = (writer, memory) =>
306+
{
307+
var span = Unsafe.As<Memory<TItem>, Memory<float>>(ref memory).Span;
308+
PrimitivesArrayWriter.WriteSingles(writer, span);
309+
};
310+
break;
311+
case var t when t == typeof(double):
312+
readItems = reader => PrimitivesArrayReader.ReadDoubles(reader) as TItem[];
313+
writeItems = (writer, memory) =>
314+
{
315+
var span = Unsafe.As<Memory<TItem>, Memory<double>>(ref memory).Span;
316+
PrimitivesArrayWriter.WriteDoubles(writer, span);
317+
};
318+
break;
319+
case var t when t == typeof(decimal):
320+
readItems = reader => PrimitivesArrayReader.ReadDecimal128(reader) as TItem[];
321+
writeItems = (writer, memory) =>
322+
{
323+
var span = Unsafe.As<Memory<TItem>, Memory<decimal>>(ref memory).Span;
324+
PrimitivesArrayWriter.WriteDecimal128(writer, span);
325+
};
326+
break;
327+
default:
328+
throw new NotSupportedException($"Not supported memory type {typeof(TItem)}. Only primitive numeric types are supported.");
329+
};
330+
331+
return (readItems, writeItems);
332+
}
333+
}
334+
}

0 commit comments

Comments
 (0)