Skip to content

Commit 39b60e0

Browse files
committed
feat(csharp): restore xlang api for native collections
1 parent bb5c696 commit 39b60e0

12 files changed

Lines changed: 949 additions & 14 deletions

File tree

csharp/src/Fory.Generator/ForyObjectGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1282,7 +1282,7 @@ memberType is INamedTypeSymbol nts &&
12821282
classification,
12831283
group,
12841284
classification.IsCollection || classification.IsMap,
1285-
classification.IsMap && !IsTypeSealed(unwrappedType),
1285+
classification.IsMap && !classification.IsBuiltIn && !IsTypeSealed(unwrappedType),
12861286
!unwrappedType.IsValueType && classification.TypeId != 21,
12871287
FieldNeedsTypeInfo(classification, dynamicAnyKind, unwrappedType),
12881288
dynamicAnyKind == DynamicAnyKind.None ? DynamicAnyKind.None : dynamicAnyKind,

csharp/src/Fory/CollectionSerializers.cs

Lines changed: 429 additions & 0 deletions
Large diffs are not rendered by default.

csharp/src/Fory/Config.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,24 @@ namespace Apache.Fory;
2424
public sealed class Config
2525
{
2626
internal Config(
27+
bool xlang,
2728
bool trackRef,
2829
bool compatible,
2930
bool checkStructVersion,
3031
int maxDepth)
3132
{
33+
Xlang = xlang;
3234
TrackRef = trackRef;
3335
Compatible = compatible;
3436
CheckStructVersion = checkStructVersion;
3537
MaxDepth = maxDepth;
3638
}
3739

40+
/// <summary>
41+
/// Gets whether cross-language framing mode is enabled.
42+
/// </summary>
43+
public bool Xlang { get; }
44+
3845
/// <summary>
3946
/// Gets whether shared and circular reference tracking is enabled.
4047
/// </summary>
@@ -61,11 +68,23 @@ internal Config(
6168
/// </summary>
6269
public sealed class ForyBuilder
6370
{
71+
private bool _xlang = true;
6472
private bool _trackRef;
6573
private bool _compatible;
6674
private bool _checkStructVersion;
6775
private int _maxDepth = 20;
6876

77+
/// <summary>
78+
/// Enables or disables cross-language protocol mode.
79+
/// </summary>
80+
/// <param name="enabled">Whether to enable xlang mode. Defaults to <c>true</c>.</param>
81+
/// <returns>The same builder instance.</returns>
82+
public ForyBuilder Xlang(bool enabled = true)
83+
{
84+
_xlang = enabled;
85+
return this;
86+
}
87+
6988
/// <summary>
7089
/// Enables or disables reference tracking for shared and circular object graphs.
7190
/// </summary>
@@ -119,6 +138,7 @@ public ForyBuilder MaxDepth(int value)
119138
private Config BuildConfig()
120139
{
121140
return new Config(
141+
xlang: _xlang,
122142
trackRef: _trackRef,
123143
compatible: _compatible,
124144
checkStructVersion: _checkStructVersion,

csharp/src/Fory/DictionarySerializers.cs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,134 @@ private static TValue ReadValueElement(
491491
}
492492
}
493493

494+
public sealed class IDictionarySerializer<TKey, TValue> : Serializer<IDictionary<TKey, TValue>>
495+
where TKey : notnull
496+
{
497+
public override IDictionary<TKey, TValue> DefaultValue => null!;
498+
499+
public override void WriteData(WriteContext context, in IDictionary<TKey, TValue> value, bool hasGenerics)
500+
{
501+
if (NativeCollectionTypeCodec.Enabled(context.TypeResolver))
502+
{
503+
NativeCollectionTypeId typeId = NativeCollectionTypeId.Dictionary;
504+
if (value is not null &&
505+
NativeCollectionTypeCodec.TryGetTypeId(value.GetType(), out NativeCollectionTypeId runtimeTypeId) &&
506+
runtimeTypeId is
507+
NativeCollectionTypeId.Dictionary or
508+
NativeCollectionTypeId.SortedDictionary or
509+
NativeCollectionTypeId.SortedList or
510+
NativeCollectionTypeId.ConcurrentDictionary or
511+
NativeCollectionTypeId.NullableKeyDictionary)
512+
{
513+
typeId = runtimeTypeId;
514+
NativeCollectionTypeCodec.WriteTypeId(context, typeId);
515+
TypeInfo typeInfo = context.TypeResolver.GetTypeInfo(value.GetType());
516+
context.TypeResolver.WriteDataObject(typeInfo, context, value, hasGenerics);
517+
return;
518+
}
519+
520+
NativeCollectionTypeCodec.WriteTypeId(context, typeId);
521+
Dictionary<TKey, TValue> nativeFallback = value as Dictionary<TKey, TValue> ?? (value is null ? [] : new Dictionary<TKey, TValue>(value));
522+
context.TypeResolver.GetSerializer<Dictionary<TKey, TValue>>().WriteData(context, nativeFallback, hasGenerics);
523+
return;
524+
}
525+
526+
if (value is not null &&
527+
NativeCollectionTypeCodec.TryGetTypeId(value.GetType(), out NativeCollectionTypeId xlangTypeId) &&
528+
xlangTypeId is
529+
NativeCollectionTypeId.Dictionary or
530+
NativeCollectionTypeId.SortedDictionary or
531+
NativeCollectionTypeId.SortedList or
532+
NativeCollectionTypeId.ConcurrentDictionary or
533+
NativeCollectionTypeId.NullableKeyDictionary)
534+
{
535+
TypeInfo typeInfo = context.TypeResolver.GetTypeInfo(value.GetType());
536+
context.TypeResolver.WriteDataObject(typeInfo, context, value, hasGenerics);
537+
return;
538+
}
539+
540+
Dictionary<TKey, TValue> map = value as Dictionary<TKey, TValue> ?? (value is null ? [] : new Dictionary<TKey, TValue>(value));
541+
context.TypeResolver.GetSerializer<Dictionary<TKey, TValue>>().WriteData(context, map, hasGenerics);
542+
}
543+
544+
public override IDictionary<TKey, TValue> ReadData(ReadContext context)
545+
{
546+
if (!NativeCollectionTypeCodec.Enabled(context.TypeResolver))
547+
{
548+
return context.TypeResolver.GetSerializer<Dictionary<TKey, TValue>>().ReadData(context);
549+
}
550+
551+
NativeCollectionTypeId typeId = NativeCollectionTypeCodec.ReadTypeId(context);
552+
Type concreteType = NativeCollectionTypeCodec.ResolveDictionaryType<TKey, TValue>(typeId);
553+
TypeInfo typeInfo = context.TypeResolver.GetTypeInfo(concreteType);
554+
return (IDictionary<TKey, TValue>)context.TypeResolver.ReadDataObject(typeInfo, context)!;
555+
}
556+
}
557+
558+
public sealed class IReadOnlyDictionarySerializer<TKey, TValue> : Serializer<IReadOnlyDictionary<TKey, TValue>>
559+
where TKey : notnull
560+
{
561+
public override IReadOnlyDictionary<TKey, TValue> DefaultValue => null!;
562+
563+
public override void WriteData(WriteContext context, in IReadOnlyDictionary<TKey, TValue> value, bool hasGenerics)
564+
{
565+
if (NativeCollectionTypeCodec.Enabled(context.TypeResolver))
566+
{
567+
NativeCollectionTypeId typeId = NativeCollectionTypeId.Dictionary;
568+
if (value is not null &&
569+
NativeCollectionTypeCodec.TryGetTypeId(value.GetType(), out NativeCollectionTypeId runtimeTypeId) &&
570+
runtimeTypeId is
571+
NativeCollectionTypeId.Dictionary or
572+
NativeCollectionTypeId.SortedDictionary or
573+
NativeCollectionTypeId.SortedList or
574+
NativeCollectionTypeId.ConcurrentDictionary or
575+
NativeCollectionTypeId.NullableKeyDictionary)
576+
{
577+
typeId = runtimeTypeId;
578+
NativeCollectionTypeCodec.WriteTypeId(context, typeId);
579+
TypeInfo typeInfo = context.TypeResolver.GetTypeInfo(value.GetType());
580+
context.TypeResolver.WriteDataObject(typeInfo, context, value, hasGenerics);
581+
return;
582+
}
583+
584+
NativeCollectionTypeCodec.WriteTypeId(context, typeId);
585+
Dictionary<TKey, TValue> nativeFallback = value as Dictionary<TKey, TValue> ?? (value is null ? [] : new Dictionary<TKey, TValue>(value));
586+
context.TypeResolver.GetSerializer<Dictionary<TKey, TValue>>().WriteData(context, nativeFallback, hasGenerics);
587+
return;
588+
}
589+
590+
if (value is not null &&
591+
NativeCollectionTypeCodec.TryGetTypeId(value.GetType(), out NativeCollectionTypeId xlangTypeId) &&
592+
xlangTypeId is
593+
NativeCollectionTypeId.Dictionary or
594+
NativeCollectionTypeId.SortedDictionary or
595+
NativeCollectionTypeId.SortedList or
596+
NativeCollectionTypeId.ConcurrentDictionary or
597+
NativeCollectionTypeId.NullableKeyDictionary)
598+
{
599+
TypeInfo typeInfo = context.TypeResolver.GetTypeInfo(value.GetType());
600+
context.TypeResolver.WriteDataObject(typeInfo, context, value, hasGenerics);
601+
return;
602+
}
603+
604+
Dictionary<TKey, TValue> map = value as Dictionary<TKey, TValue> ?? (value is null ? [] : new Dictionary<TKey, TValue>(value));
605+
context.TypeResolver.GetSerializer<Dictionary<TKey, TValue>>().WriteData(context, map, hasGenerics);
606+
}
607+
608+
public override IReadOnlyDictionary<TKey, TValue> ReadData(ReadContext context)
609+
{
610+
if (!NativeCollectionTypeCodec.Enabled(context.TypeResolver))
611+
{
612+
return context.TypeResolver.GetSerializer<Dictionary<TKey, TValue>>().ReadData(context);
613+
}
614+
615+
NativeCollectionTypeId typeId = NativeCollectionTypeCodec.ReadTypeId(context);
616+
Type concreteType = NativeCollectionTypeCodec.ResolveDictionaryType<TKey, TValue>(typeId);
617+
TypeInfo typeInfo = context.TypeResolver.GetTypeInfo(concreteType);
618+
return (IReadOnlyDictionary<TKey, TValue>)context.TypeResolver.ReadDataObject(typeInfo, context)!;
619+
}
620+
}
621+
494622
public class DictionarySerializer<TKey, TValue> : DictionaryLikeSerializer<Dictionary<TKey, TValue>, TKey, TValue>
495623
where TKey : notnull
496624
{

csharp/src/Fory/Fory.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ internal Fory(Config config)
3434
{
3535
Config = config;
3636
_typeResolver = new TypeResolver();
37+
_typeResolver.SetXlang(Config.Xlang);
3738
_writeContext = new WriteContext(
3839
new ByteWriter(),
3940
_typeResolver,
@@ -231,7 +232,11 @@ public T Deserialize<T>(ref ReadOnlySequence<byte> payload)
231232
/// <param name="isNone">Whether the payload value is null.</param>
232233
internal void WriteHead(ByteWriter writer, bool isNone)
233234
{
234-
byte bitmap = ForyHeaderFlag.IsXlang;
235+
byte bitmap = 0;
236+
if (Config.Xlang)
237+
{
238+
bitmap |= ForyHeaderFlag.IsXlang;
239+
}
235240

236241
if (isNone)
237242
{
@@ -251,7 +256,7 @@ internal bool ReadHead(ByteReader reader)
251256
{
252257
byte bitmap = reader.ReadUInt8();
253258
bool peerIsXlang = (bitmap & ForyHeaderFlag.IsXlang) != 0;
254-
if (!peerIsXlang)
259+
if (peerIsXlang != Config.Xlang)
255260
{
256261
throw new InvalidDataException("xlang bitmap mismatch");
257262
}

csharp/src/Fory/TypeInfo.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,8 @@ private static bool TryResolveBuiltInTypeId(Type type, out TypeId typeId)
411411
{
412412
Type genericType = type.GetGenericTypeDefinition();
413413
if (genericType == typeof(List<>) ||
414+
genericType == typeof(IList<>) ||
415+
genericType == typeof(IReadOnlyList<>) ||
414416
genericType == typeof(LinkedList<>) ||
415417
genericType == typeof(Queue<>) ||
416418
genericType == typeof(Stack<>))
@@ -420,6 +422,9 @@ private static bool TryResolveBuiltInTypeId(Type type, out TypeId typeId)
420422
}
421423

422424
if (genericType == typeof(HashSet<>) ||
425+
genericType == typeof(ISet<>) ||
426+
genericType == typeof(IReadOnlySet<>) ||
427+
genericType == typeof(IImmutableSet<>) ||
423428
genericType == typeof(SortedSet<>) ||
424429
genericType == typeof(ImmutableHashSet<>))
425430
{
@@ -428,6 +433,8 @@ private static bool TryResolveBuiltInTypeId(Type type, out TypeId typeId)
428433
}
429434

430435
if (genericType == typeof(Dictionary<,>) ||
436+
genericType == typeof(IDictionary<,>) ||
437+
genericType == typeof(IReadOnlyDictionary<,>) ||
431438
genericType == typeof(SortedDictionary<,>) ||
432439
genericType == typeof(SortedList<,>) ||
433440
genericType == typeof(ConcurrentDictionary<,>) ||

csharp/src/Fory/TypeResolver.cs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ private static class GenericTypeCache<T>
103103
private readonly UInt64Map<TypeMeta> _validatedTypeMetaByType = new();
104104

105105
private readonly UInt64Map<TypeInfo> _typeInfos = new();
106+
private bool _xlang = true;
106107
private ulong _versionHash;
107108
private bool _finalized;
108109

@@ -140,6 +141,8 @@ public Serializer<T> GetSerializer<T>()
140141
return typeInfo.RequireSerializer<T>();
141142
}
142143

144+
internal bool IsXlang => _xlang;
145+
143146
public TypeInfo GetTypeInfo(Type type)
144147
{
145148
return GetOrCreateTypeInfo(type, null);
@@ -398,7 +401,7 @@ static ulong MixFieldType(ulong hash, TypeMetaFieldType fieldType)
398401
}
399402

400403
ulong hash = offsetBasis;
401-
hash = MixBool(hash, true);
404+
hash = MixBool(hash, _xlang);
402405

403406
List<TypeInfo> typeInfos = new(_typeInfos.Count);
404407
_typeInfos.AddValuesTo(typeInfos);
@@ -479,6 +482,17 @@ static ulong MixFieldType(ulong hash, TypeMetaFieldType fieldType)
479482
return null;
480483
}
481484

485+
internal void SetXlang(bool xlang)
486+
{
487+
if (_xlang == xlang)
488+
{
489+
return;
490+
}
491+
492+
_xlang = xlang;
493+
InvalidateFinalizedVersion();
494+
}
495+
482496
internal TypeInfo RequireRegisteredTypeInfo(Type type)
483497
{
484498
TypeInfo? info = GetRegisteredTypeInfo(type);
@@ -1708,12 +1722,42 @@ private TypeInfo CreateBindingCore(Type type)
17081722
return CreateTypeInfo(type, serializerType);
17091723
}
17101724

1725+
if (genericType == typeof(IList<>))
1726+
{
1727+
Type serializerType = typeof(IListSerializer<>).MakeGenericType(genericArgs[0]);
1728+
return CreateTypeInfo(type, serializerType);
1729+
}
1730+
1731+
if (genericType == typeof(IReadOnlyList<>))
1732+
{
1733+
Type serializerType = typeof(IReadOnlyListSerializer<>).MakeGenericType(genericArgs[0]);
1734+
return CreateTypeInfo(type, serializerType);
1735+
}
1736+
17111737
if (genericType == typeof(HashSet<>))
17121738
{
17131739
Type serializerType = typeof(SetSerializer<>).MakeGenericType(genericArgs[0]);
17141740
return CreateTypeInfo(type, serializerType);
17151741
}
17161742

1743+
if (genericType == typeof(ISet<>))
1744+
{
1745+
Type serializerType = typeof(ISetSerializer<>).MakeGenericType(genericArgs[0]);
1746+
return CreateTypeInfo(type, serializerType);
1747+
}
1748+
1749+
if (genericType == typeof(IReadOnlySet<>))
1750+
{
1751+
Type serializerType = typeof(IReadOnlySetSerializer<>).MakeGenericType(genericArgs[0]);
1752+
return CreateTypeInfo(type, serializerType);
1753+
}
1754+
1755+
if (genericType == typeof(IImmutableSet<>))
1756+
{
1757+
Type serializerType = typeof(IImmutableSetSerializer<>).MakeGenericType(genericArgs[0]);
1758+
return CreateTypeInfo(type, serializerType);
1759+
}
1760+
17171761
if (genericType == typeof(SortedSet<>))
17181762
{
17191763
Type serializerType = typeof(SortedSetSerializer<>).MakeGenericType(genericArgs[0]);
@@ -1750,6 +1794,18 @@ private TypeInfo CreateBindingCore(Type type)
17501794
return CreateTypeInfo(type, serializerType);
17511795
}
17521796

1797+
if (genericType == typeof(IDictionary<,>))
1798+
{
1799+
Type serializerType = typeof(IDictionarySerializer<,>).MakeGenericType(genericArgs[0], genericArgs[1]);
1800+
return CreateTypeInfo(type, serializerType);
1801+
}
1802+
1803+
if (genericType == typeof(IReadOnlyDictionary<,>))
1804+
{
1805+
Type serializerType = typeof(IReadOnlyDictionarySerializer<,>).MakeGenericType(genericArgs[0], genericArgs[1]);
1806+
return CreateTypeInfo(type, serializerType);
1807+
}
1808+
17531809
if (genericType == typeof(SortedDictionary<,>))
17541810
{
17551811
Type serializerType = typeof(SortedDictionarySerializer<,>).MakeGenericType(genericArgs[0], genericArgs[1]);

0 commit comments

Comments
 (0)