我有一个类似下面的界面:
public interface IInterface<T>
where T : IInterface<T>
{
}
现在我需要使用反射创建一个表示此接口的类型,例如
typeof(IInterface<>).MakeGenericType(someType);
但是,我实际上并不知道'someType'在运行时会是什么类型,并且该类型可能无法作为泛型接口的类型参数有效,因此MakeGenericType失败。
问题是,如何检查'someType'是否对泛型约束有效?
答案 0 :(得分:18)
说实话,最简单的方法只是调用MakeGenericType
并捕获ArgumentException
如果任何类型参数错误(或者你是'我得到了错误的类型参数数量。)
虽然可能使用Type.GetGenericParameterConstraints
来查找约束,然后找出每个约束的含义,但这将是丑陋且容易出错的代码。
我通常喜欢暗示“只是尝试并抓住”,但在这种情况下,我认为这将是最可靠的方法。否则你只是重新实现CLR将要执行的检查 - 你有什么机会完美地重新实现它们? :)
答案 1 :(得分:4)
这是可能的。给定约束,您使用Type.GenericParameterAttributes
和掩码
GenericParameterAttributes.ReferenceTypeConstraint
GenericParameterAttributes.NotNullableValueTypeConstraint
GenericParameterAttributes.DefaultConstructorConstraint
检查是否存在class
,struct
或new()
限制。你可以很容易地检查一个给定的类型是否满足这些约束(第一个很容易实现(使用Type.IsClass
),第二个有点棘手,但是你可以使用反射来做,第三个有一点点你的单位测试将检测(Type.GetConstructor(new Type[0])
不会返回值类型的默认构造函数,但您知道它们仍然有默认构造函数。)
在此之后,使用Type.GetGenericParameterConstraints
来获取类型层次结构约束(where T : Base, IInterface
类似约束)并运行它们以检查给定类型是否满足它们。
答案 2 :(得分:2)
在网上看一下这样的事情,我是{3}来自Scott Hansen。阅读之后(它很简短),并且已经从@Jon Skeet的回答中考虑了扩展方法的路线,我把这个小小的花絮扔到了一起并快速运行:
public static class Extensions
{
public static bool IsImplementationOf(this System.Type objectType, System.Type interfaceType)
{
return (objectType.GetInterface(interfaceType.FullName) != null);
}
}
它实际上适用于我所说的少数测试。当我在DID实现我传递的接口的类型上使用它时它返回true,当我向它传递一个没有实现接口的类型时它失败了。我甚至从成功类型中删除了接口声明并再次尝试它并且失败了。我这样用过:
if (myType.IsImplementationOf(typeof(IFormWithWorker)))
{
//Do Something
MessageBox.Show(myType.GetInterface(typeof(DocumentDistributor.Library.IFormWithWorker).FullName).FullName);
}
else
{
MessageBox.Show("It IS null");
}
我可能会玩它但我最终可能会发布到:found this article
答案 3 :(得分:0)
这是我对3种扩展方法的实现:
bool CanMakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType)
Type MakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType)
MethodInfo MakeGenericMethodVia(this MethodInfo openConstructedMethod, params Type[] closedConstructedParameterTypes)
第一个允许您检查闭合构造的类型是否与开放构造的类型定义匹配。如果是这样,第二个可以推断出所有必需的类型参数,以从给定的闭合构造类型返回一个闭合构造。最后,第三种方法可以自动解决所有这些方法。
请注意,如果将另一个开放构造的类型作为“闭合构造”类型参数传递,则这些方法不会失败或返回false,只要此第二种类型遵循初始开放构造类型的所有类型约束。相反,他们将从给定类型中解析尽可能多的类型信息。因此,如果要确保分辨率为完全闭合构造类型,则应检查结果ContainsGenericParameters
是否返回false。这符合.NET的MakeGenericType
或MakeGenericMethod
。
另请注意,我并未充分了解合作和逆转,因此这些实施在这方面可能不正确。
使用示例:
public static void GenericMethod<T0, T1>(T0 direct, IEnumerable<T1> generic)
where T0 : struct
where T1 : class, new(), IInterface
{ }
public interface IInterface { }
public class CandidateA : IInterface { private CandidateA(); }
public struct CandidateB : IInterface { }
public class CandidateC { public CandidateC(); }
public class CandidateD : IInterface { public CandidateD(); }
var method = GetMethod("GenericMethod");
var type0 = method.GetParameters()[0].ParameterType;
var type1 = method.GetParameters()[1].ParameterType;
// Results:
type0.CanMakeGenericTypeVia(typeof(int)) // true
type0.CanMakeGenericTypeVia(typeof(IList)) // false, fails struct
type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateA>))
// false, fails new()
type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateB>))
// false, fails class
type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateC>))
// false, fails : IInterface
type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateD>))
// true
type0.MakeGenericTypeVia(typeof(int))
// typeof(int)
type1.MakeGenericTypeVia(typeof(List<CandidateD>))
// IEnumerable<CandidateD>
method.MakeGenericMethodVia(123.GetType(), (new CandidateD[0]).GetType())
// GenericMethod(int, IEnumerable<CandidateD>)
method.MakeGenericMethodVia(123.GetType(), type1)
// GenericMethod<T1>(int, IEnumerable<T1>)
// (partial resolution)
实现:
public static bool CanMakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType)
{
if (openConstructedType == null)
{
throw new ArgumentNullException("openConstructedType");
}
if (closedConstructedType == null)
{
throw new ArgumentNullException("closedConstructedType");
}
if (openConstructedType.IsGenericParameter) // e.g.: T
{
// The open-constructed type is a generic parameter.
// First, check if all special attribute constraints are respected.
var constraintAttributes = openConstructedType.GenericParameterAttributes;
if (constraintAttributes != GenericParameterAttributes.None)
{
// e.g.: where T : struct
if (constraintAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint) &&
!closedConstructedType.IsValueType)
{
return false;
}
// e.g.: where T : class
if (constraintAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint) &&
closedConstructedType.IsValueType)
{
return false;
}
// e.g.: where T : new()
if (constraintAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint) &&
closedConstructedType.GetConstructor(Type.EmptyTypes) == null)
{
return false;
}
// TODO: Covariance and contravariance?
}
// Then, check if all type constraints are respected.
// e.g.: where T : BaseType, IInterface1, IInterface2
foreach (var constraint in openConstructedType.GetGenericParameterConstraints())
{
if (!constraint.IsAssignableFrom(closedConstructedType))
{
return false;
}
}
return true;
}
else if (openConstructedType.ContainsGenericParameters)
{
// The open-constructed type is not a generic parameter but contains generic parameters.
// It could be either a generic type or an array.
if (openConstructedType.IsGenericType) // e.g. Generic<T1, int, T2>
{
// The open-constructed type is a generic type.
var openConstructedGenericDefinition = openConstructedType.GetGenericTypeDefinition(); // e.g.: Generic<,,>
var openConstructedGenericArguments = openConstructedType.GetGenericArguments(); // e.g.: { T1, int, T2 }
// Check a list of possible candidate closed-constructed types:
// - the closed-constructed type itself
// - its base type, if any (i.e.: if the closed-constructed type is not object)
// - its implemented interfaces
var inheritedClosedConstructedTypes = new List<Type>();
inheritedClosedConstructedTypes.Add(closedConstructedType);
if (closedConstructedType.BaseType != null)
{
inheritedClosedConstructedTypes.Add(closedConstructedType.BaseType);
}
inheritedClosedConstructedTypes.AddRange(closedConstructedType.GetInterfaces());
foreach (var inheritedClosedConstructedType in inheritedClosedConstructedTypes)
{
if (inheritedClosedConstructedType.IsGenericType &&
inheritedClosedConstructedType.GetGenericTypeDefinition() == openConstructedGenericDefinition)
{
// The inherited closed-constructed type and the open-constructed type share the same generic definition.
var inheritedClosedConstructedGenericArguments = inheritedClosedConstructedType.GetGenericArguments(); // e.g.: { float, int, string }
// For each open-constructed generic argument, recursively check if it
// can be made into a closed-constructed type via the closed-constructed generic argument.
for (int i = 0; i < openConstructedGenericArguments.Length; i++)
{
if (!openConstructedGenericArguments[i].CanMakeGenericTypeVia(inheritedClosedConstructedGenericArguments[i])) // !T1.IsAssignableFromGeneric(float)
{
return false;
}
}
// The inherited closed-constructed type matches the generic definition of
// the open-constructed type and each of its type arguments are assignable to each equivalent type
// argument of the constraint.
return true;
}
}
// The open-constructed type contains generic parameters, but no
// inherited closed-constructed type has a matching generic definition.
return false;
}
else if (openConstructedType.IsArray) // e.g. T[]
{
// The open-constructed type is an array.
if (!closedConstructedType.IsArray ||
closedConstructedType.GetArrayRank() != openConstructedType.GetArrayRank())
{
// Fail if the closed-constructed type isn't an array of the same rank.
return false;
}
var openConstructedElementType = openConstructedType.GetElementType();
var closedConstructedElementType = closedConstructedType.GetElementType();
return openConstructedElementType.CanMakeGenericTypeVia(closedConstructedElementType);
}
else
{
// I don't believe this can ever happen.
throw new NotImplementedException("Open-constructed type contains generic parameters, but is neither an array nor a generic type.");
}
}
else
{
// The open-constructed type does not contain generic parameters,
// we can proceed to a regular closed-type check.
return openConstructedType.IsAssignableFrom(closedConstructedType);
}
}
public static Type MakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType, Dictionary<Type, Type> resolvedGenericParameters, bool safe = true)
{
if (openConstructedType == null)
{
throw new ArgumentNullException("openConstructedType");
}
if (closedConstructedType == null)
{
throw new ArgumentNullException("closedConstructedType");
}
if (resolvedGenericParameters == null)
{
throw new ArgumentNullException("resolvedGenericParameters");
}
if (safe && !openConstructedType.CanMakeGenericTypeVia(closedConstructedType))
{
throw new InvalidOperationException("Open-constructed type is not assignable from closed-constructed type.");
}
if (openConstructedType.IsGenericParameter) // e.g.: T
{
// The open-constructed type is a generic parameter.
// We can directly map it to the closed-constructed type.
// Because this is the lowest possible level of type resolution,
// we will add this entry to our list of resolved generic parameters
// in case we need it later (e.g. for resolving generic methods).
// Note that we allow an open-constructed type to "make" another
// open-constructed type, as long as the former respects all of
// the latter's constraints. Therefore, we will only add the resolved
// parameter to our dictionary if it actually is resolved.
if (!closedConstructedType.ContainsGenericParameters)
{
if (resolvedGenericParameters.ContainsKey(openConstructedType))
{
if (resolvedGenericParameters[openConstructedType] != closedConstructedType)
{
throw new InvalidOperationException("Nested generic parameters resolve to different values.");
}
}
else
{
resolvedGenericParameters.Add(openConstructedType, closedConstructedType);
}
}
return closedConstructedType;
}
else if (openConstructedType.ContainsGenericParameters) // e.g.: Generic<T1, int, T2>
{
// The open-constructed type is not a generic parameter but contains generic parameters.
// It could be either a generic type or an array.
if (openConstructedType.IsGenericType) // e.g. Generic<T1, int, T2>
{
// The open-constructed type is a generic type.
var openConstructedGenericDefinition = openConstructedType.GetGenericTypeDefinition(); // e.g.: Generic<,,>
var openConstructedGenericArguments = openConstructedType.GetGenericArguments(); // e.g.: { T1, int, T2 }
// Check a list of possible candidate closed-constructed types:
// - the closed-constructed type itself
// - its base type, if any (i.e.: if the closed-constructed type is not object)
// - its implemented interfaces
var inheritedCloseConstructedTypes = new List<Type>();
inheritedCloseConstructedTypes.Add(closedConstructedType);
if (closedConstructedType.BaseType != null)
{
inheritedCloseConstructedTypes.Add(closedConstructedType.BaseType);
}
inheritedCloseConstructedTypes.AddRange(closedConstructedType.GetInterfaces());
foreach (var inheritedCloseConstructedType in inheritedCloseConstructedTypes)
{
if (inheritedCloseConstructedType.IsGenericType &&
inheritedCloseConstructedType.GetGenericTypeDefinition() == openConstructedGenericDefinition)
{
// The inherited closed-constructed type and the open-constructed type share the same generic definition.
var inheritedClosedConstructedGenericArguments = inheritedCloseConstructedType.GetGenericArguments(); // e.g.: { float, int, string }
// For each inherited open-constructed type generic argument, recursively resolve it
// via the equivalent closed-constructed type generic argument.
var closedConstructedGenericArguments = new Type[openConstructedGenericArguments.Length];
for (int j = 0; j < openConstructedGenericArguments.Length; j++)
{
closedConstructedGenericArguments[j] = MakeGenericTypeVia
(
openConstructedGenericArguments[j],
inheritedClosedConstructedGenericArguments[j],
resolvedGenericParameters,
safe: false // We recursively checked before, no need to do it again
);
// e.g.: Resolve(T1, float)
}
// Construct the final closed-constructed type from the resolved arguments
return openConstructedGenericDefinition.MakeGenericType(closedConstructedGenericArguments);
}
}
// The open-constructed type contains generic parameters, but no
// inherited closed-constructed type has a matching generic definition.
// This cannot happen in safe mode, but could in unsafe mode.
throw new InvalidOperationException("Open-constructed type is not assignable from closed-constructed type.");
}
else if (openConstructedType.IsArray) // e.g. T[]
{
var arrayRank = openConstructedType.GetArrayRank();
// The open-constructed type is an array.
if (!closedConstructedType.IsArray ||
closedConstructedType.GetArrayRank() != arrayRank)
{
// Fail if the closed-constructed type isn't an array of the same rank.
// This cannot happen in safe mode, but could in unsafe mode.
throw new InvalidOperationException("Open-constructed type is not assignable from closed-constructed type.");
}
var openConstructedElementType = openConstructedType.GetElementType();
var closedConstructedElementType = closedConstructedType.GetElementType();
return openConstructedElementType.MakeGenericTypeVia
(
closedConstructedElementType,
resolvedGenericParameters,
safe: false
).MakeArrayType(arrayRank);
}
else
{
// I don't believe this can ever happen.
throw new NotImplementedException("Open-constructed type contains generic parameters, but is neither an array nor a generic type.");
}
}
else
{
// The open-constructed type does not contain generic parameters,
// it is by definition already resolved.
return openConstructedType;
}
}
public static MethodInfo MakeGenericMethodVia(this MethodInfo openConstructedMethod, params Type[] closedConstructedParameterTypes)
{
if (openConstructedMethod == null)
{
throw new ArgumentNullException("openConstructedMethod");
}
if (closedConstructedParameterTypes == null)
{
throw new ArgumentNullException("closedConstructedParameterTypes");
}
if (!openConstructedMethod.ContainsGenericParameters)
{
// The method contains no generic parameters,
// it is by definition already resolved.
return openConstructedMethod;
}
var openConstructedParameterTypes = openConstructedMethod.GetParameters().Select(p => p.ParameterType).ToArray();
if (openConstructedParameterTypes.Length != closedConstructedParameterTypes.Length)
{
throw new ArgumentOutOfRangeException("closedConstructedParameterTypes");
}
var resolvedGenericParameters = new Dictionary<Type, Type>();
for (int i = 0; i < openConstructedParameterTypes.Length; i++)
{
// Resolve each open-constructed parameter type via the equivalent
// closed-constructed parameter type.
var openConstructedParameterType = openConstructedParameterTypes[i];
var closedConstructedParameterType = closedConstructedParameterTypes[i];
openConstructedParameterType.MakeGenericTypeVia(closedConstructedParameterType, resolvedGenericParameters);
}
// Construct the final closed-constructed method from the resolved arguments
var openConstructedGenericArguments = openConstructedMethod.GetGenericArguments();
var closedConstructedGenericArguments = openConstructedGenericArguments.Select(openConstructedGenericArgument =>
{
// If the generic argument has been successfully resolved, use it;
// otherwise, leave the open-constructe argument in place.
if (resolvedGenericParameters.ContainsKey(openConstructedGenericArgument))
{
return resolvedGenericParameters[openConstructedGenericArgument];
}
else
{
return openConstructedGenericArgument;
}
}).ToArray();
return openConstructedMethod.MakeGenericMethod(closedConstructedGenericArguments);
}