// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Nova.Utilities
{
/// <summary>
/// Static helper methods for <see cref="Type"/>.
/// </summary>
public static class TypeUtil
{
#region /* STATIC HELPER METHODS */
/// <summary>
/// Returns the name without any trailing '`' and type parameter count.
/// </summary>
public static string NonGenericName(Type thisType)
{
// The names of generic types have a trailing "`" followed by the number of type arguments that they
// have (generic methods do NOT). However, the type argument count only includes those arguments at
// the current level, and not any arguments of enclosing types, even though calling GetGenericArguments()
// will return all type arguments from all levels. If a nested type has one or more generic types
// enclosing it, then it's considered a generic type even if it doesn't have any type parameters of
// its own (IsGenericType will be true, and GetGenericArguments() will return the type arguments of
// its enclosing generic types), however its name won't have the "`" suffix.
string name = thisType.Name;
int index = name.LastIndexOf('`');
return (index >= 0 ? name.Substring(0, index) : name);
}
/// <summary>
/// Return true if the type is static, otherwise false.
/// </summary>
public static bool IsStatic(Type thisType)
{
return (thisType.IsAbstract && thisType.IsSealed);
}
/// <summary>
/// Return true if the type is private, otherwise false.
/// </summary>
public static bool IsPrivate(Type thisType)
{
return (thisType.IsNested && thisType.IsNestedPrivate);
}
/// <summary>
/// Return true if the type is protected, otherwise false.
/// </summary>
public static bool IsProtected(Type thisType)
{
return (thisType.IsNested && thisType.IsNestedFamily);
}
/// <summary>
/// Return true if the type is internal, otherwise false.
/// </summary>
public static bool IsInternal(Type thisType)
{
return (thisType.IsNested ? thisType.IsNestedAssembly : thisType.IsNotPublic);
}
/// <summary>
/// Determine if the type is a user-defined class (excludes 'object' and 'string').
/// </summary>
public static bool IsUserClass(Type thisType)
{
// Exclude built-in types that are classes (object and string)
return (thisType.IsClass && thisType != typeof(object) && thisType != typeof(string));
}
/// <summary>
/// Determine if the type is a user-defined struct (excludes primitive types including 'void' and 'decimal', and enums).
/// </summary>
public static bool IsUserStruct(Type thisType)
{
// Exclude primitive types, enums, and built-in types that are value types but aren't primitive (void and decimal)
return (thisType.IsValueType && !thisType.IsPrimitive && !thisType.IsEnum && thisType != typeof(void) && thisType != typeof(decimal));
}
/// <summary>
/// Determine if the type is a nullable type.
/// </summary>
public static bool IsNullableType(Type thisType)
{
return (thisType.IsGenericType && thisType.GetGenericTypeDefinition() == typeof(Nullable<>) && !thisType.IsGenericTypeDefinition);
}
/// <summary>
/// Determine if the type is a delegate type.
/// </summary>
public static bool IsDelegateType(Type thisType)
{
return typeof(Delegate).IsAssignableFrom(thisType);
}
/// <summary>
/// The name of the flags attribute.
/// </summary>
public const string FlagsAttributeName = "FlagsAttribute";
/// <summary>
/// Determine if the type is a bit-flags style enum.
/// </summary>
public static bool IsBitFlagsEnum(Type thisType)
{
return MemberInfoUtil.HasCustomAttribute(thisType, FlagsAttributeName);
}
/// <summary>
/// Determine if the type implements the specified interface. If a generic interface is specified, and
/// it's not the generic type definition of the interface, then the type arguments are also compared.
/// </summary>
public static bool IsImplementationOf(Type thisType, Type interfaceType)
{
if (interfaceType.IsGenericType)
{
if (interfaceType.IsGenericTypeDefinition)
{
// If the specified interface is a generic type definition, then match on generic type definitions
foreach (Type @interface in thisType.GetInterfaces())
{
if (@interface.IsGenericType && @interface.GetGenericTypeDefinition() == interfaceType)
return true;
}
}
else
{
// If the specified interface isn't a generic type definition, then also match on any type arguments
foreach (Type @interface in thisType.GetInterfaces())
{
if (@interface.IsGenericType && @interface == interfaceType && HasSameTypeArguments(@interface, interfaceType))
return true;
}
}
}
else
{
// Handle non-generic interfaces
foreach (Type @interface in thisType.GetInterfaces())
{
if (@interface == interfaceType)
return true;
}
}
return false;
}
/// <summary>
/// Make an array type.
/// </summary>
public static Type MakeArrayType(Type thisType, List<int> arrayRanks)
{
Type type = thisType;
foreach (int dim in arrayRanks)
type = type.MakeArrayType(dim);
return type;
}
/// <summary>
/// Get the parameters for a delegate type.
/// </summary>
public static ParameterInfo[] GetDelegateParameters(Type thisType)
{
MethodInfo methodInfo = GetInvokeMethod(thisType);
return (methodInfo != null ? methodInfo.GetParameters() : null);
}
/// <summary>
/// Get the return type for a delegate type.
/// </summary>
public static Type GetDelegateReturnType(Type thisType)
{
MethodInfo methodInfo = GetInvokeMethod(thisType);
return (methodInfo != null ? methodInfo.ReturnType : null);
}
/// <summary>
/// Get the 'Invoke()' method for the specified <see cref="Type"/>.
/// </summary>
public static MethodInfo GetInvokeMethod(Type type)
{
// As a workaround for a possible fix in one of the TypeRef constructors that we haven't activated
// because it breaks other things, make certain that we're using the generic type definition, so
// that any type parameters in the method signature reflect the ones from the definition as opposed
// to those from a parent generic type or method.
if (type.IsGenericType && !type.IsGenericTypeDefinition)
type = type.GetGenericTypeDefinition();
return type.GetMethod("Invoke");
}
/// <summary>
/// Get the number of local generic arguments for the type, NOT including arguments from any enclosing generic types.
/// </summary>
public static int GetLocalGenericArgumentCount(Type thisType)
{
int count = thisType.GetGenericArguments().Length;
if (count > 0 && thisType.IsNested)
{
Type declaringType = thisType.DeclaringType;
if (declaringType != null)
count -= declaringType.GetGenericArguments().Length;
}
return count;
}
/// <summary>
/// Get the local generic arguments for the type, NOT including arguments from any enclosing generic types.
/// </summary>
public static Type[] GetLocalGenericArguments(Type thisType)
{
Type[] totalArguments = thisType.GetGenericArguments();
int totalCount = totalArguments.Length;
if (totalCount == 0 || !thisType.IsNested)
return totalArguments;
int localCount = GetLocalGenericArgumentCount(thisType);
if (localCount == totalCount)
return totalArguments;
Type[] localArguments = new Type[localCount];
if (localCount > 0)
Array.Copy(totalArguments, totalCount - localCount, localArguments, 0, localCount);
return localArguments;
}
/// <summary>
/// Determine if this type has the same type arguments as the specified type.
/// </summary>
public static bool HasSameTypeArguments(Type thisType, Type type)
{
Type[] thisArguments = thisType.GetGenericArguments();
Type[] arguments = type.GetGenericArguments();
if (thisArguments.Length != arguments.Length)
return false;
for (int i = 0; i < thisArguments.Length; ++i)
{
if (thisArguments[i] != arguments[i])
return false;
}
return true;
}
/// <summary>
/// Special type used to match any generic parameter type in GetMethodExt().
/// </summary>
public class T
{
}
/// <summary>
/// Search for a method by name and parameter types. Unlike GetMethod(), does 'loose' matching on generic
/// parameter types, and searches base interfaces.
/// </summary>
/// <exception cref="AmbiguousMatchException"/>
public static MethodInfo GetMethod(Type thisType, string name, params Type[] parameterTypes)
{
return GetMethod(thisType, name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy, parameterTypes);
}
/// <summary>
/// Search for a method by name, parameter types, and binding flags. Unlike GetMethod(), does 'loose' matching on generic
/// parameter types, and searches base interfaces.
/// </summary>
/// <exception cref="AmbiguousMatchException"/>
public static MethodInfo GetMethod(Type thisType, string name, BindingFlags bindingFlags, params Type[] parameterTypes)
{
MethodInfo matchingMethod = null;
// Check all methods with the specified name, including in base classes
GetMethod(ref matchingMethod, thisType, name, bindingFlags, parameterTypes);
// If we're searching an interface, we have to manually search base interfaces
if (matchingMethod == null && thisType.IsInterface)
{
foreach (Type interfaceType in thisType.GetInterfaces())
GetMethod(ref matchingMethod, interfaceType, name, bindingFlags, parameterTypes);
}
return matchingMethod;
}
private static void GetMethod(ref MethodInfo matchingMethod, Type type, string name, BindingFlags bindingFlags, params Type[] parameterTypes)
{
// Check all methods with the specified name, including in base classes if BindingFlags.FlattenHierarchy is specified
foreach (MethodInfo methodInfo in type.GetMember(name, MemberTypes.Method, bindingFlags))
{
// Check that the parameter counts and types match, with 'loose' matching on generic parameters
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
if (parameterInfos.Length == parameterTypes.Length)
{
int i = 0;
for (; i < parameterInfos.Length; ++i)
{
if (!IsSimilarType(parameterInfos[i].ParameterType, parameterTypes[i]))
break;
}
if (i == parameterInfos.Length)
{
if (matchingMethod == null)
{
matchingMethod = methodInfo;
return;
}
}
}
}
}
/// <summary>
/// Search for a property by name. Unlike GetProperty(), searches base interfaces.
/// </summary>
/// <exception cref="AmbiguousMatchException"/>
public static PropertyInfo GetProperty(Type thisType, string name)
{
PropertyInfo matchingProperty = thisType.GetProperty(name);
// If we're searching an interface, we have to manually search base interfaces
if (matchingProperty == null && thisType.IsInterface)
{
foreach (Type interfaceType in thisType.GetInterfaces())
{
matchingProperty = interfaceType.GetProperty(name);
if (matchingProperty != null)
break;
}
}
return matchingProperty;
}
/// <summary>
/// Determines if the two types are either identical, or are both generic parameters or generic types
/// with generic parameters in the same locations (generic parameters match any other generic parameter,
/// but NOT concrete types).
/// </summary>
private static bool IsSimilarType(Type thisType, Type type)
{
// Ignore any 'ref' types
if (thisType.IsByRef)
thisType = thisType.GetElementType();
if (type.IsByRef)
type = type.GetElementType();
// Handle array types
if (thisType.IsArray && type.IsArray)
return IsSimilarType(thisType.GetElementType(), type.GetElementType());
// If the types are identical, or the names/namespaces match (they could be defined in separate assemblies), or
// if they're both generic parameters or the special 'T' type, treat as a match.
if (thisType == type || (thisType.Name == type.Name && thisType.Namespace == type.Namespace)
|| ((thisType.IsGenericParameter || thisType == typeof(T)) && (type.IsGenericParameter || type == typeof(T))))
return true;
// Handle any generic arguments
if (thisType.IsGenericType && type.IsGenericType)
{
Type[] thisArguments = thisType.GetGenericArguments();
Type[] arguments = type.GetGenericArguments();
if (thisArguments.Length == arguments.Length)
{
for (int i = 0; i < thisArguments.Length; ++i)
{
if (!IsSimilarType(thisArguments[i], arguments[i]))
return false;
}
return true;
}
}
return false;
}
/// <summary>
/// Find a type argument for the specified type parameter.
/// </summary>
public static Type FindTypeArgument(Type thisType, Type typeParameter)
{
if (thisType.IsGenericType)
{
foreach (Type genericParameter in thisType.GetGenericTypeDefinition().GetGenericArguments())
{
if (genericParameter == typeParameter)
return thisType.GetGenericArguments()[genericParameter.GenericParameterPosition];
}
}
// If we didn't find a match, search any base types
return FindTypeArgumentInBase(thisType, typeParameter);
}
/// <summary>
/// Find the index of the specified type parameter.
/// </summary>
public static int FindTypeParameterIndex(Type thisType, Type typeParameter)
{
if (thisType != null && thisType.IsGenericType && typeParameter != null)
{
// If the TypeParameter is declared in a nested type that isn't 'thisType', move up through it's enclosing
// types looking for a matching type, and update the TypeParameter if we find one.
Type declaringType = typeParameter.DeclaringType;
while (declaringType != null && declaringType.IsGenericType)
{
if (declaringType == thisType || declaringType.GetGenericTypeDefinition() == thisType.GetGenericTypeDefinition())
{
if (declaringType != typeParameter.DeclaringType)
{
// If we moved up to a enclosing type, update the TypeParameter to the one declared there if possible
Type[] genericArguments = declaringType.GetGenericArguments();
if (typeParameter.GenericParameterPosition < genericArguments.Length)
typeParameter = genericArguments[typeParameter.GenericParameterPosition];
}
break;
}
declaringType = declaringType.DeclaringType;
}
// If TypeParameter isn't found in 'thisType' and it's nested, move up through it's enclosing types, searching them also
Type currentType = thisType;
do
{
// Search the generic type definition first, but if the type isn't the definition and we don't find a match in the
// definition, then also search the type itself. This is necessary because if one generic type references another
// in it's definition, the referenced generic type will have type parameters of the "parent" generic type and we
// can match on those - avoiding extra work of searching until we find the parent generic type instance. It's also
// important to NOT check that 'currentType' matches 'declaringType' here for this reason, AND we can't just look
// at the TypeParameter's position, but must scan all of the type arguments.
if (!currentType.IsGenericTypeDefinition)
{
foreach (Type genericParameter in thisType.GetGenericTypeDefinition().GetGenericArguments())
{
if (genericParameter == typeParameter)
return genericParameter.GenericParameterPosition;
}
}
// In this case, we can't use GenericParameterPosition, because we're not necessarily using the GenericTypeDefinition,
// so the position might refer to the position within the declaring type, which might not be the same as 'thisType'.
Type[] genericArguments = thisType.GetGenericArguments();
for (int i = 0; i < genericArguments.Length; ++i)
{
if (genericArguments[i] == typeParameter)
return i;
}
currentType = currentType.DeclaringType;
}
while (currentType != null && currentType.IsGenericType);
}
return -1;
}
/// <summary>
/// Find a type argument in a base class for the specified type parameter.
/// </summary>
public static Type FindTypeArgumentInBase(Type thisType, Type typeParameter)
{
Type found = null;
Type baseType = thisType.BaseType;
if (baseType != null && baseType != typeof(object))
found = FindTypeArgument(baseType, typeParameter);
if (found != null) return found;
Type[] interfaces = thisType.GetInterfaces();
foreach (Type @interface in interfaces)
{
found = FindTypeArgument(@interface, typeParameter);
if (found != null) return found;
}
return null;
}
/// <summary>
/// Change the specified object to the specified type, forcing larger integers into smaller ones without exceptions.
/// </summary>
public static object ChangeType(object obj, Type toType)
{
if (obj == null || toType == null)
return null;
object newObj = null;
try
{
// Tragically, the Convert.ChangeType() method can't be used to explicitly convert from larger to smaller
// types (it will throw an exception and fail). Therefore, we do explicit conversions here for any that
// might otherwise throw exceptions, and use Convert.ChangeType() for all others. Very ugly, but it works.
TypeCode toTypeCode = Type.GetTypeCode(toType);
TypeCode fromTypeCode = Type.GetTypeCode(obj.GetType());
switch (toTypeCode)
{
case TypeCode.Byte:
switch (fromTypeCode)
{
case TypeCode.Byte:
newObj = obj;
break;
case TypeCode.SByte:
newObj = (byte)(sbyte)obj;
break;
case TypeCode.Int16:
newObj = (byte)(short)obj;
break;
case TypeCode.UInt16:
newObj = (byte)(ushort)obj;
break;
case TypeCode.Int32:
newObj = (byte)(int)obj;
break;
case TypeCode.UInt32:
newObj = (byte)(uint)obj;
break;
case TypeCode.Int64:
newObj = (byte)(long)obj;
break;
case TypeCode.UInt64:
newObj = (byte)(ulong)obj;
break;
case TypeCode.Single:
newObj = (byte)(float)obj;
break;
case TypeCode.Double:
newObj = (byte)(double)obj;
break;
case TypeCode.Decimal:
newObj = (byte)(decimal)obj;
break;
}
break;
case TypeCode.SByte:
switch (fromTypeCode)
{
case TypeCode.Byte:
newObj = (sbyte)(byte)obj;
break;
case TypeCode.SByte:
newObj = obj;
break;
case TypeCode.Int16:
newObj = (sbyte)(short)obj;
break;
case TypeCode.UInt16:
newObj = (sbyte)(ushort)obj;
break;
case TypeCode.Int32:
newObj = (sbyte)(int)obj;
break;
case TypeCode.UInt32:
newObj = (sbyte)(uint)obj;
break;
case TypeCode.Int64:
newObj = (sbyte)(long)obj;
break;
case TypeCode.UInt64:
newObj = (sbyte)(ulong)obj;
break;
case TypeCode.Single:
newObj = (sbyte)(float)obj;
break;
case TypeCode.Double:
newObj = (sbyte)(double)obj;
break;
case TypeCode.Decimal:
newObj = (sbyte)(decimal)obj;
break;
}
break;
case TypeCode.Int16:
switch (fromTypeCode)
{
case TypeCode.Int16:
newObj = obj;
break;
case TypeCode.UInt16:
newObj = (short)(ushort)obj;
break;
case TypeCode.Int32:
newObj = (short)(int)obj;
break;
case TypeCode.UInt32:
newObj = (short)(uint)obj;
break;
case TypeCode.Int64:
newObj = (short)(long)obj;
break;
case TypeCode.UInt64:
newObj = (short)(ulong)obj;
break;
case TypeCode.Single:
newObj = (short)(float)obj;
break;
case TypeCode.Double:
newObj = (short)(double)obj;
break;
case TypeCode.Decimal:
newObj = (short)(decimal)obj;
break;
}
break;
case TypeCode.UInt16:
switch (fromTypeCode)
{
case TypeCode.SByte:
newObj = (ushort)(sbyte)obj;
break;
case TypeCode.Int16:
newObj = (ushort)(short)obj;
break;
case TypeCode.UInt16:
newObj = obj;
break;
case TypeCode.Int32:
newObj = (ushort)(int)obj;
break;
case TypeCode.UInt32:
newObj = (ushort)(uint)obj;
break;
case TypeCode.Int64:
newObj = (ushort)(long)obj;
break;
case TypeCode.UInt64:
newObj = (ushort)(ulong)obj;
break;
case TypeCode.Single:
newObj = (ushort)(float)obj;
break;
case TypeCode.Double:
newObj = (ushort)(double)obj;
break;
case TypeCode.Decimal:
newObj = (ushort)(decimal)obj;
break;
}
break;
case TypeCode.Int32:
switch (fromTypeCode)
{
case TypeCode.Int32:
newObj = obj;
break;
case TypeCode.UInt32:
newObj = (int)(uint)obj;
break;
case TypeCode.Int64:
newObj = (int)(long)obj;
break;
case TypeCode.UInt64:
newObj = (int)(ulong)obj;
break;
case TypeCode.Single:
newObj = (int)(float)obj;
break;
case TypeCode.Double:
newObj = (int)(double)obj;
break;
case TypeCode.Decimal:
newObj = (int)(decimal)obj;
break;
}
break;
case TypeCode.UInt32:
switch (fromTypeCode)
{
case TypeCode.SByte:
newObj = (uint)(sbyte)obj;
break;
case TypeCode.Int16:
newObj = (uint)(short)obj;
break;
case TypeCode.Int32:
newObj = (uint)(int)obj;
break;
case TypeCode.UInt32:
newObj = obj;
break;
case TypeCode.Int64:
newObj = (uint)(long)obj;
break;
case TypeCode.UInt64:
newObj = (uint)(ulong)obj;
break;
case TypeCode.Single:
newObj = (uint)(float)obj;
break;
case TypeCode.Double:
newObj = (uint)(double)obj;
break;
case TypeCode.Decimal:
newObj = (uint)(decimal)obj;
break;
}
break;
case TypeCode.Int64:
switch (fromTypeCode)
{
case TypeCode.Int64:
newObj = obj;
break;
case TypeCode.UInt64:
newObj = (long)(ulong)obj;
break;
case TypeCode.Single:
newObj = (long)(float)obj;
break;
case TypeCode.Double:
newObj = (long)(double)obj;
break;
case TypeCode.Decimal:
newObj = (long)(decimal)obj;
break;
}
break;
case TypeCode.UInt64:
switch (fromTypeCode)
{
case TypeCode.SByte:
newObj = (ulong)(sbyte)obj;
break;
case TypeCode.Int16:
newObj = (ulong)(short)obj;
break;
case TypeCode.Int32:
newObj = (ulong)(int)obj;
break;
case TypeCode.Int64:
newObj = (ulong)(long)obj;
break;
case TypeCode.UInt64:
newObj = obj;
break;
case TypeCode.Single:
newObj = (ulong)(float)obj;
break;
case TypeCode.Double:
newObj = (ulong)(double)obj;
break;
case TypeCode.Decimal:
newObj = (ulong)(decimal)obj;
break;
}
break;
case TypeCode.Single:
switch (fromTypeCode)
{
case TypeCode.Char:
newObj = (float)(char)obj;
break;
case TypeCode.Single:
newObj = obj;
break;
case TypeCode.Double:
newObj = (float)(double)obj;
break;
}
break;
case TypeCode.Double:
switch (fromTypeCode)
{
case TypeCode.Char:
newObj = (double)(char)obj;
break;
case TypeCode.Double:
newObj = obj;
break;
}
break;
case TypeCode.Decimal:
switch (fromTypeCode)
{
case TypeCode.Single:
newObj = (decimal)(float)obj;
break;
case TypeCode.Double:
newObj = (decimal)(double)obj;
break;
case TypeCode.Decimal:
newObj = obj;
break;
}
break;
}
if (newObj == null)
{
//Type fromType = obj.GetType();
if (toType == typeof(IntPtr))
{
if (fromTypeCode == TypeCode.Int32)
newObj = (IntPtr)(int)obj;
else if (fromTypeCode == TypeCode.Int64)
newObj = (IntPtr)(long)obj;
//else if (fromType == typeof(void*))
// newObj = (IntPtr)(void*)obj;
}
else if (toType == typeof(UIntPtr))
{
if (fromTypeCode == TypeCode.UInt32)
newObj = (UIntPtr)(uint)obj;
else if (fromTypeCode == TypeCode.UInt64)
newObj = (UIntPtr)(ulong)obj;
//else if (fromType == typeof(void*))
// newObj = (UIntPtr)(void*)obj;
}
else if (IsNullableType(toType))
{
// If the object type differs from the nullable's type element type, convert it
Type elementType = toType.GetGenericArguments()[0];
if (fromTypeCode != Type.GetTypeCode(elementType))
obj = ChangeType(obj, elementType);
// It's unfortunately not possible to return a nullable type as an object, because
// nullable types can't be boxed - they are implicitly converted to either null or their
// wrapped type. This means that a cast to an 'int?' will evaluate as an 'int' type.
// Fixing this for Nova's purposes would require a custom Nullable type.
//newObj = Activator.CreateInstance(toType, obj);
newObj = obj;
}
else if (toType.IsPointer)
newObj = obj;
else
newObj = Convert.ChangeType(obj, toType);
}
}
catch { }
return newObj;
}
#endregion
}
}