Parece que a sintaxe do inicializador de objeto C # 3.0 permite excluir o par abrir / fechar de parênteses no construtor quando existe um construtor sem parâmetros. Exemplo:
var x = new XTypeName { PropA = value, PropB = value };
Ao contrário de:
var x = new XTypeName() { PropA = value, PropB = value };
Estou curioso para saber por que o par de parênteses de abertura / fechamento do construtor é opcional aqui depois XTypeName
?
c#
syntax
types
language-design
initializer
James Dunne
fonte
fonte
Respostas:
Esta questão foi o assunto do meu blog em 20 de setembro de 2010 . As respostas de Josh e Chad ("eles não agregam valor, então por que exigi-los?" E "para eliminar a redundância") são basicamente corretas. Para aprofundar um pouco mais:
O recurso de permitir que você elimine a lista de argumentos como parte do "recurso maior" dos inicializadores de objeto encontrou nosso limite para recursos "açucarados". Alguns pontos que consideramos:
Dê uma outra olhada na lista de critérios acima. Um deles é que a mudança não introduz nenhuma nova ambigüidade na análise lexical, gramatical ou semântica de um programa. Sua mudança proposta faz introduzir uma ambigüidade análise semântica:
A linha 1 cria um novo C, chama o construtor padrão e, a seguir, chama o método de instância M no novo objeto. A linha 2 cria uma nova instância de BM e chama seu construtor padrão. Se os parênteses na linha 1 fossem opcionais, a linha 2 seria ambígua. Teríamos então que propor uma regra que resolvesse a ambigüidade; não poderíamos cometer um erro porque isso seria uma alteração significativa que transforma um programa C # válido existente em um programa quebrado.
Portanto, a regra teria que ser muito complicada: essencialmente, os parênteses são opcionais apenas nos casos em que não introduzem ambigüidades. Teríamos que analisar todos os casos possíveis que introduzem ambigüidades e, em seguida, escrever código no compilador para detectá-los.
Sob essa luz, volte e veja todos os custos que mencionei. Quantos deles agora se tornam grandes? Regras complicadas têm grandes custos de design, especificação, desenvolvimento, teste e documentação. Regras complicadas têm muito mais probabilidade de causar problemas com interações inesperadas com recursos no futuro.
Tudo para quê? Um pequeno benefício para o cliente que não adiciona nenhum novo poder de representação à linguagem, mas adiciona casos extremos malucos apenas esperando para gritar "peguei você" para alguma pobre alma ingênua que se depara com isso. Recursos como esse são eliminados imediatamente e colocados na lista "nunca faça isso".
Aquele foi imediatamente claro; Estou bastante familiarizado com as regras em C # para determinar quando um nome pontilhado é esperado.
Todos três. Na maioria das vezes, apenas olhamos para as especificações e o macarrão, como fiz acima. Por exemplo, suponha que desejamos adicionar um novo operador de prefixo ao C # chamado "frob":
(ATUALIZAÇÃO:
frob
é claroawait
; a análise aqui é essencialmente a análise que a equipe de design fez ao adicionarawait
.)"frob" aqui é como "novo" ou "++" - vem antes de uma expressão de algum tipo. Trabalharíamos na precedência e associatividade desejadas e assim por diante, e então começaríamos a fazer perguntas como "e se o programa já tiver um tipo, campo, propriedade, evento, método, constante ou local chamado frob?" Isso levaria imediatamente a casos como:
isso significa "fazer a operação frob no resultado de x = 10, ou criar uma variável do tipo frob chamada x e atribuir 10 a ela?" (Ou, se frobbing produz uma variável, poderia ser uma atribuição de 10 a
frob x
. Afinal,*x = 10;
analisa e é válido sex
forint*
.)Isso significa "frob o resultado do operador mais unário em x" ou "adicionar expressão frob a x"?
E assim por diante. Para resolver essas ambigüidades, podemos introduzir heurísticas. Quando você diz "var x = 10;" isso é ambíguo; pode significar "inferir o tipo de x" ou pode significar "x é do tipo var". Portanto, temos uma heurística: primeiro tentamos pesquisar um tipo denominado var, e apenas se não existir um inferimos o tipo de x.
Ou podemos alterar a sintaxe para que não seja ambígua. Quando projetaram o C # 2.0, eles tiveram este problema:
Isso significa "rendimento x em um iterador" ou "chamar o método de rendimento com o argumento x?" Mudando para
agora é inequívoco.
No caso de parênteses opcionais em um inicializador de objeto, é direto raciocinar sobre se existem ambigüidades introduzidas ou não porque o número de situações nas quais é permitido introduzir algo que começa com {é muito pequeno . Basicamente, apenas vários contextos de instrução, lambdas de instrução, inicializadores de array e isso é tudo. É fácil raciocinar em todos os casos e mostrar que não há ambigüidade. Garantir que o IDE permaneça eficiente é um pouco mais difícil, mas pode ser feito sem muitos problemas.
Esse tipo de manipulação das especificações geralmente é suficiente. Se for um recurso particularmente complicado, retiramos ferramentas mais pesadas. Por exemplo, ao projetar o LINQ, um dos caras do compilador e um dos caras do IDE, ambos com experiência em teoria do analisador, construíram um gerador de analisador que poderia analisar gramáticas em busca de ambigüidades e, em seguida, alimentou nele as gramáticas C # propostas para compreensão de consultas ; fazendo isso, encontramos muitos casos em que as consultas eram ambíguas.
Ou, quando fizemos inferência de tipo avançada em lambdas em C # 3.0, escrevemos nossas propostas e as enviamos para a Microsoft Research em Cambridge, onde a equipe de idiomas era boa o suficiente para elaborar uma prova formal de que a proposta de inferência de tipo era teoricamente correto.
Certo.
Em C # 1, fica claro o que isso significa. É o mesmo que:
Ou seja, ele chama G com dois argumentos que são bools. Em C # 2, isso pode significar o que significava em C # 1, mas também poderia significar "passe 0 para o método genérico F que usa os parâmetros de tipo A e B e, em seguida, passe o resultado de F para G". Adicionamos uma heurística complicada ao analisador que determina qual dos dois casos você provavelmente se refere.
Da mesma forma, os casts são ambíguos mesmo em C # 1.0:
Isso é "lançar -x em T" ou "subtrair x de T"? Novamente, temos uma heurística que dá uma boa estimativa.
fonte
Porque é assim que o idioma foi especificado. Eles não agregam valor, então por que incluí-los?
Também é muito semelhante a matrizes digitadas implicitamente
Referência: http://msdn.microsoft.com/en-us/library/ms364047%28VS.80%29.aspx
fonte
Isso foi feito para simplificar a construção de objetos. Os designers de linguagem não disseram (que eu saiba) especificamente por que achavam que isso era útil, embora seja explicitamente mencionado na página de especificações do C # versão 3.0 :
Suponho que eles sentiram que os parênteses, neste caso, não eram necessários para mostrar a intenção do desenvolvedor, uma vez que o inicializador de objetos mostra a intenção de construir e definir as propriedades do objeto.
fonte
Em seu primeiro exemplo, o compilador infere que você está chamando o construtor padrão (a especificação da linguagem C # 3.0 declara que, se nenhum parêntese for fornecido, o construtor padrão será chamado).
No segundo, você chama explicitamente o construtor padrão.
Você também pode usar essa sintaxe para definir propriedades enquanto passa explicitamente os valores para o construtor. Se você tivesse a seguinte definição de classe:
Todas as três afirmações são válidas:
fonte
Não sou Eric Lippert, então não posso dizer com certeza, mas presumo que seja porque o parêntese vazio não é necessário ao compilador para inferir a construção de inicialização. Portanto, torna-se uma informação redundante e desnecessária.
fonte