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);
}
精彩评论