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

时间:2013-08-06 11:02:16

标签: c# .net clr

我到目前为止的代码如下所示,我想要解决的是摆脱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(),但是消费者代码传递的某些类型可能会导致TypeLoadExceptionRuntimeType内部(例如3.5中的typeof(ArgIterator).MakeArrayType().MakeArrayType(),但不是4.0+),我需要首先检查它是否永远不是有效的泛型参数。 try-catch有效,但没有好处。

请注意,它引发的案例可能会因.Net framework的不同版本而异。


修改:

该方法的替代版本是:

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()

我知道某些类型并非正常使用,但我无法避免它们被用于消费者的代码中。

5 个答案:

答案 0 :(得分:2)

当您尝试构造参数为typeof(ArgIterator).MakeArrayType().MakeArrayType()的泛型类型时,它是引发异常的内部本机CLR代码。这个事实最重要的一点是,它是一个抛出的CLR实现细节,它不是标准或公开暴露的API的一部分,它决定了泛型类型参数的有效性。这意味着没有好的方法来确定是否可以在不实际尝试的情况下构建泛型类型。 编辑:这也意味着没有好办法确定某些内容是否适用于特定版本的CLR而不适用于另一种版本。

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

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

答案 1 :(得分:1)

问题是由于您的客户可能传递的类型没有有效的公共参数构造函数吗?如果是这种情况,您可以通过向通用方法添加条件来限制允许他们发送的输入:

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

}

此代码仅允许具有不带参数的公共构造函数的泛型类型T.您可以在where clause here

上找到更多信息

我不知道你如何实现你的可变参数类型参数,但是你可以在添加了上面的子句的情况下接受类型为T的params集合,如下所示:

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

这会让他们传入尽可能多的项目,但将它们限制为您想要支持的泛型类型。

答案 2 :(得分:1)

如果您添加了一些测试用例,以便我们的编码人员可以明确知道您的期望,那将会很有帮助。但你提供了赏金,所以我看到这是否接近你想要的。

以下代码的结果:

测试具有定义Test&lt;&gt;
实际类型是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()
    {
    }
}

答案 3 :(得分:1)

我看不出它可以比你已经完成更多。这是我的版本:

// 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>

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

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

答案 4 :(得分:0)

以下是解决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);
        }
    }