En los lenguajes orientados a objetos que admiten parámetros de tipo genérico (también conocidos como plantillas de clase y polimorfismo paramétrico, aunque, por supuesto, cada nombre tiene connotaciones diferentes), a menudo es posible especificar una restricción de tipo en el parámetro de tipo, de modo que descienda de otro tipo Por ejemplo, esta es la sintaxis en C #:
//for classes:
class ExampleClass<T> where T : I1 {
}
//for methods:
S ExampleMethod<S>(S value) where S : I2 {
...
}
¿Cuáles son las razones para usar tipos de interfaz reales sobre tipos restringidos por esas interfaces? Por ejemplo, ¿cuáles son las razones para hacer la firma del método I2 ExampleMethod(I2 value)
?
object-oriented
type-systems
generics
GregRos
fuente
fuente
ref
parámetros de tipo de valor, en realidad podría modificar el tipo de valor.Respuestas:
Usar la versión paramétrica da
Como ejemplo aleatorio, supongamos que tenemos un método que calcula las raíces de una ecuación cuadrática
Y luego quieres que funcione en otros tipos de números como cosas además
int
. Puedes escribir algo comoEl problema es que esto no dice lo que quieres. Dice
¡No podemos hacer algo como
int sol = solve(a, b, c)
ifa
,,b
yc
areint
s porque no sabemos que el método devolveráint
al final! Esto lleva a un baile incómodo con abatimiento y oración si queremos usar la solución en una expresión más amplia.Dentro de la función, alguien podría entregarnos un float, un bigint y grados, y tendríamos que sumarlos y multiplicarlos. Nos gustaría rechazar estáticamente esto porque las operaciones entre estas 3 clases van a ser un galimatías. Los grados son mod 360, por lo que no será el caso
a.plus(b) = b.plus(a)
y surgirán hilaridades similares.Si usamos el polimorfismo paramétrico con subtipado, podemos descartar todo esto porque nuestro tipo realmente dice lo que queremos decir
O en palabras "Si me das algún tipo que sea un número, puedo resolver ecuaciones con esos coeficientes".
Esto aparece en muchos otros lugares también. Otra fuente buena de ejemplos son funciones que abstracto sobre algún tipo de contenedor, ala
reverse
,sort
,map
, etc.fuente
Num<int>
) como argumento adicional. Siempre puede implementar la interfaz para cualquier tipo a través de la delegación. Esto es esencialmente lo que son las clases de tipo de Haskell, excepto que es mucho más tedioso de usar ya que tienes que pasar explícitamente por la interfaz.Porque eso es lo que necesitas ...
son dos firmas decididamente diferentes. El primero toma cualquier tipo que implemente la interfaz y la única garantía que ofrece es que el valor de retorno satisface la interfaz.
El segundo toma cualquier tipo que implemente la interfaz y garantiza que devolverá al menos ese tipo nuevamente (en lugar de algo que satisfaga la interfaz menos restrictiva).
A veces, quieres la garantía más débil. A veces quieres el más fuerte.
fuente
Or
que toma dosParser
objetos (una clase base abstracta, pero el principio se cumple) y devuelve una nuevaParser
(pero con un tipo diferente). El usuario final no debe saber o importar cuál es el tipo concreto.IEnumerable<T>
, devuelve otroIEnumerable<T>
que es, por ejemplo, en realidad unOrderedEnumerable<T>
)El uso de genéricos restringidos para los parámetros del método puede permitir que un método tenga su tipo de retorno basado en el de la cosa que se pasa. En .NET también pueden tener ventajas adicionales. Entre ellos:
Un método que acepta un constreñidos genérico como una
ref
oout
parámetro puede hacerse pasar una variable que satisface la restricción; por el contrario, un método no genérico con un parámetro de tipo de interfaz se limitaría a aceptar variables de ese tipo de interfaz exacto.Un método con el parámetro de tipo genérico T puede aceptar colecciones genéricas de T. Un método que acepte un
IList<T> where T:IAnimal
será capaz de aceptar unList<SiameseCat>
, pero un método que quisiera unIList<Animal>
no podría hacerlo.Una restricción a veces puede especificar una interfaz en términos del tipo genérico, por ejemplo
where T:IComparable<T>
.Una estructura que implementa una interfaz puede mantenerse como un tipo de valor cuando se pasa a un método que acepta un parámetro genérico restringido, pero debe encuadrarse cuando se pasa como un tipo de interfaz. Esto puede tener un gran efecto en la velocidad.
Un parámetro genérico puede tener múltiples restricciones, mientras que no hay otra forma de especificar un parámetro de "algún tipo que implemente tanto IFoo como IBar". A veces, esto puede ser un arma de doble filo, ya que el código que ha recibido un parámetro de tipo
IFoo
encontrará muy difícil pasarlo a un método que espere un genérico de doble restricción, incluso si la instancia en cuestión satisfaría todas las restricciones.Si en una situación particular no hubiera ventaja en usar un genérico, simplemente acepte un parámetro del tipo de interfaz. El uso de un genérico obligará al sistema de tipos y JITter a realizar un trabajo adicional, por lo que si no hay ningún beneficio, uno no debería hacerlo. Por otro lado, es muy común que se aplique al menos una de las ventajas anteriores.
fuente