Recientemente estuve trabajando con un DateTime
objeto y escribí algo como esto:
DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?
La documentación de intellisense AddDays()
dice que agrega un día a la fecha, lo cual no es así; en realidad, devuelve una fecha con un día agregado, por lo que debe escribirlo como:
DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt; // tomorrow's date
Este me ha mordido varias veces antes, así que pensé que sería útil catalogar las peores trampas de C #.
Respuestas:
Blammo Su aplicación se bloquea sin seguimiento de pila. Pasa todo el tiempo.
(Observe el capital en
MyVar
lugar de minúsculasmyVar
en el captador).fuente
Type.GetType
El que he visto morder a mucha gente es
Type.GetType(string)
. Se preguntan por qué funciona para los tipos en su propio ensamblaje, y algunos tipos les gustaSystem.String
, pero noSystem.Windows.Forms.Form
. La respuesta es que solo se ve en el ensamblaje actual y enmscorlib
.Métodos anónimos
C # 2.0 introdujo métodos anónimos, lo que lleva a situaciones desagradables como esta:
¿Qué imprimirá eso? Bueno, depende completamente de la programación. Imprimirá 10 números, pero probablemente no imprimirá 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, que es lo que puede esperar. El problema es que es la
i
variable que se ha capturado, no su valor en el momento de la creación del delegado. Esto se puede resolver fácilmente con una variable local adicional del alcance correcto:Ejecución diferida de bloques iteradores
Esta "prueba de unidad del pobre" no pasa, ¿por qué no?
La respuesta es que el código dentro de la fuente del
CapitalLetters
código no se ejecuta hastaMoveNext()
que se llama por primera vez al método del iterador .Tengo algunas otras rarezas en mi página de acertijos .
fuente
Relanzando excepciones
Un truco que tiene muchos desarrolladores nuevos es la semántica de excepción de relanzamiento.
Mucho tiempo veo código como el siguiente
El problema es que borra el seguimiento de la pila y hace que el diagnóstico de problemas sea mucho más difícil, ya que no puede rastrear dónde se originó la excepción.
El código correcto es la instrucción throw sin argumentos:
O envolviendo la excepción en otra, y usando la excepción interna para obtener el seguimiento de la pila original:
fuente
La ventana de vigilancia de Heisenberg
Esto puede morderte mal si estás haciendo cosas de carga a pedido, como esta:
Ahora digamos que tiene algún código en otro lugar usando esto:
Ahora quieres depurar tu
CreateMyObj()
método. Entonces pones un punto de interrupción en la línea 3 anterior, con la intención de ingresar al código. Solo por si acaso, también pones un punto de interrupción en la línea de arriba que dice_myObj = CreateMyObj();
, e incluso un punto de interrupción dentro deCreateMyObj()
sí mismo.El código llega a su punto de interrupción en la línea 3. Entras en el código. Espera ingresar el código condicional, porque
_myObj
obviamente es nulo, ¿verdad? Uh ... entonces ... ¿por qué se saltó la condición y se fue directamentereturn _myObj
? Pasa el mouse sobre _myObj ... y, de hecho, ¡tiene un valor! ¡¿Cómo pasó eso?!La respuesta es que su IDE provocó que obtuviera un valor, porque tiene abierta una ventana de "observación", especialmente la ventana de observación "Autos", que muestra los valores de todas las variables / propiedades relevantes para la línea de ejecución actual o anterior. Cuando llegaste a tu punto de interrupción en la línea 3, la ventana del reloj decidió que te interesaría saber el valor de
MyObj
, por lo que detrás de escena, ignorando cualquiera de tus puntos de interrupción , se fue y calculó el valorMyObj
para ti, incluida la llamada aCreateMyObj()
ese establece el valor de _myObj!Es por eso que llamo a esto la Ventana de vigilancia de Heisenberg: no se puede observar el valor sin afectarlo ... :)
GOTCHA!
Editar : creo que el comentario de @ ChristianHayter merece ser incluido en la respuesta principal, porque parece una solución efectiva para este problema. Entonces, cada vez que tenga una propiedad con carga lenta ...
fuente
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
o[DebuggerDisplay("<loaded on demand>")]
.Lazy<T>
clase (en particular por suValue
propiedad) es un ejemplo de dónde se usa esto.ToString
. Cada vez que se cernía sobre él, la información sobre herramientas le daba un valor diferente: no podía entenderlo ...Aquí hay otro momento que me atrapa:
TimeSpan.Seconds es la porción de segundos del intervalo de tiempo (2 minutos y 0 segundos tiene un valor de segundos de 0).
TimeSpan.TotalSeconds es el intervalo de tiempo completo medido en segundos (2 minutos tiene un valor total de segundos de 120).
fuente
TimeSpan
incluso tiene unaSeconds
propiedad. ¿Quién le importa a una rata cuál es la porción de segundos de un intervalo de tiempo, de todos modos? Es un valor arbitrario, dependiente de la unidad; No puedo concebir ningún uso práctico para ello.SecondsPart
ySecondsTotal
para distinguir los dos.Pérdida de memoria porque no desenganchó eventos.
Esto incluso atrapó a algunos desarrolladores senior que conozco.
Imagine un formulario WPF con muchas cosas en él, y en algún lugar allí se suscribe a un evento. Si no cancela la suscripción, todo el formulario se guarda en la memoria después de cerrarse y desreferenciarse.
¡Creo que el problema que vi fue crear un DispatchTimer en el formulario WPF y suscribirse al evento Tick, si no haces un - = en el temporizador tu formulario pierde memoria!
En este ejemplo, su código de desmontaje debería tener
Este es especialmente complicado ya que creó la instancia del DispatchTimer dentro del formulario WPF, por lo que podría pensar que sería una referencia interna manejada por el proceso de Recolección de Basura ... desafortunadamente, el DispatchTimer usa una lista interna estática de suscripciones y servicios solicitudes en el subproceso de la interfaz de usuario, por lo que la referencia es 'propiedad' de la clase estática.
fuente
timer.Tick += (s, e,) => { Console.WriteLine(s); }
Tal vez no sea realmente un problema porque el comportamiento está escrito claramente en MSDN, pero me rompió el cuello una vez porque lo encontré bastante contra-intuitivo:
Este tipo deja el
"nice.pic"
archivo bloqueado hasta que se elimine la imagen. En el momento en que lo enfrenté, pensé que sería bueno cargar íconos sobre la marcha y no me di cuenta (al principio) de que terminé con docenas de archivos abiertos y bloqueados. La imagen realiza un seguimiento de dónde había cargado el archivo ...¿Cómo resolver esto? Pensé que un trazador de líneas haría el trabajo. Esperaba un parámetro adicional para
FromFile()
, pero no tenía ninguno, así que escribí esto ...fuente
Si cuenta ASP.NET, diría que el ciclo de vida de los formularios web es un gran problema para mí. He pasado innumerables horas depurando código de formularios web mal escrito, solo porque muchos desarrolladores simplemente no entienden cuándo usar qué controlador de eventos (incluido yo, lamentablemente).
fuente
sobrecargado == operadores y contenedores sin tipo (listas de matrices, conjuntos de datos, etc.):
Soluciones?
siempre use
string.Equals(a, b)
cuando compare tipos de cadenasusando genéricos como
List<string>
para asegurar que ambos operandos sean cadenas.fuente
Moraleja de la historia: los inicializadores de campo no se ejecutan al deserializar un objeto
fuente
DateTime.ToString ("dd / MM / aaaa") ; En realidad, esto no siempre le dará dd / MM / aaaa, sino que tendrá en cuenta la configuración regional y reemplazará su separador de fecha dependiendo de dónde se encuentre. Entonces puede obtener dd-MM-aaaa o algo similar.
La forma correcta de hacer esto es usar DateTime.ToString ("dd '/' MM '/' aaaa");
Se supone que DateTime.ToString ("r") se convierte a RFC1123, que usa GMT. GMT está dentro de una fracción de segundo desde UTC, y sin embargo, el especificador de formato "r" no se convierte a UTC , incluso si el DateTime en cuestión se especifica como Local.
Esto da como resultado el siguiente problema (varía según qué tan lejos esté su hora local de UTC):
Whoops!
fuente
DateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
Vi este publicado el otro día, y creo que es bastante oscuro y doloroso para aquellos que no saben
Como eso devolverá 0 y no 1 como la mayoría esperaría
fuente
Llego un poco tarde a esta fiesta, pero tengo dos problemas que me han mordido recientemente:
Resolución de fecha y hora
La propiedad Ticks mide el tiempo en 10 millonésimas de segundo (bloques de 100 nanosegundos), sin embargo, la resolución no es de 100 nanosegundos, es de aproximadamente 15 ms.
Este código:
le dará una salida de (por ejemplo):
Del mismo modo, si observa DateTime.Now.Millisecond, obtendrá valores en fragmentos redondeados de 15.625 ms: 15, 31, 46, etc.
Este comportamiento particular varía de un sistema a otro , pero hay otros problemas relacionados con la resolución en esta API de fecha / hora.
Ruta combinada
Es una excelente manera de combinar rutas de archivos, pero no siempre se comporta de la manera que cabría esperar.
Si el segundo parámetro comienza con un
\
carácter, no le dará una ruta completa:Este código:
Te da esta salida:
fuente
CD
comando para ver lo que quiero decir ... 1) Ir aC:\Windows\System32
2) TipoCD \Users
3) ¡Woah! Ahora estás enC:\Users
... ¿Lo tienes ? ... Path.Combine (@ "C: \ Windows \ System32", @ "\ Users") debería volver, lo\Users
que significa precisamente el[current_drive_here]:\Users
Cuando comienzas un proceso (usando System.Diagnostics) que escribe en la consola, pero nunca lees la consola. Fuera de la transmisión, después de una cierta cantidad de salida, tu aplicación parecerá bloquearse.
fuente
No hay atajos de operador en Linq-To-Sql
Ver aquí .
En resumen, dentro de la cláusula condicional de una consulta Linq-To-Sql, no puede usar atajos condicionales como
||
y&&
para evitar excepciones de referencia nula; ¡Linq-To-Sql evalúa ambos lados del operador OR o AND incluso si la primera condición elimina la necesidad de evaluar la segunda condición!fuente
Usar parámetros predeterminados con métodos virtuales
fuente
Base
, ¿de dónde debería obtener el compilador el valor predeterminadoBase
? Pensé que es un poco más complicado que el valor predeterminado pueda ser diferente si el tipo declarado es el tipo derivado , aunque el método llamado (estáticamente) es el método base.Valorar objetos en colecciones mutables
no tiene efecto.
mypoints[i]
devuelve una copia de unPoint
objeto de valor. C # felizmente le permite modificar un campo de la copia. Silenciosamente no haciendo nada.Actualización: esto parece solucionarse en C # 3.0:
fuente
arr[i].attr=
es una sintaxis especial para matrices que no puede codificar en contenedores de biblioteca; (. ¿Por qué está permitido (<value expression>). attr = <expr>? ¿Puede tener sentido?Quizás no sea lo peor, pero algunas partes del marco .net usan grados, mientras que otras usan radianes (y la documentación que aparece con Intellisense nunca te dice cuál, debes visitar MSDN para averiguarlo)
Todo esto podría haberse evitado teniendo una
Angle
clase en su lugar ...fuente
Para los programadores de C / C ++, la transición a C # es natural. Sin embargo, el mayor problema con el que me he encontrado personalmente (y he visto con otros que hacen la misma transición) no es comprender completamente la diferencia entre clases y estructuras en C #.
En C ++, las clases y las estructuras son idénticas; solo difieren en la visibilidad predeterminada, donde las clases usan la visibilidad privada y las estructuras usan la visibilidad pública. En C ++, esta definición de clase
es funcionalmente equivalente a esta definición de estructura.
Sin embargo, en C #, las clases son tipos de referencia, mientras que las estructuras son tipos de valor. Esto hace una GRAN diferencia en (1) decidir cuándo usar uno sobre el otro, (2) probar la igualdad de objetos, (3) el rendimiento (por ejemplo, boxing / unboxing), etc.
Hay todo tipo de información en la web relacionada con las diferencias entre los dos (por ejemplo, aquí ). Recomiendo encarecidamente que cualquiera que haga la transición a C # tenga al menos un conocimiento práctico de las diferencias y sus implicaciones.
fuente
Recolección de basura y desechar (). Aunque no tiene que hacer nada para liberar memoria , aún tiene que liberar recursos a través de Dispose (). Esto es inmensamente fácil de olvidar cuando usa WinForms o rastrea objetos de cualquier manera.
fuente
Implementar matrices
IList
Pero no lo implementes. Cuando llamas a Agregar, te dice que no funciona. Entonces, ¿por qué una clase implementa una interfaz cuando no puede soportarla?
Compila, pero no funciona:
Tenemos mucho este problema, porque el serializador (WCF) convierte todas las ILists en matrices y obtenemos errores de tiempo de ejecución.
fuente
IEnumerable<T>
yIEnumerator<T>
admitir unaFeatures
propiedad, así como algunos métodos "opcionales" cuya utilidad estaría determinada por lo que reportaron las "Características". Sin embargo, mantengo mi punto principal, que es que hay casos en los que el código que recibe unIEnumerable<T>
necesitará promesas más fuertes que las queIEnumerable<T>
ofrece. LlamarToList
produciría unaIEnumerable<T>
que cumpla tales promesas, pero en muchos casos sería innecesariamente costosa. Yo diría que debería haber ...IEnumerable<T>
podría hacer una copia de los contenidos si fuera necesario, pero podría abstenerse de hacerlo innecesariamente.foreach bucles variables alcance!
imprime cinco "amet", mientras que el siguiente ejemplo funciona bien
fuente
MS SQL Server no puede manejar fechas anteriores a 1753. Significativamente, eso no está sincronizado con la
DateTime.MinDate
constante .NET , que es 1/1/1. Entonces, si intenta guardar una fecha mínima, una fecha con formato incorrecto (como me sucedió recientemente en una importación de datos) o simplemente la fecha de nacimiento de William the Conqueror, tendrá problemas. No hay una solución integrada para esto; Si es probable que necesite trabajar con fechas anteriores a 1753, debe escribir su propia solución.fuente
The Nasty Linq Caching Gotcha
Vea mi pregunta que condujo a este descubrimiento, y al blogger que descubrió el problema.
En resumen, el DataContext mantiene un caché de todos los objetos Linq-to-Sql que alguna vez haya cargado. Si alguien más realiza algún cambio en un registro que haya cargado previamente, no podrá obtener los datos más recientes, ¡ incluso si vuelve a cargar el registro explícitamente!
Esto se debe a una propiedad llamada
ObjectTrackingEnabled
en el DataContext, que por defecto es verdadera. Si establece esa propiedad en falso, el registro se cargará de nuevo cada vez ... PERO ... no puede persistir ningún cambio en ese registro con SubmitChanges ().GOTCHA!
fuente
El contrato en Stream.Read es algo que he visto tropezar con mucha gente:
La razón por la que esto es incorrecto es que
Stream.Read
leerá como máximo el número especificado de bytes, pero es completamente libre de leer solo 1 byte, incluso si hay otros 7 bytes disponibles antes del final de la secuencia.No ayuda que esto se parezca tanto a
Stream.Write
que se garantiza que ha escrito todos los bytes si regresa sin excepción. Tampoco ayuda que el código anterior funcione casi todo el tiempo . Y, por supuesto, no ayuda que no haya un método conveniente y listo para leer exactamente N bytes correctamente.Entonces, para tapar el agujero y aumentar la conciencia de esto, aquí hay un ejemplo de una forma correcta de hacer esto:
fuente
var r = new BinaryReader(stream); ulong data = r.ReadUInt64();
. BinaryReader también tiene unFillBuffer
método ...Eventos
Nunca entendí por qué los eventos son una característica del lenguaje. Son complicados de usar: debe verificar si hay nulos antes de llamar, debe anular el registro (usted mismo), no puede averiguar quién está registrado (por ejemplo: ¿me registré?). ¿Por qué un evento no es solo una clase en la biblioteca? Básicamente un especializado
List<delegate>
?fuente
button.Click += (s, e) => { Console.WriteLine(s); }
?IEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);});
y darme de baja a través declickSubscription.Dispose();
. Si mi objeto mantendría todas las suscripciones durante toda su vida útil,MySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);}));
y luegoMySubscriptions.Dispose()
eliminaría todas las suscripciones.Hoy arreglé un error que eludió por mucho tiempo. El error estaba en una clase genérica que se usaba en un escenario de subprocesos múltiples y se usaba un campo int estático para proporcionar sincronización sin bloqueo usando Interlocked. El error fue causado porque cada instanciación de la clase genérica para un tipo tiene su propia estática. Entonces, cada subproceso tiene su propio campo estático y no se usó un bloqueo como se esperaba.
Esto imprime 5 10 5
fuente
i
tuviera el tipoT
.Type
.SomeGeneric<int>
es un tipo diferente deSomeGeneric<string>
; así que, por supuesto, cada uno tiene su propiopublic static int i
Los enumerables se pueden evaluar más de una vez
Te morderá cuando tengas un enumerable vagamente enumerado y lo repitas dos veces y obtengas resultados diferentes. (u obtiene los mismos resultados pero se ejecuta dos veces innecesariamente)
Por ejemplo, mientras escribía una prueba determinada, necesitaba algunos archivos temporales para probar la lógica:
Imagina mi sorpresa cuando
File.Delete(file)
lanzaFileNotFound
!!Lo que sucede aquí es que el
files
enumerable se repitió dos veces (los resultados de la primera iteración simplemente no se recuerdan) y en cada nueva iteración volvería a llamarPath.GetTempFilename()
para que obtenga un conjunto diferente de nombres de archivos temporales.La solución es, por supuesto, enumerar ansiosamente el valor usando
ToArray()
oToList()
:Esto es aún más aterrador cuando haces algo con varios subprocesos, como:
y descubres que
content.Length
sigue siendo 0 después de todas las escrituras !! Luego, comienzas a comprobar rigurosamente que no tienes una condición de carrera cuando ... después de una hora desperdiciada ... descubriste que es solo esa pequeña cosa de Enumerable que olvidaste ...fuente
Acabo de encontrar uno extraño que me tuvo atrapado en la depuración por un tiempo:
Puede incrementar nulo para un int anulable sin lanzar una excepción y el valor permanece nulo.
fuente
Sí, este comportamiento está documentado, pero eso ciertamente no lo hace correcto.
fuente