如何找到两种类型之间最佳拟合的最小协变类型?

IsAssignableFrom方法返回一个布尔值,指示是否可以从另一个类型分配一个类型。

我们怎样才能测试它们是否可以相互分配,还能知道最小协变类型以获得最佳拟合?

考虑以下示例(C#4.0)

  •  // method body of Func is irrelevant, use default() instead Func x = default(Func); Func y = default(Func); Func f = default(Func); Func g = default(Func); g=x; g=y; y=x; // won't compile x=y; // won't compile // following two are okay; Array is the type for the covariance f=x; // Array > char[] -> Func > Func f=y; // Array > int[] -> Func > Func // following two are okay; IList is the interface for the covariance g=x; g=y; 

在上面的示例中,要查找的是char[]int[]之间的类型。

更新:

事实certificate, FindInterfaceWith可以简化,并且构建一个flatten类型层次结构变得多余,因为基类不一定涉及,只要我们在它是接口时考虑类型本身; 所以我添加了一个扩展方法GetInterfaces(bool) 。 由于我们可以按照覆盖规则对相互作用进行排序,因此接口的排序交集是候选。 如果所有这些都同样好,我说没有一个被认为是最好的。 如果不是这样,那么最好的一个必须覆盖其中一个; 并且因为它们是有序的,所以这种关系应该存在于数组中最右边的两个接口中,以表示存在最具特异性的最佳接口。


使用Linq可以简化代码; 但在我的场景中,我应尽可能减少引用和命名空间的要求。

  •  using System; public static class TypeExtensions { static int CountOverlapped(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 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[] ax, T[] ay) { return Array.FindAll(ax, x => Array.FindIndex(ay, y => y.Equals(x))>=0); } /* static T[] SubtractPreserveOrder(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 ,另一个是一个重要的方法FindInterfaceWith ,因为已经有一个名为TypeGetTypeArray的方法,具有不同的用法。

它的工作方式类似于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数组实际上是一个展平树。 该数组实际上在模型中如下:

  • rFbtV.png

    注意某些接口实现的关系,例如IList实现ICollection不与此图中的行链接。

返回数组中的接口按Array.Sort排序, Array.Sort具有GetCoverageComparison提供的排序规则。

有一些事情需要提及,例如,在一些答案中不仅提到了多个接口实现的可能性(如[ this ]); 我已经定义了解决它们的方法,它们是:

  • 注意

    1. GetInterfaces方法不以特定顺序返回接口,例如按字母顺序或声明顺序。 您的代码不得依赖于返回接口的顺序,因为该顺序会有所不同。

    2. 由于递归,基类始终是有序的。

    3. 如果两个接口具有相同的覆盖范围,则它们都不会被视为合格。

      假设我们定义了这些接口(或类很好):

       public interface IDelta { } public interface ICharlie { } public interface IBravo: IDelta, ICharlie { } public interface IAlpha: IDelta, ICharlie { } 

      那么哪一个更适合分配IAlphaIBravo ? 在这种情况下, FindInterfaceWith只返回null

在问题[ 如何找到两种类型中最小的可分配类型(重复)? ],我说:

  • 错误的扣除

    如果这个假设是正确的,那么FindInterfaceWith就变成了一个冗余方法; 因为FindInterfaceWithFindAssignableWith之间的唯一区别是:

    如果有最佳的类选择, FindInterfaceWith返回null ; FindAssignableWith直接返回确切的类。

但是,现在我们可以看一下FindAssignableWith方法,它必须调用其他两个方法是基于原始假设,这个矛盾的bug只是神奇地消失了。


关于排序接口的覆盖率比较规则,在委托GetCoverageComparison ,我使用:

  • 双重规则

    1. 比较源接口数组中的两个接口,每个接口通过调用CountOverlapped覆盖源中的其他接口

    2. 如果规则1没有区分它们(返回0 ),则辅助排序是调用CountOccurrence来确定哪些被其他人inheritance了多次然后比较

      这两个规则等同于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将执行可能的递归调用,以确定此接口是否足以被识别为最常见的接口或仅仅是IAlphaIBravo类的另一个关系。

关于方法FindBaseClassWith ,它返回的内容与原始假设不同,如果任何参数为null则返回null。 它实际上返回传入的另一个参数。

这与问题[ FindBaseClassWith`方法应该返回什么有关? 关于FindBaseClassWith方法链接。 在当前的实现中,我们可以称之为:

  • 方法链

     var type= typeof(int[]) .FindBaseClassWith(null) .FindBaseClassWith(null) .FindBaseClassWith(typeof(char[])); 

    它将返回typeof(Array) ; 感谢这个function,我们甚至可以打电话

     var type= typeof(String) .FindAssignableWith(null) .FindAssignableWith(null) .FindAssignableWith(typeof(String)); 

    我们可能无法对我的实现做的是调用上面的FindInterfaceWith ,因为有可能像IAlphaIBravo这样的关系。

我在某些情况下通过调用FindAssignableWith测试了代码,如下所示:

  • 可分配类型的输出

     (Dictionary`2, Dictionary`2) = Dictionary`2 (List`1, List`1) = IList (Dictionary`2, KeyValuePair`2) = Object (IAlpha, IBravo) =  (IBravo, IAlpha) =  (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)测试了typeof(List) typeof(List) ; Dictionary'2都是Dictionary 。 很抱歉,我没有做的工作来提供确切的类型名称。

最简单的情况是迭代一个对象的基类型并检查它们是否可以与另一个类型一起分配,如下所示:

  •  public Type GetClosestType(Type a, Type b) { var t=a; while(a!=null) { if(a.IsAssignableFrom(b)) return a; a=a.BaseType; } return null; } 

如果它们都是类,这将为两个不相关的类型生成System.Object 。 我不确定这种行为是否符合您的要求。

对于更高级的情况,我使用名为IsExtendablyAssignableFrom的自定义扩展方法。

它可以处理不同的数字类型,generics,接口,generics参数,隐式转换,可空,装箱/拆箱,以及我在实现自己的编译器时遇到的几乎所有类型。

我已将代码上传到单独的github存储库[ 此处 ],因此您可以在项目中使用它。

如果你只看基类,问题是微不足道的,并且Impworks的答案给出了一个解决方案(“迭代一个对象的父级并检查它们是否可以与另一个类型一起分配”)。

但是如果你想要包含接口,那么问题没有独特的解决方案,因为你要注意自己的IDeltaICharlie示例。 两个或更多接口可以很容易地同样“好”,因此没有单一的最佳解决方案。 人们可以很容易地构造任意复杂的界面inheritance图(图),从这些图中很容易看出没有明确定义的“FindAssignableWith”。

此外,C#中的协方差/逆变用于generics类型的方差种类。 让我举个例子。 我们有

 type1: System.Func type2: System.Func> 

当然,对于基类,“FindAssignableWith”可能是

 solutionA: System.MulticastDelegate 

但是类型Func在其类型参数T也是协变的out )。 因此,类型

 solutionB: System.Func 

也就是IsAssignableFrom从两个给定类型type1type2的意义上的解决方案。 但同样可以这么说

 solutionC: System.Func 

这是有效的,因为stringTuple<>都是IComparable

所以在一般情况下,没有独特的解决方案。 因此,除非您指定描述所需内容的精确规则,否则我们无法提出找到解决方案的算法。