-
-
Notifications
You must be signed in to change notification settings - Fork 754
Description
Bug description
The frozen collections of .NET 8 aren't supported yet and lead to an exception like this:
MessagePack.MessagePackSerializationException: Failed to serialize System.Collections.Frozen.FrozenSet1[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] value. ---> System.TypeInitializationException: The type initializer for 'FormatterCache1' threw an exception.
Repro steps
var frozenSet = new[] { 1, 2, 3 }.ToFrozenSet();
var data = MessagePackSerializer.Serialize(frozenSet); // BOOM
Expected behavior
No exception, proper (de-)serialization.
Actual behavior
See above
- Version used: 2.5.129
- Runtime: .NET 8
Additional context
Below is a minimal custom resolver that enables proper (de-)serialization of frozen collections.
Is is very, very similar to the ImmutableCollectionResolver.
(Sorry, I don't have the time at the moment to put the code a the right place, write some tests and file a PR, but maybe this is helpful)
public sealed class FrozenCollectionResolver : IFormatterResolver
{
/// <summary>
/// Gets the instance of the <see cref="FrozenCollectionResolver"/>.
/// </summary>
public static readonly FrozenCollectionResolver Instance = new();
private FrozenCollectionResolver()
{
}
/// <inheritdoc />
public IMessagePackFormatter<T>? GetFormatter<T>() => FormatterCache<T>.Formatter;
private static class FormatterCache<T>
{
internal static readonly IMessagePackFormatter<T>? Formatter = (IMessagePackFormatter<T>?)FrozenCollectionGetFormatterHelper.GetFormatter(typeof(T));
}
private static class FrozenCollectionGetFormatterHelper
{
private static readonly Dictionary<Type, Type> FormatterMap = new()
{
{ typeof(FrozenSet<>), typeof(FrozenSetFormatter<>) },
{ typeof(FrozenDictionary<,>), typeof(FrozenDictionaryFormatter<,>) },
};
internal static object? GetFormatter(Type t)
{
if (t.IsGenericType)
{
var genericType = t.GetGenericTypeDefinition();
if (FormatterMap.TryGetValue(genericType, out var formatterType))
{
return CreateInstance(formatterType, t.GenericTypeArguments);
}
}
return null;
}
private static object? CreateInstance(Type genericType, Type[] genericTypeArguments, params object[] arguments) => Activator.CreateInstance(genericType.MakeGenericType(genericTypeArguments), arguments);
private sealed class FrozenDictionaryFormatter<TKey, TValue> : DictionaryFormatterBase<TKey, TValue, Dictionary<TKey, TValue>, FrozenDictionary<TKey, TValue>.Enumerator, FrozenDictionary<TKey, TValue>>
where TKey : notnull
{
protected override void Add(Dictionary<TKey, TValue> collection, int index, TKey key, TValue value, MessagePackSerializerOptions options) => collection.Add(key, value);
protected override FrozenDictionary<TKey, TValue> Complete(Dictionary<TKey, TValue> intermediateCollection) => intermediateCollection.ToFrozenDictionary();
protected override Dictionary<TKey, TValue> Create(int count, MessagePackSerializerOptions options) => new(options.Security.GetEqualityComparer<TKey>());
protected override FrozenDictionary<TKey, TValue>.Enumerator GetSourceEnumerator(FrozenDictionary<TKey, TValue> source) => source.GetEnumerator();
}
private sealed class FrozenSetFormatter<T> : CollectionFormatterBase<T, HashSet<T>, FrozenSet<T>.Enumerator, FrozenSet<T>>
{
protected override void Add(HashSet<T> collection, int index, T value, MessagePackSerializerOptions options) => collection.Add(value);
protected override FrozenSet<T> Complete(HashSet<T> intermediateCollection) => intermediateCollection.ToFrozenSet();
protected override HashSet<T> Create(int count, MessagePackSerializerOptions options) => new(options.Security.GetEqualityComparer<T>());
protected override FrozenSet<T>.Enumerator GetSourceEnumerator(FrozenSet<T> source) => source.GetEnumerator();
}
}
}