// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #nullable enable using System; using System.Reflection; namespace Microsoft.Extensions.Internal { /// /// Helper related to generic interface definitions and implementing classes. /// internal static class ClosedGenericMatcher { /// /// Determine whether is or implements a closed generic /// created from . /// /// The of interest. /// The open generic to match. Usually an interface. /// /// The closed generic created from that /// is or implements. null if the two s have no such /// relationship. /// /// /// This method will return if is /// typeof(KeyValuePair{,}), and is /// typeof(KeyValuePair{string, object}). /// public static Type? ExtractGenericInterface(Type queryType, Type interfaceType) { if (queryType == null) { throw new ArgumentNullException(nameof(queryType)); } if (interfaceType == null) { throw new ArgumentNullException(nameof(interfaceType)); } if (IsGenericInstantiation(queryType, interfaceType)) { // queryType matches (i.e. is a closed generic type created from) the open generic type. return queryType; } // Otherwise check all interfaces the type implements for a match. // - If multiple different generic instantiations exists, we want the most derived one. // - If that doesn't break the tie, then we sort alphabetically so that it's deterministic. // // We do this by looking at interfaces on the type, and recursing to the base type // if we don't find any matches. return GetGenericInstantiation(queryType, interfaceType); } private static bool IsGenericInstantiation(Type candidate, Type interfaceType) { return candidate.GetTypeInfo().IsGenericType && candidate.GetGenericTypeDefinition() == interfaceType; } private static Type? GetGenericInstantiation(Type queryType, Type interfaceType) { Type? bestMatch = null; var interfaces = queryType.GetInterfaces(); foreach (var @interface in interfaces) { if (IsGenericInstantiation(@interface, interfaceType)) { if (bestMatch == null) { bestMatch = @interface; } else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0) { bestMatch = @interface; } else { // There are two matches at this level of the class hierarchy, but @interface is after // bestMatch in the sort order. } } } if (bestMatch != null) { return bestMatch; } // BaseType will be null for object and interfaces, which means we've reached 'bottom'. var baseType = queryType?.GetTypeInfo().BaseType; if (baseType == null) { return null; } else { return GetGenericInstantiation(baseType, interfaceType); } } } }