Skip to content

Instantly share code, notes, and snippets.

@takeshik
Created June 12, 2011 22:39
Show Gist options
  • Save takeshik/1022066 to your computer and use it in GitHub Desktop.
Save takeshik/1022066 to your computer and use it in GitHub Desktop.
public static class DispatchHelper
{
public static Tuple<MethodInfo, Expression[]> DispatchMethod(IEnumerable<MethodInfo> methods, IList<Type> typeArguments, IList<Expression> arguments)
{
return methods
.Select(m => Tuple.Create(m, new Dictionary<Type, Type>(), arguments))
.If(_ => typeArguments != null && typeArguments.Any(), s => s
.Where(_ => _.Item1.IsGenericMethodDefinition && _.Item1.GetGenericArguments().Length == typeArguments.Count)
.Select(_ => _.Item1.MakeGenericMethod(typeArguments.ToArray())
.Let(m => Tuple.Create(m, m.GetGenericParameterMap(), arguments))
)
)
.Where(t => t.Item1.GetParameters().If(_ => t.Item1.IsParamArrayMethod(),
ps => t.Item3.Count >= ps.Length - 1
&& ps.SkipLast(1).Zip(t.Item3,
(p, a) => IsAppropriate(p.ParameterType, a.Type)
).All(_ => _)
&& EnumerableEx.Repeat(ps.Last()).Zip(t.Item3.Skip(ps.Length - 1),
(p, a) => IsAppropriate(p.ParameterType.GetElementType(), a.Type)
).All(_ => _),
ps => arguments.Count == ps.Length
&& ps.Zip(arguments,
(p, a) => IsAppropriate(p.ParameterType, a.Type)
).All(_ => _)
))
.Select(t => t
.If(_ => _.Item1.IsGenericMethod, _ =>
InferTypeArguments(t.Item1.GetParameters().Select(p => p.ParameterType), t.Item3.Select(e => e.Type))
.Let(m => Tuple.Create(_.Item1.MakeGenericMethod(m.Values.ToArray()), m, _.Item3))
)
.If(_ => _.Item1.IsParamArrayMethod(), _ =>
_.Item1.GetParameters().Let(ps =>
ps.Last().ParameterType.GetElementType().Let(et =>
Tuple.Create(_.Item1, _.Item2, (IList<Expression>) _.Item3
.Take(ps.Length - 1)
.Concat(EnumerableEx.Return(Expression.NewArrayInit(et, _.Item3
.Skip(ps.Length - 1)
.Select(e => e.Type == et ? e : Expression.Convert(e, et))
)))
.ToArray()
)
)
)
)
)
.OrderBy(t => t.Item1.IsParamArrayMethod())
.ThenByDescending(t => t.Item2.Count)
.First()
.Let(t => Tuple.Create(
t.Item1,
t.Item1.GetParameters()
.Select(p => p.ParameterType)
.Zip(t.Item3, (pt, a) => pt != a.Type ? Expression.Convert(a, pt) : a)
.ToArray()
));
}
public static Dictionary<Type, Type> InferTypeArguments(IEnumerable<Type> parameters, IEnumerable<Type> arguments)
{
return parameters
.Where(t => t.IsGenericParameter || t.IsGenericType)
.Zip(arguments, (p, a) => p.IsGenericParameter
? EnumerableEx.Return(Tuple.Create(p, a))
: p.GetGenericArguments()
.Zip(a.GetConvertibleTypes()
.Single(t => t.IsGenericType && p.GetGenericTypeDefinition() == t.GetGenericTypeDefinition())
.GetGenericArguments(),
Tuple.Create
)
)
.SelectMany(_ => _)
.Distinct()
.ToDictionary(_ => _.Item1, _ => _.Item2);
}
public static IEnumerable<Type> GetConvertibleTypes(this Type type)
{
return EnumerableEx.Concat(
EnumerableEx.Generate(type, t => t != null, t => t.BaseType, _ => _),
type.GetInterfaces()
);
}
public static Boolean IsParamArrayMethod(this MethodBase method)
{
return method.GetParameters()
.Let(_ => _.Any() && Attribute.IsDefined(method.GetParameters().Last(), typeof(ParamArrayAttribute)));
}
public static Dictionary<Type, Type> GetGenericParameterMap(this Type type)
{
return type.GetGenericTypeDefinition().GetGenericArguments()
.Zip(type.GetGenericArguments(), Tuple.Create)
.ToDictionary(_ => _.Item1, _ => _.Item2);
}
public static Dictionary<Type, Type> GetGenericParameterMap(this MethodInfo method)
{
return method.GetGenericMethodDefinition().GetGenericArguments()
.Zip(method.GetGenericArguments(), Tuple.Create)
.ToDictionary(_ => _.Item1, _ => _.Item2);
}
public static Boolean IsAppropriate(Type parameter, Type argument)
{
return parameter.IsGenericParameter
? argument.GetConvertibleTypes()
.Let(ts => parameter.GetGenericParameterConstraints().All(c => ts.Contains(c)))
: argument.GetConvertibleTypes().If(
_ => parameter.ContainsGenericParameters,
_ => _.Select(t => t.IsGenericType ? t.GetGenericTypeDefinition() : t)
).Let(_ => _.Contains(parameter.IsGenericType ? parameter.GetGenericTypeDefinition() : parameter));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment