检查对象是否满足通用参数约束

时间:2011-02-01 15:34:11

标签: c# generics reflection

我有一个类似下面的界面:

public interface IInterface<T>
    where T : IInterface<T>
{
}

现在我需要使用反射创建一个表示此接口的类型,例如

typeof(IInterface<>).MakeGenericType(someType);

但是,我实际上并不知道'someType'在运行时会是什么类型,并且该类型可能无法作为泛型接口的类型参数有效,因此MakeGenericType失败。

问题是,如何检查'someType'是否对泛型约束有效?

4 个答案:

答案 0 :(得分:18)

说实话,最简单的方法只是调用MakeGenericType并捕获ArgumentException如果任何类型参数错误(或者你是'我得到了错误的类型参数数量。)

虽然可能使用Type.GetGenericParameterConstraints来查找约束,然后找出每个约束的含义,但这将是丑陋且容易出错的代码。

通常喜欢暗示“只是尝试并抓住”,但在这种情况下,我认为这将是最可靠的方法。否则你只是重新实现CLR将要执行的检查 - 你有什么机会完美地重新实现它们? :)

答案 1 :(得分:4)

这是可能的。给定约束,您使用Type.GenericParameterAttributes和掩码

GenericParameterAttributes.ReferenceTypeConstraint
GenericParameterAttributes.NotNullableValueTypeConstraint
GenericParameterAttributes.DefaultConstructorConstraint

检查是否存在classstructnew()限制。你可以很容易地检查一个给定的类型是否满足这些约束(第一个很容易实现(使用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的MakeGenericTypeMakeGenericMethod

的行为

另请注意,我并未充分了解合作和逆转,因此这些实施在这方面可能不正确。

使用示例:

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);
}