Compartir a través de


CLR

Lo nuevo en la biblioteca de clase de base .NET 4.5

Immo Landwerth

 

La biblioteca de clase de base de (BCL) Microsoft .NET Framework se centra en los aspectos básicos. Si bien algunos de los modelos básicos son estables y no cambian demasiado (por ejemplo, System.Int32 y System.String), Microsoft continúa invirtiendo bastante trabajo en este ámbito. Este artículo abarca las grandes mejoras (y algunas de las menores) realizadas a la BCL en .NET Framework 4.5.

Al leer este artículo, tenga presente que se basa en la versión beta de .NET Framework 4.5, no en el producto ni las API finales, de manera que algunas características están sujetas a cambio.

Si desea una descripción general sobre otras áreas de .NET Framework, como Windows Communication Foundation (WCF) o Windows Presentation Foundation (WPF), consulte la página de la biblioteca de MSDN, “Lo nuevo de la versión beta de .NET Framework 4.5” (bit.ly/p6We9u).

Programación asincrónica simplificada

El uso de E/S asincrónicas tiene varias ventajas. Evita el bloqueo de la interfaz de usuario y puede reducir la cantidad de subprocesos que tiene que usar el sistema operativo. Y sin embargo, es muy probable que no las esté aprovechando porque la programación asincrónica solía ser muy compleja. El mayor problema era que el antiguo modelo de programación asincrónica (APM) se diseñó alrededor de pares de métodos Begin/End. Para ilustrar cómo funciona este patrón, considere el siguiente método sincrónico sencillo que copia un flujo:

public void CopyTo(Stream source, Stream destination)
{
  byte[] buffer = new byte[0x1000];
  int numRead;
  while ((numRead = source.Read(buffer, 0, buffer.Length)) != 0)
  {
    destination.Write(buffer, 0, numRead);
  }
}

Para poder convertir este método en asincrónico con el uso del APM anterior, tendríamos que escribir el código que aparece en la Ilustración 1.

Ilustración 1 Copiado de una secuencia con el método asincrónico antiguo

public void CopyToAsyncTheHardWay(Stream source, Stream destination)
{
  byte[] buffer = new byte[0x1000];
  Action<IAsyncResult> readWriteLoop = null;
  readWriteLoop = iar =>
  {
    for (bool isRead = (iar == null); ; isRead = !isRead)
    {
      switch (isRead)
      {
        case true:
          iar = source.BeginRead(buffer, 0, buffer.Length, 
            readResult =>
          {
            if (readResult.CompletedSynchronously) return;
            readWriteLoop(readResult);
          }, null);
          if (!iar.CompletedSynchronously) return;
          break;
        case false:
          int numRead = source.EndRead(iar);
          if (numRead == 0)
          {
            return;
          }
          iar = destination.BeginWrite(buffer, 0, numRead, 
            writeResult =>
          {
            if (writeResult.CompletedSynchronously) return;
            destination.EndWrite(writeResult);
            readWriteLoop(null);
          }, null);
          if (!iar.CompletedSynchronously) return;
          destination.EndWrite(iar);
          break;
        }
      }
  };
  readWriteLoop(null);
}

Lejos, la versión asincrónica no se puede comprender tan fácilmente como su contraparte sincrónica. Resulta difícil distinguir la intención en la cantidad de código repetitivo requerido para hacer que los modelos de programación básicos, por ejemplo bucles, funcionen cuando se usan delegados. Si esto todavía no lo asusta, intente agregar el control y cancelación de excepciones.

Afortunadamente, esta versión de la BCL posee un nuevo modelo de programación asincrónica creada en torno a Task y Task<T>. C# y Visual Basic agregaron una funcionalidad de primera clase en el lenguaje, al agregar las palabras clave async y await (por cierto, F# ya contaba con esta funcionalidad mediante los flujos de trabajo asincrónicos y, de hecho, sirvió de inspiración para esta característica). Como resultado, los compiladores ahora se encargan de la mayoría, si no es de todo, el código que antiguamente debíamos escribir a mano. La funcionalidad del lenguaje, como también ciertas adiciones a la API a .NET Framework, trabajan en conjunto para que la escritura de métodos asincrónicos sea tan fácil como la escritura de código sincrónico. Véalo con sus propios ojos: para convertir el método CopyTo en asincrónico ahora solo se requieren los siguientes cambios destacados:

public async Task CopyToAsync(Stream source, Stream destination)
{
  byte[] buffer = new byte[0x1000];
  int numRead;
  while ((numRead = await 
     source.ReadAsync(buffer, 0, buffer.Length)) != 0)
  {
    await destination.WriteAsync(buffer, 0, numRead);
  }
}

Hay muchas cosas interesantes que decir sobre la programación asincrónica con las nuevas características del lenguaje, pero me quiero concentrar en el impacto que tienen en la BCL y en usted, el consumidor de la BCL. Sin embargo, si tiene mucha curiosidad, consulte la página de MSDN Library “Programación asincrónica con async y await (C# y Visual Basic)” (bit.ly/nXerAc).

Para crear nuestras propias operaciones asincrónicas, necesitamos componentes de bajo nivel. A partir de estos, podemos construir métodos más complejos, como el método CopyToAsync que mostré anteriormente. Ese método solo usa los métodos ReadAsync y WriteAsync de la clase Stream como componentes. La Ilustración 2 enumera algunas de las API asincrónicas más importantes que se agregaron a la BCL.

Ilustración 2 Métodos asincrónicos en la BCL

Tipo Métodos
System.IO.Stream

ReadAsync

WriteAsync

FlushAsync

CopyToAsync

System.IO.TextReader

ReadAsync

ReadBlockAsync

ReadLineAsync

ReadToEndAsync

System.IO.TextWriter

WriteAsync

WriteLineAsync

FlushAsync

Observe que no agregamos versiones asincrónicas de las API demasiado detalladas, como por ejemplo TextReader.Peek. La razón es que las API asincrónicas también agregan cierta sobrecarga y queríamos evitar que los desarrolladores se desviaran accidentalmente del camino. Esto también significa que decidimos específicamente no proporcionar versiones asincrónicas de los métodos BinaryReader y BinaryWriter. Para usar esas API, recomendamos usar Task.Run para iniciar una nueva operación asincrónica y luego usar las API asincrónicas desde dentro de la operación, pero no hacerlo por llamada de método. La regla general es: intente hacer que la operación asincrónica sea lo más gruesa posible. Por ejemplo, si desea leer 1.000 Int32 de un flujo con BinaryReader, resulta mejor ejecutar y esperar una sola tarea que lee los 1.000 valores sincrónicamente, que ejecutar y esperar individualmente 1.000 tareas que leen un solo Int32.

Si desea conocer más sobre la escritura de código asincrónico, le recomiendo que lea el blog sobre programación paralela en .NET (blogs.msdn.com/b/pfxteam).

Interfaces de colección de solo lectura

Una de las solicitudes de característica de más larga data en la BCL son las interfaces de colección de solo lectura (que no se debe confundir con las colecciones inmutables; consulte “Diferentes nociones de colecciones de solo lectura” en la página 22 para obtener más detalles). Nuestra posición ha sido que este aspecto específico (al ser solo de lectura) se modela mejor al usar el patrón de característica opcional. En este patrón, se proporciona una API que le permite a los consumidores probar si se reconoce una característica determinada y arrojar una excepción NotSupportedException en caso negativo.

La ventaja de este patrón es que requiere menos tipos, ya que no tenemos que modelar combinaciones de características. La clase Stream, por ejemplo, proporciona varias características y todas se expresan mediante descriptores de acceso booleanos de obtención (CanSeek, CanRead, CanWrite y CanTimeout). Esto permite que la BCL tenga un solo tipo para los flujos y que a pesar de esto sea compatible con las características de secuencia en todas sus combinaciones.

Con los años, llegamos a la conclusión de que valía la pena agregar una interfaz de colección de solo lectura, aunque aumentara la complejidad. En primer lugar, quiero presentar las interfaces y luego analizar las características que ofrecen. En la Ilustración 3 vemos un diagrama de clase de Visual Studio con las interfaces de colección existentes (mutables); en la Ilustración 4 aparecen las interfaces solo de lectura correspondientes.

Mutable Collection Interfaces
Ilustración 3 Interfaces de colección mutables

Read-Only Collection Interfaces
Ilustración 4 Interfaces de colección de solo lectura

Tenga en cuenta que IReadOnlyCollection<T> no entró en la versión beta de .NET Framework 4.5 beta, así que no se sorprenda si no la encuentra. En la versión beta, IReadOnlyList<T> y IReadOnlyDictionary<TKey,TValue> se derivan directamente de IEnumerable<T> y cada interfaz define su propia propiedad Count.

IEnumerable<T> es covariante. Eso significa que si un método acepta IEnumerable<Forma>, podemos llamarlo con IEnumerable<Círculo> (suponiendo que Círculo se deriva de Forma). Eso resulta útil en las situaciones con jerarquías de tipos y algoritmos que pueden operar en tipos específicos, como una aplicación que puede dibujar diferentes formas. IEnumerable<T> es suficiente en la mayoría de los casos que lidian con colecciones de tipos, pero algunas veces se necesitamos más poder del que entrega:

  1. Materialization.IEnumerable<T> no nos permite expresar si la colección ya está disponible (“materializada”) o si se calcula en cada iteración (por ejemplo, si representa una consulta LINQ). Cuando un algoritmo requiere varias iteraciones sobre la colección, puede sufrir el rendimiento si el procesamiento de la secuencia es costoso; también puede causar errores sutiles debidos a discordancias de identidad cuando los objetos se vuelven a generar en pasos subsiguientes.
  2. Count.IEnumerable<T> no proporciona ningún conteo. De hecho, es posible que ni siquiera posea uno, ya que puede tratarse de una secuencia infinita. Pero en muchos casos el método de extensión estático Enumerable.Count resulta suficiente. En primer lugar, clasifica como casos especiales los tipos de colección, como por ejemplo ICollection<T>, para evitar iterar sobre la secuencia completa; en segundo lugar, en muchos casos no resulta costoso calcular el resultado. Sin embargo, según el tamaño de la colección, esto puede marcar la diferencia.
  3. Indexing.IEnumerable<T> no permite el acceso aleatorio a los elementos. En algunos algoritmos, como el de ordenación rápida, es necesario acceder a un elemento a partir de su índice. Nuevamente, existe un método de extensión estático (Enumerable.ElementAt) que usa una ruta de código optimizada si el enumerable está respaldado por IList<T>. Sin embargo, si la indización se usa dentro de un bucle, la búsqueda lineal puede tener resultados desastrosas en el rendimiento, ya que convertirán un algoritmo O(n) en uno O(n2). Por lo tanto, si necesita acceso aleatorio, realmente necesita acceso aleatorio.

Así que ¿por qué no usar simplemente ICollection<T>/IList<T> en vez de IEnumerable<T>? Porque perdemos la covariancia y porque ya no podríamos diferenciar entre los métodos que solo leen las colecciones y los que también las modifican; algo que se vuelve cada vez más importante cuando usamos la programación asincrónica o el multithreading en general. En otras palabras, queremos quedarnos con el pan y con la torta.

Entran IReadOnlyCollection<T> y IReadOnlyList<T>. IReadOnlyCollection<T> es básicamente lo mismo que IEnumerable<T>, pero agrega la propiedad Count. Esto permite crear algoritmos que pueden expresar la necesidad de colecciones materializadas, o colecciones con un tamaño finito y conocido. IReadOnlyList<T> expande lo anterior al agregar un indizador. Estas dos interfaces son covariantes, lo que significa que si un método acepta IReadOnlyList<Forma>, podemos llamarlo con List<Círculo>:

class Shape { /*...*/ }
class Circle : Shape { /*...*/ }
void LayoutShapes(IReadOnlyList<Shape> shapes) { /*...*/ }
void LayoutCircles()
{
  List<Circle> circles = GetCircles();
  LayoutShapes(circles);
}

Desafortunadamente, nuestro sistema de tipos no permite hacer que los tipos T sean covariantes, a menos que no tenga métodos que acepten T como entrada. Por lo tanto, no podemos agregar un método IndexOf a IReadOnlyList<T>. Creemos que este es un sacrificio pequeño, comparado con no contar con la covariancia en absoluto.

Todas las implementaciones de nuestras colecciones integradas, como las matrices, List<T>, Collection<T> y ReadOnlyCollection<T>, también implementan las interfaces de colección de solo lectura. Como ahora cualquier colección se puede tratar como una colección de solo lectura, los algoritmos pueden declarar su intención de manera más precisa sin limitar el nivel de reutilización; se pueden usar en todos los tipos de colección. En el ejemplo anterior, el consumidor de LayoutShapes podía pasar List<Círculo>, pero LayoutShapes también aceptaría una matriz de Círculos o Collection<Círculo>.

Otra ventaja de estos tipos de colección es la excelente experiencia que brindan para trabajar con Windows en tiempo de ejecución (WinRT). WinRT proporciona sus propios tipos de colección, como Windows.Foundation.Collections.IIterable<T> y Windows.Foundation.Collec­tions.IVector<T>. La capa de metadatos de CLR los proyecta directamente a lo tipos de datos de la BCL correspondientes. Por ejemplo, al consumir WinRT de .NET Framework, IIterable<T> se convierte en IEnumerable<T> y IVector<T> se convierte en IList<T>. De hecho, un desarrollador que use Visual Studio e IntelliSense ni siquiera se daría cuenta que WinRT tiene tipos de colección diferentes. Como WinRT también proporciona versiones de solo lectura (como IVectorView<T>), las nuevas interfaces de solo lectura completan el panorama para que todos los tipos de colección se puedan compartir fácilmente entre .NET Framework y WinRT.

Diferentes nociones de las colecciones de solo lectura

  • Mutable (o que no es de solo lectura) El tipo de colección más común en el mundo de .NET. Estas son colecciones como List<T> que permiten la lectura y también agregar, eliminar y cambiar elementos.
  • Solo de lectura Son colecciones que no se pueden modificar desde fuera. Sin embargo, esta noción de colecciones no garantiza que el contenido nunca cambiará. Por ejemplo, las colecciones de las claves y valores de un diccionario no se pueden actualizar directamente, pero al agregar al diccionario se actualizan las colecciones de las claves y valores de manera indirecta.
  • Inmutable Son colecciones que una vez creadas ofrecen la garantía de que nunca cambiarán. Resulta una propiedad excelente para el multithreading. Si una estructura de datos compleja es totalmente inmutable, siempre se la puede pasar con seguridad a un trabajador en segundo plano. No tenemos que preocuparnos de que alguien la modifique al mismo tiempo. La biblioteca de clase de base de (BCL) de Microsoft .NET Framework actualmente no incluye este tipo de colección.
  • Congelable Estas colecciones se comportan como colecciones mutables hasta que se congelan. Entonces se comportan como colecciones inmutables. Aunque la BCL no define estas colecciones, las podemos encontrar en Windows Presentation Foundation.

Andrew Arnott escribió un excelente artículo en su blog, donde describe con más detalles las diferentes nociones sobre las colecciones (bit.ly/pDNNdM).

Compatibilidad con archivos .zip

Otra solicitud común de larga data era la posibilidad de leer y escribir archivos .zip comunes. .NET Framework 3.0 y posterior permite leer y escribir los archivos de Open Packaging Convention (bit.ly/ddsfZ7). Pero System.IO.Packaging se adaptó para que fuera compatible con esa especificación puntual y por lo general no se puede usar para procesar los archivos .zip comunes.

Esta versión agrega compatibilidad de primera clase con Zip, mediante System.IO.Com­pression.ZipArchive. Además, corregimos algunos problemas de larga data con relación al rendimiento y la calidad de compresión en nuestra implementación de DeflateStream. A partir de .NET Framework 4.5, la clase Deflate­Stream usa la biblioteca popular zlib. Como resultado, proporciona una implementación mejor del algoritmo Deflate y, en la mayoría de los casos, un archivo comprimido menor que en las versiones anteriores de .NET Framework.

La extracción de un archivo completo al disco se puede realizar en una sola línea:

ZipFile.ExtractToDirectory(@"P:\files.zip", @"P:\files");

También no aseguramos de que para realizar las operaciones típicas no haya que leer todo el archivo en memoria. Por ejemplo, la extracción de un solo archivo desde un archivo grande se puede realizar del siguiente modo:

using (ZipArchive zipArchive = 
  ZipFile.Open(@"P:\files.zip", ZipArchiveMode.Read))
{
  foreach (ZipArchiveEntry entry in zipArchive.Entries)
  {
    if (entry.Name == "file.txt")
    {
      using (Stream stream = entry.Open())
      {
        ProcessFile(stream);
      }
    }
  }     
}

En este caso, la única parte que se carga en la memoria es la tabla de contenido del archivo .zip. El archivo que se extrae se transmite completamente, es decir, no hace falta que quepa en la memoria. Esto permite procesar archivos .zip arbitrariamente grandes, incluso cuando la memoria es limitada.

La creación de archivos .zip funciona de manera semejante. Para crear un archivo .zip a partir de un directorio, tenemos que escribir una sola línea:

ZipFile.CreateFromDirectory(@"P:\files", @"P:\files.zip");

Por supuesto, también podemos construir manualmente un archivo .zip, lo que nos entrega un control total sobre la estructura interna. El siguiente código muestra cómo crear un archivo .zip donde solo se agregan los archivos de código de fuente C# (además, el archivo .zip ahora también posee un subdirectorio llamado SourceCode):

IEnumerable<string> files = 
  Directory.EnumerateFiles(@"P:\files", "*.cs");
using (ZipArchive zipArchive = 
  ZipFile.Open(@"P:\files.zip", ZipArchiveMode.Create))
{
  foreach (string file in files)
  {
    var entryName = Path.Combine("SourceCode", Path.GetFileName(file));
    zipArchive.CreateEntryFromFile(file, entryName);
  }
}

Gracias al uso de flujos, también podemos construir archivos .zip donde las entradas no provienen de un archivo real. Lo mismo sucede con el propio archivo .zip. Por ejemplo, en vez de ZipFile.Open, podemos usar el constructor ZipArchive que toma un flujo. Esto nos sirve, por ejemplo, para construir un servidor web que crea archivos .zip sobre la marcha a partir de los contenidos almacenados en una base de datos y que también escribe los resultados directamente en el flujo de respuesta, en vez de hacerlo en el disco.

Vale la pena explicar más en profundidad un detalle sobre el diseño de la API. Probablemente se percató de que los métodos de conveniencia estática están definidos en la clase ZipFile, mientras que la instancia real tiene el tipo ZipArchive. ¿A qué se debe esto?

A partir de .NET Framework 4.5, tuvimos la portabilidad en mente al diseñar las API. Algunas plataformas .NET no son compatibles con las rutas de archivo, como por ejemplo .NET para las aplicaciones estilo Metro en Windows 8. En esta plataforma, el acceso al sistema de archivos se negocia para permitir un modelo basado en funciones. Para poder leer o escribir archivos, ya no podemos usar las API comunes de Win32, sino que ahora debemos usar las API de WinRT.

Como ya mostré, la funcionalidad de .zip misma no requiere de las rutas de archivo. Por lo tanto, dividimos la funcionalidad en dos partes:

  1. System.IO.Compression.dll. Este ensamblado contiene la funcionalidad .zip de uso general. No admite rutas de archivos. La clase principal de este ensamblado es ZipArchive.
  2. System.IO.Compression.FileSystem.dll. Este ensamblado proporciona la clase estática ZipFile que define métodos de extensión y auxiliares estáticos. Esas API aumentan la funcionalidad de .zip con métodos convenientes en las plataformas .NET que son compatibles con las rutas de sistemas de archivos.

Para obtener más información sobre la escritura de código .NET portable, visite la página de bibliotecas de clase portable en MSDN Library en bit.ly/z2r3eM.

Mejoras varias

Por supuesto, siempre hay algo más que decir. Después de trabajar en una versión durante tanto tiempo, elegir las características más importantes deja la misma sensación que cuando alguien nos pregunta cuál es nuestro hijo favorito. A continuación, quisiera destacar algunas áreas que vale la pena mencionar, pero que no tuvieron el espacio que se merecen en este artículo:

AssemblyMetadataAttribute Algo que aprendimos en .NET Framework sobre los atributos es que sin importar la cantidad que haya, siempre se necesitan más (por cierto, .NET Framework 4 ya tenía más de 300 atributos de nivel de ensamblado). AssemblyMetadataAttribute es un atributo de ensamblado de uso general que permite asociar un par valor clave basado en una cadena con un ensamblado. Esto sirve para apuntar a la página de inicio del producto o a la etiqueta de control de versión que corresponde a la fuente a partir de la cual se creó el ensamblado.

WeakReference<T> El tipo WeakReference no genérico existente tiene dos problemas: En primer lugar, obliga a los consumidores a realizar una conversión cada vez que se debe acceder al objetivo. Más importante aún, tiene un error de diseño que lo hace propenso a las condiciones de carrera: expone una API para revisar si el objeto está vivo (IsAlive) y otra API para llegar al objeto propiamente tal (Target). WeakReference<T> soluciona este problema al proporcionar la API única TryGetTarget, que realiza ambas operaciones en una forma atómica.

Comparer<T>.Create(Comparison<T>) La BCL proporciona dos formas para implementar comparadores de colecciones. Una es a través de la interfaz IComparer<T>; la otra es el delegado Comparison<T>. Es muy fácil convertir IComparer<T> en Comparison<T>. La mayoría de los lenguajes proporcionan una conversión implícita de un método a un tipo de delegado, lo que permite construir fácilmente Comparison<T> a partir del método Compare IComparer<T>. En la otra dirección, sin embargo, debíamos implementar IComparer<T> por nuestra propia cuenta. En .NET Framework 4.5 agregamos un método estático Create en Comparer<T> que, dado Comparison<T>, nos entrega una implementación de IComparer<T>.

ArraySegment<T> En .NET Framework 2.0 agregamos la estructura ArraySegment<T>, que permite representar un subconjunto de una matriz dada sin tener que copiar. Desafortunadamente, ArraySegment<T> no implementó ninguna de las interfaces de colección, por lo que no lo podíamos pasar a los métodos que operan sobre las interfaces de colección. En esta versión corregimos ese problema y ArraySegment<T> ahora implementa IEnumerable, IEnumerable<T>, ICollection<T> y IList<T>, como también IReadOnlyCollection<T> y IReadOnlyList<T>.

SemaphoreSlim.WaitAsync Es la única primitiva de sincronización que permite esperar hasta que se retire el bloqueo. Si desea saber más sobre las razones, consulte “Lo nuevo del paralelismo en la versión beta de .NET 4.5” (bit.ly/AmAUIF).

ReadOnlyDictionary<TKey,TValue> Desde .NET Framework 2.0, la BCL proporciona ReadOnlyCollection<T>, que sirve como un contenedor de solo lectura alrededor de una instancia de colección dada. Esto permite que los implementadores de modelos de objetos expongan colecciones que el consumidor no puede cambiar. En esta versión, agregamos el mismo concepto para los diccionarios.

BinaryReader, BinaryWriter, StreamReader, StreamWriter: opción para no eliminar el flujo subyacente Todas las clases de lector y escritor de nivel superior aceptan una instancia de flujo en los constructores. En las versiones anteriores, esto significaba que la pertenencia del flujo pasaba a la instancia del lector/escritor, lo que suponía que al eliminar el lector/escritor también se eliminaba la secuencia subyacente. Esto es bastante práctico y útil cuando tenemos un solo lector/escritor, pero dificulta la tarea cuando debemos componer varias API que operan en secuencias, pero tenemos que usar lectores/escritores como parte de la implementación. Lo mejor que podíamos hacer en las versiones anteriores era no eliminar el lector/escritor y dejar un comentario en la fuente que explicara el problema (este enfoque también nos obligaba a vaciar el escritor manualmente para evitar la pérdida de datos). .NET Framework 4.5 permite expresar este contrato mediante un constructor lector/escritor que recibe el parámetro leaveOpen, donde podemos especificar explícitamente que el lector/escritor no debe eliminar la secuencia subyacente.

Console.IsInputRedirected, Console.IsOutputRedirected y Console.IsErrorRedirected Redirección de la entrada y salida en los programas de línea de comando. En la mayoría de las aplicaciones, esto es transparente. Sin embargo, algunas veces queremos un comportamiento diferente cuando se activa el redireccionamiento. Por ejemplo, cuando la salida de consola con colores no sea útil y no se pueda configurar la posición del cursor. Estas propiedades permiten consultar si se redireccionó alguno de los flujos estándar.

Console.OutputEncoding y Console.InputEncoding ahora se pueden establecer como Encoding.Unicode Al establecer Console.OutputEncoding en Encoding.Unicode el programa puede escribir caracteres que no se podían representar en la página de código OEM asociada con la consola. Esta característica también facilita la visualización de texto en diferentes sistemas de escritura en la consola. Más detalles estarán disponibles en la documentación revisada de la clase Console que aparecerá pronto en MSDN Library.

ExceptionDispatchInfo El control de los errores es un aspecto importante en la creación de componentes de marco. Algunas veces, volver a generar excepciones (en C# a través de “throw”) no es suficiente, ya que solo puede suceder dentro de un controlador de excepciones. Algunos componentes de marco, como la infraestructura Task, tienen que volver a generar la excepción en un momento posterior (por ejemplo, después de volver a calcular las referencias del subproceso de origen). En el pasado, eso significaba que se perdía el seguimiento de la pila original y la clasificación del Informe de errores de Windows (WER) (conocido también como depósitos Watson), ya que al volver a generar simplemente el mismo objeto de excepción, se sobrescribiría esta información. ExceptionDispatchInfo permite capturar un objeto de excepción existente y volver a generarlo, sin perder la información valiosa grabada en el objeto de excepción.

Regex.Timeout Las expresiones regulares son una excelente forma para validar las entradas. Sin embargo, es poco sabido que el cálculo de algunas expresiones regulares puede ser extremadamente costoso al aplicarlas a entradas de texto específicas; es decir, poseen una complejidad de tiempo exponencial. Esto resulta especialmente problemático en los entornos de servidor, donde las expresiones regulares propiamente tales están sujetas a configuración. Como resulta difícil, por no decir imposible, predecir el comportamiento en tiempo de ejecución de una expresión regular dada, la opción más conservadora en dichos casos es establecer un límite de tiempo para cada búsqueda del motor Regex. Por esta razón Regex ahora posee varias API que aceptan un tiempo de expiración: Regex.IsMatch, Regex.Match, Regex.Matches, Regex.Split y Regex.Replace.

Involúcrese

Las características analizadas en este artículo están disponibles en la versión beta de Visual Studio 11. Cumple con nuestros altos estándares para las versiones preliminares de software, por lo que le damos soporte técnico en los entornos de producción. Puede descargar la versión beta en bit.ly/9JWDT9. Como se trata de una versión preliminar de software, estamos interesados en obtener comentarios, ya sea que encontró algún problema (connect.microsoft.com/visualstudio) o si tiene ideas para características nuevas (visualstudio.uservoice.com). También sugiero que se suscriba al blog de mi equipo en blogs.msdn.com/b/bclteam para que se mantenga informado sobre los próximos cambios y anuncios.

Immo Landwerth es administrador de programas en el equipo de CLR de Microsoft, donde trabaja en la biblioteca de clase de base (BCL) de Microsoft .NET Framework, en el diseño de API y de bibliotecas de clase portátiles. Puede ponerse en contacto con él a través del blog del equipo de BCL en blogs.msdn.com/b/bclteam.

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Nicholas Blumhardt, Greg Paperin, Daniel Plaisted, Evgeny Roubinchtein, Alok Shriram, Chris Szurgot, Stephen Toub y Mircea Trofin