From e5759637f5032f880fdde770340a865824ad70f5 Mon Sep 17 00:00:00 2001 From: Remco Beurskens Date: Fri, 18 Aug 2017 16:30:40 +0200 Subject: [PATCH 1/4] Added support for deserializing properties/fields of collection interface types for collections that were already supported (Lists and Dictionaries). ISet and non-generic IDictionary are still unsupported. --- UnitTests/Tests.cs | 29 +++++++++++++++++++++++++++++ fastBinaryJSON/BJSON.cs | 8 +++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/UnitTests/Tests.cs b/UnitTests/Tests.cs index 922a632..bb35446 100644 --- a/UnitTests/Tests.cs +++ b/UnitTests/Tests.cs @@ -1165,6 +1165,35 @@ public static void CustomTypes() Assert.AreEqual(ip.ip, o.ip); } + public class CollectionDto + { + public IDictionary Dictionary = new Dictionary(); + public IDictionary Dictionary2 = new Dictionary(); + public ICollection Collection; + public IList OrderedCollection; + } + + [Test] + public static void CollectionInterfaces() + { + var dto = new CollectionDto + { + Dictionary = {{"test", 2}, {"test2", 3}}, + Dictionary2 = {{1, 2}, {2, 4}}, + Collection = new HashSet {"3", "5", "7"}, + OrderedCollection = new[] {DateTime.Now, DateTime.MaxValue} + }; + + var s = BJSON.ToBJSON(dto); + var o = BJSON.ToObject(s); + + Assert.IsNotNull(o); + CollectionAssert.AreEquivalent(dto.Dictionary, o.Dictionary); + CollectionAssert.AreEquivalent(dto.Dictionary2, o.Dictionary2); + CollectionAssert.AreEquivalent(dto.Collection, o.Collection); + CollectionAssert.AreEqual(dto.OrderedCollection, o.OrderedCollection); + } + //[Test] //public static void stringint() //{ diff --git a/fastBinaryJSON/BJSON.cs b/fastBinaryJSON/BJSON.cs index 374dabc..c4d2a8d 100644 --- a/fastBinaryJSON/BJSON.cs +++ b/fastBinaryJSON/BJSON.cs @@ -660,7 +660,7 @@ private object CreateGenericList(List data, Type pt, Type bt, Dictionary { if (pt != typeof(object)) { - IList col = (IList)Reflection.Instance.FastCreateInstance(pt); + IList col = (IList)Reflection.Instance.FastCreateInstance(pt.IsInterface ? typeof(List<>).MakeGenericType(bt??typeof(object)) : pt); // create an array of objects foreach (object ob in data) { @@ -686,12 +686,13 @@ private object CreateGenericList(List data, Type pt, Type bt, Dictionary private object CreateStringKeyDictionary(Dictionary reader, Type pt, Type[] types, Dictionary globalTypes) { - var col = (IDictionary)Reflection.Instance.FastCreateInstance(pt); Type arraytype = null; Type t2 = null; if (types != null) t2 = types[1]; + var col = (IDictionary)Reflection.Instance.FastCreateInstance(pt.IsInterface ? typeof(Dictionary<,>).MakeGenericType(typeof(string), t2??typeof(object)) : pt); + Type generictype = null; var ga = t2.GetGenericArguments(); if (ga.Length > 0) @@ -727,7 +728,6 @@ private object CreateStringKeyDictionary(Dictionary reader, Type private object CreateDictionary(List reader, Type pt, Type[] types, Dictionary globalTypes) { - IDictionary col = (IDictionary)Reflection.Instance.FastCreateInstance(pt); Type t1 = null; Type t2 = null; if (types != null) @@ -736,6 +736,8 @@ private object CreateDictionary(List reader, Type pt, Type[] types, Dict t2 = types[1]; } + IDictionary col = (IDictionary)Reflection.Instance.FastCreateInstance(pt.IsInterface && t1!=null ? typeof(Dictionary<,>).MakeGenericType(t1, t2??typeof(object)) : pt); + foreach (Dictionary values in reader) { object key = values["k"]; From b7ae021311b1948ec1c17733d3cc3b0d97ad11b8 Mon Sep 17 00:00:00 2001 From: Remco Beurskens Date: Mon, 21 Aug 2017 12:22:32 +0200 Subject: [PATCH 2/4] Now caching the results of Type.MakeGenericType() used when selecting a generic collection type that implements an interface (this can be used to for other generic types, too) --- fastBinaryJSON/BJSON.cs | 6 +++--- fastBinaryJSON/Reflection.cs | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/fastBinaryJSON/BJSON.cs b/fastBinaryJSON/BJSON.cs index c4d2a8d..71d1331 100644 --- a/fastBinaryJSON/BJSON.cs +++ b/fastBinaryJSON/BJSON.cs @@ -660,7 +660,7 @@ private object CreateGenericList(List data, Type pt, Type bt, Dictionary { if (pt != typeof(object)) { - IList col = (IList)Reflection.Instance.FastCreateInstance(pt.IsInterface ? typeof(List<>).MakeGenericType(bt??typeof(object)) : pt); + var col = (IList)Reflection.Instance.FastCreateInstance(pt.IsInterface ? Reflection.Instance.GetGenericType(typeof(List<>),bt ?? typeof(object)) : pt); // create an array of objects foreach (object ob in data) { @@ -691,7 +691,7 @@ private object CreateStringKeyDictionary(Dictionary reader, Type if (types != null) t2 = types[1]; - var col = (IDictionary)Reflection.Instance.FastCreateInstance(pt.IsInterface ? typeof(Dictionary<,>).MakeGenericType(typeof(string), t2??typeof(object)) : pt); + var col = (IDictionary)Reflection.Instance.FastCreateInstance(pt.IsInterface ? Reflection.Instance.GetGenericType(typeof(Dictionary<,>), typeof(string), t2??typeof(object)) : pt); Type generictype = null; var ga = t2.GetGenericArguments(); @@ -736,7 +736,7 @@ private object CreateDictionary(List reader, Type pt, Type[] types, Dict t2 = types[1]; } - IDictionary col = (IDictionary)Reflection.Instance.FastCreateInstance(pt.IsInterface && t1!=null ? typeof(Dictionary<,>).MakeGenericType(t1, t2??typeof(object)) : pt); + var col = (IDictionary)Reflection.Instance.FastCreateInstance(pt.IsInterface && t1!=null ? Reflection.Instance.GetGenericType(typeof(Dictionary<,>), t1, t2??typeof(object)) : pt); foreach (Dictionary values in reader) { diff --git a/fastBinaryJSON/Reflection.cs b/fastBinaryJSON/Reflection.cs index ae1cbd0..3e3ea16 100644 --- a/fastBinaryJSON/Reflection.cs +++ b/fastBinaryJSON/Reflection.cs @@ -85,8 +85,9 @@ private Reflection() private SafeDictionary _constrcache = new SafeDictionary(); private SafeDictionary _getterscache = new SafeDictionary(); private SafeDictionary> _propertycache = new SafeDictionary>(); - private SafeDictionary _genericTypes = new SafeDictionary(); + private SafeDictionary _genericArguments = new SafeDictionary(); private SafeDictionary _genericTypeDef = new SafeDictionary(); + private SafeDictionary, Type> _genericTypes = new SafeDictionary, Type>(); // If it is possible to use the System.ValueTuple type, the key would be (Type genericDef,Type param1,Type param2) #region bjson custom types internal UnicodeEncoding unicode = new UnicodeEncoding(); @@ -125,6 +126,30 @@ internal bool IsTypeRegistered(Type t) } #endregion + public Type GetGenericType(Type openType, Type genericArgument1, Type genericArgument2) + { + Type result; + var key = Tuple.Create(openType, genericArgument1, genericArgument2); + if (_genericTypes.TryGetValue(key, out result)) + return result; + System.Diagnostics.Debug.Assert(openType.GetGenericArguments().Length == 2); + result = openType.MakeGenericType(genericArgument1, genericArgument2); + _genericTypes.Add(key, result); + return result; + } + + public Type GetGenericType(Type openType, Type genericArgument1) + { + Type result; + var key = Tuple.Create(openType, genericArgument1, (Type)null); + if (_genericTypes.TryGetValue(key, out result)) + return result; + System.Diagnostics.Debug.Assert(openType.GetGenericArguments().Length == 1); + result = openType.MakeGenericType(genericArgument1); + _genericTypes.Add(key, result); + return result; + } + public Type GetGenericTypeDefinition(Type t) { Type tt = null; @@ -141,12 +166,12 @@ public Type GetGenericTypeDefinition(Type t) public Type[] GetGenericArguments(Type t) { Type[] tt = null; - if (_genericTypes.TryGetValue(t, out tt)) + if (_genericArguments.TryGetValue(t, out tt)) return tt; else { tt = t.GetGenericArguments(); - _genericTypes.Add(t, tt); + _genericArguments.Add(t, tt); return tt; } } @@ -605,8 +630,9 @@ internal void ClearReflectionCache() _constrcache = new SafeDictionary(); _getterscache = new SafeDictionary(); _propertycache = new SafeDictionary>(); - _genericTypes = new SafeDictionary(); + _genericArguments = new SafeDictionary(); _genericTypeDef = new SafeDictionary(); + _genericTypes = new SafeDictionary, Type>(); } } } From 6f3d2caa8d65133331dcd5c633552862e0d87dbd Mon Sep 17 00:00:00 2001 From: Remco Beurskens Date: Mon, 21 Aug 2017 17:09:15 +0200 Subject: [PATCH 3/4] A bit of optimization using a custom struct as dictionary key --- fastBinaryJSON/Reflection.cs | 85 ++++++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/fastBinaryJSON/Reflection.cs b/fastBinaryJSON/Reflection.cs index 3e3ea16..f652d81 100644 --- a/fastBinaryJSON/Reflection.cs +++ b/fastBinaryJSON/Reflection.cs @@ -87,7 +87,67 @@ private Reflection() private SafeDictionary> _propertycache = new SafeDictionary>(); private SafeDictionary _genericArguments = new SafeDictionary(); private SafeDictionary _genericTypeDef = new SafeDictionary(); - private SafeDictionary, Type> _genericTypes = new SafeDictionary, Type>(); // If it is possible to use the System.ValueTuple type, the key would be (Type genericDef,Type param1,Type param2) + private SafeDictionary _genericTypes = new SafeDictionary(); + + #region private implementation types + /// + /// Composite key to lookup cached constructed generic types. + /// + private struct GenericTypeKey : IEquatable + { + private readonly Type _genericDefinition; + private readonly Type[] _parameters; + + public Type[] GetParameters() + { + // Results of this method are not exposed, so we can omit making a copy to keep the instance immutable + return _parameters ?? Type.EmptyTypes; + } + + public GenericTypeKey(Type genericDefinition, params Type[] parameters) + { + _genericDefinition = genericDefinition; + // input comes from another class, just make a copy to be sure the array's elements do not change after creation. + _parameters = new Type[parameters.Length]; + Array.Copy(parameters, _parameters, parameters.Length); + } + + public bool Equals(GenericTypeKey other) + { + if (_genericDefinition != other._genericDefinition || (_parameters?.Length ?? -1) != (other._parameters?.Length ?? -1)) + return false; + if (_parameters == null && other._parameters == null) return true; // if one is null the other is too at this point, so checking only one would be enough + for (int i = 0; i < _parameters.Length; i++) + { + if (_parameters[i] != other._parameters[i]) + return false; + } + return true; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is GenericTypeKey && Equals((GenericTypeKey) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (_genericDefinition != null ? _genericDefinition.GetHashCode() : 0); + if (_parameters == null) return (hashCode * 397); + hashCode = (hashCode * 397) ^ _parameters.Length.GetHashCode(); + foreach (Type t in _parameters) + { + hashCode = (hashCode * 397) ^ (t != null ? t.GetHashCode() : 0); + } + return hashCode; + } + } + + } + #endregion #region bjson custom types internal UnicodeEncoding unicode = new UnicodeEncoding(); @@ -126,26 +186,15 @@ internal bool IsTypeRegistered(Type t) } #endregion - public Type GetGenericType(Type openType, Type genericArgument1, Type genericArgument2) - { - Type result; - var key = Tuple.Create(openType, genericArgument1, genericArgument2); - if (_genericTypes.TryGetValue(key, out result)) - return result; - System.Diagnostics.Debug.Assert(openType.GetGenericArguments().Length == 2); - result = openType.MakeGenericType(genericArgument1, genericArgument2); - _genericTypes.Add(key, result); - return result; - } - - public Type GetGenericType(Type openType, Type genericArgument1) + public Type GetGenericType(Type openType, params Type[] genericArguments) { Type result; - var key = Tuple.Create(openType, genericArgument1, (Type)null); + var key = new GenericTypeKey(openType, genericArguments); if (_genericTypes.TryGetValue(key, out result)) return result; - System.Diagnostics.Debug.Assert(openType.GetGenericArguments().Length == 1); - result = openType.MakeGenericType(genericArgument1); + var args = key.GetParameters(); + System.Diagnostics.Debug.Assert(openType.GetGenericArguments().Length == args.Length); + result = openType.MakeGenericType(args); _genericTypes.Add(key, result); return result; } @@ -632,7 +681,7 @@ internal void ClearReflectionCache() _propertycache = new SafeDictionary>(); _genericArguments = new SafeDictionary(); _genericTypeDef = new SafeDictionary(); - _genericTypes = new SafeDictionary, Type>(); + _genericTypes = new SafeDictionary(); } } } From e01548716d7c260940034ee5eba815262b5b819f Mon Sep 17 00:00:00 2001 From: Remco Beurskens Date: Tue, 17 Oct 2017 15:37:01 +0200 Subject: [PATCH 4/4] Added some flexibility for collections (and collection interfaces) that are not explicitly supported. --- UnitTests/Tests.cs | 10 +++++-- fastBinaryJSON/BJSON.cs | 58 +++++++++++++++++++++++++++++++++--- fastBinaryJSON/Reflection.cs | 25 ++++++++++++++++ 3 files changed, 86 insertions(+), 7 deletions(-) diff --git a/UnitTests/Tests.cs b/UnitTests/Tests.cs index bb35446..b18ef37 100644 --- a/UnitTests/Tests.cs +++ b/UnitTests/Tests.cs @@ -1169,7 +1169,8 @@ public class CollectionDto { public IDictionary Dictionary = new Dictionary(); public IDictionary Dictionary2 = new Dictionary(); - public ICollection Collection; + public ISet Set; + public ICollection Collection; public IList OrderedCollection; } @@ -1180,7 +1181,8 @@ public static void CollectionInterfaces() { Dictionary = {{"test", 2}, {"test2", 3}}, Dictionary2 = {{1, 2}, {2, 4}}, - Collection = new HashSet {"3", "5", "7"}, + Set = new SortedSet {"5", "3", "7"}, + Collection = new[] {"2", "8", "5"}, OrderedCollection = new[] {DateTime.Now, DateTime.MaxValue} }; @@ -1190,7 +1192,9 @@ public static void CollectionInterfaces() Assert.IsNotNull(o); CollectionAssert.AreEquivalent(dto.Dictionary, o.Dictionary); CollectionAssert.AreEquivalent(dto.Dictionary2, o.Dictionary2); - CollectionAssert.AreEquivalent(dto.Collection, o.Collection); + CollectionAssert.AreEquivalent(dto.Set, o.Set); + Assert.IsTrue(o.Set.SetEquals(dto.Set)); + CollectionAssert.AreEqual(dto.Collection, o.Collection); CollectionAssert.AreEqual(dto.OrderedCollection, o.OrderedCollection); } diff --git a/fastBinaryJSON/BJSON.cs b/fastBinaryJSON/BJSON.cs index 71d1331..5d050d5 100644 --- a/fastBinaryJSON/BJSON.cs +++ b/fastBinaryJSON/BJSON.cs @@ -6,6 +6,7 @@ #endif using System.IO; using System.Collections.Specialized; +using System.Linq.Expressions; namespace fastBinaryJSON { @@ -183,7 +184,7 @@ public static byte[] ToBJSON(object obj, BJSONParameters param) return new BJSONSerializer(param).ConvertToBJSON(obj); } /// - /// Fill a given object with the binary json represenation + /// Fill a given object with the binary json representation /// /// /// @@ -551,8 +552,8 @@ private object ParseDictionary(Dictionary d, Dictionary)v, pi.pt, pi.bt, globaltypes); + if (pi.IsGenericType && !pi.IsValueType) + oset = CreateGenericCollection((List)v, pi.pt, pi.bt, globaltypes); else if ((pi.IsClass || pi.IsStruct || pi.IsInterface) && v is Dictionary) { var oo = (Dictionary)v; @@ -656,7 +657,7 @@ private object CreateArray(List data, Type pt, Type bt, Dictionary data, Type pt, Type bt, Dictionary globalTypes) + private object CreateGenericList(IEnumerable data, Type pt, Type bt, Dictionary globalTypes) { if (pt != typeof(object)) { @@ -684,6 +685,55 @@ private object CreateGenericList(List data, Type pt, Type bt, Dictionary return data; } + private object CreateGenericCollection(IEnumerable data, Type pt, Type bt, Dictionary globalTypes) + { + if (pt != typeof(object)) + { + var objType = pt; + if (pt.IsInterface) + { +#if !NET35 + // ISet is also a collection interface, but is not implemented by any List or Dictionary, so use HashSet as default implementation + objType = new[] {typeof(List<>), typeof(HashSet<>)} + .Select(t => Reflection.Instance.GetGenericType(t, bt ?? typeof(object))) + .FirstOrDefault(pt.IsAssignableFrom); +#else + objType = Reflection.Instance.GetGenericType(typeof(List<>), bt ?? typeof(object)); + if (!pt.IsAssignableFrom(objType)) + objType = null; +#endif + if (objType == null) + throw new NotSupportedException("Unable to resolve an implementation type for " + pt.FullName); + } + if (typeof(IList).IsAssignableFrom(objType)) + return CreateGenericList(data, objType, bt, globalTypes); + // create an array of objects + var add = Reflection.Instance.GetCollectionAdder(objType); + if (add == null) throw new NotSupportedException("Unable to find an Add() method for collection " + objType.FullName); + var col = Reflection.Instance.FastCreateInstance(objType); + + foreach (object ob in data) + { + if (ob is IDictionary) + add(col, ParseDictionary((Dictionary) ob, globalTypes, bt, null)); + + else if (ob is List) + { + if (bt.IsGenericType) + add(col, (List) ob); + else + add(col, ((List) ob).ToArray()); + } + else if (ob is typedarray) + add(col, ((typedarray) ob).data.ToArray()); + else + add(col, ob); + } + return col; + } + return data; + } + private object CreateStringKeyDictionary(Dictionary reader, Type pt, Type[] types, Dictionary globalTypes) { Type arraytype = null; diff --git a/fastBinaryJSON/Reflection.cs b/fastBinaryJSON/Reflection.cs index f652d81..91a2ff4 100644 --- a/fastBinaryJSON/Reflection.cs +++ b/fastBinaryJSON/Reflection.cs @@ -8,6 +8,7 @@ using System.Data; #endif using System.Collections.Specialized; +using System.Linq.Expressions; namespace fastBinaryJSON { @@ -79,6 +80,7 @@ private Reflection() internal delegate object GenericSetter(object target, object value); internal delegate object GenericGetter(object obj); private delegate object CreateObject(); + internal delegate void AddItemToCollection(object collection, object item); private SafeDictionary _tyname = new SafeDictionary(); private SafeDictionary _typecache = new SafeDictionary(); @@ -88,6 +90,7 @@ private Reflection() private SafeDictionary _genericArguments = new SafeDictionary(); private SafeDictionary _genericTypeDef = new SafeDictionary(); private SafeDictionary _genericTypes = new SafeDictionary(); + private SafeDictionary _genericCollectionAdders = new SafeDictionary(); #region private implementation types /// @@ -186,6 +189,28 @@ internal bool IsTypeRegistered(Type t) } #endregion + public AddItemToCollection GetCollectionAdder(Type collectionType) + { + var collType = typeof(IList).IsAssignableFrom(collectionType) ? typeof(IList) : collectionType; // Delegate the call to IList.Add(object) for all types that implement it. + if (_genericCollectionAdders.TryGetValue(collType, out var adder)) + return adder; + + var itemType = collType.IsGenericType ? GetGenericArguments(collType).SingleOrDefault() ?? typeof(object) : typeof(object); + var mi = collType.GetMethod(nameof(ICollection.Add), new[] {itemType}); + if (mi != null) + { + var inst = Expression.Parameter(typeof(object), "collection"); + var item = Expression.Parameter(typeof(object), "item"); + var itemArgument = itemType == item.Type ? item : Expression.Convert(item, itemType) as Expression; // Cast the item only if itemType is not object + adder = Expression.Lambda( + Expression.Call(Expression.Convert(inst, collType), mi, itemArgument), + inst, + item).Compile(); + } + _genericCollectionAdders[collType] = adder; + return adder; + } + public Type GetGenericType(Type openType, params Type[] genericArguments) { Type result;