开发者

How can one get the "basic" types in a "container type" in .NET?

I'm after a method which, when passed List<List<int>> will return {int}, when passed double[,] will return {double}, when passed Dictionary<string, byte[]> will return {string, byte} and so on. Basically, it's about recursing down the input type until the type found is not a "container type" anymore and then reporting this "basic" type.

My initial guess was to test the input type for an implementation of IEnumerable, but this didn't seem to work. I also did some trial and error with GetNestedTypes(), but this seems to be unrelated. I ended up with the method below, which relies on ICollection instead. I've thrown some quite weird types and it seems to work; what I would like to know is if the method covers all the container types out there or if it misses something (i.e. does it actually work or are the results of my tests a "happy coincidence"?)

Thanks a lot.

EDIT(1): if passed, say, Dictionary<Foo, Bar>, it's okay for the method to return {Foo, Bar}, regardless of the internal structure of these types (it doesn't need to go beyond that).

    public void GetPrimitiveTypes(Type inputType, ref List<Type> primitiveTypes)
    {
        if (inputType.IsGenericType)
            foreach (Type genericArg in inputType.GetGenericArguments())
                GetPrimitiveTypes(genericArg, ref primitiveTypes);

        if (inputType.IsArray)            
            GetPrimitiveTypes(inputType.GetElementType(), ref primitiveTypes);

        if (inputType.GetInterface("ICollection") == null)
            primitiveTypes.Add(inputType);
    }

EDIT(2): Here's an improved version (I believe). It does some error handling and replaces IsArray with HasElementType (from Josh's answer). I've also decided to leave pointers ouside. Nulls and pointers aside, the assumptions are: (1) All "container" types implement ICollection and (2) All types implementing ICollection are either generic or have an element type. If these assumptions are correct, then I think the method below should work, but that's what I'm not sure about. I've also replaced "primitive" by "basic" in the question, as "primitive" has its own, different meaning.

    public void GetPrimitiveTypes2(Type inputType, ref List<Type> primitiveTypes)
    {
        // Handle null
        if (inputType == null)
            throw new ArgumentNullException("inputType");

        // Leave pointers out
        if (inputType.IsPointer)
            throw new ArgumentException(message: "Pointer types are not supported", paramName: "inputType");

        // Option 1: type is generic
        if (inputType.IsGenericType)
        {
            foreach (Type genericArg in inputType.GetGenericArguments())
                GetPrimitiveTypes2(genericArg, ref primitiveTypes);

            return;
        }

        // Option 2: type has element type
        if (inputType.HasElementType)
        {
            GetPrimitiveTypes2(inputType.GetElementType(), ref primitiveTypes);
            return;
        }

        // Option 3: type is not a container
        // Remark: can't use IsPrimitive as it will be false for, say, type Foo
        if (inputType.GetInterf开发者_高级运维ace("ICollection") == null)
        {
            primitiveTypes.Add(inputType);
            return;
        }

        // Do options 1-3 above cover all cases?
        throw new ArgumentException(message: "Unhandled type", paramName: "inputType");
    }


Here's a method I use. As you'll see it's not much different from yours. It doesn't handle generics with multiple type parameters though.

    /// <summary>
    /// When given certain types such as those based on <see cref="T:Nullable`1"/>, <see cref="T:IEnumerable`1"/>,
    /// or <see cref="T:Array"/>, returns the element type associated with the input type.
    /// </summary>
    /// <remarks>
    /// For example, calling this method with Nullable(Of Boolean) would return Boolean. Passing Int32[] would
    /// return Int32, etc. All other types will return the input.
    /// </remarks>
    /// <param name="type">The a nullable type, array type, etc. whose element type you want to retrieve.</param>
    /// <returns>The type that the input type is based on.</returns>
    public static Type GetElementType( Type type )
    {

        ParameterValidation.ThrowIfNull( type, "type" );

        if ( type.IsGenericType ) {
            var typeArgs = type.GetGenericArguments( );
            if ( typeArgs.Length == 1 ) {
                return typeArgs[0];
            }   // if
        }   // if

        if ( type.HasElementType ) {
            return type.GetElementType( );
        }   // if

        if ( type.IsEnum ) {
            return Enum.GetUnderlyingType( type );
        }   // if

        return type;

    }


Building on Josh's answer, but correcting the terminology and limiting the results to collections/enumerations:

public static Type GetElementType(Type type)
{
    if (type == null)
        throw new ArgumentNullException("type");

    if (type.HasElementType)
        return type.GetElementType();

    Type[] interfaces = type.GetInterfaces();
    foreach (Type t in interfaces)
    {
        if (t.IsGenericType)
        {
            Type generic = t.GetGenericTypeDefinition();
            if (generic == typeof(IEnumerable<>))
                return t.GetGenericArguments()[0];
        }
    }

    /* If you want to allow weakly typed collections (and just have element type
     * Object), you can uncomment the following:
     */
    //if (typeof(IEnumerable).IsAssignableFrom(type))
    //    return typeof(object);

    return null;
}

public static Type GetUnderlyingType(Type type)
{
    if (type == null)
        throw new ArgumentNullException("type");

    if (type.IsEnum)
        return type.GetEnumUnderlyingType();

    return Nullable.GetUnderlyingType(type);
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜