using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using Pfz.Extensions.DictionaryExtensions;
using Pfz.Extensions.MonitorLockExtensions;
namespace Pfz.Extensions.TypeExtensions
{
/// <summary>
/// Adds some methods to the Type class so you can discover the
/// sub-types easily.
/// </summary>
public static class PfzTypeExtensions
{
#region GetDirectSubClasses
private static Dictionary<KeyValuePair<Type, Assembly>, ReadOnlyCollection<Type>> fSubClasses = new Dictionary<KeyValuePair<Type, Assembly>, ReadOnlyCollection<Type>>();
/// <summary>
/// Gets the sub-classes of the specific type, in the specific assembly.
/// </summary>
public static ReadOnlyCollection<Type> GetDirectSubClasses(this Type type, Assembly inAssembly)
{
ReadOnlyCollection<Type> result = null;
KeyValuePair<Type, Assembly> pair = new KeyValuePair<Type,Assembly>(type, inAssembly);
fSubClasses.LockWithTimeout
(
delegate
{
if (!fSubClasses.TryGetValue(pair, out result))
{
List<Type> list = new List<Type>();
foreach(Type possibleSubType in inAssembly.GetTypes())
if (possibleSubType.BaseType == type)
list.Add(possibleSubType);
result = new ReadOnlyCollection<Type>(list.ToArray());
fSubClasses.Add(pair, result);
}
}
);
return result;
}
#endregion
#region GetSubClassesRecursive
private static Dictionary<KeyValuePair<Type, Assembly>, ReadOnlyCollection<Type>> fSubClassesRecursive = new Dictionary<KeyValuePair<Type, Assembly>, ReadOnlyCollection<Type>>();
/// <summary>
/// Gets the sub-classes of the specific type, in the specific assembly.
/// </summary>
public static ReadOnlyCollection<Type> GetSubClassesRecursive(this Type type, Assembly inAssembly)
{
ReadOnlyCollection<Type> result = null;
KeyValuePair<Type, Assembly> pair = new KeyValuePair<Type,Assembly>(type, inAssembly);
fSubClassesRecursive.LockWithTimeout
(
delegate
{
if (!fSubClassesRecursive.TryGetValue(pair, out result))
{
List<Type> list = new List<Type>();
foreach(Type possibleSubType in inAssembly.GetTypes())
if (possibleSubType != type && type.IsAssignableFrom(possibleSubType))
list.Add(possibleSubType);
result = new ReadOnlyCollection<Type>(list.ToArray());
fSubClassesRecursive.Add(pair, result);
}
}
);
return result;
}
#endregion
#region GetReferencedAssemblies
/// <summary>
/// Gets all assemblies that must be referenced if you want to
/// use everything from the given type. Useful for CodeDOM.
/// </summary>
/// <param name="type">The type to get all referenced assemblies.</param>
/// <returns>A hashset with all referenced assemblies.</returns>
public static HashSet<Assembly> GetReferencedAssemblies(this Type type)
{
HashSet<Type> referencedTypes = new HashSet<Type>();
HashSet<Assembly> referencedAssemblies = new HashSet<Assembly>();
AddReferencedAssemblies(type, referencedAssemblies, referencedTypes);
return referencedAssemblies;
}
/// <summary>
/// Gets all assemblies that must be referenced if you want to
/// use everything from the given types. Useful for CodeDOM.
/// </summary>
/// <param name="types">A collection of all needed types.</param>
/// <returns>A HashSet of all referenced assemblies.</returns>
public static HashSet<Assembly> GetReferencedAssemblies(this IEnumerable<Type> types)
{
HashSet<Type> processedTypes = new HashSet<Type>();
HashSet<Assembly> processedAssemblies = new HashSet<Assembly>();
foreach(Type type in types)
AddReferencedAssemblies(type, processedAssemblies, processedTypes);
return processedAssemblies;
}
#endregion
#region AddReferencedAssemblies
/// <summary>
/// Adds all the referenced types/assemblies into the given hashsets.
/// This is almost an internal implementation, but if you need to add
/// many references but do not have a collection previouly created, you
/// must use this method for performance reasons.
/// </summary>
/// <param name="type">The type to get all the referencies.</param>
/// <param name="alreadyAddedAssemblies">A hashset, that is used to add the new referenced assemblies.</param>
/// <param name="alreadyAddedTypes">A hashset, that is used to add the new referenced types and to avoid reprocessing them.</param>
public static void AddReferencedAssemblies(this Type type, HashSet<Assembly> alreadyAddedAssemblies, HashSet<Type> alreadyAddedTypes)
{
if (type == null)
return;
// returns if this type was already processed.
if (!alreadyAddedTypes.Add(type))
return;
alreadyAddedAssemblies.Add(type.Assembly);
AddReferencedAssemblies(type.BaseType, alreadyAddedAssemblies, alreadyAddedTypes);
AddReferencedAssemblies(type.DeclaringType, alreadyAddedAssemblies, alreadyAddedTypes);
foreach(Type interfaceType in type.GetInterfaces())
AddReferencedAssemblies(interfaceType, alreadyAddedAssemblies, alreadyAddedTypes);
foreach(FieldInfo field in type.GetFields())
AddReferencedAssemblies(field.FieldType, alreadyAddedAssemblies, alreadyAddedTypes);
foreach(MethodInfo method in type.GetMethods())
{
AddReferencedAssemblies(method.ReturnType, alreadyAddedAssemblies, alreadyAddedTypes);
foreach(ParameterInfo parameter in method.GetParameters())
AddReferencedAssemblies(parameter.ParameterType, alreadyAddedAssemblies, alreadyAddedTypes);
}
foreach(PropertyInfo property in type.GetProperties())
{
AddReferencedAssemblies(property.PropertyType, alreadyAddedAssemblies, alreadyAddedTypes);
foreach(ParameterInfo parameter in property.GetIndexParameters())
AddReferencedAssemblies(parameter.ParameterType, alreadyAddedAssemblies, alreadyAddedTypes);
}
foreach(EventInfo eventInfo in type.GetEvents())
AddReferencedAssemblies(eventInfo.EventHandlerType, alreadyAddedAssemblies, alreadyAddedTypes);
}
#endregion
#region GetOrderedInterfaces
private static Dictionary<Type, ReadOnlyCollection<Type>> fOrderedInterfaces = new Dictionary<Type, ReadOnlyCollection<Type>>();
/// <summary>
/// Gets the interfaces from this type ordered from the most "new" to the most
/// "old" in the base types. Note that 2 or more interfaces added at the same
/// "level" will not have an specific order.
/// </summary>
public static ReadOnlyCollection<Type> GetOrderedInterfaces(this Type type)
{
if (type == null)
throw new ArgumentNullException("type");
ReadOnlyCollection<Type> result;
lock(fOrderedInterfaces)
result = fOrderedInterfaces.GetValueOrDefault(type);
if (result != null)
return result;
List<Type> orderedInterfacesList = new List<Type>();
HashSet<Type> allInterfaces = new HashSet<Type>(type.GetInterfaces());
HashSet<Type> interfacesToRemove = new HashSet<Type>();
while(allInterfaces.Count > 0)
{
interfacesToRemove.Clear();
foreach(var interfaceType in allInterfaces)
foreach(var interfaceToRemove in interfaceType.GetInterfaces())
interfacesToRemove.Add(interfaceToRemove);
HashSet<Type> copy = new HashSet<Type>(allInterfaces);
foreach(var interfaceType in interfacesToRemove)
copy.Remove(interfaceType);
foreach(var interfaceType in copy)
{
orderedInterfacesList.Add(interfaceType);
allInterfaces.Remove(interfaceType);
}
}
var orderedInterfacesArray = orderedInterfacesList.ToArray();
result = new ReadOnlyCollection<Type>(orderedInterfacesArray);
lock(fOrderedInterfaces)
fOrderedInterfaces[type] = result;
return result;
}
#endregion
#region GetInterfaceProperties
/// <summary>
/// If this type is an interface, gets all the properties from this
/// type to it's base interfaces.
/// If this is not an interface, uses the custom GetProperty.
/// </summary>
public static IEnumerable<PropertyInfo> GetInterfaceProperties(this Type type)
{
if (type == null)
throw new ArgumentNullException("type");
foreach(var property in type.GetProperties())
yield return property;
if (type.IsInterface)
foreach(var interfaceType in type.GetOrderedInterfaces())
foreach(var propertyInfo in interfaceType.GetProperties())
yield return propertyInfo;
}
#endregion
#region GetInterfaceProperties
/// <summary>
/// If this type is an interface, gets all the properties from this
/// it's base interfaces to this interface.
/// If this is not an interface, uses the custom GetProperty.
/// </summary>
public static IEnumerable<PropertyInfo> GetInterfacePropertiesFromBase(this Type type)
{
if (type == null)
throw new ArgumentNullException("type");
if (type.IsInterface)
{
var interfaceTypes = type.GetOrderedInterfaces();
for (int i=interfaceTypes.Count-1; i>=0; i--)
{
Type interfaceType = interfaceTypes[i];
foreach(var propertyInfo in interfaceType.GetProperties())
yield return propertyInfo;
}
}
foreach(var property in type.GetProperties())
yield return property;
}
#endregion
#region TryGetInterfaceProperty
/// <summary>
/// Tries to get a property by it's name.
/// If this is an interface, it also looks all the base interfaces to find
/// the property.
/// </summary>
public static PropertyInfo TryGetInterfaceProperty(this Type type, string propertyName)
{
if (type == null)
throw new ArgumentNullException("type");
if (propertyName == null)
throw new ArgumentNullException("propertyName");
var result = type.GetProperty(propertyName);
if (result != null)
return result;
if (type.IsInterface)
{
foreach(var interfaceType in type.GetOrderedInterfaces())
{
result = interfaceType.GetProperty(propertyName);
if (result != null)
return result;
}
}
return null;
}
#endregion
#region GetInterfaceProperty
/// <summary>
/// Gets a property by it's name.
/// If this type is an interface, search in it's base interfaces.
/// Throws an exception if no such property is found.
/// </summary>
public static PropertyInfo GetInterfaceProperty(this Type type, string propertyName)
{
PropertyInfo result = TryGetInterfaceProperty(type, propertyName);
if (result == null)
throw new ArgumentException("Property \"" + propertyName + "\" was not found in type " + type.FullName + ".");
return result;
}
#endregion
}
}