Skip to content

Instantly share code, notes, and snippets.

@wdolek
Created October 19, 2019 17:00
Show Gist options
  • Save wdolek/6f06317583155c5c4e02bec134c74be7 to your computer and use it in GitHub Desktop.
Save wdolek/6f06317583155c5c4e02bec134c74be7 to your computer and use it in GitHub Desktop.
Type extensions
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