Merge branch 'dev-v7' into 7.3.0
This commit is contained in:
@@ -5,7 +5,6 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq.Expressions;
|
||||
using System.Web.Services.Description;
|
||||
using Umbraco.Core.Cache;
|
||||
|
||||
namespace Umbraco.Core.Dynamics
|
||||
@@ -18,7 +17,7 @@ namespace Umbraco.Core.Dynamics
|
||||
/// <summary>
|
||||
/// The static cache for extension methods found that match the criteria that we are looking for
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Tuple<Type, string, int>, MethodInfo[]> MethodCache = new ConcurrentDictionary<Tuple<Type, string, int>, MethodInfo[]>();
|
||||
private static readonly ConcurrentDictionary<Tuple<Type, string, int>, ExtensionMethodSearchResult[]> MethodCache = new ConcurrentDictionary<Tuple<Type, string, int>, ExtensionMethodSearchResult[]>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the enumerable of all extension method info's in the app domain = USE SPARINGLY!!!
|
||||
@@ -66,46 +65,66 @@ namespace Umbraco.Core.Dynamics
|
||||
/// <param name="argumentCount">
|
||||
/// The arguments EXCLUDING the 'this' argument in an extension method
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
/// <returns>
|
||||
/// Returns the MethodInfo that matched + the generic type
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// NOTE: This will be an intensive method to call! Results will be cached based on the key (args) of this method
|
||||
/// </remarks>
|
||||
internal static IEnumerable<MethodInfo> GetAllExtensionMethods(IRuntimeCacheProvider runtimeCache, Type thisType, string name, int argumentCount)
|
||||
internal static IEnumerable<ExtensionMethodSearchResult> GetAllExtensionMethods(IRuntimeCacheProvider runtimeCache, Type thisType, string name, int argumentCount)
|
||||
{
|
||||
var key = new Tuple<Type, string, int>(thisType, name, argumentCount);
|
||||
|
||||
return MethodCache.GetOrAdd(key, tuple =>
|
||||
{
|
||||
return MethodCache.GetOrAdd(key, tuple =>
|
||||
{
|
||||
var candidates = GetAllExtensionMethodsInAppDomain(runtimeCache);
|
||||
|
||||
// filter by name
|
||||
var filtr1 = candidates.Where(m => m.Name == name);
|
||||
|
||||
// filter by args count
|
||||
// ensure we add + 1 to the arg count because the 'this' arg is not included in the count above!
|
||||
var filtr2 = filtr1.Where(m => m.GetParameters().Length == argumentCount + 1);
|
||||
//filter by name
|
||||
var methodsByName = candidates.Where(m => m.Name == name);
|
||||
|
||||
// filter by first parameter type (target of the extension method)
|
||||
// ie find the right overload that can take genericParameterType
|
||||
// (which will be either DynamicNodeList or List<DynamicNode> which is IEnumerable)
|
||||
var filtr3 = filtr2.Select(x =>
|
||||
{
|
||||
var t = x.GetParameters()[0].ParameterType; // exists because of +1 above
|
||||
var bindings = new Dictionary<string, List<Type>>();
|
||||
if (thisType.MatchType(t, bindings) == false) return null;
|
||||
//ensure we add + 1 to the arg count because the 'this' arg is not included in the count above!
|
||||
var byParamCount = methodsByName.Where(m => m.GetParameters().Length == argumentCount + 1);
|
||||
|
||||
// create the generic method if necessary
|
||||
if (x.ContainsGenericParameters == false) return x;
|
||||
var targs = t.GetGenericArguments().Select(y => bindings[y.Name].First()).ToArray();
|
||||
return x.MakeGenericMethod(targs);
|
||||
}).Where(x => x != null);
|
||||
var methodsWithFirstParamType = byParamCount.Select(methodInfo => new {methodInfo = methodInfo, firstParamType = FirstParameterType(methodInfo)})
|
||||
.ToArray(); // get the array so we don't keep enumerating.
|
||||
|
||||
return filtr3.ToArray();
|
||||
});
|
||||
//Find the method with an assignable first parameter type - this is an easy match
|
||||
var methodsWhereArgZeroIsTargetType = methodsWithFirstParamType
|
||||
.Where(method => method.firstParamType != null && TypeHelper.IsTypeAssignableFrom(method.firstParamType, thisType))
|
||||
.ToArray();
|
||||
|
||||
//found some so return this before trying to look for generics
|
||||
if (methodsWhereArgZeroIsTargetType.Any())
|
||||
return methodsWhereArgZeroIsTargetType.Select(mt => new ExtensionMethodSearchResult(mt.methodInfo)).ToArray();
|
||||
|
||||
//this is where it gets tricky, none of the first parameters were assignable to our type which means that
|
||||
// if they are assignable they are generic arguments
|
||||
return methodsWithFirstParamType
|
||||
.Where(method => method.firstParamType != null)
|
||||
.Select(x => new
|
||||
{
|
||||
methodInfo = x.methodInfo,
|
||||
genericMethodResult = TypeHelper.IsAssignableFromGeneric(x.firstParamType, thisType)
|
||||
})
|
||||
.Where(x => x.genericMethodResult.Success)
|
||||
.Select(x => new ExtensionMethodSearchResult(x.methodInfo, x.genericMethodResult.Result))
|
||||
.ToArray();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static Type FirstParameterType(MethodInfo m)
|
||||
{
|
||||
var p = m.GetParameters();
|
||||
if (p.Any())
|
||||
{
|
||||
return p.First().ParameterType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static MethodInfo DetermineMethodFromParams(IEnumerable<MethodInfo> methods, Type genericType, IEnumerable<object> args)
|
||||
private static MethodInfo DetermineMethodFromParams(IEnumerable<ExtensionMethodSearchResult> methods, Type thisType, IEnumerable<object> args)
|
||||
{
|
||||
MethodInfo methodToExecute = null;
|
||||
|
||||
@@ -118,9 +137,9 @@ namespace Umbraco.Core.Dynamics
|
||||
|
||||
var methodsWithArgTypes = methods.Select(method => new
|
||||
{
|
||||
method,
|
||||
method = method,
|
||||
//skip the first arg because that is the extension method type ('this') that we are looking for
|
||||
types = method.GetParameters().Select(pi => pi.ParameterType).Skip(1)
|
||||
types = method.MethodInfo.GetParameters().Select(pi => pi.ParameterType).Skip(1)
|
||||
});
|
||||
|
||||
//This type comparer will check
|
||||
@@ -138,20 +157,23 @@ namespace Umbraco.Core.Dynamics
|
||||
|
||||
if (firstMatchingOverload != null)
|
||||
{
|
||||
methodToExecute = firstMatchingOverload.method;
|
||||
methodToExecute = firstMatchingOverload.method.MethodInfo;
|
||||
|
||||
//if this is null then we can't need to create the generic method
|
||||
if (methodToExecute.IsGenericMethodDefinition && firstMatchingOverload.method.BaseGenericTypeMatch.IsGenericType)
|
||||
{
|
||||
//This should never be null in this case
|
||||
if (firstMatchingOverload.method.BaseGenericTypeMatch == null) throw new InvalidOperationException("No base generic type could be resolved");
|
||||
|
||||
methodToExecute = methodToExecute.MakeGenericMethod(firstMatchingOverload.method.BaseGenericTypeMatch.GetGenericArguments());
|
||||
}
|
||||
}
|
||||
|
||||
return methodToExecute;
|
||||
return methodToExecute;
|
||||
}
|
||||
|
||||
public static MethodInfo FindExtensionMethod(IRuntimeCacheProvider runtimeCache, Type thisType, object[] args, string name, bool argsContainsThis)
|
||||
{
|
||||
Type genericType = null;
|
||||
if (thisType.IsGenericType)
|
||||
{
|
||||
genericType = thisType.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
args = args
|
||||
//if the args contains 'this', remove the first one since that is 'this' and we don't want to use
|
||||
//that in the method searching
|
||||
@@ -159,8 +181,34 @@ namespace Umbraco.Core.Dynamics
|
||||
.ToArray();
|
||||
|
||||
var methods = GetAllExtensionMethods(runtimeCache, thisType, name, args.Length).ToArray();
|
||||
|
||||
return DetermineMethodFromParams(methods, genericType, args);
|
||||
|
||||
return DetermineMethodFromParams(methods, thisType, args);
|
||||
}
|
||||
|
||||
internal class ExtensionMethodSearchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
|
||||
/// </summary>
|
||||
public ExtensionMethodSearchResult(MethodInfo methodInfo)
|
||||
{
|
||||
MethodInfo = methodInfo;
|
||||
IsGeneric = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
|
||||
/// </summary>
|
||||
public ExtensionMethodSearchResult(MethodInfo methodInfo, Type baseGenericTypeMatch)
|
||||
{
|
||||
MethodInfo = methodInfo;
|
||||
IsGeneric = true;
|
||||
BaseGenericTypeMatch = baseGenericTypeMatch;
|
||||
}
|
||||
|
||||
public MethodInfo MethodInfo { get; private set; }
|
||||
public bool IsGeneric { get; private set; }
|
||||
public Type BaseGenericTypeMatch { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
internal void AddAdditionalData(UmbracoEntity entity, IDictionary<string, object> originalEntityProperties)
|
||||
{
|
||||
var entityProps = TypeHelper.GetPublicProperties(typeof(IUmbracoEntity)).Select(x => x.Name).ToArray();
|
||||
var entityProps = typeof(IUmbracoEntity).GetPublicProperties().Select(x => x.Name).ToArray();
|
||||
|
||||
//figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data
|
||||
foreach (var k in originalEntityProperties.Keys
|
||||
|
||||
@@ -131,20 +131,7 @@ namespace Umbraco.Core
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// that method is broken (will return duplicates) and useless (GetInterfaces already does the job)
|
||||
//public static IEnumerable<Type> AllInterfaces(this Type target)
|
||||
//{
|
||||
// foreach (var IF in target.GetInterfaces())
|
||||
// {
|
||||
// yield return IF;
|
||||
// foreach (var childIF in IF.AllInterfaces())
|
||||
// {
|
||||
// yield return childIF;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
public static IEnumerable<Type> GetBaseTypes(this Type type, bool andSelf)
|
||||
{
|
||||
if (andSelf)
|
||||
@@ -308,6 +295,53 @@ namespace Umbraco.Core
|
||||
| BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all public properties including inherited properties even for interfaces
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// taken from http://stackoverflow.com/questions/358835/getproperties-to-return-all-properties-for-an-interface-inheritance-hierarchy
|
||||
/// </remarks>
|
||||
public static PropertyInfo[] GetPublicProperties(this Type type)
|
||||
{
|
||||
if (type.IsInterface)
|
||||
{
|
||||
var propertyInfos = new List<PropertyInfo>();
|
||||
|
||||
var considered = new List<Type>();
|
||||
var queue = new Queue<Type>();
|
||||
considered.Add(type);
|
||||
queue.Enqueue(type);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var subType = queue.Dequeue();
|
||||
foreach (var subInterface in subType.GetInterfaces())
|
||||
{
|
||||
if (considered.Contains(subInterface)) continue;
|
||||
|
||||
considered.Add(subInterface);
|
||||
queue.Enqueue(subInterface);
|
||||
}
|
||||
|
||||
var typeProperties = subType.GetProperties(
|
||||
BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.Instance);
|
||||
|
||||
var newPropertyInfos = typeProperties
|
||||
.Where(x => !propertyInfos.Contains(x));
|
||||
|
||||
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||
}
|
||||
|
||||
return propertyInfos.ToArray();
|
||||
}
|
||||
|
||||
return type.GetProperties(BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public | BindingFlags.Instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified actual type is type.
|
||||
/// </summary>
|
||||
@@ -389,100 +423,6 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
|
||||
#region Match Type
|
||||
|
||||
private static void ReduceGenericParameterCandidateTypes(ICollection<Type> allStuff, Type type)
|
||||
{
|
||||
var at1 = new List<Type>();
|
||||
var t = type;
|
||||
while (t != null)
|
||||
{
|
||||
at1.Add(t);
|
||||
t = t.BaseType;
|
||||
}
|
||||
var r = allStuff.Where(x => x.IsClass && at1.Contains(x) == false).ToArray();
|
||||
foreach (var x in r) allStuff.Remove(x);
|
||||
var ai1 = type.GetInterfaces();
|
||||
if (type.IsInterface) ai1 = ai1.Union(new[] { type }).ToArray();
|
||||
r = allStuff.Where(x => x.IsInterface && ai1.Contains(x) == false).ToArray();
|
||||
foreach (var x in r) allStuff.Remove(x);
|
||||
}
|
||||
|
||||
private static bool MatchGeneric(Type inst, Type type, IDictionary<string, List<Type>> bindings)
|
||||
{
|
||||
if (inst.IsGenericType == false) return false;
|
||||
|
||||
var instd = inst.GetGenericTypeDefinition();
|
||||
var typed = type.GetGenericTypeDefinition();
|
||||
|
||||
if (instd != typed) return false;
|
||||
|
||||
var insta = inst.GetGenericArguments();
|
||||
var typea = type.GetGenericArguments();
|
||||
|
||||
if (insta.Length != typea.Length) return false;
|
||||
|
||||
// but... there is no ZipWhile, and we have arrays anyway
|
||||
//var x = insta.Zip<Type, Type, bool>(typea, (instax, typeax) => { ... });
|
||||
|
||||
for (var i = 0; i < insta.Length; i++)
|
||||
if (MatchType(insta[i], typea[i], bindings) == false)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetGenericParameterCandidateTypes(Type type)
|
||||
{
|
||||
yield return type;
|
||||
var t = type.BaseType;
|
||||
while (t != null)
|
||||
{
|
||||
yield return t;
|
||||
t = t.BaseType;
|
||||
}
|
||||
foreach (var i in type.GetInterfaces())
|
||||
yield return i;
|
||||
}
|
||||
|
||||
public static bool MatchType(this Type inst, Type type)
|
||||
{
|
||||
return MatchType(inst, type, new Dictionary<string, List<Type>>());
|
||||
}
|
||||
|
||||
internal static bool MatchType(this Type inst, Type type, IDictionary<string, List<Type>> bindings)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
if (MatchGeneric(inst, type, bindings)) return true;
|
||||
var t = inst.BaseType;
|
||||
while (t != null)
|
||||
{
|
||||
if (MatchGeneric(t, type, bindings)) return true;
|
||||
t = t.BaseType;
|
||||
}
|
||||
return inst.GetInterfaces().Any(i => MatchGeneric(i, type, bindings));
|
||||
}
|
||||
|
||||
if (type.IsGenericParameter)
|
||||
{
|
||||
if (bindings.ContainsKey(type.Name))
|
||||
{
|
||||
ReduceGenericParameterCandidateTypes(bindings[type.Name], inst);
|
||||
return bindings[type.Name].Count > 0;
|
||||
}
|
||||
|
||||
bindings[type.Name] = new List<Type>(GetGenericParameterCandidateTypes(inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (inst == type) return true;
|
||||
if (type.IsClass && inst.IsClass && inst.IsSubclassOf(type)) return true;
|
||||
if (type.IsInterface && inst.GetInterfaces().Contains(type)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -131,8 +131,87 @@ namespace Umbraco.Core
|
||||
return Attempt<Type>.Fail();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the type <paramref name="implementation"/> is assignable from the specified implementation <typeparamref name="TContract"/>,
|
||||
/// <summary>
|
||||
/// Checks if the generic type passed in can be assigned from the given type
|
||||
/// </summary>
|
||||
/// <param name="contract"></param>
|
||||
/// <param name="implementation"></param>
|
||||
/// <returns>
|
||||
/// Returns an Attempt{Type} which if true will include the actual type that matched the genericType
|
||||
/// being compared.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// First we need to check a special case, if the generic type is a generic definition but has no FullName, then
|
||||
/// we cannot compare it with traditional means because the types will never match.
|
||||
/// Generic types will not have a FullName in these cases: http://blogs.msdn.com/b/haibo_luo/archive/2006/02/17/534480.aspx
|
||||
/// or when you retrieve a generic method parameter using reflection, for example, typeof(IEnumerable{}) is not IEnumerable{T} since
|
||||
/// when reflected 'T' is actually something.
|
||||
///
|
||||
/// This is using a version modified from: http://stackoverflow.com/a/1075059/1968
|
||||
/// </remarks>
|
||||
public static Attempt<Type> IsAssignableFromGeneric(Type contract, Type implementation)
|
||||
{
|
||||
if (contract.IsGenericTypeDefinition == false && contract.FullName.IsNullOrWhiteSpace())
|
||||
{
|
||||
return IsTypeAssignableFromReflectedGeneric(contract, implementation);
|
||||
}
|
||||
|
||||
var genericTypeDef = implementation.IsGenericType ? implementation.GetGenericTypeDefinition() : null;
|
||||
|
||||
if (genericTypeDef != null && genericTypeDef == contract)
|
||||
return Attempt<Type>.Succeed(implementation);
|
||||
|
||||
var its = implementation.GetInterfaces();
|
||||
|
||||
foreach (var it in its)
|
||||
{
|
||||
genericTypeDef = it.IsGenericType ? it.GetGenericTypeDefinition() : null;
|
||||
|
||||
if (genericTypeDef != null && genericTypeDef == contract)
|
||||
return Attempt<Type>.Succeed(it);
|
||||
}
|
||||
|
||||
var baseType = implementation.BaseType;
|
||||
return baseType != null
|
||||
? IsAssignableFromGeneric(contract, baseType)
|
||||
: Attempt<Type>.Fail();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used in IsAssignableToGenericType
|
||||
/// </summary>
|
||||
/// <param name="contract">The generic type contract</param>
|
||||
/// <param name="implementation"></param>
|
||||
/// <returns>
|
||||
/// Returns an Attempt{Type} which if true will include the actual type that matched the genericType
|
||||
/// being compared.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// See remarks in method IsAssignableFromGeneric
|
||||
/// </remarks>
|
||||
private static Attempt<Type> IsTypeAssignableFromReflectedGeneric(Type contract, Type implementation)
|
||||
{
|
||||
if (implementation.IsGenericType && implementation.GetGenericTypeDefinition().Name == contract.Name && implementation.GenericTypeArguments.Length == contract.GenericTypeArguments.Length)
|
||||
return Attempt<Type>.Succeed(implementation);
|
||||
|
||||
var its = implementation.GetInterfaces();
|
||||
|
||||
foreach (var it in its)
|
||||
{
|
||||
if (it.IsGenericType && it.GetGenericTypeDefinition().Name == contract.Name && implementation.GenericTypeArguments.Length == contract.GenericTypeArguments.Length)
|
||||
{
|
||||
return Attempt<Type>.Succeed(it);
|
||||
}
|
||||
}
|
||||
|
||||
var baseType = implementation.BaseType;
|
||||
return baseType != null
|
||||
? IsTypeAssignableFromReflectedGeneric(contract, baseType)
|
||||
: Attempt<Type>.Fail();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the type <paramref name="implementation"/> is assignable from the specified implementation,
|
||||
/// and caches the result across the application using a <see cref="ConcurrentDictionary{TKey,TValue}"/>.
|
||||
/// </summary>
|
||||
/// <param name="contract">The type of the contract.</param>
|
||||
@@ -156,8 +235,20 @@ namespace Umbraco.Core
|
||||
return IsTypeAssignableFrom(typeof(TContract), implementation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the object instance <paramref name="implementation"/> is assignable from the specified implementation <typeparamref name="TContract"/>,
|
||||
/// and caches the result across the application using a <see cref="ConcurrentDictionary{TKey,TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContract">The type of the contract.</typeparam>
|
||||
/// <param name="implementation">The implementation.</param>
|
||||
public static bool IsTypeAssignableFrom<TContract>(object implementation)
|
||||
{
|
||||
if (implementation == null) throw new ArgumentNullException("implementation");
|
||||
return IsTypeAssignableFrom<TContract>(implementation.GetType());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cached method to determine whether <paramref name="implementation"/> represents a value type.
|
||||
/// A method to determine whether <paramref name="implementation"/> represents a value type.
|
||||
/// </summary>
|
||||
/// <param name="implementation">The implementation.</param>
|
||||
public static bool IsValueType(Type implementation)
|
||||
@@ -166,7 +257,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cached method to determine whether <paramref name="implementation"/> is an implied value type (<see cref="Type.IsValueType"/>, <see cref="Type.IsEnum"/> or a string).
|
||||
/// A method to determine whether <paramref name="implementation"/> is an implied value type (<see cref="Type.IsValueType"/>, <see cref="Type.IsEnum"/> or a string).
|
||||
/// </summary>
|
||||
/// <param name="implementation">The implementation.</param>
|
||||
public static bool IsImplicitValueType(Type implementation)
|
||||
@@ -174,14 +265,8 @@ namespace Umbraco.Core
|
||||
return IsValueType(implementation) || implementation.IsEnum || implementation == typeof (string);
|
||||
}
|
||||
|
||||
public static bool IsTypeAssignableFrom<TContract>(object implementation)
|
||||
{
|
||||
if (implementation == null) throw new ArgumentNullException("implementation");
|
||||
return IsTypeAssignableFrom<TContract>(implementation.GetType());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a PropertyInfo from a type
|
||||
/// Returns (and caches) a PropertyInfo from a type
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="name"></param>
|
||||
@@ -203,54 +288,7 @@ namespace Umbraco.Core
|
||||
return x.Name == name;
|
||||
return x.Name.InvariantEquals(name);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all public properties including inherited properties even for interfaces
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// taken from http://stackoverflow.com/questions/358835/getproperties-to-return-all-properties-for-an-interface-inheritance-hierarchy
|
||||
/// </remarks>
|
||||
public static PropertyInfo[] GetPublicProperties(Type type)
|
||||
{
|
||||
if (type.IsInterface)
|
||||
{
|
||||
var propertyInfos = new List<PropertyInfo>();
|
||||
|
||||
var considered = new List<Type>();
|
||||
var queue = new Queue<Type>();
|
||||
considered.Add(type);
|
||||
queue.Enqueue(type);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var subType = queue.Dequeue();
|
||||
foreach (var subInterface in subType.GetInterfaces())
|
||||
{
|
||||
if (considered.Contains(subInterface)) continue;
|
||||
|
||||
considered.Add(subInterface);
|
||||
queue.Enqueue(subInterface);
|
||||
}
|
||||
|
||||
var typeProperties = subType.GetProperties(
|
||||
BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.Instance);
|
||||
|
||||
var newPropertyInfos = typeProperties
|
||||
.Where(x => !propertyInfos.Contains(x));
|
||||
|
||||
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||
}
|
||||
|
||||
return propertyInfos.ToArray();
|
||||
}
|
||||
|
||||
return type.GetProperties(BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public | BindingFlags.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets (and caches) <see cref="FieldInfo"/> discoverable in the current <see cref="AppDomain"/> for a given <paramref name="type"/>.
|
||||
@@ -286,5 +324,104 @@ namespace Umbraco.Core
|
||||
&& (includeIndexed || !y.GetIndexParameters().Any()))
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
|
||||
#region Match Type
|
||||
|
||||
//TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric
|
||||
|
||||
private static void ReduceGenericParameterCandidateTypes(ICollection<Type> allStuff, Type type)
|
||||
{
|
||||
var at1 = new List<Type>();
|
||||
var t = type;
|
||||
while (t != null)
|
||||
{
|
||||
at1.Add(t);
|
||||
t = t.BaseType;
|
||||
}
|
||||
var r = allStuff.Where(x => x.IsClass && at1.Contains(x) == false).ToArray();
|
||||
foreach (var x in r) allStuff.Remove(x);
|
||||
var ai1 = type.GetInterfaces();
|
||||
if (type.IsInterface) ai1 = ai1.Union(new[] { type }).ToArray();
|
||||
r = allStuff.Where(x => x.IsInterface && ai1.Contains(x) == false).ToArray();
|
||||
foreach (var x in r) allStuff.Remove(x);
|
||||
}
|
||||
|
||||
private static bool MatchGeneric(Type inst, Type type, IDictionary<string, List<Type>> bindings)
|
||||
{
|
||||
if (inst.IsGenericType == false) return false;
|
||||
|
||||
var instd = inst.GetGenericTypeDefinition();
|
||||
var typed = type.GetGenericTypeDefinition();
|
||||
|
||||
if (instd != typed) return false;
|
||||
|
||||
var insta = inst.GetGenericArguments();
|
||||
var typea = type.GetGenericArguments();
|
||||
|
||||
if (insta.Length != typea.Length) return false;
|
||||
|
||||
// but... there is no ZipWhile, and we have arrays anyway
|
||||
//var x = insta.Zip<Type, Type, bool>(typea, (instax, typeax) => { ... });
|
||||
|
||||
for (var i = 0; i < insta.Length; i++)
|
||||
if (MatchType(insta[i], typea[i], bindings) == false)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetGenericParameterCandidateTypes(Type type)
|
||||
{
|
||||
yield return type;
|
||||
var t = type.BaseType;
|
||||
while (t != null)
|
||||
{
|
||||
yield return t;
|
||||
t = t.BaseType;
|
||||
}
|
||||
foreach (var i in type.GetInterfaces())
|
||||
yield return i;
|
||||
}
|
||||
|
||||
public static bool MatchType(Type inst, Type type)
|
||||
{
|
||||
return MatchType(inst, type, new Dictionary<string, List<Type>>());
|
||||
}
|
||||
|
||||
internal static bool MatchType(Type inst, Type type, IDictionary<string, List<Type>> bindings)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
if (MatchGeneric(inst, type, bindings)) return true;
|
||||
var t = inst.BaseType;
|
||||
while (t != null)
|
||||
{
|
||||
if (MatchGeneric(t, type, bindings)) return true;
|
||||
t = t.BaseType;
|
||||
}
|
||||
return inst.GetInterfaces().Any(i => MatchGeneric(i, type, bindings));
|
||||
}
|
||||
|
||||
if (type.IsGenericParameter)
|
||||
{
|
||||
if (bindings.ContainsKey(type.Name))
|
||||
{
|
||||
ReduceGenericParameterCandidateTypes(bindings[type.Name], inst);
|
||||
return bindings[type.Name].Count > 0;
|
||||
}
|
||||
|
||||
bindings[type.Name] = new List<Type>(GetGenericParameterCandidateTypes(inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (inst == type) return true;
|
||||
if (type.IsClass && inst.IsClass && inst.IsSubclassOf(type)) return true;
|
||||
if (type.IsInterface && inst.GetInterfaces().Contains(type)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -202,50 +202,24 @@ namespace Umbraco.Tests.DynamicsAndReflection
|
||||
Assert.AreEqual("T", t3.GetGenericArguments()[0].Name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Test]
|
||||
public void MatchTypesTest()
|
||||
public void Find_Generic_Enumerable_Method()
|
||||
{
|
||||
var bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(int).MatchType(typeof(int), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
MethodInfo method;
|
||||
var class1 = Enumerable.Empty<TestClass>();
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsFalse(typeof(int).MatchType(typeof(string), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(IEnumerable<TestClass>), new object[] { }, "GenericMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class1 });
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(List<int>).MatchType(typeof(System.Collections.IEnumerable), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
var class2 = new TestClassCollection();
|
||||
|
||||
var m = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod7");
|
||||
var t1 = m.GetParameters()[0].ParameterType; // List<T>
|
||||
var t2 = m.GetParameters()[0].ParameterType.GetGenericArguments()[0]; // <T>
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(int).MatchType(t2, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(IList<int>).MatchType(t1, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(List<int>).MatchType(typeof(IList<int>), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(List<int>).MatchType(t1, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(typeof(Dictionary<int, string>).MatchType(typeof(IDictionary<,>), bindings));
|
||||
Assert.AreEqual(2, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["TKey"].FirstOrDefault());
|
||||
Assert.AreEqual(typeof(string), bindings["TValue"].FirstOrDefault());
|
||||
}
|
||||
method = ExtensionMethodFinder.FindExtensionMethod(new NullCacheProvider(), typeof(TestClassCollection), new object[] { }, "GenericMethod", false);
|
||||
Assert.IsNotNull(method);
|
||||
method.Invoke(null, new object[] { class2 });
|
||||
}
|
||||
|
||||
[Ignore("This is just testing the below GetMethodForArguments method - Stephen was working on this but it's not used in the core")]
|
||||
[Test]
|
||||
@@ -374,12 +348,22 @@ namespace Umbraco.Tests.DynamicsAndReflection
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class TestClassCollection : List<TestClass>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region Tests Elements
|
||||
|
||||
|
||||
static class ExtensionMethodFinderTestsExtensions
|
||||
{
|
||||
public static void GenericMethod<T>(this IEnumerable<T> source)
|
||||
{ }
|
||||
|
||||
public static void SimpleMethod(this ExtensionMethodFinderTests.TestClass source, int value)
|
||||
{ }
|
||||
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data.Odbc;
|
||||
using System.Data.OleDb;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Tests.DynamicsAndReflection;
|
||||
using Umbraco.Web.Cache;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Scheduling;
|
||||
using UmbracoExamine.DataServices;
|
||||
|
||||
@@ -18,6 +25,72 @@ namespace Umbraco.Tests.Plugins
|
||||
public class TypeHelperTests
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void Is_Generic_Assignable()
|
||||
{
|
||||
var type1 = typeof (DynamicPublishedContentList);
|
||||
var type2 = typeof (IEnumerable<IPublishedContent>);
|
||||
var type3 = typeof(IQueryable<IPublishedContent>);
|
||||
var type4 = typeof(List<IPublishedContent>);
|
||||
var type5 = typeof(IEnumerable<>);
|
||||
|
||||
Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(type2, type1));
|
||||
Assert.IsFalse(TypeHelper.IsTypeAssignableFrom(type3, type1));
|
||||
Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(type2, type4));
|
||||
|
||||
//Will always fail which is correct, you cannot 'assign' IEnumerable<IPublishedContent> simply to IEnumerable<>
|
||||
//Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(type5, type2));
|
||||
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(type5, type2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Is_Assignable_To_Generic_Type()
|
||||
{
|
||||
//modified from: https://gist.github.com/klmr/4174727
|
||||
//using a version modified from: http://stackoverflow.com/a/1075059/1968
|
||||
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(typeof(Base<>), typeof(Derived<int>)));
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(typeof(IEnumerable<>), typeof(List<int>)));
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(typeof(Derived<>), typeof(Derived<int>)));
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(typeof(Base<>), typeof(Derived2<int>)));
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(typeof(IBase<>), typeof(DerivedI<int>)));
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(typeof(IBase<>), typeof(Derived2<int>)));
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(typeof(Nullable<>), typeof(int?)));
|
||||
|
||||
Assert.IsFalse(TypeHelper.IsAssignableFromGeneric(typeof(Object), typeof(Derived<int>)));
|
||||
Assert.IsFalse(TypeHelper.IsAssignableFromGeneric(typeof(List<>), typeof(Derived<int>)));
|
||||
Assert.IsFalse(TypeHelper.IsAssignableFromGeneric(typeof(IEnumerable<>), typeof(Derived<int>)));
|
||||
Assert.IsFalse(TypeHelper.IsAssignableFromGeneric(typeof(Base<int>), typeof(Derived<int>)));
|
||||
Assert.IsFalse(TypeHelper.IsAssignableFromGeneric(typeof(IEnumerable<int>), typeof(List<int>)));
|
||||
Assert.IsFalse(TypeHelper.IsAssignableFromGeneric(typeof(Nullable<>), typeof(int)));
|
||||
|
||||
//This get's the "Type" from the Count extension method on IEnumerable<T>, however the type IEnumerable<T> isn't
|
||||
// IEnumerable<> and it is not a generic definition, this attempts to explain that:
|
||||
// http://blogs.msdn.com/b/haibo_luo/archive/2006/02/17/534480.aspx
|
||||
|
||||
var genericEnumerableNonGenericDefinition = typeof (Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
|
||||
.Single(x => x.Name == "Count" && x.GetParameters().Count() == 1)
|
||||
.GetParameters()
|
||||
.Single()
|
||||
.ParameterType;
|
||||
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(genericEnumerableNonGenericDefinition, typeof(List<int>)));
|
||||
|
||||
}
|
||||
|
||||
class Base<T> { }
|
||||
|
||||
interface IBase<T> { }
|
||||
|
||||
interface IDerived<T> : IBase<T> { }
|
||||
|
||||
class Derived<T> : Base<T>, IBase<T> { }
|
||||
|
||||
class Derived2<T> : Derived<T> { }
|
||||
|
||||
class DerivedI<T> : IDerived<T> { }
|
||||
|
||||
[Test]
|
||||
public void Is_Static_Class()
|
||||
{
|
||||
@@ -68,5 +141,50 @@ namespace Umbraco.Tests.Plugins
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MatchTypesTest()
|
||||
{
|
||||
var bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(int), typeof(int), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsFalse(TypeHelper.MatchType(typeof(int), typeof(string), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), typeof(System.Collections.IEnumerable), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
var m = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod7");
|
||||
var t1 = m.GetParameters()[0].ParameterType; // List<T>
|
||||
var t2 = m.GetParameters()[0].ParameterType.GetGenericArguments()[0]; // <T>
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(int), t2, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(IList<int>), t1, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), typeof(IList<int>), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), t1, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(Dictionary<int, string>), typeof(IDictionary<,>), bindings));
|
||||
Assert.AreEqual(2, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["TKey"].FirstOrDefault());
|
||||
Assert.AreEqual(typeof(string), bindings["TValue"].FirstOrDefault());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Dynamics;
|
||||
@@ -16,8 +15,6 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core;
|
||||
using System.Reflection;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Strings;
|
||||
using ContentType = umbraco.cms.businesslogic.ContentType;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user