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
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.
Debido a la recursión, las clases base siempre están ordenadas.
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
eIBravo
? En este caso,FindInterfaceWith
simplemente devuelvenull
.
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 entreFindInterfaceWith
yFindAssignableWith
es:FindInterfaceWith
devuelvenull
si había una mejor opción de clase; mientras queFindAssignableWith
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
compare dos interfaces en una matriz de interfaces de origen, con cada una cubriendo cuántas otras en el origen, llamando a
CountOverlapped
Si la regla 1 no los distingue (devuelve
0
), el orden secundario es llamar aCountOccurrence
para determinar cuál ha sido heredada más veces por otros y luego compararLas 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 comoIAlpha
eIBravo
.
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 llamarvar 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 comoIAlpha
eIBravo
.
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 queIList
se debe a que probétypeof(List<int>)
contypeof(List<String>)
; y elDictionary''2
son ambosDictionary<String, String>
. Lo siento, no hice el trabajo para presentar los nombres de tipo exactos.