update:
It turns out that FindInterfaceWith can be simplified, and building a hierarchy of type flatten becomes unnecessary, because base classes are not necessarily involved if we take this type into account when it is an interface; so I added the GetInterfaces(bool) extension method. Since we can sort intervals by coverage rules, sorted interface intersections are candidates. If they are all equally good, I said that none of them are considered the best. If this is not so, then the best should cover one of the others; and because they are sorted, this kind of relationship must exist in the correct majority of the two interfaces in the array to indicate that there is a better interface, which is the most specific.
Code can be simplified using Linq ; but in my scenario, I should reduce the requirements for links and namespaces as little as possible.
The code
using System; public static class TypeExtensions { static int CountOverlapped<T>(T[] ax, T[] ay) { return IntersectPreserveOrder(ay, ax).Length; } static int CountOccurrence(Type[] ax, Type ty) { var a = Array.FindAll(ax, x => Array.Exists(x.GetInterfaces(), tx => tx.Equals(ty))); return a.Length; } static Comparison<Type> GetCoverageComparison(Type[] az) { return (tx, ty) => { int overlapped, occurrence; var ay = ty.GetInterfaces(); var ax = tx.GetInterfaces(); if(0!=(overlapped=CountOverlapped(az, ax).CompareTo(CountOverlapped(az, ay)))) { return overlapped; } if(0!=(occurrence=CountOccurrence(az, tx).CompareTo(CountOccurrence(az, ty)))) { return occurrence; } return 0; }; } static T[] IntersectPreserveOrder<T>(T[] ax, T[] ay) { return Array.FindAll(ax, x => Array.FindIndex(ay, y => y.Equals(x))>=0); } public static Type[] GetInterfaces(this Type x, bool includeThis) { var a = x.GetInterfaces(); if(includeThis&&x.IsInterface) { Array.Resize(ref a, 1+a.Length); a[a.Length-1]=x; } return a; } public static Type FindInterfaceWith(this Type type1, Type type2) { var ay = type2.GetInterfaces(true); var ax = type1.GetInterfaces(true); var types = IntersectPreserveOrder(ax, ay); if(types.Length<1) { return null; } Array.Sort(types, GetCoverageComparison(types)); var type3 = types[types.Length-1]; if(types.Length<2) { return type3; } var type4 = types[types.Length-2]; return Array.Exists(type3.GetInterfaces(), x => x.Equals(type4)) ? type3 : null; } public static Type FindBaseClassWith(this Type type1, Type type2) { if(null==type1) { return type2; } if(null==type2) { return type1; } for(var type4 = type2; null!=type4; type4=type4.BaseType) { for(var type3 = type1; null!=type3; type3=type3.BaseType) { if(type4==type3) { return type4; } } } return null; } public static Type FindAssignableWith(this Type type1, Type type2) { var baseClass = type2.FindBaseClassWith(type1); if(null==baseClass||typeof(object)==baseClass) { var @interface = type2.FindInterfaceWith(type1); if(null!=@interface) { return @interface; } } return baseClass; } }
There are two recursive methods; one is FindInterfaceWith , the other is an important GetTypesArray method, since there already exists a method called GetTypeArray of the Type class with a different use.
It works as an Akim method provided GetClassHierarchy ; but in this version it creates an array like:
As we know, they are in a certain order, and that is how it works. The GetTypesArray actually built is a flattened tree. The array is actually in the model as follows:
diagram

Note that the implementation relation of some interfaces, such as IList<int> implements ICollection<int> , is not related to the lines in this diagram.
The interfaces in the returned array are sorted by Array.Sort with the ordering rules provided by GetCoverageComparison .
We can mention, for example, the possibility of using several interfaces not only once in some answers (for example, <this]); and I determined a way to solve them:
note
The GetInterfaces method does not return interfaces in a specific order, for example, in alphabetical order or in declaration order. Your code should not depend on the return order of the interfaces, since this order is changing.
Because of recursion, base classes are always ordered.
If two interfaces have the same coverage, none of them will be considered acceptable.
Suppose these interfaces are defined (or the classes are just fine):
public interface IDelta { } public interface ICharlie { } public interface IBravo: IDelta, ICharlie { } public interface IAlpha: IDelta, ICharlie { }
which one is better for appointing IAlpha and IBravo ? In this case, FindInterfaceWith just returns null .
In the question [ How to find the smallest assignable type in two types (duplicate)? ], I stated:
wrong conclusion
If this assumption was correct, then FindInterfaceWith becomes a redundant method; due to the only difference between FindInterfaceWith and FindAssignableWith is:
FindInterfaceWith returns null if there was a better choice of class; and FindAssignableWith returns the exact class directly.
However, now we can look at the FindAssignableWith method, it should call the other two methods based on the original assumption. The paradoxical mistake simply disappeared magically.
About matching rules with the scope of ordering interfaces, in the delegate of GetCoverageComparison , I use:
double rules
compare two interfaces in an array of source interfaces, each of which covers the number of others in the source, calling CountOverlapped
If rule 1 does not distinguish between them (returns 0 ), the second ordering is to call CountOccurrence to determine what is inherited more than others, and then compare
two rules are equivalent to a Linq query:
interfaces=( from it in interfaces let order1=it.GetInterfaces().Intersect(interfaces).Count() let order2=( from x in interfaces where x.GetInterfaces().Contains(it) select x ).Count() orderby order1, order2 select it ).ToArray();
FindInterfaceWith will FindInterfaceWith make a recursive call to find out if this interface is sufficient for recognition as the most common interface or just another relation like IAlpha and IBravo .
And about the FindBaseClassWith method, what it returns is different from the original assumption that any parameter is null, and then returns null. It actually returns another argument passed in.
This is due to the question [ What should the `FindBaseClassWith` method return? ] about the FindBaseClassWith method FindBaseClassWith . In the current implementation, we can call it as follows:
method chain
var type= typeof(int[]) .FindBaseClassWith(null) .FindBaseClassWith(null) .FindBaseClassWith(typeof(char[]));
It will return typeof(Array) ; thanks to this feature, we can even call
var type= typeof(String) .FindAssignableWith(null) .FindAssignableWith(null) .FindAssignableWith(typeof(String));
What we cannot do with my implementation is to call FindInterfaceWith , as indicated above, due to the possibility of relationships such as IAlpha and IBravo .
In some situations, I tested the code by calling FindAssignableWith as the examples shown:
assignment type assignment
(Dictionary`2, Dictionary`2) = Dictionary`2 (List`1, List`1) = IList (Dictionary`2, KeyValuePair`2) = Object (IAlpha, IBravo) = <null> (IBravo, IAlpha) = <null> (ICollection, IList) = ICollection (IList, ICollection) = ICollection (Char[], Int32[]) = IList (Int32[], Char[]) = IList (IEnumerable`1, IEnumerable`1) = IEnumerable (String, Array) = Object (Array, String) = Object (Char[], Int32[]) = IList (Form, SplitContainer) = ContainerControl (SplitContainer, Form) = ContainerControl
The List'1 IList test will appear because I tested typeof(List<int>) with typeof(List<String>) ; and Dictionary'2 are Dictionary<String, String> . Sorry I did not do the work to represent exact type names.