¿Cuáles son algunas de las mayores fallas de diseño en C # o .NET Framework en general?
Ejemplo: no hay un tipo de cadena que no acepte valores NULL y debe verificar DBNull al obtener valores de un IDataReader.
¿Cuáles son algunas de las mayores fallas de diseño en C # o .NET Framework en general?
Ejemplo: no hay un tipo de cadena que no acepte valores NULL y debe verificar DBNull al obtener valores de un IDataReader.
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.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,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,
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.IArithmetic; También son posibles otras interfaces de operador compartidas útiles,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.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? :-)
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.
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)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?Contains, entonces Add), por lo que una colección que sincroniza distintas operaciones no es tan útil
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.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 claroFoo(SqlConnection! connection)(que inyecta una verificación nula / throw) sería agradable (en contraste con, int?etc.)
dynamic, o puede habilitarlo así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)
ApplicationExceptionfue un error, no útil como esperaban. También dijeron que System.Exceptiondebería haber sido abstract.
TextWriter es una clase base de StreamWriter. wtf?
Eso siempre me confunde al extremo.
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.
class Foo { new(int j) {i = j} int i; }
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.
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)
{
}
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í.
DBNull.Value, cuando en nullsí 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.
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? :)
(int x) => x * (x -1);puede significar Func<int, int>o puede significarExpression<Func<int, int>>
+no está definido para object.
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:
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.
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.
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.
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.
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.
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.
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 #…).
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?
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.
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
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.
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.
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.
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)
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.
DateTime.Nowes la condición de carrera más obvia del mundo, pero +1 para el resto
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:
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.
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 :-)
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.
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.
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.
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.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é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).WeakReference<T>(puede escribir fácilmente el suyo, pero usará conversiones internamente).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.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();.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.
IListReader<T>;) - Utilizo la palabra "fuente" como antónimo de "sumidero" (una interfaz de solo escritura).
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.
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, 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.
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 }
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)