Skip to content

Instantly share code, notes, and snippets.

@MiloszKrajewski
Last active October 18, 2024 13:42
Show Gist options
  • Save MiloszKrajewski/d2d81a08a40044aeff58c0c430e9eb5a to your computer and use it in GitHub Desktop.
Save MiloszKrajewski/d2d81a08a40044aeff58c0c430e9eb5a to your computer and use it in GitHub Desktop.
TypeExtensions
internal static class TypeExtensions
{
/// <summary>
/// Type distance cache. It should Concurrent dictionary but it is not available
/// on all flavors of Portable Class Library.
/// </summary>
private static readonly ConcurrentDictionary<(Type, Type), int> TypeDistanceMap = new();
/// <summary>Checks if child type inherits (or implements) from parent.</summary>
/// <param name="child">The child.</param>
/// <param name="parent">The parent.</param>
/// <returns><c>true</c> if child type inherits (or implements) from parent; <c>false</c> otherwise</returns>
public static bool InheritsFrom(this Type child, Type parent) =>
parent.IsAssignableFrom(child);
/// <summary>Calculates distance between child and parent type.</summary>
/// <param name="child">The child.</param>
/// <param name="grandparent">The parent.</param>
/// <returns>Inheritance distance between child and parent.</returns>
/// <exception cref="System.ArgumentException">Thrown when child does not inherit from parent at all.</exception>
public static int DistanceFrom(this Type child, Type grandparent)
{
ArgumentNullException.ThrowIfNull(child);
ArgumentNullException.ThrowIfNull(grandparent);
return child == grandparent ? 0 : TypeDistanceMap.GetOrAdd((child, grandparent), ResolveDistance);
}
private static int ResolveDistance((Type, Type) types)
{
var (child, grandparent) = types;
if (child == grandparent) return 0;
if (!child.InheritsFrom(grandparent))
throw new ArgumentException(
$"Type '{child.Name}' does not inherit nor implements '{grandparent.Name}'");
int Up1(int value) => value == int.MaxValue ? value : value + 1;
var distances = GetIntermediateParents(child, grandparent)
.Select(t => Up1(DistanceFrom(t, grandparent)))
.ToArray();
// this may happen with covariant interfaces
// they are "assignable from" but not "inheriting" from each other
return distances.Length == 0 ? int.MaxValue : distances.Min();
}
/// <summary>Gets the list of parent types which also inherit for grandparent.</summary>
/// <param name="child">The child.</param>
/// <param name="grandparent">The parent.</param>
/// <returns>Collection of types.</returns>
private static IEnumerable<Type> GetIntermediateParents(Type child, Type grandparent)
{
var baseType = child.BaseType;
if (grandparent.IsInterface)
{
// determines if given interface "leads" to grandparent
// and if child is first implementor of given interface
// note: this is special case for interfaces as they are reported on every child
// along the way, and we want the most distant one (when it was implemented
// for the first time in hierarchy)
bool IsFirstImplementation(Type interfaceType) =>
interfaceType.InheritsFrom(grandparent) && // right path
(baseType == null || !baseType.InheritsFrom(interfaceType)); // first time
var baseInterfaces = child.GetInterfaces().Where(IsFirstImplementation);
foreach (var i in baseInterfaces)
yield return i;
}
if (baseType?.InheritsFrom(grandparent) ?? false)
yield return baseType;
}
private static readonly ConcurrentDictionary<Type, string> FriendlyNames = new();
public static string GetFriendlyName(this Type type) =>
FriendlyNames.GetOrAdd(type, NewFriendlyName);
private static string NewFriendlyName(Type? type)
{
if (type == null)
return "<null>";
var typeName = type.Name;
if (!type.IsGenericType)
return typeName;
var length = typeName.IndexOf('`') switch { < 0 => typeName.Length, var x => x };
return new StringBuilder()
.Append(typeName, 0, length)
.Append('<')
.Append(string.Join(",", type.GetGenericArguments().Select(GetFriendlyName)))
.Append('>')
.ToString();
}
public static string GetObjectFriendlyName(this object? subject) =>
subject is not null
? $"{subject.GetType().GetFriendlyName()}@{RuntimeHelpers.GetHashCode(subject):x}"
: "<null>";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment