Compartir a través de


Este artículo proviene de un motor de traducción automática.

Todo sobre CLR

Novedades de las bibliotecas de clase base en .NET Framework 4

Justin Van Patten

Casi todas las personas que usan Microsoft .NET utilizan la biblioteca de clases base (BCL). Al mejorar la BCL, casi todo desarrollador de aplicaciones administradas se ve beneficiado. Esta columna explora las nuevas adiciones a la BCL en .NET 4 beta 1.

Tres de las adiciones ya se han tratado en artículos anteriores. Comenzaré con una breve revisión de lo siguiente:

  • Compatibilidad para contratos de código
  • Extensiones paralelas (tareas, colecciones simultáneas y estructuras de datos de coordinación)
  • Compatibilidad con tuplas
    A continuación, en la parte principal del artículo, hablaré sobre tres nuevas adiciones sobre las que todavía no se ha escrito:
  • Mejoras de E/S de archivo
  • Compatibilidad para archivos asignados en memoria
  • Una colección de conjunto ordenada

No hay espacio para describir todas las nuevas mejoras de BCL en este artículo, pero puede leer acerca de ellas en las próximas publicaciones en el blog del equipo BCL en blogs.msdn.com/bclteam. Por ejemplo:

  • Compatibilidad para enteros arbitrariamente grandes
  • Anotaciones de variación genérica en interfaces y delegados
  • Compatibilidad para tener acceso a las vistas de registro de 32 bits y 64 bits y crear claves de registro volátiles
  • Actualizaciones de los datos de globalización
  • Lógica de retroceso de búsqueda mejorada de System.Resourcesresource
  • Mejoras de compresión

También se está planeando incluir algunas características y mejoras adicionales para beta 2 sobre las que podrá leer en el blog del equipo de BCL cuando se distribuya la versión beta 2.

Contratos de código

Una de las principales características nuevas agregadas a la BCL en .NET Framework 4 son los contratos de código. Esta biblioteca nueva proporciona una manera lengua-agnóstica para especificar las condiciones previas, las condiciones posteriores y las invariantes de objeto en el código. Encontrará más información sobre los contratos de código en la columna CLR Inside Out de Melitta Andersen en la entrega de agosto de 2009 de MSDN Magazine. También debe visitar el sitio de DevLabs de contratos de código en msdn.microsoft.com/devlabs/dd491992.aspx y en el blog del equipo de BCL en blogs.msdn.com/bclteam.

Extensiones paralelas

Como los procesadores multinúcleo se han vuelto más importantes en el cliente y los servidores masivamente paralelos se han empezado a propagar más, es más importante que nunca facilitar a los programadores el uso de todos estos procesadores. Otra incorporación nueva importante para la BCL de .NET 4 es la característica de Parallel Extensions (PFX) que distribuye el equipo de Parallel Computing Platform. PFX incluye la Biblioteca de tareas de Parallel (TPL), las estructuras de datos de coordinación, las colecciones simultáneas y Parallel LINQ (PLINQ); todo esto facilitará la escritura de código que pueda beneficiarse de los equipos multinúcleo. Se puede obtener más información sobre PFX en el artículo de Stephen Toub y Hazim Shafi "Mayor compatibilidad para paralelismo en la siguiente versión de Visual Studio" en la entrega de octubre msdn.microsoft.com/magazine/cc817396.aspx. El blog del equipo de PFX en blogs.msdn.com/pfxteam también es una excelente fuente de información sobre PFX.

Tuplas

Otra incorporación a la BCL de .NET 4 es la compatibilidad para tuplas, que son similares a las clases anónimas que se pueden crear sobre la marcha. Una tupla es una estructura de datos usada en muchos idiomas funcionales y dinámicos, como F# y Iron Python. Al proporcionar tipos comunes de tupla en la BCL ayudamos a facilitar mejor la interoperabilidad entre idiomas. Muchos programadores encuentran convenientes las tuplas, especialmente para devolver varios valores desde los métodos; por lo tanto, hasta los desarrolladores de C# o Visual Basic las encuentran útiles. La columna CLR Inside Out de Matt Ellis en la entrega de julio de 2009 de MSDN Magazine describe la nueva compatibilidad para tuplas en .NET 4.

Mejoras de E/S de archivo

Una de las novedades de .NET 4 de la cual todavía no hemos escrito con detalles son los nuevos métodos en System.IO.File para leer y escribir archivos de texto. Desde .NET 2.0, si necesitaba leer las líneas de un archivo de texto, podría llamar al método File.ReadAllLines, que devuelve una matriz de cadena de todas las líneas en el archivo. El siguiente código usa File.ReadAllLines para leer las líneas de un archivo de texto y escribe la longitud de la línea junto con la propia línea en la consola:

string[] lines = File.ReadAllLines("file.txt");
foreach (var line in lines) {
Console.WriteLine("Length={0}, Line={1}", line.Length, line);
}

Desafortunadamente, hay un problema sutil con este código. El problema proviene del hecho de que ReadAllLines devuelve una matriz. Antes de que ReadAllLines pueda volver, debe leer todas las líneas y asignar una matriz para volver. Esto no es un problema importante para archivos relativamente pequeños, pero puede ser problemático para archivos de texto grandes con millones de líneas. Imagine abrir un archivo de texto de la libreta de teléfonos o una novela con 900 páginas. ReadAll-Lines se bloqueará hasta que todas las líneas se carguen en la memoria. No sólo es un uso ineficaz de la memoria, sino que también retrasa el procesamiento de las líneas, ya que no puede tener acceso a la primera línea hasta que todas las líneas se hayan leído en la memoria.

Para evitar el problema, puede usar un TextReader para abrir el archivo y, a continuación, leer las líneas del archivo en la memoria, una línea por vez. Esto funciona, pero no es tan sencillo como llamar a File.ReadAllLines:

using (TextReader reader = new StreamReader("file.txt")) {
string line;
while ((line = reader.ReadLine()) != null) {
Console.WriteLine("Length={0}, Line={1}", line.Length, line);
}
}

En .NET 4, hemos agregado un nuevo método a un archivo denominado ReadLines (en oposición a ReadAllLines) que devuelve IEnumerable <string> en lugar de string[]. Este nuevo método es mucho más eficaz porque no carga todas las líneas en la memoria a la vez; sino que lee las líneas una por vez. El siguiente código usa File.Read-Lines para leer eficazmente las líneas de un archivo y es tan fácil de usar como el menor efficientFile.ReadAllLines:

IEnumerable<string> lines = File.ReadLines(@"verylargefile.txt");
foreach (var line in lines) {
Console.WriteLine("Length={0}, Line={1}", line.Length, line);
}

Tenga en cuenta que la llamada a File.ReadLines hace la devolución inmediatamente. Ya no tendrá que esperar hasta que todas las líneas se lean en la memoria antes de poder repetir el proceso con las líneas. La iteración del bucle foreach realmente dirige la lectura del archivo. Esto no sólo mejora significativamente el rendimiento percibido del código, porque puede iniciar el procesamiento de las líneas mientras se leen, sino que también es mucho más eficaz porque las líneas se leen de a una por vez. Usar el archivo. ReadLines tiene una ventaja adicional porque le permite salir del bucle pronto si es necesario, sin desperdiciar tiempo en leer líneas adicionales que no le interesan.

También hemos agregado nuevas sobrecargas para File.WriteAllLines que toman un parámetro de IEnumerable <string> similar a las sobrecargas existentes que toman un parámetro de string[]. Y agregamos un nuevo método denominado AppendAllLines que toma un IEnumerable <string> para anexar líneas a un archivo. Estos nuevos métodos le permiten escribir o anexar líneas fácilmente a un archivo sin tener que pasar en una matriz. Esto significa que si tiene una colección de cadenas, la puede pasarla directamente a estos métodos sin tener que convertirla primero en una matriz de cadenas.

Hay otros lugares en la BCL que se beneficiarían del uso de IEnumerable <T> a través de matrices. Tomemos la enumeración de sistema de archivos de API, por ejemplo. En versiones anteriores del marco de trabajo, para tener los archivos en un directorio, llamaba a un método como DirectoryInfo. GetFiles, que devuelve una matriz de objetos de FileInfo. Recién en ese momento podía repetir el proceso con los objetos de FileInfo para obtener información acerca de los archivos, tales como el nombre y la longitud de cada archivo. El código para hacer eso puede ser como éste:

DirectoryInfo directory = new DirectoryInfo(@"\\share\symbols");
FileInfo[] files = directory.GetFiles();
foreach (var file in files) {
Console.WriteLine("Name={0}, Length={1}", file.Name, file.Length);
}

Hay dos problemas con este código. El primer problema no debería sorprenderlo; igual que con File.ReadAllLines, se debe al hecho de que GetFiles devuelve una matriz. Antes de que GetFiles pueda volver, debe recuperar la lista completa de archivos en el directorio del sistema de archivos y, a continuación, asignar una matriz para volver. Esto significa que debe esperar a que todos los archivos se recuperen antes de obtener los primeros resultados y éste es un uso ineficaz de memoria. Si el directorio contiene un millón de archivos, deberá esperar hasta que se haya recuperado el 1.000.000 de archivos y se haya asignado una matriz con la longitud de un millón.

El segundo problema con el código anterior es un poco más sutil. Se crean instancias de FileInfo al pasar una ruta de acceso de archivo al constructor de FileInfo. Las propiedades de FileInfo como longitud y hora de creación se inicializan la primera vez se tiene acceso a una de las propiedades. Cuando se obtiene acceso por primera vez a una propiedad, se llama a FileInfo.Refresh que solicita al sistema operativo para que recupere las propiedades del archivo desde el sistema de archivos. Esto evita una llamada para recuperar los datos si nunca se usan las propiedades y, cuando se usan, ayuda a garantizar que los datos no sean obsoletos cuando se obtiene acceso a ellos por primera vez. Esto funciona excelente para las instancias de uso único de FileInfo, pero puede ser problemático cuando se enumera contenido de un directorio porque significa que se realizarán llamadas adicionales al sistema de archivos para obtener las propiedades de archivo. Estas llamadas adicionales pueden afectar al rendimiento cuando recorre los resultados. Esto es especialmente problemático si está enumerando el contenido de un archivo compartido remoto, porque significa una llamada de ida y vuelta adicional en el equipo remoto a través de la red.

En .NET 4, se han resuelto estos problemas. Para solucionar el primer problema, hemos agregado nuevos métodos en Directory y DirectoryInfo que devuelven IEnumerable<T> en lugar de matrices.

Como File.ReadLines, estos nuevos métodos basados en IEnumerable<T> son mucho más eficaces que los equivalentes anteriores basados en la matriz. Considere el siguiente código que se ha actualizado para usar DirectoryInfo de .NET 4. Método EnumerateFiles en lugar de DirectoryInfo.GetFiles:

DirectoryInfo directory = new DirectoryInfo(@"\\share\symbols");
IEnumerable<FileInfo> files = directory.EnumerateFiles();
foreach (var file in files) {
Console.WriteLine("Name={0}, Length={1}", file.Name, file.Length);
}

A diferencia de GetFiles, EnumerateFiles no tiene que bloquearse hasta que se recuperen todos los archivos ni tiene que asignar una matriz. En su lugar, hace la devolución inmediatamente y puede procesar cada archivo a medida que se devuelve desde el sistema de archivos.

Para solucionar el segundo problema, DirectoryInfo ahora usa los datos que el sistema operativo ya provee desde el sistema de archivos durante enumeración. Las funciones de Win32 subyacentes que llama Directory-Info para obtener el contenido del sistema de archivos durante la enumeración realmente incluyen datos sobre cada archivo, como por ejemplo, la hora de creación y la longitud. Ahora usamos estos datos al inicializar las instancias de FileInfo y DirectoryInfo devueltas desde el método anterior basado en la matriz como el método nuevo basado en IEnumerable<T> en DirectoryInfo. Esto significa que en el código anterior, no hay llamadas subyacentes adicionales para que el sistema de archivos recupere la longitud del archivo cuando se llama a file.Length, puesto que ya se han inicializado estos datos.

Juntos, los nuevos métodos basados en IEnumerable<T> en Archivo y Directorio permiten algunos escenarios interesantes. Observe el siguiente código:

var errorlines =
from file in Directory.EnumerateFiles(@"C:\logs", "*.log")
from line in File.ReadLines(file)
where line.StartsWith("Error:", StringComparison.OrdinalIgnoreCase)
select string.Format("File={0}, Line={1}", file, line);
File.WriteAllLines(@"C:\errorlines.log", errorlines);

Esto usa los nuevos métodos en el Directorio y Archivo, junto con LINQ, para buscar de forma eficiente archivos que tengan una extensión .log, en especial las líneas de esos archivos que comiencen con "Error:". La consulta proyecta los resultados en una nueva secuencia de cadenas, con cada cadena formateada para mostrar la ruta hacia el archivo y la línea de error. Por último, el archivo. WriteAllLines se usa para escribir las líneas de error en un nuevo archivo denominado "errorlines.log" sin necesidad de convertir las líneas de error en una matriz. Lo mejor de este código es que es muy eficaz. En ningún momento se introduce toda la lista de archivos en la memoria, ni se introduce todo el contenido de un archivo en la memoria. Independientemente de si C:\logs contiene 10 archivos o un millón de archivos y no importa si los archivos contienen 10 líneas o un millón de líneas, el código anterior es igualmente eficaz, y usa una cantidad mínima de memoria.

Archivos asignados en memoria

La compatibilidad para los archivos asignados en memoria es otra característica nueva en .NET Framework 4. Los archivos asignados en memoria se pueden usar para editar archivos grandes o crear una memoria compartida para una comunicación entre procesos (IPC). Los archivos asignados en memoria le permiten asignar un archivo en el espacio de dirección de un proceso. Una vez asignada, una aplicación puede simplemente leer o escribir en memoria para tener acceso o modificar el contenido del archivo. Puesto que se tiene acceso al archivo a través del administrador de memoria del sistema operativo, el archivo se divide automáticamente en un número de páginas que se paginan hacia adentro y hacia afuera de la memoria según sea necesario. Esto facilita el trabajo con archivos grandes, puesto que no es necesario controlar personalmente la administración de memoria. También permite un acceso aleatorio completo a un archivo sin la necesidad de buscar.

Se pueden crear archivos asignados en memoria sin un archivo de copia de seguridad. Tales archivos asignados en memoria están respaldados por el archivo de paginación del sistema (sólo si existe y el contenido debe ser paginado fuera de la memoria). Los archivos asignados en memoria pueden compartirse entre varios procesos, lo que significa que son un buen medio para crear una memoria compartida para la comunicación entre procesos. Cada asignación puede tener un nombre asociado que otros procesos puedan usar para abrir el mismo archivo asignado en memoria.

Para usar archivos asignados en memoria, primero debe crear una instancia de memoria-MappedFile mediante uno de los siguientes métodos de fábrica estáticos en la clase de System.IO.MemoryMappedFiles.MemoryMappedFile:

  • CreateFromFile
  • CreateNew
  • CreateOrOpen
  • OpenExisting

Después de esto, puede crear una o más vistas que realmente asignen el archivo al espacio de la dirección del proceso. Cada vista puede asignar todo el archivo asignado en memoria o parte de él, y las vistas pueden superponerse.

Puede ser necesario usar más de una vista si el archivo es mayor que el tamaño del espacio de memoria lógico del proceso disponible para la asignación (2 GB en un equipo de 32 bits). Puede crear una vista al llamar a los métodos CreateViewStream o CreateViewAccessor en el objeto de MemoryMappedFile. CreateViewStream devuelve una instancia de MemoryMappedFileViewStream, que hereda de System.IO.UnmanagedMemoryStream. Puede usarse como cualquier otra secuencia en el marco de trabajo. Por otro lado, CreateViewAccessor devuelve una instancia de MemoryMappedFileViewAccessor, que hereda del nuevo System.IO.UnmanagedMemoryAccessorclass. UnmanagedMemoryAccessor permite un acceso aleatorio, mientras que UnmanagedMemoryStream permite un acceso secuencial.

Nuevos métodos...

... en System.IO.File

  • IEnumerable<string>ReadLines (ruta de acceso de cadena) estática pública
  • WriteAllLines no válido, estático, público (ruta de acceso de cadena, contenido de IEnumerable<string>)
  • AppendAllLines no válido, estático, público (ruta de acceso de cadena, contenido de IEnumerable<string>)

... en System.IO.Directory

  • Enumerable<string>EnumerateDirectories(ruta de acceso de cadena) estático, público
  • IEnumerable<string>EnumerateFiles(ruta de acceso de cadena) estático, público
  • IEnumerable<string>EnumerateFileSystemEntries(ruta de acceso de cadena) estático, público

... en System.IO.DirectoryInfo

  • publicIEnumerable<DirectoryInfo>EnumerateDirectories()
  • publicIEnumerable<FileInfo>EnumerateFiles()
  • publicIEnumerable<FileSystemInfo>EnumerateFileSystemInfos()

En el siguiente ejemplo se muestra cómo usar archivos asignados en memoria para crear una memoria compartida para IPC. El proceso 1, como se muestra en la Figura 1, crea una nueva instancia de MemoryMappedFile mediante el método CreateNew que especifica el nombre del archivo asignado en memoria junto con la capacidad en bytes. Esto creará un archivo asignado en memoria respaldado por el archivo de paginación del sistema. Tenga en cuenta que internamente, la capacidad especificada se redondea al siguiente múltiplo del tamaño de página del sistema (si tiene curiosidad puede obtener el tamaño de página del sistema en Environment.System-PageSize, que es nueva en .NET 4). A continuación, se crea una secuencia de vista con CreateViewStream y “Hello Word!” se escribe en la secuencia mediante una instancia de BinaryWriter. Después, se inicia el segundo proceso.

El proceso 2, como se muestra en la Figura 2, abre el archivo asignado en memoria existente mediante el método OpenExisting, mientras se especifica el nombre apropiado del archivo asignado en memoria. Desde allí, se crea una secuencia de vista y la cadena se lee usando una instancia de BinaryReader.

Figura 1 Proceso 1

using (varmmf = MemoryMappedFile.CreateNew("mymappedfile", 1000))
using (var stream = mmf.CreateViewStream()) {
var writer = new BinaryWriter(stream);
writer.Write("Hello World!");
varstartInfo = new ProcessStartInfo("process2.exe");
startInfo.UseShellExecute = false;
Process.Start(startInfo).WaitForExit();
}

Figura 2 Proceso 2

using (varmmf = MemoryMappedFile.OpenExisting("mymappedfile"))
using (var stream = mmf.CreateViewStream()) {
var reader = new BinaryReader(stream);
Console.WriteLine(reader.ReadString());
}

SortedSet<T>

Además de las nuevas colecciones de System.Collections.Concurrent (parte de PFX), .NET Framework 4 incluye una nueva colección de conjuntos en System.Collections.Generic, denominada SortedSet<T>. Como HashSet<T>, que se agregó en .NET 3.5, SortedSet<T> es una colección de elementos únicos, pero a diferencia de HashSet<T>, SortedSet<T> mantiene los elementos ordenados.

SortedSet<T> se implementa mediante un árbol rojo-negro con autoequilibrio que proporciona una complejidad de rendimiento de O(log n) para insertar, eliminar y buscar. HashSet<T>, por otro lado, proporciona un rendimiento un poco mejor de O(1) para insertar, eliminar y buscar. Si sólo necesita un conjunto de propósito general, en la mayoría de los casos debe usar HashSet<T>. Sin embargo, si necesita mantener los elementos ordenados, obtener el subconjunto de elementos en un rango determinado u obtener el elemento máximo o mínimo, deseará usar SortedSet<T>. El siguiente código muestra el uso de SortedSet<T> con enteros:

var set1 = new SortedSet<int>() { 2, 5, 6, 2, 1, 4, 8 };
bool first = true;
foreach (var i in set1) {
if (first) {
first = false;
}
else {
Console.Write(",");
}
Console.Write(i);
}
// Output: 1,2,4,5,6,8

El conjunto se crea e inicializa mediante la sintaxis del inicializador de colección de C#. Tenga en cuenta que los enteros se agregan al conjunto en ningún orden en particular. Tenga en cuenta también que 2 se agrega dos veces. No debería sorprenderlo que al recorrer los elementos del set1, vea que los enteros están ordenados y que el conjunto incluye sólo un 2. Como HashSet<T>, el método de adición de SortedSet<T> tiene un tipo de devolución de bool que se puede usar para determinar si el elemento se agregó correctamente (true) o si no se agregó porque el conjunto ya contiene el elemento (false).

La Figura 3 muestra cómo obtener los elementos máximos y mínimos dentro del conjunto y un subconjunto de elementos en un rango determinado.

Figura 3 Obtener vista de elementos máxima, mínima y de subconjunto

var set1 = new SortedSet<int>() { 2, 5, 6, 2, 1, 4, 8 };
Console.WriteLine("Min: {0}", set1.Min);
Console.WriteLine("Max: {0}", set1.Max);
var subset1 = set1.GetViewBetween(2, 6);
Console.Write("Subset View: ");
bool first = true;
foreach (var i in subset1) {
if (first) {
first = false;
}
else {
Console.Write(",");
}
Console.Write(i);
}
// Output:
// Min: 1
// Max: 8
// Subset View: 2,4,5,6

El método GetViewBetween devuelve una vista del conjunto original. Esto significa que los cambios realizados en la vista se reflejarán en el original. Por ejemplo, si se agrega 3 a subset1 en el código anterior, realmente se agrega a set1. Tenga en cuenta que no se pueden agregar elementos a una vista fuera de los límites especificados. Por ejemplo, intentar agregar un 9 al subset1 en el código anterior dará como resultado una excepción de argumento porque la vista está entre 2 y 6.

Pruébelo

Las nuevas características de BCL analizadas en esta columna son una muestra de la nueva funcionalidad disponible en .NET Framework 4. Estas características están disponibles en el formulario de vista previa como parte de .NET Framework 4 beta 1, que se puede descargar junto con Visual Studio 2010 beta 1 enmsdn.microsoft.com/netframework/dd819232.aspx. Descargue la versión beta, pruebe la nueva funcionalidad y díganos qué piensa en connect.microsoft.com/VisualStudio/content/content.aspx?ContentID=12362. Asegúrese de estar atento al blog del equipo de BCL para conocer las próximas publicaciones sobre otras adiciones de BCL y un anuncio sobre qué es nuevo en la versión beta 2.

Justin Van Patten es administrador de programas en el equipo de CLR en Microsoft, donde trabaja en las bibliotecas de clase base. Puede comunicarse con él a través del blog del equipo de BCL en blogs.msdn.com/bclteam.