Cleanup TypeHelper, add tests - IsAssignableFromGeneric may have issues?
This commit is contained in:
@@ -330,94 +330,99 @@ namespace Umbraco.Core
|
||||
|
||||
//TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric
|
||||
|
||||
private static void ReduceGenericParameterCandidateTypes(ICollection<Type> allStuff, Type type)
|
||||
// readings:
|
||||
// http://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase
|
||||
// http://stackoverflow.com/questions/2208043/generic-variance-in-c-sharp-4-0
|
||||
// http://stackoverflow.com/questions/8401738/c-sharp-casting-generics-covariance-and-contravariance
|
||||
// http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class
|
||||
// http://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059
|
||||
|
||||
private static bool MatchGeneric(Type implementation, Type contract, IDictionary<string, Type> bindings)
|
||||
{
|
||||
var at1 = new List<Type>();
|
||||
var t = type;
|
||||
while (t != null)
|
||||
// trying to match eg List<int> with List<T>
|
||||
// or List<List<List<int>>> with List<ListList<T>>>
|
||||
// classes are NOT invariant so List<string> does not match List<object>
|
||||
|
||||
if (implementation.IsGenericType == false) return false;
|
||||
|
||||
// must have the same generic type definition
|
||||
var implDef = implementation.GetGenericTypeDefinition();
|
||||
var contDef = contract.GetGenericTypeDefinition();
|
||||
if (implDef != contDef) return false;
|
||||
|
||||
// must have the same number of generic arguments
|
||||
var implArgs = implementation.GetGenericArguments();
|
||||
var contArgs = contract.GetGenericArguments();
|
||||
if (implArgs.Length != contArgs.Length) return false;
|
||||
|
||||
// generic arguments must match
|
||||
// in insta we should have actual types (eg int, string...)
|
||||
// in typea we can have generic parameters (eg <T>)
|
||||
for (var i = 0; i < implArgs.Length; i++)
|
||||
{
|
||||
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)
|
||||
const bool variance = false; // classes are NOT invariant
|
||||
if (MatchType(implArgs[i], contArgs[i], bindings, variance) == false)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetGenericParameterCandidateTypes(Type type)
|
||||
public static bool MatchType(Type implementation, Type contract)
|
||||
{
|
||||
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;
|
||||
return MatchType(implementation, contract, new Dictionary<string, Type>());
|
||||
}
|
||||
|
||||
public static bool MatchType(Type inst, Type type)
|
||||
internal static bool MatchType(Type implementation, Type contract, IDictionary<string, Type> bindings, bool variance = true)
|
||||
{
|
||||
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 (contract.IsGenericType)
|
||||
{
|
||||
if (MatchGeneric(inst, type, bindings)) return true;
|
||||
var t = inst.BaseType;
|
||||
// eg type is List<int> or List<T>
|
||||
// if we have variance then List<int> can match IList<T>
|
||||
// if we don't have variance it can't - must have exact type
|
||||
|
||||
// try to match implementation against contract
|
||||
if (MatchGeneric(implementation, contract, bindings)) return true;
|
||||
|
||||
// if no variance, fail
|
||||
if (variance == false) return false;
|
||||
|
||||
// try to match an ancestor of implementation against contract
|
||||
var t = implementation.BaseType;
|
||||
while (t != null)
|
||||
{
|
||||
if (MatchGeneric(t, type, bindings)) return true;
|
||||
if (MatchGeneric(t, contract, bindings)) return true;
|
||||
t = t.BaseType;
|
||||
}
|
||||
return inst.GetInterfaces().Any(i => MatchGeneric(i, type, bindings));
|
||||
|
||||
// try to match an interface of implementation against contract
|
||||
return implementation.GetInterfaces().Any(i => MatchGeneric(i, contract, bindings));
|
||||
}
|
||||
|
||||
if (type.IsGenericParameter)
|
||||
if (contract.IsGenericParameter)
|
||||
{
|
||||
if (bindings.ContainsKey(type.Name))
|
||||
// eg <T>
|
||||
|
||||
if (bindings.ContainsKey(contract.Name))
|
||||
{
|
||||
ReduceGenericParameterCandidateTypes(bindings[type.Name], inst);
|
||||
return bindings[type.Name].Count > 0;
|
||||
// already bound: ensure it's compatible
|
||||
return bindings[contract.Name] == implementation;
|
||||
}
|
||||
|
||||
bindings[type.Name] = new List<Type>(GetGenericParameterCandidateTypes(inst));
|
||||
// not already bound: bind
|
||||
bindings[contract.Name] = implementation;
|
||||
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;
|
||||
// not a generic type, not a generic parameter
|
||||
// so normal class or interface
|
||||
// fixme structs? enums? array types?
|
||||
// about primitive types, value types, etc:
|
||||
// http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class
|
||||
|
||||
if (implementation == contract) return true;
|
||||
if (contract.IsClass && implementation.IsClass && implementation.IsSubclassOf(contract)) return true;
|
||||
if (contract.IsInterface && implementation.GetInterfaces().Contains(contract)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Umbraco.Tests.DynamicsAndReflection
|
||||
public void TestMethod6(int value) { }
|
||||
public void TestMethod6(string value) { }
|
||||
public void TestMethod7<T>(IList<T> value) { }
|
||||
public void TestMethod8<T>(IDictionary<T, T> value) { }
|
||||
|
||||
public interface ITestDict<T> : IDictionary<T, T> { }
|
||||
|
||||
|
||||
@@ -75,8 +75,7 @@ namespace Umbraco.Tests.Plugins
|
||||
.Single()
|
||||
.ParameterType;
|
||||
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(genericEnumerableNonGenericDefinition, typeof(List<int>)));
|
||||
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(genericEnumerableNonGenericDefinition, typeof(List<int>)));
|
||||
}
|
||||
|
||||
class Base<T> { }
|
||||
@@ -144,47 +143,186 @@ namespace Umbraco.Tests.Plugins
|
||||
[Test]
|
||||
public void MatchTypesTest()
|
||||
{
|
||||
var bindings = new Dictionary<string, List<Type>>();
|
||||
var bindings = new Dictionary<string, Type>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(int), typeof(int), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
bindings = new Dictionary<string, Type>();
|
||||
Assert.IsFalse(TypeHelper.MatchType(typeof(int), typeof(string), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
bindings = new Dictionary<string, 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>
|
||||
var t1 = typeof(IList<>); // IList<>
|
||||
var a1 = t1.GetGenericArguments()[0]; // <T>
|
||||
t1 = t1.MakeGenericType(a1); // IList<T>
|
||||
var t2 = a1;
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
bindings = new Dictionary<string, Type>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(int), t2, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
Assert.AreEqual(typeof(int), bindings["T"]);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
bindings = new Dictionary<string, Type>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(IList<int>), t1, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
Assert.AreEqual(typeof(int), bindings["T"]);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
bindings = new Dictionary<string, Type>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), typeof(IList<int>), bindings));
|
||||
Assert.AreEqual(0, bindings.Count);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
bindings = new Dictionary<string, Type>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), t1, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault());
|
||||
Assert.AreEqual(typeof(int), bindings["T"]);
|
||||
|
||||
bindings = new Dictionary<string, List<Type>>();
|
||||
bindings = new Dictionary<string, 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());
|
||||
Assert.AreEqual(typeof(int), bindings["TKey"]);
|
||||
Assert.AreEqual(typeof(string), bindings["TValue"]);
|
||||
|
||||
t1 = typeof(IDictionary<,>); // IDictionary<,>
|
||||
a1 = t1.GetGenericArguments()[0]; // <TKey>
|
||||
t1 = t1.MakeGenericType(a1, a1); // IDictionary<TKey,TKey>
|
||||
|
||||
bindings = new Dictionary<string, Type>();
|
||||
Assert.IsFalse(TypeHelper.MatchType(typeof(Dictionary<int, string>), t1, bindings));
|
||||
|
||||
bindings = new Dictionary<string, Type>();
|
||||
Assert.IsTrue(TypeHelper.MatchType(typeof(Dictionary<int, int>), t1, bindings));
|
||||
Assert.AreEqual(1, bindings.Count);
|
||||
Assert.AreEqual(typeof(int), bindings["TKey"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MatchType_Vs_IsAssignableFromGeneric()
|
||||
{
|
||||
// both are OK
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(List<int>), typeof(IEnumerable<>));
|
||||
|
||||
var t1 = typeof (IDictionary<,>); // IDictionary<,>
|
||||
var a1 = t1.GetGenericArguments()[0];
|
||||
t1 = t1.MakeGenericType(a1, a1); // IDictionary<T,T>
|
||||
|
||||
// both are OK
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(Dictionary<int, int>), t1);
|
||||
|
||||
// TODO FIXME - IsAssignableFromGeneric returns true! does not manage generic parameters binding
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(Dictionary<int, string>), t1);
|
||||
|
||||
// these are all of there from Is_Assignable_To_Generic_Type
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(Derived<int>), typeof(Base<>));
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(List<int>), typeof(IEnumerable<>));
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(Derived<int>), typeof(Derived<>));
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(Derived2<int>), typeof(Base<>));
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(DerivedI<int>), typeof(IBase<>));
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(Derived2<int>), typeof(IBase<>));
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(int?), typeof(Nullable<>));
|
||||
|
||||
// TODO FIXME - those that are marked below should actually be (true,...) but were false in Is_Assignable_To_Generic_Type
|
||||
|
||||
// TODO FIXME - why would IsAssignableFromGeneric returns false? Derived<T> inherits from Object
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(Derived<int>), typeof(Object));
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(Derived<int>), typeof(List<>));
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(Derived<int>), typeof(IEnumerable<>));
|
||||
// TODO FIXME - why would IsAssignableFromGeneric returns false? Derived<T> inherits from Base<T>
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(Derived<int>), typeof(Base<int>));
|
||||
// TODO FIXME - why would IsAssignableFromGeneric returns false? List<T> implements IEnumerable<T>
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(List<int>), typeof(IEnumerable<int>));
|
||||
Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(int), typeof(Nullable<>));
|
||||
|
||||
//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_MatchType_Vs_IsAssignableFromGeneric(true, typeof(List<int>), genericEnumerableNonGenericDefinition);
|
||||
|
||||
}
|
||||
|
||||
private void Assert_MatchType_Vs_IsAssignableFromGeneric(bool expected, Type implementation, Type contract)
|
||||
{
|
||||
if (expected)
|
||||
{
|
||||
Assert.IsTrue(TypeHelper.MatchType(implementation, contract));
|
||||
Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(contract, implementation));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsFalse(TypeHelper.MatchType(implementation, contract));
|
||||
Assert.IsFalse(TypeHelper.IsAssignableFromGeneric(contract, implementation));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateOpenGenericTypes()
|
||||
{
|
||||
// readings
|
||||
// http://stackoverflow.com/questions/13466078/create-open-constructed-type-from-string
|
||||
// http://stackoverflow.com/questions/6704722/c-sharp-language-how-to-get-type-of-bound-but-open-generic-class
|
||||
|
||||
// note that FullName returns "The fully qualified name of the type, including its namespace but not its
|
||||
// assembly; or null if the current instance represents a generic type parameter, an array type, pointer
|
||||
// type, or byref type based on a type parameter, or a generic type that is not a generic type definition
|
||||
// but contains unresolved type parameters."
|
||||
|
||||
var t = Type.GetType("System.Collections.Generic.IList`1");
|
||||
Assert.IsNotNull(t);
|
||||
Assert.IsTrue(t.IsGenericTypeDefinition);
|
||||
Assert.AreEqual("IList`1", t.Name);
|
||||
Assert.AreEqual("System.Collections.Generic.IList`1", t.FullName);
|
||||
Assert.AreEqual("System.Collections.Generic.IList`1[T]", t.ToString());
|
||||
|
||||
t = typeof (IList<>);
|
||||
Assert.IsTrue(t.IsGenericTypeDefinition);
|
||||
Assert.AreEqual("IList`1", t.Name);
|
||||
Assert.AreEqual("System.Collections.Generic.IList`1", t.FullName);
|
||||
Assert.AreEqual("System.Collections.Generic.IList`1[T]", t.ToString());
|
||||
|
||||
t = typeof(IDictionary<,>);
|
||||
Assert.IsTrue(t.IsGenericTypeDefinition);
|
||||
Assert.AreEqual("IDictionary`2", t.Name);
|
||||
Assert.AreEqual("System.Collections.Generic.IDictionary`2", t.FullName);
|
||||
Assert.AreEqual("System.Collections.Generic.IDictionary`2[TKey,TValue]", t.ToString());
|
||||
|
||||
t = typeof(IDictionary<,>);
|
||||
t = t.MakeGenericType(t.GetGenericArguments()[0], t.GetGenericArguments()[1]);
|
||||
Assert.AreEqual("IDictionary`2", t.Name);
|
||||
Assert.AreEqual("System.Collections.Generic.IDictionary`2", t.FullName);
|
||||
Assert.AreEqual("System.Collections.Generic.IDictionary`2[TKey,TValue]", t.ToString());
|
||||
|
||||
t = typeof(IDictionary<,>);
|
||||
t = t.MakeGenericType(t.GetGenericArguments()[0], t.GetGenericArguments()[0]);
|
||||
Assert.IsFalse(t.IsGenericTypeDefinition); // not anymore
|
||||
Assert.AreEqual("IDictionary`2", t.Name);
|
||||
Assert.IsNull(t.FullName); // see note above
|
||||
Assert.AreEqual("System.Collections.Generic.IDictionary`2[TKey,TKey]", t.ToString());
|
||||
|
||||
t = typeof(IList<>);
|
||||
Assert.IsTrue(t.IsGenericTypeDefinition);
|
||||
t = t.MakeGenericType(t.GetGenericArguments()[0]);
|
||||
Assert.AreEqual("IList`1", t.Name);
|
||||
Assert.AreEqual("System.Collections.Generic.IList`1", t.FullName);
|
||||
Assert.AreEqual("System.Collections.Generic.IList`1[T]", t.ToString());
|
||||
|
||||
t = typeof(IList<int>);
|
||||
Assert.AreEqual("System.Collections.Generic.IList`1[System.Int32]", t.ToString());
|
||||
|
||||
t = typeof (IDictionary<,>);
|
||||
t = t.MakeGenericType(typeof(int), t.GetGenericArguments()[1]);
|
||||
Assert.IsFalse(t.IsGenericTypeDefinition); // not anymore
|
||||
Assert.AreEqual("IDictionary`2", t.Name);
|
||||
Assert.IsNull(t.FullName); // see note above
|
||||
Assert.AreEqual("System.Collections.Generic.IDictionary`2[System.Int32,TValue]", t.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user