Skip to content

Commit 5e27219

Browse files
rstamDmitryLukyanov
authored andcommitted
CSHARP-4549: Better support for Tuples and ValueTuples in LINQ3.
1 parent 07bb37e commit 5e27219

File tree

15 files changed

+3110
-310
lines changed

15 files changed

+3110
-310
lines changed

src/MongoDB.Bson/Serialization/Serializers/TupleSerializers.cs

Lines changed: 293 additions & 290 deletions
Large diffs are not rendered by default.

src/MongoDB.Bson/Serialization/Serializers/ValueTupleSerializers.cs

Lines changed: 162 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,58 @@
1414
*/
1515

1616
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
1719
using MongoDB.Bson.IO;
1820

1921
namespace MongoDB.Bson.Serialization.Serializers
2022
{
23+
/// <summary>
24+
/// A factory class for ValueTupleSerializers.
25+
/// </summary>
26+
public static class ValueTupleSerializer
27+
{
28+
/// <summary>
29+
/// Creates a ValueTupleSerializer.
30+
/// </summary>
31+
/// <param name="itemSerializers">The item serializers.</param>
32+
/// <returns>A ValueTupleSerializer.</returns>
33+
public static IBsonSerializer Create(IEnumerable<IBsonSerializer> itemSerializers)
34+
{
35+
var itemSerializersArray = itemSerializers.ToArray();
36+
var valueTupleSerializerType = CreateValueTupleSerializerType(itemSerializersArray);
37+
return (IBsonSerializer)Activator.CreateInstance(valueTupleSerializerType, itemSerializersArray);
38+
39+
static Type CreateValueTupleSerializerType(IBsonSerializer[] itemSerializersArray)
40+
{
41+
var itemTypes = itemSerializersArray.Select(s => s.ValueType).ToArray();
42+
var valueTupleSerializerTypeDefinition = CreateValueTupleSerializerTypeDefinition(itemTypes.Length);
43+
return valueTupleSerializerTypeDefinition.MakeGenericType(itemTypes);
44+
}
45+
46+
static Type CreateValueTupleSerializerTypeDefinition(int itemCount)
47+
{
48+
return itemCount switch
49+
{
50+
1 => typeof(ValueTupleSerializer<>),
51+
2 => typeof(ValueTupleSerializer<,>),
52+
3 => typeof(ValueTupleSerializer<,,>),
53+
4 => typeof(ValueTupleSerializer<,,,>),
54+
5 => typeof(ValueTupleSerializer<,,,,>),
55+
6 => typeof(ValueTupleSerializer<,,,,,>),
56+
7 => typeof(ValueTupleSerializer<,,,,,,>),
57+
8 => typeof(ValueTupleSerializer<,,,,,,,>),
58+
_ => throw new Exception($"Invalid number of ValueTuple items : {itemCount}.")
59+
};
60+
}
61+
}
62+
}
63+
2164
/// <summary>
2265
/// Represents a serializer for a <see cref="ValueTuple{T1}"/>.
2366
/// </summary>
2467
/// <typeparam name="T1">The type of item 1.</typeparam>
25-
public sealed class ValueTupleSerializer<T1> : StructSerializerBase<ValueTuple<T1>>
68+
public sealed class ValueTupleSerializer<T1> : StructSerializerBase<ValueTuple<T1>>, IBsonTupleSerializer
2669
{
2770
// private fields
2871
private readonly Lazy<IBsonSerializer<T1>> _lazyItem1Serializer;
@@ -43,7 +86,7 @@ public ValueTupleSerializer()
4386
public ValueTupleSerializer(
4487
IBsonSerializer<T1> item1Serializer)
4588
{
46-
if (item1Serializer == null) { throw new ArgumentNullException((nameof(item1Serializer))); }
89+
if (item1Serializer == null) { throw new ArgumentNullException(nameof(item1Serializer)); }
4790

4891
_lazyItem1Serializer = new Lazy<IBsonSerializer<T1>>(() => item1Serializer);
4992
}
@@ -101,6 +144,16 @@ public override ValueTuple<T1> Deserialize(BsonDeserializationContext context, B
101144
return new ValueTuple<T1>(item1);
102145
}
103146

147+
/// <inheritdoc/>
148+
public IBsonSerializer GetItemSerializer(int itemNumber)
149+
{
150+
return itemNumber switch
151+
{
152+
1 => _lazyItem1Serializer.Value,
153+
_ => throw new IndexOutOfRangeException(nameof(itemNumber))
154+
};
155+
}
156+
104157
/// <inheritdoc/>
105158
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ValueTuple<T1> value)
106159
{
@@ -115,7 +168,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
115168
/// </summary>
116169
/// <typeparam name="T1">The type of item 1.</typeparam>
117170
/// <typeparam name="T2">The type of item 2.</typeparam>
118-
public sealed class ValueTupleSerializer<T1, T2> : StructSerializerBase<ValueTuple<T1, T2>>
171+
public sealed class ValueTupleSerializer<T1, T2> : StructSerializerBase<ValueTuple<T1, T2>>, IBsonTupleSerializer
119172
{
120173
// private fields
121174
private readonly Lazy<IBsonSerializer<T1>> _lazyItem1Serializer;
@@ -139,7 +192,7 @@ public ValueTupleSerializer(
139192
IBsonSerializer<T1> item1Serializer,
140193
IBsonSerializer<T2> item2Serializer)
141194
{
142-
if (item1Serializer == null) { throw new ArgumentNullException((nameof(item1Serializer))); }
195+
if (item1Serializer == null) { throw new ArgumentNullException(nameof(item1Serializer)); }
143196
if (item2Serializer == null) { throw new ArgumentNullException(nameof(item2Serializer)); }
144197

145198
_lazyItem1Serializer = new Lazy<IBsonSerializer<T1>>(() => item1Serializer);
@@ -208,6 +261,17 @@ public override ValueTuple<T1, T2> Deserialize(BsonDeserializationContext contex
208261
return new ValueTuple<T1, T2>(item1, item2);
209262
}
210263

264+
/// <inheritdoc/>
265+
public IBsonSerializer GetItemSerializer(int itemNumber)
266+
{
267+
return itemNumber switch
268+
{
269+
1 => _lazyItem1Serializer.Value,
270+
2 => _lazyItem2Serializer.Value,
271+
_ => throw new IndexOutOfRangeException(nameof(itemNumber))
272+
};
273+
}
274+
211275
/// <inheritdoc/>
212276
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ValueTuple<T1, T2> value)
213277
{
@@ -224,7 +288,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
224288
/// <typeparam name="T1">The type of item 1.</typeparam>
225289
/// <typeparam name="T2">The type of item 2.</typeparam>
226290
/// <typeparam name="T3">The type of item 3.</typeparam>
227-
public sealed class ValueTupleSerializer<T1, T2, T3> : StructSerializerBase<ValueTuple<T1, T2, T3>>
291+
public sealed class ValueTupleSerializer<T1, T2, T3> : StructSerializerBase<ValueTuple<T1, T2, T3>>, IBsonTupleSerializer
228292
{
229293
// private fields
230294
private readonly Lazy<IBsonSerializer<T1>> _lazyItem1Serializer;
@@ -251,7 +315,7 @@ public ValueTupleSerializer(
251315
IBsonSerializer<T2> item2Serializer,
252316
IBsonSerializer<T3> item3Serializer)
253317
{
254-
if (item1Serializer == null) { throw new ArgumentNullException((nameof(item1Serializer))); }
318+
if (item1Serializer == null) { throw new ArgumentNullException(nameof(item1Serializer)); }
255319
if (item2Serializer == null) { throw new ArgumentNullException(nameof(item2Serializer)); }
256320
if (item3Serializer == null) { throw new ArgumentNullException(nameof(item3Serializer)); }
257321

@@ -331,6 +395,18 @@ public override ValueTuple<T1, T2, T3> Deserialize(BsonDeserializationContext co
331395
return new ValueTuple<T1, T2, T3>(item1, item2, item3);
332396
}
333397

398+
/// <inheritdoc/>
399+
public IBsonSerializer GetItemSerializer(int itemNumber)
400+
{
401+
return itemNumber switch
402+
{
403+
1 => _lazyItem1Serializer.Value,
404+
2 => _lazyItem2Serializer.Value,
405+
3 => _lazyItem3Serializer.Value,
406+
_ => throw new IndexOutOfRangeException(nameof(itemNumber))
407+
};
408+
}
409+
334410
/// <inheritdoc/>
335411
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ValueTuple<T1, T2, T3> value)
336412
{
@@ -349,7 +425,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
349425
/// <typeparam name="T2">The type of item 2.</typeparam>
350426
/// <typeparam name="T3">The type of item 3.</typeparam>
351427
/// <typeparam name="T4">The type of item 4.</typeparam>
352-
public sealed class ValueTupleSerializer<T1, T2, T3, T4> : StructSerializerBase<ValueTuple<T1, T2, T3, T4>>
428+
public sealed class ValueTupleSerializer<T1, T2, T3, T4> : StructSerializerBase<ValueTuple<T1, T2, T3, T4>>, IBsonTupleSerializer
353429
{
354430
// private fields
355431
private readonly Lazy<IBsonSerializer<T1>> _lazyItem1Serializer;
@@ -470,6 +546,19 @@ public override ValueTuple<T1, T2, T3, T4> Deserialize(BsonDeserializationContex
470546
return new ValueTuple<T1, T2, T3, T4>(item1, item2, item3, item4);
471547
}
472548

549+
/// <inheritdoc/>
550+
public IBsonSerializer GetItemSerializer(int itemNumber)
551+
{
552+
return itemNumber switch
553+
{
554+
1 => _lazyItem1Serializer.Value,
555+
2 => _lazyItem2Serializer.Value,
556+
3 => _lazyItem3Serializer.Value,
557+
4 => _lazyItem4Serializer.Value,
558+
_ => throw new IndexOutOfRangeException(nameof(itemNumber))
559+
};
560+
}
561+
473562
/// <inheritdoc/>
474563
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ValueTuple<T1, T2, T3, T4> value)
475564
{
@@ -490,7 +579,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
490579
/// <typeparam name="T3">The type of item 3.</typeparam>
491580
/// <typeparam name="T4">The type of item 4.</typeparam>
492581
/// <typeparam name="T5">The type of item 5.</typeparam>
493-
public sealed class ValueTupleSerializer<T1, T2, T3, T4, T5> : StructSerializerBase<ValueTuple<T1, T2, T3, T4, T5>>
582+
public sealed class ValueTupleSerializer<T1, T2, T3, T4, T5> : StructSerializerBase<ValueTuple<T1, T2, T3, T4, T5>>, IBsonTupleSerializer
494583
{
495584
// private fields
496585
private readonly Lazy<IBsonSerializer<T1>> _lazyItem1Serializer;
@@ -625,6 +714,20 @@ public override ValueTuple<T1, T2, T3, T4, T5> Deserialize(BsonDeserializationCo
625714
return new ValueTuple<T1, T2, T3, T4, T5>(item1, item2, item3, item4, item5);
626715
}
627716

717+
/// <inheritdoc/>
718+
public IBsonSerializer GetItemSerializer(int itemNumber)
719+
{
720+
return itemNumber switch
721+
{
722+
1 => _lazyItem1Serializer.Value,
723+
2 => _lazyItem2Serializer.Value,
724+
3 => _lazyItem3Serializer.Value,
725+
4 => _lazyItem4Serializer.Value,
726+
5 => _lazyItem5Serializer.Value,
727+
_ => throw new IndexOutOfRangeException(nameof(itemNumber))
728+
};
729+
}
730+
628731
/// <inheritdoc/>
629732
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ValueTuple<T1, T2, T3, T4, T5> value)
630733
{
@@ -647,7 +750,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
647750
/// <typeparam name="T4">The type of item 4.</typeparam>
648751
/// <typeparam name="T5">The type of item 5.</typeparam>
649752
/// <typeparam name="T6">The type of item 6.</typeparam>
650-
public sealed class ValueTupleSerializer<T1, T2, T3, T4, T5, T6> : StructSerializerBase<ValueTuple<T1, T2, T3, T4, T5, T6>>
753+
public sealed class ValueTupleSerializer<T1, T2, T3, T4, T5, T6> : StructSerializerBase<ValueTuple<T1, T2, T3, T4, T5, T6>>, IBsonTupleSerializer
651754
{
652755
// private fields
653756
private readonly Lazy<IBsonSerializer<T1>> _lazyItem1Serializer;
@@ -796,6 +899,21 @@ public override ValueTuple<T1, T2, T3, T4, T5, T6> Deserialize(BsonDeserializati
796899
return new ValueTuple<T1, T2, T3, T4, T5, T6>(item1, item2, item3, item4, item5, item6);
797900
}
798901

902+
/// <inheritdoc/>
903+
public IBsonSerializer GetItemSerializer(int itemNumber)
904+
{
905+
return itemNumber switch
906+
{
907+
1 => _lazyItem1Serializer.Value,
908+
2 => _lazyItem2Serializer.Value,
909+
3 => _lazyItem3Serializer.Value,
910+
4 => _lazyItem4Serializer.Value,
911+
5 => _lazyItem5Serializer.Value,
912+
6 => _lazyItem6Serializer.Value,
913+
_ => throw new IndexOutOfRangeException(nameof(itemNumber))
914+
};
915+
}
916+
799917
/// <inheritdoc/>
800918
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ValueTuple<T1, T2, T3, T4, T5, T6> value)
801919
{
@@ -820,7 +938,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
820938
/// <typeparam name="T5">The type of item 5.</typeparam>
821939
/// <typeparam name="T6">The type of item 6.</typeparam>
822940
/// <typeparam name="T7">The type of item 7.</typeparam>
823-
public sealed class ValueTupleSerializer<T1, T2, T3, T4, T5, T6, T7> : StructSerializerBase<ValueTuple<T1, T2, T3, T4, T5, T6, T7>>
941+
public sealed class ValueTupleSerializer<T1, T2, T3, T4, T5, T6, T7> : StructSerializerBase<ValueTuple<T1, T2, T3, T4, T5, T6, T7>>, IBsonTupleSerializer
824942
{
825943
// private fields
826944
private readonly Lazy<IBsonSerializer<T1>> _lazyItem1Serializer;
@@ -983,6 +1101,22 @@ public override ValueTuple<T1, T2, T3, T4, T5, T6, T7> Deserialize(BsonDeseriali
9831101
return new ValueTuple<T1, T2, T3, T4, T5, T6, T7>(item1, item2, item3, item4, item5, item6, item7);
9841102
}
9851103

1104+
/// <inheritdoc/>
1105+
public IBsonSerializer GetItemSerializer(int itemNumber)
1106+
{
1107+
return itemNumber switch
1108+
{
1109+
1 => _lazyItem1Serializer.Value,
1110+
2 => _lazyItem2Serializer.Value,
1111+
3 => _lazyItem3Serializer.Value,
1112+
4 => _lazyItem4Serializer.Value,
1113+
5 => _lazyItem5Serializer.Value,
1114+
6 => _lazyItem6Serializer.Value,
1115+
7 => _lazyItem7Serializer.Value,
1116+
_ => throw new IndexOutOfRangeException(nameof(itemNumber))
1117+
};
1118+
}
1119+
9861120
/// <inheritdoc/>
9871121
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ValueTuple<T1, T2, T3, T4, T5, T6, T7> value)
9881122
{
@@ -1009,7 +1143,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
10091143
/// <typeparam name="T6">The type of item 6.</typeparam>
10101144
/// <typeparam name="T7">The type of item 7.</typeparam>
10111145
/// <typeparam name="TRest">The type of the rest item.</typeparam>
1012-
public sealed class ValueTupleSerializer<T1, T2, T3, T4, T5, T6, T7, TRest> : StructSerializerBase<ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>>
1146+
public sealed class ValueTupleSerializer<T1, T2, T3, T4, T5, T6, T7, TRest> : StructSerializerBase<ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>>, IBsonTupleSerializer
10131147
where TRest : struct
10141148
{
10151149
// private fields
@@ -1187,6 +1321,23 @@ public override ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest> Deserialize(BsonDe
11871321
return new ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>(item1, item2, item3, item4, item5, item6, item7, rest);
11881322
}
11891323

1324+
/// <inheritdoc/>
1325+
public IBsonSerializer GetItemSerializer(int itemNumber)
1326+
{
1327+
return itemNumber switch
1328+
{
1329+
1 => _lazyItem1Serializer.Value,
1330+
2 => _lazyItem2Serializer.Value,
1331+
3 => _lazyItem3Serializer.Value,
1332+
4 => _lazyItem4Serializer.Value,
1333+
5 => _lazyItem5Serializer.Value,
1334+
6 => _lazyItem6Serializer.Value,
1335+
7 => _lazyItem7Serializer.Value,
1336+
8 => _lazyRestSerializer.Value,
1337+
_ => throw new IndexOutOfRangeException(nameof(itemNumber))
1338+
};
1339+
}
1340+
11901341
/// <inheritdoc/>
11911342
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest> value)
11921343
{

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstExpression.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ public static AstExpression ComputedArray(IEnumerable<AstExpression> items)
191191
return new AstComputedArrayExpression(items);
192192
}
193193

194+
public static AstExpression ComputedArray(params AstExpression[] items)
195+
{
196+
return ComputedArray((IEnumerable<AstExpression>)items);
197+
}
198+
194199
public static AstExpression ComputedDocument(IEnumerable<AstComputedField> fields)
195200
{
196201
return new AstComputedDocumentExpression(fields);

src/MongoDB.Driver/Linq/Linq3Implementation/Misc/TypeExtensions.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,36 @@
1515

1616
using System;
1717
using System.Collections.Generic;
18+
using System.Linq;
1819

1920
namespace MongoDB.Driver.Linq.Linq3Implementation.Misc
2021
{
2122
internal static class TypeExtensions
2223
{
24+
private static Type[] __tupleTypeDefinitions =
25+
{
26+
typeof(Tuple<>),
27+
typeof(Tuple<,>),
28+
typeof(Tuple<,,>),
29+
typeof(Tuple<,,,>),
30+
typeof(Tuple<,,,,>),
31+
typeof(Tuple<,,,,,>),
32+
typeof(Tuple<,,,,,,>),
33+
typeof(Tuple<,,,,,,,>)
34+
};
35+
36+
private static Type[] __valueTupleTypeDefinitions =
37+
{
38+
typeof(ValueTuple<>),
39+
typeof(ValueTuple<,>),
40+
typeof(ValueTuple<,,>),
41+
typeof(ValueTuple<,,,>),
42+
typeof(ValueTuple<,,,,>),
43+
typeof(ValueTuple<,,,,,>),
44+
typeof(ValueTuple<,,,,,,>),
45+
typeof(ValueTuple<,,,,,,,>)
46+
};
47+
2348
public static Type GetIEnumerableGenericInterface(this Type enumerableType)
2449
{
2550
if (enumerableType.TryGetIEnumerableGenericInterface(out var ienumerableGenericInterface))
@@ -166,6 +191,29 @@ public static bool IsSameAsOrNullableOf(this Type type, Type valueType)
166191
return type == valueType || type.IsNullableOf(valueType);
167192
}
168193

194+
public static bool IsTuple(this Type type)
195+
{
196+
return
197+
type.IsConstructedGenericType &&
198+
type.GetGenericTypeDefinition() is var typeDefinition &&
199+
__tupleTypeDefinitions.Contains(typeDefinition);
200+
201+
}
202+
203+
public static bool IsTupleOrValueTuple(this Type type)
204+
{
205+
return IsTuple(type) || IsValueTuple(type);
206+
}
207+
208+
public static bool IsValueTuple(this Type type)
209+
{
210+
return
211+
type.IsConstructedGenericType &&
212+
type.GetGenericTypeDefinition() is var typeDefinition &&
213+
__valueTupleTypeDefinitions.Contains(typeDefinition);
214+
215+
}
216+
169217
public static bool TryGetIDictionaryGenericInterface(this Type type, out Type idictionaryGenericInterface)
170218
{
171219
foreach (var interfaceType in type.GetInterfaces())

0 commit comments

Comments
 (0)