更新:
事实证明FindInterfaceWith
可以简化并且构建扁平类型层次结构变得多余,因为不一定涉及基类,只要我们在类型本身是接口时考虑到它;所以我添加了一个扩展方法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(null!=@interface) {
return @interface;
}
}
return baseClass;
}
}
有两种递归方法;一个是FindInterfaceWith
,另一个是一个重要的方法GetTypesArray
,因为已经有一个名为GetTypeArray
class的方法Type
具有不同的用法。
它的工作方式类似于Akim提供的GetClassHierarchy方法;但在这个版本中,它构建了一个数组,如:
正如我们所知道的,它们是按特定顺序排列的,这就是它使事情起作用的方式。构建的数组GetTypesArray
实际上是一棵扁平树。该数组实际上在模型中,如下所示:
图表
注意一些接口实现的关系,例如IList<int>
implements ICollection<int>
,在这个图中没有用线链接。
返回数组中的接口Array.Sort
按照GetCoverageComparison
.
有一些事情要提一下,例如,多个接口实现的可能性不仅在某些答案中提到过一次(例如[ this ]);我已经定义了解决它们的方法,它们是:
笔记
GetInterfaces方法不按特定顺序返回接口,例如字母顺序或声明顺序。您的代码不能依赖于返回接口的顺序,因为该顺序会有所不同。
由于递归,基类总是有序的。
如果两个接口具有相同的覆盖范围,则它们都不会被认为是合格的。
假设我们定义了这些接口(或者类就可以了):
public interface IDelta {
}
public interface ICharlie {
}
public interface IBravo: IDelta, ICharlie {
}
public interface IAlpha: IDelta, ICharlie {
}
那么哪个更适合分配IAlpha
and IBravo
?在这种情况下,FindInterfaceWith
只需返回null
.
在问题[如何在两种类型(重复)中找到最小的可分配类型?],我说:
错误的扣除
如果这个假设是正确的,那么就FindInterfaceWith
变成了一种多余的方法;FindInterfaceWith
因为和之间的唯一区别FindAssignableWith
是:
FindInterfaceWith
null
如果有最好的班级选择,则返回;whileFindAssignableWith
直接返回确切的类。
但是,现在我们可以看一下方法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
and IBravo
。
关于方法FindBaseClassWith
,它返回的内容与原始假设不同,即如果任何参数为空,则返回空。它实际上返回另一个传入的参数。
这与问题 [ `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>
。对不起,我没有做工作来提供确切的类型名称。