From 5819382089867463b1566af7b0861de18b74cb87 Mon Sep 17 00:00:00 2001 From: Alex Zavadsky Date: Tue, 16 Feb 2021 23:00:56 +0300 Subject: [PATCH 1/4] Enable serialization IEnumerable and IReadOnlyDictionary descendants that have constructor with relevant collection --- .../Formatters/CollectionFormatter.cs | 19 ++ .../Formatters/DictionaryFormatter.cs | 19 ++ .../Resolvers/DynamicGenericResolver.cs | 40 ++++ .../Tests/ShareTests/CollectionTest.cs | 194 ++++++++++++++++++ 4 files changed, 272 insertions(+) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/CollectionFormatter.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/CollectionFormatter.cs index 590719e4e..7a50ae1dc 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/CollectionFormatter.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/CollectionFormatter.cs @@ -464,6 +464,25 @@ protected override void Add(TCollection collection, int index, TElement value, M } } + public sealed class GenericEnumerableFormatter : CollectionFormatterBase + where TCollection : IEnumerable + { + protected override TElement[] Create(int count, MessagePackSerializerOptions options) + { + return new TElement[count]; + } + + protected override void Add(TElement[] collection, int index, TElement value, MessagePackSerializerOptions options) + { + collection[index] = value; + } + + protected override TCollection Complete(TElement[] intermediateCollection) + { + return (TCollection)Activator.CreateInstance(typeof(TCollection), intermediateCollection); + } + } + public sealed class LinkedListFormatter : CollectionFormatterBase, LinkedList.Enumerator, LinkedList> { protected override void Add(LinkedList collection, int index, T value, MessagePackSerializerOptions options) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/DictionaryFormatter.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/DictionaryFormatter.cs index 4b9347a14..727d9d1df 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/DictionaryFormatter.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/DictionaryFormatter.cs @@ -174,6 +174,25 @@ protected override TDictionary Create(int count, MessagePackSerializerOptions op } } + public sealed class GenericReadOnlyDictionaryFormatter : DictionaryFormatterBase, TDictionary> + where TDictionary : IReadOnlyDictionary + { + protected override void Add(Dictionary collection, int index, TKey key, TValue value, MessagePackSerializerOptions options) + { + collection.Add(key, value); + } + + protected override Dictionary Create(int count, MessagePackSerializerOptions options) + { + return new Dictionary(count, options.Security.GetEqualityComparer()); + } + + protected override TDictionary Complete(Dictionary intermediateCollection) + { + return (TDictionary)Activator.CreateInstance(typeof(TDictionary), intermediateCollection); + } + } + public sealed class InterfaceDictionaryFormatter : DictionaryFormatterBase, IDictionary> { protected override void Add(Dictionary collection, int index, TKey key, TValue value, MessagePackSerializerOptions options) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicGenericResolver.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicGenericResolver.cs index bb555243e..7f9f44517 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicGenericResolver.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicGenericResolver.cs @@ -313,6 +313,29 @@ internal static object GetFormatter(Type t) return CreateInstance(typeof(GenericDictionaryFormatter<,,>), new[] { keyType, valueType, t }); } + // generic readonly dictionary + var readOnlyDictionaryDef = ti.ImplementedInterfaces.FirstOrDefault(x => x.GetTypeInfo().IsConstructedGenericType() && x.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)); + if (readOnlyDictionaryDef != null) + { + Type keyType = readOnlyDictionaryDef.GenericTypeArguments[0]; + Type valueType = readOnlyDictionaryDef.GenericTypeArguments[1]; + Type[] allowedParameterTypes = new Type[] + { + typeof(IDictionary<,>).MakeGenericType(keyType, valueType), + typeof(IReadOnlyDictionary<,>).MakeGenericType(keyType, valueType), + typeof(IEnumerable<>).MakeGenericType(typeof(KeyValuePair<,>).MakeGenericType(keyType, valueType)), + }; + foreach (var constructor in ti.DeclaredConstructors) + { + ParameterInfo[] parameters = constructor.GetParameters(); + if (parameters.Length == 1 && + allowedParameterTypes.Any(allowedType => allowedType.IsAssignableFrom(parameters[0].ParameterType))) + { + return CreateInstance(typeof(GenericReadOnlyDictionaryFormatter<,,>), new[] { keyType, valueType, t }); + } + } + } + // generic collection var collectionDef = ti.ImplementedInterfaces.FirstOrDefault(x => x.GetTypeInfo().IsConstructedGenericType() && x.GetGenericTypeDefinition() == typeof(ICollection<>)); if (collectionDef != null && ti.DeclaredConstructors.Any(x => x.GetParameters().Length == 0)) @@ -320,6 +343,23 @@ internal static object GetFormatter(Type t) Type elemType = collectionDef.GenericTypeArguments[0]; return CreateInstance(typeof(GenericCollectionFormatter<,>), new[] { elemType, t }); } + + // generic IEnumerable collection + // looking for combination of IEnumerable and constructor that takes + // enumeration of the same type + foreach (var enumerableCollectionDef in ti.ImplementedInterfaces.Where(x => x.GetTypeInfo().IsConstructedGenericType() && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + { + Type elemType = enumerableCollectionDef.GenericTypeArguments[0]; + Type paramInterface = typeof(IEnumerable<>).MakeGenericType(elemType); + foreach (var constructor in ti.DeclaredConstructors) + { + var parameters = constructor.GetParameters(); + if (parameters.Length == 1 && paramInterface.IsAssignableFrom(parameters[0].ParameterType)) + { + return CreateInstance(typeof(GenericEnumerableFormatter<,>), new[] { elemType, t }); + } + } + } } return null; diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/CollectionTest.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/CollectionTest.cs index 121806e32..fce5184fc 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/CollectionTest.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/CollectionTest.cs @@ -251,6 +251,41 @@ public void CustomGenericInterfaceCollectionTest() } } + [Fact] + public void CustomGenericEnumerableCollectionTest() + { + { + var xs = new ImplGenericEnumerable(); + xs.Add(1); + xs.Add(2); + xs.Add(3); + + this.Convert(xs).Is(1, 2, 3); + } + } + + [Fact] + public void CustomGenericInterfaceReadOnlyCollectionTest() + { + { + var xs = new ImplGenericReadonlyCollection(); + xs.Add(1); + xs.Add(2); + xs.Add(3); + + this.Convert(xs).Is(1, 2, 3); + } + + { + var xs = new ImplGenericGenericReadOnlyCollection(); + xs.Add(1); + xs.Add(2); + xs.Add(3); + + this.Convert(xs).Is(1, 2, 3); + } + } + [Fact] public void CustomGenericDictionaryTest() { @@ -262,6 +297,18 @@ public void CustomGenericDictionaryTest() d2["foo"].Is(10); d2["bar"].Is(20); } + + [Fact] + public void CustomGenericReadOnlyDictionaryTest() + { + var d = new ImplReadonlyDictionary(); + d.Add("foo", 10); + d.Add("bar", 20); + + var d2 = this.Convert(d); + d2["foo"].Is(10); + d2["bar"].Is(20); + } } public class ImplNonGenericList : IList @@ -429,6 +476,38 @@ IEnumerator IEnumerable.GetEnumerator() } } + public class ImplGenericGenericReadOnlyCollection : IReadOnlyCollection + { + private readonly ICollection inner; + + public ImplGenericGenericReadOnlyCollection() + { + this.inner = new List(); + } + + public ImplGenericGenericReadOnlyCollection(IEnumerable items) + { + this.inner = new List(items); + } + + public IEnumerator GetEnumerator() + { + return inner.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)inner).GetEnumerator(); + } + + public int Count => inner.Count; + + public void Add(T item) + { + inner.Add(item); + } + } + public class ImplDictionary : IDictionary { private readonly Dictionary inner; @@ -503,4 +582,119 @@ IEnumerator IEnumerable.GetEnumerator() return ((IDictionary)inner).GetEnumerator(); } } + + public class ImplGenericReadonlyCollection : IReadOnlyCollection + { + private readonly ICollection inner; + + public ImplGenericReadonlyCollection() + { + this.inner = new List(); + } + + public ImplGenericReadonlyCollection(IEnumerable items) + { + this.inner = new List(items); + } + + public int Count => inner.Count; + + public IEnumerator GetEnumerator() + { + return inner.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return inner.GetEnumerator(); + } + + public void Add(int item) + { + inner.Add(item); + } + } + + public class ImplReadonlyDictionary : IReadOnlyDictionary + { + private readonly Dictionary inner; + + public ImplReadonlyDictionary() + { + this.inner = new Dictionary(); + } + + public ImplReadonlyDictionary(IDictionary inner) + { + this.inner = new Dictionary(inner); + } + + public IEnumerator> GetEnumerator() + { + return inner.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)inner).GetEnumerator(); + } + + public int Count => inner.Count; + + public bool ContainsKey(string key) + { + return inner.ContainsKey(key); + } + + public bool TryGetValue(string key, out int value) + { + return inner.TryGetValue(key, out value); + } + + public int this[string key] => inner[key]; + + public IEnumerable Keys => inner.Keys; + + public IEnumerable Values => inner.Values; + + public void Add(string key, int value) + { + inner.Add(key, value); + } + } + + public class ImplGenericEnumerable : IEnumerable + { + private readonly List inner = new List(); + + public ImplGenericEnumerable() + { + } + + public ImplGenericEnumerable(IEnumerable values) + { + } + + public ImplGenericEnumerable(IEnumerable values) + { + inner.AddRange(values); + } + + public ImplGenericEnumerable(ICollection values) + { + inner.AddRange(values); + } + + public IEnumerator GetEnumerator() + { + return inner.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)inner).GetEnumerator(); + } + + public void Add(int item) => inner.Add(item); + } } From bd0c26a24ad1f463ae2ff589f4764f89884e0d52 Mon Sep 17 00:00:00 2001 From: Alex Zavadsky Date: Mon, 22 Feb 2021 18:02:20 +0300 Subject: [PATCH 2/4] Add new classes to PublicAPI --- src/MessagePack/PublicAPI.Unshipped.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/MessagePack/PublicAPI.Unshipped.txt b/src/MessagePack/PublicAPI.Unshipped.txt index d31186945..453d06bca 100644 --- a/src/MessagePack/PublicAPI.Unshipped.txt +++ b/src/MessagePack/PublicAPI.Unshipped.txt @@ -14,6 +14,10 @@ MessagePack.Formatters.ForceTypelessFormatter MessagePack.Formatters.ForceTypelessFormatter.Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions options) -> T MessagePack.Formatters.ForceTypelessFormatter.ForceTypelessFormatter() -> void MessagePack.Formatters.ForceTypelessFormatter.Serialize(ref MessagePack.MessagePackWriter writer, T value, MessagePack.MessagePackSerializerOptions options) -> void +MessagePack.Formatters.GenericEnumerableFormatter +MessagePack.Formatters.GenericEnumerableFormatter.GenericEnumerableFormatter() -> void +MessagePack.Formatters.GenericReadOnlyDictionaryFormatter +MessagePack.Formatters.GenericReadOnlyDictionaryFormatter.GenericReadOnlyDictionaryFormatter() -> void MessagePack.Formatters.MemoryFormatter MessagePack.Formatters.MemoryFormatter.Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions options) -> System.Memory MessagePack.Formatters.MemoryFormatter.MemoryFormatter() -> void From 2e6fd46243ebd791354f3356f53c505a53581cbb Mon Sep 17 00:00:00 2001 From: Alex Zavadsky Date: Mon, 22 Mar 2021 10:49:44 +0300 Subject: [PATCH 3/4] Fix for possible incorrect constructor search. Allow serialization IDictionary<> descendants --- .../Resolvers/DynamicGenericResolver.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicGenericResolver.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicGenericResolver.cs index 7f9f44517..fbfb7de3a 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicGenericResolver.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicGenericResolver.cs @@ -313,12 +313,13 @@ internal static object GetFormatter(Type t) return CreateInstance(typeof(GenericDictionaryFormatter<,,>), new[] { keyType, valueType, t }); } - // generic readonly dictionary - var readOnlyDictionaryDef = ti.ImplementedInterfaces.FirstOrDefault(x => x.GetTypeInfo().IsConstructedGenericType() && x.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)); - if (readOnlyDictionaryDef != null) + // generic dictionary with collection ctor + var dictionaryInterfaceDef = ti.ImplementedInterfaces.FirstOrDefault(x => x.GetTypeInfo().IsConstructedGenericType() && + (x.GetGenericTypeDefinition() == typeof(IDictionary<,>) || x.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>))); + if (dictionaryInterfaceDef != null) { - Type keyType = readOnlyDictionaryDef.GenericTypeArguments[0]; - Type valueType = readOnlyDictionaryDef.GenericTypeArguments[1]; + Type keyType = dictionaryInterfaceDef.GenericTypeArguments[0]; + Type valueType = dictionaryInterfaceDef.GenericTypeArguments[1]; Type[] allowedParameterTypes = new Type[] { typeof(IDictionary<,>).MakeGenericType(keyType, valueType), @@ -329,7 +330,7 @@ internal static object GetFormatter(Type t) { ParameterInfo[] parameters = constructor.GetParameters(); if (parameters.Length == 1 && - allowedParameterTypes.Any(allowedType => allowedType.IsAssignableFrom(parameters[0].ParameterType))) + allowedParameterTypes.Any(allowedType => parameters[0].ParameterType.IsAssignableFrom(allowedType))) { return CreateInstance(typeof(GenericReadOnlyDictionaryFormatter<,,>), new[] { keyType, valueType, t }); } @@ -354,7 +355,7 @@ internal static object GetFormatter(Type t) foreach (var constructor in ti.DeclaredConstructors) { var parameters = constructor.GetParameters(); - if (parameters.Length == 1 && paramInterface.IsAssignableFrom(parameters[0].ParameterType)) + if (parameters.Length == 1 && parameters[0].ParameterType.IsAssignableFrom(paramInterface)) { return CreateInstance(typeof(GenericEnumerableFormatter<,>), new[] { elemType, t }); } From f5e5bf9acb04919e362e597c79a01219d6c7f877 Mon Sep 17 00:00:00 2001 From: Alex Zavadsky Date: Mon, 22 Mar 2021 11:13:25 +0300 Subject: [PATCH 4/4] Add new classes to PublicAPI --- src/MessagePack/net5.0/PublicAPI.Unshipped.txt | 4 ++++ src/MessagePack/netcoreapp2.1/PublicAPI.Unshipped.txt | 6 +++++- src/MessagePack/netcoreapp3.1/PublicAPI.Unshipped.txt | 6 +++++- src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt | 6 +++++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/MessagePack/net5.0/PublicAPI.Unshipped.txt b/src/MessagePack/net5.0/PublicAPI.Unshipped.txt index 1525d0ccb..4b94f00b2 100644 --- a/src/MessagePack/net5.0/PublicAPI.Unshipped.txt +++ b/src/MessagePack/net5.0/PublicAPI.Unshipped.txt @@ -20,3 +20,7 @@ static MessagePack.Nil.operator !=(MessagePack.Nil left, MessagePack.Nil right) static MessagePack.Nil.operator ==(MessagePack.Nil left, MessagePack.Nil right) -> bool static readonly MessagePack.Formatters.HalfFormatter.Instance -> MessagePack.Formatters.IMessagePackFormatter virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void +MessagePack.Formatters.GenericEnumerableFormatter +MessagePack.Formatters.GenericEnumerableFormatter.GenericEnumerableFormatter() -> void +MessagePack.Formatters.GenericReadOnlyDictionaryFormatter +MessagePack.Formatters.GenericReadOnlyDictionaryFormatter.GenericReadOnlyDictionaryFormatter() -> void \ No newline at end of file diff --git a/src/MessagePack/netcoreapp2.1/PublicAPI.Unshipped.txt b/src/MessagePack/netcoreapp2.1/PublicAPI.Unshipped.txt index 367e23039..f053d3cd8 100644 --- a/src/MessagePack/netcoreapp2.1/PublicAPI.Unshipped.txt +++ b/src/MessagePack/netcoreapp2.1/PublicAPI.Unshipped.txt @@ -13,4 +13,8 @@ MessagePack.SequencePool.SequencePool(int maxSize, System.Buffers.ArrayPool void static MessagePack.Nil.operator !=(MessagePack.Nil left, MessagePack.Nil right) -> bool static MessagePack.Nil.operator ==(MessagePack.Nil left, MessagePack.Nil right) -> bool -virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void \ No newline at end of file +virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void +MessagePack.Formatters.GenericEnumerableFormatter +MessagePack.Formatters.GenericEnumerableFormatter.GenericEnumerableFormatter() -> void +MessagePack.Formatters.GenericReadOnlyDictionaryFormatter +MessagePack.Formatters.GenericReadOnlyDictionaryFormatter.GenericReadOnlyDictionaryFormatter() -> void \ No newline at end of file diff --git a/src/MessagePack/netcoreapp3.1/PublicAPI.Unshipped.txt b/src/MessagePack/netcoreapp3.1/PublicAPI.Unshipped.txt index 5c253f312..73b4e7c06 100644 --- a/src/MessagePack/netcoreapp3.1/PublicAPI.Unshipped.txt +++ b/src/MessagePack/netcoreapp3.1/PublicAPI.Unshipped.txt @@ -15,4 +15,8 @@ MessagePack.SequencePool.SequencePool(int maxSize, System.Buffers.ArrayPool void static MessagePack.Nil.operator !=(MessagePack.Nil left, MessagePack.Nil right) -> bool static MessagePack.Nil.operator ==(MessagePack.Nil left, MessagePack.Nil right) -> bool -virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void \ No newline at end of file +virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void +MessagePack.Formatters.GenericEnumerableFormatter +MessagePack.Formatters.GenericEnumerableFormatter.GenericEnumerableFormatter() -> void +MessagePack.Formatters.GenericReadOnlyDictionaryFormatter +MessagePack.Formatters.GenericReadOnlyDictionaryFormatter.GenericReadOnlyDictionaryFormatter() -> void \ No newline at end of file diff --git a/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt b/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt index 5c253f312..73b4e7c06 100644 --- a/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt @@ -15,4 +15,8 @@ MessagePack.SequencePool.SequencePool(int maxSize, System.Buffers.ArrayPool void static MessagePack.Nil.operator !=(MessagePack.Nil left, MessagePack.Nil right) -> bool static MessagePack.Nil.operator ==(MessagePack.Nil left, MessagePack.Nil right) -> bool -virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void \ No newline at end of file +virtual MessagePack.MessagePackStreamReader.Dispose(bool disposing) -> void +MessagePack.Formatters.GenericEnumerableFormatter +MessagePack.Formatters.GenericEnumerableFormatter.GenericEnumerableFormatter() -> void +MessagePack.Formatters.GenericReadOnlyDictionaryFormatter +MessagePack.Formatters.GenericReadOnlyDictionaryFormatter.GenericReadOnlyDictionaryFormatter() -> void \ No newline at end of file