2012-02-05 31 views
7

Tengo esta función¿Quién decide por última vez cuál es el tipo genérico?

public static T2 MyFunc<T1, T2>(T1 a, T1 b, T2 c) 
     { 
      return c; 
     }  

estoy creando instancias de la clase 2 Personas:

class Person 
     { } 

      Person p = new Person(); 
      Person p2 = new Person(); 

Voy a llamar a la función con:

MyClass.MyFunc(p, p2, 5); 

mi pregunta es:

¿Quién realmente decide sobre el tipo T1? (P p2??)

Porque si el de la izquierda es de Apple por lo que comprueba que el segundo es también una manzana

y si el segundo es naranja - se debe comprobar que el primero es también una Naranja.

enter image description here

Parece raro que pedir que Becuase en tiempo de compilación se producirá un error si no es el mismo.

Aún así, ¿quién decide sobre el tipo?

Y segundo - Si lo cambio a dinámico - en tiempo de ejecución - ¿quién decidirá qué tipo de T1 debería ser?

+3

No hay 'decisión'. Se encuentra una coincidencia o se produce un error. Simplemente intente con 'MyFunc (p1," ", 5)' o 'MyFunc (" ", p2, 5)'. –

Respuesta

13

En un nivel alto, la inferencia del tipo de método funciona así.

En primer lugar, hacer una lista de todos los argumentos - las expresiones que usted provee - y sus correspondientes parámetro formal de tipo.

Veamos un ejemplo más interesante que el que das. Supongamos que tenemos

class Person {} 
class Employee : Person {} 
... 
Person p = whatever; 
Employee p2 = whatever; 

y la misma llamada. Así hacemos las correspondencias:

p --> T1 
p2 --> T1 
5 --> T2 

A continuación hacemos una lista de lo que "los límites" son en cada parámetro de tipo y si son "fijos". Tenemos dos parámetros de tipo, y comenzamos sin límites superiores, inferiores o exactos.

T1: (unfixed) upper { } lower { } exact { } 
T2: (unfixed) upper { } lower { } exact { } 

(Recordemos nuestra discusión reciente en otra pregunta acerca de los tamaños relativos de los tipos que se basan en si es o no un tipo era más o menos restrictivo; un tipo que es más restrictivo es menor que uno que es menos restrictivo. Jirafa es más pequeña que Animal porque más cosas son animales que jirafas. Los conjuntos ligados "superior" e "inferior" son exactamente eso: la solución al problema de inferencia tipo para un parámetro de tipo dado debe ser más grande o idéntica a cada límite inferior y más pequeño que o idéntico a cada límite superior, y idéntico a cada límite exacto.)

Luego vemos cada argumento y su tipo correspondiente. (Si los argumentos son lambdas, entonces deberíamos averiguar el orden en el que vemos los argumentos, pero no tiene lambdas aquí, así que ignoremos ese detalle). Para cada argumento hacemos una inferencia para el tipo de parámetro formal, y agrega los hechos que deducimos sobre esa inferencia al conjunto vinculado. Así que después de ver el primer argumento, se deduce de los límites:

T1: (unfixed) upper { } lower { Person } exact { } 
T2: (unfixed) upper { } lower { } exact { } 

después del segundo argumento se deduce de los límites

T1: (unfixed) upper { } lower { Person, Employee } exact { } 
T2: (unfixed) upper { } lower { } exact { } 

Después de que el tercer argumento se deduce de los límites:

T1: (unfixed) upper { } lower { Person, Employee } exact { } 
T2: (unfixed) upper { } lower { int } exact { } 

Después de que hayamos avanzado tanto como podamos, "corregimos" los límites encontrando el mejor tipo en el conjunto de límites que satisface cada límite .

Para T1, hay dos tipos en los conjuntos de límites, Person y Employee. ¿Hay alguno de ellos que satisfaga todos los límites establecidos? Sí. Employee no cumple con el límite Person porque Employee es un tipo más pequeño que Person; Person es un límite inferior - significa ningún tipo más pequeño que Person es legal. Person satisface todos los límites: Person es idéntico a Person y es mayor que Employee, por lo que cumple ambos límites. El mejor tipo en el conjunto de límites que satisface cada límite es para T1 es Person y para T2 obviamente es int porque solo hay un tipo en los límites establecidos para T2. Entonces arreglamos los parámetros de tipo:

T1: (fixed) Person 
T2: (fixed) int 

Luego preguntamos "¿tenemos un límite fijo para cada parámetro de tipo?" y la respuesta es "sí", por lo que la inferencia de tipo tiene éxito.

Si cambio el tipo del primer argumento a dynamic, ¿cómo se deduce T1?

Si algún argumento es dinámica entonces la inferencia de T1 y T2 se difiere hasta que el tiempo de ejecución, en cuyo punto el analizador semántico considera la más derivada de tiempo de ejecución accesible tipo del valor como el tipo para el límite inferior suministrada por el argumento dinámico.


Si este tema de interés usted y desea obtener más información, hay un video de mí explicar la versión de C# 3 del algoritmo aquí:

http://blogs.msdn.com/b/ericlippert/archive/2006/11/17/a-face-made-for-email-part-three.aspx

(C# 3 no tienen límites superiores, solo límites inferiores y exactos; aparte de eso, los algoritmos son más o menos lo mismo.)

Varios artículos que he escrito sobre problemas de inferencia de tipo están aquí:

http://blogs.msdn.com/b/ericlippert/archive/tags/type+inference/

+1

Siento que el compilador me dio una respuesta. muchas gracias . (otra vez) –

+0

@RoyiNamir: De nada. He agregado algunos enlaces a algunos artículos y videos útiles si desea más información sobre este tema. –

+0

Entonces C# 4 agregó límites superiores? ¿Porqué es eso? ¿En qué situaciones son útiles? – svick

7

La posibilidad de omitir los tipos en la llamada

MyClass.MyFunc(p1, p2, 5); 

es un caramelo de sintaxis (a menos que estés usando los tipos anónimos), y se compila exactamente idéntica a

MyClass.MyFunc<Person, int>(p1, p2, 5); 

Los deduce del compilador los valores para T1 y T2 según los tipos de los parámetros a, b y c. Si p1 y p2 son de tipos incompatibles (ver svick's answer, el compilador no será capaz de deducir T1 y esto dará lugar a un error de compilación.

+0

Todavía, el compilador necesita un poco de anclaje para ver si debe verificar si hay manzana o naranja –

+1

Creo que al compilador no le importa cuáles son los tipos, solo comprueba si el primer y el segundo param son del mismo tipo. –

+1

@RoyiNamir, estás hablando de personas y estás hablando de manzanas y naranjas ... Perdón, tengo problemas para seguirte :) –

2

no hay prioridad, tanto (a y b) debe ser el mismo , es decir, por diseño, T1 se resuelve en la compilación. Si cambia a dynamic, simplemente pospone la resolución de tipo en tiempo de ejecución y fallará, en cambio, en tiempo de compilación si los tipos no son los mismos. Si desea que sean diferentes, Necesito introducir T3.

Editar:

La parte interesante:

Orange a = new Orange(); 
Apple b = new Apple(); 
string c = "Doh."; 

MyFunc<dynamic, string>(a,b,c); 

public static T2 MyFunc<T1, T2>(T1 a, T1 b, T2 c) where T2 : class 
{ 
    return (a.ToString() + b.ToString() + c.ToString()) as T2; 
}  

salidas:

I am an orange. I am an apple. Doh. 

que esto:

dynamic a = new Orange(); 
dynamic b = new Apple(); 
string c = "Doh."; 

MyFunc<Apple, string>(a,b,c); 

tirará:

RuntimeBinderException: The best overloaded method match for 'UserQuery.MyFunc<UserQuery.Apple,string>(UserQuery.Apple, UserQuery.Apple, string)' has some invalid arguments 

Sin embargo, parece que realmente que encontrar un buen libro o recurso sobre los tipos dinámicos en C# 4.0 para entender la magia que sucede aquí.

+0

tipos no son lo mismo ... bien! pero ¿quién debería ser exacto para quién? –

+1

No sé si entiendo lo que quiere decir, pero ... si a.GetType() es igual a b.GetType(), entonces también es verdadero al revés. Ambos necesitan ser T1 (siempre que no exista ninguna dinámica, ver mi edición anterior) – doblak

+1

La magia en ese caso es extremadamente simple: un argumento de tipo explícito 'dynamic' se trata exactamente como si dijeras' object 'cuando la expresión se analiza en tiempo de ejecución. Si tiene una pregunta específica sobre algo que no entiende, este es un sitio de preguntas y respuestas; Considera publicar una pregunta. –

0

En tiempo de compilación si los tipos son explícitos, entonces el compilador verificará los tipos de parámetros pasados, y verá si corresponden y pueden coincidir con los tipos en los genéricos (sin conflictos).

De todos modos, la comprobación real se realiza en "runtime" el código genérico se compilará como genérico de todos modos (a diferencia de las plantillas de C++). Y luego, cuando el compilador JIT compila la línea, comprobará y verá si puede crear el método de acuerdo con las plantillas que le asignó y los parámetros enviados.

1

¿Quién realmente decide sobre el tipo T1? (p? p2?)

¿No es obvio? Ambos. Los tipos de p y p2 tienen que ser compatibles. Al contrario de lo que dicen otras respuestas, no tienen que ser lo mismo. La regla actual es que tiene que haber una conversión implícita desde on de los tipos a los demás.

Por lo tanto, por ejemplo MyFunc("a", new object(), 5) es lo mismo que MyFunc<object, int>("a", new object(), 5), porque string es implícitamente convertible en object. Como otro ejemplo, MyFunc(42L, 42, 4) es lo mismo que MyFunc<long, int>(42L, 42, 4), porque int es implícitamente convertible a long.

Además, hay casos en los que la capacidad de permitir que el compilador deduzca los tipos no es simplemente agradable, es necesario. Específicamente, eso sucede cuando se usan tipos anónimos. Por ejemplo, MyFunc(new { p = "p" }, new { p = "p2" }, 5) no se puede volver a escribir para especificar los tipos explícitamente.

1

"que en realidad decidir sobre el tipo T1? (P? P2?)"

Normalmente, el compilador de C# decide esto. Si uno de los argumentos del método es dinámico, la decisión se tomará en tiempo de ejecución (por la biblioteca Microsoft.CSharp). En ambos casos, se aplica el algoritmo de inferencia de tipos descrito en la especificación C#: Tanto los tipos de p y p2 se añaden a T1 's conjunto de límites inferiores (límites superiores también son posibles, pero sólo cuando contravariant genéricos están involucrados).

Luego, el compilador selecciona uno de los tipos en el conjunto de límites que también cumple con todos los otros límites. Cuando solo hay un límite porque p y p2 tienen el mismo tipo, esta opción es trivial. De lo contrario (suponiendo que solo se trata de límites inferiores), eso significa que el compilador elige un tipo para que todos los demás tipos de candidatos sean implícitamente convertibles a ese tipo (lo que describe la respuesta de svick).

Si no hay una opción única, la inferencia de tipo es elegida; si es posible, se elige otra sobrecarga, de lo contrario se produce un error de compilación (cuando la decisión se realiza en tiempo de ejecución (dinámica), se produce una excepción).

+0

Querías decir genéricos * contravariantes *. –

+0

@Eric Lippert: Ups, siempre mezclo los términos covarianza/contravariancia. De hecho, los busqué cuando escribí esta pregunta, y de alguna manera aún logré escribir la incorrecta. Gracias por usar in/out en C#, eso es mucho más fácil de recordar. – Daniel

Cuestiones relacionadas