-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EnumerableSerializeFactory fixes #81
Changes from 2 commits
301b4cf
9c90456
08d6d5e
109e2b9
f345f93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Linq.Expressions; | ||
using Hyperion.Extensions; | ||
using Hyperion.ValueSerializers; | ||
|
||
|
@@ -22,12 +23,21 @@ public class EnumerableSerializerFactory : ValueSerializerFactory | |
{ | ||
public override bool CanSerialize(Serializer serializer, Type type) | ||
{ | ||
//TODO: check for constructor with IEnumerable<T> param | ||
// Stack<T> has IEnumerable<T> constructor, but reverses order of the stack, so can't be used. | ||
if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Stack<>)) | ||
return false; | ||
|
||
var countProperty = type.GetTypeInfo().GetProperty("Count"); | ||
if (countProperty == null || countProperty.PropertyType != typeof(int)) | ||
return false; | ||
|
||
var hasEnumerableConstructor = GetEnumerableConstructor(type) != null; | ||
if (hasEnumerableConstructor) | ||
return true; | ||
|
||
if (!HasParameterlessConstructor(type)) | ||
return false; | ||
|
||
if (!type.GetTypeInfo().GetMethods().Any(IsAddMethod)) | ||
return false; | ||
|
||
|
@@ -41,18 +51,27 @@ public override bool CanSerialize(Serializer serializer, Type type) | |
return false; | ||
} | ||
|
||
private static readonly BindingFlags allInstanceBindings = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; | ||
|
||
private static bool IsAddMethod(MethodInfo methodInfo) => | ||
(methodInfo.Name == "AddRange" || methodInfo.Name == "Add") | ||
&& (methodInfo.ReturnType == typeof(void) || methodInfo.ReturnType == typeof(bool)) // sets return bool on Add | ||
&& !methodInfo.IsStatic | ||
&& HasValidParameters(methodInfo); | ||
|
||
private static bool HasValidParameters(MethodInfo methodInfo) | ||
{ | ||
var parameters = methodInfo.GetParameters(); | ||
return parameters.Length == 1; | ||
} | ||
|
||
private static bool HasParameterlessConstructor(Type type) | ||
{ | ||
return type.GetTypeInfo() | ||
.GetConstructors(allInstanceBindings) | ||
.Any(ctor => !ctor.GetParameters().Any()); | ||
} | ||
|
||
public override bool CanDeserialize(Serializer serializer, Type type) | ||
{ | ||
return CanSerialize(serializer, type); | ||
|
@@ -68,58 +87,119 @@ private static Type GetEnumerableType(Type type) | |
.FirstOrDefault(); | ||
} | ||
|
||
private static ConstructorInfo GetEnumerableConstructor(Type type) | ||
{ | ||
BindingFlags bindings = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; | ||
var enumerableType = GetEnumerableType(type); | ||
var iEnumerableType = typeof(IEnumerable<>).MakeGenericType(enumerableType); | ||
return enumerableType != null | ||
? type.GetTypeInfo() | ||
.GetConstructors(bindings) | ||
.Where(ctor => HasSingleParameterOfType(ctor, iEnumerableType)) | ||
.FirstOrDefault() | ||
: null; | ||
} | ||
|
||
private static bool HasSingleParameterOfType(MethodBase methodInfo, Type type) | ||
{ | ||
var parameters = methodInfo.GetParameters(); | ||
return parameters.Length == 1 && parameters[0].ParameterType == type; | ||
} | ||
|
||
private static Func<object, object> CompileCtorToDelegate(ConstructorInfo ctor, Type argType) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to check which is faster: using lambda There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 Is there any standards for benchmarking I should follow, e.g. which benchmarking library to use, what data to test on, etc.? |
||
{ | ||
var arg = Expression.Parameter(typeof(object)); | ||
var castArg = Expression.Convert(arg, argType); | ||
var call = Expression.New(ctor, new Expression[] { castArg }); | ||
var castRes = Expression.Convert(call, typeof(object)); | ||
var lambda = Expression.Lambda<Func<object, object>>(castRes, arg); | ||
var compiled = lambda.Compile(); | ||
return compiled; | ||
} | ||
|
||
private static Action<object, object> CompileMethodToDelegate(MethodInfo method, Type instanceType, Type argType) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as with calling |
||
{ | ||
var instance = Expression.Parameter(typeof(object)); | ||
var arg = Expression.Parameter(typeof(object)); | ||
var castInstance = Expression.Convert(instance, instanceType); | ||
var castArg = Expression.Convert(arg, argType); | ||
var call = Expression.Call(castInstance, method, new Expression[] { castArg }); | ||
var lambda = Expression.Lambda<Action<object, object>>(call, instance, arg); | ||
var compiled = lambda.Compile(); | ||
return compiled; | ||
} | ||
|
||
public override ValueSerializer BuildSerializer(Serializer serializer, Type type, | ||
ConcurrentDictionary<Type, ValueSerializer> typeMapping) | ||
{ | ||
var x = new ObjectSerializer(type); | ||
typeMapping.TryAdd(type, x); | ||
|
||
var preserveObjectReferences = serializer.Options.PreserveObjectReferences; | ||
|
||
var elementType = GetEnumerableType(type) ?? typeof(object); | ||
var elementSerializer = serializer.GetSerializerByType(elementType); | ||
|
||
var countProperty = type.GetTypeInfo().GetProperty("Count"); | ||
var addRange = type.GetTypeInfo().GetMethod("AddRange"); | ||
var add = type.GetTypeInfo().GetMethod("Add"); | ||
|
||
Func<object, int> countGetter = o => (int)countProperty.GetValue(o); | ||
|
||
var enumerableConstructor = GetEnumerableConstructor(type); | ||
var addRangeMethod = type.GetTypeInfo().GetMethod("AddRange"); | ||
var addMethod = type.GetTypeInfo().GetMethod("Add"); | ||
|
||
ObjectReader reader = (stream, session) => | ||
Func<object, int> countGetter = o => (int)countProperty.GetValue(o); | ||
ObjectReader reader = null; | ||
if (enumerableConstructor != null) | ||
{ | ||
var instance = Activator.CreateInstance(type); | ||
if (preserveObjectReferences) | ||
var construct = CompileCtorToDelegate(enumerableConstructor, elementType.MakeArrayType()); | ||
reader = (stream, session) => | ||
{ | ||
session.TrackDeserializedObject(instance); | ||
} | ||
|
||
var count = stream.ReadInt32(session); | ||
|
||
if (addRange != null) | ||
var count = stream.ReadInt32(session); | ||
var items = Array.CreateInstance(elementType, count); | ||
for (var i = 0; i < count; i++) | ||
{ | ||
var value = stream.ReadObject(session); | ||
items.SetValue(value, i); | ||
} | ||
var instance = construct(items); | ||
if (preserveObjectReferences) | ||
{ | ||
session.TrackDeserializedObject(instance); | ||
} | ||
return instance; | ||
}; | ||
} | ||
else if (addRangeMethod != null) | ||
{ | ||
var addRange = CompileMethodToDelegate(addRangeMethod, type, elementType.MakeArrayType()); | ||
reader = (stream, session) => | ||
{ | ||
var instance = Activator.CreateInstance(type, true); | ||
var count = stream.ReadInt32(session); | ||
var items = Array.CreateInstance(elementType, count); | ||
for (var i = 0; i < count; i++) | ||
{ | ||
var value = stream.ReadObject(session); | ||
items.SetValue(value, i); | ||
} | ||
//HACK: this needs to be fixed, codegenerated or whatever | ||
|
||
addRange.Invoke(instance, new object[] { items }); | ||
addRange(instance, items); | ||
return instance; | ||
} | ||
if (add != null) | ||
}; | ||
} | ||
else if (addMethod != null) | ||
{ | ||
var add = CompileMethodToDelegate(addMethod, type, elementType); | ||
reader = (stream, session) => | ||
{ | ||
var instance = Activator.CreateInstance(type, true); | ||
var count = stream.ReadInt32(session); | ||
for (var i = 0; i < count; i++) | ||
{ | ||
var value = stream.ReadObject(session); | ||
add.Invoke(instance, new[] { value }); | ||
add(instance, value); | ||
} | ||
} | ||
|
||
|
||
return instance; | ||
}; | ||
return instance; | ||
}; | ||
} | ||
|
||
ObjectWriter writer = (stream, o, session) => | ||
{ | ||
|
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a
BindingFlagsEx.All
member, which does exactly that ;)