@lagerdalek: haría +1 en ese comentario si pudiera; bien recordado
Marc Gravell
Respuestas:
39
Estoy de acuerdo enfáticamente con esta publicación (para aquellos que hacen popó por la falta de ToString, hay un atributo de depuración para proporcionar un formato personalizado para su clase).
En la parte superior de la lista anterior, también agregaría las siguientes solicitudes razonables:
tipos de referencia que no aceptan valores NULL como complemento de los tipos de valores que aceptan valores NULL,
permitir anular el constructor vacío de una estructura,
permitir restricciones de tipo genérico para especificar clases selladas,
Estoy de acuerdo con otro cartel aquí que solicitó firmas de constructor arbitrarias cuando se usa como restricciones, es decir. donde T : new(string)o dondeT : new(string, int)
También estoy de acuerdo con otro cartel aquí sobre la corrección de eventos, tanto para listas de eventos vacías como en la configuración concurrente (aunque este último es complicado)
los operadores deben definirse como métodos de extensión, y no como métodos estáticos de la clase (o no solo como métodos estáticos al menos),
permitir propiedades estáticas y métodos para interfaces (Java tiene esto, pero C # no),
permitir la inicialización de eventos en los inicializadores de objetos (actualmente solo se permiten campos y propiedades),
¿Por qué la sintaxis del "inicializador de objeto" solo se puede utilizar al crear un objeto? ¿Por qué no ponerlo a disposición en cualquier momento?var e = new Foo(); e { Bar = baz };
todas las colecciones deben tener instantáneas inmutables para la iteración (es decir, mutar la colección no debe invalidar el iterador),
las tuplas son fáciles de agregar, pero un tipo algebraico cerrado eficiente como " Either<T>" no lo es, así que me encantaría alguna forma de declarar un tipo algebraico cerrado y hacer cumplir una coincidencia de patrones exhaustiva en él (básicamente soporte de primera clase para el patrón de visitante, pero mucho más eficiente); así que solo tome enumeraciones, extiéndalas con soporte exhaustivo de coincidencia de patrones y no permita casos no válidos,
Me encantaría el soporte para la coincidencia de patrones en general, pero al menos para las pruebas de tipo de objeto; También me gusta un poco la sintaxis de cambio propuesta en otra publicación aquí,
Estoy de acuerdo con otra publicación en que las System.IOclases, como Stream, están algo mal diseñadas; cualquier interfaz que requiera algunas implementaciones para lanzar NotSupportedExceptiones un mal diseño,
IListdebería ser mucho más simple de lo que es; de hecho, esto puede ser cierto para muchas de las interfaces de colección concretas, como ICollection,
demasiados métodos arrojan excepciones, como IDictionary, por ejemplo,
Preferiría una forma de excepciones comprobadas mejor que la disponible en Java (consulte la investigación sobre sistemas de tipos y efectos para saber cómo se puede hacer esto),
arregla varios casos de esquina molestos en la resolución de sobrecarga de métodos genéricos; por ejemplo, intente proporcionar dos métodos de extensión sobrecargados, uno que opera en tipos de referencia y el otro en tipos de estructura que aceptan valores NULL, y vea cómo le gusta eso a su inferencia de tipos,
proporcionar una forma de reflexionar de forma segura sobre los nombres de campos y miembros para interfaces como INotifyPropertyChanged, que toman el nombre del campo como una cadena; puede hacer esto usando un método de extensión que toma un lambda con MemberExpression, es decir. () => Foo, pero eso no es muy eficiente,
Actualización: C # 6.0 agregó el nameof()operador para nombres de miembros individuales, pero no funciona en genéricos (en nameof(T) == "T"lugar del nombre del argumento de tipo real: aún debe hacerlo typeof(T).Name)), ni le permite obtener una cadena de "ruta" , por ejemplo, nameof(this.ComplexProperty.Value) == "Value"limitando sus posibles aplicaciones.
permitir operadores en interfaces y hacer que todos los tipos de números centrales se implementen IArithmetic; También son posibles otras interfaces de operador compartidas útiles,
hacer que sea más difícil mutar los campos / propiedades de los objetos, o al menos, permitir la anotación de campos inmutables y hacer que el verificador de tipos lo aplique (simplemente trátelo como una propiedad de solo captador, por Dios, no es difícil!) de hecho, unifique campos y propiedades de una manera más sensata ya que no tiene sentido tener ambos; Las propiedades automáticas de C # 3.0 son un primer paso en esta dirección, pero no van lo suficientemente lejos,
Actualización: aunque C # tenía la readonlypalabra clave, y C # 6.0 agregó propiedades automáticas de solo lectura, aunque no es tan estricto como el verdadero soporte de lenguaje para tipos y valores inmutables.
simplificar la declaración de constructores; Me gusta el enfoque de F #, pero la otra publicación aquí que requiere simplemente "nuevo" en lugar del nombre de la clase es mejor al menos,
Supongo que es suficiente por ahora. Todas estas son irritaciones con las que me he encontrado la semana pasada. Probablemente podría continuar durante horas si realmente me lo propongo. C # 4.0 ya está agregando argumentos con nombre, opcionales y predeterminados, que apruebo enfáticamente.
Ahora, para una solicitud irrazonable:
Sería realmente bueno si C # / CLR pudiera admitir el polimorfismo del constructor de tipos, es decir. genéricos sobre genéricos,
Con respecto al n. ° 1, cada tipo debe tener algún valor predeterminado o el sistema debe proporcionar un medio para ejecutar un constructor siempre que se asigne una variable o campo de un tipo en particular. Preferiría lo último (una derivación del n. ° 2) pero el n. ° 1 podría adaptarse si los métodos / propiedades no virtuales pudieran decorarse para especificar que deben llamarse sin una verificación nula. Esto permitiría que cosas como los campos "String" se comporten como si tuvieran por defecto una cadena vacía en lugar de nula (ya que la función de "longitud" estática de String podría devolver 0 si se invoca en una cadena nula).
supercat
1
Con respecto al # 2, sería útil en general si las estructuras pudieran especificar no solo un constructor que no sea fill-with-zero, sino también un constructor de copia que no sea byte-por-byte-copy. En realidad, realmente me gustaría ver un marco similar a .net que podría hacer un buen trabajo al reconocer que las entidades pueden tener valor o semántica de referencia, y permitir que los objetos de montón de tipo valor se etiqueten como mutables, compartidos-inmutables o no comprometidos (un objeto de valor no comprometido puede ser mutado si su modo es primero CompareExchange'd a mutable, o puede ser compartido si su modo es primero CompareExchange'd a compartido).
supercat
1
¡Excelentes puntos! Para el n. ° 1, una anotación del sistema de tipos es la solución común, pero prefiero propagar las restricciones del constructor a través de variables de tipo, como T: new (). Re: # 2, buen punto sobre los constructores de copia, pero estaría contento con constructores más generales en las líneas que describí anteriormente. Aún mejor sería eliminar los constructores como métodos distinguidos por completo y simplemente convertirlos en métodos estáticos. Esto permite patrones de construcción más simples y generales, particularmente si permitimos métodos estáticos en interfaces. Constructores-como-métodos-estáticos + métodos-estáticos-en-interfaces resuelve el # 1 también.
Naasking
3
# 3: ¿Cuál es el punto de usar una clase sellada como un parámetro de tipo genérico, por ejemplo, Foo <T> donde T: cadena? # 11: Bien, tengo List<T>un millón de Ts. ¿Cómo propone que la instantánea se tome de manera eficiente? # 21: Use la readonlypalabra clave ... si bien hay algunas buenas sugerencias aquí, en su mayoría son solo eso: sugerencias, no fallas de diseño.
Qwertie
2
Esta es una respuesta muy interesante, pero creo que deberíamos actualizar con las características de C # 6. Ejemplo: Se implementaron los elementos 19 y 21 =)
eduardobr
72
el Reset()método en IEnumerator<T>fue un error (para los bloques de iteradores, la especificación del lenguaje incluso exige que esto arroje una excepción)
los métodos de reflexión que devuelven matrices fueron, en opinión de Eric, un error
la covarianza de matriz era y sigue siendo una rareza
Actualización: C # 4.0 con .NET 4.0 agregó soporte de covarianza / contravarianza a interfaces genéricas (como IEnumerable<out T>y Func<in T, out TResult>, pero no tipos concretos (como List<T>).
ApplicationException más bien cayó en desgracia, ¿fue un error?
Colecciones sincronizadas: una buena idea, pero no necesariamente útil en la realidad: normalmente necesita sincronizar varias operaciones ( Contains, entonces Add), por lo que una colección que sincroniza distintas operaciones no es tan útil
Actualización: Los System.Collections.Concurrenttipos , con TryAdd, GetOrAdd, TryRemove, etc se añadieron en .NET Framework 4.0 - a pesar de los métodos que aceptan un delegado de la fábrica no garantizan la fábrica sólo se invoca una vez por llave.
se podría haber hecho más uso del patrón using/ lock, quizás permitiéndoles compartir una sintaxis reutilizable (¿extensible?); puede simular esto regresando IDisposabley usando using, pero podría haber sido más claro
bloques de iteradores: no hay una forma sencilla de verificar los argumentos antes de tiempo (en lugar de hacerlo de forma perezosa) Claro, puedes escribir dos métodos encadenados, pero eso es feo
una inmutabilidad más simple estaría bien; C # 4.0 ayuda un poco , pero no lo suficiente
no se admite "este parámetro de tipo ref no puede ser nulo", aunque los contratos (en 4.0) ayudan un poco con esto. Pero una sintaxis como Foo(SqlConnection! connection)(que inyecta una verificación nula / throw) sería agradable (en contraste con, int?etc.)
falta de soporte de operadores y constructores no predeterminados con genéricos; C # 4.0 resuelve esto un poco con dynamic, o puede habilitarlo así
la variable iteradora se declara fuera del while en la foreachexpansión, lo que significa que anon-methods / lambdas capturan la variable única, en lugar de una por iteración (doloroso con threading / async / etc)
IEnumerable! = IEnumerable <object> es realmente extraño
Rauhotz
2
Bueno, lo de IEnumerable es una resaca 1.1; puede usar .Cast <object> () con LINQ, al menos
Marc Gravell
8
La gente de BCL dijo que ApplicationExceptionfue un error, no útil como esperaban. También dijeron que System.Exceptiondebería haber sido abstract.
Jay Bazuzi
2
No anulable: debería ser un error de compilación pasar un tipo de referencia regular T a algo que toma una T no anulable. (al igual que no puede pasar int? a int). Pasar en la otra dirección está bien, por supuesto.
Jay Bazuzi
1
@Jon Harrop: En mi humilde opinión, debería haber soporte para matrices inmutables y para referencias de matrices de solo lectura. Posiblemente también para algunas otras variantes de matriz (por ejemplo, una "matriz redimensionable" (referencia indirecta) o una matriz de referencia con un desplazamiento y un límite).
supercat
60
TextWriter es una clase base de StreamWriter. wtf?
+1 Tengo que buscarlo cada vez. (¿Qué significa que no puedo escribir un nuevo TextWriter ()?)
Nicholas Piasecki
1
Gracias a Dios ... pensé que era solo yo.
IJ Kennedy
? Siempre es algo que escribe texto, pero solo StreamWriter lo hace en una transmisión. Parece bastante sencillo.
Jon Hanna
4
El nombre StreamWriter no hace que el hecho de que escriba texto sea lo suficientemente obvio en mi opinión. Suena de forma aislada como si solo escribiera bytes y TextWriter sería una implementación elegante en la parte superior que convertiría la api de (string toWrite) en bytes para usted. Ahora, si se llamara StreamTextWriter, entonces seguro, sería inmediatamente obvio pero un poco largo :(
Quibbleome
44
Una pequeña mascota de C #: los constructores usan la sintaxis de C ++ / Java de tener el constructor con el mismo nombre que la clase.
New()o ctor()hubiera sido mucho mejor.
Y claro, herramientas como coderush hacen que esto sea menos problemático para cambiar el nombre de las clases, pero desde un punto de vista de legibilidad, New () proporciona una gran claridad.
Erm, ¿cómo sabría de qué estás intentando crear una nueva instancia?
BlueRaja - Danny Pflughoeft
4
@BlueRaja: Scott se refiere a la denominación de constructores en clases. class Foo { new(int j) {i = j} int i; }
dalle
Si bien estoy 100% de acuerdo en que ctor () o constructor () hubiera sido mejor (no, Newlas palabras clave en mayúsculas están en contra de la convención), dudo en llamarlo un defecto de diseño. Querían atraer a los desarrolladores de C ++ / Java existentes, y podría decirse que tomar prestadas muchas convenciones sintácticas estúpidas y antiguas les ayudó a alcanzar su objetivo.
Qwertie
relacionado con esta pregunta: stackoverflow.com/questions/32101993/c-sharp-sorted-linkedlist no hay forma (usando solo .NET) de simplemente ordenar una LinkedList con MergeSort, bucketsort o cualquier otro algoritmo de clasificación pero usando linq que es mucho más lento que una implementación ad hoc.
CoffeDeveloper
29
No entiendo que no puedas hacer
donde T: nuevo (U)
Entonces declaras que el tipo genérico T tiene un constructor no predeterminado.
Un tipo genérico declara cómo utilizará los objetos de ese tipo, es decir, su interfaz. El constructor es un detalle de implementación de esa interfaz, que no es asunto del consumidor. Cuando necesite crear una instancia parametrizada, use una fábrica.
Bryan Watts
Para obtener información, aunque no puede verificar en tiempo de compilación, hay código en MiscUtil para usar constructores no predeterminados (en genéricos) de manera eficiente, es decir, sin Activator.CreateInstance o reflexión.
Marc Gravell
Porque no tiene sentido y es confuso en algunos usos. No obstante, puede resultar útil cuando se trabaja con objetos inmutables.
Pop Catalin
7
En general, la falta de restricciones de miembros es molesta, sí.
MichaelGG
3
amén, siempre quise esto
Steve
20
Estoy realmente sorprendido de ser el primero en mencionar este:
Los conjuntos de datos con tipo de ADO.NET no exponen columnas que aceptan valores NULL como propiedades de tipos que aceptan valores NULL. Debería poder escribir esto:
int? i = myRec.Field;
myRec.Field = null;
En cambio, tienes que escribir esto, que es simplemente estúpido:
int? i = (int?)myRec.IsFieldNull() ? (int?)null : myRec.Field;
myRec.SetFieldNull();
Esto era molesto en .NET 2.0, y es aún más molesto ahora que tienes que usar jiggery-pokery como el anterior en tus agradables consultas LINQ.
También es molesto que el Add<TableName>Rowmétodo generado sea igualmente insensible a la noción de tipos que aceptan valores NULL. Tanto más cuanto que los TableAdaptermétodos generados no lo son.
No hay muchas cosas en .NET que me hagan sentir como si el equipo de desarrollo dijera "Está bien, chicos, estamos lo suficientemente cerca, ¡envíelo!" Pero esto seguro que sí.
¡Estoy totalmente de acuerdo! Esto me molesta cada vez que tengo que usarlo (que es a menudo). ¡Argh! +1
Eyvind
2
Lo mínimo que podrían hacer sería crear una nueva clase DataSetV2 (mal nombre, solo por el bien del argumento), que usara tipos de valor anulables en lugar de DBNull en todo momento.
Christian Hayter
No olvidemos el absurdo de requerir un valor especial DBNull.Value, cuando en nullsí mismo hubiera sido perfectamente adecuado para representar NULL. Afortunadamente, LINQ-to-SQL solo usa null para NULL.
Qwertie
En realidad, ese absurdo es la piedra sobre la que se construyó todo el absurdo edificio.
Robert Rossney
20
No soy un gran admirador de las clases Stream, StringWriter, StringReader, TextReader, TextWriter ... simplemente no es intuitivo qué es qué.
IEnumerable.Reset arroja una excepción para los iteradores. Tengo algunos componentes de terceros que siempre llaman al restablecimiento cuando están vinculados a datos, lo que requiere que primero envíe a una lista para usarlos.
Xml Serializer debería tener elementos IDictionary serializados
Me olvidé por completo de la API de HttpWebRequest y FTP, qué dolor en mi ... (gracias por el comentario, Nicholas, para recordarme esto :-)
Edición
5. Otra de mis molestias es cómo System.Reflection.BindingFlags, tiene diferentes usos dependiendo del método que estés usando. En FindFields, por ejemplo, ¿qué significa CreateInstance o SetField? Este es un caso en el que han sobrecargado el significado detrás de esta enumeración que es confusa.
+1 Tengo que buscar cualquiera de las clases XmlTextWriter, TextWriter, etc. cada vez. Lo mismo con el material HttpWebRequest / Response. API completamente intuitiva allí.
Nicholas Piasecki
+ 1-1 = 0: XmlTextWriter, etc., estoy de acuerdo en que es imposible inferir realmente lo que son del nombre. HttpWebRequest No estoy de acuerdo, lo encuentro bastante intuitivo.
AnthonyWJones
A cada uno lo suyo, supongo. Supongo que con FTP esperaría un nivel de abstracción superior al que tienen.
JoshBerke
15
No sé si iría tan lejos como para decir que es un defecto de diseño, pero sería realmente bueno si pudiera inferir una expresión lambda de la misma manera que puede en VB:
VB:
Dim a = Function(x) x * (x - 1)
C#
Sería bueno si pudiera hacer esto:
var a = x => x * (x - 1);
En lugar de tener que hacer esto:
Func<int, int> a = x => x * (x - 1);
Me doy cuenta de que no es mucho más largo, ¡pero en Code Golf cada personaje cuenta, maldita sea! ¿No tienen eso en cuenta cuando diseñan estos lenguajes de programación? :)
¿Microsoft debería tener en cuenta Code Golf cuando diseña lenguajes?
jrcs3
3
@Ray Burns: ¿Cómo lo sabe en VB? VB lo admite, entonces, ¿cuál es la diferencia?
BenAlabaster
3
@RayBurns ¿Inferencia de tipo? Lo he estado usando desde 1989.
RD1
3
Las lamdas son homoicónicas en C #. el fragmento (int x) => x * (x -1);puede significar Func<int, int>o puede significarExpression<Func<int, int>>
Scott Weinstein
3
@BenAlabaster: VB admite operadores aritméticos de enlace tardío. C # tiene que resolverlos en tiempo de compilación. Es una diferencia de idioma. Por ejemplo, VB puede agregar dos objetos juntos. C # no puede porque +no está definido para object.
Equals y GetHashCode: no todas las clases son comparables o se pueden usar con hash, deben moverse a una interfaz. Me viene a la mente IEquatable o IComparable (o similar).
ToString: no todas las clases se pueden convertir en una cadena, deben moverse a una interfaz. Me viene a la mente IFormattable (o similar).
1. Esos métodos son tan comunes que se decidió que los casos raros estaban bien, ¿importa si alguien puede llamar a Object.Equals en tu clase? Se sabe que puede haber o no una implementación, y requerir: IEquatable, IFormattable en el 99% de las clases es extraño.
Guvante
1
Es útil, por ejemplo, poder construir un diccionario con objetos definidos por el usuario como claves, utilizando la igualdad de referencia predeterminada, sin tener que agregar código explícitamente a los objetos definidos por el usuario para ese propósito. Consideraría que Finalizar es un desperdicio mucho mayor (una mejor alternativa habría sido tener objetos que necesitarán finalización, implementar iFinalizable y registrarse explícitamente para la finalización). OTOH, debería haber habido más soporte inherente para iDisposable, incluida la llamada a Dispose si un constructor lanza una excepción.
supercat
1
@supercat: Todo lo que se necesita es actualizar EqualityComparer<T>.Defaultcorrectamente. Entonces ambos var dict = new Dictionary<object, string>(EqualityComparer<object>.Default)y var dict = new Dictionary<object, string>()usarán comparación / igualdad de referencia.
dalle
1
@supercat: Lo que describe es exactamente lo que EqualityComparer<T>.Defaulthace. No es necesario comprobar en cada búsqueda. El comparador es una propiedad de la Dictionaryinstancia y cada uno Dictionarysabe cuál está usando.
dalle
1
@supercat: Un diccionario debe usar el tipo más genérico (es decir, la clase base común) como clave, el uso de Strings y DateTime en el mismo diccionario no tendría ningún sentido a menos que use comparación de referencia, a menos que se proporcione una comparación definida por el usuario que es. Recuerde que el nombre de este tema es "Defectos de diseño de C # (.NET)".
dalle
12
Una de las cosas que me irrita es la Predicate<T> != Func<T, bool>paradoja. Ambos son delegados de tipo T -> booly, sin embargo, no son compatibles con la asignación.
Hay un truco al usar Delegate.Create y algo de casting para hacer la conversión, pero al menos ser capaz de hacer un cast explícito sería bueno (sin embargo, puedo entender la falta de soporte para implícito)
Guvante
El diseño de los delegados en general es defectuoso; por ejemplo, la falta de eventos débiles (la implementación de eventos débiles del lado de la fuente sin un esfuerzo especial del suscriptor solo se puede hacer con un montón de reflexión y ReflectionPermission, consulte codeproject.com/Articles/29922/Weak-Events-in-C ), y la ineficiencia que proviene del requisito de que los delegados deben ser tipos de referencia (los delegados habrían sido más rápidos y habrían usado 1/3 de la memoria en muchos casos, si hubieran sido tipos de valor, entonces simplemente serían un par de punteros que podría pasar en la pila.)
Qwertie
11
Algunas personas (ISV) desean que pueda compilarlo en código de máquina en el momento de la compilación y vincularlo para crear un ejecutable nativo que no necesite el tiempo de ejecución de dotNet.
¿No hay una herramienta de ofuscación que haga esto? Integrará el marco en su exe para que no tenga que implementarlo. No puedo recordar su nombre ... no es PreEmptive ...
JoshBerke
2
¿No hace eso Postbuild de Xenocode? Sería bueno si Visual Studio tuviera una manera de hacer esto ...
BenAlabaster
11
Sabemos mucho sobre las técnicas adecuadas de OO. Desacoplamiento, programación por contrato, evitar herencias indebidas, uso apropiado de excepciones, principal abierto / cerrado, sustituibilidad de Liskov, etc. Aún así, los marcos .Net no emplean las mejores prácticas.
Para mí, la falla más grande en el diseño de .Net no está sobre los hombros de gigantes; Promover paradigmas de programación menos que ideales para las masas de programadores que usan sus marcos. .
Si MS prestó atención a esto, el mundo de la ingeniería de software podría haber dado grandes saltos en términos de calidad, estabilidad y escalabilidad en esta década, pero lamentablemente, parece estar retrocediendo.
+1 por la perorata sobre el objetivo; Yo agregaría a esto la incapacidad de subclasificar cada clase, la falta de interfaces para las clases fundamentales y la renuencia a corregir errores de framework incluso después de varios años
Steven A. Lowe
1
Si vamos al grano, ¿estamos diciendo que los frameworks .Net son tan malos que es difícil decidir cuál de las fallas es la peor? Me siento mejor después de un desahogo y aprecio el voto positivo, ya que esperaba que los fanáticos de MS me gritaran.
Daniel Paull
No he votado de ninguna manera, y de todos modos soy muy reacio a emitir votos en contra, pero estoy tratando de descubrir por qué NO ME IMPORTA.
Mike Dunlavey
2
Creo que estás diciendo "no es tan bueno como podría ser", lo cual no es una respuesta. Nada es perfecto. Proporcione detalles.
jcollum
3
No, estoy diciendo que hay numerosos casos en los que el diseño es obviamente defectuoso, luego, cuando crees que lo hacen bien, todavía lo hacen mal. Por ejemplo, mi publicación en los foros de MSDN aquí: social.msdn.microsoft.com/forums/en-US/wpf/thread/…
En realidad, creo que sería incluso mejor si requiriera una sola declaración o un bloque entre llaves, como todo lo demás en C #. Sin la capacidad de fallar, la sintaxis de ruptura actual es un poco dudosa y tampoco se limita al alcance. (AFAIK, podría, no lo sé)
Tamas Czinege
No entiendo lo que quieres decir. Publique su propia declaración de cambio 'ideal' aquí. No quiero fallar sino valores separados por comas.
tuinstoel
8
Por cierto, estoy de acuerdo con OP. switchestá fundamentalmente roto en todos los lenguajes que emulan la versión deliberadamente lisiada de C (¡optimizada para la velocidad!). A VB le va mucho mejor, pero todavía está a años luz de los lenguajes con coincidencia de patrones (Haskell, F #…).
Konrad Rudolph
1
tuinostel: algo así como cambiar (a) {caso 1 {hacer_algo; } caso 2 {hacer_algo_más; }} - es decir, deshacerse de la instrucción break y requerir bloques de código adecuados para cada caso
Tamas Czinege
2
Tener una ruptura parece un error en general, ¿no es un error de compilación emitirlo? Parece que solo está ahí para facilitar la transición de C a C # (también conocido como enseñar a los desarrolladores que no pueden fallar automáticamente)
Guvante
10
Eventos en C #, donde debe verificar explícitamente si hay oyentes. ¿No era ese el punto con los eventos, transmitir a quienquiera que estuviera allí? ¿Incluso si no hay ninguno?
Es molesto que no haya azúcar para esto cuando lo quieres, veo por qué lo hacen difícil, anima a no instanciar los argumentos del evento si no es necesario
ShuggyCoUk
Hm. No entiendo o no estoy de acuerdo, o ambas cosas :-). No puedo decir que alguna vez me haya sentido desanimado a instanciar algo. Esto me huele a optimización prematura.
Thomas Eyde
Los métodos parciales son una alternativa viable, en algunos casos.
Robert Harvey
ADEMÁS de todo el acoplamiento que esto causa ... MS tiene CAB que soluciona ambos problemas, pero CAB tiene muchos problemas propios debido a las limitaciones en C # (por ejemplo, cadenas en lugar de enumeraciones como temas de eventos) - ¿por qué no hacerlos libremente -¿¡Eventos acoplados forman parte del lenguaje !?
BlueRaja - Danny Pflughoeft
9
El comportamiento horrible (y bastante invisible para la mayoría de la gente) O (N ^ 2) de los iteradores anidados / recursivos .
Estoy bastante destrozado de que lo sepan, sepan cómo solucionarlo, pero no se considera que tenga la prioridad suficiente para merecer su inclusión.
Trabajo con estructuras en forma de árbol todo el tiempo y tengo que corregir el código de personas inteligentes cuando inadvertidamente introducen operaciones muy costosas de esta manera.
La belleza de "yield foreach" es que la sintaxis más simple y fácil fomenta el código correcto y eficaz. Este es el "pozo del éxito" al que creo que deberían aspirar antes de agregar nuevas funciones para el éxito a largo plazo de la plataforma.
Esto es especialmente defectuoso, porque va en contra de las expectativas intuitivas del comportamiento de O y, por lo tanto, atrapará a muchas personas.
Bueno, no puede cambiar la cantidad de elementos en una matriz, por lo que no hay nada que Agregar, Borrar, Insertar y Eliminar (en) pueda hacer más que lanzar NotSupported ... De hecho, esperaría CUALQUIER implementación de IList que devuelva verdadero para IsFixedSize les arrojaría.
CB
@CB un poco tarde para la fiesta, supongo :) Pero si Array no puede satisfacer a "IList", ¿por qué implementarlo de todos modos? Eso es una violación del principio L en SOLID.
Extremo Sendon
7
Miembros estáticos y tipos anidados en interfaces.
Esto es particularmente útil cuando un miembro de la interfaz tiene un parámetro de un tipo que es específico de la interfaz ( por ejemplo, an enum). Sería bueno anidar el tipo de enumeración en el tipo de interfaz.
No, este es sobre el lenguaje C # y el otro es sobre el framework. No a todo el mundo le importa la distinción, así que déjame decir que este se trata de lo que está permitido y el otro se trata de lo que se proporciona.
Jay Bazuzi
6
La naturaleza por defecto terriblemente peligrosa de los eventos. El hecho de que pueda llamar a un evento y estar en un estado inconsistente debido a la eliminación de suscriptores es simplemente horrible. Vea los excelentes artículos de Jon Skeet y Eric Lippert para leer más sobre el tema.
No me importaría si los eventos no fueran seguros para subprocesos de forma predeterminada (podría aumentar el rendimiento en código de un solo subproceso); lo tonto es que agregar / quitar es seguro por defecto, pero que la forma natural de disparar un evento es insegura y no hay ninguna facilidad para hacerlo seguro fácilmente.
Qwertie
@Qwertie: Lo que es más tonto es que durante bastante tiempo, agregar / quitar usaría el bloqueo y aún así no sería seguro para subprocesos.
supercat
6
null En todas partes.
const en ninguna parte.
Las API son incoherentes, por ejemplo, la mutación de un arreglo devuelve voidpero agregando a un StringBufferdevuelve el mismo mutable StringBuffer.
Interfaces de la colección son incompatibles con las estructuras de datos inmutables, por ejemplo, Adden System.Collections.Generic.IList<_>que no puede devolver un resultado.
Sin escritura estructural, por lo que escribe en System.Windows.Media.Effects.SamplingMode.Bilinearlugar de solo Bilinear.
IEnumeratorInterfaz mutable implementada por clases cuando debería ser inmutable struct.
La igualdad y la comparación son un desastre: que tienes System.IComparabley Equalspero luego también tienes System.IComparable<_>, System.IEquatable, System.Collections.IComparer, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable, System.Collections.Generic.IComparery System.Collections.Generic.IEqualityComparer.
Las tuplas deben ser estructuras, pero las estructuras inhiben innecesariamente la eliminación de llamadas de cola, por lo que uno de los tipos de datos más comunes y fundamentales se asignará innecesariamente y destruirá el paralelismo escalable.
No hay escritura estructural en CLR, pero parece que la escritura estructural se mezcla con la inferencia de tipos o con la función Ruby conocida como "símbolos". La tipificación estructural sería si CLR considerara que Func <int, bool> y Predicate <int> son del mismo tipo, o al menos implícitamente convertibles.
Qwertie
Hablando de comparación, ¡no olvide Comparer <T>!
Qwertie
@Qwertie Me refería a características del lenguaje de programación como variantes polimórficas en OCaml. La biblioteca LablGL de OCaml tiene muchos ejemplos interesantes de tipificación estructural que son útiles en el contexto de los gráficos. Nada que ver con la inferencia de tipos y solo tangencialmente relacionado con los símbolos.
JD
1
¿Cómo se usaría una estructura inmutable IEnumerator?
+1 enum es una de las pocas cosas que Java hizo bien mientras que C # no lo hizo
BlueRaja - Danny Pflughoeft
5
Para agregar a la larga lista de buenos puntos ya mencionados por otros:
DateTime.Now == DateTime.Now en la mayoría de los casos, pero no en todos.
Stringque es inmutable tiene un montón de opciones de construcción y manipulación, pero StringBuilder(que es mutable) no.
Monitor.Entery Monitor.Exitdeberían haber sido métodos de instancia, por lo que en lugar de crear un objeto específico para bloquearlo, podría usar un nuevo Monitory bloquearlo.
Los destructores nunca deberían haber sido llamados destructores. La especificación ECMA los llama finalizadores, lo que es mucho menos confuso para la multitud de C ++, pero la especificación del lenguaje todavía se refiere a ellos como destructores.
Esta DateTime.Nowes la condición de carrera más obvia del mundo, pero +1 para el resto
BlueRaja - Danny Pflughoeft
No es tanto que sea una condición de carrera, sino el hecho de que lo convirtieron en una propiedad. Las propiedades se ven exactamente como campos, por lo que es un comportamiento bastante sorprendente en mi opinión.
Brian Rasmussen
4
@Brian Rasmussen: DateTime.Now es una propiedad bastante propiamente dicha, ya que no se modifica al leerla, sino a factores externos. Si uno lee una propiedad como SomeForm.Width, y luego, después de que el usuario ha redimensionado el formulario, lo vuelve a leer, el valor en la segunda lectura será diferente. Si bien es posible que el primer DateTime.Now tarde en ejecutarse lo suficiente como para influir en el valor leído por el segundo, tal efecto no sería diferente de cualquier otra función cuya ejecución tomó la misma cantidad de tiempo.
supercat
4
La forma en que usamos las propiedades me irrita a veces. Me gusta pensar en ellos como el equivalente de los métodos getFoo () y setFoo () de Java. Pero no lo son.
Si las Pautas de uso de propiedades establecen que las propiedades deben poder configurarse en cualquier orden para que la serialización funcione, entonces son inútiles para la validación en tiempo de establecimiento. Si viene de un entorno en el que le gusta evitar que un objeto se permita entrar en un estado no válido, entonces las propiedades no son su solución. A veces no veo cómo son mejores que los miembros públicos, ya que estamos tan limitados en el tipo de cosas que se supone que debemos hacer en las propiedades.
Con ese fin, siempre he deseado (esto es principalmente pensar en voz alta aquí, simplemente desearía poder hacer algo como esto) que pudiera extender la sintaxis de la propiedad de alguna manera. Imagina algo como esto:
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;
}
}
No estoy seguro de si eso es útil o cómo se vería acceder a ellos. Pero solo desearía poder hacer más con las propiedades y realmente tratarlas como métodos get y set.
Creo que proporcionan la interfaz ISerializable y el constructor implícito para hacer cosas así, también conocidas como cuando no quieres que el serializador simplemente llame a las propiedades. Si bien es un poco más de trabajo, parece que ya está haciendo la mayor parte de ese trabajo con su método.
Guvante
4
Los métodos de extensión son agradables, pero son una forma fea de resolver problemas que podrían haberse resuelto de manera más limpia con mixins reales (mira ruby para ver de qué estoy hablando), sobre el tema de los mixins. Una forma realmente agradable de agregarlos al lenguaje hubiera sido permitir que los genéricos se usaran para la herencia. Esto le permite extender las clases existentes de una manera agradable orientada a objetos:
publicclassMyMixin<T> : T
{
// etc...
}
esto se puede usar así para extender una cadena, por ejemplo:
var newMixin = new MyMixin<string>();
Es mucho más poderoso que los métodos de extensión porque le permite anular métodos, por ejemplo, envolverlos permitiendo una funcionalidad similar a AOP dentro del lenguaje.
Interesante, pero prefiero los métodos de extensión. Si obtengo una biblioteca que contiene un montón de métodos de extensión para cadenas, no quiero tener que cambiar todas las referencias de cadenas a MyMixin <string> para obtener las cosas nuevas. Es un poco menor, claro, pero esa adición transparente de métodos es lo que hace que los métodos de extensión sean tan agradables.
RCIX
Por cierto, ¿sabías que eso ya funciona?
RCIX
2
No veo cómo LINQ podría funcionar de esta manera
BlueRaja - Danny Pflughoeft
2
@RCIX: Los mixins suenan como pensé que los métodos de extensión deberían funcionar. El problema de hacer implícitos los métodos de extensión es que significa que los miembros reales de la clase deben tener prioridad sobre los métodos de extensión. Si se define un método de extensión Graphics.DrawParallelogram (Pen p, Point v1, Point v2, Point v3) y luego se agrega una función DrawParallelogram a System.Graphics que usa puntos en un orden diferente, el código que usa el método de extensión se romperá sin advertencia. Por cierto, ¿habría habido algún problema al usar dos puntos para los métodos de extensión (por ejemplo, object..method ()?)
supercat
3
Microsoft no solucionará errores obvios en el marco y no proporcionará enlaces para que los usuarios finales puedan solucionarlos.
Además, no hay forma de aplicar parches binarios a los ejecutables de .NET en tiempo de ejecución y no hay forma de especificar versiones privadas de las bibliotecas de .NET framework sin parchear las bibliotecas nativas (para interceptar la llamada de carga), e ILDASM no es redistribuible, por lo que no puedo automatizar el parche de todos modos.
# 1 Haga clic en un control secundario parcialmente visible de un control desplazable. El control se cambia a la vista antes de recibir el evento MouseDown, lo que hace que el clic esté en otro lugar del control de lo esperado. Es peor en las vistas de árbol, donde también desencadena una operación de arrastre.
Poder invocar un método de extensión en una variable nula es discutible, por ejemplo
objeto a = nulo; a.MyExtMethod (); // esto es invocable, suponga que en algún lugar ha definido MyExtMethod
Podría ser útil pero es ambiguo en temas de excepción de referencia nula.
Un "defecto" de denominación. La 'C' de "configuración" en System.configuration.dll debe estar en mayúscula.
Manejo de excepciones. La excepción debe ser capturada a la fuerza o lanzada como en Java, el compilador debe verificarla en el momento de la compilación. Los usuarios no deben confiar en los comentarios para obtener información sobre excepciones dentro de la invocación de destino.
Muy útil, sin embargo, tengo un método de extensión "ThrowIfNull` para la verificación de parámetros ;-p
Marc Gravell
2
¿Puedes hacerlo? ugh ThrowIfNull es una extensión interesante, pero parece incorrecta.
JoshBerke
1
En CLR simple, puede invocar métodos de instancia en referencias nulas y si el método no accede al objeto o sus campos, la llamada no arroja una excepción de referencia nula. (No puede hacer esto en C # ya que usa callvirt incluso para métodos no virtuales)
Pop Catalin
7
la excepción es totalmente incorrecta. No puede fallar rápido si TIENE que detectar todas las malditas excepciones que puedan aparecer en la pila de llamadas. Pero DESEO que fuera mucho más fácil identificar todas y cada una de las excepciones que podrían lanzarse en una llamada en particular y su pila de llamadas resultante ...
3
@Will: El manejo de excepciones es complicado tanto en Java como en .net, ya que el mecanismo utilizado une íntimamente tres conceptos que están algo relacionados pero también algo ortogonales: (1) Qué tipo de cosas salieron mal (un error de límites de matriz, un tiempo de espera de E / S, etc.); (2) Si cierto código debería tomar medidas como resultado; (3) En qué momento el problema debe considerarse "resuelto". Considere una rutina que se supone que debe mutar un objeto con datos leídos de un IEnumerable. ¿Qué debería suceder si ocurre una excepción en el procesamiento de ese IEnumerable?
supercat
3
El método .Parameters.Add () en SqlCommand en V1 del marco se diseñó horriblemente; una de las sobrecargas básicamente no funcionaría si pasara un parámetro con un valor (int) de 0; esto los llevó a crear el método .Parameters.AddWithValue () en la clase SqlCommand.
Estoy de acuerdo, pero creo que te refieres al método SqlCommand.Parameters.Add ().
Matt Peterson
3
No hay subconjuntos de ICollection<T>y IList<T>; como mínimo, una interfaz de recopilación covariante de solo lecturaIListSource<out T> (con enumerador, indexador y recuento) hubiera sido extremadamente útil.
.NET no admite delegados débiles . Las soluciones alternativas son torpes en el mejor de los casos, y las soluciones alternativas del lado del oyente son imposibles en la confianza parcial (se requiere ReflectionPermission).
No es posible comparar bit a bit dos tipos de valores para la igualdad. En una estructura de datos " persistente " funcional , estaba escribiendo unTransform(Sequence<T>, Func<T,T>) función que necesitaba determinar rápidamente si la función devuelve el mismo valor o un valor diferente. Si la función no modifica la mayoría / todos sus argumentos, entonces la secuencia de salida puede compartir parte / toda la memoria de la secuencia de entrada. Sin la capacidad de comparar bit a bit cualquier tipo de valor T, se debe usar una comparación mucho más lenta, lo que perjudica enormemente el rendimiento.
.NET no parece ser capaz de soportar interfaces ad-hoc (como las que se ofrecen en Go o Rust) de manera eficiente. Tales interfaces le habrían permitido convertir List<T>a un hipotético IListSource<U>(donde T: U) incluso aunque la clase no implemente explícitamente esa interfaz. Hay al menos tres bibliotecas diferentes (escritas de forma independiente) para proporcionar esta funcionalidad (con inconvenientes de rendimiento, por supuesto; si fuera posible una solución perfecta, no sería justo llamarlo un defecto en .NET).
Otros problemas de rendimiento: IEnumerator requiere dos llamadas de interfaz por iteración. Los punteros de método simple (delegados abiertos del tamaño de IntPtr) o los delegados de tipo valor (IntPtr * 2) no son posibles. Las matrices de tamaño fijo (de tipo arbitrario T) no se pueden incrustar dentro de clases. No existe WeakReference<T>(puede escribir fácilmente el suyo, pero usará conversiones internamente).
El hecho de que tipos de delegados idénticos se consideren incompatibles (sin conversión implícita) ha sido una molestia para mí en algunas ocasiones (por ejemplo, Predicate<T>vs Func<T,bool>). A menudo desearía que pudiéramos tener tipificación estructural para interfaces y delegados, para lograr un acoplamiento más flexible entre componentes, porque en .NET no es suficiente que las clases en DLL independientes implementen la misma interfaz; también deben compartir una referencia común a una tercera DLL que define la interfaz.
DBNull.Valueexiste aunque nullhubiera servido igualmente bien para el mismo propósito.
C # no tiene operador ?? =; debes escribir variable = variable ?? value. De hecho, hay algunos lugares en C # que carecen innecesariamente de simetría. Por ejemplo, puede escribir if (x) y(); else z();(sin llaves), pero no puede escribir try y(); finally z();.
Al crear un hilo, es imposible hacer que el hilo secundario herede los valores locales del hilo del hilo principal. El BCL no solo no admite esto, sino que no puede implementarlo usted mismo a menos que cree todos los subprocesos manualmente; incluso si hubiera un evento de creación de hilo, .NET no puede decirle los "padres" o "hijos" de un hilo dado.
El hecho de que haya dos atributos de longitud diferentes para diferentes tipos de datos, "Longitud" y "Recuento", es una molestia menor.
Podría seguir y seguir para siempre sobre el mal diseño de WPF ... y WCF (aunque bastante útil para algunos escenarios) también está lleno de verrugas. En general, la hinchazón, la falta de intuición y la documentación limitada de muchas de las subbibliotecas más nuevas de BCL me hacen reacio a usarlas. Muchas de las cosas nuevas podrían haber sido mucho más simples, más pequeñas, más fáciles de usar y comprender, más acopladas, mejor documentadas, aplicables a más casos de uso, más rápidas y / o más fuertemente tipadas.
A menudo me muerde el acoplamiento innecesario entre los captadores de propiedades y los definidores: en una clase o interfaz derivada, no se puede simplemente agregar un definidor cuando la clase base o la interfaz base solo tienen un captador; si anula un captador, no se le permite definir un definidor; y no se puede definir el setter como virtual pero el getter como no virtual.
Estoy de acuerdo contigo sobre un subconjunto de IList<T>, aunque usaría IReadableByIndex<out T>y IAppendable<in T>. Muchas de tus otras cosas son graznidos con los que también estoy de acuerdo.
supercat
Ese es un nombre muy largo. Quizás podríamos comprometernos IListReader<T>;) - Utilizo la palabra "fuente" como antónimo de "sumidero" (una interfaz de solo escritura).
Qwertie
Quizás IListSource<in T>o IReadableList<out T>. Puede ser valioso que los tipos de interfaz base incluyan métodos que no existen en todas las derivadas, aunque creo que a menudo es bueno tener interfaces algo especializadas. Por ejemplo, uno podría tener un IList<T>que contiene métodos de cambio de tamaño que pueden funcionar o no, y uno IResizableList<T>que implementa los mismos métodos, pero garantiza que deberían funcionar. Este enfoque puede ser útil en los casos en que un campo puede contener la única referencia existente a una lista mutable, o una referencia compartida a una inmutable.
supercat
En tal caso, el código que quiera cambiar el contenido de la lista verificaría si es un tipo mutable y, de lo contrario, generaría una nueva instancia mutable que contiene los mismos elementos que la lista inmutable y luego comenzará a usarla. Sería molesto si el código tuviera que encasillar constantemente el campo cada vez que quisiera usar un método de mutación en él.
supercat
@supercat Eso es molesto porque C # no proporciona una manera realmente fácil de verificar que una interfaz está implementada y usarla de inmediato. MS debería agregar una función de idioma para hacerlo más fácil. Mi técnica preferida sería una expresión vinculante if (rl:(list as IResizableList<T>) != null) rl.Add(...);, pero hay otras propuestas. Como autor de varias colecciones y adaptadores de colecciones, lo que me molesta es escribir muchos métodos ficticios que arrojan excepciones. Como fanático de la seguridad de tipos, no quiero que se me permita llamar a métodos ilegales. Un fan de IntelliSense, no quiero verlos en la lista.
Qwertie
2
Una cosa que me molestó en 1.x fue que cuando uso el System.Xml.XmlValidatingReader, el ValidationEventHandler's ValidationEventArgsno expone el subyacente XmlSchemaException(marcado interno) que tiene toda la información útil como linenumbery position. En su lugar, se espera que analice esto fuera de la propiedad de la cadena de mensaje o use la reflexión para desenterrarlo. No es tan bueno cuando desea devolver un error más desinfectado al usuario final.
Sin embargo, esto ni siquiera tiene sentido. typeof(Color)! = typeof(SpecialColors).
Kirk Woll
10
Es bastante fácil de hacer:enum SpecialColors { blue = Colors.blue, red = Colors.red, yellow = Colors.Yellow }
Trystan Spangler
0
Las variables tipadas implícitamente se implementaron de manera deficiente en la OMI. Sé que solo deberías usarlos cuando trabajes con expresiones Linq, pero es molesto que no puedas declararlos fuera del ámbito local.
Desde MSDN:
var solo se puede usar cuando se declara e inicializa una variable local en la misma declaración; la variable no se puede inicializar como nula, ni en un grupo de métodos ni en una función anónima.
var no se puede utilizar en campos en el ámbito de la clase.
Las variables declaradas mediante var no se pueden utilizar en la expresión de inicialización. En otras palabras, esta expresión es legal: int i = (i = 20); pero esta expresión produce un error en tiempo de compilación: var i = (i = 20);
No se pueden inicializar varias variables de tipo implícito en la misma instrucción.
Si un tipo llamado var está dentro del alcance, entonces la palabra clave var se resolverá en ese nombre de tipo y no se tratará como parte de una declaración de variable local escrita implícitamente.
La razón por la que creo que es una implementación deficiente es que la llaman var, pero está muy lejos de ser una variante. En realidad, es solo una sintaxis abreviada para no tener que escribir el nombre completo de la clase (excepto cuando se usa con Linq)
Respuestas:
Estoy de acuerdo enfáticamente con esta publicación (para aquellos que hacen popó por la falta de ToString, hay un atributo de depuración para proporcionar un formato personalizado para su clase).
En la parte superior de la lista anterior, también agregaría las siguientes solicitudes razonables:
T : new(string)
o dondeT : new(string, int)
var e = new Foo(); e { Bar = baz };
Either<T>
" no lo es, así que me encantaría alguna forma de declarar un tipo algebraico cerrado y hacer cumplir una coincidencia de patrones exhaustiva en él (básicamente soporte de primera clase para el patrón de visitante, pero mucho más eficiente); así que solo tome enumeraciones, extiéndalas con soporte exhaustivo de coincidencia de patrones y no permita casos no válidos,System.IO
clases, comoStream
, están algo mal diseñadas; cualquier interfaz que requiera algunas implementaciones para lanzarNotSupportedException
es un mal diseño,IList
debería ser mucho más simple de lo que es; de hecho, esto puede ser cierto para muchas de las interfaces de colección concretas, comoICollection
,INotifyPropertyChanged
, que toman el nombre del campo como una cadena; puede hacer esto usando un método de extensión que toma un lambda conMemberExpression
, es decir.() => Foo
, pero eso no es muy eficiente,nameof()
operador para nombres de miembros individuales, pero no funciona en genéricos (ennameof(T) == "T"
lugar del nombre del argumento de tipo real: aún debe hacerlotypeof(T).Name
)), ni le permite obtener una cadena de "ruta" , por ejemplo,nameof(this.ComplexProperty.Value) == "Value"
limitando sus posibles aplicaciones.IArithmetic
; También son posibles otras interfaces de operador compartidas útiles,readonly
palabra clave, y C # 6.0 agregó propiedades automáticas de solo lectura, aunque no es tan estricto como el verdadero soporte de lenguaje para tipos y valores inmutables.Supongo que es suficiente por ahora. Todas estas son irritaciones con las que me he encontrado la semana pasada. Probablemente podría continuar durante horas si realmente me lo propongo. C # 4.0 ya está agregando argumentos con nombre, opcionales y predeterminados, que apruebo enfáticamente.
Ahora, para una solicitud irrazonable:
¿Bastante por favor? :-)
fuente
List<T>
un millón de Ts. ¿Cómo propone que la instantánea se tome de manera eficiente? # 21: Use lareadonly
palabra clave ... si bien hay algunas buenas sugerencias aquí, en su mayoría son solo eso: sugerencias, no fallas de diseño.Reset()
método enIEnumerator<T>
fue un error (para los bloques de iteradores, la especificación del lenguaje incluso exige que esto arroje una excepción)IEnumerable<out T>
yFunc<in T, out TResult>
, pero no tipos concretos (comoList<T>
).ApplicationException
más bien cayó en desgracia, ¿fue un error?Contains
, entoncesAdd
), por lo que una colección que sincroniza distintas operaciones no es tan útilSystem.Collections.Concurrent
tipos , conTryAdd
,GetOrAdd
,TryRemove
, etc se añadieron en .NET Framework 4.0 - a pesar de los métodos que aceptan un delegado de la fábrica no garantizan la fábrica sólo se invoca una vez por llave.using
/lock
, quizás permitiéndoles compartir una sintaxis reutilizable (¿extensible?); puede simular esto regresandoIDisposable
y usandousing
, pero podría haber sido más claroFoo(SqlConnection! connection)
(que inyecta una verificación nula /throw
) sería agradable (en contraste con,int?
etc.)dynamic
, o puede habilitarlo asíforeach
expansión, lo que significa que anon-methods / lambdas capturan la variable única, en lugar de una por iteración (doloroso con threading / async / etc)fuente
ApplicationException
fue un error, no útil como esperaban. También dijeron queSystem.Exception
debería haber sidoabstract
.TextWriter es una clase base de StreamWriter. wtf?
Eso siempre me confunde al extremo.
fuente
Una pequeña mascota de C #: los constructores usan la sintaxis de C ++ / Java de tener el constructor con el mismo nombre que la clase.
New()
octor()
hubiera sido mucho mejor.Y claro, herramientas como coderush hacen que esto sea menos problemático para cambiar el nombre de las clases, pero desde un punto de vista de legibilidad, New () proporciona una gran claridad.
fuente
class Foo { new(int j) {i = j} int i; }
New
las palabras clave en mayúsculas están en contra de la convención), dudo en llamarlo un defecto de diseño. Querían atraer a los desarrolladores de C ++ / Java existentes, y podría decirse que tomar prestadas muchas convenciones sintácticas estúpidas y antiguas les ayudó a alcanzar su objetivo.No entiendo que no puedas hacer
donde T: nuevo (U)
Entonces declaras que el tipo genérico T tiene un constructor no predeterminado.
editar:
Quiero hacer esto:
public class A { public A(string text) { } } public class Gen<T> where T : new(string text) { }
fuente
Estoy realmente sorprendido de ser el primero en mencionar este:
Los conjuntos de datos con tipo de ADO.NET no exponen columnas que aceptan valores NULL como propiedades de tipos que aceptan valores NULL. Debería poder escribir esto:
int? i = myRec.Field; myRec.Field = null;
En cambio, tienes que escribir esto, que es simplemente estúpido:
int? i = (int?)myRec.IsFieldNull() ? (int?)null : myRec.Field; myRec.SetFieldNull();
Esto era molesto en .NET 2.0, y es aún más molesto ahora que tienes que usar jiggery-pokery como el anterior en tus agradables consultas LINQ.
También es molesto que el
Add<TableName>Row
método generado sea igualmente insensible a la noción de tipos que aceptan valores NULL. Tanto más cuanto que losTableAdapter
métodos generados no lo son.No hay muchas cosas en .NET que me hagan sentir como si el equipo de desarrollo dijera "Está bien, chicos, estamos lo suficientemente cerca, ¡envíelo!" Pero esto seguro que sí.
fuente
DBNull.Value
, cuando ennull
sí mismo hubiera sido perfectamente adecuado para representar NULL. Afortunadamente, LINQ-to-SQL solo usa null para NULL.Edición
5. Otra de mis molestias es cómo System.Reflection.BindingFlags, tiene diferentes usos dependiendo del método que estés usando. En FindFields, por ejemplo, ¿qué significa CreateInstance o SetField? Este es un caso en el que han sobrecargado el significado detrás de esta enumeración que es confusa.
fuente
No sé si iría tan lejos como para decir que es un defecto de diseño, pero sería realmente bueno si pudiera inferir una expresión lambda de la misma manera que puede en VB:
VB:
Dim a = Function(x) x * (x - 1)
C#
Sería bueno si pudiera hacer esto:
var a = x => x * (x - 1);
En lugar de tener que hacer esto:
Func<int, int> a = x => x * (x - 1);
Me doy cuenta de que no es mucho más largo, ¡pero en Code Golf cada personaje cuenta, maldita sea! ¿No tienen eso en cuenta cuando diseñan estos lenguajes de programación? :)
fuente
(int x) => x * (x -1);
puede significarFunc<int, int>
o puede significarExpression<Func<int, int>>
+
no está definido paraobject
.La clase System.Object :
Equals y GetHashCode: no todas las clases son comparables o se pueden usar con hash, deben moverse a una interfaz. Me viene a la mente IEquatable o IComparable (o similar).
ToString: no todas las clases se pueden convertir en una cadena, deben moverse a una interfaz. Me viene a la mente IFormattable (o similar).
La propiedad ICollection.SyncRoot :
Los genéricos deberían haber estado ahí desde el principio:
fuente
EqualityComparer<T>.Default
correctamente. Entonces ambosvar dict = new Dictionary<object, string>(EqualityComparer<object>.Default)
yvar dict = new Dictionary<object, string>()
usarán comparación / igualdad de referencia.EqualityComparer<T>.Default
hace. No es necesario comprobar en cada búsqueda. El comparador es una propiedad de laDictionary
instancia y cada unoDictionary
sabe cuál está usando.Una de las cosas que me irrita es la
Predicate<T> != Func<T, bool>
paradoja. Ambos son delegados de tipoT -> bool
y, sin embargo, no son compatibles con la asignación.fuente
Algunas personas (ISV) desean que pueda compilarlo en código de máquina en el momento de la compilación y vincularlo para crear un ejecutable nativo que no necesite el tiempo de ejecución de dotNet.
fuente
Sabemos mucho sobre las técnicas adecuadas de OO. Desacoplamiento, programación por contrato, evitar herencias indebidas, uso apropiado de excepciones, principal abierto / cerrado, sustituibilidad de Liskov, etc. Aún así, los marcos .Net no emplean las mejores prácticas.
Para mí, la falla más grande en el diseño de .Net no está sobre los hombros de gigantes; Promover paradigmas de programación menos que ideales para las masas de programadores que usan sus marcos. .
Si MS prestó atención a esto, el mundo de la ingeniería de software podría haber dado grandes saltos en términos de calidad, estabilidad y escalabilidad en esta década, pero lamentablemente, parece estar retrocediendo.
fuente
No me gusta la declaración de cambio de C #.
Me gustaria algo como esto
switch (a) { 1 : do_something; 2 : do_something_else; 3,4 : do_something_different; else : do_something_weird; }
Así que no más descansos (fáciles de olvidar) y la posibilidad de separar por comas diferentes valores.
fuente
switch
está fundamentalmente roto en todos los lenguajes que emulan la versión deliberadamente lisiada de C (¡optimizada para la velocidad!). A VB le va mucho mejor, pero todavía está a años luz de los lenguajes con coincidencia de patrones (Haskell, F #…).Eventos en C #, donde debe verificar explícitamente si hay oyentes. ¿No era ese el punto con los eventos, transmitir a quienquiera que estuviera allí? ¿Incluso si no hay ninguno?
fuente
El comportamiento horrible (y bastante invisible para la mayoría de la gente) O (N ^ 2) de los iteradores anidados / recursivos .
Estoy bastante destrozado de que lo sepan, sepan cómo solucionarlo, pero no se considera que tenga la prioridad suficiente para merecer su inclusión.
Trabajo con estructuras en forma de árbol todo el tiempo y tengo que corregir el código de personas inteligentes cuando inadvertidamente introducen operaciones muy costosas de esta manera.
La belleza de "yield foreach" es que la sintaxis más simple y fácil fomenta el código correcto y eficaz. Este es el "pozo del éxito" al que creo que deberían aspirar antes de agregar nuevas funciones para el éxito a largo plazo de la plataforma.
fuente
Algunas clases implementan interfaces pero no implementan muchos de los métodos de esa interfaz, por ejemplo Array implementa IList pero 4 de 9 métodos arrojan NotSupportedException http://msdn.microsoft.com/en-us/library/system.array_members .aspx
fuente
Miembros estáticos y tipos anidados en interfaces.
Esto es particularmente útil cuando un miembro de la interfaz tiene un parámetro de un tipo que es específico de la interfaz ( por ejemplo, an
enum
). Sería bueno anidar el tipo de enumeración en el tipo de interfaz.fuente
La naturaleza por defecto terriblemente peligrosa de los eventos. El hecho de que pueda llamar a un evento y estar en un estado inconsistente debido a la eliminación de suscriptores es simplemente horrible. Vea los excelentes artículos de Jon Skeet y Eric Lippert para leer más sobre el tema.
fuente
null
En todas partes.const
en ninguna parte.Las API son incoherentes, por ejemplo, la mutación de un arreglo devuelve
void
pero agregando a unStringBuffer
devuelve el mismo mutableStringBuffer
.Interfaces de la colección son incompatibles con las estructuras de datos inmutables, por ejemplo,
Add
enSystem.Collections.Generic.IList<_>
que no puede devolver un resultado.Sin escritura estructural, por lo que escribe en
System.Windows.Media.Effects.SamplingMode.Bilinear
lugar de soloBilinear
.IEnumerator
Interfaz mutable implementada por clases cuando debería ser inmutablestruct
.La igualdad y la comparación son un desastre: que tienes
System.IComparable
yEquals
pero luego también tienesSystem.IComparable<_>
,System.IEquatable
,System.Collections.IComparer
,System.Collections.IStructuralComparable
,System.Collections.IStructuralEquatable
,System.Collections.Generic.IComparer
ySystem.Collections.Generic.IEqualityComparer
.Las tuplas deben ser estructuras, pero las estructuras inhiben innecesariamente la eliminación de llamadas de cola, por lo que uno de los tipos de datos más comunes y fundamentales se asignará innecesariamente y destruirá el paralelismo escalable.
fuente
IEnumerator
?0 pluriempleo como enumeración
peculiaridades de la enumeración: http://blogs.msdn.com/abhinaba/archive/2007/01/09/more-peculiarites-of-enum.aspx
como lo ilustra este buen ejemplo: http://plus.kaist.ac.kr/~shoh/postgresql/Npgsql/apidocs/Npgsql.NpgsqlParameterCollection.Add_overload_3.html
mi sugerencia, haga un buen uso del signo "@":
en vez de:
si ((myVar y MyEnumName.ColorRed)! = 0)
utilizar esta:
si ((myVar y MyEnumName.ColorRed)! = @ 0)
fuente
Para agregar a la larga lista de buenos puntos ya mencionados por otros:
DateTime.Now == DateTime.Now
en la mayoría de los casos, pero no en todos.String
que es inmutable tiene un montón de opciones de construcción y manipulación, peroStringBuilder
(que es mutable) no.Monitor.Enter
yMonitor.Exit
deberían haber sido métodos de instancia, por lo que en lugar de crear un objeto específico para bloquearlo, podría usar un nuevoMonitor
y bloquearlo.Los destructores nunca deberían haber sido llamados destructores. La especificación ECMA los llama finalizadores, lo que es mucho menos confuso para la multitud de C ++, pero la especificación del lenguaje todavía se refiere a ellos como destructores.
fuente
DateTime.Now
es la condición de carrera más obvia del mundo, pero +1 para el restoLa forma en que usamos las propiedades me irrita a veces. Me gusta pensar en ellos como el equivalente de los métodos getFoo () y setFoo () de Java. Pero no lo son.
Si las Pautas de uso de propiedades establecen que las propiedades deben poder configurarse en cualquier orden para que la serialización funcione, entonces son inútiles para la validación en tiempo de establecimiento. Si viene de un entorno en el que le gusta evitar que un objeto se permita entrar en un estado no válido, entonces las propiedades no son su solución. A veces no veo cómo son mejores que los miembros públicos, ya que estamos tan limitados en el tipo de cosas que se supone que debemos hacer en las propiedades.
Con ese fin, siempre he deseado (esto es principalmente pensar en voz alta aquí, simplemente desearía poder hacer algo como esto) que pudiera extender la sintaxis de la propiedad de alguna manera. Imagina algo como esto:
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; } }
No estoy seguro de si eso es útil o cómo se vería acceder a ellos. Pero solo desearía poder hacer más con las propiedades y realmente tratarlas como métodos get y set.
fuente
Los métodos de extensión son agradables, pero son una forma fea de resolver problemas que podrían haberse resuelto de manera más limpia con mixins reales (mira ruby para ver de qué estoy hablando), sobre el tema de los mixins. Una forma realmente agradable de agregarlos al lenguaje hubiera sido permitir que los genéricos se usaran para la herencia. Esto le permite extender las clases existentes de una manera agradable orientada a objetos:
public class MyMixin<T> : T { // etc... }
esto se puede usar así para extender una cadena, por ejemplo:
var newMixin = new MyMixin<string>();
Es mucho más poderoso que los métodos de extensión porque le permite anular métodos, por ejemplo, envolverlos permitiendo una funcionalidad similar a AOP dentro del lenguaje.
Perdón por la perorata :-)
fuente
Microsoft no solucionará errores obvios en el marco y no proporcionará enlaces para que los usuarios finales puedan solucionarlos.
Además, no hay forma de aplicar parches binarios a los ejecutables de .NET en tiempo de ejecución y no hay forma de especificar versiones privadas de las bibliotecas de .NET framework sin parchear las bibliotecas nativas (para interceptar la llamada de carga), e ILDASM no es redistribuible, por lo que no puedo automatizar el parche de todos modos.
fuente
Poder invocar un método de extensión en una variable nula es discutible, por ejemplo
objeto a = nulo; a.MyExtMethod (); // esto es invocable, suponga que en algún lugar ha definido MyExtMethod
Podría ser útil pero es ambiguo en temas de excepción de referencia nula.
Un "defecto" de denominación. La 'C' de "configuración" en System.configuration.dll debe estar en mayúscula.
Manejo de excepciones. La excepción debe ser capturada a la fuerza o lanzada como en Java, el compilador debe verificarla en el momento de la compilación. Los usuarios no deben confiar en los comentarios para obtener información sobre excepciones dentro de la invocación de destino.
fuente
El método .Parameters.Add () en SqlCommand en V1 del marco se diseñó horriblemente; una de las sobrecargas básicamente no funcionaría si pasara un parámetro con un valor (int) de 0; esto los llevó a crear el método .Parameters.AddWithValue () en la clase SqlCommand.
fuente
ICollection<T>
yIList<T>
; como mínimo, una interfaz de recopilación covariante de solo lecturaIListSource<out T>
(con enumerador, indexador y recuento) hubiera sido extremadamente útil.Transform(Sequence<T>, Func<T,T>)
función que necesitaba determinar rápidamente si la función devuelve el mismo valor o un valor diferente. Si la función no modifica la mayoría / todos sus argumentos, entonces la secuencia de salida puede compartir parte / toda la memoria de la secuencia de entrada. Sin la capacidad de comparar bit a bit cualquier tipo de valor T, se debe usar una comparación mucho más lenta, lo que perjudica enormemente el rendimiento.List<T>
a un hipotéticoIListSource<U>
(donde T: U) incluso aunque la clase no implemente explícitamente esa interfaz. Hay al menos tres bibliotecas diferentes (escritas de forma independiente) para proporcionar esta funcionalidad (con inconvenientes de rendimiento, por supuesto; si fuera posible una solución perfecta, no sería justo llamarlo un defecto en .NET).WeakReference<T>
(puede escribir fácilmente el suyo, pero usará conversiones internamente).Predicate<T>
vsFunc<T,bool>
). A menudo desearía que pudiéramos tener tipificación estructural para interfaces y delegados, para lograr un acoplamiento más flexible entre componentes, porque en .NET no es suficiente que las clases en DLL independientes implementen la misma interfaz; también deben compartir una referencia común a una tercera DLL que define la interfaz.DBNull.Value
existe aunquenull
hubiera servido igualmente bien para el mismo propósito.variable = variable ?? value
. De hecho, hay algunos lugares en C # que carecen innecesariamente de simetría. Por ejemplo, puede escribirif (x) y(); else z();
(sin llaves), pero no puede escribirtry y(); finally z();
.fuente
IList<T>
, aunque usaríaIReadableByIndex<out T>
yIAppendable<in T>
. Muchas de tus otras cosas son graznidos con los que también estoy de acuerdo.IListReader<T>
;) - Utilizo la palabra "fuente" como antónimo de "sumidero" (una interfaz de solo escritura).IListSource<in T>
oIReadableList<out T>
. Puede ser valioso que los tipos de interfaz base incluyan métodos que no existen en todas las derivadas, aunque creo que a menudo es bueno tener interfaces algo especializadas. Por ejemplo, uno podría tener unIList<T>
que contiene métodos de cambio de tamaño que pueden funcionar o no, y unoIResizableList<T>
que implementa los mismos métodos, pero garantiza que deberían funcionar. Este enfoque puede ser útil en los casos en que un campo puede contener la única referencia existente a una lista mutable, o una referencia compartida a una inmutable.if (rl:(list as IResizableList<T>) != null) rl.Add(...);
, pero hay otras propuestas. Como autor de varias colecciones y adaptadores de colecciones, lo que me molesta es escribir muchos métodos ficticios que arrojan excepciones. Como fanático de la seguridad de tipos, no quiero que se me permita llamar a métodos ilegales. Un fan de IntelliSense, no quiero verlos en la lista.Una cosa que me molestó en 1.x fue que cuando uso el
System.Xml.XmlValidatingReader
, elValidationEventHandler
'sValidationEventArgs
no expone el subyacenteXmlSchemaException
(marcado interno) que tiene toda la información útil comolinenumber
yposition
. En su lugar, se espera que analice esto fuera de la propiedad de la cadena de mensaje o use la reflexión para desenterrarlo. No es tan bueno cuando desea devolver un error más desinfectado al usuario final.fuente
No me gusta que no pueda usar los valores de una enumeración en otra enumeración, por ejemplo:
enum Colors { white, blue, green, red, black, yellow } enum SpecialColors { Colors.blue, Colors.red, Colors.Yellow }
fuente
typeof(Color)
! =typeof(SpecialColors)
.enum SpecialColors { blue = Colors.blue, red = Colors.red, yellow = Colors.Yellow }
Las variables tipadas implícitamente se implementaron de manera deficiente en la OMI. Sé que solo deberías usarlos cuando trabajes con expresiones Linq, pero es molesto que no puedas declararlos fuera del ámbito local.
Desde MSDN:
La razón por la que creo que es una implementación deficiente es que la llaman var, pero está muy lejos de ser una variante. En realidad, es solo una sintaxis abreviada para no tener que escribir el nombre completo de la clase (excepto cuando se usa con Linq)
fuente