typeparam see remarks example cref c# .net

see - summary example c#



¿Cuál es el peor gotcha en C#o.NET? (30)

Recientemente estuve trabajando con un objeto DateTime y escribí algo como esto:

DateTime dt = DateTime.Now; dt.AddDays(1); return dt; // still today''s date! WTF?

La documentación de intellisense para 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 así:

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 los peores errores de C #.


Relanzamiento de excepciones.

Un gotcha que recibe muchos nuevos desarrolladores es la semántica de excepción de relanzamiento.

Mucho tiempo veo código como el siguiente

catch(Exception e) { // Do stuff throw e; }

El problema es que borra el seguimiento de la pila y hace que el diagnóstico de los problemas sea mucho más difícil, ya que no se puede rastrear dónde se originó la excepción.

El código correcto es la declaración de lanzamiento sin argumentos:

catch(Exception) { throw; }

O envuelva la excepción en otra, y use la excepción interna para obtener el seguimiento de la pila original:

catch(Exception e) { // Do stuff throw new MySpecialException(e); }


Valorar objetos en colecciones mutables.

struct Point { ... } List<Point> mypoints = ...; mypoints[i].x = 10;

no tiene efecto.

mypoints[i] devuelve una copia de un objeto de valor de Point . C # felizmente te permite modificar un campo de la copia. Silenciosamente no haciendo nada.

Actualización: Esto parece estar arreglado en C # 3.0:

Cannot modify the return value of ''System.Collections.Generic.List<Foo>.this[int]'' because it is not a variable


Aquí hay otra vez una que me atrapa:

static void PrintHowLong(DateTime a, DateTime b) { TimeSpan span = a - b; Console.WriteLine(span.Seconds); // WRONG! Console.WriteLine(span.TotalSeconds); // RIGHT! }

TimeSpan.Seconds es la parte de segundos del intervalo de tiempo (2 minutos y 0 segundos tiene un valor de segundos de 0).

TimeSpan.TotalSeconds es el tiempo total medido en segundos (2 minutos tiene un valor total de segundos de 120).


Cuando inicias un proceso (utilizando System.Diagnostics) que escribe en la consola, pero nunca lees la secuencia Console.Out, después de cierta cantidad de resultados, tu aplicación parecerá que se cuelga.


Fuga de memoria porque no desenganchó eventos.

Esto incluso sorprendió 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 guardará en la memoria después de que se haya cerrado y eliminado la referencia.

Creo que el problema que vi fue crear un DispatchTimer en el formulario WPF y suscribirse al evento Tick, si no hace un - = en el temporizador, su formulario pierde memoria.

En este ejemplo, su código de desmontaje debería tener

timer.Tick -= TimerTickEventHandler;

Este es especialmente complicado ya que creó la instancia de DispatchTimer dentro del formulario WPF, por lo que pensaría 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.


Llego un poco tarde a esta fiesta, pero tengo dos errores 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 codigo

long now = DateTime.Now.Ticks; for (int i = 0; i < 10; i++) { System.Threading.Thread.Sleep(1); Console.WriteLine(DateTime.Now.Ticks - now); }

le dará una salida de (por ejemplo):

0 0 0 0 0 0 0 156254 156254 156254

De manera similar, si observa DateTime.Now.Millisecond, obtendrá valores en trozos redondeados de 15.625 ms: 15, 31, 46, etc.

Este comportamiento particular varía de un sistema a otro , pero hay otros errores relacionados con la resolución en esta API de fecha / hora.

Path.Combine

Una excelente manera de combinar rutas de archivos, pero no siempre se comporta de la manera que usted espera.

Si el segundo parámetro comienza con un carácter / , no le dará una ruta completa:

Este codigo

string prefix1 = "C://MyFolder//MySubFolder"; string prefix2 = "C://MyFolder//MySubFolder//"; string suffix1 = "log//"; string suffix2 = "//log//"; Console.WriteLine(Path.Combine(prefix1, suffix1)); Console.WriteLine(Path.Combine(prefix1, suffix2)); Console.WriteLine(Path.Combine(prefix2, suffix1)); Console.WriteLine(Path.Combine(prefix2, suffix2));

Te da esta salida:

C:/MyFolder/MySubFolder/log/ /log/ C:/MyFolder/MySubFolder/log/ /log/


Para los programadores de C / C ++, la transición a C # es natural. Sin embargo, el problema más grande con el que me he encontrado personalmente (y que he visto con otros que hacen la misma transición) no es entender completamente la diferencia entre clases y estructuras en C #.

En C ++, las clases y las estructuras son idénticas; solo difieren en la visibilidad por defecto, donde las clases por defecto a la visibilidad privada y las estructuras por defecto a la visibilidad pública. En C ++, esta definición de clase.

class A { public: int i; };

es funcionalmente equivalente a esta definición de estructura.

struct A { int i; };

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, boxeo / desempaquetado), etc.

Hay todo tipo de información en la web relacionada con las diferencias entre los dos (por ejemplo, here ). Recomendaría encarecidamente a todos los que realicen la transición a C # que tengan al menos un conocimiento práctico de las diferencias y sus implicaciones.


Quizás no sea lo peor, pero algunas partes del marco .net usan grados, mientras que otras usan radians (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 clase de Angle lugar ...


Si cuenta ASP.NET, diría que el ciclo de vida de los formularios web es muy importante para mí. He pasado innumerables horas depurando código de formularios web mal escritos, solo porque muchos desarrolladores simplemente no entienden cuándo usar qué controlador de eventos (incluido yo, por desgracia).


Sobrecargados == operadores y contenedores sin tipo (arrailistas, conjuntos de datos, etc.):

string my = "my "; Debug.Assert(my+"string" == "my string"); //true var a = new ArrayList(); a.Add(my+"string"); a.Add("my string"); // uses ==(object) instead of ==(string) Debug.Assert(a[1] == "my string"); // true, due to interning magic Debug.Assert(a[0] == "my string"); // false

Soluciones?

  • siempre use string.Equals(a, b) cuando esté comparando tipos de string

  • usando genéricos como List<string> para asegurar que ambos operandos sean cadenas.


Tal vez no sea realmente una broma porque el comportamiento está escrito claramente en MSDN, pero me rompió el cuello una vez porque lo encontré bastante contraintuitivo:

Image image = System.Drawing.Image.FromFile("nice.pic");

Este tipo deja el archivo "nice.pic" bloqueado hasta que se desecha 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 mantiene un registro de donde había cargado el archivo desde ...

¿Cómo resolver esto? Pensé que una sola línea haría el trabajo. Esperaba un parámetro extra para FromFile() , pero no tenía ninguno, así que escribí esto ...

using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read)) { image = System.Drawing.Image.FromStream(fs); }


Vi este publicado el otro día, y creo que es bastante oscuro y doloroso para aquellos que no saben

int x = 0; x = x++; return x;

Como eso devolverá 0 y no 1 como la mayoría esperaría


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. Así que podrías obtener dd-MM-yyyy o algo parecido.

La forma correcta de hacer esto es usar DateTime.ToString ("dd ''/'' MM ''/'' yyyy");

Se supone que DateTime.ToString ("r") se convierte a RFC1123, que utiliza GMT. GMT está dentro de una fracción de segundo de 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 la siguiente gotcha (varía según la distancia a la que se encuentre su hora local de UTC):

DateTime.Parse("Tue, 06 Sep 2011 16:35:12 GMT").ToString("r") > "Tue, 06 Sep 2011 17:35:12 GMT"

Whoops!


El contrato en Stream.Read es algo que he visto hacer tropezar a mucha gente:

// Read 8 bytes and turn them into a ulong byte[] data = new byte[8]; stream.Read(data, 0, 8); // <-- WRONG! ulong data = BitConverter.ToUInt64(data);

La razón por la que esto es incorrecto es que Stream.Readleerá a lo sumo el número especificado de bytes, pero es completamente libre de leer solo 1 byte, incluso si otros 7 bytes están disponibles antes del final de la transmisión.

No ayuda que se vea tan similar a Stream.Write, lo que se garantiza que haya 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:

/// <summary> /// Attempts to fill the buffer with the specified number of bytes from the /// stream. If there are fewer bytes left in the stream than requested then /// all available bytes will be read into the buffer. /// </summary> /// <param name="stream">Stream to read from.</param> /// <param name="buffer">Buffer to write the bytes to.</param> /// <param name="offset">Offset at which to write the first byte read from /// the stream.</param> /// <param name="length">Number of bytes to read from the stream.</param> /// <returns>Number of bytes read from the stream into buffer. This may be /// less than requested, but only if the stream ended before the /// required number of bytes were read.</returns> public static int FillBuffer(this Stream stream, byte[] buffer, int offset, int length) { int totalRead = 0; while (length > 0) { var read = stream.Read(buffer, offset, length); if (read == 0) return totalRead; offset += read; length -= read; totalRead += read; } return totalRead; } /// <summary> /// Attempts to read the specified number of bytes from the stream. If /// there are fewer bytes left before the end of the stream, a shorter /// (possibly empty) array is returned. /// </summary> /// <param name="stream">Stream to read from.</param> /// <param name="length">Number of bytes to read from the stream.</param> public static byte[] Read(this Stream stream, int length) { byte[] buf = new byte[length]; int read = stream.FillBuffer(buf, 0, length); if (read < length) Array.Resize(ref buf, read); return buf; }


El desagradable Linq Caching Gotcha

Vea mi pregunta que condujo a este descubrimiento y el blogger que descubrió el problema.

En resumen, el DataContext mantiene un caché de todos los objetos Linq-to-Sql que haya cargado. Si alguien más realiza algún cambio en un registro que usted haya cargado previamente, no podrá obtener los últimos datos, incluso si recarga el registro explícitamente.

Esto se debe a una propiedad llamada ObjectTrackingEnableden 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!


Enumerables se pueden evaluar más de una vez

Te morderá cuando tengas un enumerable enumerado de manera perezosa y lo repitas dos veces y obtengas resultados diferentes. (o obtienes los mismos resultados pero se ejecuta dos veces innecesariamente)

Por ejemplo, mientras escribía una cierta prueba, necesitaba algunos archivos temporales para probar la lógica:

var files = Enumerable.Range(0, 5) .Select(i => Path.GetTempFileName()); foreach (var file in files) File.WriteAllText(file, "HELLO WORLD!"); /* ... many lines of codes later ... */ foreach (var file in files) File.Delete(file);

Imagina mi sorpresa cuando File.Delete(file)tira FileNotFound!!

Lo que sucede aquí es que el filesenumerable se iteró dos veces (los resultados de la primera iteración simplemente no se recuerdan) y en cada nueva iteración se volverá a llamar, Path.GetTempFilename()por lo que obtendrá un conjunto diferente de nombres temporales.

La solución es, por supuesto, enumerar con entusiasmo el valor utilizando ToArray()o ToList():

var files = Enumerable.Range(0, 5) .Select(i => Path.GetTempFileName()) .ToArray();

Esto es aún más aterrador cuando estás haciendo algo multihilo, como:

foreach (var file in files) content = content + File.ReadAllText(file);

y te das cuenta content.Lengthsigue siendo 0 después de todas las escrituras !! Luego comienzas a verificar rigurosamente que no tienes una condición de carrera cuando ... después de una hora perdida ... descubriste que es solo esa pequeña cosa Enumerable que has olvidado ...


Eventos

Nunca entendí por qué los eventos son una característica del lenguaje. Son complicados de usar: debe verificar si hay un nulo antes de llamar, necesita cancelar su registro (usted mismo), no puede saber quién está registrado (por ejemplo: ¿me registré?). ¿Por qué un evento no es solo una clase en la biblioteca? ¿Básicamente una especializada List<delegate>?


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:

IList<int> myList = new int[] { 1, 2, 4 }; myList.Add(5);

Tenemos mucho este problema, porque el serializador (WCF) convierte a todos los ILists en arreglos y obtenemos errores de tiempo de ejecución.


La ventana de vigilancia de Heisenberg

Esto puede morderte gravemente si estás haciendo cosas de carga bajo demanda, como esta:

private MyClass _myObj; public MyClass MyObj { get { if (_myObj == null) _myObj = CreateMyObj(); // some other code to create my object return _myObj; } }

Ahora digamos que tienes algún código en otro lugar usando esto:

// blah // blah MyObj.DoStuff(); // Line 3 // blah

Ahora desea depurar su método CreateMyObj() . Así que coloca un punto de interrupción en la Línea 3 anterior, con la intención de ingresar en el código. Solo por si acaso, también coloca un punto de interrupción en la línea que dice _myObj = CreateMyObj(); , e incluso un punto de interrupción dentro de CreateMyObj() sí.

El código llega a su punto de interrupción en la Línea 3. Usted entra en el código. _myObj ingresar el código condicional, porque _myObj es obviamente nulo, ¿verdad? Uh ... entonces ... ¿por qué se saltó la condición y se fue directo a return _myObj ? Pasas el mouse sobre _myObj ... y, de hecho, ¡tiene un valor! ¡¿Cómo ocurrió eso?!

La respuesta es que su IDE hizo que obtuviera un valor, porque tiene una ventana de "vigilancia" abierta, especialmente la ventana de vigilancia "Autos", que muestra los valores de todas las variables / propiedades relevantes para la línea de ejecución actual o anterior. Cuando llegó a su punto de interrupción en la Línea 3, la ventana de vigilancia decidió que le interesaría saber el valor de MyObj , por lo que, entre bambalinas, ignorando cualquiera de sus puntos de interrupción , fue y calculó el valor de MyObj para usted, incluida la llamada. a CreateMyObj() que establece el valor de _myObj!

Por eso lo llamo Heisenberg Watch Window: no puede observar el valor sin afectarlo ... :)

GOTCHA!

Editar : siento que el comentario de @HristianHayter merece ser incluido en la respuesta principal, porque parece ser una solución efectiva para este problema. Así que en cualquier momento usted tiene una propiedad cargada perezoso ...

Decora tu propiedad con [DebuggerBrowsable (DebuggerBrowsableState.Never)] o [DebuggerDisplay ("")]. - Christian Hayter


No hay atajos de operador en Linq-To-Sql

Ver here

En resumen, dentro de la cláusula condicional de una consulta Linq-To-Sql, no puede usar accesos directos condicionales como || y && para evitar excepciones de referencia nulas; ¡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!


Type.GetType

El que he visto morder a mucha gente es Type.GetType(string) . Se preguntan por qué funciona para tipos en su propio ensamblaje, y algunos tipos como System.String , pero no System.Windows.Forms.Form . La respuesta es que solo se ve en el ensamblaje actual y en mscorlib .

Métodos anónimos

C # 2.0 introdujo métodos anónimos, lo que lleva a situaciones desagradables como esta:

using System; using System.Threading; class Test { static void Main() { for (int i=0; i < 10; i++) { ThreadStart ts = delegate { Console.WriteLine(i); }; new Thread(ts).Start(); } } }

¿Qué se imprimirá eso? Bueno, todo depende 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 podría esperar. El problema es que es la variable i 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:

using System; using System.Threading; class Test { static void Main() { for (int i=0; i < 10; i++) { int copy = i; ThreadStart ts = delegate { Console.WriteLine(copy); }; new Thread(ts).Start(); } } }

Ejecución diferida de bloques iteradores

Esta "prueba de unidad del hombre pobre" no pasa, ¿por qué no?

using System; using System.Collections.Generic; using System.Diagnostics; class Test { static IEnumerable<char> CapitalLetters(string input) { if (input == null) { throw new ArgumentNullException(input); } foreach (char c in input) { yield return char.ToUpper(c); } } static void Main() { // Test that null input is handled correctly try { CapitalLetters(null); Console.WriteLine("An exception should have been thrown!"); } catch (ArgumentNullException) { // Expected } } }

La respuesta es que el código dentro de la fuente del código de CapitalLetters no se ejecuta hasta que se llama por primera vez al método MoveNext() del iterador.

Tengo algunas otras rarezas en mi página de acertijos .


Usando parámetros por defecto con métodos virtuales

abstract class Base { public virtual void foo(string s = "base") { Console.WriteLine("base " + s); } } class Derived : Base { public override void foo(string s = "derived") { Console.WriteLine("derived " + s); } } ... Base b = new Derived(); b.foo();

Salida:
base derivada


Acabo de encontrar uno extraño que me tuvo atrapado en la depuración por un tiempo:

Puede incrementar el valor nulo para un int sin nulos sin lanzar una excepción y el valor permanece nulo.

int? i = null; i++; // I would have expected an exception but runs fine and stays as null


Foreach bucles variables alcance!

var l = new List<Func<string>>(); var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" }; foreach (var s in strings) { l.Add(() => s); } foreach (var a in l) Console.WriteLine(a());

imprime cinco "amet", mientras que el siguiente ejemplo funciona bien

var l = new List<Func<string>>(); var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" }; foreach (var s in strings) { var t = s; l.Add(() => t); } foreach (var a in l) Console.WriteLine(a());


Hoy he arreglado un error que eludió durante mucho tiempo. El error se encontraba en una clase genérica que se usaba en un escenario de subprocesos múltiples y se usó un campo int estático para proporcionar una sincronización sin bloqueo utilizando Interlocked. El error se debió a que cada creación de instancias de la clase genérica para un tipo tiene su propia estática. Así que cada hilo tiene su propio campo estático y no se usó un bloqueo como se esperaba.

class SomeGeneric<T> { public static int i = 0; } class Test { public static void main(string[] args) { SomeGeneric<int>.i = 5; SomeGeneric<string>.i = 10; Console.WriteLine(SomeGeneric<int>.i); Console.WriteLine(SomeGeneric<string>.i); Console.WriteLine(SomeGeneric<int>.i); } }

Esto imprime 5 10 5


MS SQL Server no puede manejar fechas antes de 1753. Significativamente, eso no está sincronizado con la DateTime.MinDateconstante .NET , que es 1/1/1. Entonces, si intentas guardar una mentalidad, una fecha malformada (como me ocurrió recientemente en una importación de datos) o simplemente la fecha de nacimiento de Guillermo el Conquistador, estarás en 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.


Recogida de basuras y disposición (). Aunque no tiene que hacer nada para liberar memoria , aún debe liberar recursos a través de Dispose (). Esto es una cosa muy fácil de olvidar cuando está utilizando WinForms, o rastrear objetos de cualquier manera.


TextInfo textInfo = Thread.CurrentThread.CurrentCulture.TextInfo; textInfo.ToTitleCase("hello world!"); //Returns "Hello World!" textInfo.ToTitleCase("hElLo WoRld!"); //Returns "Hello World!" textInfo.ToTitleCase("Hello World!"); //Returns "Hello World!" textInfo.ToTitleCase("HELLO WORLD!"); //Returns "HELLO WORLD!"

Sí, este comportamiento está documentado, pero eso ciertamente no lo hace correcto.


[Serializable] class Hello { readonly object accountsLock = new object(); } //Do stuff to deserialize Hello with BinaryFormatter //and now... accountsLock == null ;)

La moraleja de la historia: los inicializadores de campo no se ejecutan al deserializar un objeto


private int myVar; public int MyVar { get { return MyVar; } }

Blammo Su aplicación se bloquea sin rastro de pila. Pasa todo el tiempo.

(Observe el capital myVar en lugar de minúscula myVar en el getter).