Added DynamicInstanceHelper for any dynamic object that should find matches on methods, properties and extension methods. Fixed ExtensionMethodFinder to find the Enumerable extensions (was previously set to IEnumerable which would find nothing).
199 lines
7.6 KiB
C#
199 lines
7.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Web.Compilation;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Collections;
|
|
using System.Linq.Expressions;
|
|
|
|
namespace Umbraco.Core.Dynamics
|
|
{
|
|
/// <summary>
|
|
/// Utility class for finding extension methods on a type to execute
|
|
/// </summary>
|
|
internal static class ExtensionMethodFinder
|
|
{
|
|
/// <summary>
|
|
/// Returns all extension methods found matching the definition
|
|
/// </summary>
|
|
/// <param name="thisType"></param>
|
|
/// <param name="name"></param>
|
|
/// <param name="argumentCount"></param>
|
|
/// <param name="argsContainsThis"></param>
|
|
/// <returns></returns>
|
|
/// <remarks>
|
|
/// TODO: NOTE: This will be an intensive method to call!! Results should be cached!
|
|
/// </remarks>
|
|
private static IEnumerable<MethodInfo> GetAllExtensionMethods(Type thisType, string name, int argumentCount, bool argsContainsThis)
|
|
{
|
|
//only scan assemblies we know to contain extension methods (user assemblies)
|
|
var assembliesToScan = TypeFinder.GetAssembliesWithKnownExclusions();
|
|
|
|
//get extension methods from runtime
|
|
var candidates = (
|
|
from assembly in assembliesToScan
|
|
where assembly.IsDefined(typeof(ExtensionAttribute), false)
|
|
from type in assembly.GetTypes()
|
|
where (type.IsDefined(typeof(ExtensionAttribute), false)
|
|
&& type.IsSealed && !type.IsGenericType && !type.IsNested)
|
|
from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)
|
|
// this filters extension methods
|
|
where method.IsDefined(typeof(ExtensionAttribute), false)
|
|
select method
|
|
);
|
|
|
|
//add the extension methods defined in IEnumerable
|
|
candidates = candidates.Concat(typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public));
|
|
|
|
//filter by name
|
|
var methodsByName = candidates.Where(m => m.Name == name);
|
|
|
|
var isGenericAndRightParamCount = methodsByName.Where(m => m.GetParameters().Length == argumentCount + (argsContainsThis ? 0 : 1));
|
|
|
|
//find the right overload that can take genericParameterType
|
|
//which will be either DynamicNodeList or List<DynamicNode> which is IEnumerable`
|
|
|
|
var withGenericParameterType = isGenericAndRightParamCount.Select(m => new { m, t = FirstParameterType(m) });
|
|
|
|
var methodsWhereArgZeroIsTargetType = (from method in withGenericParameterType
|
|
where
|
|
method.t != null && MethodArgZeroHasCorrectTargetType(method.m, method.t, thisType)
|
|
select method);
|
|
|
|
return methodsWhereArgZeroIsTargetType.Select(mt => mt.m);
|
|
}
|
|
|
|
private static bool MethodArgZeroHasCorrectTargetType(MethodInfo method, Type firstArgumentType, Type thisType)
|
|
{
|
|
//This is done with seperate method calls because you can't debug/watch lamdas - if you're trying to figure
|
|
//out why the wrong method is returned, it helps to be able to see each boolean result
|
|
|
|
return
|
|
|
|
// is it defined on me?
|
|
MethodArgZeroHasCorrectTargetTypeTypeMatchesExactly(method, firstArgumentType, thisType) ||
|
|
|
|
// or on any of my interfaces?
|
|
MethodArgZeroHasCorrectTargetTypeAnInterfaceMatches(method, firstArgumentType, thisType) ||
|
|
|
|
// or on any of my base types?
|
|
MethodArgZeroHasCorrectTargetTypeIsASubclassOf(method, firstArgumentType, thisType) ||
|
|
|
|
//share a common interface (e.g. IEnumerable)
|
|
MethodArgZeroHasCorrectTargetTypeShareACommonInterface(method, firstArgumentType, thisType);
|
|
|
|
|
|
}
|
|
|
|
private static bool MethodArgZeroHasCorrectTargetTypeShareACommonInterface(MethodInfo method, Type firstArgumentType, Type thisType)
|
|
{
|
|
Type[] interfaces = firstArgumentType.GetInterfaces();
|
|
if (interfaces.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
bool result = interfaces.All(i => thisType.GetInterfaces().Contains(i));
|
|
return result;
|
|
}
|
|
|
|
private static bool MethodArgZeroHasCorrectTargetTypeIsASubclassOf(MethodInfo method, Type firstArgumentType, Type thisType)
|
|
{
|
|
bool result = thisType.IsSubclassOf(firstArgumentType);
|
|
return result;
|
|
}
|
|
|
|
private static bool MethodArgZeroHasCorrectTargetTypeAnInterfaceMatches(MethodInfo method, Type firstArgumentType, Type thisType)
|
|
{
|
|
bool result = thisType.GetInterfaces().Contains(firstArgumentType);
|
|
return result;
|
|
}
|
|
|
|
private static bool MethodArgZeroHasCorrectTargetTypeTypeMatchesExactly(MethodInfo method, Type firstArgumentType, Type thisType)
|
|
{
|
|
bool result = (thisType == firstArgumentType);
|
|
return result;
|
|
}
|
|
|
|
private static Type FirstParameterType(MethodInfo m)
|
|
{
|
|
ParameterInfo[] p = m.GetParameters();
|
|
if (p.Any())
|
|
{
|
|
return p.First().ParameterType;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static MethodInfo DetermineMethodFromParams(IEnumerable<MethodInfo> methods, Type genericType, IEnumerable<object> args)
|
|
{
|
|
if (!methods.Any())
|
|
{
|
|
return null;
|
|
}
|
|
MethodInfo methodToExecute = null;
|
|
if (methods.Count() > 1)
|
|
{
|
|
//Given the args, lets get the types and compare the type sequence to try and find the correct overload
|
|
var argTypes = args.ToList().ConvertAll(o =>
|
|
{
|
|
var oe = (o as Expression);
|
|
if (oe != null)
|
|
{
|
|
return oe.Type.FullName;
|
|
}
|
|
return o.GetType().FullName;
|
|
});
|
|
var methodsWithArgTypes = methods.Select(method => new { method, types = method.GetParameters().Select(pi => pi.ParameterType.FullName) });
|
|
var firstMatchingOverload = methodsWithArgTypes.FirstOrDefault(m => m.types.SequenceEqual(argTypes));
|
|
if (firstMatchingOverload != null)
|
|
{
|
|
methodToExecute = firstMatchingOverload.method;
|
|
}
|
|
}
|
|
|
|
if (methodToExecute == null)
|
|
{
|
|
var firstMethod = methods.FirstOrDefault();
|
|
// NH: this is to ensure that it's always the correct one being chosen when using the LINQ extension methods
|
|
if (methods.Count() > 1)
|
|
{
|
|
var firstGenericMethod = methods.FirstOrDefault(x => x.IsGenericMethodDefinition);
|
|
if (firstGenericMethod != null)
|
|
{
|
|
firstMethod = firstGenericMethod;
|
|
}
|
|
}
|
|
|
|
if (firstMethod != null)
|
|
{
|
|
if (firstMethod.IsGenericMethodDefinition)
|
|
{
|
|
if (genericType != null)
|
|
{
|
|
methodToExecute = firstMethod.MakeGenericMethod(genericType);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
methodToExecute = firstMethod;
|
|
}
|
|
}
|
|
}
|
|
return methodToExecute;
|
|
}
|
|
|
|
public static MethodInfo FindExtensionMethod(Type thisType, object[] args, string name, bool argsContainsThis)
|
|
{
|
|
Type genericType = null;
|
|
if (thisType.IsGenericType)
|
|
{
|
|
genericType = thisType.GetGenericArguments()[0];
|
|
}
|
|
|
|
var methods = GetAllExtensionMethods(thisType, name, args.Length, argsContainsThis).ToArray();
|
|
return DetermineMethodFromParams(methods, genericType, args);
|
|
}
|
|
}
|
|
}
|