@lagerdalek - Eu marcaria esse comentário com +1 se pudesse; bem lembrado
Marc Gravell
Respostas:
39
Eu concordo enfaticamente com este post (para aqueles que reclamam da falta de ToString, existe um atributo de depurador para fornecer um formato personalizado para sua classe).
No topo da lista acima, eu também adicionaria as seguintes solicitações razoáveis:
tipos de referência não anuláveis como um complemento aos tipos de valor anuláveis,
permitem substituir o construtor vazio de uma estrutura,
permitir restrições de tipo genérico para especificar classes seladas,
Concordo com outro postador aqui que solicitou assinaturas de construtor arbitrárias quando usadas como restrições, ou seja, onde T : new(string)ou ondeT : new(string, int)
Também concordo com outro postador aqui sobre a correção de eventos, tanto para listas de eventos vazios quanto na configuração simultânea (embora o último seja complicado),
operadores devem ser definidos como métodos de extensão, e não como métodos estáticos da classe (ou não apenas como métodos estáticos, pelo menos),
permite propriedades e métodos estáticos para interfaces (Java tem isso, mas C # não),
permitir a inicialização de eventos em inicializadores de objetos (apenas campos e propriedades são permitidos atualmente),
por que a sintaxe do "inicializador de objeto" só pode ser usada ao criar um objeto? Por que não torná-lo disponível a qualquer momento, ou seja.var e = new Foo(); e { Bar = baz };
todas as coleções devem ter instantâneos imutáveis para iteração (ou seja, alterar a coleção não deve invalidar o iterador),
tuplas são fáceis de adicionar, mas um tipo algébrico fechado eficiente como " Either<T>" não é, então eu adoraria alguma maneira de declarar um tipo algébrico fechado e impor correspondência de padrão exaustiva nele (basicamente suporte de primeira classe para o padrão de visitante, mas muito mais eficiente); então apenas pegue enums, estenda-os com suporte exaustivo de correspondência de padrões e não permita casos inválidos,
Eu adoraria suporte para correspondência de padrões em geral, mas pelo menos para teste de tipo de objeto; Também gosto da sintaxe do switch proposta em outro post aqui,
Concordo com outro post que as System.IOaulas, tipo Stream, são um tanto mal elaboradas; qualquer interface que requer algumas implementações para lançar NotSupportedExceptioné um projeto ruim,
IListdeve ser muito mais simples do que é; na verdade, isso pode ser verdade para muitas das interfaces de coleção concretas, como ICollection,
muitos métodos lançam exceções, como IDictionary por exemplo,
Eu preferiria uma forma de exceções verificadas melhor do que aquela disponível em Java (veja a pesquisa sobre sistemas de tipo e efeito para saber como isso pode ser feito),
consertar vários casos incômodos na resolução de sobrecarga de método genérico; por exemplo, tente fornecer dois métodos de extensão sobrecarregados, um que opera em tipos de referência e o outro em tipos de struct anuláveis e veja como sua inferência de tipo gosta disso,
fornecem uma maneira de refletir com segurança sobre os nomes dos campos e membros para interfaces como INotifyPropertyChanged, que usam o nome do campo como uma string; você pode fazer isso usando um método de extensão que pega um lambda com a MemberExpression, ie. () => Foo, mas isso não é muito eficiente,
Atualização: C # 6.0 adicionou o nameof()operador para nomes de membro único, mas não funciona em genéricos (em nameof(T) == "T"vez do nome do argumento de tipo real: você ainda precisa fazer typeof(T).Name)) - nem permite que você obtenha uma string de "caminho" , por exemplo, nameof(this.ComplexProperty.Value) == "Value"limitando suas possíveis aplicações.
permitir operadores em interfaces e implementar todos os tipos de número de núcleo IArithmetic; outras interfaces de operação compartilhadas úteis também são possíveis,
tornar mais difícil a mutação de campos / propriedades de objetos ou, pelo menos, permitir a anotação de campos imutáveis e fazer o verificador de tipo aplicá-los (apenas trate-os como propriedade somente getter para chrissakes, não é difícil!); na verdade, unifique campos e propriedades de uma forma mais sensata, já que não adianta ter os dois; As propriedades automáticas do C # 3.0 são um primeiro passo nessa direção, mas não vão longe o suficiente,
Atualização: Enquanto C # tinha a readonlypalavra - chave, e C # 6.0 adicionou propriedades automáticas somente leitura, embora não seja tão rigoroso quanto o suporte de linguagem verdadeira para tipos e valores imutáveis.
simplificar a declaração de construtores; Eu gosto da abordagem do F #, mas o outro post aqui que requer simplesmente "novo" em vez do nome da classe é pelo menos melhor,
Suponho que seja o suficiente por enquanto. Essas são todas as irritações que encontrei na semana passada. Eu provavelmente poderia continuar por horas se eu realmente me concentrasse nisso. C # 4.0 já está adicionando argumentos nomeados, opcionais e padrão, que eu aprovo enfaticamente.
Agora, para um pedido irracional:
seria muito, muito bom se C # / CLR pudesse suportar polimorfismo de construtor de tipo, ou seja, genéricos sobre genéricos,
Com relação ao # 1, cada tipo deve ter algum valor padrão ou o sistema deve fornecer um meio de executar um construtor sempre que uma variável ou campo de um tipo específico for alocado. Eu preferiria o último (um desdobramento do nº 2), mas o nº 1 poderia ser acomodado se métodos / propriedades não virtuais pudessem ser decorados para especificar que eles deveriam ser chamados sem uma verificação de nulo. Isso permitiria que coisas como campos "String" se comportassem como se fossem uma string vazia em vez de nula (uma vez que a função "comprimento" estática de String poderia retornar 0 se chamada em uma string nula).
supercat
1
Com relação ao # 2, seria útil em geral se structs pudessem especificar não apenas um construtor diferente de preencher com zero, mas também um construtor de cópia diferente de byte para byte-cópia. Na verdade, eu realmente gostaria de ver uma estrutura semelhante a .net que pudesse fazer um bom trabalho em reconhecer que entidades podem ter valor ou semântica de referência e permitir que objetos de heap de tipo de valor sejam marcados como mutáveis, compartilhados-imutáveis ou não comprometidos (um objeto de valor não confirmado pode sofrer mutação se seu modo for primeiro CompareExchange'd para mutável ou pode ser compartilhado se seu modo for primeiro CompareExchange'd para compartilhado).
supercat
1
Pontos excelentes! Para o nº 1, uma anotação do sistema de tipos é a solução comum, mas eu prefiro propagar restrições do construtor por meio de variáveis de tipo, como T: new (). Ref. # 2, boa observação sobre construtores de cópia, mas eu ficaria feliz com construtores mais gerais ao longo das linhas que descrevi acima. Melhor ainda seria eliminar inteiramente os métodos construtores como distintos e simplesmente torná-los métodos estáticos. Isso permite padrões de construção mais simples e gerais, especialmente se permitirmos métodos estáticos nas interfaces. Construtores-como-métodos-estáticos + métodos-estáticos-em-interfaces resolve o # 1 também.
naasking
3
# 3: Qual é o ponto de usar uma classe selada como um parâmetro de tipo genérico, por exemplo, Foo <T> onde T: string? # 11: Ok, então eu tenho List<T>um milhão de Ts. Como você propõe que o instantâneo seja obtido com eficiência? # 21: Use a readonlypalavra - chave ... embora haja algumas boas sugestões aqui, elas são apenas isso - sugestões, não falhas de design.
Qwertie
2
Esta é uma resposta muito interessante, mas acho que devemos atualizar com os recursos do C # 6. Exemplo: Itens 19 e 21 foram implementados =)
eduardobr
72
o Reset()método on IEnumerator<T>foi um erro (para blocos de iteradores, a especificação da linguagem exige até que isso lance uma exceção)
os métodos de reflexão que retornam matrizes foram, na opinião de Eric, um erro
covariância de matriz era e continua sendo uma raridade
Atualização: C # 4.0 com .NET 4.0 adicionado suporte covariante / contravariância para interfaces genéricas (como IEnumerable<out T>e Func<in T, out TResult>, mas não tipos concretos (como List<T>).
ApplicationException em vez disso, caiu em desgraça - foi um erro?
coleções sincronizadas - uma boa ideia, mas não necessariamente útil na realidade: você geralmente precisa sincronizar várias operações ( Containsentão Add), então uma coleção que sincroniza operações distintas não é tão útil
Update: Os System.Collections.Concurrenttipos , com TryAdd, GetOrAdd, TryRemove, etc foram adicionados no .NET Framework 4.0 - embora os métodos que aceitam um delegado fábrica não garantem a fábrica só será chamada uma vez por chave.
mais uso poderia ter sido feito do padrão using/ lock- talvez permitindo que eles compartilhem uma sintaxe reutilizável (extensível?); você pode simular isso retornando IDisposablee usando using, mas poderia ter sido mais claro
blocos iteradores: nenhuma maneira simples de verificar os argumentos antecipadamente (ao invés de preguiçosamente). Claro, você pode escrever dois métodos encadeados, mas isso é feio
uma imutabilidade mais simples seria bom; C # 4.0 ajuda um pouco , mas não o suficiente
não há suporte para "este parâmetro ref-type não pode ser nulo" - embora os contratos (em 4.0) ajudem de alguma forma. Mas sintaxe como Foo(SqlConnection! connection)(que injeta uma verificação de nulo / throw) seria bom (contraste com int?etc)
falta de suporte de operadores e construtores não padrão com genéricos; C # 4.0 resolve isso um pouco com dynamic, ou você pode habilitá-lo assim
a variável iteradora sendo declarada fora do while na foreachexpansão, o que significa que anon-methods / lambdas capturam a única variável, em vez de uma por iteração (doloroso com threading / async / etc)
IEnumerable! = IEnumerable <object> é realmente estranho
Rauhotz
2
Bem, a coisa do IEnumerable é uma ressaca do 1.1; você pode usar .Cast <object> () com LINQ, pelo menos
Marc Gravell
8
O pessoal da BCL disse que ApplicationExceptionera um erro - não era útil como eles esperavam. Eles também disseram que System.Exceptiondeveria ter sido abstract.
Jay Bazuzi
2
Não anulável: deve ser um erro de compilação passar um tipo de referência regular T para algo que recebe um T não anulável! (assim como você não pode passar int? para int). Passar na outra direção está bem, é claro.
Jay Bazuzi
1
@Jon Harrop: IMHO, deveria haver suporte para arrays imutáveis e para referências de array somente leitura. Possivelmente também para algumas outras variantes de array (por exemplo, um "array redimensionável" (referência indireta) ou um array de referência com um deslocamento e limite).
supercat
60
TextWriter é uma classe base de StreamWriter. wtf?
1 Tenho que pesquisar todas as vezes. (O que quer dizer que não consigo novo TextWriter ()?)
Nicholas Piasecki
1
Graças a Deus ... pensei que fosse só eu.
IJ Kennedy
? É sempre algo que grava texto, mas apenas StreamWriter o faz em um fluxo. Parece muito simples.
Jon Hanna
4
O nome StreamWriter não torna o fato de que ele escreve texto óbvio o suficiente IMO. Soa isoladamente como se fosse apenas escrever bytes e TextWriter seria uma implementação sofisticada que converteria a api de (string toWrite) em bytes para você. Agora, se fosse chamado de StreamTextWriter, com certeza, seria imediatamente óbvio, mas um pouco longo :(
Quibblesome
44
Uma pequena petição de C # - construtores usam a sintaxe C ++ / Java de ter o construtor com o mesmo nome da classe.
New()ou ctor()teria sido muito melhor.
E, claro, ferramentas como o coderush tornam esse problema menor para renomear classes, mas de um POV de legibilidade, New () fornece grande clareza.
Erm, como ele saberia do que você está tentando criar uma nova instância?
BlueRaja - Danny Pflughoeft
4
@BlueRaja: Scott está se referindo à nomenclatura de construtores em classes. class Foo { new(int j) {i = j} int i; }
dalle
Embora eu concorde 100% que ctor () ou constructor () teria sido melhor ( Newpalavras-chave não --uppercase são contra a convenção), hesito em chamar isso de falha de design. Eles queriam atrair desenvolvedores C ++ / Java existentes, e o uso de muitas convenções sintáticas antigas e idiotas, sem dúvida, os ajudou a alcançar seu objetivo.
Qwertie
relacionado a esta questão: stackoverflow.com/questions/32101993/c-sharp-sorted-linkedlist não há como (usando apenas .NET) simplesmente classificar uma LinkedList com MergeSort, bucketsort ou qualquer outro algoritmo de classificação, mas usando linq que é muito mais lento do que uma implementação ad hoc.
CoffeDeveloper de
29
Eu não entendo que você não pode fazer
onde T: novo (U)
Portanto, você declara que o tipo genérico T tem um construtor não padrão.
Um tipo genérico declara como você usará objetos desse tipo, ou seja, sua interface. O construtor é um detalhe de implementação dessa interface, o que não é preocupação do consumidor. Quando você precisar criar uma instância parametrizada, use uma fábrica.
Bryan Watts
Para obter informações, embora ele não possa fazer a verificação em tempo de compilação, há algum código em MiscUtil para usar construtores não padrão (em genéricos) de forma eficiente, ou seja, sem Activator.CreateInstance ou reflexão.
Marc Gravell
Porque não faz sentido e é confuso em alguns usos. No entanto, pode ser útil ao trabalhar com objetos imutáveis.
Pop Catalin
7
Em geral, a falta de restrições de membros é irritante, sim.
MichaelGG
3
Amém, sempre quis isso
Steve
20
Estou realmente surpreso por ser o primeiro a mencionar este:
Os conjuntos de dados do tipo ADO.NET não expõem colunas anuláveis como propriedades de tipos anuláveis. Você deve ser capaz de escrever isto:
int? i = myRec.Field;
myRec.Field = null;
Em vez disso, você tem que escrever isso, o que é simplesmente estúpido:
int? i = (int?)myRec.IsFieldNull() ? (int?)null : myRec.Field;
myRec.SetFieldNull();
Isso era irritante no .NET 2.0 e é ainda mais irritante agora que você tem que usar jiggery-pokery como o acima em suas consultas LINQ legais.
Também é irritante que o Add<TableName>Rowmétodo gerado seja igualmente insensível à noção de tipos anuláveis. Ainda mais porque os TableAdaptermétodos gerados não são.
Não há muito no .NET que me faça sentir como se a equipe de desenvolvimento dissesse "Ok, rapazes, estamos perto o suficiente - envie!" Mas isso com certeza faz.
Eu concordo de todo o coração! Isso me incomoda toda vez que preciso usá-lo (o que é frequente). Argh! +1
Eyvind
2
O mínimo que eles poderiam fazer seria criar uma nova classe DataSetV2 (nome ruim - apenas para argumentar), que usasse tipos de valor anuláveis em vez de DBNull.
Christian Hayter
Não nos esqueçamos do absurdo de exigir um valor especial,, DBNull.Valuequando nullele próprio seria perfeitamente adequado para representar NULL. Felizmente o LINQ-to-SQL usa apenas null para NULL.
Qwertie
Na verdade, esse absurdo é a rocha sobre a qual todo o edifício absurdo foi construído.
Robert Rossney
20
Não sou um grande fã das classes Stream, StringWriter, StringReader, TextReader, TextWriter ... apenas não é intuitivo o que é o quê.
IEnumerable.Reset lançando uma exceção para iteradores. Tenho alguns componentes de terceiros que sempre chamam a redefinição quando ligados aos dados e exigem que eu faça o cast para uma lista primeiro para usá-los.
O Xml Serializer deve ter elementos IDictionary serializados
Eu esqueci totalmente da API HttpWebRequest & FTP, que chatice ... (obrigado pelo comentário Nicholas para me lembrar disso :-)
Editar
5. Outro aborrecimento meu é como System.Reflection.BindingFlags, tem usos diferentes dependendo do método que você está usando. Em FindFields, por exemplo, o que CreateInstance ou SetField significa? Este é um caso em que eles sobrecarregaram o significado por trás dessa enumeração, o que é confuso.
+1 Eu tenho que procurar qualquer uma das classes XmlTextWriter, TextWriter, etc. todas as vezes. O mesmo acontece com o material HttpWebRequest / Response. API completamente não intuitiva lá.
Nicholas Piasecki
+ 1-1 = 0: XmlTextWriter etc, eu concordo que é impossível inferir verdadeiramente o que eles são a partir do nome. HttpWebRequest Não concordo, acho bastante intuitivo.
AnthonyWJones
Para cada um, suponho. Acho que com o FTP eu esperaria uma abstração de nível mais alto do que eles.
JoshBerke
15
Não sei se chegaria a dizer que é uma falha de design, mas seria muito bom se você pudesse inferir uma expressão lambda da mesma forma que você pode no VB:
VB:
Dim a = Function(x) x * (x - 1)
C #
Seria bom se pudesse fazer isso:
var a = x => x * (x - 1);
Em vez de ter que fazer isso:
Func<int, int> a = x => x * (x - 1);
Sei que não é muito mais, mas no Code Golf cada personagem conta, droga! Eles não levam isso em consideração ao projetar essas linguagens de programação? :)
A Microsoft deve levar o Code Golf em consideração ao desenvolver linguagens?
jrcs3
3
@Ray Burns: Como ele sabe em VB? VB suporta, então qual é a diferença?
BenAlabaster
3
Inferência de tipo @RayBurns? Uso isso desde 1989.
RD1
3
Lamdas são homoicônicos em C #. o fragmento (int x) => x * (x -1);pode significar Func<int, int>ou pode significarExpression<Func<int, int>>
Scott Weinstein
3
@BenAlabaster: VB suporta operadores aritméticos de limite tardio. C # deve resolvê-los em tempo de compilação. É uma diferença de idioma. Por exemplo, o VB pode adicionar dois objetos juntos. C # não pode porque +não está definido para object.
Equals e GetHashCode - nem todas as classes são comparáveis ou hashable, devem ser movidas para uma interface. IEquatable ou IComparable (ou similar) vem à mente.
ToString - nem todas as classes podem ser convertidas em uma string, devem ser movidas para uma interface. IFormattable (ou similar) vem à mente.
1. Esses métodos são tão comuns que foi decidido que os casos estranhos estavam ok, faz diferença se alguém pode chamar Object.Equals em sua classe? Sabe-se que pode ou não haver uma implementação, e exigir: IEquatable, IFormattable em 99% das classes é ímpar.
Guvante
1
É útil ser capaz de, por exemplo, construir um dicionário com objetos definidos pelo usuário como chaves, usando a igualdade de referência padrão, sem ter que adicionar explicitamente o código aos objetos definidos pelo usuário para esse propósito. Eu consideraria Finalizar um desperdício muito maior (uma alternativa melhor teria sido fazer com que os objetos que precisariam de finalização implementassem iFinalizable e se registrassem explicitamente para finalização). OTOH, deveria haver mais suporte inerente para iDisposable, incluindo a chamada de Dispose se um construtor lançar uma exceção.
supercat de
1
@supercat: Tudo o que é necessário é atualizar EqualityComparer<T>.Defaultcorretamente. Então, var dict = new Dictionary<object, string>(EqualityComparer<object>.Default)e var dict = new Dictionary<object, string>()usarão comparação / igualdade de referência.
dalle
1
@supercat: O que você descreve é exatamente o que EqualityComparer<T>.Defaultfaz. Não há necessidade de verificar a cada consulta. O comparador é uma propriedade da Dictionaryinstância e cada um Dictionarysabe qual está usando.
dalle
1
@supercat: Um dicionário deve usar o tipo mais genérico (ou seja, a classe base comum) como chave, usando ambas Strings e DateTime no mesmo dicionário não faria qualquer sentido, a menos que use a comparação de referência, a menos que uma comparação definida pelo usuário seja provada que é. Lembre-se de que o nome deste tópico é "C # (.NET) Design Flaws".
dalle de
12
Uma das coisas que me irrita é o Predicate<T> != Func<T, bool>paradoxo. Ambos são delegados do tipo, T -> boolmas não são compatíveis com atribuições.
Há um truque usando Delegate.Create e algum casting para fazer a conversão, mas pelo menos ser capaz de fazer um cast explícito seria bom (eu posso entender a falta de suporte para implícito, no entanto)
Guvante
O design dos delegados em geral é falho; por exemplo, a falta de eventos fracos (a implementação de eventos fracos do lado da origem sem esforço especial do assinante só pode ser feita com um monte de reflexão e ReflectionPermission, consulte codeproject.com/Articles/29922/Weak-Events-in-C ), e a ineficiência que vem do requisito de que os delegados devem ser tipos de referência (os delegados teriam sido mais rápidos e teriam usado 1/3 da quantidade de memória em muitos casos, se fossem tipos de valor - então seriam simplesmente um par de ponteiros que você pode passar na pilha.)
Qwertie
11
Algumas pessoas (ISVs) desejam que você possa compilá-lo para o código de máquina em tempo de construção e vinculá-lo, a fim de criar um executável nativo que não precise do tempo de execução dotNet.
Não existe uma ferramenta de ofuscação que faz isso? Ele irá incorporar a estrutura em seu exe para que você não precise implantá-la. Não consigo me lembrar do nome ... não é da PreEmptive ...
JoshBerke
2
O Postbuild do Xenocode não faz isso? Seria bom se o Visual Studio tivesse uma maneira de fazer isso ...
BenAlabaster
11
Sabemos muito sobre as técnicas OO corretas . Decoupling, programação por contrato, evitando herança imprópria, uso apropriado de exceções, principal aberto / fechado, substituibilidade de Liskov e assim por diante. De qualquer forma, os frameworks .Net não empregam as melhores práticas.
Para mim, a maior falha no design do .Net é não se apoiar nos ombros de gigantes; promovendo paradigmas de programação menos do que ideais para as massas de programadores que usam seus frameworks .
Se a MS prestasse atenção a isso, o mundo da engenharia de software poderia ter dado grandes saltos em termos de qualidade, estabilidade e escalabilidade nesta década, mas, infelizmente, parece estar regredindo.
+1 para o discurso retórico certeiro; eu acrescentaria a isso a incapacidade de criar subclasses de todas as classes, a falta de interfaces para classes fundamentais e a relutância em corrigir bugs do framework mesmo depois de vários anos
Steven A. Lowe
1
Se formos direto ao ponto, estamos dizendo que os frameworks .Net são tão ruins que é difícil definir qual falha é a pior? Eu me sinto melhor depois de um desabafo e aprecio o voto positivo, pois esperava ser rebatido pelos fãs de MS.
Daniel Paull
Eu não votei de qualquer maneira e, de qualquer forma, estou muito relutante em emitir votos negativos, mas estou tentando descobrir por que SÓ NÃO ME IMPORTO.
Mike Dunlavey
2
Acho que você está dizendo "não é tão bom quanto poderia ser", o que não é uma resposta. Nada é perfeito. Forneça detalhes.
jcollum
3
Não, estou dizendo que há vários casos em que o design é obviamente defeituoso, então, quando você acha que eles acertaram, eles ainda erram. Por exemplo, minha postagem nos fóruns do MSDN aqui: social.msdn.microsoft.com/forums/en-US/wpf/thread/…
Na verdade, acho que seria ainda melhor se isso exigisse uma única instrução ou um bloco entre chaves - como tudo o mais em C #. Sem a capacidade de falhar, a sintaxe de quebra atual é um pouco duvidosa e não se limita ao escopo também. (AFAIK, pode ser, não sei)
Tamas Czinege
Eu não entendo o que você quer dizer. Publique sua própria declaração de switch 'ideal' aqui. Eu não quero cair, mas valores separados por vírgulas.
tuinstoel
8
A propósito, concordo com o OP. switchestá fundamentalmente quebrado em todas as linguagens que emulam a versão deliberadamente aleijada de C (otimizada para velocidade!). O VB se sai muito melhor, mas ainda está anos-luz atrás das linguagens com correspondência de padrões (Haskell, F # ...).
Konrad Rudolph
1
tuinostel: algo como switch (a) {case 1 {do_something; } caso 2 {do_something_else; }} - isto é, livrar-se da instrução break e exigir blocos de código adequados para cada caso
Tamas Czinege
2
Ter break parece um erro em geral, não é um erro de compilação emiti-lo? Parece estar lá apenas para facilitar a transição de C para C # (também conhecido como ensinar desenvolvedores que não podem
falhar
10
Eventos em C #, em que você deve verificar explicitamente os ouvintes. Não era esse o objetivo dos eventos, transmitir para quem quer que estivesse lá? Mesmo se não houver nenhum?
É irritante que não haja açúcar para isso quando você quer, vejo por que eles fazem isso difícil, incentiva a não instanciar os argumentos do evento se não for necessário
ShuggyCoUk
Hm. Eu não entendo ou discordo, ou ambos :-). Não posso dizer que já me senti desencorajado a instanciar nada. Isso cheira a otimização prematura para mim.
Thomas Eyde
Os métodos parciais são uma alternativa viável, em alguns casos.
Robert Harvey
MAIS todo o acoplamento que isso causa ... MS tem CAB que corrige esses dois problemas, mas CAB tem muitos problemas próprios devido às limitações em C # (por exemplo, strings em vez de enums como tópicos de evento) - por que não fazer vagamente -coupled events parte da linguagem !?
BlueRaja - Danny Pflughoeft
9
O terrível (e quase invisível para a maioria das pessoas) O (N ^ 2) comportamento de iteradores aninhados / recursivos .
Estou bastante abatido que eles saibam sobre isso, sabem como consertar, mas não é visto como tendo prioridade suficiente para merecer inclusão.
Eu trabalho com estruturas semelhantes a árvores o tempo todo e tenho que corrigir o código de pessoas inteligentes quando, inadvertidamente, introduzem operações muito caras dessa maneira.
A beleza de "yield foreach" é que a sintaxe mais simples e fácil incentiva o código correto e de alto desempenho. Esse é o "poço do sucesso" que eu acho que eles deveriam aspirar antes de adicionar novos recursos para o sucesso de longo prazo da plataforma.
Bem, você não pode alterar o número de elementos em um Array, então não há nada que Add, Clear, Insert e Remove (At) possam fazer a não ser lançar NotSupported ... Na verdade, eu esperaria QUALQUER implementação de IList que retornasse verdadeiro para IsFixedSize iria lançar sobre eles.
CB
@CB um pouco atrasado para a festa, eu acho :) Mas se Array não pode satisfazer "IList", por que implementá-lo de qualquer maneira? Isso é uma violação de L no princípio SOLID.
Winger Sendon
7
Membros estáticos e tipos aninhados em interfaces.
Isso é particularmente útil quando um membro da interface tem um parâmetro de um tipo específico da interface ( por exemplo, an enum). Seria bom aninhar o tipo de enum no tipo de interface.
Não, este é sobre a linguagem C # e o outro é sobre o framework. Nem todo mundo se preocupa com a distinção, então deixe-me dizer que esta é sobre o que é permitido e a outra é sobre o que é fornecido.
Jay Bazuzi
6
A natureza padrão terrivelmente perigosa dos eventos. O fato de você poder ligar para um evento e estar em um estado inconsistente devido à remoção de assinantes é simplesmente horrível. Veja os excelentes artigos de Jon Skeet e Eric Lippert para mais leituras sobre o assunto.
Eu não me importaria se os eventos não fossem seguros para thread por padrão (isso poderia aumentar o desempenho em código de thread único); o que é bobo é que adicionar / remover são seguros por padrão, mas que a maneira natural de disparar um evento não é segura e não há nenhum recurso para torná-lo seguro facilmente.
Qwertie
@Qwertie: O que é mais tolo é que por um bom tempo, adicionar / remover usaria bloqueio e ainda assim não seria thread-safe.
supercat
6
null em toda parte.
const lugar algum.
APIs são inconsistentes, por exemplo, alterar um array retorna, voidmas anexar a um StringBufferretorna o mesmo mutável StringBuffer.
Interfaces de coleção são incompatíveis com estruturas de dados imutáveis, por exemplo, Addin System.Collections.Generic.IList<_>não pode retornar um resultado.
Nenhuma digitação estrutural, então você escreve em System.Windows.Media.Effects.SamplingMode.Bilinearvez de apenas Bilinear.
IEnumeratorInterface mutável implementada por classes quando deveria ser imutável struct.
Igualdade e comparação são uma confusão: você tem System.IComparablee Equalsmas então você também tem System.IComparable<_>, System.IEquatable, System.Collections.IComparer, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable, System.Collections.Generic.IComparere System.Collections.Generic.IEqualityComparer.
As tuplas devem ser estruturas, mas as estruturas inibem desnecessariamente a eliminação da chamada final, portanto, um dos tipos de dados mais comuns e fundamentais será alocado desnecessariamente e destruirá o paralelismo escalável.
Não há tipagem estrutural no CLR, mas você parece ter tipagem estrutural misturada com a inferência de tipo ou com o recurso Ruby conhecido como "símbolos". A tipagem estrutural seria se o CLR considerasse Func <int, bool> e Predicate <int> como sendo do mesmo tipo, ou pelo menos implicitamente conversível.
Qwertie
Falando em comparação, não se esqueça do Comparer <T>!
Qwertie
@Qwertie Eu estava me referindo aos recursos da linguagem de programação, como variantes polimórficas no OCaml. A biblioteca LablGL do OCaml tem muitos exemplos interessantes de tipagem estrutural que é útil no contexto de gráficos. Nada a ver com a inferência de tipo e apenas tangencialmente relacionado aos símbolos.
JD
1
Como alguém usaria uma estrutura imutável IEnumerator?
+1 enum é uma das poucas coisas que Java fez certo, enquanto C # não fez
BlueRaja - Danny Pflughoeft
5
Para adicionar à longa lista de pontos positivos já feitos por outros:
DateTime.Now == DateTime.Now na maioria, mas não em todos os casos.
Stringque é imutável tem um monte de opções para construção e manipulação, mas StringBuilder(que é mutável) não tem.
Monitor.Entere Monitor.Exitdeveriam ter sido métodos de instância, então em vez de criar um objeto específico para bloqueio, você poderia criar um novo a Monitore bloquear nele .
Os destruidores nunca deveriam ter sido nomeados destruidores. A especificação ECMA os chama de finalizadores, o que é muito menos confuso para o pessoal do C ++, mas a especificação da linguagem ainda se refere a eles como destruidores.
O DateTime.Nowprimeiro é a condição de corrida mais óbvia do mundo, mas +1 para o resto
BlueRaja - Danny Pflughoeft
Não é tanto que seja uma condição de corrida, mas o fato de que eles a tornaram uma propriedade. Propriedades parecem exatamente com campos, por isso é um comportamento bastante surpreendente IMO.
Brian Rasmussen
4
@Brian Rasmussen: DateTime.Now é muito propriamente uma propriedade, já que não é alterada pela leitura, mas por fatores externos. Se alguém ler uma propriedade como SomeForm.Width, e então - após o usuário ter redimensionado o formulário - ler novamente, o valor na segunda leitura será diferente. Embora seja possível que o primeiro DateTime.Now demore o suficiente para ser executado a ponto de influenciar o valor lido pelo segundo, tal efeito não seria diferente de qualquer outra função cuja execução levasse o mesmo tempo.
supercat
4
A maneira como usamos as propriedades às vezes me irrita. Gosto de pensar neles como equivalentes aos métodos getFoo () e setFoo () do Java. Mas eles não são.
Se as Diretrizes de Uso de Propriedade afirmam que as propriedades devem poder ser definidas em qualquer ordem para que a serialização possa funcionar, então elas são inúteis para a validação de tempo de definição. Se você vem de um plano de fundo no qual deseja evitar que um objeto se permita jamais entrar em um estado inválido, então as propriedades não são a sua solução. Às vezes não consigo ver apenas como eles são melhores do que membros do público, já que estamos tão limitado em que tipos de coisas que estamos suposto que fazer em propriedades.
Para esse fim, eu sempre desejei (isso é principalmente pensar em voz alta aqui, eu apenas gostaria de poder fazer algo assim) poder estender a sintaxe de propriedade de alguma forma. Imagine algo assim:
privatestring password;
publicstring Password
{
// Called when being set by a deserializer or a persistence// framework
deserialize
{
// I could put some backward-compat hacks in here. Like// weak passwords are grandfathered in without blowing upthis.password = value;
}
get
{
if (Thread.CurrentPrincipal.IsInRole("Administrator"))
{
returnthis.password;
}
else
{
thrownew PermissionException();
}
}
set
{
if (MeetsPasswordRequirements(value))
{
thrownew BlahException();
}
this.password = value;
}
serialize
{
returnthis.password;
}
}
Não tenho certeza se isso é útil ou como seria acessá-los. Mas eu só gostaria de poder fazer mais com propriedades e realmente tratá-las como métodos get e set.
Eu acredito que eles fornecem a interface ISerializable e o construtor implícito para fazer coisas assim, também conhecido como quando você não quer que o serializador apenas chame as propriedades. Embora funcione um pouco mais, parece que você já está fazendo a maior parte desse trabalho com seu método.
Guvante
4
Métodos de extensão são bons, mas são uma maneira feia de resolver problemas que poderiam ser resolvidos de forma mais limpa com mixins reais (olhe para o ruby para ver do que estou falando), sobre o assunto de mixins. Uma maneira realmente boa de adicioná-los à linguagem seria permitir que os genéricos fossem usados para herança. Isso permite que você estenda as classes existentes de uma boa maneira orientada a objetos:
publicclassMyMixin<T> : T
{
// etc...
}
isso pode ser usado assim para estender uma string, por exemplo:
var newMixin = new MyMixin<string>();
É muito mais poderoso do que os métodos de extensão porque permite que você substitua os métodos, por exemplo, para envolvê-los, permitindo uma funcionalidade semelhante a AOP dentro da linguagem.
Interessante, mas prefiro métodos de extensão. Se eu obtiver uma biblioteca que contém um monte de métodos de extensão para strings, não quero ter que alterar todas as referências de string para MyMixin <string> para obter o material novo. É um pouco menor, com certeza, mas essa adição transparente de métodos é o que torna os métodos de extensão tão bons.
RCIX
BTW, você sabia que isso já funciona?
RCIX
2
Não vejo como o LINQ poderia funcionar dessa maneira
BlueRaja - Danny Pflughoeft
2
@RCIX: Mixins soam como eu pensei que métodos de extensão deveriam funcionar. O problema de tornar os métodos de extensão implícitos é que isso significa que os membros reais da classe precisam ter prioridade sobre os métodos de extensão. Se um método de extensão Graphics.DrawParallelogram (Pen p, Point v1, Point v2, Point v3) for definido e posteriormente uma função DrawParallelogram for adicionada a System.Graphics que usa pontos em uma ordem diferente, o código usando o método de extensão será interrompido sem Atenção. BTW, teria havido algum problema usando dois pontos para métodos de extensão (por exemplo, object..method ()?)
supercat
3
A Microsoft não corrigirá bugs óbvios na estrutura e não fornecerá ganchos para que os usuários finais possam corrigi-los.
Além disso, não há como aplicar patch binário aos executáveis .NET em tempo de execução e nem especificar versões privadas de bibliotecas do .NET Framework sem aplicar patch binário às bibliotecas nativas (para interceptar a chamada de carregamento), e ILDASM não é redistribuível, portanto não posso automatizar o patch de qualquer maneira.
# 1 Clique em um controle filho parcialmente visível de um controle rolável. O controle é colocado no modo de exibição antes de receber o evento MouseDown, fazendo com que o clique esteja em outro lugar no controle do que o esperado. É pior em visualizações em árvore, onde também aciona uma operação de arrastar.
Ser capaz de invocar um método de extensão em uma variável nula é discutível, por exemplo
objeto a = nulo; a.MyExtMethod (); // isso é chamável, suponha que em algum lugar ele tenha definido MyExtMethod
Pode ser útil, mas é ambíguo em tópicos de exceção de referência nula.
Um nomeando 'falha'. 'C' de "configuração" em System.configuration.dll deve estar em maiúscula.
Manipulação de exceção. A exceção deve ser capturada ou lançada à força como em Java, o compilador deve verificá-la no momento da compilação. Os usuários não devem confiar em comentários para informações de exceções na invocação de destino.
Muito útil, porém - eu tenho um método de extensão "ThrowIfNull` para verificação de parâmetro ;-p
Marc Gravell
2
você consegue fazer isso? ugh ThrowIfNull é uma extensão interessante, mas isso parece errado.
JoshBerke
1
No CLR simples, você pode invocar métodos de instância em referências nulas e, se o método não acessar o objeto ou seus campos, a chamada não lançará exceção de referência nula. (Você não pode fazer isso em C #, pois ele usa callvirt mesmo para métodos não virtuais)
Pop Catalin
7
a coisa da exceção está completamente errada. Você não pode falhar rapidamente se tiver que capturar todas as malditas exceções que podem ser lançadas na pilha de chamadas. Mas eu gostaria que fosse muito mais fácil identificar todas e quaisquer exceções que podem ser lançadas em uma determinada chamada e sua pilha de chamadas resultante ...
3
@Will: O tratamento de exceções é desagradável em Java e .net, uma vez que o mecanismo usado vincula intimamente três conceitos que são um tanto relacionados, mas também ortogonais: (1) Que tipo de coisa deu errado (um erro de limite de matriz, um tempo limite de E / S, etc.); (2) Se determinado código deve agir como resultado; (3) Em que ponto o problema deve ser considerado "resolvido". Considere uma rotina que supostamente altera um objeto com dados lidos de um IEnumerable. O que deve acontecer se ocorrer uma exceção no processamento desse IEnumerable?
supercat
3
O método .Parameters.Add () no SqlCommand em V1 do framework foi horrivelmente projetado - uma das sobrecargas basicamente não funcionaria se você passasse um parâmetro com um valor (int) de 0 - isso os levou à criação o método .Parameters.AddWithValue () na classe SqlCommand.
Eu concordo, mas acho que você quer dizer o método SqlCommand.Parameters.Add ().
Matt Peterson,
3
Não há subconjuntos de ICollection<T>e IList<T>; no mínimo, uma interface de coleção covariant somente leitura IListSource<out T>(com um enumerador, indexador e Count) teria sido extremamente útil.
.NET não oferece suporte a delegados fracos . As soluções alternativas são, na melhor das hipóteses, desajeitadas e as soluções alternativas do lado do ouvinte são impossíveis na confiança parcial (é necessário ReflectionPermission).
Não é possível comparar bit a bit dois tipos de valor para igualdade. Em uma estrutura de dados " persistente " funcional , eu estava escrevendo uma Transform(Sequence<T>, Func<T,T>)função que precisava determinar rapidamente se a função retorna o mesmo valor ou um valor diferente. Se a função não modificar a maioria / todos os seus argumentos, a sequência de saída pode compartilhar parte / toda a memória da sequência de entrada. Sem a capacidade de comparar bit a bit qualquer tipo de valor T, uma comparação muito mais lenta deve ser usada, o que prejudica o desempenho tremendamente.
.NET não parece ser capaz de oferecer suporte a interfaces ad-hoc (como as oferecidas em Go ou Rust) de maneira eficiente. Essas interfaces teriam permitido a você lançar List<T>para um hipotético IListSource<U>(onde T: U), mesmo que a classe não implemente explicitamente essa interface. Existem pelo menos três bibliotecas diferentes (escritas independentemente) para fornecer esta funcionalidade (com desvantagens de desempenho, é claro - se uma solução alternativa perfeita fosse possível, não seria justo chamá-la de uma falha no .NET).
Outros problemas de desempenho: IEnumerator requer duas chamadas de interface por iteração. Ponteiros de método simples (delegados abertos de tamanho IntPtr) ou delegados digitados por valor (IntPtr * 2) não são possíveis. Arrays de tamanho fixo (de tipo arbitrário T) não podem ser embutidos em classes. Não há WeakReference<T>(você pode escrever facilmente o seu, mas usará casts internamente).
O fato de que tipos de delegados idênticos são considerados incompatíveis (sem conversão implícita) tem sido um incômodo para mim em algumas ocasiões (por exemplo, Predicate<T>vs Func<T,bool>). Muitas vezes gostaria que pudéssemos ter tipagem estrutural para interfaces e delegados, para obter um acoplamento mais flexível entre os componentes, porque no .NET não é suficiente para classes em DLLs independentes implementarem a mesma interface - eles também devem compartilhar uma referência comum a um terceiro DLL que define a interface.
DBNull.Valueexiste mesmo que nulltivesse servido ao mesmo propósito igualmente bem.
C # não tem operador ?? =; você deve escrever variable = variable ?? value. Na verdade, existem alguns lugares em C # que carecem de simetria desnecessariamente. Por exemplo, você pode escrever if (x) y(); else z();(sem colchetes), mas não pode escrever try y(); finally z();.
Ao criar um thread, é impossível fazer com que o thread filho herde os valores locais do thread do thread pai. Não apenas o BCL não suporta isso, mas você não pode implementá-lo sozinho, a menos que crie todos os threads manualmente; mesmo se houver um evento de criação de thread, o .NET não pode dizer os "pais" ou "filhos" de um determinado thread.
O fato de haver dois atributos de comprimento diferentes para tipos de dados diferentes, "Comprimento" e "Contagem", é um pequeno incômodo.
Eu poderia continuar para sempre sobre o design pobre do WPF ... e o WCF (embora bastante útil para alguns cenários) também está cheio de falhas. Em geral, o inchaço, a falta de intuição e a documentação limitada de muitas das novas sub-bibliotecas do BCL me deixam relutante em usá-las. Muitas das coisas novas poderiam ser muito mais simples, menores, mais fáceis de usar e entender, mais fracamente acopladas, melhor documentadas, aplicáveis a mais casos de uso, mais rápidas e / ou com tipos mais fortes.
Muitas vezes sou mordido pelo acoplamento desnecessário entre getters e setters de propriedade: em uma classe derivada ou interface derivada, você não pode simplesmente adicionar um setter quando a classe base ou interface base tem apenas um getter; se você substituir um getter, não terá permissão para definir um setter; e você não pode definir o setter como virtual, mas o getter como não virtual.
Eu concordo com você sobre um subconjunto de IList<T>, embora eu usasse IReadableByIndex<out T>e IAppendable<in T>. Muitas das suas outras coisas são grasnidos com que eu também concordaria.
supercat
Esse é um nome muito longo. Talvez possamos chegar a um acordo IListReader<T>;) - Eu uso a palavra "fonte" como o antônimo de "dissipador" (uma interface somente de gravação).
Qwertie
Talvez IListSource<in T>ou IReadableList<out T>. Pode ser útil fazer com que os tipos de interface básicos incluam métodos que não existem em todos os derivados, embora eu ache que muitas vezes é bom ter interfaces um tanto especializadas. Por exemplo, um pode ter um IList<T>contendo métodos de redimensionamento que podem ou não funcionar, e um IResizableList<T>que implementa os mesmos métodos, mas garante que eles funcionarão. Tal abordagem pode ser útil nos casos em que um campo pode conter a única referência existente a uma lista mutável ou uma referência compartilhada a uma lista imutável.
supercat de
Nesse caso, o código que deseja alterar o conteúdo da lista verificaria se é um tipo mutável e, se não, geraria uma nova instância mutável contendo os mesmos itens da lista imutável e, então, começaria a usá-la. Seria enfadonho se o código tivesse que digitar constantemente o campo toda vez que quisesse usar um método mutante nele.
supercat
@supercat Isso é irritante porque C # não fornece uma maneira realmente fácil de verificar se uma interface está implementada e usá-la imediatamente. A MS deve adicionar um recurso de idioma para tornar isso mais fácil. Minha técnica preferida seria uma expressão vinculativa if (rl:(list as IResizableList<T>) != null) rl.Add(...);, mas há outras propostas. Como autor de várias coleções e adaptadores de coleção, o que me irrita é escrever muitos métodos fictícios que lançam exceções. Como um fã de segurança de tipo, não quero ter permissão para chamar métodos ilegais. Um fã do IntelliSense, não quero vê-los listados.
Qwertie
2
Uma coisa que me irritou no 1.x foi que, ao usar o System.Xml.XmlValidatingReader, o ValidationEventHandlerdo ValidationEventArgsnão expõe o subjacente XmlSchemaException(marcado como interno), que contém todas as informações úteis como linenumbere position. Em vez disso, espera-se que você analise isso a partir da propriedade da string Message ou use a reflexão para descobrir. Não é tão bom quando você deseja retornar um erro mais limpo para o usuário final.
Isso nem mesmo faz sentido, no entanto. typeof(Color)! = typeof(SpecialColors).
Kirk Woll
10
É fácil de fazer:enum SpecialColors { blue = Colors.blue, red = Colors.red, yellow = Colors.Yellow }
Trystan Spangler
0
Variáveis implicitamente digitadas foram implementadas de forma inadequada na IMO. Eu sei que você só deve usá-los ao trabalhar com expressões do Linq, mas é irritante que você não possa declará-los fora do escopo local.
Do MSDN:
var só pode ser usado quando uma variável local é declarada e inicializada na mesma instrução; a variável não pode ser inicializada como nula, ou para um grupo de métodos ou uma função anônima.
var não pode ser usado em campos no escopo da classe.
Variáveis declaradas usando var não podem ser usadas na expressão de inicialização. Em outras palavras, essa expressão é válida: int i = (i = 20); mas essa expressão produz um erro em tempo de compilação: var i = (i = 20);
Várias variáveis digitadas implicitamente não podem ser inicializadas na mesma instrução.
Se um tipo chamado var estiver no escopo, a palavra-chave var será resolvida para esse nome de tipo e não será tratada como parte de uma declaração de variável local digitada implicitamente.
O motivo pelo qual acho que é uma implementação ruim é que eles a chamam de var, mas está muito longe de ser uma variante. É apenas uma sintaxe abreviada para não ter que digitar o nome completo da classe (exceto quando usado com Linq)
Respostas:
Eu concordo enfaticamente com este post (para aqueles que reclamam da falta de ToString, existe um atributo de depurador para fornecer um formato personalizado para sua classe).
No topo da lista acima, eu também adicionaria as seguintes solicitações razoáveis:
T : new(string)
ou ondeT : new(string, int)
var e = new Foo(); e { Bar = baz };
Either<T>
" não é, então eu adoraria alguma maneira de declarar um tipo algébrico fechado e impor correspondência de padrão exaustiva nele (basicamente suporte de primeira classe para o padrão de visitante, mas muito mais eficiente); então apenas pegue enums, estenda-os com suporte exaustivo de correspondência de padrões e não permita casos inválidos,System.IO
aulas, tipoStream
, são um tanto mal elaboradas; qualquer interface que requer algumas implementações para lançarNotSupportedException
é um projeto ruim,IList
deve ser muito mais simples do que é; na verdade, isso pode ser verdade para muitas das interfaces de coleção concretas, comoICollection
,INotifyPropertyChanged
, que usam o nome do campo como uma string; você pode fazer isso usando um método de extensão que pega um lambda com aMemberExpression
, ie.() => Foo
, mas isso não é muito eficiente,nameof()
operador para nomes de membro único, mas não funciona em genéricos (emnameof(T) == "T"
vez do nome do argumento de tipo real: você ainda precisa fazertypeof(T).Name
)) - nem permite que você obtenha uma string de "caminho" , por exemplo,nameof(this.ComplexProperty.Value) == "Value"
limitando suas possíveis aplicações.IArithmetic
; outras interfaces de operação compartilhadas úteis também são possíveis,readonly
palavra - chave, e C # 6.0 adicionou propriedades automáticas somente leitura, embora não seja tão rigoroso quanto o suporte de linguagem verdadeira para tipos e valores imutáveis.Suponho que seja o suficiente por enquanto. Essas são todas as irritações que encontrei na semana passada. Eu provavelmente poderia continuar por horas se eu realmente me concentrasse nisso. C # 4.0 já está adicionando argumentos nomeados, opcionais e padrão, que eu aprovo enfaticamente.
Agora, para um pedido irracional:
Por favor? :-)
fonte
List<T>
um milhão de Ts. Como você propõe que o instantâneo seja obtido com eficiência? # 21: Use areadonly
palavra - chave ... embora haja algumas boas sugestões aqui, elas são apenas isso - sugestões, não falhas de design.Reset()
método onIEnumerator<T>
foi um erro (para blocos de iteradores, a especificação da linguagem exige até que isso lance uma exceção)IEnumerable<out T>
eFunc<in T, out TResult>
, mas não tipos concretos (comoList<T>
).ApplicationException
em vez disso, caiu em desgraça - foi um erro?Contains
entãoAdd
), então uma coleção que sincroniza operações distintas não é tão útilSystem.Collections.Concurrent
tipos , comTryAdd
,GetOrAdd
,TryRemove
, etc foram adicionados no .NET Framework 4.0 - embora os métodos que aceitam um delegado fábrica não garantem a fábrica só será chamada uma vez por chave.using
/lock
- talvez permitindo que eles compartilhem uma sintaxe reutilizável (extensível?); você pode simular isso retornandoIDisposable
e usandousing
, mas poderia ter sido mais claroFoo(SqlConnection! connection)
(que injeta uma verificação de nulo /throw
) seria bom (contraste comint?
etc)dynamic
, ou você pode habilitá-lo assimforeach
expansão, o que significa que anon-methods / lambdas capturam a única variável, em vez de uma por iteração (doloroso com threading / async / etc)fonte
ApplicationException
era um erro - não era útil como eles esperavam. Eles também disseram queSystem.Exception
deveria ter sidoabstract
.TextWriter é uma classe base de StreamWriter. wtf?
Isso sempre me confunde ao extremo.
fonte
Uma pequena petição de C # - construtores usam a sintaxe C ++ / Java de ter o construtor com o mesmo nome da classe.
New()
ouctor()
teria sido muito melhor.E, claro, ferramentas como o coderush tornam esse problema menor para renomear classes, mas de um POV de legibilidade, New () fornece grande clareza.
fonte
class Foo { new(int j) {i = j} int i; }
New
palavras-chave não --uppercase são contra a convenção), hesito em chamar isso de falha de design. Eles queriam atrair desenvolvedores C ++ / Java existentes, e o uso de muitas convenções sintáticas antigas e idiotas, sem dúvida, os ajudou a alcançar seu objetivo.Eu não entendo que você não pode fazer
onde T: novo (U)
Portanto, você declara que o tipo genérico T tem um construtor não padrão.
editar:
Eu quero fazer isso:
public class A { public A(string text) { } } public class Gen<T> where T : new(string text) { }
fonte
Estou realmente surpreso por ser o primeiro a mencionar este:
Os conjuntos de dados do tipo ADO.NET não expõem colunas anuláveis como propriedades de tipos anuláveis. Você deve ser capaz de escrever isto:
int? i = myRec.Field; myRec.Field = null;
Em vez disso, você tem que escrever isso, o que é simplesmente estúpido:
int? i = (int?)myRec.IsFieldNull() ? (int?)null : myRec.Field; myRec.SetFieldNull();
Isso era irritante no .NET 2.0 e é ainda mais irritante agora que você tem que usar jiggery-pokery como o acima em suas consultas LINQ legais.
Também é irritante que o
Add<TableName>Row
método gerado seja igualmente insensível à noção de tipos anuláveis. Ainda mais porque osTableAdapter
métodos gerados não são.Não há muito no .NET que me faça sentir como se a equipe de desenvolvimento dissesse "Ok, rapazes, estamos perto o suficiente - envie!" Mas isso com certeza faz.
fonte
DBNull.Value
quandonull
ele próprio seria perfeitamente adequado para representar NULL. Felizmente o LINQ-to-SQL usa apenas null para NULL.Editar
5. Outro aborrecimento meu é como System.Reflection.BindingFlags, tem usos diferentes dependendo do método que você está usando. Em FindFields, por exemplo, o que CreateInstance ou SetField significa? Este é um caso em que eles sobrecarregaram o significado por trás dessa enumeração, o que é confuso.
fonte
Não sei se chegaria a dizer que é uma falha de design, mas seria muito bom se você pudesse inferir uma expressão lambda da mesma forma que você pode no VB:
VB:
Dim a = Function(x) x * (x - 1)
C #
Seria bom se pudesse fazer isso:
var a = x => x * (x - 1);
Em vez de ter que fazer isso:
Func<int, int> a = x => x * (x - 1);
Sei que não é muito mais, mas no Code Golf cada personagem conta, droga! Eles não levam isso em consideração ao projetar essas linguagens de programação? :)
fonte
(int x) => x * (x -1);
pode significarFunc<int, int>
ou pode significarExpression<Func<int, int>>
+
não está definido paraobject
.A classe System.Object :
Equals e GetHashCode - nem todas as classes são comparáveis ou hashable, devem ser movidas para uma interface. IEquatable ou IComparable (ou similar) vem à mente.
ToString - nem todas as classes podem ser convertidas em uma string, devem ser movidas para uma interface. IFormattable (ou similar) vem à mente.
A propriedade ICollection.SyncRoot :
Os genéricos deveriam estar lá desde o início:
fonte
EqualityComparer<T>.Default
corretamente. Então,var dict = new Dictionary<object, string>(EqualityComparer<object>.Default)
evar dict = new Dictionary<object, string>()
usarão comparação / igualdade de referência.EqualityComparer<T>.Default
faz. Não há necessidade de verificar a cada consulta. O comparador é uma propriedade daDictionary
instância e cada umDictionary
sabe qual está usando.Uma das coisas que me irrita é o
Predicate<T> != Func<T, bool>
paradoxo. Ambos são delegados do tipo,T -> bool
mas não são compatíveis com atribuições.fonte
Algumas pessoas (ISVs) desejam que você possa compilá-lo para o código de máquina em tempo de construção e vinculá-lo, a fim de criar um executável nativo que não precise do tempo de execução dotNet.
fonte
Sabemos muito sobre as técnicas OO corretas . Decoupling, programação por contrato, evitando herança imprópria, uso apropriado de exceções, principal aberto / fechado, substituibilidade de Liskov e assim por diante. De qualquer forma, os frameworks .Net não empregam as melhores práticas.
Para mim, a maior falha no design do .Net é não se apoiar nos ombros de gigantes; promovendo paradigmas de programação menos do que ideais para as massas de programadores que usam seus frameworks .
Se a MS prestasse atenção a isso, o mundo da engenharia de software poderia ter dado grandes saltos em termos de qualidade, estabilidade e escalabilidade nesta década, mas, infelizmente, parece estar regredindo.
fonte
Não gosto da instrução switch C #.
Eu gostaria de algo assim
switch (a) { 1 : do_something; 2 : do_something_else; 3,4 : do_something_different; else : do_something_weird; }
Portanto, não há mais interrupções (fácil de esquecer) e a possibilidade de separar valores diferentes por vírgulas.
fonte
switch
está fundamentalmente quebrado em todas as linguagens que emulam a versão deliberadamente aleijada de C (otimizada para velocidade!). O VB se sai muito melhor, mas ainda está anos-luz atrás das linguagens com correspondência de padrões (Haskell, F # ...).Eventos em C #, em que você deve verificar explicitamente os ouvintes. Não era esse o objetivo dos eventos, transmitir para quem quer que estivesse lá? Mesmo se não houver nenhum?
fonte
O terrível (e quase invisível para a maioria das pessoas) O (N ^ 2) comportamento de iteradores aninhados / recursivos .
Estou bastante abatido que eles saibam sobre isso, sabem como consertar, mas não é visto como tendo prioridade suficiente para merecer inclusão.
Eu trabalho com estruturas semelhantes a árvores o tempo todo e tenho que corrigir o código de pessoas inteligentes quando, inadvertidamente, introduzem operações muito caras dessa maneira.
A beleza de "yield foreach" é que a sintaxe mais simples e fácil incentiva o código correto e de alto desempenho. Esse é o "poço do sucesso" que eu acho que eles deveriam aspirar antes de adicionar novos recursos para o sucesso de longo prazo da plataforma.
fonte
Algumas classes implementam interfaces, mas não implementam muitos dos métodos dessa interface, por exemplo, Array implementa IList, mas 4 de 9 métodos lançam NotSupportedException http://msdn.microsoft.com/en-us/library/system.array_members .aspx
fonte
Membros estáticos e tipos aninhados em interfaces.
Isso é particularmente útil quando um membro da interface tem um parâmetro de um tipo específico da interface ( por exemplo, an
enum
). Seria bom aninhar o tipo de enum no tipo de interface.fonte
A natureza padrão terrivelmente perigosa dos eventos. O fato de você poder ligar para um evento e estar em um estado inconsistente devido à remoção de assinantes é simplesmente horrível. Veja os excelentes artigos de Jon Skeet e Eric Lippert para mais leituras sobre o assunto.
fonte
null
em toda parte.const
lugar algum.APIs são inconsistentes, por exemplo, alterar um array retorna,
void
mas anexar a umStringBuffer
retorna o mesmo mutávelStringBuffer
.Interfaces de coleção são incompatíveis com estruturas de dados imutáveis, por exemplo,
Add
inSystem.Collections.Generic.IList<_>
não pode retornar um resultado.Nenhuma digitação estrutural, então você escreve em
System.Windows.Media.Effects.SamplingMode.Bilinear
vez de apenasBilinear
.IEnumerator
Interface mutável implementada por classes quando deveria ser imutávelstruct
.Igualdade e comparação são uma confusão: você tem
System.IComparable
eEquals
mas então você também temSystem.IComparable<_>
,System.IEquatable
,System.Collections.IComparer
,System.Collections.IStructuralComparable
,System.Collections.IStructuralEquatable
,System.Collections.Generic.IComparer
eSystem.Collections.Generic.IEqualityComparer
.As tuplas devem ser estruturas, mas as estruturas inibem desnecessariamente a eliminação da chamada final, portanto, um dos tipos de dados mais comuns e fundamentais será alocado desnecessariamente e destruirá o paralelismo escalável.
fonte
IEnumerator
?0 moonlighting como enum
peculiaridades de enum: http://blogs.msdn.com/abhinaba/archive/2007/01/09/more-peculiarites-of-enum.aspx
conforme ilustrado por este bom exemplo: http://plus.kaist.ac.kr/~shoh/postgresql/Npgsql/apidocs/Npgsql.NpgsqlParameterCollection.Add_overload_3.html
minha sugestão, faça bom uso do sinal "@":
ao invés de:
if ((myVar & MyEnumName.ColorRed)! = 0)
usa isto:
if ((myVar & MyEnumName.ColorRed)! = @ 0)
fonte
Para adicionar à longa lista de pontos positivos já feitos por outros:
DateTime.Now == DateTime.Now
na maioria, mas não em todos os casos.String
que é imutável tem um monte de opções para construção e manipulação, masStringBuilder
(que é mutável) não tem.Monitor.Enter
eMonitor.Exit
deveriam ter sido métodos de instância, então em vez de criar um objeto específico para bloqueio, você poderia criar um novo aMonitor
e bloquear nele .Os destruidores nunca deveriam ter sido nomeados destruidores. A especificação ECMA os chama de finalizadores, o que é muito menos confuso para o pessoal do C ++, mas a especificação da linguagem ainda se refere a eles como destruidores.
fonte
DateTime.Now
primeiro é a condição de corrida mais óbvia do mundo, mas +1 para o restoA maneira como usamos as propriedades às vezes me irrita. Gosto de pensar neles como equivalentes aos métodos getFoo () e setFoo () do Java. Mas eles não são.
Se as Diretrizes de Uso de Propriedade afirmam que as propriedades devem poder ser definidas em qualquer ordem para que a serialização possa funcionar, então elas são inúteis para a validação de tempo de definição. Se você vem de um plano de fundo no qual deseja evitar que um objeto se permita jamais entrar em um estado inválido, então as propriedades não são a sua solução. Às vezes não consigo ver apenas como eles são melhores do que membros do público, já que estamos tão limitado em que tipos de coisas que estamos suposto que fazer em propriedades.
Para esse fim, eu sempre desejei (isso é principalmente pensar em voz alta aqui, eu apenas gostaria de poder fazer algo assim) poder estender a sintaxe de propriedade de alguma forma. Imagine algo assim:
private string password; public string Password { // Called when being set by a deserializer or a persistence // framework deserialize { // I could put some backward-compat hacks in here. Like // weak passwords are grandfathered in without blowing up this.password = value; } get { if (Thread.CurrentPrincipal.IsInRole("Administrator")) { return this.password; } else { throw new PermissionException(); } } set { if (MeetsPasswordRequirements(value)) { throw new BlahException(); } this.password = value; } serialize { return this.password; } }
Não tenho certeza se isso é útil ou como seria acessá-los. Mas eu só gostaria de poder fazer mais com propriedades e realmente tratá-las como métodos get e set.
fonte
Métodos de extensão são bons, mas são uma maneira feia de resolver problemas que poderiam ser resolvidos de forma mais limpa com mixins reais (olhe para o ruby para ver do que estou falando), sobre o assunto de mixins. Uma maneira realmente boa de adicioná-los à linguagem seria permitir que os genéricos fossem usados para herança. Isso permite que você estenda as classes existentes de uma boa maneira orientada a objetos:
public class MyMixin<T> : T { // etc... }
isso pode ser usado assim para estender uma string, por exemplo:
var newMixin = new MyMixin<string>();
É muito mais poderoso do que os métodos de extensão porque permite que você substitua os métodos, por exemplo, para envolvê-los, permitindo uma funcionalidade semelhante a AOP dentro da linguagem.
Desculpe pelo discurso retórico :-)
fonte
A Microsoft não corrigirá bugs óbvios na estrutura e não fornecerá ganchos para que os usuários finais possam corrigi-los.
Além disso, não há como aplicar patch binário aos executáveis .NET em tempo de execução e nem especificar versões privadas de bibliotecas do .NET Framework sem aplicar patch binário às bibliotecas nativas (para interceptar a chamada de carregamento), e ILDASM não é redistribuível, portanto não posso automatizar o patch de qualquer maneira.
fonte
Ser capaz de invocar um método de extensão em uma variável nula é discutível, por exemplo
objeto a = nulo; a.MyExtMethod (); // isso é chamável, suponha que em algum lugar ele tenha definido MyExtMethod
Pode ser útil, mas é ambíguo em tópicos de exceção de referência nula.
Um nomeando 'falha'. 'C' de "configuração" em System.configuration.dll deve estar em maiúscula.
Manipulação de exceção. A exceção deve ser capturada ou lançada à força como em Java, o compilador deve verificá-la no momento da compilação. Os usuários não devem confiar em comentários para informações de exceções na invocação de destino.
fonte
O método .Parameters.Add () no SqlCommand em V1 do framework foi horrivelmente projetado - uma das sobrecargas basicamente não funcionaria se você passasse um parâmetro com um valor (int) de 0 - isso os levou à criação o método .Parameters.AddWithValue () na classe SqlCommand.
fonte
ICollection<T>
eIList<T>
; no mínimo, uma interface de coleção covariant somente leituraIListSource<out T>
(com um enumerador, indexador e Count) teria sido extremamente útil.Transform(Sequence<T>, Func<T,T>)
função que precisava determinar rapidamente se a função retorna o mesmo valor ou um valor diferente. Se a função não modificar a maioria / todos os seus argumentos, a sequência de saída pode compartilhar parte / toda a memória da sequência de entrada. Sem a capacidade de comparar bit a bit qualquer tipo de valor T, uma comparação muito mais lenta deve ser usada, o que prejudica o desempenho tremendamente.List<T>
para um hipotéticoIListSource<U>
(onde T: U), mesmo que a classe não implemente explicitamente essa interface. Existem pelo menos três bibliotecas diferentes (escritas independentemente) para fornecer esta funcionalidade (com desvantagens de desempenho, é claro - se uma solução alternativa perfeita fosse possível, não seria justo chamá-la de uma falha no .NET).WeakReference<T>
(você pode escrever facilmente o seu, mas usará casts internamente).Predicate<T>
vsFunc<T,bool>
). Muitas vezes gostaria que pudéssemos ter tipagem estrutural para interfaces e delegados, para obter um acoplamento mais flexível entre os componentes, porque no .NET não é suficiente para classes em DLLs independentes implementarem a mesma interface - eles também devem compartilhar uma referência comum a um terceiro DLL que define a interface.DBNull.Value
existe mesmo quenull
tivesse servido ao mesmo propósito igualmente bem.variable = variable ?? value
. Na verdade, existem alguns lugares em C # que carecem de simetria desnecessariamente. Por exemplo, você pode escreverif (x) y(); else z();
(sem colchetes), mas não pode escrevertry y(); finally z();
.fonte
IList<T>
, embora eu usasseIReadableByIndex<out T>
eIAppendable<in T>
. Muitas das suas outras coisas são grasnidos com que eu também concordaria.IListReader<T>
;) - Eu uso a palavra "fonte" como o antônimo de "dissipador" (uma interface somente de gravação).IListSource<in T>
ouIReadableList<out T>
. Pode ser útil fazer com que os tipos de interface básicos incluam métodos que não existem em todos os derivados, embora eu ache que muitas vezes é bom ter interfaces um tanto especializadas. Por exemplo, um pode ter umIList<T>
contendo métodos de redimensionamento que podem ou não funcionar, e umIResizableList<T>
que implementa os mesmos métodos, mas garante que eles funcionarão. Tal abordagem pode ser útil nos casos em que um campo pode conter a única referência existente a uma lista mutável ou uma referência compartilhada a uma lista imutável.if (rl:(list as IResizableList<T>) != null) rl.Add(...);
, mas há outras propostas. Como autor de várias coleções e adaptadores de coleção, o que me irrita é escrever muitos métodos fictícios que lançam exceções. Como um fã de segurança de tipo, não quero ter permissão para chamar métodos ilegais. Um fã do IntelliSense, não quero vê-los listados.Uma coisa que me irritou no 1.x foi que, ao usar o
System.Xml.XmlValidatingReader
, oValidationEventHandler
doValidationEventArgs
não expõe o subjacenteXmlSchemaException
(marcado como interno), que contém todas as informações úteis comolinenumber
eposition
. Em vez disso, espera-se que você analise isso a partir da propriedade da string Message ou use a reflexão para descobrir. Não é tão bom quando você deseja retornar um erro mais limpo para o usuário final.fonte
Não gosto que você não possa usar os valores de um enum em outro enum, por exemplo:
enum Colors { white, blue, green, red, black, yellow } enum SpecialColors { Colors.blue, Colors.red, Colors.Yellow }
fonte
typeof(Color)
! =typeof(SpecialColors)
.enum SpecialColors { blue = Colors.blue, red = Colors.red, yellow = Colors.Yellow }
Variáveis implicitamente digitadas foram implementadas de forma inadequada na IMO. Eu sei que você só deve usá-los ao trabalhar com expressões do Linq, mas é irritante que você não possa declará-los fora do escopo local.
Do MSDN:
O motivo pelo qual acho que é uma implementação ruim é que eles a chamam de var, mas está muito longe de ser uma variante. É apenas uma sintaxe abreviada para não ter que digitar o nome completo da classe (exceto quando usado com Linq)
fonte