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

Ken*_*Kin 21 c# types covariance contravariance

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

怎能不只有他们分配的测试对方,但也知道了最低协变,以获得最佳类型?

考虑以下示例(C#4.0)

  • // method body of Func is irrelevant, use default() instead
    Func<char[]> x = default(Func<char[]>);
    Func<int[]> y = default(Func<int[]>);
    
    Func<Array> f = default(Func<Array>);
    Func<IList> g = default(Func<IList>);
    
    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<Array> > Func<char[]> 
    f=y; // Array > int[] -> Func<Array> > Func<int[]> 
    
    // following two are okay; IList is the interface for the covariance
    g=x;
    g=y;
    
    Run Code Online (Sandbox Code Playgroud)

在上面的例子中,有什么发现之间的类型char[]int[].

Ken*_*Kin 25

更新:

事实证明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(null!=@interface) {
                    return @interface;
                }
            }
    
            return baseClass;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

有两种递归方法; 一个是FindInterfaceWith,另一个是一个重要的方法,GetTypesArray因为已经有一个名为GetTypeArrayclass 的方法,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
    
    Run Code Online (Sandbox Code Playgroud)

正如我们所知,它们是按照特定的顺序排列的,这就是它如何使事情发挥作用.GetTypesArray构建的阵列实际上是一棵平坦的树.该数组实际上在模型中如下:

  • rFbtV.png

    请注意,某些接口实现(如IList<int>实现)的ICollection<int>关系不与此图中的行相关联.

返回数组中的接口按照Array.Sort提供的排序规则进行排序GetCoverageComparison.

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

  • 注意

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

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

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

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

      public interface IDelta {
      }
      
      public interface ICharlie {
      }
      
      public interface IBravo: IDelta, ICharlie {
      }
      
      public interface IAlpha: IDelta, ICharlie {
      }
      
      Run Code Online (Sandbox Code Playgroud)

      然后哪一个更适合分配IAlphaIBravo?在这种情况下,FindInterfaceWith只需返回null.

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

  • 错误的扣除

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

    FindInterfaceWithnull如果有最好的选择,则返回; while FindAssignableWith直接返回确切的类.

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


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

  • 双重规则

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

    2. 如果规则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();
      
      Run Code Online (Sandbox Code Playgroud)

      FindInterfaceWith然后将执行可能的递归调用,以确定此接口是否足以被识别为最常见的接口或仅仅是IAlpha和其他关系IBravo.

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

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

  • 方法链

    var type=
        typeof(int[])
            .FindBaseClassWith(null)
            .FindBaseClassWith(null)
            .FindBaseClassWith(typeof(char[]));
    
    Run Code Online (Sandbox Code Playgroud)

    它会回来typeof(Array); 感谢这个功能,我们甚至可以打电话

    var type=
        typeof(String)
            .FindAssignableWith(null)
            .FindAssignableWith(null)
            .FindAssignableWith(typeof(String));
    
    Run Code Online (Sandbox Code Playgroud)

    我们可能无法对我的实现做的是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
    
    Run Code Online (Sandbox Code Playgroud)

    List'1出现的测试IList是因为我测试typeof(List<int>)typeof(List<String>); 并且Dictionary'2都是Dictionary<String, String>.对不起,我没有做工作来提供确切的类型名称.