Ответ 1
update:
Оказывается, FindInterfaceWith
может быть упрощено, а построение иерархии типа flatten становится излишним, поскольку базовые классы не обязательно задействованы, если мы принимаем этот тип во внимание, когда он является интерфейсом; поэтому я добавил метод расширения GetInterfaces(bool)
. Поскольку мы можем сортировать интервалы по правилам покрытия, сортированные пересечения интерфейсов являются кандидатами. Если все они одинаково хороши, я сказал, что ни один из них не считается лучшим. Если это не так, то лучший должен покрыть один из других; и потому, что они отсортированы, такой вид отношений должен существовать в правильном большинстве двух интерфейсов в массиве, чтобы обозначить, что существует лучший интерфейс, который является наиболее конкретным.
Код можно упростить, используя Linq
; но в моем сценарии я должен как можно меньше уменьшить требования к ссылкам и пространствам имен.
-
Код
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); } /* static T[] SubtractPreserveOrder<T>(T[] ax, T[] ay) { return Array.FindAll(ax, x => Array.FindIndex(ay, y => y.Equals(x))<0); } static Type[] GetTypesArray(Type typeNode) { if(null==typeNode) { return Type.EmptyTypes; } var baseArray = GetTypesArray(typeNode.BaseType); var interfaces = SubtractPreserveOrder(typeNode.GetInterfaces(), baseArray); var index = interfaces.Length+baseArray.Length; var typeArray = new Type[1+index]; typeArray[index]=typeNode; Array.Sort(interfaces, GetCoverageComparison(interfaces)); Array.Copy(interfaces, 0, typeArray, index-interfaces.Length, interfaces.Length); Array.Copy(baseArray, typeArray, baseArray.Length); return typeArray; } */ 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([email protected]) { return @interface; } } return baseClass; } }
Есть два рекурсивных метода; один - FindInterfaceWith
, другой - важный метод GetTypesArray
, поскольку уже существует метод с именем GetTypeArray
класса Type
с другим использованием.
Он работает как метод Akim при условии GetClassHierarchy; но в этой версии он создает массив вроде:
-
вывод иерархии
a[8]=System.String a[7]=System.Collections.Generic.IEnumerable`1[System.Char] a[6]=System.Collections.IEnumerable a[5]=System.ICloneable a[4]=System.IComparable a[3]=System.IConvertible a[2]=System.IEquatable`1[System.String] a[1]=System.IComparable`1[System.String] a[0]=System.Object
Как мы знаем, они находятся в определенном порядке, и именно так оно и работает. Массив GetTypesArray
, построенный на самом деле, является сплюснутым деревом. Массив фактически находится в модели следующим образом:
-
диаграмма
Обратите внимание, что отношение реализации некоторых интерфейсов, таких как
IList<int>
реализуетICollection<int>
, не связано с строками на этой диаграмме.
Интерфейсы в возвращаемом массиве сортируются по Array.Sort
с правилами упорядочения, предоставляемыми GetCoverageComparison
.
Можно упомянуть, например, возможность использования нескольких интерфейсов не только один раз в некоторых ответах (например, < this]); и я определил способ их решения:
-
note
-
GetInterfaces метод не возвращает интерфейсы в определенном порядке, например, в алфавитном порядке или порядке объявления. Ваш код не должен зависеть от порядка возврата интерфейсов, поскольку этот порядок меняется.
-
Из-за рекурсии базовые классы всегда упорядочены.
-
Если два интерфейса имеют одинаковое покрытие, ни один из них не будет считаться приемлемым.
Предположим, что эти интерфейсы определены (или классы просто прекрасны):
public interface IDelta { } public interface ICharlie { } public interface IBravo: IDelta, ICharlie { } public interface IAlpha: IDelta, ICharlie { }
то какой из них лучше для назначения
IAlpha
иIBravo
? В этом случаеFindInterfaceWith
просто возвращаетnull
.
-
В вопросе [Как найти наименьший присваиваемый тип в двух типах (дубликат)?], я заявил:
-
неправильный вывод
Если это предположение было правильным, то
FindInterfaceWith
становится избыточным методом; из-за единственной разницы междуFindInterfaceWith
иFindAssignableWith
является:FindInterfaceWith
возвращаетnull
, если был лучший выбор класса; аFindAssignableWith
возвращает точный класс напрямую.
Однако теперь мы можем посмотреть на метод FindAssignableWith
, он должен вызывать другие два метода, основываясь на исходном предположении. Парадоксальная ошибка просто исчезла волшебным образом.
О правилах сопоставления с охватом интерфейсов упорядочения, в делегате GetCoverageComparison
, я использую:
-
двойные правила
-
сравнивайте два интерфейса в массиве исходных интерфейсов, каждый из которых покрывает количество других в источнике, вызывая
CountOverlapped
-
Если правило 1 не различает их (возвращает
0
), вторичное упорядочение - это вызовCountOccurrence
для определения того, что унаследовано больше раз другими, а затем сравнениедва правила эквивалентны запросу
Linq
: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
выполнит, возможно, рекурсивный вызов, чтобы выяснить, является ли этот интерфейс достаточным для распознавания как наиболее общий интерфейс или просто другое отношение, подобноеIAlpha
иIBravo
.
-
И о методе FindBaseClassWith
, то, что он возвращает, отличается от исходного предположения о том, что любой параметр имеет значение null, а затем возвращает null. Он фактически возвращает другой аргумент, переданный в.
Это связано с вопросом [Что должен возвращать метод` FindBaseClassWith`?] о цепочке методов FindBaseClassWith
. В текущей реализации мы можем назвать это следующим образом:
-
цепочка методов
var type= typeof(int[]) .FindBaseClassWith(null) .FindBaseClassWith(null) .FindBaseClassWith(typeof(char[]));
Он вернет
typeof(Array)
; благодаря этой функции, мы можем даже позвонитьvar type= typeof(String) .FindAssignableWith(null) .FindAssignableWith(null) .FindAssignableWith(typeof(String));
То, что мы не можем сделать с моей реализацией, - это вызвать
FindInterfaceWith
, как указано выше, из-за возможности таких отношений, какIAlpha
иIBravo
.
В некоторых ситуациях я тестировал код, вызывая FindAssignableWith
в качестве показанных примеров:
-
вывод назначаемых типов
(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
Появится тест
List'1
IList
, потому что я протестировалtypeof(List<int>)
с помощьюtypeof(List<String>)
; иDictionary'2
являютсяDictionary<String, String>
. Извините, что я не выполнял работу, чтобы представить точные имена типов.