Skip to content

.NET 8: System.Collections.Frozen not supported #1720

@N-Olbert

Description

@N-Olbert

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();
        }
    }
}

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions