what method how generic fun examples delegate c# types covariance contravariance

c# - method - ¿Cómo encontrar el tipo de covariante mínimo para el mejor ajuste entre dos tipos?



how to use func c# (3)

El método IsAssignableFrom devuelve un valor booleano que indica si un tipo es asignable de otro tipo.

¿Cómo podemos no solo probar si son asignables entre sí, sino también saber el tipo de covariante mínimo para el mejor ajuste?

Considere el siguiente ejemplo (C # 4.0)

  • Código

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

En el ejemplo anterior, lo que se debe buscar es el tipo entre char[] y int[] .


El caso más simple sería iterar sobre los tipos base de un objeto y verificar que no se puedan asignar con el otro tipo, como este:

  • Código

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

Esto producirá System.Object para dos tipos que no están relacionados, si ambas son clases. No estoy seguro de si este comportamiento cumple con su requisito.

Para casos más avanzados, estoy usando un método de extensión personalizado llamado IsExtendablyAssignableFrom .

Puede manejar diferentes tipos numéricos, genéricos, interfaces, parámetros genéricos, conversiones implícitas, anulables, boxeo / desempaquetado y prácticamente todos los tipos que he encontrado al implementar mi propio compilador.

He cargado el código en un repositorio de github separado [ here ], para que puedas usarlo en tu proyecto.


Si observa solo las clases base, el problema es trivial y la respuesta de Impworks da una solución ("iterar sobre los padres de un objeto y verificar que sean asignables con el otro tipo").

Pero si desea incluir también interfaces, no hay una solución única para el problema, como se nota con su ejemplo de IDelta e ICharlie . Dos o más interfaces pueden ser igualmente "buenas", por lo que no existe una mejor solución. Uno puede construir fácilmente diagramas (gráficos) arbitrariamente complejos de herencias de interfaz, y es fácil ver en tales diagramas que no hay un "FindAssignableWith" bien definido.

Además, la covarianza / contravarianza en C # se usa para tipos de varianza de tipos genéricos . Déjame dar un ejemplo. Supongamos que tenemos

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

luego, por supuesto, con clases base, el "FindAssignableWith" podría ser

solutionA: System.MulticastDelegate

Pero el tipo Func<out T> también es covariante ( out ) en su tipo de parámetro T Por lo tanto, el tipo

solutionB: System.Func<System.Object>

También es una solución en el sentido de que es IsAssignableFrom los dos tipos dados type1 y type2 . Pero lo mismo se podría decir de

solutionC: System.Func<System.IComparable>

que funciona porque tanto la string como Tuple<> son IComparable .

Entonces, en el caso general, no hay una solución única. Entonces, a menos que especifique reglas precisas que describan lo que quiere, no podemos encontrar un algoritmo que encuentre su solución.


actualizar:

Resulta que FindInterfaceWith se puede simplificar y construir una jerarquía de tipos FindInterfaceWith se vuelve redundante ya que las clases base no están necesariamente involucradas, siempre que tengamos en cuenta el tipo en sí mismo cuando se trata de una interfaz; así que he añadido un método de extensión GetInterfaces(bool) . Dado que podemos clasificar los espacios según las reglas de cobertura, la intersección ordenada de las interfaces son las candidatas. Si todos ellos son igual de buenos, dije que ninguno de ellos es considerado el mejor. Si no es el caso, entonces el mejor debe cubrir uno de los otros; y debido a que están ordenados, este tipo de relación debería existir en la mayoría de las dos interfaces correctas en la matriz para indicar que existe una mejor interfaz en común, que es la más específica.

El código se puede simplificar utilizando Linq ; pero en mi caso, debería reducir el requisito de referencias y espacios de nombres como sea posible.

  • Código

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

Hay dos métodos recursivos; uno es FindInterfaceWith , el otro es un método importante GetTypesArray ya que ya existe un método llamado GetTypeArray de Type de clase con un uso diferente.

Funciona como el método que Akim proporcionó a GetClassHierarchy ; pero en esta versión, construye una matriz como:

  • salida de jerarquía

    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

Como sabemos, están en un orden particular, que es como hace que las cosas funcionen. La matriz que GetTypesArray construyó es de hecho un árbol GetTypesArray . La matriz está en realidad en el modelo de la siguiente manera:

  • diagrama

    Tenga en cuenta la relación de algunas interfaces de implementación, como los implementos de IList<int> ICollection<int> no están vinculados con líneas en este diagrama.

Las interfaces en la matriz de retorno están ordenadas por Array.Sort con las reglas de ordenamiento proporcionadas por GetCoverageComparison .

Hay algunas cosas que mencionar, por ejemplo, la posibilidad de implementación de múltiples interfaces ha sido mencionada no solo una vez en algunas respuestas (como [ this ]); Y he definido la forma de resolverlos, esos son:

  • Nota

    1. El método GetInterfaces no devuelve interfaces en un orden particular, como orden alfabético o de declaración. Su código no debe depender del orden en que se devuelven las interfaces, porque ese orden varía.

    2. Debido a la recursión, las clases base siempre están ordenadas.

    3. Si dos interfaces tienen la misma cobertura, ninguna de ellas se considerará elegible.

      Supongamos que tenemos estas interfaces definidas (o las clases están bien):

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

      Entonces, ¿cuál es mejor para la asignación de IAlpha e IBravo ? En este caso, FindInterfaceWith simplemente devuelve null .

En la pregunta [ ¿Cómo encontrar el tipo asignable más pequeño en dos tipos (duplicado)? ], Afirmé:

  • una deducción equivocada

    Si esta suposición era correcta, entonces FindInterfaceWith convierte en un método redundante; debido a que la única diferencia entre FindInterfaceWith y FindAssignableWith es:

    FindInterfaceWith devuelve null si había una mejor opción de clase; mientras que FindAssignableWith devuelve la clase exacta directamente.

Sin embargo, ahora podemos ver que el método FindAssignableWith , que tiene que llamar a otros dos métodos, se basa en el supuesto original: el error paradójico simplemente desapareció mágicamente.

Sobre la regla de comparación de cobertura de interfaces de pedido, en el delegado GetCoverageComparison , uso:

  • reglas duales

    1. compare dos interfaces en una matriz de interfaces de origen, con cada una cubriendo cuántas otras en el origen, llamando a CountOverlapped

    2. Si la regla 1 no los distingue (devuelve 0 ), el orden secundario es llamar a CountOccurrence para determinar cuál ha sido heredada más veces por otros y luego comparar

      Las dos reglas son equivalentes a la consulta 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 continuación, FindInterfaceWith realizará la llamada recursiva, para descubrir si esta interfaz es suficiente para ser reconocida como la interfaz más común o simplemente como otra relación como IAlpha e IBravo .

Y sobre el método FindBaseClassWith , lo que devuelve es diferente de la suposición original de que si algún parámetro es nulo, devuelve nulo. En realidad, devuelve otro argumento pasado.

Esto está relacionado con la pregunta [ ¿Qué debería devolver el método `FindBaseClassWith`? ] sobre el encadenamiento de FindBaseClassWith de FindBaseClassWith . En la implementación actual, podemos llamarlo así:

  • método de encadenamiento

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

    Se devolverá typeof(Array) ; Gracias a esta característica, incluso podemos llamar

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

    Lo que tal vez no podamos hacer con mi implementación es llamar a FindInterfaceWith como arriba, debido a la posibilidad de relaciones como IAlpha e IBravo .

He probado el código en algunas situaciones llamando a FindAssignableWith como se muestra en los ejemplos:

  • salida de tipos asignables

    (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

    La prueba List''1 parece que IList se debe a que probé typeof(List<int>) con typeof(List<String>) ; y el Dictionary''2 son ambos Dictionary<String, String> . Lo siento, no hice el trabajo para presentar los nombres de tipo exactos.