Created
October 19, 2019 17:00
-
-
Save wdolek/6f06317583155c5c4e02bec134c74be7 to your computer and use it in GitHub Desktop.
Type extensions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Reflection; | |
namespace System | |
{ | |
/// <summary> | |
/// Extensions of <see cref="Type"/>. | |
/// </summary> | |
internal static class TypeExtensions | |
{ | |
/// <summary> | |
/// Array of open generic collection types. | |
/// </summary> | |
/// <remarks> | |
/// Order of types matters, from more specific to less specific type. | |
/// </remarks> | |
private static readonly Type[] OpenGenericCollectionTypes = | |
{ | |
// List<>: this[int i] (matches `Array` as well) | |
typeof(IReadOnlyList<>), | |
// Dictionary<,>: this[TKey k] | |
typeof(IReadOnlyDictionary<,>), | |
typeof(IDictionary<,>), | |
// IEnumerable<>: (IReadOnlyCollection<>, ICollection<>, ...) | |
typeof(IEnumerable<>) | |
}; | |
/// <summary> | |
/// Gets all declared properties of given <paramref name="type"/> and its parents. | |
/// </summary> | |
/// <param name="type">Type being traversed.</param> | |
/// <returns> | |
/// Enumerable of declared properties of type. | |
/// </returns> | |
public static IEnumerable<PropertyInfo> GetAllDeclaredProperties(this Type type) | |
{ | |
TypeInfo typeInfo = type.GetTypeInfo(); | |
do | |
{ | |
foreach (var property in typeInfo.DeclaredProperties) | |
{ | |
yield return property; | |
} | |
type = type.BaseType.GetTypeInfo(); | |
} while (type != typeof(object).GetTypeInfo()); | |
} | |
/// <summary> | |
/// Unwraps generic collection type argument (if generic collection). | |
/// </summary> | |
/// <param name="type">Type to be unwrapped.</param> | |
/// <param name="unwrapCallback">Action called when unwrapping type argument.</param> | |
/// <returns> | |
/// Returns type of generic collection type argument, | |
/// or directly <paramref name="type"/> if it isn't collection. | |
/// </returns> | |
public static Type UnwrapGenericCollectionTypeArgument(this Type type, Action unwrapCallback) | |
{ | |
Type unwrapped = type; | |
Type genericArg = type.GetCollectionGenericTypeArgument(); | |
while (genericArg != null) | |
{ | |
unwrapCallback(); | |
// keep unwrapped type and try to continue in unwrapping if there's another collection | |
unwrapped = genericArg; | |
genericArg = genericArg.GetCollectionGenericTypeArgument(); | |
} | |
return unwrapped; | |
} | |
/// <summary> | |
/// Gets generic collection type argument (of value). | |
/// </summary> | |
/// <param name="type">Type to examine.</param> | |
/// <returns> | |
/// Returns collection value type or <c>null</c> when given <paramref name="type"/> is not a collection. | |
/// </returns> | |
private static Type GetCollectionGenericTypeArgument(this Type type) | |
{ | |
foreach (Type openGenericCollectionType in OpenGenericCollectionTypes) | |
{ | |
Type collectionInterface = FindGenericInterface(type, openGenericCollectionType); | |
if (collectionInterface != null) | |
{ | |
var genericArgs = collectionInterface.GenericTypeArguments; | |
// return last generic type argument | |
// - List<T> -> T | |
// - Dictionary<TKey, TValue> -> TValue | |
return genericArgs[genericArgs.Length - 1]; | |
} | |
} | |
return null; | |
} | |
/// <summary> | |
/// Finds generic collection interface. | |
/// </summary> | |
/// <param name="actual">Actual type.</param> | |
/// <param name="expected">Expected open generic collection type.</param> | |
/// <returns> | |
/// Returns type of generic collection interface | |
/// or <c>null</c> if given <paramref name="actual"/> is not a collection | |
/// or when open generic collection interface is not recognized. | |
/// </returns> | |
private static Type FindGenericInterface(Type actual, Type expected) | |
{ | |
TypeInfo actualTypeInfo = actual.GetTypeInfo(); | |
if (actualTypeInfo.IsGenericType && actual.GetGenericTypeDefinition() == expected) | |
{ | |
return actual; | |
} | |
IEnumerable<Type> interfaces = actualTypeInfo.ImplementedInterfaces; | |
foreach (var interfaceType in interfaces) | |
{ | |
if (interfaceType.GetTypeInfo().IsGenericType | |
&& interfaceType.GetGenericTypeDefinition() == expected) | |
{ | |
return interfaceType; | |
} | |
} | |
return null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment