Inline se e interfaces (polimorfismo)
Pergunta
public class Foo : IFooBarable {...}
public class Bar : IFooBarable {...}
Então, por que isso não vai compilar ...
int a = 1;
IFooBarable ting = a == 1 ? new Foo() : new Bar();
Mas isso vai ...
IFooBarable ting = a == 1 ? new Foo() : new Foo();
IFooBarable ting = a == 1 ? new Bar() : new Bar();
Solução
O compilador primeiro tenta avaliar a expressão à direita:
? new Foo() : new Bar();
Não há conversão implícita entre esses dois, daí a mensagem de erro. Você consegue fazer isso:
IFooBarable ting = a == 1 ? (IFooBarable)(new Foo()) : (IFooBarable)(new Bar());
Outras dicas
Isso é abordado na seção 7.13 da especificação de idioma C#. Essencialmente, o que está matando esse cenário é que deve haver uma conversão implícita entre os tipos de 2 valores para os operandos ternários. Essa conversão é considerada na abdência do tipo de variável.
Então também Foo
deve ser conversível para Bar
ou vice-versa. Nem é um erro de compilação.
Os últimos dois funcionam porque eles consideram apenas 1 tipo (ou seja Foo
ou Bar
). Porque eles são do mesmo tipo que determinam o tipo de expressão é simples e funciona bem.
Apenas para adicionar um pouco às respostas corretas postadas aqui: Existem duas diretrizes de design que levam a essa especificação.
A primeira é que raciocinamos de "de dentro para fora". Quando voce diz
double x = 2 + y;
Primeiro, elaboramos o tipo de x, depois o tipo de 2, depois o tipo de y, depois o tipo de (2+y) e, finalmente, descobrimos se x e (2+y) têm tipos compatíveis. Mas não usamos o tipo de x para decidir qual é o tipo de 2, y ou 2+y.
A razão pela qual essa é uma boa regra é porque muitas vezes o tipo de "receptor" é exatamente o que estamos tentando descobrir:
void M(Foo f) {}
void M(Bar b) {}
...
M(x ? y : z);
O que devemos fazer aqui? Temos que descobrir o tipo de expressão condicional para fazer resolução de sobrecarga, a fim de determinar se isso vai fazer ou barrar. Portanto, não podemos usar O fato de que isso é, digamos, ir à nossa análise do tipo de expressão condicional! Isso é um problema de galinha e ovo.
A exceção a esta regra são as expressões lambda, que Faz Tire o tipo deles do contexto deles. Fazer esse recurso funcionar corretamente era incrivelmente complicado; veja meu série de blogs Sobre expressões lambda vs métodos anônimos, se você estiver interessado.
O segundo elemento é que nunca "magia" um tipo para você. Quando recebeu um monte de coisas das quais devemos deduzir um tipo, sempre deduzimos um tipo que estava realmente bem à nossa frente.
No seu exemplo, a análise é assim:
- elaborar o tipo de consequência
- resolver o tipo de alternativa
- Encontre o melhor tipo que é compatível com a consequência e a alternativa
- Certifique -se de que exista uma conversão do tipo de expressão condicional para o tipo de coisa que está usando a expressão condicional.
De acordo com o primeiro ponto, não raciocinamos fora para insensibilidade; Não usamos o fato de sabermos o tipo de variável que vamos para descobrir o tipo de expressão. Mas o interessante agora é que quando você tem
b ? new Cat() : new Dog()
Dizemos "o tipo de expressão condicional é o melhor tipo no conjunto {Cat, Dog}". Não dizemos "o tipo de expressão condicional é o melhor tipo compatível com gato e cachorro". Isso seria mamífero, mas não fazemos isso. Em vez disso, dizemos "o resultado deve ser algo que realmente vimos" e dessas duas opções, nem o vencedor claro. Se você disse
b ? (Animal) (new Cat()) : new Dog()
Então temos uma escolha entre animal e cachorro, e o animal é o vencedor claro.
Agora, observe que na verdade não implementamos corretamente a especificação C# ao fazer essa análise de tipo! Para detalhes do erro, veja meu artigo nele.
Porque o tipo de expressão condicional é sempre inferido a partir de suas duas partes, não da variável à qual o resultado deve ser aplicado. Essa inferência só funciona quando os tipos são iguais ou um é de referência compatível com o outro. Nesse caso, nenhum dos dois tipos é de referência compatível ao outro.