Há boas razões para ternaries em C# são limitadas?
-
13-09-2019 - |
Pergunta
Falha:
object o = ((1==2) ? 1 : "test");
Êxito:
object o;
if (1 == 2)
{
o = 1;
}
else
{
o = "test";
}
O erro na primeira instrução é:
Tipo de expressão condicional não pode ser determinado porque não há nenhuma conversão implícita entre 'int' e 'cadeia'.
Por que há a necessidade de ser, porém, eu estou atribuindo a elas valores para uma variável do tipo object.
Editar: O exemplo acima é trivial, sim, mas há exemplos em que isso seria muito útil:
int? subscriptionID; // comes in as a parameter
EntityParameter p1 = new EntityParameter("SubscriptionID", DbType.Int32)
{
Value = ((subscriptionID == null) ? DBNull.Value : subscriptionID),
}
Solução
usar:
object o = ((1==2) ? (object)1 : "test");
A questão é que o tipo de retorno do operador condicional não pode ser determinado sem ambiguidade. Ou seja, entre int e string, não há melhor escolha. O compilador sempre usará o tipo de expressão verdadeira e lançará implicitamente a expressão falsa, se necessário.
Editar:Em seu segundo exemplo:
int? subscriptionID; // comes in as a parameter
EntityParameter p1 = new EntityParameter("SubscriptionID", DbType.Int32)
{
Value = subscriptionID.HasValue ? (object)subscriptionID : DBNull.Value,
}
PS:
Isso não é chamado de 'operador ternário'. Isto é um operador ternário, mas é chamado de 'operador condicional'.
Outras dicas
Embora as outras respostas são correto, no sentido de que eles fazem verdadeiras e relevantes declarações, existem algumas sutis pontos da linguagem de design aqui que ainda não foi expressa ainda.Diversos fatores contribuem para o design atual do operador condicional.
Primeiro, é desejável como muitas expressões como possível ter uma inequívoca tipo que pode ser determinado unicamente a partir do conteúdo da expressão.Isso é desejável por várias razões.Por exemplo:ele faz a construção de um motor de IntelliSense muito mais fácil.Você digita x.M(some-expression.
e o IntelliSense precisa ser capaz de analisar alguns expressão, determinar o seu tipo, e produzir uma lista suspensa ANTES de IntelliSense sabe qual o método x.M se refere.IntelliSense não sabe o que x.M refere-se a certeza se M é sobrecarregado até que ele vê todos os argumentos, mas você ainda não digitou no mesmo o primeiro argumento ainda.
Segundo, nós preferimos informações de tipo de fluxo "de dentro para fora", devido, precisamente, o cenário que acabei de mencionar:resolução de sobrecarga.Considere o seguinte:
void M(object x) {}
void M(int x) {}
void M(string x) {}
...
M(b ? 1 : "hello");
O que se deve fazer?Ele deve chamar o objeto de sobrecarga?Deve ele, às vezes, chamada de seqüência de caracteres de sobrecarga e, por vezes, chamada int sobrecarga?O que se você tivesse outra sobrecarga, dizer M(IComparable x)
-- quando você pegá-lo?
As coisas ficam muito complicadas quando informações de tipo de "fluxos de ambos os lados".Dizendo: "eu estou atribuindo isso a uma variável do tipo object, portanto, o compilador deve saber que é OK para escolher o objeto do tipo" não lavar;é frequentemente o caso que nós não sabemos o tipo de variável que você está atribuindo porque é isso que nós estamos no processo de tentar descobrir.Resolução de sobrecarga é exatamente o processo de trabalho os tipos de parâmetros, que são as variáveis que você está atribuindo os argumentos, a partir dos tipos dos argumentos.Se os tipos dos argumentos depende do tipo para o qual estão sendo atribuído, em seguida, temos uma circularidade em nosso raciocínio.
O tipo de informação que "o fluxo de ambos os lados" para expressões lambda;implementação eficiente que me levou a maior parte do ano.Eu escrevi uma longa série de artigos que descrevem algumas das dificuldades na concepção e implementação de um compilador que pode fazer análise, onde o tipo de fluxos de informação complexos, expressões com base no contexto em que a expressão é, possivelmente, a ser utilizado;a parte um é aqui:
Você pode dizer "bem, OK, não vejo por que o fato de que eu sou a atribuir ao objecto não pode ser utilizado com segurança pelo compilador, e não vejo por que é necessário para que a expressão tem uma inequívoca do tipo, mas porque não é o tipo de expressão de objeto, uma vez que ambos int e string são convertíveis em objeto?" Isso me leva ao terceiro ponto:
Terceiro, uma das sutil, mas aplicada de forma consistente os princípios de design de C# é "não produzem tipos de magia".Quando é fornecida uma lista de expressões a partir do qual devemos determinar um tipo, o tipo de determinarmos está sempre na lista em algum lugar.Nós nunca magia de um novo tipo e escolha-o para você;o tipo que você recebe é sempre aquele que você nos deu para escolher.Se você disser para encontrar o melhor tipo em um conjunto de tipos, encontramos o melhor tipo EM que o conjunto de tipos.No conjunto {int, string}, não existe um melhor tipo comum, a forma não é, digamos, "Animal, Animal, Mamífero, Wallaby".Essa decisão de projeto aplica-se o operador condicional, para a inferência de tipo unificação cenários, a inferência de tipo implícito tipos de matriz, e assim por diante.
A razão para esta decisão de projeto é que ele torna mais fácil para o comum dos seres humanos, para descobrir o que o compilador vai fazer em qualquer situação onde uma melhor tipo deve ser determinada;se você sabe que um tipo que está ali, olhando na cara, vai ser escolhido, é muito mais fácil descobrir o que vai acontecer.
Ele também evita ter de trabalhar muito de regras complexas sobre qual é o melhor tipo comum de um conjunto de tipos quando há conflitos.Suponha que você tenha os tipos de {Foo Bar}, onde ambas as classes implementam IBlah, e ambas as classes herdam de Baz.Qual é o melhor tipo comum, IBlah, que tanto a implementar, ou Baz, que tanto se estender?Não queremos ter que responder a esta pergunta;queremos evitá-lo inteiramente.
Finalmente, observe que o compilador C# realmente é a determinação dos tipos de sutilmente errado, em alguns casos obscuros.Meu primeiro artigo sobre o que é aqui:
http://blogs.msdn.com/ericlippert/archive/2006/05/24/type-inference-woes-part-one.aspx
É possível argumentar que, na verdade, o compilador faz isso direito e a especificação é errado;o design de implementação na minha opinião é melhor do que o do spec'd design.
Enfim, isso é apenas algumas das razões para o design desse aspecto particular do operador ternário.Há outras sutilezas aqui, por exemplo, como o verificador CLR determina se um dado conjunto de ramificação de caminhos são garantidos para deixar o tipo correto na pilha em todos os caminhos possíveis.Discutir em detalhe me levaria ao invés de longe.
Qual é o recurso X desta forma, muitas vezes é uma pergunta muito difícil de responder.É muito mais fácil para responder o comportamento real.
Meu palpite para a razão.O operador condicional é permitido de forma sucinta e laconicamente usar uma expressão booleana para escolher entre 2 valores relacionados.Eles devem ser relacionados como eles estão sendo usados em um único local.Se o usuário vez pega 2 alheios valores talvez o tinha um sutil erro / bug lá de código e compilador é melhor alertando-os para este, ao invés de incluir implicitamente carcaça para o objeto.O que pode ser algo que não esperava.
"int" é um tipo primitivo, não um objeto enquanto "string" é considerada mais um "objeto primitivo". Quando você faz algo como "Objeto O = 1", você está realmente boxe o "int" para um "int32". Aqui está um link para um artigo sobre boxe:
http://msdn.microsoft.com/en-us/magazine/cc301569.aspx
Geralmente, o boxe deve ser evitado devido ao desempenho perdas difíceis de rastrear.
Quando você usa uma expressão ternária, o compilador não analisa a variável de atribuição para determinar qual é o tipo final. Para dividir sua declaração original sobre o que o compilador está fazendo:
Declaração: objeto o = ((1 == 2)? 1: "teste");
Compilador:
- Quais são os tipos de "1" e "teste" em '((1 == 2)? 1: "teste")? Eles combinam?
- O tipo final do número 1 corresponde ao tipo de operador de atribuição para 'Objeto O'?
Como o compilador não avalia o #2 até que o #1 seja concluído, ele falha.