简体   繁体   English

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

[英]How to find the minimum covariant type for best fit between two types?

There's IsAssignableFrom method returns a boolean value indicates if one type is assignable from another type. IsAssignableFrom方法返回一个布尔值,指示是否可以从另一个类型分配一个类型。

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) 考虑以下示例(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[] . 在上面的示例中,要查找的是char[]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; 事实证明FindInterfaceWith可以简化,并且构建一个flatten类型层次结构变得多余,因为基类不一定涉及,只要我们在它是接口时考虑类型本身; so I've added an extension method GetInterfaces(bool) . 所以我添加了一个扩展方法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 ; 使用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. 一个是FindInterfaceWith ,另一个是一个重要的方法GetTypesArray因为已经有一个名为方法GetTypeArray类的Type有不同的用法。

It works like the method Akim provided GetClassHierarchy ; 它的工作方式类似于Akim提供的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. 构建的GetTypesArray数组实际上是一个展平树。 The array is actually in the model as the following: 该数组实际上在模型中如下:

  • diagram

    rFbtV.png

    Note the relation of some interfaces implementation such as IList<int> implements ICollection<int> are not linked with lines in this diagram. 注意某些接口实现的关系,例如IList<int>实现ICollection<int>没有与此图中的行链接。

The interfaces in the returning array is sorted by Array.Sort with the ordering rules provided by the GetCoverageComparison . 返回数组中的接口按Array.Sort排序, Array.Sort使用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 ]); 有一些事情需要提及,例如,在一些答案中不仅提到了多个接口实现的可能性(如[ 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. GetInterfaces方法不以特定顺序返回接口,例如按字母顺序或声明顺序。 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 ? 那么哪一个更适合分配IAlphaIBravo In this case, FindInterfaceWith just returns null . 在这种情况下, FindInterfaceWith只返回null

In the question [ How to find the smallest assignable type in two types (duplicate)? 在问题[ 如何找到两种类型中最小的可分配类型(重复)? ], I stated: ],我说:

  • a wrong deduction 错误的扣除

    If this supposition was correct, then the FindInterfaceWith becomes a redundant method; 如果这个假设是正确的,那么FindInterfaceWith就变成了一个冗余方法; because of the only difference between FindInterfaceWith and FindAssignableWith is: 因为FindInterfaceWithFindAssignableWith之间的唯一区别是:

    FindInterfaceWith returns null if there was a best choice of class; 如果有最佳的类选择, FindInterfaceWith将返回null ; while FindAssignableWith returns the exact class directly. FindAssignableWith直接返回确切的类。

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


About coverage comparison rule of ordering interfaces, in the delegate GetCoverageComparison , I use: 关于排序接口的覆盖率比较规则,在委托GetCoverageComparison ,我使用:

  • dual rules 双重规则

    1. compare two interfaces in a source interfaces array, with each covering how many others in the source, by calling CountOverlapped 比较源接口数组中的两个接口,每个接口通过调用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 如果规则1没有区分它们(返回0 ),则辅助排序是调用CountOccurrence来确定哪些被其他人继承了多次然后比较

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

And about the method FindBaseClassWith , what it returns is different from the original assumption of if any parameter is null then it returns null. 关于FindBaseClassWith方法,它返回的内容与原始假设不同,如果任何参数为null,则返回null。 It actually returns another argument passed in. 它实际上返回传入的另一个参数。

This is related to the question [ What should the method `FindBaseClassWith` return? 这与问题[ FindBaseClassWith`方法应该返回什么有关? ] about method chaining of FindBaseClassWith . 关于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) ; 它将返回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 . 我们可能无法对我的实现做的是调用上面的FindInterfaceWith ,因为有可能像IAlphaIBravo这样的关系。

I've had the code tested in some situations by calling FindAssignableWith as the examples shown: 我在某些情况下通过调用FindAssignableWith测试了代码,如下所示:

  • 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>) ; List'1测试出现IList是因为我用typeof(List<String>)测试了typeof(List<int>) typeof(List<String>) ; and the Dictionary'2 are both Dictionary<String, String> . Dictionary'2都是Dictionary<String, String> Sorry that I did not do the work to present the exact type names. 对不起,我没有做工作来提供确切的类型名称。

The simplest case would be iterating over the base types of one object and checking them for being assignable with the other type, like this: 最简单的情况是迭代一个对象的基类型并检查它们是否可以与另一个类型一起分配,如下所示:

  • Code

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

This will produce System.Object for two types which are unrelated, if they are both classes. 如果它们都是类,这将为两个不相关的类型生成System.Object I'm not sure if this behaviour met your requirement. 我不确定这种行为是否符合您的要求。

For more advanced cases, I'm using a custom extension method called IsExtendablyAssignableFrom . 对于更高级的案例,我使用名为IsExtendablyAssignableFrom的自定义扩展方法。

It can handle different numeric types, generics, interfaces, generic parameters, implicit conversions, nullable, boxing/unboxing, and pretty much all the types I have come across with when implementing my own compiler. 它可以处理不同的数字类型,泛型,接口,泛型参数,隐式转换,可空,装箱/拆箱,以及我在实现自己的编译器时遇到的几乎所有类型。

I've uploaded the code into a separate github repository [ here ], so you could use it in your project. 我已将代码上传到单独的github存储库[ 此处 ],因此您可以在项目中使用它。

If you look at base classes only , the problem is trivial and a solution is given by Impworks's answer ("iterating over one object's parents and checking them for being assignable with the other type"). 如果你看一下基类而已 ,问题是琐碎的解决方案是通过Impworks的回答给定(“遍历一个对象的父母,并检查他们被分配与其他类型的”)。

But if you want to include also interfaces, there's no unique solution to the problem, as you note yourself with your IDelta and ICharlie example. 但是如果你想要包含接口,那么问题没有独特的解决方案,因为你要注意自己的IDeltaICharlie示例。 Two or more interfaces can easily be equally "good", so there's no single best solution. 两个或更多接口可以很容易地同样“好”,因此没有单一的最佳解决方案。 One can easily construct arbitrarily complex diagrams (graphs) of interface inheritances, and it's easy to see from such diagrams that there's no well-defined "FindAssignableWith". 人们可以很容易地构造任意复杂的界面继承图(图),从这些图中很容易看出,没有明确定义的“FindAssignableWith”。

Besides, covariance/contravariance in C# are used for variance kinds of generic types. 此外,C#中的协方差/逆变用于泛型类型的方差种类。 Let me give an example. 让我举个例子吧。 Supose we have 我们有

type1: System.Func<string>
type2: System.Func<Tuple<int>>

then of course with base classes, the "FindAssignableWith" could be 当然,对于基类,“FindAssignableWith”可能是

solutionA: System.MulticastDelegate

But the type Func<out T> is also covariant ( out ) in its type parameter T . 但是类型Func<out T>在其类型参数T也是协变的out )。 Therefore, the type 因此,类型

solutionB: System.Func<System.Object>

is also a solution in the sense that it IsAssignableFrom the two given types type1 and type2 . 也就是IsAssignableFrom从两个给定类型type1type2的意义上的解决方案。 But the same could be said of 但同样可以这么说

solutionC: System.Func<System.IComparable>

which works because both string and Tuple<> are IComparable . 这是有效的,因为stringTuple<>都是IComparable

So in the general case, there's no unique solution. 所以在一般情况下,没有独特的解决方案。 So unless you specify precise rules describing what you want, we can't come up with an algorithm that finds your solution. 因此,除非您指定描述所需内容的精确规则,否则我们无法提出找到解决方案的算法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM