1 // ***********************************************************************
2 // Copyright (c) 2015 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
30 using System.Collections.Generic;
31 using System.Reflection;
32 using NUnit.Compatibility;
34 namespace NUnit.Framework.Internal
37 /// GenericMethodHelper is able to deduce the Type arguments for
38 /// a generic method from the actual arguments provided.
40 public class GenericMethodHelper
43 /// Construct a GenericMethodHelper for a method
45 /// <param name="method">MethodInfo for the method to examine</param>
46 public GenericMethodHelper(MethodInfo method)
48 Guard.ArgumentValid(method.IsGenericMethod, "Specified method must be generic", "method");
52 TypeParms = Method.GetGenericArguments();
53 TypeArgs = new Type[TypeParms.Length];
55 var parms = Method.GetParameters();
56 ParmTypes = new Type[parms.Length];
57 for (int i = 0; i < parms.Length; i++)
58 ParmTypes[i] = parms[i].ParameterType;
61 private MethodInfo Method { get; set; }
63 private Type[] TypeParms { get; set; }
64 private Type[] TypeArgs { get; set; }
66 private Type[] ParmTypes { get; set; }
69 /// Return the type argments for the method, deducing them
70 /// from the arguments actually provided.
72 /// <param name="argList">The arguments to the method</param>
73 /// <returns>An array of type arguments.</returns>
74 public Type[] GetTypeArguments(object[] argList)
76 Guard.ArgumentValid(argList.Length == ParmTypes.Length, "Supplied arguments do not match required method parameters", "argList");
78 for (int argIndex = 0; argIndex < ParmTypes.Length; argIndex++)
80 var arg = argList[argIndex];
84 Type argType = arg.GetType();
85 TryApplyArgType(ParmTypes[argIndex], argType);
92 private void TryApplyArgType(Type parmType, Type argType)
94 if (parmType.IsGenericParameter)
96 ApplyArgType(parmType, argType);
98 else if (parmType.GetTypeInfo().ContainsGenericParameters)
100 var genericArgTypes = parmType.GetGenericArguments();
102 if (argType.HasElementType)
104 ApplyArgType(genericArgTypes[0], argType.GetElementType());
106 else if (argType.GetTypeInfo().IsGenericType && IsAssignableToGenericType(argType, parmType))
108 Type[] argTypes = argType.GetGenericArguments();
110 if (argTypes.Length == genericArgTypes.Length)
111 for (int i = 0; i < genericArgTypes.Length; i++)
112 TryApplyArgType(genericArgTypes[i], argTypes[i]);
117 private void ApplyArgType(Type parmType, Type argType)
119 // Note: parmType must be generic parameter type - checked by caller
121 var index = Array.IndexOf(TypeParms, parmType);
123 var index = parmType.GenericParameterPosition;
125 TypeArgs[index] = TypeHelper.BestCommonType(TypeArgs[index], argType);
128 // Simulates IsAssignableTo generics
129 private bool IsAssignableToGenericType(Type givenType, Type genericType)
131 var interfaceTypes = givenType.GetInterfaces();
133 foreach (var iterator in interfaceTypes)
135 if (iterator.GetTypeInfo().IsGenericType)
137 // The Type returned by GetGenericTyeDefinition may have the
138 // FullName set to null, so we do our own comparison
139 Type gtd = iterator.GetGenericTypeDefinition();
140 if (gtd.Name == genericType.Name && gtd.Namespace == genericType.Namespace)
145 if (givenType.GetTypeInfo().IsGenericType)
147 // The Type returned by GetGenericTyeDefinition may have the
148 // FullName set to null, so we do our own comparison
149 Type gtd = givenType.GetGenericTypeDefinition();
150 if (gtd.Name == genericType.Name && gtd.Namespace == genericType.Namespace)
154 Type baseType = givenType.GetTypeInfo().BaseType;
155 if (baseType == null)
158 return IsAssignableToGenericType(baseType, genericType);