From f341ce134504d9805de6abe06cbf4c46bf7b989a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2020 15:17:28 +0000 Subject: [PATCH 1/4] Bump Microsoft.NET.Test.Sdk from 16.4.0 to 16.5.0 (#155) --- src/common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common.props b/src/common.props index 10118f1e..18e5c1fd 100644 --- a/src/common.props +++ b/src/common.props @@ -12,7 +12,7 @@ Added `ArrayList` and non-generic `IEnumerable` support. 2.4.1 - 16.4.0 + 16.5.0 1.2.2 \ No newline at end of file From a66b88e25c71afd9f92597ab4ba2b438e3ab69d1 Mon Sep 17 00:00:00 2001 From: Igor Fedchenko Date: Thu, 6 Feb 2020 18:18:29 +0300 Subject: [PATCH 2/4] IDictionary support (#156) * Added failing spec * Added IDictionary<,> serialization support --- .../GenericDictionarySerializerTests.cs | 189 ++++++++++++++++++ .../DictionarySerializerFactory.cs | 12 +- 2 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 src/Hyperion.Tests/GenericDictionarySerializerTests.cs diff --git a/src/Hyperion.Tests/GenericDictionarySerializerTests.cs b/src/Hyperion.Tests/GenericDictionarySerializerTests.cs new file mode 100644 index 00000000..5edc5468 --- /dev/null +++ b/src/Hyperion.Tests/GenericDictionarySerializerTests.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using FluentAssertions; +using Xunit; + +namespace Hyperion.Tests +{ + public class GenericDictionarySerializerTests : TestBase + { + [Fact] + public void CanSerializeDictionary() + { + var customDict = new CustomDictionary(new Dictionary() + { + ["key"] = 1 + }); + SerializeAndAssertEquivalent(customDict); + } + + private void SerializeAndAssertEquivalent(T expected) + { + Serialize(expected); + Reset(); + var res = Deserialize(); + res.Should().BeEquivalentTo(expected); + AssertMemoryStreamConsumed(); + } + + /// + /// Just a custom class wrapper for another + /// + class CustomDictionary : IDictionary, IDictionary + { + private readonly IDictionary _dictGeneric; + private readonly IDictionary _dict; + + /// + /// For serialization + /// + public CustomDictionary() : this(new Dictionary()) + { + } + + public CustomDictionary(Dictionary dict) + { + _dictGeneric = dict; + _dict = dict; + } + + /// + public bool Contains(object key) + { + return _dict.Contains(key); + } + + /// + IDictionaryEnumerator IDictionary.GetEnumerator() + { + return _dict.GetEnumerator(); + } + + /// + public void Remove(object key) + { + _dict.Remove(key); + } + + /// + public bool IsFixedSize => _dict.IsFixedSize; + + /// + public IEnumerator> GetEnumerator() + { + return _dictGeneric.GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable) _dictGeneric).GetEnumerator(); + } + + /// + public void Add(KeyValuePair item) + { + _dictGeneric.Add(item); + } + + /// + public void Add(object key, object? value) + { + _dict.Add(key, value); + } + + /// + public void Clear() + { + _dictGeneric.Clear(); + } + + /// + public bool Contains(KeyValuePair item) + { + return _dictGeneric.Contains(item); + } + + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + _dictGeneric.CopyTo(array, arrayIndex); + } + + /// + public bool Remove(KeyValuePair item) + { + return _dictGeneric.Remove(item); + } + + /// + public void CopyTo(Array array, int index) + { + _dict.CopyTo(array, index); + } + + /// + public int Count => _dictGeneric.Count; + + /// + public bool IsSynchronized => _dict.IsSynchronized; + + /// + public object SyncRoot => _dict.SyncRoot; + + /// + public bool IsReadOnly => _dictGeneric.IsReadOnly; + + /// + public object? this[object key] + { + get => _dict[key]; + set => _dict[key] = value; + } + + /// + public void Add(TKey key, TValue value) + { + _dictGeneric.Add(key, value); + } + + /// + public bool ContainsKey(TKey key) + { + return _dictGeneric.ContainsKey(key); + } + + /// + public bool Remove(TKey key) + { + return _dictGeneric.Remove(key); + } + + /// + public bool TryGetValue(TKey key, out TValue value) + { + return _dictGeneric.TryGetValue(key, out value); + } + + /// + public TValue this[TKey key] + { + get => _dictGeneric[key]; + set => _dictGeneric[key] = value; + } + + /// + public ICollection Keys => _dictGeneric.Keys; + + /// + ICollection IDictionary.Values => _dict.Values; + + /// + ICollection IDictionary.Keys => _dict.Keys; + + /// + public ICollection Values => _dictGeneric.Values; + } + } +} \ No newline at end of file diff --git a/src/Hyperion/SerializerFactories/DictionarySerializerFactory.cs b/src/Hyperion/SerializerFactories/DictionarySerializerFactory.cs index 62bf47e0..6dbab34e 100644 --- a/src/Hyperion/SerializerFactories/DictionarySerializerFactory.cs +++ b/src/Hyperion/SerializerFactories/DictionarySerializerFactory.cs @@ -43,22 +43,18 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type ObjectReader reader = (stream, session) => { - throw new NotSupportedException("Generic IDictionary are not yet supported"); -#pragma warning disable CS0162 // Unreachable code detected - var instance = Activator.CreateInstance(type); -#pragma warning restore CS0162 // Unreachable code detected + var instance = Activator.CreateInstance(type) as IDictionary; if (preserveObjectReferences) { session.TrackDeserializedObject(instance); } var count = stream.ReadInt32(session); - var entries = new DictionaryEntry[count]; for (var i = 0; i < count; i++) { var entry = (DictionaryEntry) stream.ReadObject(session); - entries[i] = entry; + instance.Add(entry.Key, entry.Value); } - //TODO: populate dictionary + return instance; }; @@ -70,7 +66,7 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type } var dict = obj as IDictionary; // ReSharper disable once PossibleNullReferenceException - Int32Serializer.WriteValueImpl(stream,dict.Count,session); + Int32Serializer.WriteValueImpl(stream, dict.Count, session); foreach (var item in dict) { stream.WriteObject(item, typeof (DictionaryEntry), elementSerializer, From 01ed0c81d37ea19a210048ff262c788f2bdc2d63 Mon Sep 17 00:00:00 2001 From: Igor Fedchenko Date: Fri, 7 Feb 2020 21:39:52 +0300 Subject: [PATCH 3/4] Update IDictionary serializer to not use IDictionary assumption (#158) * Updated spec to serialize object without IDictionary * Updated implementation to not use IDictionary assumption --- .../GenericDictionarySerializerTests.cs | 56 +------------------ .../DictionarySerializerFactory.cs | 49 +++++++++++++--- 2 files changed, 41 insertions(+), 64 deletions(-) diff --git a/src/Hyperion.Tests/GenericDictionarySerializerTests.cs b/src/Hyperion.Tests/GenericDictionarySerializerTests.cs index 5edc5468..8bea41b7 100644 --- a/src/Hyperion.Tests/GenericDictionarySerializerTests.cs +++ b/src/Hyperion.Tests/GenericDictionarySerializerTests.cs @@ -30,10 +30,9 @@ private void SerializeAndAssertEquivalent(T expected) /// /// Just a custom class wrapper for another /// - class CustomDictionary : IDictionary, IDictionary + class CustomDictionary : IDictionary { private readonly IDictionary _dictGeneric; - private readonly IDictionary _dict; /// /// For serialization @@ -45,30 +44,8 @@ public CustomDictionary() : this(new Dictionary()) public CustomDictionary(Dictionary dict) { _dictGeneric = dict; - _dict = dict; } - /// - public bool Contains(object key) - { - return _dict.Contains(key); - } - - /// - IDictionaryEnumerator IDictionary.GetEnumerator() - { - return _dict.GetEnumerator(); - } - - /// - public void Remove(object key) - { - _dict.Remove(key); - } - - /// - public bool IsFixedSize => _dict.IsFixedSize; - /// public IEnumerator> GetEnumerator() { @@ -87,12 +64,6 @@ public void Add(KeyValuePair item) _dictGeneric.Add(item); } - /// - public void Add(object key, object? value) - { - _dict.Add(key, value); - } - /// public void Clear() { @@ -117,31 +88,12 @@ public bool Remove(KeyValuePair item) return _dictGeneric.Remove(item); } - /// - public void CopyTo(Array array, int index) - { - _dict.CopyTo(array, index); - } - /// public int Count => _dictGeneric.Count; - /// - public bool IsSynchronized => _dict.IsSynchronized; - - /// - public object SyncRoot => _dict.SyncRoot; - /// public bool IsReadOnly => _dictGeneric.IsReadOnly; - /// - public object? this[object key] - { - get => _dict[key]; - set => _dict[key] = value; - } - /// public void Add(TKey key, TValue value) { @@ -176,12 +128,6 @@ public TValue this[TKey key] /// public ICollection Keys => _dictGeneric.Keys; - /// - ICollection IDictionary.Values => _dict.Values; - - /// - ICollection IDictionary.Keys => _dict.Keys; - /// public ICollection Values => _dictGeneric.Values; } diff --git a/src/Hyperion/SerializerFactories/DictionarySerializerFactory.cs b/src/Hyperion/SerializerFactories/DictionarySerializerFactory.cs index 6dbab34e..3e44faec 100644 --- a/src/Hyperion/SerializerFactories/DictionarySerializerFactory.cs +++ b/src/Hyperion/SerializerFactories/DictionarySerializerFactory.cs @@ -39,11 +39,12 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type var preserveObjectReferences = serializer.Options.PreserveObjectReferences; var ser = new ObjectSerializer(type); typeMapping.TryAdd(type, ser); - var elementSerializer = serializer.GetSerializerByType(typeof (DictionaryEntry)); + var dictionaryTypes = GetKeyValuePairType(type); + var elementSerializer = serializer.GetSerializerByType(dictionaryTypes.KeyValuePairType); ObjectReader reader = (stream, session) => { - var instance = Activator.CreateInstance(type) as IDictionary; + var instance = Activator.CreateInstance(type); // IDictionary if (preserveObjectReferences) { session.TrackDeserializedObject(instance); @@ -51,8 +52,16 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type var count = stream.ReadInt32(session); for (var i = 0; i < count; i++) { - var entry = (DictionaryEntry) stream.ReadObject(session); - instance.Add(entry.Key, entry.Value); + var entry = stream.ReadObject(session); // KeyValuePair + + // Get entry.Key and entry.Value + var key = dictionaryTypes.KeyValuePairType.GetProperty(nameof(KeyValuePair.Key)).GetValue(entry, null); + var value = dictionaryTypes.KeyValuePairType.GetProperty(nameof(KeyValuePair.Value)).GetValue(entry, null); + + // Same as: instance.Add(key, value) + dictionaryTypes.DictionaryInterfaceType + .GetMethod(nameof(IDictionary.Add), new []{ dictionaryTypes.KeyType, dictionaryTypes.ValueType }) + .Invoke(instance, new [] { key, value }); } return instance; @@ -64,19 +73,41 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type { session.TrackSerializedObject(obj); } - var dict = obj as IDictionary; + + var dict = obj as IEnumerable; // IDictionary is IEnumerable> + var count = dict.Cast().Count(); // ReSharper disable once PossibleNullReferenceException - Int32Serializer.WriteValueImpl(stream, dict.Count, session); + Int32Serializer.WriteValueImpl(stream, count, session); foreach (var item in dict) { - stream.WriteObject(item, typeof (DictionaryEntry), elementSerializer, - serializer.Options.PreserveObjectReferences, session); - // elementSerializer.WriteValue(stream,item,session); + stream.WriteObject(item, dictionaryTypes.KeyValuePairType, elementSerializer, serializer.Options.PreserveObjectReferences, session); } }; ser.Initialize(reader, writer); return ser; } + + private GenericDictionaryTypes GetKeyValuePairType(Type dictImplementationType) + { + var dictInterface = dictImplementationType.GetInterfaces().First(i => i.GetGenericTypeDefinition() == typeof (IDictionary<,>)); + var keyType = dictInterface.GetGenericArguments()[0]; + var valueType = dictInterface.GetGenericArguments()[1]; + return new GenericDictionaryTypes() + { + KeyType = keyType, + ValueType = valueType, + KeyValuePairType = typeof(KeyValuePair<,>).MakeGenericType(keyType, valueType), + DictionaryInterfaceType = typeof(IDictionary<,>).MakeGenericType(keyType, valueType) + }; + } + + class GenericDictionaryTypes + { + public Type KeyType { get; set; } + public Type ValueType { get; set; } + public Type KeyValuePairType { get; set; } + public Type DictionaryInterfaceType { get; set; } + } } } \ No newline at end of file From f24687b5febc5c15430f7f55d85eb673f2c1ba61 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Sun, 9 Feb 2020 18:00:01 -0600 Subject: [PATCH 4/4] added v0.9.13 release notes (#159) --- RELEASE_NOTES.md | 5 ++--- src/common.props | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1bb23226..f4517b18 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,4 +1,3 @@ -### 0.9.12 January 20 2020 #### +### 0.9.13 February 09 2020 #### -* Added version tolerance during deserialization. -* Added `ArrayList` and non-generic `IEnumerable` support. \ No newline at end of file +* [Added support for serializing and deserializing `IDictionary`](https://github.com/akkadotnet/Hyperion/pull/156) \ No newline at end of file diff --git a/src/common.props b/src/common.props index 18e5c1fd..5235b284 100644 --- a/src/common.props +++ b/src/common.props @@ -2,9 +2,8 @@ Copyright © 2016-2017 Akka.NET Team Akka.NET Team - 0.9.12 - Added version tolerance during deserialization. -Added `ArrayList` and non-generic `IEnumerable` support. + 0.9.13 + [Added support for serializing and deserializing `IDictionary<TKey, TValue>`](https://github.com/akkadotnet/Hyperion/pull/156) http://getakka.net/images/akkalogo.png https://github.com/akkadotnet/Hyperion https://github.com/akkadotnet/Hyperion/blob/master/LICENSE