diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/CollectionFormatter.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/CollectionFormatter.cs index 2255cd707..c7cc82729 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 dc54e6268..787fcc10e 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicGenericResolver.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicGenericResolver.cs @@ -316,6 +316,30 @@ internal static object GetFormatter(Type t) return CreateInstance(typeof(GenericDictionaryFormatter<,,>), new[] { keyType, valueType, t }); } + // 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 = dictionaryInterfaceDef.GenericTypeArguments[0]; + Type valueType = dictionaryInterfaceDef.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 => parameters[0].ParameterType.IsAssignableFrom(allowedType))) + { + 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)) @@ -323,6 +347,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 && parameters[0].ParameterType.IsAssignableFrom(paramInterface)) + { + 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); + } } diff --git a/src/MessagePack/net5.0/PublicAPI.Unshipped.txt b/src/MessagePack/net5.0/PublicAPI.Unshipped.txt index 519b17eec..1447ef0d5 100644 --- a/src/MessagePack/net5.0/PublicAPI.Unshipped.txt +++ b/src/MessagePack/net5.0/PublicAPI.Unshipped.txt @@ -22,3 +22,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