GenericMethodHelper
GenericMethodHelper is able to deduce the Type arguments for
a generic method from the actual arguments provided.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace NUnit.Framework.Internal
{
[NullableContext(1)]
[Nullable(0)]
public class GenericMethodHelper
{
[NullableContext(0)]
private static class ConflictingTypesMarkerClass
{
}
private static readonly Type ConflictingTypesMarker = typeof(ConflictingTypesMarkerClass);
private MethodInfo Method { get; }
private Type[] TypeParms { get; }
private Type[] TypeArgs { get; }
private Type[] ParmTypes { get; }
public GenericMethodHelper(MethodInfo method)
{
Guard.ArgumentValid(method.IsGenericMethod, "Specified method must be generic", "method");
Method = method;
TypeParms = Method.GetGenericArguments();
TypeArgs = new Type[TypeParms.Length];
ParameterInfo[] parameters = Method.GetParameters();
ParmTypes = new Type[parameters.Length];
for (int i = 0; i < parameters.Length; i++) {
ParmTypes[i] = parameters[i].ParameterType;
}
}
public bool TryGetTypeArguments([Nullable(new byte[] {
1,
2
})] object[] argList, [Nullable(new byte[] {
2,
1
})] [NotNullWhen(true)] out Type[] typeArguments)
{
Guard.ArgumentValid(argList.Length == ParmTypes.Length, "Supplied arguments do not match required method parameters", "argList");
for (int i = 0; i < ParmTypes.Length; i++) {
object obj = argList[i];
if (obj != null) {
Type type = obj.GetType();
TryApplyArgType(ParmTypes[i], type);
}
}
Type[] typeArgs = TypeArgs;
foreach (Type type2 in typeArgs) {
if ((object)type2 == null || type2 == ConflictingTypesMarker) {
typeArguments = null;
return false;
}
}
typeArguments = TypeArgs;
return true;
}
private void TryApplyArgType(Type parmType, Type argType)
{
if (parmType.IsGenericParameter)
ApplyArgType(parmType, argType);
else if (parmType.ContainsGenericParameters) {
Type[] array = (!parmType.IsArray) ? parmType.GetGenericArguments() : new Type[1] {
parmType.GetElementType()
};
if (argType.HasElementType)
ApplyArgType(array[0], argType.GetElementType());
else if (argType.IsGenericType && IsAssignableToGenericType(argType, parmType)) {
Type[] genericArguments = argType.GetGenericArguments();
if (genericArguments.Length == array.Length) {
for (int i = 0; i < array.Length; i++) {
TryApplyArgType(array[i], genericArguments[i]);
}
}
} else if (Reflect.IsNullable(parmType) && !argType.IsClass) {
ApplyArgType(array[0], argType);
}
}
}
private void ApplyArgType(Type parmType, Type argType)
{
int genericParameterPosition = parmType.GenericParameterPosition;
if (!TypeHelper.TryGetBestCommonType(TypeArgs[genericParameterPosition], argType, out TypeArgs[genericParameterPosition]))
TypeArgs[genericParameterPosition] = ConflictingTypesMarker;
}
private bool IsAssignableToGenericType(Type givenType, Type genericType)
{
Type[] interfaces = givenType.GetInterfaces();
Type[] array = interfaces;
foreach (Type type in array) {
if (type.IsGenericType) {
Type genericTypeDefinition = type.GetGenericTypeDefinition();
if (genericTypeDefinition.Name == genericType.Name && genericTypeDefinition.Namespace == genericType.Namespace)
return true;
}
}
if (givenType.IsGenericType) {
Type genericTypeDefinition2 = givenType.GetGenericTypeDefinition();
if (genericTypeDefinition2.Name == genericType.Name && genericTypeDefinition2.Namespace == genericType.Namespace)
return true;
}
Type baseType = givenType.BaseType;
if ((object)baseType == null)
return false;
return IsAssignableToGenericType(baseType, genericType);
}
}
}