Skip to content
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

Merged
merged 5 commits into from
Jan 8, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 106 additions & 26 deletions Hyperion/SerializerFactories/EnumerableSerializerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
using Hyperion.Extensions;
using Hyperion.ValueSerializers;

Expand All @@ -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;

Expand All @@ -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;
Copy link
Contributor

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 ;)


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);
Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to check which is faster: using lambda Func<,> which call the constructor, or calling ConstructorInfo.Invoke directly and use that one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as with calling ConstructorInfo.Invoke vs Func<,>.

{
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) =>
{
Expand Down
92 changes: 0 additions & 92 deletions Hyperion/SerializerFactories/LinkedListSerializerFactory.cs

This file was deleted.

1 change: 0 additions & 1 deletion Hyperion/SerializerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public class SerializerOptions
new DefaultDictionarySerializerFactory(),
new DictionarySerializerFactory(),
new ArraySerializerFactory(),
new LinkedListSerializerFactory(),
#if SERIALIZATION
new ISerializableSerializerFactory(), //TODO: this will mess up the indexes in the serializer payload
#endif
Expand Down