Quais são algumas das maiores falhas de design no C # ou no .NET Framework em geral?
Exemplo: não há tipo de string não anulável e você deve verificar se há DBNull ao buscar valores de um IDataReader.
Quais são algumas das maiores falhas de design no C # ou no .NET Framework em geral?
Exemplo: não há tipo de string não anulável e você deve verificar se há DBNull ao buscar valores de um IDataReader.
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.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,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,
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.IArithmetic; outras interfaces de operação compartilhadas úteis também são possíveis,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.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? :-)
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.
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)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?Containsentão Add), então uma coleção que sincroniza operações distintas não é tão útil
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.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 claroFoo(SqlConnection! connection)(que injeta uma verificação de nulo / throw) seria bom (contraste com int?etc)
dynamic, ou você pode habilitá-lo assimforeachexpansã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)
ApplicationExceptionera um erro - não era útil como eles esperavam. Eles também disseram que System.Exceptiondeveria ter sido abstract.
TextWriter é uma classe base de StreamWriter. wtf?
Isso sempre me confunde ao extremo.
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.
class Foo { new(int j) {i = j} int i; }
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.
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)
{
}
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.
DBNull.Valuequando nullele 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.
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? :)
(int x) => x * (x -1);pode significar Func<int, int>ou pode significarExpression<Func<int, int>>
+não está definido para object.
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:
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.
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.
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.
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.
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.
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.
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 # ...).
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?
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.
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
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.
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.
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.
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)
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.
DateTime.Nowprimeiro é a condição de corrida mais óbvia do mundo, mas +1 para o resto
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:
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.
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 :-)
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.
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.
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.
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.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é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).WeakReference<T>(você pode escrever facilmente o seu, mas usará casts internamente).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.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();.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.
IListReader<T>;) - Eu uso a palavra "fonte" como o antônimo de "dissipador" (uma interface somente de gravação).
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.
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, 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.
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 }
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)