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

Support generic methods in methodinfoserializerfactory #78

Merged
merged 1 commit into from
Nov 3, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 16 additions & 0 deletions Hyperion.Tests/ExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization;
Expand Down Expand Up @@ -474,6 +475,21 @@ public void CanSerializeLambdaExpression()
}
}

[Fact]
public void CanSerializeLambdaExpressionContainingGenericMethod() {
Expression<Func<Dummy, bool>> expr = dummy => dummy.TestField.Contains('s');
var serializer = new Serializer(new SerializerOptions(preserveObjectReferences: true));
using (var ms = new MemoryStream())
{
serializer.Serialize(expr, ms);
ms.Seek(0, SeekOrigin.Begin);
var deserialized = serializer.Deserialize<Expression<Func<Dummy, bool>>>(ms);
Assert.NotNull(((MethodCallExpression)deserialized.Body).Method);
Assert.True(deserialized.Compile()(new Dummy("sausages")));
Assert.False(deserialized.Compile()(new Dummy("field")));
}
}

[Fact]
public void CanSerializeInvocationExpression()
{
Expand Down
113 changes: 113 additions & 0 deletions Hyperion/Extensions/TypeEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,118 @@ public static string ToQualifiedAssemblyName(string shortName)
var res = shortName.Replace(",%core%", CoreAssemblyName);
return res;
}

/// <summary>
/// Search for a method by name, parameter types, and binding flags.
/// Unlike GetMethod(), does 'loose' matching on generic
/// parameter types, and searches base interfaces.
/// </summary>
/// <exception cref="AmbiguousMatchException"/>
public static MethodInfo GetMethodExt(this Type thisType,
string name,
BindingFlags bindingFlags,
params Type[] parameterTypes)
{
MethodInfo matchingMethod = null;

// Check all methods with the specified name, including in base classes
GetMethodExt(ref matchingMethod, thisType, name, bindingFlags, parameterTypes);

// If we're searching an interface, we have to manually search base interfaces
if (matchingMethod == null && thisType.GetTypeInfo().IsInterface)
{
foreach (Type interfaceType in thisType.GetInterfaces())
GetMethodExt(ref matchingMethod,
interfaceType,
name,
bindingFlags,
parameterTypes);
}

return matchingMethod;
}

private static void GetMethodExt(ref MethodInfo matchingMethod,
Type type,
string name,
BindingFlags bindingFlags,
params Type[] parameterTypes)
{
// Check all methods with the specified name, including in base classes
foreach (MethodInfo methodInfo in type.GetTypeInfo().GetMember(name,
MemberTypes.Method,
bindingFlags))
{
// Check that the parameter counts and types match,
// with 'loose' matching on generic parameters
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
if (parameterInfos.Length == parameterTypes.Length)
{
int i = 0;
for (; i < parameterInfos.Length; ++i)
{
if (!parameterInfos[i].ParameterType
.IsSimilarType(parameterTypes[i]))
break;
}
if (i == parameterInfos.Length)
{
if (matchingMethod == null)
matchingMethod = methodInfo;
else
throw new AmbiguousMatchException(
"More than one matching method found!");
}
}
}
}

/// <summary>
/// Special type used to match any generic parameter type in GetMethodExt().
/// </summary>
public class T
{ }

/// <summary>
/// Determines if the two types are either identical, or are both generic
/// parameters or generic types with generic parameters in the same
/// locations (generic parameters match any other generic paramter,
/// and concrete types).
/// </summary>
private static bool IsSimilarType(this Type thisType, Type type)
{
// Ignore any 'ref' types
if (thisType.IsByRef)
thisType = thisType.GetElementType();
if (type.IsByRef)
type = type.GetElementType();

// Handle array types
if (thisType.IsArray && type.IsArray)
return thisType.GetElementType().IsSimilarType(type.GetElementType());

// If the types are identical, or they're both generic parameters
// or the special 'T' type, treat as a match
if (thisType == type || thisType.IsGenericParameter || thisType == typeof(T) || type.IsGenericParameter || type == typeof(T))
return true;

// Handle any generic arguments
if (thisType.GetTypeInfo().IsGenericType && type.GetTypeInfo().IsGenericType)
{
Type[] thisArguments = thisType.GetGenericArguments();
Type[] arguments = type.GetGenericArguments();
if (thisArguments.Length == arguments.Length)
{
for (int i = 0; i < thisArguments.Length; ++i)
{
if (!thisArguments[i].IsSimilarType(arguments[i]))
return false;
}
return true;
}
}

return false;
}
}
}
37 changes: 16 additions & 21 deletions Hyperion/SerializerFactories/MethodInfoSerializerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,37 +37,32 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type
{
var name = stream.ReadString(session);
var owner = stream.ReadObject(session) as Type;
var arguments = stream.ReadObject(session) as Type[];
var parameterTypes = stream.ReadObject(session) as Type[];
var method = owner.GetMethodExt(name,
BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
parameterTypes);
if (method.IsGenericMethodDefinition) {
var genericTypeArguments = stream.ReadObject(session) as Type[];
method = method.MakeGenericMethod(genericTypeArguments);
}

#if NET45
var method = owner.GetTypeInfo().GetMethod(
name,
BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
null,
CallingConventions.Any,
arguments,
null);
return method;
#else
var methods = owner.GetTypeInfo()
.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic);
var method = methods.FirstOrDefault(m => m.Name == name &&
m.GetParameters()
.Select(p => p.ParameterType)
.SequenceEqual(arguments));
return method;
#endif
};
ObjectWriter writer = (stream, obj, session) =>
{
var method = (MethodInfo) obj;
var name = method.Name;
var owner = method.DeclaringType;
var arguments = method.GetParameters().Select(p => p.ParameterType).ToArray();
StringSerializer.WriteValueImpl(stream,name,session);
StringSerializer.WriteValueImpl(stream, name, session);
stream.WriteObjectWithManifest(owner, session);
var arguments = method.GetParameters().Select(p => p.ParameterType).ToArray();
stream.WriteObjectWithManifest(arguments, session);
if (method.IsGenericMethod) {
// we use the parameter types to find the method above but, if generic, we need to store the generic type arguments as well
// in order to MakeGenericType
var genericTypeArguments = method.GetGenericArguments();
stream.WriteObjectWithManifest(genericTypeArguments, session);
}
};
os.Initialize(reader, writer);

Expand Down