本文介绍了如何找到两种类型之间最佳拟合的最小协变类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我们如何不仅可以测试它们是否可以fromto彼此分配,而且还知道最小协变类型以获得最佳拟合?

考虑以下示例(C# 4.0)

  • 代码

    //Func的方法体无关,使用default()代替Funcx = default(Func);Funcy = default(Func);Func<Array>f = default(Func);Funcg = default(Func);g=x;g=y;y=x;//不会编译x=y;//不会编译//下面两个都可以;数组是协方差的类型f=x;//数组 >字符[] ->Func<Array>>Funcf=y;//数组 >int[] ->Func<Array>>Func//下面两个都可以;IList 是协方差的接口g=x;g=y;

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

解决方案

更新:

事实证明 FindInterfaceWith 可以简化,构建扁平类型层次结构变得多余,因为不一定涉及基类,只要我们在它是接口时考虑类型本身;所以我添加了一个扩展方法 GetInterfaces(bool).由于我们可以根据覆盖规则对接口进行排序,因此排序后的接口交集是候选对象.如果他们都一样好,我说没有一个被认为是最好的.如果不是这样,那么最好的一个必须覆盖其他之一;并且因为它们是排序的,所以这种关系应该存在于数组中最右边的两个接口中,以表示有一个最好的共同接口,也是最具体的.

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

  • 代码

    使用系统;公共静态类 TypeExtensions {静态 int CountOverlapped(T[] ax, T[] ay) {返回 IntersectPreserveOrder(ay, ax).Length;}静态 int CountOccurrence(Type[] ax, Type ty) {var a = Array.FindAll(ax, x => Array.Exists(x.GetInterfaces(), tx => tx.Equals(ty)));返回 a.Length;}静态比较GetCoverageComparison(类型[] az){返回 (tx, ty) =>{int 重叠,发生;var ay = ty.GetInterfaces();var ax = tx.GetInterfaces();if(0!=(overlapped=CountOverlapped(az, ax)).CompareTo(CountOverlapped(az, ay)))) {返回重叠;}if(0!=(occurrence=CountOccurrence(az, tx).CompareTo(CountOccurrence(az, ty)))) {返回发生;}返回0;};}静态 T[] IntersectPreserveOrder(T[] ax, T[] ay) {返回 Array.FindAll(ax, x => Array.FindIndex(ay, y => y.Equals(x))>=0);}/*静态 T[] SubtractPreserveOrder(T[] ax, T[] ay) {return Array.FindAll(ax, x => Array.FindIndex(ay, y => y.Equals(x)) x.Equals(type4)) ?类型3:空;}public static Type FindBaseClassWith(this Type type1, Type type2) {如果(空==类型1){返回类型2;}如果(空==类型2){返回类型1;}for(var type4 = type2; null!=type4; type4=type4.BaseType) {for(var type3 = type1; null!=type3; type3=type3.BaseType) {如果(type4==type3){返回类型4;}}}返回空;}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);如果(空!=@接口){返回@interface;}}返回基类;}}

有两种递归方法;一个是FindInterfaceWith,另一个是重要的方法GetTypesArray,因为已经有一个Type类的名为GetTypeArray的方法有不同的用法.

它的工作原理类似于 ]);我已经定义了解决它们的方法,它们是:

  • 注意

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

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

    3. 如果两个接口的覆盖范围相同,则它们都不符合条件.

      假设我们定义了这些接口(或者类就可以了):

      公共接口 IDelta {}公共接口 ICharlie {}公共接口 IBRAVO:IDelta、ICharlie {}公共接口 IAlpha: IDelta, ICharlie {}

      那么对于IAlphaIBravo的赋值,哪个更好呢?在这种情况下,FindInterfaceWith 只返回 null.

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

  • 错误的推断

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

    FindInterfaceWith 返回 null 如果有最好的类选择;而 FindAssignableWith 直接返回确切的类.

然而,现在我们可以看看方法FindAssignableWith,它要调用另外两个方法是基于原来的假设,矛盾的bug就这样神奇地消失了.

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

  • 双重规则

    1. 通过调用CountOverlapped

    2. 比较源接口数组中的两个接口,每个接口覆盖源中的其他接口数量
    3. 如果规则 1 没有区分它们(返回 0),则二级排序是调用 CountOccurrence 来确定哪些被其他人继承了更多次并然后比较

      这两条规则等价于Linq查询:

      interfaces=(从它在接口让 order1=it.GetInterfaces().Intersect(interfaces).Count()让订单2=(从接口中的 x其中 x.GetInterfaces().Contains(it)选择 x).数数()orderby order1, order2选择它).ToArray();

      FindInterfaceWith 然后将执行可能的递归调用,以确定这个接口是否足以识别为最常见的接口或只是另一种关系,如 IAlpha伊布拉沃.

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

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

  • 方法链

    var type=类型(整数[]).FindBaseClassWith(null).FindBaseClassWith(null).FindBaseClassWith(typeof(char[]));

    它将返回typeof(Array);多亏了这个功能,我们甚至可以打电话

    var type=类型(字符串).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) = 对象(IAlpha,IBravo) = <null>(IBravo, IAlpha) = <null>(ICollection, IList) = ICollection(IList, ICollection) = ICollection(Char[], Int32[]) = IList(Int32[], Char[]) = IList(IEnumerable`1, IEnumerable`1) = IEnumerable(字符串,数组)= 对象(数组,字符串)= 对象(Char[], Int32[]) = IList(Form, SplitContainer) = ContainerControl(SplitContainer, Form) = ContainerControl

    List'1 测试出现 IList 是因为我用 typeof(Listtypeof(List)字符串>);和 Dictionary'2 都是 Dictionary.抱歉,我没有做显示确切类型名称的工作.

There's IsAssignableFrom method returns a boolean value indicates if one type is assignable from another type.

How can we not only test if they are assignable from or to each other, but also know the minimum covariant type for best fit?

Consider the following example(C# 4.0)

  • Code

    // 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;
    

In the example above, what to find is the type between char[] and int[].

解决方案

update:

It turns out FindInterfaceWith can be simplified and to build a flatten type hierarchy becomes redundant as the base classes are not necessarily involved, as long as we take the type itself into account when it is an interface; so I've added an extension method GetInterfaces(bool). Since we can sort the interaces by the rules of coverage, the sorted intersection of interfaces are the candidates. If all of them are equally good, I said none of them is considered the best one. If it's not the case, then the best one must cover one of the others; and because they are sorted, this kind of relationship should exists in the right most two interfaces in the array to denote that there is a best interface in common which is the most specific.


The code can be simplified by using Linq; but in my scenario, I should reduce the requirement of references and namespaces as possible ..

  • 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);
        }
    
        /*
        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;
        }
    }
    


There're two recursive methods; one is FindInterfaceWith, the other is an important method GetTypesArray as there is already a method named GetTypeArray of class Type with a different of usage.

It works like the method Akim provided GetClassHierarchy; but in this version, it builds an array like:

  • output of hierarchy

    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
    

As we are aware of they are in a particular order, which is how it makes things work. The array GetTypesArray built is in fact a flatten tree. The array is actually in the model as the following:

  • diagram

The interfaces in the returning array is sorted by Array.Sort with the ordering rules provided by the GetCoverageComparison.

There are some things to mention, for example, the possibility of multiple interfaces implementation been mentioned not only once in some answers(like [this]); and I have defined the way to solve them, those are:

  • note

    1. The GetInterfaces method does not return interfaces in a particular order, such as alphabetical or declaration order. Your code must not depend on the order in which interfaces are returned, because that order varies.

    2. Because of recursion, the base classes are always ordered.

    3. If two interfaces have the same coverage, neither of them will be considered eligible.

      Suppose we have these interfaces defined(or classes are just fine):

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

      then which one is better for assignment of 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:

  • a wrong deduction

However, now we can look at the method FindAssignableWith, it has to call other two methods is based on the original assumption, The paradoxical bug just disappeared magically.


About coverage comparison rule of ordering interfaces, in the delegate GetCoverageComparison, I use:

  • dual rules

    1. compare two interfaces in a source interfaces array, with each covering how many others in the source, by calling CountOverlapped

    2. If rule 1 does not distinguish them (returns 0), the secondary ordering is to call CountOccurrence to determine which has been inherited more times by others and then comparing

      the two rules are equivalent to the 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 then perform the possibly recursive call, to figure out is this interface sufficient to recognized as the most common interface or just another relation like IAlpha and IBravo.

And about the method FindBaseClassWith, what it returns is different from the original assumption of if any parameter is null then it returns null. It actually returns another argument passed in.

This is related to the question [ What should the method `FindBaseClassWith` return? ] about method chaining of FindBaseClassWith. In the current implementation, we can call it like:

  • method chaining

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

    It will return typeof(Array); thank to this feature, we can even call

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

    What we may not able to do with my implementation is to call FindInterfaceWith like above, because of the possibility of relations like IAlpha and IBravo.

I've had the code tested in some situations by calling FindAssignableWith as the examples shown:

  • output of assignable types

    (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 test appears IList is because I tested typeof(List<int>) with typeof(List<String>); and the Dictionary'2 are both Dictionary<String, String>. Sorry that I did not do the work to present the exact type names.

这篇关于如何找到两种类型之间最佳拟合的最小协变类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-22 10:47