How can I get the "base" types in the "container type" in .NET?
I get a method which, when transmitted List<List<int>>, will return {int}, the transfer double[,]will be back {double}when the transfer Dictionary<string, byte[]>will be back {string, byte}and so on. Basically, it is about recursion by input type until the found type is no longer a "container type", and then reports this "base" type.
My initial assumption was to check the type of input to implement IEnumerable, but that didn't seem to work. I also made some trial and error with GetNestedTypes(), but this does not seem to be related. I ended up with the method below, which relies on instead ICollection. I have selected some rather strange types, and it seems to work; what I would like to know is that the method covers all types of containers or if it skips something (i.e. does this really work or are the results of my tests a “happy coincidence”?)
Many thanks.
EDIT (1): if passed, say, Dictionary<Foo, Bar>it's ok if the method returns {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): ( ). IsArray HasElementType ( ). ouside. , : (1) "" ICollection (2) , ICollection, , . , , , , . "" "" , "" , .
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.GetInterface("ICollection") == null)
{
primitiveTypes.Add(inputType);
return;
}
// Do options 1-3 above cover all cases?
throw new ArgumentException(message: "Unhandled type", paramName: "inputType");
}
. , . .
/// <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;
}
, /:
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);
}