如何检查类型是否永远不是有效的泛型参数

本文关键字:有效 参数 泛型 永远 是否 何检查 检查 类型 | 更新日期: 2023-09-27 18:26:32

到目前为止,我的代码如下,我想解决的是摆脱try-catch:

public static bool IsNeverValidGenericArgument(this Type type) {
    var elementType=type.GetElementType();
    if(null!=elementType) {
        if(type.IsArray)
            try {
                typeof(IList<>).MakeGenericType(elementType);
                return false;
            }
            catch(ArgumentException) {
            }
            catch(TypeLoadException) {
            }
        return true; // pointer or byref 
    }
    return
        typeof(void)==type||typeof(RuntimeArgumentHandle)==type
        ||
        typeof(ArgIterator)==type||typeof(TypedReference)==type;
}

我正在尝试编写动态类型构造的代码,我的代码会对传递的每个类型调用GetInterfaces(),但消费者代码传递的某些类型可能会在内部导致RuntimeType中的TypeLoadException(例如3.5中的typeof(ArgIterator).MakeArrayType().MakeArrayType(),但不是4.0+),我首先需要检查它是否从来都不是有效的泛型参数。试试抓东西,但没用。

请注意,它抛出的情况可能因.Net框架的不同版本而异。


编辑:

该方法的另一个版本是:

public static bool IsNeverValidGenericArgument(this Type type) {
    var elementType=type.GetElementType();
    if(null!=elementType) {
        if(type.IsArray)
            return elementType.IsNeverValidGenericArgument();
        return true; // pointer or byref 
    }
    return
        typeof(void)==type||typeof(RuntimeArgumentHandle)==type
        ||
        typeof(ArgIterator)==type||typeof(TypedReference)==type;
}

但这会将某些类型报告为无效,而这些类型实际上不会导致RuntimeType中的异常,例如typeof(ArgIterator).MakeArrayType(2).MakeArrayType()

我知道有些类型不是通常使用的,但我无法避免在消费者的代码中使用它们。

如何检查类型是否永远不是有效的泛型参数

当您尝试使用参数typeof(ArgIterator).MakeArrayType().MakeArrayType()构造泛型类型时,抛出异常的是内部本机CLR代码。这一事实最重要的启示是,抛出的是CLR实现细节,而不是决定泛型类型参数有效性的标准或公开的API的一部分。这意味着没有很好的方法来确定是否可以在不实际尝试的情况下构造泛型类型。编辑:这也意味着没有什么好的方法可以确定某些东西是否可以在CLR的特定版本上工作,而不能在另一个版本上工作

但是,更重要的是,如果你试图用一个无效的参数构造一个泛型类型,那确实是一种例外情况,正确的做法是抛出一个异常。我不知道你的代码是做什么的,但如果你担心你的消费者用导致抛出TypeLoadException的类调用它,也许你应该让这些错误冒出来,这样消费者就知道有问题了。

TL;DR:你可能不应该做任何你想做的事情来处理异常情况。就让它扔吧

问题是由于您的客户可能传入的类型没有有效的无公共参数构造函数吗?如果是这样的话,你可以通过在你的通用方法中添加一个条件来限制他们允许发送给你的输入:

public class MyClass<T>
    where T : new()
{
}

此代码只允许具有不带参数的公共构造函数的泛型类型T。你可以在这里找到更多关于where子句的信息

我不知道你是如何实现你的可变类型参数的,但你可以接受一个类型为t的params集合,并添加上面的子句,就像这样:

public class MyClass<T>
    where T : new()
{
    public void MyMethod(params T[] items)
    {
        //...Do stuff...
    }
}

这将允许它们传递任意数量的项,但将它们限制为您想要支持的泛型类型。

如果您添加一些测试用例,以便我们的程序员能够具体了解您的期望,那将非常有用。但你提供了赏金,所以我想看看这是否符合你的要求。

以下代码的结果:

测试有定义测试<>
实际类型为UserQuery+Test `1[System.Int32]

public static class Extensions
{
    /// <summary>
    /// Checks whether this type has the specified definition in its ancestry.
    /// </summary>   
    public static bool HasGenericDefinition(this Type type, Type definition)
    {
        return GetTypeWithGenericDefinition(type, definition) != null;
    }
    /// <summary>
    /// Returns the actual type implementing the specified definition from the
    /// ancestry of the type, if available. Else, null.
    /// </summary>
    public static Type GetTypeWithGenericDefinition(this Type type, Type definition)
    {
        if (type == null) throw new ArgumentNullException("type");
        if (definition == null) throw new ArgumentNullException("definition");
        if (!definition.IsGenericTypeDefinition) throw new ArgumentException("The definition needs to be a GenericTypeDefinition", "definition");
        if (definition.IsInterface)
            foreach (var interfaceType in type.GetInterfaces())
                if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == definition)
                    return interfaceType;
        for (Type t = type; t != null; t = t.BaseType)
            if (t.IsGenericType && t.GetGenericTypeDefinition() == definition)
                return t;
        return null;
    }
}
void Main()
{
    Type a = typeof(Test<int>);
    Type b = typeof(Test<>);
    if(a.HasGenericDefinition(b)) Console.WriteLine("Test<int> has definition Test<>");
    Type c = a.GetTypeWithGenericDefinition(b);
    Console.WriteLine("Actual Type is {0}", c.ToString());
}
public class Test<T>
{
    public Test()
    {
    }
}

我看不出它能比你已经做的更好。这是我的版本:

// Predicts whether the given type cannot be used as a type argument.
public static bool IsNeverValidGenericArgument(this Type type)
{
  if (type == null)
    throw new ArgumentNullException("type");
  // Pointer types and ByRef types.
  if (type.IsPointer || type.IsByRef)
    return true;
  // The following four special cases were found by reflecting through all types in mscorlib.dll, System.dll, and System.Core.dll.
  // The list may be different in other versions of the framework.
  var exceptions = new HashSet<Type>
  {
    typeof(ArgIterator), typeof(RuntimeArgumentHandle), typeof(TypedReference), typeof(void),
  };
  return exceptions.Contains(type);
}

请注意,这只考虑类型系统是否将构造一个以type作为其类型参数的封闭泛型类型。有些类型是毫无意义的,例如:

typeof(IList<>).MakeGenericType(typeof(Math))  // will work
// but C# does not allow the notation IList<Math>

其中CCD_ 10是静态类(抽象和密封类型)。对于这样的静态类,我的方法仍然会返回false

有些类型甚至不存在,比如type = typeof(int).MakeByRefType().MakeArrayType()(将抛出),所以我的方法无法检查这些混蛋。

以下是针对ArgIterator问题的代码"替代"版本的更新。

    public static bool IsNeverValidGenericArgument(this Type type)
    {
        return type.IsNeverValidGenericArgument(true);
    }
    private static bool IsNeverValidGenericArgument(this Type type, bool isRoot)
    {
        var elementType = type.GetElementType();
        if (null != elementType)
        {
            if (type.IsArray)
                return elementType.IsNeverValidGenericArgument(false);
            return true; // pointer or byref 
        }
        if (isRoot)
        {
            return
                typeof(void) == type || typeof(RuntimeArgumentHandle) == type
                ||
                typeof(ArgIterator) == type || typeof(TypedReference) == type;
        }
        else
        {
            return (typeof(void) == type || typeof(TypedReference) == type);
        }
    }