如何验证类型是否可以传递给C#中的泛型方法,还可以基于传递的参数恢复泛型参数类型



让我试着解释一下我的意思。例如,我有以下代码:

public static class MyClass
{
public static void DoSmthWithCollection<T>(IEnumerable<T> collection)
{
...
}
}

当然,编译器会允许向它传递一个List<int>类型的对象。此外,编译器会根据参数类型将方法的类型T解析为int类型。

我只需要通过使用System.Reflection或Roslyn动态地执行同样的操作。假设我得到该方法的MethodInfo,我得到通用ParameterInfo。有没有什么简单的方法可以弄清楚typeof(List<int>)类型的对象实际上适合作为该方法的参数,并且当您传递它时,该方法将T解析为int类型?我说的是一些相对简单的解决方案,而不需要检查类型和子类型以及泛型类型约束。我认为Roslyn至少应该有一个,因为这就是C#编译器正在做的事情。

谢谢你的帮助。

我不会称之为简单,但我写了一些似乎有效的东西。

private IEnumerable<Type> BaseTypes(Type t)
{
do
{
yield return t;
t = t.BaseType;
} while (t != null);
}

var method = new Action<IEnumerable<object>>(MyClass.DoSmthWithCollection).Method.GetGenericMethodDefinition();
var testParms = new Type[] { typeof(List<int>) };
var methodParms = method.GetParameters();
var methodTypes = method.GetGenericArguments();
var actualTypes = new Type[methodTypes.Length];
for (var parmIndex = 0; parmIndex < methodParms.Length; parmIndex++)
{
var methodParmType = methodParms[parmIndex].ParameterType;
if (!methodParmType.ContainsGenericParameters)
continue;
var methodParmBaseType = methodParmType.GetGenericTypeDefinition();
var testParmType = testParms[parmIndex];
IEnumerable<Type> compareTypes = methodParmType.IsInterface ? testParmType.GetInterfaces() : BaseTypes(testParmType);
var match = compareTypes.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == methodParmBaseType).Single();
var testArgs = match.GetGenericArguments();
var parmArgs = methodParmType.GetGenericArguments();
for (var i = 0; i < parmArgs.Length; i++)
{
if (parmArgs[i].IsGenericMethodParameter)
{
for(var idx = 0; idx < methodTypes.Length; idx++)
if (methodTypes[idx] == parmArgs[i])
{
actualTypes[idx] = testArgs[i];
break;
}
}
}
}
var genericMethod = method.MakeGenericMethod(actualTypes);

我希望有更简单的方法,但我还没有找到。

我想我用Jeremy Lakeman下面的例子中的一些想法想出了一个不错的解决方案。它甚至考虑了约束(尽管我认为它会在递归约束上崩溃(。

public static bool CheckConcreteTypeSatisfiesGenericParamConstraints(this Type concreteType, Type genericParamType)
{
bool hasReferenceTypeConstraint = genericParamType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint);
bool hasNewConstraint =
genericParamType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint);
bool isNonNullable = genericParamType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint);
if (hasReferenceTypeConstraint)
{
if (concreteType.IsValueType)
return false;
}
else if (isNonNullable && !concreteType.IsValueType)
{
return true;
}
if (hasNewConstraint)
{
ConstructorInfo constrInfo = concreteType.GetConstructor(new Type[] { });
if (constrInfo != null)
return false;
}
Type[] constraintTypes = genericParamType.GetGenericParameterConstraints();
if (constraintTypes == null)
return true;
Type[] concreteTypeSuperTypes = concreteType.GetSelfSuperTypesAndInterfaces().Distinct().ToArray();
foreach(Type constraintType in constraintTypes)
{
if (!concreteTypeSuperTypes.Contains(constraintType))
{
return false;
}
}
return true;
}

public static IEnumerable<Type> GetSelfSuperTypesAndInterfaces(this Type type)
{
if (type != null)
{
yield return type;
}
if (type.BaseType != null)
{
foreach (var superType in type.BaseType.GetSelfSuperTypesAndInterfaces())
{
yield return superType;
}
}
foreach (var interfaceType in type.GetInterfaces())
{
foreach (var superInterface in interfaceType.GetSelfSuperTypesAndInterfaces())
{
yield return superInterface;
}
}
}
public class GenericParamInfo
{
public Type GenericParamType { get; }
public Type ConcreteType { get; set; }
public GenericParamInfo(Type genericParamType)
{
GenericParamType = genericParamType;
}
}
// returns false if cannot resolve
public static bool ResolveType
(
this Type argToResolveType, 
Type genericArgType, 
IEnumerable<GenericParamInfo> genericTypeParamsToConcretize)
{
if (genericArgType.IsGenericParameter)
{
GenericParamInfo foundParamInfo = genericTypeParamsToConcretize.Single(t => t.GenericParamType == genericArgType);
if (!argToResolveType.CheckConcreteTypeSatisfiesGenericParamConstraints(foundParamInfo.GenericParamType))
{
return false;
}
foundParamInfo.ConcreteType = argToResolveType;
return true;
}
else if (genericArgType.IsGenericType)
{
var matchingSuperType =
argToResolveType.GetSelfSuperTypesAndInterfaces()
.Distinct()
.Where(arg => arg.IsGenericType)
.Single(arg => arg.GetGenericTypeDefinition() == genericArgType.GetGenericTypeDefinition());
if (matchingSuperType == null)
return false;
Type[] concreteArgs = matchingSuperType.GetGenericArguments();
Type[] genericArgs = genericArgType.GetGenericArguments();
foreach((Type concrArgType, Type genArgType) in concreteArgs.Zip(genericArgs, (c, g) => (c, g)))
{
if (!concrArgType.ResolveType(genArgType, genericTypeParamsToConcretize))
return false;
}
return true;
}
return true;
}

下面,我展示了如何在3种方法上进行测试:DoSmth1<T1, T2>(IEnumerable<KeyValuePair<T1, T2>> collection)DoSmth2<T>(T val)DoSmth3<T>(IEnumerable<T> val):

public static void DoSmth1<T1, T2>(IEnumerable<KeyValuePair<T1, T2>> collection)
where T2 : struct
{
}
public static void DoSmth2<T>(T val)
{
}
public static void DoSmth3<T>(IEnumerable<T> val)
{
}
static (bool, GenericParamInfo[]) TestResolveMethodType(Type argToResolve, string methodName)
{
MethodInfo methodInfo = typeof(Program).GetMethod(methodName);
//assuming that we always try to resolve the first method parameter for simplicity
Type genericArgType = methodInfo.GetParameters()[0].ParameterType;
GenericParamInfo[] typesToConcretize =
methodInfo.GetGenericArguments().Select(t => new GenericParamInfo(t)).ToArray();
bool result = argToResolve.ResolveType(genericArgType, typesToConcretize);
return (result, typesToConcretize);
}

static void Main(string[] args)
{
(bool result1, GenericParamInfo[] concretizedTypes1) = 
TestResolveMethodType(typeof(Dictionary<int, double>), nameof(DoSmth1));
(bool result2, GenericParamInfo[] concretizedTypes2) =
TestResolveMethodType(typeof(int), nameof(DoSmth2));
(bool result3, GenericParamInfo[] concretizedTypes3) =
TestResolveMethodType(typeof(List<int>), nameof(DoSmth3));
}

最新更新