Em linguagens orientadas a objetos que suportam parâmetros de tipo genéricos (também conhecidos como modelos de classe e polimorfismo paramétrico, embora, é claro, cada nome possua conotações diferentes), muitas vezes é possível especificar uma restrição de tipo no parâmetro de tipo, de forma que ele desça de outro tipo. Por exemplo, esta é a sintaxe em C #:
//for classes:
class ExampleClass<T> where T : I1 {
}
//for methods:
S ExampleMethod<S>(S value) where S : I2 {
...
}
Quais são os motivos para usar tipos de interface reais sobre tipos restritos por essas interfaces? Por exemplo, quais são os motivos para fazer a assinatura do método I2 ExampleMethod(I2 value)
?
object-oriented
type-systems
generics
GregRos
fonte
fonte
ref
parâmetros do tipo de valor, pode realmente modificar o tipo de valor.Respostas:
O uso da versão paramétrica fornece
Como exemplo aleatório, suponha que tenhamos um método que calcule as raízes de uma equação quadrática
E então você quer que ele funcione em outros tipos de números, como outras coisas
int
. Você pode escrever algo comoA questão é que isso não diz o que você deseja. Diz
Não podemos fazer algo como
int sol = solve(a, b, c)
sea
,b
ec
somosint
s porque não sabemos que o método retornará umint
no final! Isso leva a uma dança desajeitada, com abatimento e oração, se quisermos usar a solução em uma expressão maior.Dentro da função, alguém pode nos dar um float, um bigint e graus, e teríamos que adicioná-los e multiplicá-los. Gostaríamos de rejeitar estaticamente isso porque as operações entre essas três classes serão sem sentido. Os graus são mod 360, por isso não será o caso
a.plus(b) = b.plus(a)
e hilaridades semelhantes surgirão.Se usarmos o polimorfismo paramétrico com subtipagem, podemos descartar tudo isso, porque nosso tipo realmente diz o que queremos dizer
Ou em palavras "Se você me der um tipo que é um número, eu posso resolver equações com esses coeficientes".
Isso aparece em muitos outros lugares também. Outra boa fonte de exemplos são funções que abstrato sobre algum tipo de recipiente, ala
reverse
,sort
,map
, etc.fonte
Num<int>
) como um argumento extra. Você sempre pode implementar a interface para qualquer tipo através da delegação. Isso é essencialmente o que as classes de tipo de Haskell são, exceto muito mais entediante de usar, pois você precisa passar explicitamente pela interface.Porque é disso que você precisa ...
são duas assinaturas decididamente diferentes. O primeiro leva qualquer tipo de implementação da interface e a única garantia que faz é que o valor de retorno satisfaça a interface.
O segundo pega qualquer tipo que implementa a interface e garante que ele retornará pelo menos esse tipo novamente (em vez de algo que satisfaça a interface menos restritiva).
Às vezes, você quer a garantia mais fraca. Às vezes você quer o mais forte.
fonte
Or
que pega doisParser
objetos (uma classe base abstrata, mas o princípio é válido) e retorna um novoParser
(mas com um tipo diferente). O usuário final não deve saber ou se importar com o tipo de concreto.IEnumerable<T>
, retorna outroIEnumerable<T>
que é, por exemplo, na verdade umOrderedEnumerable<T>
)O uso de genéricos restritos para parâmetros de método pode permitir que um método seja muito do seu tipo de retorno com base no que foi transmitido. No .NET, eles também podem ter vantagens adicionais. Entre eles:
Um método que aceita um genérico restrito como parâmetro
ref
ouout
pode receber uma variável que satisfaça a restrição; por outro lado, um método não genérico com um parâmetro do tipo de interface seria limitado a aceitar variáveis desse tipo exato de interface.Um método com o parâmetro de tipo genérico T pode aceitar coleções genéricas de T. Um método que aceita um
IList<T> where T:IAnimal
poderá aceitar aList<SiameseCat>
, mas um método que desejasse umIList<Animal>
não seria capaz de fazê-lo.Às vezes, uma restrição pode especificar uma interface em termos do tipo genérico, por exemplo
where T:IComparable<T>
.Uma estrutura que implementa uma interface pode ser mantida como um tipo de valor quando passada para um método que aceita um parâmetro genérico restrito, mas deve ser encaixotada quando passada como um tipo de interface. Isso pode ter um efeito enorme na velocidade.
Um parâmetro genérico pode ter várias restrições, enquanto não há outra maneira de especificar um parâmetro de "algum tipo que implemente IFoo e IBar". Às vezes, isso pode ser uma faca de dois gumes, pois o código que recebeu um parâmetro do tipo
IFoo
dificilmente passará para um método que espera um genérico de dupla restrição, mesmo que a instância em questão atenda a todas as restrições.Se em uma situação específica não houver vantagem em usar um genérico, basta aceitar um parâmetro do tipo de interface. O uso de um genérico forçará o sistema de tipos e o JITter a realizar um trabalho extra; portanto, se não houver benefício, não se deve fazê-lo. Por outro lado, é muito comum que pelo menos uma das vantagens acima se aplique.
fonte