Como discutimos anteriormente, nós introduzimos variância de interface e delegate em uma hipotética futura versão do C#, então precisamos de uma sintaxe para ela. Aqui estão algumas possibilidades.
Opção 1:
interface IFoo<+T, -U> { T Foo(U u); }
A CLR usa a convenção que estamos usando em toda a série de "+ sendo covariante e - contravarianteâ€. Embora isto tenha algum valor mnemónico (porque + quer dizer "é compatível com um tipo maior"), muitas pessoas (incluindo membros da comissão de design do C#!) têm dificuldades em se lembrar o que é exatamente.
Esta convenção também é utilizada pela linguagem de programação Scala.
Opção 2:
interface IFoo<T:*, *:U> {x
Este indica mais graficamente "alguma coisa que é extendida por T" e "alguma coisa a qual extende U". É similiar as keywords Java, onde elas dizem "extends U" ou "super T".
Embora isto não seja terrível, penso que isto funde um pouco as noções de extensão e compatibilidade de atribuição. Eu não quero implicar que IEnumerable<Animal> é a base de IEnumerable<Girafa>, mesmo que Animal seja a base de Girafa. Em vez disso, quero dizer que IEnumerable<Girafa> é convertível para IEnumerable<Animal>, ou a atribuição é compatível. Eu não quero exceder os conceitos o mecanismo de herança. Isto é ruim, nós estamos unindo classes base com interfaces base.
Opção 3:
interface IFoo<T, U> where T: covariant, U: contravariant {
Novamente, nada mal. O perigo aqui é similar ao dos sinais mais e menos: que ninguém se lembra o que "contravariante" e "covariante" significa. Este tem a vantagem de que pelo menos você pode buscar pelas palavras e achar alguma explicação.
Opção 4:
interface IFoo<[Covariant] T, [Contravariant] U> {
Similar à opção 3.
Opção 5:
interface IFoo<out T, in U> {
Estamos tomando um rumo diferente com essa sintaxe. Nas opções anteriores nós estávamos descrevendo como o usuário da interface pode trata-lá, respeitando as regras do sistema de conversão de tipos para conversão implícita – que é, quais são as variâncias válidas nos tipos dos parâmetros. Em vez disso, aqui nós estamos descrevendo como o implementador da interface tem a intenção de usar os tipos dos parâmetros.
A desvantagem é que, como discutido em artigos anteriores, você acaba em situações como essa:
delegate void Meta<out T>(Action<T> action);
onde o "out" T seja claramente utilizada em uma posição de entrada.