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

There, the IsAssignableFrom method returns a boolean value, indicates whether one type is assignable from another type.

How can we not only check whether they can be assigned from or to each other, but also know the minimum covariance type for the best fit?

Consider the following example (C # 4.0)

  • the 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 above example, what to find is the type between char[] and int[] .

+21
c # types covariance contravariance
Jan 23 '13 at 3:45
source share
3 answers

update:

It turns out that FindInterfaceWith can be simplified, and building a hierarchy of type flatten becomes unnecessary, because base classes are not necessarily involved if we take this type into account when it is an interface; so I added the GetInterfaces(bool) extension method. Since we can sort intervals by coverage rules, sorted interface intersections are candidates. If they are all equally good, I said that none of them are considered the best. If this is not so, then the best should cover one of the others; and because they are sorted, this kind of relationship must exist in the correct majority of the two interfaces in the array to indicate that there is a better interface, which is the most specific.




Code can be simplified using Linq ; but in my scenario, I should reduce the requirements for links and namespaces as little as possible.

  • The 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 are two recursive methods; one is FindInterfaceWith , the other is an important GetTypesArray method, since there already exists a method called GetTypeArray of the Type class with a different use.

It works as an Akim method provided GetClassHierarchy ; but in this version it creates an array like:

  • hierarchy inference

     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 know, they are in a certain order, and that is how it works. The GetTypesArray actually built is a flattened tree. The array is actually in the model as follows:

  • diagram

    rFbtV.png

    Note that the implementation relation of some interfaces, such as IList<int> implements ICollection<int> , is not related to the lines in this diagram.

The interfaces in the returned array are sorted by Array.Sort with the ordering rules provided by GetCoverageComparison .

We can mention, for example, the possibility of using several interfaces not only once in some answers (for example, <this]); and I determined a way to solve them:

  • note

    • The GetInterfaces method does not return interfaces in a specific order, for example, in alphabetical order or in declaration order. Your code should not depend on the return order of the interfaces, since this order is changing.

    • Because of recursion, base classes are always ordered.

    • If two interfaces have the same coverage, none of them will be considered acceptable.

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

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

      which one is better for appointing 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:

  • wrong conclusion

    If this assumption was correct, then FindInterfaceWith becomes a redundant method; due to the only difference between FindInterfaceWith and FindAssignableWith is:

    FindInterfaceWith returns null if there was a better choice of class; and FindAssignableWith returns the exact class directly.

However, now we can look at the FindAssignableWith method, it should call the other two methods based on the original assumption. The paradoxical mistake simply disappeared magically.




About matching rules with the scope of ordering interfaces, in the delegate of GetCoverageComparison , I use:

  • double rules

    • compare two interfaces in an array of source interfaces, each of which covers the number of others in the source, calling CountOverlapped

    • If rule 1 does not distinguish between them (returns 0 ), the second ordering is to call CountOccurrence to determine what is inherited more than others, and then compare

      two rules are equivalent to a 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 FindInterfaceWith make a recursive call to find out if this interface is sufficient for recognition as the most common interface or just another relation like IAlpha and IBravo .

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

This is due to the question [ What should the `FindBaseClassWith` method return? ] about the FindBaseClassWith method FindBaseClassWith . In the current implementation, we can call it as follows:

  • method chain

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

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

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

    What we cannot do with my implementation is to call FindInterfaceWith , as indicated above, due to the possibility of relationships such as IAlpha and IBravo .

In some situations, I tested the code by calling FindAssignableWith as the examples shown:

  • assignment type assignment

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

+25
Jan 23 '13 at 3:45
source share

The simplest case will be iterating over the basic types of one object and checking them for assignment to another type, for example:

  • the 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 create a System.Object for two types that are not related if they are both classes. I am not sure if this behavior meets your requirements.

For more complex cases, I use my own extension method called IsExtendablyAssignableFrom .

It can handle various numerical types, generics, interfaces, general parameters, implicit conversions, nullify, box / unpack and almost all types that I encountered when implementing my own compiler.

I uploaded the code to a separate github repository [ here ] so you can use it in your project.

+2
Mar 19 '13 at 12:04 on
source share

If you look only at the base classes, the problem is trivial, and the solution is given by the Impworks answer ("iterating over the parents of one object and checking them for assignment to another type").

But if you want to include interfaces, there is no single solution to the problem, as you are IDelta yourself with the IDelta and ICharlie . Two or more interfaces can be equally “good”, so there is no single best solution. You can easily build arbitrarily complex diagrams (graphs) of interface inheritance, and on such diagrams it is easy to see that there is no clearly defined "FindAssignableWith".

In addition, covariance / contravariance in C # is used for variance types of generic types. Let me give you an example. Assume that

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

then of course with the base classes, "FindAssignableWith" can be

 solutionA: System.MulticastDelegate 

But the type Func<out T> also covariant ( out ) in its type parameter T Therefore type

 solutionB: System.Func<System.Object> 

is also a solution in the sense that it is IsAssignableFrom two given types, type1 and type2 . But the same can be said of

 solutionC: System.Func<System.IComparable> 

which works because string and Tuple<> are IComparable .

Thus, in the general case, there is no single solution. Therefore, if you do not specify the exact rules that describe what you want, we will not be able to find an algorithm that will find your solution.

+1
Mar 19 '13 at 21:57
source share



All Articles