C# 检查对象是否满足常规参数约束

C# 检查对象是否满足常规参数约束,c#,generics,reflection,C#,Generics,Reflection,我有一个类似于下面的界面: public interface IInterface<T> where T : IInterface<T> { } 公共接口接口 T:界面在哪里 { } 现在我需要使用反射创建一个表示这个接口的类型,例如 typeof(IInterface<>).MakeGenericType(someType); typeof(界面)。MakeGenericType(someType); 但是,直到运行时,我才真正知道“some

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

public interface IInterface<T>
    where T : IInterface<T>
{
}
公共接口接口
T:界面在哪里
{
}
现在我需要使用反射创建一个表示这个接口的类型,例如

typeof(IInterface<>).MakeGenericType(someType);
typeof(界面)。MakeGenericType(someType);
但是,直到运行时,我才真正知道“someType”是什么类型,并且该类型可能作为泛型接口的类型参数无效,因此MakeGenericType失败

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

老实说,最简单的方法是调用
MakeGenericType
并捕获
ArgumentException
,如果任何类型参数错误(或者如果类型参数数量错误),将抛出该异常

虽然您可以使用查找约束,然后找出它们各自的含义,但这将是难看且容易出现错误的代码


我通常不喜欢建议“试一试,然后抓住”,但在这种情况下,我认为这将是最可靠的方法。否则,您只需重新实现CLR无论如何都要执行的检查—您完美地重新实现这些检查的机会有多大?:)

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

GenericParameterAttributes.ReferenceTypeConstraint
GenericParameterAttributes.NotNullableValueTypeConstraint
GenericParameterAttributes.DefaultConstructorConstraint
检查是否存在
class
struct
new()
约束。您可以很容易地检查给定类型是否满足这些约束(第一个很容易实现(使用
type.IsClass
),第二个稍微有点棘手,但您可以使用反射来完成,第三个有一点您的单元测试将检测到的问题(
type.GetConstructor(新类型[0]))
不会返回值类型的默认构造函数,但您知道这些值类型仍然有默认构造函数)


在此之后,使用
Type.GetGenericParameterConstraints
获取类型层次结构约束(其中的
与T:Base、IInterface类似的约束),并遍历它们以检查给定类型是否满足它们。

在网上查找类似的内容,由Scott Hanselman编写。阅读后(它很短),并且已经按照@Jon Skeet的答案中的扩展方法进行了思考,我把这个小贴士放在一起,并快速运行了一下:

public static class Extensions
{
    public static bool IsImplementationOf(this System.Type objectType, System.Type interfaceType)
    {
        return (objectType.GetInterface(interfaceType.FullName) != null);
    }
}
它实际上适用于我将其放入的几个测试。当我在一个实现了接口的类型上使用它时,它返回true。当我向它传递了一个没有实现接口的类型时,它失败。我甚至从成功的类型中删除了接口声明,然后重试,它失败了。我像这样使用它:

if (myType.IsImplementationOf(typeof(IFormWithWorker)))
{
    //Do Something
    MessageBox.Show(myType.GetInterface(typeof(DocumentDistributor.Library.IFormWithWorker).FullName).FullName);
}
else
{
    MessageBox.Show("It IS null");
}

我可能会玩弄它,但最终可能会将其发布到:

以下是我对3种扩展方法的实现:

  • bool可以生成GenericTypevia(此类型为openConstructedType,类型为closedConstructedType)
  • 键入MakeGenericTypeVia(此类型为openConstructedType,键入closedConstructedType)
  • MethodInfo MakeGenericMethodVia(此MethodInfo openConstructedMethod,参数类型[]closedConstructedParameterTypes)
第一种方法允许您检查封闭构造的类型是否与开放构造的类型定义匹配。如果匹配,第二种方法可以推断所有必需的类型参数,以从给定的封闭构造类型返回封闭构造。最后,第三种方法可以为方法自动解决所有这些问题

请注意,如果将另一个开放构造类型作为“封闭构造”传递,则这些方法不会失败或返回false类型参数,只要第二个类型尊重初始开放构造类型的所有类型约束。它们将从给定类型中解析尽可能多的类型信息。因此,如果要确保解析提供了完全封闭的构造类型,应检查结果的
是否包含GenericParameters
返回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)
publicstaticvoid-GenericMethod(T0direct,IEnumerable-generic)
其中T0:struct
其中T1:class,new(),i接口
{ }
公共接口接口{}
公共类CandidateA:i接口{private CandidateA();}
公共结构候选对象B:IInterface{}
公共类CandidateC{public CandidateC();}
公共类候选:界面{public CandidateD();}
var方法=GetMethod(“GenericMethod”);
var type0=method.GetParameters()[0].ParameterType;
var type1=method.GetParameters()[1].ParameterType;
//结果:
type0.CanMakeGenericTypeVia(typeof(int))//true
type0.CanMakeGenericTypeVia(typeof(IList))//false,结构失败
type1.CanMakeGenericTypeVia(typeof(IEnumerable))
//false,新()失败
type1.CanMakeGenericTypeVia(typeof(IEnumerable))
//错,上课不及格
type1.CanMakeGenericTypeVia(typeof(IEnumerable))
//错误,失败:接口
type1.CanMakeGenericTypeVia(typeof(IEnumerable))
//真的
type0.MakeGenericTypeVia(typeof(int))
//类型(int)
type1.MakeGenericTypeVia(typeof(列表))
//数不清
方法.MakeGenericMethodVia(123.GetType(),(新候选[0]).GetType())
//GenericMethod(int,IEnumerable)
方法。MakeGenericMethodVia(123.GetType(),type1)
//GenericMethod(int,IEnumerable)
//(部分决议)
实施:

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);
}
public static bool可以生成GenericTypevia(此类型为openConstructedType,类型为closedConstructedType)
{
if(openConstructedType==null)
{
抛出新ArgumentNullException(“openConstructedType”);
}
if(closedConstructedType==null)
{
抛出新ArgumentNullException(“closedConstructedType”);