Está en la página 1de 2947

Contents

Documentación de .NET
Introducción (día 1)
Hola mundo
Tutoriales de introducción
Vídeos de .NET 101
Cómo instalar
Información general
Instalar en Windows
Instalación en macOS
Instalar en Linux
Información general
Ubuntu
Alpine
CentOS
Debian
Fedora
OpenSUSE
Red Hat Enterprise Linux
SLES
Eliminación de entornos de ejecución y SDK obsoletos
Administración de plantillas de .NET
Incidencias de certificación notarial en macOS
Cómo comprobar las versiones de .NET Core
Instalación de una versión localizada de IntelliSense
Información general (semana 1)
Paseo por .NET
Componentes de la arquitectura .NET
Bibliotecas de clases de .NET
Información general sobre .NET Core
Información general de .NET Standard
Glosario de .NET
Tutoriales
Usar Visual Studio
Crear una aplicación de consola
Depuración de una aplicación
Publicar una aplicación
Creación de una biblioteca
Prueba unitaria de una biblioteca
Instalar y usar un paquete
Crear y publicar un paquete
Usar Visual Studio Code
Crear una aplicación de consola
Depuración de una aplicación
Publicar una aplicación
Creación de una biblioteca
Prueba unitaria de una biblioteca
Instalar y usar un paquete
Crear y publicar un paquete
Uso de Visual Studio para Mac
Crear una aplicación de consola
Depuración de una aplicación
Publicar una aplicación
Creación de una biblioteca
Prueba unitaria de una biblioteca
Instalar y usar un paquete
Más tutoriales
Novedades
Evolución de .NET Core a .NET 5
Novedades de .NET Core 3.1
Novedades de .NET Core 3.0
Novedades de .NET Core 2.2
Novedades de .NET Core 2.1
Novedades de .NET Core 2.0
Novedades de .NET Standard
Herramientas y diagnósticos
Información general sobre el SDK de .NET Core
CLI de .NET Core
Información general
Referencia
dotnet
dotnet build
dotnet build-server
dotnet clean
dotnet help
dotnet migrate
dotnet msbuild
dotnet new
dotnet nuget
dotnet nuget delete
dotnet nuget locals
dotnet nuget push
dotnet nuget add source
dotnet nuget disable source
dotnet nuget enable source
dotnet nuget list source
dotnet nuget remove source
dotnet nuget update source
dotnet pack
dotnet publish
dotnet restore
dotnet run
dotnet sln
dotnet store
dotnet test
dotnet tool
dotnet tool install
dotnet tool list
dotnet tool restore
dotnet tool run
dotnet tool uninstall
dotnet tool update
dotnet vstest
Scripts de dotnet-install
Comandos de referencia del proyecto
dotnet add reference
dotnet list reference
dotnet remove reference
Comandos del paquete del proyecto
dotnet add package
dotnet list package
dotnet remove package
Herramientas globales y locales
Administración de herramientas
Herramientas de solución de problemas
Creación de herramientas para la CLI
1 - Creación de una herramienta
2 - Uso de una herramienta global
3- Uso de una herramienta local
Información general de global.json
Telemetría
Acceso elevado
Habilitación de la finalización con tabulación
Integración continua con la CLI
Desarrollo de bibliotecas con la CLI
Creación de plantillas para la CLI
Plantillas personalizadas
1 - Crear una plantilla de elemento
2 - Crear una plantilla de proyecto
3 - Crear un paquete de plantillas
MSBuild y archivos del proyecto
SDK de proyecto
Información general
Referencia
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Razor
Versiones de .NET Framework de destino
Adiciones al formato csproj
Administración de dependencias
Herramientas adicionales de .NET Core
Información general
Herramienta de desinstalación de .NET Core
Proveedor de WCF Web Service Reference
dotnet-svcutil
dotnet-svcutil.xmlserializer
Generador de serializador XML
Herramientas de diagnóstico
Información general
Depuradores administrados
Depuración de volcados de memoria de Linux
EventCounters
Registro y seguimiento
Herramientas globales de la CLI de .NET Core
dotnet-counters
dotnet-dump
dotnet-gcdump
dotnet-trace
dotnet-symbol
dotnet-sos
Tutoriales de diagnóstico de .NET Core
Obtención de métricas de rendimiento con EventCounters
Depuración de una fuga de memoria
Depuración del uso elevado de CPU
Depuración de interbloqueo
Análisis de código
Información general
Configuración de reglas
Analizador de API
Analizador de portabilidad
Modelo de ejecución
Common Language Runtime (CLR)
Proceso de ejecución administrada
Ensamblados de .NET
Metadatos y componentes autodescriptivos
Carga de dependencias
Carga de dependencias
Descripción de AssemblyLoadContext
Detalles de la carga de dependencias
Sondeo de dependencia predeterminado
Carga de ensamblados administrados
Carga de ensamblados satélite
Carga de bibliotecas no administradas
Tutoriales
Creación de una aplicación de .NET Core con complementos
Uso y depuración de la descargabilidad de ensamblado en .NET Core
Control de versiones
Información general
Selección de la versión de .NET Core
Configuración del entorno en tiempo de ejecución
Configuración
Configuración de la compilación
Depuración y configuración de perfiles
Nombre de recolector de elementos no utilizados
Configuración de la globalización
Configuración de redes
Configuración de subprocesos
Modelos de implementación
Información general
Implementación de aplicaciones con Visual Studio
Publicación de aplicaciones con la CLI
Creación de un paquete NuGet con la CLI
Puesta al día del runtime de implementación autocontenida
Implementación de archivo único y ejecutable
Recorte de implementaciones autocontenidas y ejecutables
Almacenamiento de paquetes en tiempo de ejecución
Catálogo de identificadores de tiempo de ejecución (RID)
Nombres del manifiesto de recurso
Docker
Introducción a .NET y Docker
Incluir una aplicación de .NET Core en un contenedor
Herramientas de contenedor de Visual Studio
Componentes fundamentales de la codificación
Información general de los tipos base
Common Type System y Common Language Specification
Sistema de tipos comunes
Independencia de lenguaje
Componentes independientes del lenguaje
Conversión de tipos en .NET
Tablas de conversión de tipos
Bibliotecas del marco
Introducción a la biblioteca de clases
Tipos genéricos
Información general
Introducción a los tipos genéricos
Colecciones genéricas en .NET
Delegados genéricos para manipular matrices y listas
Interfaces genéricas
Covarianza y contravarianza
Colecciones y estructuras de datos
Información general
Seleccione una clase de colección
Tipos de colección utilizados normalmente
Cuándo utilizar colecciones genéricas
Comparaciones y ordenaciones en colecciones
Tipos de colecciones ordenadas
Tipos Hashtable y Dictionary
Colecciones seguras para subprocesos
Delegados y expresiones lambda
Events
Información general
Provocación y consumo de eventos
Control de varios eventos mediante propiedades de evento
Modelo de diseño de observador
Información general
Procedimientos recomendados
Procedimiento Implementación de un proveedor
Procedimiento Implementación de un observador
Excepciones
Información general
Clase Exception y propiedades
Temas procedimentales
Uso del bloque Try/Catch para detectar excepciones
Uso de excepciones específicas en un bloque Catch
Inicio de excepciones explícitamente
Creación de excepciones definidas por el usuario
Creación de excepciones definidas por el usuario con mensajes de excepción
localizados
Uso de bloques Finally
Uso de controladores de excepciones filtradas por el usuario
Control de excepciones de interoperabilidad COM
Procedimientos recomendados
Tipos numéricos
Fechas, horas y zonas horarias
Formato de números, fechas y otros tipos
Información general
Cadenas con formato numérico estándar
Cadenas con formato numérico personalizado
Cadenas con formato de fecha y hora estándar
Cadenas con formato de fecha y hora personalizado
Cadenas de formato TimeSpan estándar
Cadenas de formato TimeSpan personalizado
Cadenas de formato de enumeración
Formatos compuestos
Temas procedimentales
Relleno de un número con ceros a la izquierda
Extracción del día de la semana de una fecha
Uso de proveedores de formato numérico personalizado
Aplicación de acciones de ida y vuelta a valores de fecha y hora
Visualización de los milisegundos en valores de fecha y hora
Visualización de las fechas en calendarios no gregorianos
Manipulación de las cadenas
Codificación de caracteres de .NET
Procedimiento para usar las clases de codificación de caracteres
Procedimientos recomendados para el uso de cadenas
Operaciones básicas de cadenas
Información general
Creación de nuevas cadenas
Recorte y eliminación de caracteres
Relleno de cadenas
Comparación de cadenas
Cambio de mayúsculas y minúsculas
Uso de la clase StringBuilder
Procedimiento para realizar manipulaciones básicas de cadena
Expresiones regulares en .NET
Información general
Referencia del lenguaje
Información general
Escapes de carácter
Clase de caracteres
Delimitadores
Construcciones de agrupamiento
Cuantificadores
Construcciones de referencia inversa
Construcciones de alternancia
Sustituciones
Opciones de expresiones regulares
Construcciones misceláneas
Procedimientos recomendados con expresiones regulares
Modelo de objetos de expresiones regulares
Comportamiento de expresiones regulares
Información general
Retroceso
Compilación y reutilización
Seguridad para subprocesos
Ejemplos
Búsqueda de HREF
Cambio de formatos de fecha
Extracción de un protocolo y un número de puerto de una URL
Eliminación de caracteres no válidos de una cadena
comprobar que las cadenas están en un formato de correo electrónico válido
Análisis de las cadenas
Información general
Análisis de cadenas numéricas
Análisis de cadenas de fecha y hora
Análisis de otras cadenas
Atributos
Información general
Aplicación de atributos
Escritura de atributos personalizados
Recuperación de la información almacenada en atributos
Elección entre tipos de tupla y anónimos
Extensiones de Microsoft
Configuración
Inserción de dependencias
Registro
Acceso a datos
LINQ
Documentos y datos XML
Microsoft.Data.Sqlite
Procesamiento paralelo, simultaneidad y asincronía
Información general
Programación asincrónica
Información general
Programación asincrónica en detalle
Patrones para la programación asincrónica
Programación en paralelo
Información general
Biblioteca de procesamiento paralelo basado en tareas (TPL)
Paralelismo de datos
Procedimiento para escribir un bucle Parallel.For simple
Procedimiento para escribir un bucle Parallel.ForEach sencillo
Procedimiento para escribir un bucle Parallel.For con variables locales de
subproceso
Procedimiento para escribir un bucle Parallel.ForEach con variables locales de
partición
Procedimiento para cancelar un bucle Parallel.For o ForEach
Procedimiento para controlar excepciones en bucles paralelos
Procedimiento para acelerar cuerpos de bucle pequeños
Procedimiento Iteración de directorios de archivos con la clase Parallel
Programación asincrónica basada en tareas
Encadenar tareas mediante tareas de continuación
Tareas secundarias asociadas y desasociadas
Cancelación de tareas
Control de excepciones
Procedimiento para usar Parallel.Invoke para ejecutar operaciones en paralelo
Procedimiento para devolver un valor a partir de una tarea
Procedimiento para cancelar una tarea y sus elementos secundarios
Procedimiento Creación de tareas precalculadas
Procedimiento para recorrer un árbol binario con tareas en paralelo
Procedimiento para desencapsular una tarea anidada
Procedimiento Evitar que una tarea secundaria se adjunte a su elemento primario
Flujo de datos
Procedimiento Escritura y lectura de mensajes en un bloque de flujo de datos
Procedimiento Implementación de un modelo de flujo de datos productor-
consumidor
Procedimiento Toma de medidas cuando un bloque de flujos de datos recibe
datos
Tutorial: Creación de una canalización de flujos de datos
Procedimiento Desvinculación de bloques de flujos de datos
Tutorial: Uso de flujos de datos en aplicaciones de Windows Forms
Procedimiento para cancelar un bloque de flujo de datos
Tutorial: Creación de tipos de bloques de flujos de datos personalizados
Procedimiento Uso de JoinBlock para leer datos de diferentes orígenes
Procedimiento Especificación del grado de paralelismo en un bloque de flujos de
datos
Procedimiento para especificar un programador de tareas en un bloque de flujos
de datos
Tutorial: Uso de BatchBlock y BatchedJoinBlock para mejorar la eficacia
Usar TPL con otros patrones asincrónicos
TPL y la programación asincrónica tradicional de .NET Framework
Procedimiento para encapsular patrones de EAP en una tarea
Problemas potenciales en el paralelismo de datos y tareas
Parallel LINQ (PLINQ)
Introducción a PLINQ
Introducción a la velocidad en PLINQ
Conservar el orden en PLINQ
Opciones de fusión mediante combinación en PLINQ
Posibles problemas con PLINQ
Procedimiento para crear y ejecutar una consulta PLINQ simple
Procedimiento para controlar la ordenación en una consulta PLINQ
Procedimiento para combinar consultas LINQ paralelas y secuenciales
Procedimiento para controlar excepciones en una consulta PLINQ
Procedimiento para cancelar una consulta PLINQ
Procedimiento para escribir una función de agregado personalizada de PLINQ
Procedimiento para especificar el modo de ejecución en PLINQ
Procedimiento para especificar opciones de fusión mediante combinación en
PLINQ
Procedimiento para iterar directorios con PLINQ
Procedimiento para medir el rendimiento de consultas PLINQ
Ejemplo de datos de PLINQ
Estructuras de datos para la programación paralela
Herramientas de diagnóstico paralelo
Particionadores personalizados para PLINQ y TPL
Información general
Procedimiento para implementar particiones dinámicas
Procedimiento para implementar un particionador para particionamiento estático
Expresiones lambda en PLINQ y TPL
Información adicional
Subprocesos
Serialización
Información general
Serialización de JSON
Información general
Cómo serializar y deserializar JSON
Procedimientos para escribir convertidores personalizados
Migración desde Newtonsoft.json
Serialización binaria
Información general
Guía de seguridad de BinaryFormatter
Conceptos de serialización
Serialización básica
Serialización selectiva
Serialización personalizada
Pasos del proceso de serialización
Serialización tolerante a versiones
Directrices de serialización
Procedimiento Fragmentación de datos serializados
Procedimiento para determinar si un objeto de .NET Standard es serializable
Ejemplo
Serialización de SOAP y XML
Información general
Serialización XML en profundidad
Ejemplos
Herramienta de definición de esquemas XML
Control de la serialización XML mediante atributos
Atributos que controlan la serialización XML
Serialización XML con servicios web XML
Atributos que controlan la serialización SOAP codificada
Temas procedimentales
Serialización de un objeto
Deserialización de un objeto
Uso de la herramienta de definición de esquemas XML para generar clases y
documentos de esquema XML
Control de la serialización de clases derivadas
Especificación de un nombre de elemento alternativo para una secuencia XML
Calificación del elemento XML y los nombres del atributo XML
Serialización de un objeto como secuencia XML con codificación SOAP
Invalidación de la serialización XML SOAP codificada
Elementos de serialización XML
system.xml.serialization
dateTimeSerialization
schemaImporterExtensions
agregar elemento para schemaImporterExtensions
xmlSerializer
Herramientas
Herramienta Generador de serializador XML (Sgen.exe)
Herramienta Definición de esquemas XML (Xsd.exe)
E/S de archivos y secuencias
Información general
Formatos de ruta de acceso de archivo en los sistemas Windows
Tareas de E/S comunes
Procedimiento para copiar directorios
Procedimiento para enumerar directorios y archivos
Procedimiento para leer y escribir en un archivo de datos recién creado
Procedimiento para abrir y anexar a un archivo de registro
Procedimiento para escribir texto en un archivo
Procedimiento para leer texto de un archivo
Procedimiento para leer caracteres de una cadena
Procedimiento para escribir caracteres en una cadena
Procedimiento para agregar o quitar entradas de la lista de control de acceso
Procedimiento para comprimir y extraer archivos
Crear secuencias
Procedimiento para convertir flujos de .NET Framework en flujos de Windows
Runtime y viceversa
E/S de archivos asincrónica
Control de errores de E/S de disco
Almacenamiento aislado
Tipos de aislamiento
Procedimiento para obtener los almacenes de almacenamiento aislado
Procedimiento para enumerar los almacenes de almacenamiento aislado
Procedimiento para eliminar almacenes de almacenamiento aislado
Procedimiento para prever condiciones de espacio insuficiente con almacenamiento
aislado
Procedimiento para crear archivos y directorios en almacenamiento aislado
Procedimiento para buscar archivos y directorios existentes en almacenamiento
aislado
Procedimiento para leer y escribir en archivos en almacenamiento aislado
Procedimiento para eliminar archivos y directorios en almacenamiento aislado
Canalizaciones
Procedimiento para usar canalizaciones anónimas para la comunicación local entre
procesos
Procedimiento para usar canalizaciones con nombre para la comunicación de red
entre procesos
Canalizaciones
Trabajo con búferes
Archivos asignados a memoria
Pruebas unitarias
Información general
Procedimientos recomendados para pruebas unitarias
xUnit
Pruebas unitarias de C#
Pruebas unitarias de F#
Pruebas unitarias de VB
Organización de un proyecto y prueba con xUnit
NUnit
Pruebas unitarias de C#
Pruebas unitarias de F#
Pruebas unitarias de VB
MSTest
Pruebas unitarias de C#
Pruebas unitarias de F#
Pruebas unitarias de VB
Ejecución de pruebas unitarias selectivas
Ordenación de pruebas unitarias
Cobertura de código de prueba unitaria
Salida publicada de una prueba unitaria
Pruebas unitarias dinámicas de proyectos de .NET Core con Visual Studio
Seguridad
Temas avanzados
Rendimiento
Administración de memoria
¿Qué es el "código administrado"?
Administración de memoria automática
Limpieza de recursos no administrados
Información general
Implementación de un método Dispose
Implementación de un método DisposeAsync
Uso de objetos que implementan IDisposable
Recolección de elementos no utilizados
Información general
Aspectos básicos
Estación de trabajo y GC de servidor
GC en segundo plano
El montón de objetos grandes
Recolección de elementos no utilizados y rendimiento
Recolecciones inducidas
Modos de latencia
Optimización de hospedaje web compartido
Notificaciones de recolección de elementos no utilizados
Supervisión de recursos de dominio de aplicación
Referencias parciales
Tipos relacionados con el intervalo y la memoria
Información general
Instrucciones de uso de Memory<T> y Span<T>
Tipos habilitados para SIMD
Interoperabilidad nativa
Información general
P/Invoke
Serialización de tipos
Personalización de la serialización de estructuras
Personalización de la serialización de parámetros
Guía de interoperabilidad
Juegos de caracteres y serialización
Exposición de los componentes de .NET Core a COM
Hospedaje de .NET Core desde código nativo
Interoperabilidad COM
Información general
Contenedores COM
Información general
Contenedor al que se puede llamar en tiempo de ejecución
Contenedor CCW (COM callable wrapper)
Habilitación de tipos de .NET para la interoperabilidad COM
Aplicación de atributos de interoperabilidad
Excepciones
Empaquetado de distribución de .NET Core
Globalización y localización
Guía de la biblioteca de código abierto
Instrucciones de diseño de los marcos
Información general
Directrices de nomenclatura
Normas referentes al uso de minúsculas y mayúsculas
Convenciones generales de nomenclatura
Nombres de ensamblados y DLL
Nombres de espacios de nombres
Nombres de clases, estructuras e interfaces
Nombres de miembros de tipos
Asignación de nombres a parámetros
Asignación de nombres a recursos
Instrucciones de diseño de tipos
Elección entre clases y estructuras
Diseño de clases abstractas
Diseño de clases estáticas
Diseño de interfaces
Diseño de estructuras
Diseño de enumeraciones
Tipos anidados
Instrucciones de diseño de miembros
Sobrecarga de miembros
Diseño de propiedades
Diseño de constructores
Diseño de eventos
Diseño de campos
Métodos de extensión
Sobrecargas de operadores
Diseño de parámetros
Diseño para la extensibilidad
Clases no selladas
Miembros protegidos
Eventos y devoluciones de llamadas
Miembros virtuales
Abstracciones (tipos e interfaces abstractos)
Clases base para la implementación de abstracciones
Sellar
Instrucciones de diseño de excepciones
Generación de excepciones
Uso de tipos de excepciones estándar
Excepciones y rendimiento
Instrucciones de uso
Matrices
Atributos
Colecciones
Serialización
Uso de System.Xml
Operadores de igualdad
Patrones de diseño comunes
Propiedades de dependencia
Guía de migración
Últimos cambios
Migración
.NET Core 2.0 a 2.1
Migración desde project.json
Asignación entre project.json y csproj
Cambios en la información general de la CLI
Migración desde DNX
Portabilidad de .NET Framework
Información general
Análisis de las dependencias de terceros
Portabilidad de las bibliotecas
Organización de proyectos para .NET Core
Tecnologías no disponibles
Herramientas
Uso del paquete de compatibilidad de Windows
Portabilidad de los proyectos de Windows Forms
Portabilidad de los proyectos de WPF
Portabilidad de los proyectos de C++/CLI
Selección entre .NET Core y .NET Framework para aplicaciones de servidor
Introducción a .NET Core
16/09/2020 • 3 minutes to read • Edit Online

En este artículo se proporciona información sobre cómo comenzar con .NET Core. .NET Core se puede instalar en
Windows, Linux y macOS. Puede programar en su editor de texto preferido y crear aplicaciones y bibliotecas
multiplataforma.
Si no está seguro de qué es .NET Core o cómo se relaciona con otras tecnologías .NET, comience con la información
general ¿Qué es .NET?. En resumen, .NET Core es una implementación multiplataforma de código abierto de .NET.

Crear una aplicación


En primer lugar, descargue e instale el SDK de .NET Core en el equipo.
A continuación, abra un terminal como PowerShell , Símbolo del sistema o bash . Escriba los comandos dotnet
siguientes para crear y ejecutar una aplicación C#:

dotnet new console --output sample1


dotnet run --project sample1

Debería ver los siguientes resultados:

Hello World!

¡Enhorabuena! Ha creado una sencilla aplicación .NET Core. También puede usar Visual Studio Code, Visual Studio
(solo Windows) o Visual Studio para Mac (solo macOS), para crear una aplicación .NET Core.

Tutoriales
Para comenzar a desarrollar aplicaciones .NET Core, puede seguir estos tutoriales paso a paso:
Windows
Linux
macOS
Creación de su primera aplicación de consola con .NET Core en Visual Studio 2019
Compilación de una biblioteca de clases con .NET Standard en Visual Studio
Tutorial: Depuración de una aplicación de consola de .NET Core con Visual Studio Code

Vea el vídeo de Channel 9 sobre cómo instalar y usar Visual


Studio Code y .NET Core.

Vea los vídeos .NET Core 101 en YouTube.

Vea el artículo Dependencias y requisitos de .NET Core para obtener una lista de las versiones de Windows
admitidas.
Primeros pasos
09/04/2020 • 2 minutes to read • Edit Online

Hay varias maneras de empezar a trabajar con .NET. Dado que .NET es una plataforma masiva, existen varios
artículos en esta documentación que pueden ayudar a trabajar con. NET, cada uno desde una perspectiva distinta.

Introducción al uso de lenguajes .NET


Si quiere obtener tutoriales de introducción de C#, Visual Basic y F#, vea lo siguiente:
Introducción a C#
Tutoriales de C#
Tutoriales de introducción de F#
Introducción a Visual Basic

Introducción al uso de .NET Core


Para obtener una guía específica de .NET Core, vea lo siguiente:
Introducción a .NET Core
Tutoriales de .NET Core

Introducción al uso de .NET Standard


Para obtener un tutorial de introducción, vea Creación de una biblioteca de .NET Standard en Visual Studio.

Introducción al uso de .NET Core en Docker


Introducción a .NET y Docker muestra cómo usar .NET Core en contenedores de Docker de Windows.
Instalación de .NET Core en Windows
16/09/2020 • 18 minutes to read • Edit Online

En este artículo obtendrá información sobre cómo instalar .NET Core en Windows. .NET Core está formado por el
entorno de ejecución y el SDK. El tiempo de ejecución se usa para ejecutar una aplicación de .NET Core, y puede o
no incluirse con la aplicación. El SDK se usa para crear aplicaciones y bibliotecas de .NET Core. El entorno de
ejecución de .NET Core siempre se instala con el SDK.
La versión más reciente de .NET Core es la 3.1.
DESCARGAR .NET
CORE

Versiones compatibles
En la tabla siguiente se muestra una lista de versiones de .NET Core actualmente compatibles y las versiones de
Windows en las que se admiten. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core
llega al fin del soporte técnico o la versión de Windows llega al final del ciclo de vida.
Las fechas de fin de servicio de Windows 10 están segmentadas por edición. En la tabla que hay a continuación
solo se tienen en cuenta las ediciones Home , Pro , Pro Education y Pro for Workstations . Para ver detalles
específicos, consulte la hoja informativa sobre el ciclo de vida de Windows.
Con la marca ✔
️ se indica que todavía se admite la versión de Windows o .NET Core.
Con la marca ❌ se indica que la versión de Windows o .NET Core no se admite en esa versión de Windows.
Cuando una versión de Windows y una versión de .NET Core tienen una marca ✔️ , significa que se admite esa
combinación de sistema operativo y .NET.

. N ET 5 ( VERSIÓ N
SIST EM A O P ERAT IVO . N ET C O RE 2. 1 . N ET C O RE 3. 1 P REL IM IN A R)

✔ Windows 10,
️ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

versión 2004

✔ Windows 10,
️ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

versión 1909

✔ Windows 10,
️ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

versión 1903

✔ Windows 10,
️ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

versión 1809

❌ Windows 10, ️ 2.1


✔ ❌ 3.1 ❌ 5.0 (versión preliminar)
versión 1803

❌ Windows 10, ❌ 2.1 ❌ 3.1 ❌ 5.0 (versión preliminar)


versión 1709

❌ Windows 10, ❌ 2.1 ❌ 3.1 ❌ 5.0 (versión preliminar)


versión 1703
. N ET 5 ( VERSIÓ N
SIST EM A O P ERAT IVO . N ET C O RE 2. 1 . N ET C O RE 3. 1 P REL IM IN A R)

❌ Windows 10, ❌ 2.1 ❌ 3.1 ❌ 5.0 (versión preliminar)


versión 1607

❌ Windows 10, ❌ 2.1 ❌ 3.1 ❌ 5.0 (versión preliminar)


versión 1511

❌ Windows 10, ❌ 2.1 ❌ 3.1 ❌ 5.0 (versión preliminar)


versión 1507

Versiones no admitidas
Las siguientes versiones de .NET Core ya no se admiten ❌. Las descargas de estas siguen estando publicadas:
3.0
2.2
2.0

Información en tiempo de ejecución


El entorno de ejecución de .NET Core se usa para ejecutar aplicaciones creadas con .NET Core. Cuando un autor
publica una aplicación, puede incluir el tiempo de ejecución. Si no lo hace, el usuario elige si quiere instalar el
tiempo de ejecución.
Hay tres entornos de ejecución distintos que se pueden instalar en Windows:
Entorno de ejecución de ASP.NET Core
Ejecuta aplicaciones de ASP.NET Core. Incluye el entorno de ejecución de .NET Core.
Entorno de ejecución de escritorio
Ejecuta aplicaciones de escritorio de .NET Core WPF y NET Core de Windows Forms para Windows. Incluye el
entorno de ejecución de .NET Core.
Entorno de ejecución de .NET Core
Este entorno de ejecución es el más sencillo y no incluye ningún otro. Le recomendamos encarecidamente que
instale el entorno de ejecución de ASP.NET Core y el entorno de ejecución de escritorio para conseguir la mejor
compatibilidad con las aplicaciones de .NET Core.
D E S C A R G A D E L E N TO R N O D E E J E C U C I Ó N D E
.NET CO RE

Información del SDK


El SDK se usa para compilar y publicar aplicaciones y bibliotecas de .NET Core. La instalación del SDK incluye los
tres entornos de ejecución: el de ASP.NET Core, el de escritorio y el de .NET Core.
D O W NLO AD .NET CO R E (Descarga del SDK de .NET Core)
SDK

Dependencias
.NET Core 3.1
.NET Core 3.0
.NET Core 2.2
.NET Core 2.1
Las versiones siguientes de Windows son compatibles con .NET Core 3.1:

NOTE
Un símbolo + representa la versión mínima.

SO VERSIÓ N A RQ UIT EC T URA S

Cliente Windows 8.1 x64, x86

Cliente de Windows 10 Versión 1609+ x64, x86

Windows Server 2012 R2 y posteriores x64, x86

Nano Server Versión 1803 y posteriores x64, ARM32

Para obtener más información sobre los sistemas operativos compatibles con .NET Core 3.1, las distribuciones y la
directiva del ciclo de vida, vea Versiones de SO compatibles con .NET Core 3.1.
Windows 7 / Vista / 8.1 / Server 2008 R2 / Server 2012 R2
Se necesitan dependencias adicionales en caso de instalar el SDK o el entorno de ejecución de .NET en las
versiones siguientes de Windows:
❌ Windows 7 SP1
❌ Windows Vista SP2
️ Windows 8.1

️ Windows Server 2008 R2

️ Windows Server 2012 R2

Instale el software siguiente:
Microsoft Visual C++ 2015 Redistributable Update 3.
KB2533623
Los requisitos anteriores también son necesarios si se encuentra con uno de los errores siguientes:

El programa no se puede iniciar porque el archivo api-ms-win-crt-runtime-l1-1-0.dll falta en el equipo. Intente


volver a instalar el programa para corregir este problema.
-o-
El programa no se puede iniciar porque falta el archivo api-ms-win-cor-timezone-l1-1-0.dll en el equipo.
Intente volver a instalar el programa para corregir este problema.
-o-
La biblioteca hostfxr.dll se ha encontrado, pero no se ha podido cargar desde C:\<path_to_app>\hostfxr.dll.

Instalación mediante la automatización de PowerShell


Los scripts de dotnet-install se usan para la automatización de CI y las instalaciones que no son de administrador
del entorno de ejecución. Se puede descargar el script desde la página de referencia del script dotnet-install.
El valor predeterminado del script es instalar la versión más reciente de soporte técnico a largo plazo (LTS), que
actualmente es .NET Core 3.1. Puede elegir una versión concreta especificando el modificador Channel . Incluya el
modificador Runtime para instalar un entorno de ejecución. De lo contrario, el script instala el SDK.

dotnet-install.ps1 -Channel 3.1 -Runtime aspnetcore

Instale el SDK omitiendo el modificador -Runtime . El modificador -Channel de este ejemplo está establecido en
Current , con lo que se instala la versión admitida más reciente.

dotnet-install.ps1 -Channel Current

Instalación con Visual Studio


Si usa Visual Studio para desarrollar aplicaciones de .NET Core, en la tabla siguiente se describe la versión mínima
necesaria de Visual Studio, basada en la versión del SDK de .NET Core de destino.

VERSIÓ N DEL SDK DE . N ET C O RE VERSIÓ N DE VISUA L ST UDIO

3.1 Visual Studio 2019, versión 16.4 o posterior.

3.0 Visual Studio 2019, versión 16.3 o posterior.

2.2 Visual Studio 2017, versión 15.9 o posterior.

2.1 Visual Studio 2017, versión 15.7 o posterior.

Si ya tiene Visual Studio instalado, puede comprobar la versión siguiendo los pasos que se detallan a
continuación.
1. Abra Visual Studio.
2. Seleccione Ayuda > Acerca de Microsoft Visual Studio .
3. Lea el número de versión en el cuadro de diálogo Acerca de .
Visual Studio puede instalar el SDK y el entorno de ejecución de .NET Core más recientes.
DESCARGUE .
V IS U A L S TU D IO

Selección de una carga de trabajo


Al instalar o modificar Visual Studio, seleccione una de las cargas de trabajo siguientes o más, en función del tipo
de aplicación que quiera compilar:
La carga de trabajo Desarrollo multiplataforma de .NET Core en la sección Otros conjuntos de
herramientas .
La carga de trabajo Desarrollo de ASP.NET y web en la sección Web y nube .
La carga de trabajo Desarrollo de Azure en la sección Web y nube .
La carga de trabajo Desarrollo de escritorio de .NET en la sección Móviles y de escritorio .
Instalación junto con Visual Studio Code
Visual Studio Code es un editor de código fuente ligero y eficaz que se ejecuta en el escritorio. Visual Studio Code
está disponible para Windows, macOS y Linux.
Aunque Visual Studio Code no viene con un instalador automatizado de .NET Core como Visual Studio, agregar
compatibilidad con .NET Core es sencillo.
1. Descargue e instale Visual Studio Code.
2. Descargue e instale el SDK de .NET Core.
3. Instale la extensión de C# desde el Marketplace de Visual Studio Code.

Descarga e instalación de forma manual


Como alternativa a los instaladores de Windows para .NET Core, puede descargar e instalar manualmente el SDK
o el entorno de ejecución. La instalación manual se suele llevar a cabo durante las pruebas de integración
continua. Para un desarrollador o usuario, generalmente es mejor usar un instalador.
Tanto el SDK de .NET Core como .NET Core Runtime se pueden instalar manualmente una vez que se han
descargado. Si instala el SDK de .NET Core, no necesita instalar el entorno de ejecución correspondiente. En
primer lugar, descargue una versión binaria del SDK o del entorno de ejecución de uno de los siguientes sitios:
✔ Descargas de la versión preliminar de .NET 5.0

️ Descargas de .NET Core 3.1

️ Descargas de .NET Core 2.1

Todas las descargas de .NET Core
Cree un directorio en el que se extraerá .NET; por ejemplo, %USERPROFILE%\dotnet . Después, extraiga el archivo ZIP
descargado en ese directorio.
De forma predeterminada, los comandos y las aplicaciones de la CLI de .NET Core no usarán la versión de .NET
Core instalada de esta manera y debe elegir explícitamente usarla. Para ello, cambie las variables de entorno con
las que se inicia una aplicación:

set DOTNET_ROOT=%USERPROFILE%\dotnet
set PATH=%USERPROFILE%\dotnet;%PATH%
set DOTNET_MULTILEVEL_LOOKUP=0
Este enfoque permite instalar varias versiones en ubicaciones independientes y elegir explícitamente qué
ubicación de instalación debe usar una aplicación mediante la ejecución de la aplicación con variables de entorno
que apuntan a esa ubicación.
Cuando DOTNET_MULTILEVEL_LOOKUP se establece en 0 , .NET Core ignora cualquier versión de .NET Core instalada
globalmente. Elimine esa configuración de entorno para que .NET Core tenga en cuenta la ubicación de instalación
global predeterminada al seleccionar el mejor marco para ejecutar la aplicación. La ubicación predeterminada
suele ser C:\Program Files\dotnet , que es la ruta de instalación predeterminada de .NET Core.

Docker
Los contenedores proporcionan una manera ligera de aislar la aplicación del resto del sistema host. Los
contenedores de la misma máquina comparten solo el kernel y usan los recursos proporcionados a la aplicación.
.NET Core puede ejecutarse en un contenedor de Docker. Las imágenes oficiales de Docker en .NET Core se
publican en el registro de contenedor de Microsoft (MCR) y se pueden encontrar en el repositorio de Docker Hub
para Microsoft .NET Core. Cada repositorio contiene imágenes para diferentes combinaciones de .NET (SDK o
Runtime) y del sistema operativo que puede usar.
Microsoft ofrece imágenes que se adaptan a escenarios específicos. Por ejemplo, el repositorio de ASP.NET Core
proporciona imágenes que se compilan para ejecutar aplicaciones de ASP.NET Core en producción.
Para obtener más información sobre el uso de .NET Core en un contenedor de Docker, vea Introducción a .NET y
Docker y Ejemplos.

Pasos siguientes
Cómo comprobar que .NET Core ya está instalado.
Tutorial: Tutorial Hola mundo.
Tutorial: Creación de una aplicación con Visual Studio Code.
Tutorial: Inclusión de una aplicación de .NET Core en un contenedor.
Instalación de .NET Core en macOS
16/09/2020 • 14 minutes to read • Edit Online

En este artículo obtendrá información sobre cómo instalar .NET Core en macOS. .NET Core está formado por el
entorno de ejecución y el SDK. El tiempo de ejecución se usa para ejecutar una aplicación de .NET Core, y puede o
no incluirse con la aplicación. El SDK se usa para crear aplicaciones y bibliotecas de .NET Core. El entorno de
ejecución de .NET Core siempre se instala con el SDK.
La versión más reciente de .NET Core es la 3.1.
DESCARGAR .NET
CORE

Versiones compatibles
En la tabla siguiente se muestra una lista de versiones de .NET Core actualmente compatibles y las versiones de
macOS que se admiten. Estas versiones se siguen admitiendo hasta que la versión de .NET Core alcance la
finalización del soporte técnico.
Con la marca ✔
️ se indica que la versión de .NET Core se sigue admitiendo.
Con la marca ❌ se indica que la versión de .NET Core no se admite.

. N ET 5 ( VERSIÓ N
SIST EM A O P ERAT IVO . N ET C O RE 2. 1 . N ET C O RE 3. 1 P REL IM IN A R)

macOS 10.15 "Catalina" ️ 2.1 (Notas de la versión)


✔ ️ 3.1 (Notas de la versión)
✔ ✔ 5.0, versión preliminar

(Notas de la versión)

macOS 10.14 "Mojave" ️ 2.1 (Notas de la versión)


✔ ️ 3.1 (Notas de la versión)
✔ ✔ 5.0, versión preliminar

(Notas de la versión)

macOS 10.13 "High Sierra" ️ 2.1 (Notas de la versión)


✔ ️ 3.1 (Notas de la versión)
✔ ✔ 5.0, versión preliminar

(Notas de la versión)

macOS 10.12 "Sierra" ️ 2.1 (Notas de la versión)


✔ ❌ 3.1 (Notas de la versión) ❌ 5.0, versión preliminar
(Notas de la versión)

Versiones no admitidas
Las siguientes versiones de .NET Core ya no se admiten ❌. aunque sus descargas siguen estando publicadas:
3.0 (Notas de la versión)
2.2 (Notas de la versión)
2.0 (Notas de la versión)

Información en tiempo de ejecución


El entorno de ejecución de .NET Core se usa para ejecutar aplicaciones creadas con .NET Core. Cuando un autor
publica una aplicación, puede incluir el tiempo de ejecución. Si no lo hace, el usuario elige si quiere instalar el
tiempo de ejecución.
Hay tres tiempos de ejecución distintos que se pueden instalar en macOS:
Entorno de ejecución de ASP.NET Core
Ejecuta aplicaciones de ASP.NET Core. Incluye el entorno de ejecución de .NET Core.
Entorno de ejecución de .NET Core
Este entorno de ejecución es el más sencillo y no incluye ningún otro. Le recomendamos encarecidamente que
instale el entorno de ejecución de ASP.NET Core para conseguir la mejor compatibilidad con las aplicaciones de
.NET Core.
D E S C A R G A D E L E N TO R N O D E E J E C U C I Ó N D E
.NET CO RE

Información del SDK


El SDK se usa para compilar y publicar aplicaciones y bibliotecas de .NET Core. La instalación del SDK incluye
ambos entornos de ejecución: ASP.NET Core y .NET Core.
D O W NLO AD .NET CO R E (Descarga del SDK de .NET Core)
SDK

Dependencias
.NET Core es compatible con las versiones siguientes de macOS:

NOTE
Un símbolo + representa la versión mínima.

VERSIÓ N DE . N ET C O RE MAC OS A RQ UIT EC T URA S

3.1 High Sierra (10.13 y x64 Más información


posteriores)

3.0 High Sierra (10.13 y x64 Más información


posteriores)

2.2 Sierra (10.12 y posteriores) x64 Más información

2.1 Sierra (10.12 y posteriores) x64 Más información

A partir de macOS Catalina (versión 10.15), se debe conceder la certificación a todo el software creado después
del 1 de junio de 2019 que se distribuye con el identificador de desarrollador. Este requisito se aplica al runtime de
.NET Core, al SDK de .NET Core y al software creado con .NET Core.
Desde el 18 de febrero de 2020, se ha concedido la certificación a los instaladores de las versiones 3.1, 3.0 y 2.1 de
.NET Core (tanto el runtime como el SDK). A las versiones publicadas anteriores no se les ha concedido la
certificación. Si ejecuta una aplicación sin certificación, verá un error similar al de la imagen siguiente:
Para obtener más información sobre cómo afecta la certificación forzada a .NET Core (y a las aplicaciones de .NET
Core), vea Trabajo con la certificación de macOS Catalina.

libgdiplus
Las aplicaciones .NET Core que usan el ensamblado System.Drawing.Common requieren la instalación de
libgdiplus.
Una manera fácil de obtener libgdiplus es usar el administrador de paquetes Homebrew ("brew") para macOS.
Después de instalar brew , instale libgdiplus mediante la ejecución de los comandos siguientes en un símbolo del
sistema de Terminal (comando):

brew update
brew install mono-libgdiplus

Instalación mediante un instalador


macOS tiene instaladores independientes que se pueden usar para instalar el SDK de .NET Core 3.1:
CPU de x64 (64 bits)

Descarga e instalación de forma manual


Como alternativa a los instaladores de macOS para .NET Core, puede descargar e instalar manualmente el SDK y
el entorno de ejecución. La instalación manual se suele llevar a cabo durante las pruebas de integración continua.
Para un desarrollador o usuario, generalmente es mejor usar un instalador.
Si instala el SDK de .NET Core, no necesita instalar el entorno de ejecución correspondiente. En primer lugar,
descargue una versión binaria del SDK o del entorno de ejecución de uno de los siguientes sitios:
✔ Descargas de la versión preliminar de .NET 5.0

️ Descargas de .NET Core 3.1

️ Descargas de .NET Core 2.1

Todas las descargas de .NET Core
A continuación, extraiga el archivo descargado y use el comando export para establecer las variables que se
utilizan en .NET Core. Luego, asegúrese de que .NET Core esté en PATH.
Para extraer el entorno de ejecución y hacer que los comandos de la CLI de .NET Core estén disponibles en el
terminal, en primer lugar, descargue una versión binaria de .NET Core. Luego, abra un terminal y ejecute los
siguientes comandos desde el directorio donde se guardó el archivo. El nombre del archivo puede ser distinto en
función de lo que haya descargado.
Use el comando siguiente para extraer el entorno de ejecución :

mkdir -p "$HOME/dotnet" && tar zxf aspnetcore-runtime-3.1.5-osx-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

Use el comando siguiente para extraer el SDK :

mkdir -p "$HOME/dotnet" && tar zxf dotnet-sdk-3.1.301-osx-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

TIP
Los comandos export anteriores solo hacen que los comandos de la CLI de .NET Core estén disponibles para la sesión de
terminal en la que se ha ejecutado.
Puede editar el perfil del shell para agregar los comandos de forma permanente. Hay una serie de shells distintos disponibles
para Linux, y cada uno de ellos tiene un perfil diferente. Por ejemplo:
Shell de Bash : ~/.bash_profile, ~/.bashrc
Shell de Korn : ~/.kshrc or .profile
Shell de Z : ~/.zshrc or .zprofile
Edite el archivo de origen adecuado para el shell y agregue :$HOME/dotnet al final de la instrucción PATH existente. Si no
se incluye ninguna instrucción PATH , agregue una nueva línea con export PATH=$PATH:$HOME/dotnet .
Además, agregue export DOTNET_ROOT=$HOME/dotnet al final del archivo.

Este enfoque le permite instalar diferentes versiones en ubicaciones independientes y elegir explícitamente cuál
usará cada aplicación.

Instalación con Visual Studio para Mac


Visual Studio para Mac instala el SDK de .NET Core cuando se selecciona la carga de trabajo .NET Core . Para
empezar con el desarrollo en .NET Core en macOS, vea Instalación de Visual Studio 2019 para Mac. Para obtener la
versión más reciente, .NET Core 3.1, se debe usar la versión de Visual Studio para Mac 8.4.
Instalación junto con Visual Studio Code
Visual Studio Code es un editor de código fuente ligero y eficaz que se ejecuta en el escritorio. Visual Studio Code
está disponible para Windows, macOS y Linux.
Aunque Visual Studio Code no viene con un instalador automatizado de .NET Core como Visual Studio, agregar
compatibilidad con .NET Core es sencillo.
1. Descargue e instale Visual Studio Code.
2. Descargue e instale el SDK de .NET Core.
3. Instale la extensión de C# desde el Marketplace de Visual Studio Code.

Instalación mediante la automatización de Bash


Los scripts de dotnet-install se usan para la automatización y las instalaciones que no son de administrador del
entorno de ejecución. Se puede descargar el script desde la página de referencia del script dotnet-install.
El valor predeterminado del script es instalar la versión más reciente de soporte técnico a largo plazo (LTS), que
actualmente es .NET Core 3.1. Puede elegir una versión concreta especificando el modificador current . Incluya el
modificador runtime para instalar un entorno de ejecución. De lo contrario, el script instala el SDK.

./dotnet-install.sh --channel 3.1 --runtime aspnetcore

NOTE
El comando anterior instala el entorno de ejecución de ASP.NET Core para obtener la máxima compatibilidad. El entorno de
ejecución de ASP.NET Core también incluye el estándar de .NET Core.
Docker
Los contenedores proporcionan una manera ligera de aislar la aplicación del resto del sistema host. Los
contenedores de la misma máquina comparten solo el kernel y usan los recursos proporcionados a la aplicación.
.NET Core puede ejecutarse en un contenedor de Docker. Las imágenes oficiales de Docker en .NET Core se
publican en el registro de contenedor de Microsoft (MCR) y se pueden encontrar en el repositorio de Docker Hub
para Microsoft .NET Core. Cada repositorio contiene imágenes para diferentes combinaciones de .NET (SDK o
Runtime) y del sistema operativo que puede usar.
Microsoft ofrece imágenes que se adaptan a escenarios específicos. Por ejemplo, el repositorio de ASP.NET Core
proporciona imágenes que se compilan para ejecutar aplicaciones de ASP.NET Core en producción.
Para obtener más información sobre el uso de .NET Core en un contenedor de Docker, vea Introducción a .NET y
Docker y Ejemplos.

Pasos siguientes
Cómo comprobar que .NET Core ya está instalado.
Trabajo con la certificación de macOS Catalina.
Tutorial: Introducción a macOS.
Tutorial: Creación de una aplicación con Visual Studio Code.
Tutorial: Inclusión de una aplicación de .NET Core en un contenedor.
Instalación de .NET Core en Linux
16/09/2020 • 14 minutes to read • Edit Online

.NET Core está disponible en diferentes distribuciones de Linux. La mayoría de las plataformas y distribuciones
de Linux tienen una versión principal cada año, y la mayoría proporcionan un administrador de paquetes que
se usa para instalar .NET Core. En este artículo se describe lo que se admite actualmente y el administrador de
paquetes que se usa.
El resto de este artículo es un desglose de cada una de las principales distribuciones de Linux que admite .NET
Core. Todas estas versiones siguen siendo compatibles hasta que la versión de .NET Core llega al fin del
soporte técnico o la distribución de Linux llega al final del ciclo de vida.
Para conseguir la mejor compatibilidad, elija una versión de lanzamiento a largo plazo (LTS).

Versiones no admitidas
Las siguientes versiones de .NET Core ya no se admiten ❌. Las descargas de estas siguen estando publicadas:
3.0
2.2
2.0
Estas versiones no admitidas no se detallan en las secciones siguientes y los resultados pueden variar si intenta
instalarlas.

Alpine
No hay instaladores para Alpine. Debe usar el script de instalación o seguir las instrucciones de instalación
manual.
En la tabla siguiente se muestra una lista de versiones de .NET Core actualmente compatibles y las versiones de
Alpine en las que se admiten. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core
llega al fin del soporte técnico o la versión de Alpine llega al final del ciclo de vida.
Una ✔️ indica que todavía se admite la versión de Alpine o de .NET Core.
Una ❌ indica que la versión de Alpine o de .NET Core no se admite en esa versión de Alpine.
Cuando una versión de Alpine y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.

. N ET 5 ( VERSIÓ N
A L P IN E . N ET C O RE 2. 1 . N ET C O RE 3. 1 P REL IM IN A R)

️ 3.12
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 3.11
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 3.10
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 3.9
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌ 3.8 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)
Para más información, consulte Instalación de .NET Core en Alpine.

CentOS
CentOS 7 usa Yum como administrador de paquetes y CentOS 8 emplea DNF.
En la tabla siguiente se muestra una lista de las versiones de .NET Core admitidas actualmente en CentOS 7 y
CentOS 8. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llegue al final del
soporte técnico o ya no se admita la versión de CentOS.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO
C EN TO S . N ET C O RE 2. 1 . N ET C O RE 3. 1 IN STA L A C IÓ N M A N UA L )

️ 8
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 7
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

Para más información, consulte Instalación de .NET Core en CentOS.

Debian
Debian usa APT (herramienta avanzada de paquetes) como administrador de paquetes.
En la tabla siguiente se muestra una lista de versiones de .NET Core actualmente compatibles y las versiones de
Debian en las que se admiten. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core
llegue al final del soporte técnico o la versión de Debian llegue al final del ciclo de vida.
Una ✔️ indica que todavía se admite la versión de Debian o de .NET Core.
Una ❌ indica que la versión de Debian o de .NET Core no se admite en esa versión de Debian.
Cuando una versión de Debian y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO
DEB IA N . N ET C O RE 2. 1 . N ET C O RE 3. 1 IN STA L A C IÓ N M A N UA L )

️ 10
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 9
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌8 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

Para más información, consulte Instalación de .NET Core en Debian.

Fedora
Fedora usa DNF como administrador de paquetes.
En la tabla siguiente se muestra una lista de versiones de .NET Core actualmente compatibles y las versiones de
Fedora en las que se admiten. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core
llegue al final del soporte técnico o la versión de Fedora llegue al final del ciclo de vida.
Una ✔
️ indica que todavía se admite la versión de Fedora o de .NET Core.
Una ❌ indica que la versión de Fedora o de .NET Core no se admite en esa versión de Fedora.
Cuando una versión de Fedora y una versión de .NET Core tienen una ✔
️ , se admite esa combinación de
sistema operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO
F EDO RA . N ET C O RE 2. 1 . N ET C O RE 3. 1 IN STA L A C IÓ N M A N UA L )

️ 32
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 31
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌ 30 ️ 2.1
✔ ️ 3.1
✔ ❌ 5.0 (versión preliminar)

❌ 29 ️ 2.1
✔ ️ 3.1
✔ ❌ 5.0 (versión preliminar)

❌ 28 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

❌ 27 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

Para más información, consulte Instalación de .NET Core en Fedora.

openSUSE
openSUSE usa zypper como administrador de paquetes.
En la tabla siguiente se muestra una lista de las versiones de .NET Core compatibles actualmente con
openSUSE 15. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llegue al final del
soporte técnico o ya no se admita la versión de openSUSE.
Una ✔️ indica que todavía se admite la versión de openSUSE o de .NET Core.
Una ❌ indica que la versión de openSUSE o de .NET Core no se admite en esa versión de openSUSE.
Cuando una versión de openSUSE y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO
O P EN SUSE . N ET C O RE 2. 1 . N ET C O RE 3. 1 IN STA L A C IÓ N M A N UA L )

️ 15
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

Para más información, consulte Instalación de .NET Core en openSUSE.

Red Hat
Red Hat Enterprise Linux (RHEL) usa yum (RHEL 7) y DNF (RHEL 8) como administrador de paquetes.
En la tabla siguiente se muestra una lista de las versiones de .NET Core compatibles actualmente con RHEL 7 y
RHEL 8. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llegue al final del soporte
técnico o ya no se admita la versión de RHEL.
Una ✔️ indica que todavía se admite la versión de RHEL o de .NET Core.
Una ❌ indica que la versión de RHEL o de .NET Core no se admite en esa versión de RHEL.
Cuando una versión de RHEL y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.
VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO
RH EL . N ET C O RE 2. 1 . N ET C O RE 3. 1 IN STA L A C IÓ N M A N UA L )

️ 8
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 7
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

Para más información, consulte Instalación de .NET Core en RHEL.

SLES
SLES usa zypper como administrador de paquetes.
En la tabla siguiente se muestra una lista de las versiones de .NET Core compatibles actualmente en SLES 12 SP
2 y SLES 15. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llegue al final del
soporte técnico o ya no se admita la versión de SLES.
Una ✔️ indica que todavía se admite la versión de SLES o de .NET Core.
Una ❌ indica que la versión de SLES o de .NET Core no se admite en esa versión de SLES.
Cuando una versión de SLES y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO
SL ES . N ET C O RE 2. 1 . N ET C O RE 3. 1 IN STA L A C IÓ N M A N UA L )

️ 15
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 12 SP2
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

Para más información, consulte Instalación de .NET Core en SLES.

Ubuntu
Ubuntu usa APT (herramienta avanzada de paquetes) como administrador de paquetes.
En la tabla siguiente se representa el estado de compatibilidad de Ubuntu y .NET Core.
Una ✔️ indica que todavía se admite la versión de Ubuntu o de .NET Core.
Una ❌ indica que la versión de Ubuntu o de .NET Core no se admite en esa versión de Ubuntu.
Cuando una versión de Ubuntu y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO
UB UN T U . N ET C O RE 2. 1 . N ET C O RE 3. 1 IN STA L A C IÓ N M A N UA L )

️ 20.04 (LTS)
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌ 19.10 ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌ 19.04 ️ 2.1
✔ ️ 3.1
✔ ❌ 5.0 (versión preliminar)

❌ 18.10 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)
VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO
UB UN T U . N ET C O RE 2. 1 . N ET C O RE 3. 1 IN STA L A C IÓ N M A N UA L )

️ 18.04 (LTS)
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌ 17.10 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

❌ 17.04 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

❌ 16.10 ❌ 2.1 ❌ 3.1 ❌ 5.0 (versión preliminar)

️ 16.04 (LTS)
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

Para más información, consulte Instalación de .NET Core en Ubuntu.

Pasos siguientes
Cómo comprobar que .NET Core ya está instalado.
Tutorial: Creación de una aplicación con Visual Studio Code.
Tutorial: Inclusión de una aplicación de .NET Core en un contenedor.
Instalación del SDK de .NET Core o de .NET Core
Runtime en Ubuntu
16/09/2020 • 42 minutes to read • Edit Online

.NET Core es compatible con Ubuntu. En este artículo se describe cómo instalar .NET Core en Ubuntu. Cuando una
versión de Ubuntu no es compatible, .NET Core deja de ser compatible con esa versión. Sin embargo, estas
instrucciones pueden ayudarle a conseguir que .NET Core se ejecute en esas versiones, aunque no se admita.
Instale el SDK (que incluye el entorno de ejecución) si quiere desarrollar aplicaciones .NET. O bien, si solo necesita
ejecutar aplicaciones, instale el entorno de ejecución. Si va a instalar el entorno de ejecución, le recomendamos que
instale el entorno de ejecución de ASP.NET Core , ya que incluye los de .NET Core y ASP.NET Core.
Si ya ha instalado el SDK o el entorno de ejecución, use los comandos dotnet --list-sdks y
dotnet --list-runtimes para ver qué versiones están instaladas. Para más información, consulte Cómo comprobar
que .NET Core ya está instalado.
Las instalaciones del administrador de paquetes solo se admiten en la arquitectura x64 . Otras arquitecturas, como
ARM , deben instalar manualmente el SDK de .NET Core o .NET Core Runtime. Para más información, consulte la
sección Instalación manual a continuación.

Distribuciones admitidas
En la tabla siguiente se muestra una lista de versiones de .NET Core actualmente compatibles y las versiones de
Ubuntu en las que se admiten. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llegue
al fin del soporte técnico o la versión de Ubuntu llegue al final del ciclo de vida.
Una ✔️ indica que todavía se admite la versión de Ubuntu o de .NET Core.
Una ❌ indica que la versión de Ubuntu o de .NET Core no se admite en esa versión de Ubuntu.
Cuando una versión de Ubuntu y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO IN STA L A C IÓ N
UB UN T U . N ET C O RE 2. 1 . N ET C O RE 3. 1 M A N UA L )

️ 20.04 (LTS)
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌ 19.10 ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌ 19.04 ️ 2.1
✔ ️ 3.1
✔ ❌ 5.0 (versión preliminar)

❌ 18.10 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

️ 18.04 (LTS)
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌ 17.10 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

❌ 17.04 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

❌ 16.10 ❌ 2.1 ❌ 3.1 ❌ 5.0 (versión preliminar)


VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO IN STA L A C IÓ N
UB UN T U . N ET C O RE 2. 1 . N ET C O RE 3. 1 M A N UA L )

️ 16.04 (LTS)
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

Las siguientes versiones de .NET Core ya no se admiten. aunque sus descargas siguen estando publicadas:
3.0
2.2
2.0

Procedimiento para instalar otras versiones


Los paquetes agregados a las fuentes del administrador de paquetes se denominan con un formato susceptible de
intrusiones: {product}-{type}-{version} .
product
Tipo de producto .NET que se va a instalar. Las opciones válidas son:
dotnet
aspnetcore
type
Elige el SDK o el entorno de ejecución. Las opciones válidas son:
sdk
motor en tiempo de ejecución
version
Versión del SDK o del entorno de ejecución que se va a instalar. En este artículo se proporcionarán siempre
las instrucciones para la última versión admitida. Las opciones válidas son cualquier versión de lanzamiento,
como las siguientes:
3.1
3.0
2.1
Es posible que el SDK o el entorno de ejecución que intenta descargar no esté disponible para su
distribución de Linux. Para obtener una lista de las distribuciones admitidas, consulte Dependencias y
requisitos de .NET Core.
Ejemplos
Instalación del runtime de ASP.NET Core 3.1: aspnetcore-runtime-3.1
Instalación del entorno de ejecución de ASP.NET Core 2.1: dotnet-runtime-2.1
Instalación del SDK de .NET Core 3.1: dotnet-sdk-3.1
Falta el paquete
Si la combinación de paquete y versión no funciona, no está disponible. Por ejemplo, no hay un SDK de ASP.NET
Core; los componentes del SDK se incluyen en el SDK de .NET Core. El valor aspnetcore-sdk-2.2 es no es correcto y
debe ser dotnet-sdk-2.2 . Para obtener una lista de las distribuciones de Linux compatibles con .NET Core, consulte
Dependencias y requisitos de .NET Core.

20.04 ✔

La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:

wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-


prod.deb
sudo dpkg -i packages-microsoft-prod.deb

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-3.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-3.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo apt-get install -y dotnet-runtime-3.1

19.10 ❌
❌ Tenga en cuenta que ya no se admite esta versión de Ubuntu.
La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:

wget https://packages.microsoft.com/config/ubuntu/19.10/packages-microsoft-prod.deb -O packages-microsoft-


prod.deb
sudo dpkg -i packages-microsoft-prod.deb

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-3.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-3.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo apt-get install -y dotnet-runtime-3.1

19.04 ❌
❌ Tenga en cuenta que ya no se admite esta versión de Ubuntu.
La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:
wget https://packages.microsoft.com/config/ubuntu/19.04/packages-microsoft-prod.deb -O packages-microsoft-
prod.deb
sudo dpkg -i packages-microsoft-prod.deb

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-3.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-3.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo apt-get install -y dotnet-runtime-3.1

18.10 ❌
❌ Tenga en cuenta que ya no se admite esta versión de Ubuntu.
La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:
wget https://packages.microsoft.com/config/ubuntu/18.10/packages-microsoft-prod.deb -O packages-microsoft-
prod.deb
sudo dpkg -i packages-microsoft-prod.deb

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-2.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-2.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-2.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-2.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-2.1 en los comandos anteriores por
dotnet-runtime-2.1 .

sudo apt-get install -y dotnet-runtime-2.1

18.04 ✔

La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:
wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-
prod.deb
sudo dpkg -i packages-microsoft-prod.deb

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-3.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-3.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo apt-get install -y dotnet-runtime-3.1

17.10 ❌
❌ Tenga en cuenta que ya no se admite esta versión de Ubuntu.
La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:
wget https://packages.microsoft.com/config/ubuntu/17.10/packages-microsoft-prod.deb -O packages-microsoft-
prod.deb
sudo dpkg -i packages-microsoft-prod.deb

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-2.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-2.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-2.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-2.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-2.1 en los comandos anteriores por
dotnet-runtime-2.1 .

sudo apt-get install -y dotnet-runtime-2.1

17.04 ❌
❌ Tenga en cuenta que ya no se admite esta versión de Ubuntu.
La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:
wget https://packages.microsoft.com/config/ubuntu/17.04/packages-microsoft-prod.deb -O packages-microsoft-
prod.deb
sudo dpkg -i packages-microsoft-prod.deb

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-2.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-2.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-2.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-2.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-2.1 en los comandos anteriores por
dotnet-runtime-2.1 .

sudo apt-get install -y dotnet-runtime-2.1

16.10 ❌
❌ Tenga en cuenta que ya no se admite esta versión de Ubuntu.
La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:
wget https://packages.microsoft.com/config/ubuntu/16.10/packages-microsoft-prod.deb -O packages-microsoft-
prod.deb
sudo dpkg -i packages-microsoft-prod.deb

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-2.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-2.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-2.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-2.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-2.1 en los comandos anteriores por
dotnet-runtime-2.1 .

sudo apt-get install -y dotnet-runtime-2.1

16.04 ✔

La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:
wget https://packages.microsoft.com/config/ubuntu/16.04/packages-microsoft-prod.deb -O packages-microsoft-
prod.deb
sudo dpkg -i packages-microsoft-prod.deb

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-3.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-3.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo apt-get install -y dotnet-runtime-3.1

SDK o entorno de ejecución de actualización de APT


Cuando hay disponible una nueva versión de revisión para .NET Core, basta con que la actualice mediante APT con
los siguientes comandos:

sudo apt-get update


sudo apt-get upgrade

Solución de problemas de APT


En esta sección se proporciona información sobre los errores comunes que puede recibir al usar ATP para instalar
.NET Core.
No se puede encontrar el paquete \ No se han podido instalar algunos paquetes
Si recibe un mensaje de error similar a No se puede encontrar el paquete {netcore-package} o No se han
podido instalar algunos paquetes , ejecute los comandos siguientes.
Hay dos marcadores de posición en el siguiente conjunto de comandos.
{dotnet-package}
Representa el paquete de .NET Core que va a instalar, como aspnetcore-runtime-3.1 . Se usa en el comando
sudo apt-get install siguiente.

{os-version}
Representa la versión de Linux en la que está. Se usa en el comando wget siguiente.
Primero, pruebe a purgar la lista de paquetes:

sudo dpkg --purge packages-microsoft-prod && sudo dpkg -i packages-microsoft-prod.deb


sudo apt-get update

Después, intente instalar .NET Core de nuevo. Si eso no funciona, puede ejecutar una instalación manual con los
comandos siguientes:

sudo apt-get install -y gpg


wget -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o microsoft.asc.gpg
sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
wget https://packages.microsoft.com/config/ubuntu/{os-version}/prod.list
sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list
sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg
sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update; \
sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y {dotnet-package}

No se pudo capturar el elemento


Al instalar el paquete de .NET Core, puede ver un error similar a
Failed to fetch ... File has unexpected size ... Mirror sync in progress? . Este error significa que la fuente de
paquetes de .NET Core se está actualizando con versiones de paquetes más recientes y que debe volver a
intentarlo más tarde. Durante una actualización, la falta de disponibilidad de la fuente de paquetes no debe ser
superior a 30 minutos. Si recibe este error continuamente durante más de 30 minutos, abra una incidencia en
https://github.com/dotnet/core/issues.

Snap
.NET Core está disponible desde el almacén de snaps.
Un snap es una agrupación de una aplicación y sus dependencias que funcionan sin modificaciones en muchas
distribuciones de Linux diferentes. Los snaps se reconocen y se instalan desde el almacén de snaps. Para más
información sobre Snap, consulte Introducción a Snap.
Solo las versiones admitidas de .NET Core están disponibles mediante Snap.
Instalación del SDK
Los paquetes Snap para el SDK de .NET Core se publican con el mismo identificador: dotnet-sdk . Se puede instalar
una versión específica del SDK mediante la especificación del canal. El SDK incluye el entorno de ejecución
correspondiente. En la tabla siguiente se enumeran los canales:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) 3.1 o latest/stable

2.1 (LTS) 2.1

.NET 5.0 (versión preliminar) 5.0/beta

Use el comando snap install para instalar un paquete Snap del SDK de .NET Core. Use el parámetro --channel
para indicar qué versión se va a instalar. Si se omite este parámetro, se usa latest/stable . En este ejemplo, se
especifica 3.1 :

sudo snap install dotnet-sdk --classic --channel=3.1

A continuación, registre el comando dotnet del sistema con el comando snap alias :

sudo snap alias dotnet-sdk.dotnet dotnet

Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-sdk.dotnet dotnet31 . Cuando use el comando dotnet31 , invocará
esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los tutoriales y
ejemplos, donde se espera que esté disponible un comando dotnet .
Instalación de la instancia en tiempo de ejecución
Los paquetes Snap de .NET Core Runtime se publican con su propio identificador de paquete. En la tabla siguiente
se muestra una lista de los identificadores de paquete:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) dotnet-runtime-31

3.0 dotnet-runtime-30

2.2 dotnet-runtime-22

2.1 (LTS) dotnet-runtime-21

Use el comando snap install para instalar un paquete Snap de .NET Core Runtime. En este ejemplo, se instala
.NET Core 3.1:

sudo snap install dotnet-runtime-31 --classic

A continuación, registre el comando dotnet del sistema con el comando snap alias :

sudo snap alias dotnet-runtime-31.dotnet dotnet


Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-runtime-31.dotnet dotnet31 . Cuando use el comando dotnet31 ,
invocará esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los
tutoriales y ejemplos, donde se espera que esté disponible un comando dotnet .
Errores de certificado SSL
Cuando .NET se instala mediante Snap, es posible que en algunas distribuciones no se encuentren los certificados
SSL de .NET y que reciba un error similar al siguiente durante la acción restore :

Processing post-creation actions...


Running 'dotnet restore' on /home/myhome/test/test.csproj...
Restoring packages for /home/myhome/test/test.csproj...
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : Unable to load the service index for source
https://api.nuget.org/v3/index.json. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The SSL connection could not be established,
see inner exception. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The remote certificate is invalid according to
the validation procedure. [/home/myhome/test/test.csproj]

Para resolver este problema, establezca algunas variables de entorno:

export SSL_CERT_FILE=[path-to-certificate-file]
export SSL_CERT_DIR=/dev/null

La ubicación del certificado variará en función de la distribución. Estas son las ubicaciones de las distribuciones en
las que hemos experimentado el problema.
Fedora: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
OpenSUSE: /etc/ssl/ca-bundle.pem
Solus: /etc/ssl/certs/ca-certificates.crt

Dependencias
Al realizar la instalación con un administrador de paquetes, estas bibliotecas se instalan automáticamente. Sin
embargo, si instala manualmente .NET Core o publica una aplicación independiente, deberá asegurarse de que
estas bibliotecas estén instaladas:
libc6
libgcc1
libgssapi-krb5-2
libicu52 (para 14.x)
libicu55 (para 16.x)
libicu60 (para 18.x)
libicu66 (para 20.x)
libssl1.0.0 (para 14.x, 16.x)
libssl1.1 (para 18.x, 20.x)
libstdc++6
zlib1g
En el caso de las aplicaciones de .NET Core que utilizan el ensamblado System.Drawing.Common, también se
necesita la dependencia siguiente:
libgdiplus (versión 6.0.1 o posteriores)
WARNING
Puede instalar una versión reciente de libgdiplus agregando el repositorio Mono al sistema. Para obtener más
información, vea https://www.mono-project.com/download/stable/.

Instalación con script


Los scripts de dotnet-install se usan para la automatización y las instalaciones que no son de administrador del
SDK y del Runtime . Puede descargar el script de https://dot.net/v1/dotnet-install.sh.
El valor predeterminado del script es instalar la versión más reciente del SDK de soporte técnico a largo plazo
(LTS), que actualmente es .NET Core 3.1. Para instalar la versión actual, que puede no ser una versión (LTS), use el
parámetro -c Current .

./dotnet-install.sh -c Current

Para instalar .NET Core Runtime en lugar del SDK, use el parámetro --runtime .

./dotnet-install.sh -c Current --runtime aspnetcore

Para instalar una versión específica, modifique el parámetro -c para indicar la versión específica. El siguiente
comando instala el SDK de .NET Core 3.1.

./dotnet-install.sh -c 3.1

Para más información, consulte la referencia de los scripts de dotnet-install.

Instalación manual
Como alternativa a los administradores de paquetes, puede descargar e instalar manualmente el SDK y el entorno
de ejecución. La instalación manual se suele llevar a cabo durante las pruebas de integración continua o en
distribuciones de Linux no admitidas. Para un desarrollador o usuario, generalmente es mejor usar un
administrador de paquetes.
Si instala el SDK de .NET Core, no necesita instalar el entorno de ejecución correspondiente. En primer lugar,
descargue una versión binaria del SDK o del entorno de ejecución de uno de los siguientes sitios:
✔ Descargas de la versión preliminar de .NET 5.0

️ Descargas de .NET Core 3.1

️ Descargas de .NET Core 2.1

Todas las descargas de .NET Core
A continuación, extraiga el archivo descargado y use el comando export para establecer las variables que se
utilizan en .NET Core. Luego, asegúrese de que .NET Core esté en PATH.
Para extraer el entorno de ejecución y hacer que los comandos de la CLI de .NET Core estén disponibles en el
terminal, en primer lugar, descargue una versión binaria de .NET Core. Luego, abra un terminal y ejecute los
siguientes comandos desde el directorio donde se guardó el archivo. El nombre del archivo puede ser distinto en
función de lo que haya descargado.
Use el comando siguiente para extraer el entorno de ejecución :
mkdir -p "$HOME/dotnet" && tar zxf aspnetcore-runtime-3.1.0-linux-x64.tar.gz -C "$HOME/dotnet"
export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

Use el comando siguiente para extraer el SDK :

mkdir -p "$HOME/dotnet" && tar zxf dotnet-sdk-3.1.301-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

TIP
Los comandos export anteriores solo hacen que los comandos de la CLI de .NET Core estén disponibles para la sesión de
terminal en la que se ha ejecutado.
Puede editar el perfil del shell para agregar los comandos de forma permanente. Hay una serie de shells distintos disponibles
para Linux, y cada uno de ellos tiene un perfil diferente. Por ejemplo:
Shell de Bash : ~/.bash_profile, ~/.bashrc
Shell de Korn : ~/.kshrc or .profile
Shell de Z : ~/.zshrc or .zprofile
Edite el archivo de origen adecuado para el shell y agregue :$HOME/dotnet al final de la instrucción PATH existente. Si no
se incluye ninguna instrucción PATH , agregue una nueva línea con export PATH=$PATH:$HOME/dotnet .
Además, agregue export DOTNET_ROOT=$HOME/dotnet al final del archivo.

Este enfoque le permite instalar diferentes versiones en ubicaciones independientes y elegir explícitamente cuál
usará cada aplicación.

Pasos siguientes
Tutorial: Creación de una aplicación de consola con el SDK de .NET Core mediante Visual Studio Code
Instalación del SDK de .NET Core o .NET Core
Runtime en Alpine
16/09/2020 • 8 minutes to read • Edit Online

En este artículo se explica cómo instalar .NET Core en Alpine. Cuando una versión de Alpine no es compatible,
.NET Core deja de ser compatible con esa versión. Pero estas instrucciones pueden ayudarle a conseguir que
.NET Core se ejecute en esas versiones, aunque no se admita.
Instale el SDK (que incluye el entorno de ejecución) si quiere desarrollar aplicaciones .NET. O bien, si solo
necesita ejecutar aplicaciones, instale el entorno de ejecución. Si va a instalar el entorno de ejecución, le
recomendamos que instale el entorno de ejecución de ASP.NET Core , ya que incluye los de .NET Core y
ASP.NET Core.
Si ya ha instalado el SDK o el entorno de ejecución, use los comandos dotnet --list-sdks y
dotnet --list-runtimes para ver qué versiones están instaladas. Para más información, consulte Cómo
comprobar que .NET Core ya está instalado.
No hay instaladores para Alpine. Debe usar el script de instalación o seguir las instrucciones de instalación
manual.

Distribuciones admitidas
En la tabla siguiente se muestra una lista de versiones de .NET Core actualmente compatibles y las versiones de
Alpine en las que se admiten. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llega
al fin del soporte técnico o la versión de Alpine llega al final del ciclo de vida.
Una ✔️ indica que todavía se admite la versión de Alpine o de .NET Core.
Una ❌ indica que la versión de Alpine o de .NET Core no se admite en esa versión de Alpine.
Cuando una versión de Alpine y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.

. N ET 5 ( VERSIÓ N
A L P IN E . N ET C O RE 2. 1 . N ET C O RE 3. 1 P REL IM IN A R)

️ 3.12
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 3.11
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 3.10
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 3.9
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌ 3.8 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

Las siguientes versiones de .NET Core ya no se admiten. aunque sus descargas siguen estando publicadas:
3.0
2.2
2.0
Dependencias
.NET Core en Alpine Linux exige que estén instaladas las siguientes dependencias:
icu-libs
krb5-libs
libgcc
libintl
libssl1.1 (Alpine 3.9 o superior)
libssl1.0 (Alpine 3.8 o inferior)
libstdc++
zlib

Instalación con script


Los scripts de dotnet-install se usan para la automatización y las instalaciones que no son de administrador del
SDK y del Runtime . Puede descargar el script de https://dot.net/v1/dotnet-install.sh.
El valor predeterminado del script es instalar la versión más reciente del SDK de soporte técnico a largo plazo
(LTS), que actualmente es .NET Core 3.1. Para instalar la versión actual, que puede no ser una versión (LTS), use
el parámetro -c Current .

./dotnet-install.sh -c Current

Para instalar .NET Core Runtime en lugar del SDK, use el parámetro --runtime .

./dotnet-install.sh -c Current --runtime aspnetcore

Para instalar una versión específica, modifique el parámetro -c para indicar la versión específica. El siguiente
comando instala el SDK de .NET Core 3.1.

./dotnet-install.sh -c 3.1

Para más información, consulte la referencia de los scripts de dotnet-install.

Instalación manual
Como alternativa a los administradores de paquetes, puede descargar e instalar manualmente el SDK y el
entorno de ejecución. La instalación manual se suele llevar a cabo durante las pruebas de integración continua o
en distribuciones de Linux no admitidas. Para un desarrollador o usuario, generalmente es mejor usar un
administrador de paquetes.
Si instala el SDK de .NET Core, no necesita instalar el entorno de ejecución correspondiente. En primer lugar,
descargue una versión binaria del SDK o del entorno de ejecución de uno de los siguientes sitios:
✔ Descargas de la versión preliminar de .NET 5.0

️ Descargas de .NET Core 3.1

️ Descargas de .NET Core 2.1

Todas las descargas de .NET Core
A continuación, extraiga el archivo descargado y use el comando export para establecer las variables que se
utilizan en .NET Core. Luego, asegúrese de que .NET Core esté en PATH.
Para extraer el entorno de ejecución y hacer que los comandos de la CLI de .NET Core estén disponibles en el
terminal, en primer lugar, descargue una versión binaria de .NET Core. Luego, abra un terminal y ejecute los
siguientes comandos desde el directorio donde se guardó el archivo. El nombre del archivo puede ser distinto
en función de lo que haya descargado.
Use el comando siguiente para extraer el entorno de ejecución :

mkdir -p "$HOME/dotnet" && tar zxf aspnetcore-runtime-3.1.0-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

Use el comando siguiente para extraer el SDK :

mkdir -p "$HOME/dotnet" && tar zxf dotnet-sdk-3.1.301-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

TIP
Los comandos export anteriores solo hacen que los comandos de la CLI de .NET Core estén disponibles para la sesión
de terminal en la que se ha ejecutado.
Puede editar el perfil del shell para agregar los comandos de forma permanente. Hay una serie de shells distintos
disponibles para Linux, y cada uno de ellos tiene un perfil diferente. Por ejemplo:
Shell de Bash : ~/.bash_profile, ~/.bashrc
Shell de Korn : ~/.kshrc or .profile
Shell de Z : ~/.zshrc or .zprofile
Edite el archivo de origen adecuado para el shell y agregue :$HOME/dotnet al final de la instrucción PATH existente. Si
no se incluye ninguna instrucción PATH , agregue una nueva línea con export PATH=$PATH:$HOME/dotnet .
Además, agregue export DOTNET_ROOT=$HOME/dotnet al final del archivo.

Este enfoque le permite instalar diferentes versiones en ubicaciones independientes y elegir explícitamente cuál
usará cada aplicación.

Pasos siguientes
Tutorial: Creación de una aplicación de consola con el SDK de .NET Core mediante Visual Studio Code
Instalación del SDK de .NET Core o .NET Core
Runtime en CentOS
16/09/2020 • 20 minutes to read • Edit Online

.NET Core es compatible con CentOS. En este artículo se describe cómo instalar .NET Core en CentOS.
Instale el SDK (que incluye el entorno de ejecución) si quiere desarrollar aplicaciones .NET. O bien, si solo necesita
ejecutar aplicaciones, instale el entorno de ejecución. Si va a instalar el entorno de ejecución, le recomendamos que
instale el entorno de ejecución de ASP.NET Core , ya que incluye los de .NET Core y ASP.NET Core.
Si ya ha instalado el SDK o el entorno de ejecución, use los comandos dotnet --list-sdks y
dotnet --list-runtimes para ver qué versiones están instaladas. Para más información, consulte Cómo comprobar
que .NET Core ya está instalado.
Las instalaciones del administrador de paquetes solo se admiten en la arquitectura x64 . Otras arquitecturas, como
ARM , deben instalar manualmente el SDK de .NET Core o .NET Core Runtime. Para más información, consulte la
sección Instalación manual a continuación.

Distribuciones admitidas
En la tabla siguiente se muestra una lista de las versiones de .NET Core admitidas actualmente en CentOS 7 y
CentOS 8. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llegue al final del soporte
técnico o ya no se admita la versión de CentOS.
Una ✔️ indica que todavía se admite la versión de CentOS o de .NET Core.
Una ❌ indica que la versión de CentOS o de .NET Core no se admite en esa versión de CentOS.
Cuando una versión de CentOS y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO IN STA L A C IÓ N
C EN TO S . N ET C O RE 2. 1 . N ET C O RE 3. 1 M A N UA L )

️ 8
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 7
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

Las siguientes versiones de .NET Core ya no se admiten. Las descargas de estas siguen estando publicadas:
3.0
2.2
2.0
Las instalaciones del administrador de paquetes solo se admiten en la arquitectura x64 . Otras arquitecturas, como
ARM , deben instalar manualmente el SDK de .NET Core o .NET Core Runtime. Para más información, consulte la
sección Instalación manual a continuación.

Procedimiento para instalar otras versiones


Los paquetes agregados a las fuentes del administrador de paquetes se denominan con un formato susceptible de
intrusiones: {product}-{type}-{version} .
product
Tipo de producto .NET que se va a instalar. Las opciones válidas son:
dotnet
aspnetcore
type
Elige el SDK o el entorno de ejecución. Las opciones válidas son:
sdk
motor en tiempo de ejecución
version
Versión del SDK o del entorno de ejecución que se va a instalar. En este artículo se proporcionarán siempre
las instrucciones para la última versión admitida. Las opciones válidas son cualquier versión de lanzamiento,
como las siguientes:
3.1
3.0
2.1
Es posible que el SDK o el entorno de ejecución que intenta descargar no esté disponible para su
distribución de Linux. Para obtener una lista de las distribuciones admitidas, consulte Dependencias y
requisitos de .NET Core.
Ejemplos
Instalación del runtime de ASP.NET Core 3.1: aspnetcore-runtime-3.1
Instalación del entorno de ejecución de ASP.NET Core 2.1: dotnet-runtime-2.1
Instalación del SDK de .NET Core 3.1: dotnet-sdk-3.1
Falta el paquete
Si la combinación de paquete y versión no funciona, no está disponible. Por ejemplo, no hay un SDK de ASP.NET
Core; los componentes del SDK se incluyen en el SDK de .NET Core. El valor aspnetcore-sdk-2.2 es no es correcto y
debe ser dotnet-sdk-2.2 . Para obtener una lista de las distribuciones de Linux compatibles con .NET Core, consulte
Dependencias y requisitos de .NET Core.

CentOS 8 ✔

.NET Core 3.1 está disponible en los repositorios de paquetes predeterminados de CentOS 8.
Instalación del SDK
El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo dnf install dotnet-sdk-3.1

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo dnf install aspnetcore-runtime-3.1

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo dnf install dotnet-runtime-3.1

CentOS 7 ✔

Antes de instalar .NET, ejecute los siguientes comandos para agregar la clave de la firma del paquete de Microsoft a
la lista de claves de confianza y agregar el repositorio de paquetes de Microsoft. Abra un terminal y ejecute los
comandos siguientes:

sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo yum install dotnet-sdk-3.1

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo yum install aspnetcore-runtime-3.1

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-2.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo yum install dotnet-runtime-3.1

Solución de problemas del administrador de paquetes


En esta sección se proporciona información sobre los errores comunes que puede obtener al usar el administrador
de paquetes para instalar .NET Core.
No se pudo capturar el elemento
Al instalar el paquete de .NET Core, puede ver un error similar a
signature verification failed for file 'repomd.xml' from repository 'packages-microsoft-com-prod' . En términos
generales, este error significa que la fuente de paquetes para .NET Core se está actualizando con versiones de
paquetes más recientes y que debe volver a intentarlo más tarde. Durante una actualización, la fuente de paquetes
no debe estar disponible durante más de 2 horas. Si recibe este error continuamente durante más de 2 horas, abra
una incidencia en https://github.com/dotnet/core/issues.

Snap
.NET Core está disponible desde el almacén de snaps.
Un snap es una agrupación de una aplicación y sus dependencias que funcionan sin modificaciones en muchas
distribuciones de Linux diferentes. Los snaps se reconocen y se instalan desde el almacén de snaps. Para más
información sobre Snap, consulte Introducción a Snap.
Solo las versiones admitidas de .NET Core están disponibles mediante Snap.
Instalación del SDK
Los paquetes Snap para el SDK de .NET Core se publican con el mismo identificador: dotnet-sdk . Se puede instalar
una versión específica del SDK mediante la especificación del canal. El SDK incluye el entorno de ejecución
correspondiente. En la tabla siguiente se enumeran los canales:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) 3.1 o latest/stable

2.1 (LTS) 2.1

.NET 5.0 (versión preliminar) 5.0/beta

Use el comando snap install para instalar un paquete Snap del SDK de .NET Core. Use el parámetro --channel
para indicar qué versión se va a instalar. Si se omite este parámetro, se usa latest/stable . En este ejemplo, se
especifica 3.1 :

sudo snap install dotnet-sdk --classic --channel=3.1

A continuación, registre el comando dotnet del sistema con el comando snap alias :

sudo snap alias dotnet-sdk.dotnet dotnet

Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-sdk.dotnet dotnet31 . Cuando use el comando dotnet31 , invocará
esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los tutoriales y
ejemplos, donde se espera que esté disponible un comando dotnet .
Instalación de la instancia en tiempo de ejecución
Los paquetes Snap de .NET Core Runtime se publican con su propio identificador de paquete. En la tabla siguiente
se muestra una lista de los identificadores de paquete:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) dotnet-runtime-31

3.0 dotnet-runtime-30

2.2 dotnet-runtime-22

2.1 (LTS) dotnet-runtime-21

Use el comando snap install para instalar un paquete Snap de .NET Core Runtime. En este ejemplo, se instala
.NET Core 3.1:
sudo snap install dotnet-runtime-31 --classic

A continuación, registre el comando dotnet del sistema con el comando snap alias :

sudo snap alias dotnet-runtime-31.dotnet dotnet

Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-runtime-31.dotnet dotnet31 . Cuando use el comando dotnet31 ,
invocará esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los
tutoriales y ejemplos, donde se espera que esté disponible un comando dotnet .
Errores de certificado SSL
Cuando .NET se instala mediante Snap, es posible que en algunas distribuciones no se encuentren los certificados
SSL de .NET y que reciba un error similar al siguiente durante la acción restore :

Processing post-creation actions...


Running 'dotnet restore' on /home/myhome/test/test.csproj...
Restoring packages for /home/myhome/test/test.csproj...
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : Unable to load the service index for source
https://api.nuget.org/v3/index.json. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The SSL connection could not be established,
see inner exception. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The remote certificate is invalid according to
the validation procedure. [/home/myhome/test/test.csproj]

Para resolver este problema, establezca algunas variables de entorno:

export SSL_CERT_FILE=[path-to-certificate-file]
export SSL_CERT_DIR=/dev/null

La ubicación del certificado variará en función de la distribución. Estas son las ubicaciones de las distribuciones en
las que hemos experimentado el problema.
Fedora: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
OpenSUSE: /etc/ssl/ca-bundle.pem
Solus: /etc/ssl/certs/ca-certificates.crt

Dependencias
Al realizar la instalación con un administrador de paquetes, estas bibliotecas se instalan automáticamente. Sin
embargo, si instala manualmente .NET Core o publica una aplicación independiente, deberá asegurarse de que
estas bibliotecas estén instaladas:
krb5-libs
libicu
openssl-libs
Si la versión de OpenSSL del entorno de tiempo de ejecución de destino es 1.1 o más reciente, deberá instalar
compat-openssl10 .
Para obtener más información sobre las dependencias, vea Aplicaciones de Linux independientes.
En el caso de las aplicaciones de .NET Core que utilizan el ensamblado System.Drawing.Common, también se
necesitará la dependencia siguiente:
libgdiplus (versión 6.0.1 o posterior)

WARNING
Puede instalar una versión reciente de libgdiplus agregando el repositorio Mono al sistema. Para obtener más
información, vea https://www.mono-project.com/download/stable/.

Instalación con script


Los scripts de dotnet-install se usan para la automatización y las instalaciones que no son de administrador del
SDK y del Runtime . Puede descargar el script de https://dot.net/v1/dotnet-install.sh.
El valor predeterminado del script es instalar la versión más reciente del SDK de soporte técnico a largo plazo
(LTS), que actualmente es .NET Core 3.1. Para instalar la versión actual, que puede no ser una versión (LTS), use el
parámetro -c Current .

./dotnet-install.sh -c Current

Para instalar .NET Core Runtime en lugar del SDK, use el parámetro --runtime .

./dotnet-install.sh -c Current --runtime aspnetcore

Para instalar una versión específica, modifique el parámetro -c para indicar la versión específica. El siguiente
comando instala el SDK de .NET Core 3.1.

./dotnet-install.sh -c 3.1

Para más información, consulte la referencia de los scripts de dotnet-install.

Instalación manual
Como alternativa a los administradores de paquetes, puede descargar e instalar manualmente el SDK y el entorno
de ejecución. La instalación manual se suele llevar a cabo durante las pruebas de integración continua o en
distribuciones de Linux no admitidas. Para un desarrollador o usuario, generalmente es mejor usar un
administrador de paquetes.
Si instala el SDK de .NET Core, no necesita instalar el entorno de ejecución correspondiente. En primer lugar,
descargue una versión binaria del SDK o del entorno de ejecución de uno de los siguientes sitios:
✔ Descargas de la versión preliminar de .NET 5.0

️ Descargas de .NET Core 3.1

️ Descargas de .NET Core 2.1

Todas las descargas de .NET Core
A continuación, extraiga el archivo descargado y use el comando export para establecer las variables que se
utilizan en .NET Core. Luego, asegúrese de que .NET Core esté en PATH.
Para extraer el entorno de ejecución y hacer que los comandos de la CLI de .NET Core estén disponibles en el
terminal, en primer lugar, descargue una versión binaria de .NET Core. Luego, abra un terminal y ejecute los
siguientes comandos desde el directorio donde se guardó el archivo. El nombre del archivo puede ser distinto en
función de lo que haya descargado.
Use el comando siguiente para extraer el entorno de ejecución :

mkdir -p "$HOME/dotnet" && tar zxf aspnetcore-runtime-3.1.0-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

Use el comando siguiente para extraer el SDK :

mkdir -p "$HOME/dotnet" && tar zxf dotnet-sdk-3.1.301-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

TIP
Los comandos export anteriores solo hacen que los comandos de la CLI de .NET Core estén disponibles para la sesión de
terminal en la que se ha ejecutado.
Puede editar el perfil del shell para agregar los comandos de forma permanente. Hay una serie de shells distintos disponibles
para Linux, y cada uno de ellos tiene un perfil diferente. Por ejemplo:
Shell de Bash : ~/.bash_profile, ~/.bashrc
Shell de Korn : ~/.kshrc or .profile
Shell de Z : ~/.zshrc or .zprofile
Edite el archivo de origen adecuado para el shell y agregue :$HOME/dotnet al final de la instrucción PATH existente. Si no
se incluye ninguna instrucción PATH , agregue una nueva línea con export PATH=$PATH:$HOME/dotnet .
Además, agregue export DOTNET_ROOT=$HOME/dotnet al final del archivo.

Este enfoque le permite instalar diferentes versiones en ubicaciones independientes y elegir explícitamente cuál
usará cada aplicación.

Pasos siguientes
Tutorial: Creación de una aplicación de consola con el SDK de .NET Core mediante Visual Studio Code
Instalación del SDK de .NET Core o de .NET Core
Runtime en Debian
16/09/2020 • 26 minutes to read • Edit Online

En este artículo se describe cómo instalar .NET Core en Debian. Cuando una versión de Debian no es compatible,
.NET Core deja de ser compatible con esa versión. Sin embargo, estas instrucciones pueden ayudarle a conseguir
que .NET Core se ejecute en esas versiones, aunque no se admita.
Instale el SDK (que incluye el entorno de ejecución) si quiere desarrollar aplicaciones .NET. O bien, si solo necesita
ejecutar aplicaciones, instale el entorno de ejecución. Si va a instalar el entorno de ejecución, le recomendamos que
instale el entorno de ejecución de ASP.NET Core , ya que incluye los de .NET Core y ASP.NET Core.
Si ya ha instalado el SDK o el entorno de ejecución, use los comandos dotnet --list-sdks y
dotnet --list-runtimes para ver qué versiones están instaladas. Para más información, consulte Cómo comprobar
que .NET Core ya está instalado.
Las instalaciones del administrador de paquetes solo se admiten en la arquitectura x64 . Otras arquitecturas, como
ARM , deben instalar manualmente el SDK de .NET Core o .NET Core Runtime. Para más información, consulte la
sección Instalación manual a continuación.

Distribuciones admitidas
En la tabla siguiente se muestra una lista de versiones de .NET Core actualmente compatibles y las versiones de
Debian en las que se admiten. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llegue
al final del soporte técnico o la versión de Debian llegue al final del ciclo de vida.
Una ✔️ indica que todavía se admite la versión de Debian o de .NET Core.
Una ❌ indica que la versión de Debian o de .NET Core no se admite en esa versión de Debian.
Cuando una versión de Debian y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO IN STA L A C IÓ N
DEB IA N . N ET C O RE 2. 1 . N ET C O RE 3. 1 M A N UA L )

️ 10
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 9
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌8 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

Las siguientes versiones de .NET Core ya no se admiten. aunque sus descargas siguen estando publicadas:
3.0
2.2
2.0

Procedimiento para instalar otras versiones


Los paquetes agregados a las fuentes del administrador de paquetes se denominan con un formato susceptible de
intrusiones: {product}-{type}-{version} .
product
Tipo de producto .NET que se va a instalar. Las opciones válidas son:
dotnet
aspnetcore
type
Elige el SDK o el entorno de ejecución. Las opciones válidas son:
sdk
motor en tiempo de ejecución
version
Versión del SDK o del entorno de ejecución que se va a instalar. En este artículo se proporcionarán siempre
las instrucciones para la última versión admitida. Las opciones válidas son cualquier versión de lanzamiento,
como las siguientes:
3.1
3.0
2.1
Es posible que el SDK o el entorno de ejecución que intenta descargar no esté disponible para su
distribución de Linux. Para obtener una lista de las distribuciones admitidas, consulte Dependencias y
requisitos de .NET Core.
Ejemplos
Instalación del runtime de ASP.NET Core 3.1: aspnetcore-runtime-3.1
Instalación del entorno de ejecución de ASP.NET Core 2.1: dotnet-runtime-2.1
Instalación del SDK de .NET Core 3.1: dotnet-sdk-3.1
Falta el paquete
Si la combinación de paquete y versión no funciona, no está disponible. Por ejemplo, no hay un SDK de ASP.NET
Core; los componentes del SDK se incluyen en el SDK de .NET Core. El valor aspnetcore-sdk-2.2 es no es correcto y
debe ser dotnet-sdk-2.2 . Para obtener una lista de las distribuciones de Linux compatibles con .NET Core, consulte
Dependencias y requisitos de .NET Core.

Debian 10 ✔

La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:

wget https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-


prod.deb
sudo dpkg -i packages-microsoft-prod.deb

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:
sudo apt-get update; \
sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-3.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-3.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo apt-get install -y dotnet-runtime-3.1

Debian 9 ✔

La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:

wget -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg


sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
wget https://packages.microsoft.com/config/debian/9/prod.list
sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list
sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg
sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:
sudo apt-get update; \
sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-3.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-3.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-3.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo apt-get install -y dotnet-runtime-3.1

Debian 8 ❌
❌ Tenga en cuenta que esta versión de Debian ya no se admite.
La instalación con APT puede realizarse con unos pocos comandos. Antes de instalar .NET, ejecute los siguientes
comandos para agregar la clave de la firma del paquete de Microsoft a la lista de claves de confianza y agregar el
repositorio de paquetes.
Abra un terminal y ejecute los comandos siguientes:

wget -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg


sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
wget https://packages.microsoft.com/config/debian/8/prod.list
sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list
sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg
sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:
sudo apt-get update; \
sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-2.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete dotnet-sdk-2.1 , consulte la sección Solución
de problemas de APT.

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo apt-get update; \


sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y aspnetcore-runtime-2.1

IMPORTANT
Si recibe un mensaje de error similar a No se puede encontrar el paquete aspnetcore-runtime-2.1 , consulte la
sección Solución de problemas de APT.

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-2.1 en los comandos anteriores por
dotnet-runtime-2.1 .

sudo apt-get install -y dotnet-runtime-2.1

SDK o entorno de ejecución de actualización de APT


Cuando hay disponible una nueva versión de revisión para .NET Core, basta con que la actualice mediante APT con
los siguientes comandos:

sudo apt-get update


sudo apt-get upgrade

Solución de problemas de APT


En esta sección se proporciona información sobre los errores comunes que puede recibir al usar ATP para instalar
.NET Core.
No se puede encontrar el paquete \ No se han podido instalar algunos paquetes
Si recibe un mensaje de error similar a No se puede encontrar el paquete {netcore-package} o No se han
podido instalar algunos paquetes , ejecute los comandos siguientes.
Hay dos marcadores de posición en el siguiente conjunto de comandos.
{dotnet-package}
Representa el paquete de .NET Core que va a instalar, como aspnetcore-runtime-3.1 . Se usa en el comando
sudo apt-get install siguiente.

{os-version}
Representa la versión de Linux en la que está. Se usa en el comando wget siguiente.

Primero, pruebe a purgar la lista de paquetes:

sudo dpkg --purge packages-microsoft-prod && sudo dpkg -i packages-microsoft-prod.deb


sudo apt-get update

Después, intente instalar .NET Core de nuevo. Si eso no funciona, puede ejecutar una instalación manual con los
comandos siguientes:

sudo apt-get install -y gpg


wget -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o microsoft.asc.gpg
sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
wget https://packages.microsoft.com/config/debian/{os-version}/prod.list
sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list
sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg
sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update; \
sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y {dotnet-package}

No se pudo capturar el elemento


Al instalar el paquete de .NET Core, puede ver un error similar a
Failed to fetch ... File has unexpected size ... Mirror sync in progress? . Este error significa que la fuente de
paquetes de .NET Core se está actualizando con versiones de paquetes más recientes y que debe volver a
intentarlo más tarde. Durante una actualización, la falta de disponibilidad de la fuente de paquetes no debe ser
superior a 30 minutos. Si recibe este error continuamente durante más de 30 minutos, abra una incidencia en
https://github.com/dotnet/core/issues.

Snap
.NET Core está disponible desde el almacén de snaps.
Un snap es una agrupación de una aplicación y sus dependencias que funcionan sin modificaciones en muchas
distribuciones de Linux diferentes. Los snaps se reconocen y se instalan desde el almacén de snaps. Para más
información sobre Snap, consulte Introducción a Snap.
Solo las versiones admitidas de .NET Core están disponibles mediante Snap.
Instalación del SDK
Los paquetes Snap para el SDK de .NET Core se publican con el mismo identificador: dotnet-sdk . Se puede instalar
una versión específica del SDK mediante la especificación del canal. El SDK incluye el entorno de ejecución
correspondiente. En la tabla siguiente se enumeran los canales:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) 3.1 o latest/stable

2.1 (LTS) 2.1


VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

.NET 5.0 (versión preliminar) 5.0/beta

Use el comando snap install para instalar un paquete Snap del SDK de .NET Core. Use el parámetro --channel
para indicar qué versión se va a instalar. Si se omite este parámetro, se usa latest/stable . En este ejemplo, se
especifica 3.1 :

sudo snap install dotnet-sdk --classic --channel=3.1

A continuación, registre el comando dotnet del sistema con el comando snap alias :

sudo snap alias dotnet-sdk.dotnet dotnet

Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-sdk.dotnet dotnet31 . Cuando use el comando dotnet31 , invocará
esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los tutoriales y
ejemplos, donde se espera que esté disponible un comando dotnet .
Instalación de la instancia en tiempo de ejecución
Los paquetes Snap de .NET Core Runtime se publican con su propio identificador de paquete. En la tabla siguiente
se muestra una lista de los identificadores de paquete:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) dotnet-runtime-31

3.0 dotnet-runtime-30

2.2 dotnet-runtime-22

2.1 (LTS) dotnet-runtime-21

Use el comando snap install para instalar un paquete Snap de .NET Core Runtime. En este ejemplo, se instala
.NET Core 3.1:

sudo snap install dotnet-runtime-31 --classic

A continuación, registre el comando dotnet del sistema con el comando snap alias :

sudo snap alias dotnet-runtime-31.dotnet dotnet

Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-runtime-31.dotnet dotnet31 . Cuando use el comando dotnet31 ,
invocará esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los
tutoriales y ejemplos, donde se espera que esté disponible un comando dotnet .
Errores de certificado SSL
Cuando .NET se instala mediante Snap, es posible que en algunas distribuciones no se encuentren los certificados
SSL de .NET y que reciba un error similar al siguiente durante la acción restore :

Processing post-creation actions...


Running 'dotnet restore' on /home/myhome/test/test.csproj...
Restoring packages for /home/myhome/test/test.csproj...
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : Unable to load the service index for source
https://api.nuget.org/v3/index.json. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The SSL connection could not be established,
see inner exception. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The remote certificate is invalid according to
the validation procedure. [/home/myhome/test/test.csproj]

Para resolver este problema, establezca algunas variables de entorno:

export SSL_CERT_FILE=[path-to-certificate-file]
export SSL_CERT_DIR=/dev/null

La ubicación del certificado variará en función de la distribución. Estas son las ubicaciones de las distribuciones en
las que hemos experimentado el problema.
Fedora: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
OpenSUSE: /etc/ssl/ca-bundle.pem
Solus: /etc/ssl/certs/ca-certificates.crt

Dependencias
Al realizar la instalación con un administrador de paquetes, estas bibliotecas se instalan automáticamente. Sin
embargo, si instala manualmente .NET Core o publica una aplicación independiente, deberá asegurarse de que
estas bibliotecas estén instaladas:
libc6
libgcc1
libgssapi-krb5-2
libicu52 (para 8.x)
libicu57 (para 9.x)
libicu63 (para 10.x)
libicu67 (para 11.x)
libssl1.0.0 (para 8.x)
libssl1.1 (para 9.x-11.x)
libstdc++6
zlib1g
En el caso de las aplicaciones de .NET Core que utilizan el ensamblado System.Drawing.Common, también se
necesita la dependencia siguiente:
libgdiplus (versión 6.0.1 o posteriores)

WARNING
Puede instalar una versión reciente de libgdiplus agregando el repositorio Mono al sistema. Para obtener más
información, vea https://www.mono-project.com/download/stable/.
Instalación con script
Los scripts de dotnet-install se usan para la automatización y las instalaciones que no son de administrador del
SDK y del Runtime . Puede descargar el script de https://dot.net/v1/dotnet-install.sh.
El valor predeterminado del script es instalar la versión más reciente del SDK de soporte técnico a largo plazo
(LTS), que actualmente es .NET Core 3.1. Para instalar la versión actual, que puede no ser una versión (LTS), use el
parámetro -c Current .

./dotnet-install.sh -c Current

Para instalar .NET Core Runtime en lugar del SDK, use el parámetro --runtime .

./dotnet-install.sh -c Current --runtime aspnetcore

Para instalar una versión específica, modifique el parámetro -c para indicar la versión específica. El siguiente
comando instala el SDK de .NET Core 3.1.

./dotnet-install.sh -c 3.1

Para más información, consulte la referencia de los scripts de dotnet-install.

Instalación manual
Como alternativa a los administradores de paquetes, puede descargar e instalar manualmente el SDK y el entorno
de ejecución. La instalación manual se suele llevar a cabo durante las pruebas de integración continua o en
distribuciones de Linux no admitidas. Para un desarrollador o usuario, generalmente es mejor usar un
administrador de paquetes.
Si instala el SDK de .NET Core, no necesita instalar el entorno de ejecución correspondiente. En primer lugar,
descargue una versión binaria del SDK o del entorno de ejecución de uno de los siguientes sitios:
✔ Descargas de la versión preliminar de .NET 5.0

️ Descargas de .NET Core 3.1

️ Descargas de .NET Core 2.1

Todas las descargas de .NET Core
A continuación, extraiga el archivo descargado y use el comando export para establecer las variables que se
utilizan en .NET Core. Luego, asegúrese de que .NET Core esté en PATH.
Para extraer el entorno de ejecución y hacer que los comandos de la CLI de .NET Core estén disponibles en el
terminal, en primer lugar, descargue una versión binaria de .NET Core. Luego, abra un terminal y ejecute los
siguientes comandos desde el directorio donde se guardó el archivo. El nombre del archivo puede ser distinto en
función de lo que haya descargado.
Use el comando siguiente para extraer el entorno de ejecución :

mkdir -p "$HOME/dotnet" && tar zxf aspnetcore-runtime-3.1.0-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

Use el comando siguiente para extraer el SDK :


mkdir -p "$HOME/dotnet" && tar zxf dotnet-sdk-3.1.301-linux-x64.tar.gz -C "$HOME/dotnet"
export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

TIP
Los comandos export anteriores solo hacen que los comandos de la CLI de .NET Core estén disponibles para la sesión de
terminal en la que se ha ejecutado.
Puede editar el perfil del shell para agregar los comandos de forma permanente. Hay una serie de shells distintos disponibles
para Linux, y cada uno de ellos tiene un perfil diferente. Por ejemplo:
Shell de Bash : ~/.bash_profile, ~/.bashrc
Shell de Korn : ~/.kshrc or .profile
Shell de Z : ~/.zshrc or .zprofile
Edite el archivo de origen adecuado para el shell y agregue :$HOME/dotnet al final de la instrucción PATH existente. Si no
se incluye ninguna instrucción PATH , agregue una nueva línea con export PATH=$PATH:$HOME/dotnet .
Además, agregue export DOTNET_ROOT=$HOME/dotnet al final del archivo.

Este enfoque le permite instalar diferentes versiones en ubicaciones independientes y elegir explícitamente cuál
usará cada aplicación.

Pasos siguientes
Tutorial: Creación de una aplicación de consola con el SDK de .NET Core mediante Visual Studio Code
Instalación del SDK o de .NET Core Runtime en
Fedora
16/09/2020 • 28 minutes to read • Edit Online

.NET Core es compatible con Fedora. En este artículo se describe cómo instalar .NET Core en Fedora. Cuando una
versión de Fedora no es compatible, .NET Core deja de ser compatible con esa versión. Sin embargo, estas
instrucciones pueden ayudarle a conseguir que .NET Core se ejecute en esas versiones, aunque no se admita.
Instale el SDK (que incluye el entorno de ejecución) si quiere desarrollar aplicaciones .NET. O bien, si solo necesita
ejecutar aplicaciones, instale el entorno de ejecución. Si va a instalar el entorno de ejecución, le recomendamos que
instale el entorno de ejecución de ASP.NET Core , ya que incluye los de .NET Core y ASP.NET Core.
Si ya ha instalado el SDK o el entorno de ejecución, use los comandos dotnet --list-sdks y
dotnet --list-runtimes para ver qué versiones están instaladas. Para más información, consulte Cómo comprobar
que .NET Core ya está instalado.
Las instalaciones del administrador de paquetes solo se admiten en la arquitectura x64 . Otras arquitecturas, como
ARM , deben instalar manualmente el SDK de .NET Core o .NET Core Runtime. Para más información, consulte la
sección Instalación manual a continuación.

Distribuciones admitidas
En la tabla siguiente se muestra una lista de versiones de .NET Core actualmente compatibles y las versiones de
Fedora en las que se admiten. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llegue
al final del soporte técnico o la versión de Fedora llegue al final del ciclo de vida.
Una ✔️ indica que todavía se admite la versión de Fedora o de .NET Core.
Una ❌ indica que la versión de Fedora o de .NET Core no se admite en esa versión de Fedora.
Cuando una versión de Fedora y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO IN STA L A C IÓ N
F EDO RA . N ET C O RE 2. 1 . N ET C O RE 3. 1 M A N UA L )

️ 32
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 31
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

❌ 30 ️ 2.1
✔ ️ 3.1
✔ ❌ 5.0 (versión preliminar)

❌ 29 ️ 2.1
✔ ️ 3.1
✔ ❌ 5.0 (versión preliminar)

❌ 28 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

❌ 27 ️ 2.1
✔ ❌ 3.1 ❌ 5.0 (versión preliminar)

Las siguientes versiones de .NET Core ya no se admiten. Las descargas de estas siguen estando publicadas:
3.0
2.2
2.0

Procedimiento para instalar otras versiones


Los paquetes agregados a las fuentes del administrador de paquetes se denominan con un formato susceptible de
intrusiones: {product}-{type}-{version} .
product
Tipo de producto .NET que se va a instalar. Las opciones válidas son:
dotnet
aspnetcore
type
Elige el SDK o el entorno de ejecución. Las opciones válidas son:
sdk
motor en tiempo de ejecución
version
Versión del SDK o del entorno de ejecución que se va a instalar. En este artículo se proporcionarán siempre
las instrucciones para la última versión admitida. Las opciones válidas son cualquier versión de lanzamiento,
como las siguientes:
3.1
3.0
2.1
Es posible que el SDK o el entorno de ejecución que intenta descargar no esté disponible para su
distribución de Linux. Para obtener una lista de las distribuciones admitidas, consulte Dependencias y
requisitos de .NET Core.
Ejemplos
Instalación del runtime de ASP.NET Core 3.1: aspnetcore-runtime-3.1
Instalación del entorno de ejecución de ASP.NET Core 2.1: dotnet-runtime-2.1
Instalación del SDK de .NET Core 3.1: dotnet-sdk-3.1
Falta el paquete
Si la combinación de paquete y versión no funciona, no está disponible. Por ejemplo, no hay un SDK de ASP.NET
Core; los componentes del SDK se incluyen en el SDK de .NET Core. El valor aspnetcore-sdk-2.2 es no es correcto y
debe ser dotnet-sdk-2.2 . Para obtener una lista de las distribuciones de Linux compatibles con .NET Core, consulte
Dependencias y requisitos de .NET Core.

Fedora 32 ✔

.NET Core 3.1 está disponible en los repositorios de paquetes predeterminados para Fedora 32.
Instalación del SDK
El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo dnf install dotnet-sdk-3.1

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo dnf install aspnetcore-runtime-3.1

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo dnf install dotnet-runtime-3.1

Fedora 31 ✔

Antes de instalar .NET, ejecute los siguientes comandos para agregar la clave de la firma del paquete de Microsoft a
la lista de claves de confianza y agregar el repositorio de paquetes de Microsoft. Abra un terminal y ejecute los
comandos siguientes:

sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc


sudo wget -O /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/31/prod.repo

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo dnf install dotnet-sdk-3.1

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo dnf install aspnetcore-runtime-3.1

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo dnf install dotnet-runtime-3.1

Fedora 30 ❌
❌ Tenga en cuenta que esta versión de Fedora ya no se admite.
Antes de instalar .NET, ejecute los siguientes comandos para agregar la clave de la firma del paquete de Microsoft a
la lista de claves de confianza y agregar el repositorio de paquetes de Microsoft. Abra un terminal y ejecute los
comandos siguientes:
sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
sudo wget -O /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/30/prod.repo

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo dnf install dotnet-sdk-3.1

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo dnf install aspnetcore-runtime-3.1

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo dnf install dotnet-runtime-3.1

Fedora 29 ❌
❌ Tenga en cuenta que esta versión de Fedora ya no se admite.
Antes de instalar .NET, ejecute los siguientes comandos para agregar la clave de la firma del paquete de Microsoft a
la lista de claves de confianza y agregar el repositorio de paquetes de Microsoft. Abra un terminal y ejecute los
comandos siguientes:

sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc


sudo wget -O /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/29/prod.repo

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo dnf install dotnet-sdk-3.0

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo dnf install aspnetcore-runtime-3.0


Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.0 en los comandos anteriores por
dotnet-runtime-3.0 .

sudo dnf install dotnet-runtime-3.0

Fedora 28 ❌
❌ Tenga en cuenta que esta versión de Fedora ya no se admite.
Antes de instalar .NET, ejecute los siguientes comandos para agregar la clave de la firma del paquete de Microsoft a
la lista de claves de confianza y agregar el repositorio de paquetes de Microsoft. Abra un terminal y ejecute los
comandos siguientes:

sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc


sudo wget -O /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/28/prod.repo

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo dnf install dotnet-sdk-2.0

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo dnf install aspnetcore-runtime-2.0

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-2.0 en los comandos anteriores por
dotnet-runtime-2.0 .

sudo dnf install dotnet-runtime-2.0

Fedora 27 ❌
❌ Tenga en cuenta que esta versión de Fedora ya no se admite.
Antes de instalar .NET, ejecute los siguientes comandos para agregar la clave de la firma del paquete de Microsoft a
la lista de claves de confianza y agregar el repositorio de paquetes de Microsoft. Abra un terminal y ejecute los
comandos siguientes:

sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc


sudo wget -O /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/27/prod.repo

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo dnf install dotnet-sdk-2.0

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo dnf install aspnetcore-runtime-2.0

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-2.0 en los comandos anteriores por
dotnet-runtime-2.0 .

sudo dnf install dotnet-runtime-2.0

Solución de problemas del administrador de paquetes


En esta sección se proporciona información sobre los errores comunes que puede obtener al usar el administrador
de paquetes para instalar .NET Core.
No se pudo capturar el elemento
Al instalar el paquete de .NET Core, puede ver un error similar a
signature verification failed for file 'repomd.xml' from repository 'packages-microsoft-com-prod' . En términos
generales, este error significa que la fuente de paquetes para .NET Core se está actualizando con versiones de
paquetes más recientes y que debe volver a intentarlo más tarde. Durante una actualización, la fuente de paquetes
no debe estar disponible durante más de 2 horas. Si recibe este error continuamente durante más de 2 horas, abra
una incidencia en https://github.com/dotnet/core/issues.

Snap
.NET Core está disponible desde el almacén de snaps.
Un snap es una agrupación de una aplicación y sus dependencias que funcionan sin modificaciones en muchas
distribuciones de Linux diferentes. Los snaps se reconocen y se instalan desde el almacén de snaps. Para más
información sobre Snap, consulte Introducción a Snap.
Solo las versiones admitidas de .NET Core están disponibles mediante Snap.
Instalación del SDK
Los paquetes Snap para el SDK de .NET Core se publican con el mismo identificador: dotnet-sdk . Se puede instalar
una versión específica del SDK mediante la especificación del canal. El SDK incluye el entorno de ejecución
correspondiente. En la tabla siguiente se enumeran los canales:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) 3.1 o latest/stable


VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

2.1 (LTS) 2.1

.NET 5.0 (versión preliminar) 5.0/beta

Use el comando snap install para instalar un paquete Snap del SDK de .NET Core. Use el parámetro --channel
para indicar qué versión se va a instalar. Si se omite este parámetro, se usa latest/stable . En este ejemplo, se
especifica 3.1 :

sudo snap install dotnet-sdk --classic --channel=3.1

A continuación, registre el comando dotnet del sistema con el comando snap alias :

sudo snap alias dotnet-sdk.dotnet dotnet

Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-sdk.dotnet dotnet31 . Cuando use el comando dotnet31 , invocará
esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los tutoriales y
ejemplos, donde se espera que esté disponible un comando dotnet .
Instalación de la instancia en tiempo de ejecución
Los paquetes Snap de .NET Core Runtime se publican con su propio identificador de paquete. En la tabla siguiente
se muestra una lista de los identificadores de paquete:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) dotnet-runtime-31

3.0 dotnet-runtime-30

2.2 dotnet-runtime-22

2.1 (LTS) dotnet-runtime-21

Use el comando snap install para instalar un paquete Snap de .NET Core Runtime. En este ejemplo, se instala
.NET Core 3.1:

sudo snap install dotnet-runtime-31 --classic

A continuación, registre el comando dotnet del sistema con el comando snap alias :

sudo snap alias dotnet-runtime-31.dotnet dotnet

Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-runtime-31.dotnet dotnet31 . Cuando use el comando dotnet31 ,
invocará esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los
tutoriales y ejemplos, donde se espera que esté disponible un comando dotnet .
Errores de certificado SSL
Cuando .NET se instala mediante Snap, es posible que en algunas distribuciones no se encuentren los certificados
SSL de .NET y que reciba un error similar al siguiente durante la acción restore :

Processing post-creation actions...


Running 'dotnet restore' on /home/myhome/test/test.csproj...
Restoring packages for /home/myhome/test/test.csproj...
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : Unable to load the service index for source
https://api.nuget.org/v3/index.json. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The SSL connection could not be established,
see inner exception. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The remote certificate is invalid according to
the validation procedure. [/home/myhome/test/test.csproj]

Para resolver este problema, establezca algunas variables de entorno:

export SSL_CERT_FILE=[path-to-certificate-file]
export SSL_CERT_DIR=/dev/null

La ubicación del certificado variará en función de la distribución. Estas son las ubicaciones de las distribuciones en
las que hemos experimentado el problema.
Fedora: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
OpenSUSE: /etc/ssl/ca-bundle.pem
Solus: /etc/ssl/certs/ca-certificates.crt

Dependencias
Al realizar la instalación con un administrador de paquetes, estas bibliotecas se instalan automáticamente. Sin
embargo, si instala manualmente .NET Core o publica una aplicación independiente, deberá asegurarse de que
estas bibliotecas estén instaladas:
krb5-libs
libicu
openssl-libs
Si la versión de OpenSSL del entorno de tiempo de ejecución de destino es 1.1 o más reciente, deberá instalar
compat-openssl10 .
Para obtener más información sobre las dependencias, vea Aplicaciones de Linux independientes.
En el caso de las aplicaciones de .NET Core que utilizan el ensamblado System.Drawing.Common, también se
necesitará la dependencia siguiente:
libgdiplus (versión 6.0.1 o posterior)

WARNING
Puede instalar una versión reciente de libgdiplus agregando el repositorio Mono al sistema. Para obtener más
información, vea https://www.mono-project.com/download/stable/.

Instalación con script


Los scripts de dotnet-install se usan para la automatización y las instalaciones que no son de administrador del
SDK y del Runtime . Puede descargar el script de https://dot.net/v1/dotnet-install.sh.
El valor predeterminado del script es instalar la versión más reciente del SDK de soporte técnico a largo plazo
(LTS), que actualmente es .NET Core 3.1. Para instalar la versión actual, que puede no ser una versión (LTS), use el
parámetro -c Current .

./dotnet-install.sh -c Current

Para instalar .NET Core Runtime en lugar del SDK, use el parámetro --runtime .

./dotnet-install.sh -c Current --runtime aspnetcore

Para instalar una versión específica, modifique el parámetro -c para indicar la versión específica. El siguiente
comando instala el SDK de .NET Core 3.1.

./dotnet-install.sh -c 3.1

Para más información, consulte la referencia de los scripts de dotnet-install.

Instalación manual
Como alternativa a los administradores de paquetes, puede descargar e instalar manualmente el SDK y el entorno
de ejecución. La instalación manual se suele llevar a cabo durante las pruebas de integración continua o en
distribuciones de Linux no admitidas. Para un desarrollador o usuario, generalmente es mejor usar un
administrador de paquetes.
Si instala el SDK de .NET Core, no necesita instalar el entorno de ejecución correspondiente. En primer lugar,
descargue una versión binaria del SDK o del entorno de ejecución de uno de los siguientes sitios:
✔ Descargas de la versión preliminar de .NET 5.0

️ Descargas de .NET Core 3.1

️ Descargas de .NET Core 2.1

Todas las descargas de .NET Core
A continuación, extraiga el archivo descargado y use el comando export para establecer las variables que se
utilizan en .NET Core. Luego, asegúrese de que .NET Core esté en PATH.
Para extraer el entorno de ejecución y hacer que los comandos de la CLI de .NET Core estén disponibles en el
terminal, en primer lugar, descargue una versión binaria de .NET Core. Luego, abra un terminal y ejecute los
siguientes comandos desde el directorio donde se guardó el archivo. El nombre del archivo puede ser distinto en
función de lo que haya descargado.
Use el comando siguiente para extraer el entorno de ejecución :

mkdir -p "$HOME/dotnet" && tar zxf aspnetcore-runtime-3.1.0-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

Use el comando siguiente para extraer el SDK :

mkdir -p "$HOME/dotnet" && tar zxf dotnet-sdk-3.1.301-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet
TIP
Los comandos export anteriores solo hacen que los comandos de la CLI de .NET Core estén disponibles para la sesión de
terminal en la que se ha ejecutado.
Puede editar el perfil del shell para agregar los comandos de forma permanente. Hay una serie de shells distintos disponibles
para Linux, y cada uno de ellos tiene un perfil diferente. Por ejemplo:
Shell de Bash : ~/.bash_profile, ~/.bashrc
Shell de Korn : ~/.kshrc or .profile
Shell de Z : ~/.zshrc or .zprofile
Edite el archivo de origen adecuado para el shell y agregue :$HOME/dotnet al final de la instrucción PATH existente. Si no
se incluye ninguna instrucción PATH , agregue una nueva línea con export PATH=$PATH:$HOME/dotnet .
Además, agregue export DOTNET_ROOT=$HOME/dotnet al final del archivo.

Este enfoque le permite instalar diferentes versiones en ubicaciones independientes y elegir explícitamente cuál
usará cada aplicación.

Pasos siguientes
Tutorial: Creación de una aplicación de consola con el SDK de .NET Core mediante Visual Studio Code
Instalación del SDK de .NET Core o de .NET Core
Runtime en openSUSE
16/09/2020 • 18 minutes to read • Edit Online

.NET Core es compatible con openSUSE. En este artículo se describe cómo instalar .NET Core en openSUSE.
Instale el SDK (que incluye el entorno de ejecución) si quiere desarrollar aplicaciones .NET. O bien, si solo necesita
ejecutar aplicaciones, instale el entorno de ejecución. Si va a instalar el entorno de ejecución, le recomendamos que
instale el entorno de ejecución de ASP.NET Core , ya que incluye los de .NET Core y ASP.NET Core.
Si ya ha instalado el SDK o el entorno de ejecución, use los comandos dotnet --list-sdks y
dotnet --list-runtimes para ver qué versiones están instaladas. Para más información, consulte Cómo comprobar
que .NET Core ya está instalado.
Las instalaciones del administrador de paquetes solo se admiten en la arquitectura x64 . Otras arquitecturas, como
ARM , deben instalar manualmente el SDK de .NET Core o .NET Core Runtime. Para más información, consulte la
sección Instalación manual a continuación.

Distribuciones admitidas
En la tabla siguiente se muestra una lista de las versiones de .NET Core compatibles actualmente con openSUSE
15. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llegue al final del soporte técnico
o ya no se admita la versión de openSUSE.
Una ✔️ indica que todavía se admite la versión de openSUSE o de .NET Core.
Una ❌ indica que la versión de openSUSE o de .NET Core no se admite en esa versión de openSUSE.
Cuando una versión de openSUSE y una versión de .NET Core tienen una ✔️ , se admite esa combinación de
sistema operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO IN STA L A C IÓ N
O P EN SUSE . N ET C O RE 2. 1 . N ET C O RE 3. 1 M A N UA L )

️ 15
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

Las siguientes versiones de .NET Core ya no se admiten. aunque sus descargas siguen estando publicadas:
3.0
2.2
2.0

Procedimiento para instalar otras versiones


Los paquetes agregados a las fuentes del administrador de paquetes se denominan con un formato susceptible de
intrusiones: {product}-{type}-{version} .
product
Tipo de producto .NET que se va a instalar. Las opciones válidas son:
dotnet
aspnetcore
type
Elige el SDK o el entorno de ejecución. Las opciones válidas son:
sdk
motor en tiempo de ejecución
version
Versión del SDK o del entorno de ejecución que se va a instalar. En este artículo se proporcionarán siempre
las instrucciones para la última versión admitida. Las opciones válidas son cualquier versión de lanzamiento,
como las siguientes:
3.1
3.0
2.1
Es posible que el SDK o el entorno de ejecución que intenta descargar no esté disponible para su
distribución de Linux. Para obtener una lista de las distribuciones admitidas, consulte Dependencias y
requisitos de .NET Core.
Ejemplos
Instalación del runtime de ASP.NET Core 3.1: aspnetcore-runtime-3.1
Instalación del entorno de ejecución de ASP.NET Core 2.1: dotnet-runtime-2.1
Instalación del SDK de .NET Core 3.1: dotnet-sdk-3.1
Falta el paquete
Si la combinación de paquete y versión no funciona, no está disponible. Por ejemplo, no hay un SDK de ASP.NET
Core; los componentes del SDK se incluyen en el SDK de .NET Core. El valor aspnetcore-sdk-2.2 es no es correcto y
debe ser dotnet-sdk-2.2 . Para obtener una lista de las distribuciones de Linux compatibles con .NET Core, consulte
Dependencias y requisitos de .NET Core.

openSUSE 15 ✔

Antes de instalar .NET, ejecute los siguientes comandos para agregar la clave de la firma del paquete de Microsoft a
la lista de claves de confianza y agregar el repositorio de paquetes de Microsoft. Abra un terminal y ejecute los
comandos siguientes:

sudo zypper install libicu


sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
wget https://packages.microsoft.com/config/opensuse/15/prod.repo
sudo mv prod.repo /etc/zypp/repos.d/microsoft-prod.repo
sudo chown root:root /etc/zypp/repos.d/microsoft-prod.repo

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Para instalar el SDK de .NET Core, ejecute los
siguientes comandos.

sudo zypper install dotnet-sdk-3.1

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.
sudo zypper install aspnetcore-runtime-3.1

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-2.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo zypper install dotnet-runtime-3.1

Solución de problemas del administrador de paquetes


En esta sección se proporciona información sobre los errores comunes que puede obtener al usar el administrador
de paquetes para instalar .NET Core.
No se pudo capturar el elemento
Al instalar el paquete de .NET Core, puede ver un error similar a
signature verification failed for file 'repomd.xml' from repository 'packages-microsoft-com-prod' . En términos
generales, este error significa que la fuente de paquetes para .NET Core se está actualizando con versiones de
paquetes más recientes y que debe volver a intentarlo más tarde. Durante una actualización, la fuente de paquetes
no debe estar disponible durante más de 2 horas. Si recibe este error continuamente durante más de 2 horas, abra
una incidencia en https://github.com/dotnet/core/issues.

Snap
.NET Core está disponible desde el almacén de snaps.
Un snap es una agrupación de una aplicación y sus dependencias que funcionan sin modificaciones en muchas
distribuciones de Linux diferentes. Los snaps se reconocen y se instalan desde el almacén de snaps. Para más
información sobre Snap, consulte Introducción a Snap.
Solo las versiones admitidas de .NET Core están disponibles mediante Snap.
Instalación del SDK
Los paquetes Snap para el SDK de .NET Core se publican con el mismo identificador: dotnet-sdk . Se puede instalar
una versión específica del SDK mediante la especificación del canal. El SDK incluye el entorno de ejecución
correspondiente. En la tabla siguiente se enumeran los canales:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) 3.1 o latest/stable

2.1 (LTS) 2.1

.NET 5.0 (versión preliminar) 5.0/beta

Use el comando snap install para instalar un paquete Snap del SDK de .NET Core. Use el parámetro --channel
para indicar qué versión se va a instalar. Si se omite este parámetro, se usa latest/stable . En este ejemplo, se
especifica 3.1 :

sudo snap install dotnet-sdk --classic --channel=3.1

A continuación, registre el comando dotnet del sistema con el comando snap alias :
sudo snap alias dotnet-sdk.dotnet dotnet

Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-sdk.dotnet dotnet31 . Cuando use el comando dotnet31 , invocará
esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los tutoriales y
ejemplos, donde se espera que esté disponible un comando dotnet .
Instalación de la instancia en tiempo de ejecución
Los paquetes Snap de .NET Core Runtime se publican con su propio identificador de paquete. En la tabla siguiente
se muestra una lista de los identificadores de paquete:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) dotnet-runtime-31

3.0 dotnet-runtime-30

2.2 dotnet-runtime-22

2.1 (LTS) dotnet-runtime-21

Use el comando snap install para instalar un paquete Snap de .NET Core Runtime. En este ejemplo, se instala
.NET Core 3.1:

sudo snap install dotnet-runtime-31 --classic

A continuación, registre el comando dotnet del sistema con el comando snap alias :

sudo snap alias dotnet-runtime-31.dotnet dotnet

Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-runtime-31.dotnet dotnet31 . Cuando use el comando dotnet31 ,
invocará esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los
tutoriales y ejemplos, donde se espera que esté disponible un comando dotnet .
Errores de certificado SSL
Cuando .NET se instala mediante Snap, es posible que en algunas distribuciones no se encuentren los certificados
SSL de .NET y que reciba un error similar al siguiente durante la acción restore :

Processing post-creation actions...


Running 'dotnet restore' on /home/myhome/test/test.csproj...
Restoring packages for /home/myhome/test/test.csproj...
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : Unable to load the service index for source
https://api.nuget.org/v3/index.json. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The SSL connection could not be established,
see inner exception. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The remote certificate is invalid according to
the validation procedure. [/home/myhome/test/test.csproj]

Para resolver este problema, establezca algunas variables de entorno:


export SSL_CERT_FILE=[path-to-certificate-file]
export SSL_CERT_DIR=/dev/null

La ubicación del certificado variará en función de la distribución. Estas son las ubicaciones de las distribuciones en
las que hemos experimentado el problema.
Fedora: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
OpenSUSE: /etc/ssl/ca-bundle.pem
Solus: /etc/ssl/certs/ca-certificates.crt

Dependencias
Al realizar la instalación con un administrador de paquetes, estas bibliotecas se instalan automáticamente. Sin
embargo, si instala manualmente .NET Core o publica una aplicación independiente, deberá asegurarse de que
estas bibliotecas estén instaladas:
krb5
libicu
libopenssl1_0_0
Si la versión de OpenSSL del entorno de tiempo de ejecución de destino es 1.1 o más reciente, deberá instalar
compat-openssl10 .
Para obtener más información sobre las dependencias, vea Aplicaciones de Linux independientes.
En el caso de las aplicaciones de .NET Core que utilizan el ensamblado System.Drawing.Common, también se
necesitará la dependencia siguiente:
libgdiplus (versión 6.0.1 o posterior)

WARNING
Puede instalar una versión reciente de libgdiplus agregando el repositorio Mono al sistema. Para obtener más
información, vea https://www.mono-project.com/download/stable/.

Instalación con script


Los scripts de dotnet-install se usan para la automatización y las instalaciones que no son de administrador del
SDK y del Runtime . Puede descargar el script de https://dot.net/v1/dotnet-install.sh.
El valor predeterminado del script es instalar la versión más reciente del SDK de soporte técnico a largo plazo
(LTS), que actualmente es .NET Core 3.1. Para instalar la versión actual, que puede no ser una versión (LTS), use el
parámetro -c Current .

./dotnet-install.sh -c Current

Para instalar .NET Core Runtime en lugar del SDK, use el parámetro --runtime .

./dotnet-install.sh -c Current --runtime aspnetcore

Para instalar una versión específica, modifique el parámetro -c para indicar la versión específica. El siguiente
comando instala el SDK de .NET Core 3.1.
./dotnet-install.sh -c 3.1

Para más información, consulte la referencia de los scripts de dotnet-install.

Instalación manual
Como alternativa a los administradores de paquetes, puede descargar e instalar manualmente el SDK y el entorno
de ejecución. La instalación manual se suele llevar a cabo durante las pruebas de integración continua o en
distribuciones de Linux no admitidas. Para un desarrollador o usuario, generalmente es mejor usar un
administrador de paquetes.
Si instala el SDK de .NET Core, no necesita instalar el entorno de ejecución correspondiente. En primer lugar,
descargue una versión binaria del SDK o del entorno de ejecución de uno de los siguientes sitios:
✔ Descargas de la versión preliminar de .NET 5.0

️ Descargas de .NET Core 3.1

️ Descargas de .NET Core 2.1

Todas las descargas de .NET Core
A continuación, extraiga el archivo descargado y use el comando export para establecer las variables que se
utilizan en .NET Core. Luego, asegúrese de que .NET Core esté en PATH.
Para extraer el entorno de ejecución y hacer que los comandos de la CLI de .NET Core estén disponibles en el
terminal, en primer lugar, descargue una versión binaria de .NET Core. Luego, abra un terminal y ejecute los
siguientes comandos desde el directorio donde se guardó el archivo. El nombre del archivo puede ser distinto en
función de lo que haya descargado.
Use el comando siguiente para extraer el entorno de ejecución :

mkdir -p "$HOME/dotnet" && tar zxf aspnetcore-runtime-3.1.0-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

Use el comando siguiente para extraer el SDK :

mkdir -p "$HOME/dotnet" && tar zxf dotnet-sdk-3.1.301-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

TIP
Los comandos export anteriores solo hacen que los comandos de la CLI de .NET Core estén disponibles para la sesión de
terminal en la que se ha ejecutado.
Puede editar el perfil del shell para agregar los comandos de forma permanente. Hay una serie de shells distintos disponibles
para Linux, y cada uno de ellos tiene un perfil diferente. Por ejemplo:
Shell de Bash : ~/.bash_profile, ~/.bashrc
Shell de Korn : ~/.kshrc or .profile
Shell de Z : ~/.zshrc or .zprofile
Edite el archivo de origen adecuado para el shell y agregue :$HOME/dotnet al final de la instrucción PATH existente. Si no
se incluye ninguna instrucción PATH , agregue una nueva línea con export PATH=$PATH:$HOME/dotnet .
Además, agregue export DOTNET_ROOT=$HOME/dotnet al final del archivo.
Este enfoque le permite instalar diferentes versiones en ubicaciones independientes y elegir explícitamente cuál
usará cada aplicación.

Pasos siguientes
Tutorial: Creación de una aplicación de consola con el SDK de .NET Core mediante Visual Studio Code
Instalación del SDK de .NET Core o de .NET Core
Runtime en RHEL
16/09/2020 • 18 minutes to read • Edit Online

.NET Core es compatible con RHEL. En este artículo se describe cómo instalar .NET Core en RHEL.
Instale el SDK (que incluye el entorno de ejecución) si quiere desarrollar aplicaciones .NET. O bien, si solo necesita
ejecutar aplicaciones, instale el entorno de ejecución. Si va a instalar el entorno de ejecución, le recomendamos que
instale el entorno de ejecución de ASP.NET Core , ya que incluye los de .NET Core y ASP.NET Core.
Si ya ha instalado el SDK o el entorno de ejecución, use los comandos dotnet --list-sdks y
dotnet --list-runtimes para ver qué versiones están instaladas. Para más información, consulte Cómo comprobar
que .NET Core ya está instalado.

Registro de la suscripción de Red Hat


Para instalar .NET Core desde Red Hat en RHEL, primero debe registrarse con el administrador de suscripciones de
Red Hat. Si esto no se ha realizado en el sistema, o si no tiene certeza de ello, vea la documentación del producto
de Red Hat para .NET Core.

Distribuciones admitidas
En la tabla siguiente se muestra una lista de las versiones de .NET Core compatibles actualmente con RHEL 7 y
RHEL 8. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llegue al final del soporte
técnico o ya no se admita la versión de RHEL.
Una ✔
️ indica que todavía se admite la versión de RHEL o de .NET Core.
Una ❌ indica que la versión de RHEL o de .NET Core no se admite en esa versión de RHEL.
Cuando una versión de RHEL y una versión de .NET Core tienen una ✔
️ , se admite esa combinación de sistema
operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO IN STA L A C IÓ N
RH EL . N ET C O RE 2. 1 . N ET C O RE 3. 1 M A N UA L )

️ 8
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 7
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

Las siguientes versiones de .NET Core ya no se admiten. Las descargas de estas siguen estando publicadas:
3.0
2.2
2.0

Procedimiento para instalar otras versiones


Consulte la documentación de Red Hat para .NET Core sobre los pasos necesarios para instalar otras versiones de
.NET Core.
RHEL 8 ✔

.NET Core se incluye en los repositorios de AppStream para RHEL 8.
Instalación del SDK
El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

sudo dnf install dotnet-sdk-3.1

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo dnf install aspnetcore-runtime-3.1

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-3.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo dnf install dotnet-runtime-3.1

RHEL 7 ✔

Antes de instalar .NET, ejecute los siguientes comandos para agregar la clave de la firma del paquete de Microsoft a
la lista de claves de confianza y agregar el repositorio de paquetes de Microsoft. Abra un terminal y ejecute los
comandos siguientes:
El siguiente comando instala el paquete scl-utils :

sudo yum install scl-utils

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Si instala el SDK de .NET Core, no necesita
instalar el entorno de ejecución correspondiente. Para instalar el SDK de .NET Core, ejecute los siguientes
comandos:

subscription-manager repos --enable=rhel-7-server-dotnet-rpms


yum install rh-dotnet31 -y
scl enable rh-dotnet31 bash

Red Hat no recomienda habilitar rh-dotnet31 de forma permanente porque puede afectar a otros programas. Por
ejemplo, rh-dotnet31 incluye una versión de libcurl que varía con respecto a la versión base de RHEL. Esto
puede dar lugar a problemas en programas que no esperan una versión diferente de libcurl . Si quiere habilitar
rh-dotnet de forma permanente, agregue la siguiente línea al archivo ~/.bashrc.

source scl_source enable rh-dotnet31


Instalación de la instancia en tiempo de ejecución
.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

subscription-manager repos --enable=rhel-7-server-dotnet-rpms


yum install rh-dotnet31-aspnetcore-runtime-3.1 -y
scl enable rh-dotnet31-aspnetcore-runtime-3.1 bash

Red Hat no recomienda habilitar rh-dotnet31-aspnetcore-runtime-3.1 de forma permanente porque puede afectar
a otros programas. Por ejemplo, rh-dotnet31-aspnetcore-runtime-3.1 incluye una versión de libcurl que varía
con respecto a la versión base de RHEL. Esto puede dar lugar a problemas en programas que no esperan una
versión diferente de libcurl . Si quiere habilitar rh-dotnet31-aspnetcore-runtime-3.1 de forma permanente,
agregue la siguiente línea al archivo ~/.bashrc.

source scl_source enable rh-dotnet31-aspnetcore-runtime-3.1

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace rh-dotnet31-aspnetcore-runtime-3.1 en los comandos
anteriores por rh-dotnet31-dotnet-runtime-3.1 .

Snap
.NET Core está disponible desde el almacén de snaps.
Un snap es una agrupación de una aplicación y sus dependencias que funcionan sin modificaciones en muchas
distribuciones de Linux diferentes. Los snaps se reconocen y se instalan desde el almacén de snaps. Para más
información sobre Snap, consulte Introducción a Snap.
Solo las versiones admitidas de .NET Core están disponibles mediante Snap.
Instalación del SDK
Los paquetes Snap para el SDK de .NET Core se publican con el mismo identificador: dotnet-sdk . Se puede instalar
una versión específica del SDK mediante la especificación del canal. El SDK incluye el entorno de ejecución
correspondiente. En la tabla siguiente se enumeran los canales:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) 3.1 o latest/stable

2.1 (LTS) 2.1

.NET 5.0 (versión preliminar) 5.0/beta

Use el comando snap install para instalar un paquete Snap del SDK de .NET Core. Use el parámetro --channel
para indicar qué versión se va a instalar. Si se omite este parámetro, se usa latest/stable . En este ejemplo, se
especifica 3.1 :

sudo snap install dotnet-sdk --classic --channel=3.1

A continuación, registre el comando dotnet del sistema con el comando snap alias :
sudo snap alias dotnet-sdk.dotnet dotnet

Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-sdk.dotnet dotnet31 . Cuando use el comando dotnet31 , invocará
esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los tutoriales y
ejemplos, donde se espera que esté disponible un comando dotnet .
Instalación de la instancia en tiempo de ejecución
Los paquetes Snap de .NET Core Runtime se publican con su propio identificador de paquete. En la tabla siguiente
se muestra una lista de los identificadores de paquete:

VERSIÓ N DE . N ET C O RE PA Q UET E SN A P

3.1 (LTS) dotnet-runtime-31

3.0 dotnet-runtime-30

2.2 dotnet-runtime-22

2.1 (LTS) dotnet-runtime-21

Use el comando snap install para instalar un paquete Snap de .NET Core Runtime. En este ejemplo, se instala
.NET Core 3.1:

sudo snap install dotnet-runtime-31 --classic

A continuación, registre el comando dotnet del sistema con el comando snap alias :

sudo snap alias dotnet-runtime-31.dotnet dotnet

Este comando tiene el formato sudo snap alias {package}.{command} {alias} . Puede elegir cualquier nombre de
{alias} que prefiera. Por ejemplo, puede asignar un nombre al comando después de la versión específica
instalada por el snap sudo snap alias dotnet-runtime-31.dotnet dotnet31 . Cuando use el comando dotnet31 ,
invocará esta versión específica de .NET. Sin embargo, esta operación no es compatible con la mayoría de los
tutoriales y ejemplos, donde se espera que esté disponible un comando dotnet .
Errores de certificado SSL
Cuando .NET se instala mediante Snap, es posible que en algunas distribuciones no se encuentren los certificados
SSL de .NET y que reciba un error similar al siguiente durante la acción restore :

Processing post-creation actions...


Running 'dotnet restore' on /home/myhome/test/test.csproj...
Restoring packages for /home/myhome/test/test.csproj...
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : Unable to load the service index for source
https://api.nuget.org/v3/index.json. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The SSL connection could not be established,
see inner exception. [/home/myhome/test/test.csproj]
/snap/dotnet-sdk/27/sdk/2.2.103/NuGet.targets(114,5): error : The remote certificate is invalid according to
the validation procedure. [/home/myhome/test/test.csproj]

Para resolver este problema, establezca algunas variables de entorno:


export SSL_CERT_FILE=[path-to-certificate-file]
export SSL_CERT_DIR=/dev/null

La ubicación del certificado variará en función de la distribución. Estas son las ubicaciones de las distribuciones en
las que hemos experimentado el problema.
Fedora: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
OpenSUSE: /etc/ssl/ca-bundle.pem
Solus: /etc/ssl/certs/ca-certificates.crt

Dependencias
Al realizar la instalación con un administrador de paquetes, estas bibliotecas se instalan automáticamente. Sin
embargo, si instala manualmente .NET Core o publica una aplicación independiente, deberá asegurarse de que
estas bibliotecas estén instaladas:
krb5-libs
libicu
openssl-libs
Si la versión de OpenSSL del entorno de tiempo de ejecución de destino es 1.1 o más reciente, deberá instalar
compat-openssl10 .
Para obtener más información sobre las dependencias, vea Aplicaciones de Linux independientes.
En el caso de las aplicaciones de .NET Core que utilizan el ensamblado System.Drawing.Common, también se
necesitará la dependencia siguiente:
libgdiplus (versión 6.0.1 o posterior)

WARNING
Puede instalar una versión reciente de libgdiplus agregando el repositorio Mono al sistema. Para obtener más
información, vea https://www.mono-project.com/download/stable/.

Instalación con script


Los scripts de dotnet-install se usan para la automatización y las instalaciones que no son de administrador del
SDK y del Runtime . Puede descargar el script de https://dot.net/v1/dotnet-install.sh.
El valor predeterminado del script es instalar la versión más reciente del SDK de soporte técnico a largo plazo
(LTS), que actualmente es .NET Core 3.1. Para instalar la versión actual, que puede no ser una versión (LTS), use el
parámetro -c Current .

./dotnet-install.sh -c Current

Para instalar .NET Core Runtime en lugar del SDK, use el parámetro --runtime .

./dotnet-install.sh -c Current --runtime aspnetcore

Para instalar una versión específica, modifique el parámetro -c para indicar la versión específica. El siguiente
comando instala el SDK de .NET Core 3.1.
./dotnet-install.sh -c 3.1

Para más información, consulte la referencia de los scripts de dotnet-install.

Instalación manual
Como alternativa a los administradores de paquetes, puede descargar e instalar manualmente el SDK y el entorno
de ejecución. La instalación manual se suele llevar a cabo durante las pruebas de integración continua o en
distribuciones de Linux no admitidas. Para un desarrollador o usuario, generalmente es mejor usar un
administrador de paquetes.
Si instala el SDK de .NET Core, no necesita instalar el entorno de ejecución correspondiente. En primer lugar,
descargue una versión binaria del SDK o del entorno de ejecución de uno de los siguientes sitios:
✔ Descargas de la versión preliminar de .NET 5.0

️ Descargas de .NET Core 3.1

️ Descargas de .NET Core 2.1

Todas las descargas de .NET Core
A continuación, extraiga el archivo descargado y use el comando export para establecer las variables que se
utilizan en .NET Core. Luego, asegúrese de que .NET Core esté en PATH.
Para extraer el entorno de ejecución y hacer que los comandos de la CLI de .NET Core estén disponibles en el
terminal, en primer lugar, descargue una versión binaria de .NET Core. Luego, abra un terminal y ejecute los
siguientes comandos desde el directorio donde se guardó el archivo. El nombre del archivo puede ser distinto en
función de lo que haya descargado.
Use el comando siguiente para extraer el entorno de ejecución :

mkdir -p "$HOME/dotnet" && tar zxf aspnetcore-runtime-3.1.0-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

Use el comando siguiente para extraer el SDK :

mkdir -p "$HOME/dotnet" && tar zxf dotnet-sdk-3.1.301-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

TIP
Los comandos export anteriores solo hacen que los comandos de la CLI de .NET Core estén disponibles para la sesión de
terminal en la que se ha ejecutado.
Puede editar el perfil del shell para agregar los comandos de forma permanente. Hay una serie de shells distintos disponibles
para Linux, y cada uno de ellos tiene un perfil diferente. Por ejemplo:
Shell de Bash : ~/.bash_profile, ~/.bashrc
Shell de Korn : ~/.kshrc or .profile
Shell de Z : ~/.zshrc or .zprofile
Edite el archivo de origen adecuado para el shell y agregue :$HOME/dotnet al final de la instrucción PATH existente. Si no
se incluye ninguna instrucción PATH , agregue una nueva línea con export PATH=$PATH:$HOME/dotnet .
Además, agregue export DOTNET_ROOT=$HOME/dotnet al final del archivo.
Este enfoque le permite instalar diferentes versiones en ubicaciones independientes y elegir explícitamente cuál
usará cada aplicación.

Pasos siguientes
Tutorial: Creación de una aplicación de consola con el SDK de .NET Core mediante Visual Studio Code
Instalación del SDK o de .NET Core Runtime en SLES
16/09/2020 • 15 minutes to read • Edit Online

.NET Core es compatible con SLES. En este artículo se describe cómo instalar .NET Core en SLES.
Instale el SDK (que incluye el entorno de ejecución) si quiere desarrollar aplicaciones .NET. O bien, si solo necesita
ejecutar aplicaciones, instale el entorno de ejecución. Si va a instalar el entorno de ejecución, le recomendamos que
instale el entorno de ejecución de ASP.NET Core , ya que incluye los de .NET Core y ASP.NET Core.
Si ya ha instalado el SDK o el entorno de ejecución, use los comandos dotnet --list-sdks y
dotnet --list-runtimes para ver qué versiones están instaladas. Para más información, consulte Cómo comprobar
que .NET Core ya está instalado.

Distribuciones admitidas
En la tabla siguiente se muestra una lista de las versiones de .NET Core compatibles actualmente en SLES 12 SP 2 y
SLES 15. Estas versiones siguen siendo compatibles hasta que la versión de .NET Core llegue al final del soporte
técnico o ya no se admita la versión de SLES.
Una ✔
️ indica que todavía se admite la versión de SLES o de .NET Core.
Una ❌ indica que la versión de SLES o de .NET Core no se admite en esa versión de SLES.
Cuando una versión de SLES y una versión de .NET Core tienen una ✔
️ , se admite esa combinación de sistema
operativo y .NET.

VERSIÓ N P REL IM IN A R DE
. N ET 5 ( SO LO IN STA L A C IÓ N
SL ES . N ET C O RE 2. 1 . N ET C O RE 3. 1 M A N UA L )

️ 15
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

️ 12 SP2
✔ ️ 2.1
✔ ️ 3.1
✔ ️ 5.0 (versión preliminar)

Las siguientes versiones de .NET Core ya no se admiten. aunque sus descargas siguen estando publicadas:
3.0
2.2
2.0

Procedimiento para instalar otras versiones


Los paquetes agregados a las fuentes del administrador de paquetes se denominan con un formato susceptible de
intrusiones: {product}-{type}-{version} .
product
Tipo de producto .NET que se va a instalar. Las opciones válidas son:
dotnet
aspnetcore
type
Elige el SDK o el entorno de ejecución. Las opciones válidas son:
sdk
motor en tiempo de ejecución
version
Versión del SDK o del entorno de ejecución que se va a instalar. En este artículo se proporcionarán siempre
las instrucciones para la última versión admitida. Las opciones válidas son cualquier versión de lanzamiento,
como las siguientes:
3.1
3.0
2.1
Es posible que el SDK o el entorno de ejecución que intenta descargar no esté disponible para su
distribución de Linux. Para obtener una lista de las distribuciones admitidas, consulte Dependencias y
requisitos de .NET Core.
Ejemplos
Instalación del runtime de ASP.NET Core 3.1: aspnetcore-runtime-3.1
Instalación del entorno de ejecución de ASP.NET Core 2.1: dotnet-runtime-2.1
Instalación del SDK de .NET Core 3.1: dotnet-sdk-3.1
Falta el paquete
Si la combinación de paquete y versión no funciona, no está disponible. Por ejemplo, no hay un SDK de ASP.NET
Core; los componentes del SDK se incluyen en el SDK de .NET Core. El valor aspnetcore-sdk-2.2 es no es correcto y
debe ser dotnet-sdk-2.2 . Para obtener una lista de las distribuciones de Linux compatibles con .NET Core, consulte
Dependencias y requisitos de .NET Core.

SLES 15 ✔

Antes de instalar .NET, ejecute los siguientes comandos para agregar la clave de la firma del paquete de Microsoft a
la lista de claves de confianza y agregar el repositorio de paquetes de Microsoft. Abra un terminal y ejecute los
comandos siguientes:

sudo rpm -Uvh https://packages.microsoft.com/config/sles/15/packages-microsoft-prod.rpm

Actualmente, el paquete de instalación del repositorio de Microsoft de SLES 15 instala el archivo microsoft-
prod.repo en el directorio incorrecto, lo que impide que zypper pueda encontrar los paquetes de .NET Core. Para
corregir este problema, cree un symlink en el directorio apropiado.

sudo ln -s /etc/yum.repos.d/microsoft-prod.repo /etc/zypp/repos.d/microsoft-prod.repo

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Para instalar el SDK de .NET Core, ejecute los
siguientes comandos.

sudo zypper install dotnet-sdk-3.1

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.
sudo zypper install aspnetcore-runtime-3.1

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-2.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo zypper install dotnet-runtime-3.1

SLES 12 ✔

.NET Core necesita SP2 como mínimo para la familia SLES 12.
Antes de instalar .NET, ejecute los siguientes comandos para agregar la clave de la firma del paquete de Microsoft a
la lista de claves de confianza y agregar el repositorio de paquetes de Microsoft. Abra un terminal y ejecute los
comandos siguientes:

sudo rpm -Uvh https://packages.microsoft.com/config/sles/12/packages-microsoft-prod.rpm

Instalación del SDK


El SDK de .NET Core permite desarrollar aplicaciones con .NET Core. Para instalar el SDK de .NET Core, ejecute los
siguientes comandos.

sudo zypper install dotnet-sdk-3.1

Instalación de la instancia en tiempo de ejecución


.NET Core Runtime le permite ejecutar aplicaciones que se realizaron con .NET Core que no incluían el entorno de
ejecución. Los siguientes comandos instalan el entorno de ejecución de ASP.NET Core, que es el entorno de
ejecución más compatible con .NET Core. En el terminal, ejecute los comandos siguientes.

sudo zypper install aspnetcore-runtime-3.1

Una alternativa al entorno de ejecución de ASP.NET Core es instalar la instancia de .NET Core Runtime que no
incluye compatibilidad con ASP.NET Core. Reemplace aspnetcore-runtime-2.1 en los comandos anteriores por
dotnet-runtime-3.1 .

sudo zypper install dotnet-runtime-3.1

Solución de problemas del administrador de paquetes


En esta sección se proporciona información sobre los errores comunes que puede obtener al usar el administrador
de paquetes para instalar .NET Core.
No se pudo capturar el elemento
Al instalar el paquete de .NET Core, puede ver un error similar a
signature verification failed for file 'repomd.xml' from repository 'packages-microsoft-com-prod' . En términos
generales, este error significa que la fuente de paquetes para .NET Core se está actualizando con versiones de
paquetes más recientes y que debe volver a intentarlo más tarde. Durante una actualización, la fuente de paquetes
no debe estar disponible durante más de 2 horas. Si recibe este error continuamente durante más de 2 horas, abra
una incidencia en https://github.com/dotnet/core/issues.

Dependencias
Al realizar la instalación con un administrador de paquetes, estas bibliotecas se instalan automáticamente. Sin
embargo, si instala manualmente .NET Core o publica una aplicación independiente, deberá asegurarse de que
estas bibliotecas estén instaladas:
krb5
libicu
libopenssl1_1
Si la versión de OpenSSL del entorno de tiempo de ejecución de destino es 1.1 o más reciente, deberá instalar
compat-openssl10 .
Para obtener más información sobre las dependencias, vea Aplicaciones de Linux independientes.
En el caso de las aplicaciones de .NET Core que utilizan el ensamblado System.Drawing.Common, también se
necesitará la dependencia siguiente:
libgdiplus (versión 6.0.1 o posterior)

WARNING
Puede instalar una versión reciente de libgdiplus agregando el repositorio Mono al sistema. Para obtener más
información, vea https://www.mono-project.com/download/stable/.

Instalación con script


Los scripts de dotnet-install se usan para la automatización y las instalaciones que no son de administrador del
SDK y del Runtime . Puede descargar el script de https://dot.net/v1/dotnet-install.sh.
El valor predeterminado del script es instalar la versión más reciente del SDK de soporte técnico a largo plazo
(LTS), que actualmente es .NET Core 3.1. Para instalar la versión actual, que puede no ser una versión (LTS), use el
parámetro -c Current .

./dotnet-install.sh -c Current

Para instalar .NET Core Runtime en lugar del SDK, use el parámetro --runtime .

./dotnet-install.sh -c Current --runtime aspnetcore

Para instalar una versión específica, modifique el parámetro -c para indicar la versión específica. El siguiente
comando instala el SDK de .NET Core 3.1.

./dotnet-install.sh -c 3.1

Para más información, consulte la referencia de los scripts de dotnet-install.

Instalación manual
Como alternativa a los administradores de paquetes, puede descargar e instalar manualmente el SDK y el entorno
de ejecución. La instalación manual se suele llevar a cabo durante las pruebas de integración continua o en
distribuciones de Linux no admitidas. Para un desarrollador o usuario, generalmente es mejor usar un
administrador de paquetes.
Si instala el SDK de .NET Core, no necesita instalar el entorno de ejecución correspondiente. En primer lugar,
descargue una versión binaria del SDK o del entorno de ejecución de uno de los siguientes sitios:
✔ Descargas de la versión preliminar de .NET 5.0

️ Descargas de .NET Core 3.1

️ Descargas de .NET Core 2.1

Todas las descargas de .NET Core
A continuación, extraiga el archivo descargado y use el comando export para establecer las variables que se
utilizan en .NET Core. Luego, asegúrese de que .NET Core esté en PATH.
Para extraer el entorno de ejecución y hacer que los comandos de la CLI de .NET Core estén disponibles en el
terminal, en primer lugar, descargue una versión binaria de .NET Core. Luego, abra un terminal y ejecute los
siguientes comandos desde el directorio donde se guardó el archivo. El nombre del archivo puede ser distinto en
función de lo que haya descargado.
Use el comando siguiente para extraer el entorno de ejecución :

mkdir -p "$HOME/dotnet" && tar zxf aspnetcore-runtime-3.1.0-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

Use el comando siguiente para extraer el SDK :

mkdir -p "$HOME/dotnet" && tar zxf dotnet-sdk-3.1.301-linux-x64.tar.gz -C "$HOME/dotnet"


export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet

TIP
Los comandos export anteriores solo hacen que los comandos de la CLI de .NET Core estén disponibles para la sesión de
terminal en la que se ha ejecutado.
Puede editar el perfil del shell para agregar los comandos de forma permanente. Hay una serie de shells distintos disponibles
para Linux, y cada uno de ellos tiene un perfil diferente. Por ejemplo:
Shell de Bash : ~/.bash_profile, ~/.bashrc
Shell de Korn : ~/.kshrc or .profile
Shell de Z : ~/.zshrc or .zprofile
Edite el archivo de origen adecuado para el shell y agregue :$HOME/dotnet al final de la instrucción PATH existente. Si no
se incluye ninguna instrucción PATH , agregue una nueva línea con export PATH=$PATH:$HOME/dotnet .
Además, agregue export DOTNET_ROOT=$HOME/dotnet al final del archivo.

Este enfoque le permite instalar diferentes versiones en ubicaciones independientes y elegir explícitamente cuál
usará cada aplicación.

Pasos siguientes
Tutorial: Creación de una aplicación de consola con el SDK de .NET Core mediante Visual Studio Code
Cómo quitar los componentes .NET Core Runtime y
SDK
16/09/2020 • 12 minutes to read • Edit Online

Con el tiempo, a medida que instale versiones actualizadas del runtime y el SDK de .NET Core, es posible que
quiera quitar del equipo versiones obsoletas de .NET Core. Al quitar versiones anteriores de runtime puede
cambiar el runtime elegido para ejecutar aplicaciones de marco compartidas, tal como se detalla en el artículo
sobre selección de la versión de .NET Core.

¿Puedo quitar una versión?


Los comportamientos de la selección de la versión de .NET Core y la compatibilidad de runtime de .NET Core en
diferentes actualizaciones permite quitar de forma segura las versiones anteriores. Las actualizaciones de .NET
Core Runtime son compatibles con una "banda" de versión principal, como 1.x y 2.x. Además, normalmente las
versiones más recientes del SDK de .NET Core mantienen la capacidad de crear aplicaciones destinadas a versiones
anteriores del tiempo de ejecución de una forma compatible.
En general, solo se necesita el SDK más reciente y la última versión de revisión de los runtimes necesarios para la
aplicación. Los casos en los que interesa conservar versiones anteriores del SDK o del runtime incluyen el
mantenimiento de aplicaciones basadas en project.json. A menos que la aplicación tenga motivos concretos para
utilizar SDK o runtimes anteriores, puede quitar las versiones anteriores.

Determinación de lo instalado
A partir de .NET Core 2.1, la CLI de .NET tiene opciones que puede utilizar para enumerar las versiones del SDK y
de runtime que están instaladas en el equipo. Use dotnet --list-sdks para ver la lista de los SDK instalados en el
equipo. Use dotnet --list-runtimes para ver la lista de los runtimes instalados en el equipo. Para más
información, consulte Cómo comprobar que .NET Core ya está instalado.

Desinstalación de .NET Core


.NET Core usa el cuadro de diálogo Aplicaciones y características de Windows para quitar las versiones del
runtime de .NET Core y del SDK. En la siguiente ilustración se muestra el cuadro de diálogo Aplicaciones y
características . Puede buscar core sdk para filtrar y mostrar las versiones instaladas de .NET Core.
Seleccione las versiones que quiera quitar de su equipo y haga clic en Desinstalar .
Para desinstalar .NET Core (SDK o runtime) en Linux tiene más opciones. La mejor forma de desinstalar .NET Core
es repetir la misma acción que se usó para instalarlo. Los detalles específicos dependerán de la distribución que
elija y del método de instalación.

IMPORTANT
Para obtener información sobre las instalaciones y desinstalaciones de .NET Core en Red Hat, consulte la Guía de introducción
a Red Hat.

A partir de .NET Core 2.1, no hace falta desinstalar el SDK de .NET Core al actualizarlo mediante un administrador
de paquetes. Los comandos update o refresh del administrador de paquetes quitarán automáticamente la
versión anterior tras la instalación correcta de una versión más reciente.
Si ha instalado .NET Core con un administrador de paquetes, use ese mismo administrador de paquetes para
desinstalar el SDK o el runtime de .NET. Las instalaciones de .NET Core admiten los administradores de paquetes
más populares. Consulte la documentación del administrador de paquetes de su distribución para conocer la
sintaxis exacta en su entorno:
apt-get(8) se utiliza en sistemas basados en Debian, incluido Ubuntu.
yum(8) se utiliza en Fedora, CentOS y Oracle Linux.
zypper(8) se utiliza en openSUSE y SUSE Linux Enterprise Server (SLES).
DNF(8) se utiliza en Fedora.
En casi todos los casos, el comando para quitar un paquete es remove .
El nombre del paquete para la instalación del SDK de .NET Core para la mayoría de administradores de paquetes es
dotnet-sdk , seguido por el número de versión. A partir de la versión 2.1.300 del SDK de .NET Core y de la versión
2.1 del runtime, solo se requieren el primer y el segundo número de versión: por ejemplo, puede hacer referencia
a la versión 2.1.300 del SDK de .NET Core como el paquete dotnet-sdk-2.1 . Para las versiones anteriores se
requiere la cadena de versión completa: por ejemplo, se requeriría dotnet-sdk-2.1.200 para la versión 2.1.200 del
SDK de .NET Core.
En los equipos en los que solo se ha instalado el runtime, y no el SDK, el nombre del paquete es
dotnet-runtime-<version> para .NET Core Runtime y aspnetcore-runtime-<version> para la pila de runtime entera.

Al instalar las versiones de .NET Core anteriores a 2.0 no se desinstaló la aplicación host al desinstalar el SDK
mediante el administrador de paquetes. Al usar apt-get , el comando es:

apt-get remove dotnet-host

Tenga en cuenta que no hay ninguna versión conectada a dotnet-host .


Si ha realizado la instalación mediante un paquete tarball, debe quitar .NET Core mediante el método manual.
En Linux, debe quitar los SDK y runtimes por separado, quitando los directorios con versiones. De esta manera, se
elimina el SDK y el runtime del disco. Por ejemplo, para quitar el runtime y el SDK 1.0.1, tendrá que usar los
siguientes comandos de bash:

version="1.0.1"
sudo rm -rf /usr/local/share/dotnet/sdk/$version
sudo rm -rf /usr/local/share/dotnet/shared/Microsoft.NETCore.App/$version
sudo rm -rf /usr/local/share/dotnet/shared/Microsoft.AspNetCore.All/$version
sudo rm -rf /usr/local/share/dotnet/shared/Microsoft.AspNetCore.App/$version
sudo rm -rf /usr/local/share/dotnet/host/fxr/$version

Los directorios primarios para el SDK y runtime se enumeran en el resultado de los comandos dotnet --list-sdks
y dotnet --list-runtimes , como se muestra en la tabla anterior.
En Mac, debe quitar los SDK y runtimes por separado, quitando los directorios con versiones. De esta manera, se
elimina el SDK y el runtime del disco. Por ejemplo, para quitar el runtime y el SDK 1.0.1, tendrá que usar los
siguientes comandos de bash:

version="1.0.1"
sudo rm -rf /usr/local/share/dotnet/sdk/$version
sudo rm -rf /usr/local/share/dotnet/shared/Microsoft.NETCore.App/$version
sudo rm -rf /usr/local/share/dotnet/shared/Microsoft.AspNetCore.All/$version
sudo rm -rf /usr/local/share/dotnet/shared/Microsoft.AspNetCore.App/$version
sudo rm -rf /usr/local/share/dotnet/host/fxr/$version

Los directorios primarios para el SDK y runtime se enumeran en el resultado de los comandos dotnet --list-sdks
y dotnet --list-runtimes , como se muestra en la tabla anterior.

Herramienta de desinstalación de .NET Core


La herramienta de desinstalación de .NET Core ( dotnet-core-uninstall ) permite quitar los SDK y los entornos de
ejecución de .NET Core de un sistema. Hay una colección de opciones disponible para especificar las versiones que
se deben desinstalar.

Dependencia de Visual Studio en versiones de SDK de .NET Core


Antes de la versión 16.3 de Visual Studio 2019, los instaladores de Visual Studio llamaron al instalador de SDK de
.NET Core independiente. Como resultado, las versiones del SDK aparecen en el cuadro de diálogo Aplicaciones y
características de Windows. La eliminación de los SDK de .NET Core que se instalaron con Visual Studio mediante
el instalador independiente podría interrumpir Visual Studio. Si Visual Studio tiene problemas después de
desinstalar los SDK, ejecute reparar en esa versión específica de Visual Studio. En la tabla siguiente se muestran
algunas de las dependencias de Visual Studio en las versiones de SDK de .NET Core:

VERSIÓ N DE VISUA L ST UDIO VERSIÓ N DEL SDK DE . N ET C O RE

Visual Studio 2019, versión 16.2 SDK de .NET Core 2.2.4xx, 2.1.8xx

Visual Studio 2019, versión 16.1 SDK de .NET Core 2.2.3xx, 2.1.7xx

Visual Studio 2019, versión 16.0 SDK de .NET Core 2.2.2xx, 2.1.6xx

Visual Studio 2017, versión 15.9 SDK de .NET Core 2.2.1xx, 2.1.5xx

Visual Studio 2017, versión 15.8 SDK de .NET Core 2.1.4xx

A partir de la versión 16.3 de Visual Studio 2019, Visual Studio se encarga de su propia copia de la SDK de
.NET Core. Por ese motivo, ya no verá las versiones del SDK en el cuadro de diálogo Aplicaciones y
características .

Eliminación de la carpeta de reserva de NuGet


Antes del SDK de .NET Core 3.0, los instaladores del SDK de .NET Core usaban una carpeta denominada
NuGetFallbackFolder para almacenar una memoria caché de paquetes NuGet. Esta memoria caché se utilizó
durante operaciones como dotnet restore o dotnet build /t:Restore . La carpeta NuGetFallbackFolder se
encuentra en C:\Archivos de programa\dotnet\sdk en Windows y en /usr/local/share/dotnet/sdk en macOS.
Es posible que desee quitar esta carpeta si:
Solo está desarrollando con el SDK de .NET Core 3.0 o versiones posteriores.
Está desarrollando con versiones del SDK de .NET Core anteriores a la 3.0, pero puede trabajar en línea.
Si desea quitar la carpeta de reserva de NuGet, puede eliminarla, pero necesitará privilegios de administrador para
hacerlo.
No se recomienda eliminar la carpeta dotnet. Si lo hace, se quitarán las herramientas globales que haya instalado
previamente. Además, en Windows:
Interrumpirá Visual Studio 2019 versión 16.3 y versiones posteriores. Puede ejecutar Reparar para recuperarlo.
Si hay entradas del SDK de .NET Core en el cuadro de diálogo Aplicaciones y características , se quedarán
huérfanas.
Administración de plantillas de proyectos y elementos
de .NET
16/09/2020 • 8 minutes to read • Edit Online

.NET Core proporciona un sistema de plantillas que permite a los usuarios instalar o desinstalar plantillas de NuGet,
un archivo de paquete NuGet o un directorio del sistema de archivos. En este artículo se describe cómo administrar
plantillas de .NET Core a través de la CLI del SDK de .NET Core.
Para obtener más información sobre la creación de plantillas, consulte Tutorial: Creación de plantillas.

Instalación de plantillas
Las plantillas se instalan mediante el comando dotnet new del SDK con el parámetro -i . Puede proporcionar el
identificador de paquete NuGet de una plantilla o una carpeta que contenga los archivos de plantilla.
Paquete hospedado en NuGet
Las plantillas de la CLI de .NET se cargan en NuGet para una distribución amplia. Las plantillas también se pueden
instalar desde una fuente privada. En lugar de cargar una plantilla en una fuente de NuGet, los archivos de plantilla
de nupkg se pueden distribuir e instalar manualmente, tal como se describe en la sección Paquete NuGet local.
Para obtener más información sobre cómo configurar las fuentes de NuGet, vea dotnet nuget add source.
Para instalar un paquete de plantillas desde la fuente de NuGet predeterminada, use el comando
dotnet new -i {package-id} :

dotnet new -i Microsoft.DotNet.Web.Spa.ProjectTemplates

Para instalar un paquete de plantillas desde la fuente de NuGet predeterminada con una versión específica, use el
comando dotnet new -i {package-id}::{version} :

dotnet new -i Microsoft.DotNet.Web.Spa.ProjectTemplates::2.2.6

Paquete NuGet local


Cuando se crea un paquete de plantillas, se genera un archivo nupkg. Si tiene un archivo nupkg que contiene
plantillas, puede instalarlo con el comando dotnet new -i {path-to-package} :

dotnet new -i c:\code\nuget-packages\Some.Templates.1.0.0.nupkg

dotnet new -i ~/code/nuget-packages/Some.Templates.1.0.0.nupkg

Carpeta
Como alternativa a la instalación de una plantilla desde un archivo nupkg, también puede instalar plantillas desde
una carpeta directamente con el comando dotnet new -i {folder-path} . La carpeta especificada se trata como el
identificador del paquete de plantillas para cualquier plantilla que se encuentre. Se instalarán todas las plantillas
que estén en la jerarquía de la carpeta especificada.
dotnet new -i c:\code\nuget-packages\some-folder\

dotnet new -i ~/code/nuget-packages/some-folder/

La ruta {folder-path} especificada en el comando se convierte en el identificador del paquete de plantillas para
todas las plantillas que se encuentren. Tal como se especifica en la sección Enumeración de las plantillas, puede
obtener una lista de las plantillas instaladas con el comando dotnet new -u . En este ejemplo, el identificador del
paquete de plantillas se muestra como la carpeta que se ha usado para la instalación:

dotnet new -u
Template Instantiation Commands for .NET Core CLI

Currently installed items:

... cut to save space ...

c:\code\nuget-packages\some-folder
Templates:
A Template Console Class (templateconsole) C#
Project for some technology (contosoproject) C#
Uninstall Command:
dotnet new -u c:\code\nuget-packages\some-folder

dotnet new -u
Template Instantiation Commands for .NET Core CLI

Currently installed items:

... cut to save space ...

/home/username/code/templates
Templates:
A Template Console Class (templateconsole) C#
Project for some technology (contosoproject) C#
Uninstall Command:
dotnet new -u /home/username/code/templates

Desinstalación de plantillas
Las plantillas se desinstalan mediante el comando dotnet new del SDK con el parámetro -u . Puede proporcionar el
identificador de paquete NuGet de una plantilla o una carpeta que contenga los archivos de plantilla.
Detección de
Una vez instalado el paquete de plantillas de NuGet, ya sea desde una fuente de NuGet o un archivo nupkg, puede
desinstalarlo mediante una referencia al identificador de paquete NuGet.
Para desinstalar un paquete de plantillas, use el comando dotnet new -u {package-id} :

dotnet new -u Microsoft.DotNet.Web.Spa.ProjectTemplates

Carpeta
Cuando las plantillas se instalan a través de una ruta de acceso de carpeta, dicha ruta se convierte en el
identificador del paquete de plantillas.
Para desinstalar un paquete de plantillas, use el comando dotnet new -u {package-folder-path} :
dotnet new -u c:\code\nuget-packages\some-folder

dotnet new -u /home/username/code/templates

Enumeración de plantillas
Al usar el comando de desinstalación estándar sin un identificador de paquete, puede ver una lista de las plantillas
instaladas, junto con el comando que desinstala cada plantilla.

dotnet new -u
Template Instantiation Commands for .NET Core CLI

Currently installed items:

... cut to save space ...

c:\code\nuget-packages\some-folder
Templates:
A Template Console Class (templateconsole) C#
Project for some technology (contosoproject) C#
Uninstall Command:
dotnet new -u c:\code\nuget-packages\some-folder

Instalación de plantillas desde otros SDK


Si ha instalado cada versión del SDK secuencialmente (por ejemplo, si ha instalado el SDK 2.0, después el SDK 2.1,
etc.), tendrá instaladas todas las plantillas del SDK. Pero si comienza con una versión posterior del SDK, como la 3.1,
solo se incluyen las plantillas para las versiones LTS (compatibilidad a largo plazo), que en el momento de la
versión 3.1 del SDK son SDK 2.1 y el SDK 3.1. No se incluyen las plantillas para ninguna otra versión.
Las plantillas de .NET Core están disponibles en NuGet y se pueden instalar como cualquier otra plantilla. Para
obtener más información, consulte Instalación del paquete hospedado en NuGet.

SDK IDEN T IF IC A DO R DEL PA Q UET E N UGET

.NET Core 2.1 Microsoft.DotNet.Common.ProjectTemplates.2.1

.NET Core 2.2 Microsoft.DotNet.Common.ProjectTemplates.2.2

.NET Core 3.0 Microsoft.DotNet.Common.ProjectTemplates.3.0

.NET Core 3.1 Microsoft.DotNet.Common.ProjectTemplates.3.1

ASP.NET Core 2.1 Microsoft.DotNet.Web.ProjectTemplates.2.1

ASP.NET Core 2.2 Microsoft.DotNet.Web.ProjectTemplates.2.2

ASP.NET Core 3.0 Microsoft.DotNet.Web.ProjectTemplates.3.0

ASP.NET Core 3.1 Microsoft.DotNet.Web.ProjectTemplates.3.1

Por ejemplo, el SDK de .NET Core incluye plantillas para una aplicación de consola que tiene como destino .NET
Core 2.1 y .NET Core 3.1. Si quiere que el destino sea .NET Core 3.0, deberá instalar las plantillas de 3.0.
1. Pruebe a crear una aplicación destinada a .NET Core 3.0.

dotnet new console --framework netcoreapp3.0

Si ve un mensaje de error, debe instalar las plantillas.

No se pudo encontrar una plantilla instalada que coincida con la entrada. Buscando en línea una que
coincida…

2. Instale las plantillas de proyecto de .NET Core 3.0.

dotnet new -i Microsoft.DotNet.Common.ProjectTemplates.3.0

3. Pruebe a crear la aplicación otra vez.

dotnet new console --framework netcoreapp3.0

Debería ver un mensaje que indica que se ha creado el proyecto.

La plantilla "Console Application" se creó correctamente.


Procesando acciones posteriores a la creación… Ejecutando "dotnet restore" en path-to-project-
file.csproj… Determinando los proyectos que se van a restaurar… Restauración completada en 1,05 s
para path-to-project-file.csproj.
Restauración correcta.

Vea también
Tutorial: Creación de una plantilla de elemento.
dotnet new
dotnet nuget add source
Certificación de macOS Catalina y el impacto en las
descargas y proyectos de .NET Core
16/09/2020 • 9 minutes to read • Edit Online

A partir de macOS Catalina (versión 10.15), se debe conceder la certificación a todo el software creado después
del 1 de junio de 2019 y que se distribuya con el identificador de desarrollador. Este requisito se aplica al runtime
de .NET Core, al SDK de .NET Core y al software creado con .NET Core. En este artículo se describen los escenarios
comunes con los que puede encontrarse con la certificación de .NET Core y macOS.

Instalación de .NET Core


Desde el 18 de febrero de 2020, se ha concedido la certificación a los instaladores de las versiones 3.1, 3.0 y 2.1
de .NET Core (tanto el runtime como el SDK). A las versiones publicadas anteriores no se les ha concedido la
certificación. Puede instalar manualmente una versión de .NET Core sin certificación si descarga primero el
instalador y, después, usa el comando sudo installer . Para obtener más información, vea Descarga e instalación
manual de macOS.
A partir de las versiones siguientes, los instaladores de .NET Core están certificados:
Runtime de .NET Core
2.1.16
3.0.3
3.1.2
SDK de .NET Core
2.1.512
3.0.103
3.1.102

appHost está deshabilitado de manera predeterminada


De forma predeterminada, las versiones sin certificación del SDK de .NET Core 3.0 y posteriores generan un
ejecutable Mach-O nativo (conocido como appHost ) cuando el proyecto se compila, se publica o se ejecuta. Este
ejecutable es una forma cómoda de ejecutar la aplicación. De lo contrario, la aplicación se debe iniciar mediante la
ejecución de dotnet <filename.dll> . Cuando appHost está habilitado, se invoca el comando dotnet run en el
contexto de appHost. Para obtener más información, vea Contexto de appHost.
A partir de las versiones con certificación del SDK de .NET Core 3.0 y posteriores, el archivo ejecutable appHost
no se genera de forma predeterminada. Puede activar la generación de appHost con el valor booleano
UseAppHost en el archivo del proyecto. También puede alternar appHost con el parámetro -p:UseAppHost en la
línea de comandos para el comando dotnet específico que ejecute:
Archivo del proyecto

<PropertyGroup>
<UseAppHost>true</UseAppHost>
</PropertyGroup>

Parámetro de línea de comandos


dotnet run -p:UseAppHost=true

Siempre se crea un instancia de appHost al publicar la aplicación de manera independiente.


Para obtener más información sobre la configuración de UseAppHost , vea Propiedades de MSBuild para
Microsoft.NET.Sdk.
Contexto de appHost
Cuando appHost está habilitado en el proyecto y se usa el comando dotnet run para ejecutar la aplicación, la
aplicación se invoca en el contexto de appHost y no en el host predeterminado (que es el comando dotnet ). Si
appHost está deshabilitado en el proyecto, el comando dotnet run ejecuta la aplicación en el contexto del host
predeterminado. Incluso si appHost está deshabilitado, la publicación de la aplicación como independiente genera
un ejecutable appHost que los usuarios utilizan para ejecutar la aplicación. La ejecución de la aplicación con
dotnet <filename.dll> invoca la aplicación con el host predeterminado, el entorno de ejecución compartido.

Cuando se invoca una aplicación que usa appHost, la partición de certificado a la que accede la aplicación es
diferente del host predeterminado certificado. Si la aplicación tiene que acceder a los certificados instalados a
través del host predeterminado, use el comando dotnet run para ejecutarla desde su archivo del proyecto, o bien
use el comando dotnet <filename.dll> para iniciar la aplicación directamente.
En la sección ASP.NET Core y macOS y los certificados se proporciona más información sobre este escenario.

ASP.NET Core y macOS y los certificados


.NET Core proporciona la capacidad de administrar certificados en la cadena de claves de macOS con la clase
System.Security.Cryptography.X509Certificates. El acceso a la cadena de claves de macOS usa la identidad de las
aplicaciones como clave principal al decidir qué partición se debe tener en cuenta. Por ejemplo, las aplicaciones
sin firmar almacenan secretos en la partición sin firmar, pero las aplicaciones con firma almacenan sus secretos
en particiones a las que solo ellas pueden acceder. El origen de ejecución que invoca la aplicación decide qué
partición se va a usar.
.NET Core proporciona tres orígenes de ejecución: appHost, host predeterminado (el comando dotnet ) y un host
personalizado. Cada modelo de ejecución puede tener identidades diferentes, con firma o sin firma, y tiene acceso
a diferentes particiones dentro de la cadena de claves. Es posible que los certificados importados por un modo no
sean accesibles desde otro. Por ejemplo, las versiones certificadas de .NET Core tienen un host predeterminado
que está firmado. Los certificados se importan en una partición segura en función de su identidad. No se puede
acceder a estos certificados desde un archivo appHost generado, ya que appHost no está firmado.
Otro ejemplo, de forma predeterminada, ASP.NET Core importa un certificado SSL predeterminado a través del
host predeterminado. Las aplicaciones ASP.NET Core que usan appHost no tendrán acceso a este certificado y
recibirán un error cuando .NET Core detecte que el certificado no es accesible. El mensaje de error proporciona
instrucciones sobre cómo corregir este problema.
Si se requiere el uso compartido de certificados, macOS proporciona opciones de configuración con la utilidad
security .

Para obtener más información sobre cómo solucionar problemas de certificados de ASP.NET Core, vea Aplicación
de HTTPS en ASP.NET Core.

Derechos predeterminados
El host predeterminado de .NET Core (el comando dotnet ) tiene un conjunto de derechos predeterminados.
Estos derechos son necesarios para el funcionamiento correcto de .NET Core. Es posible que la aplicación necesite
derechos adicionales, en cuyo caso tendrá que generar y usar un archivo appHost y, después, agregar los
derechos necesarios de forma local.
Conjunto predeterminado de derechos para .NET Core:
com.apple.security.cs.allow-jit
com.apple.security.cs.allow-unsigned-executable-memory
com.apple.security.cs.allow-dyld-environment-variables
com.apple.security.cs.disable-library-validation

Certificación de una aplicación de .NET Core


Si quiere que la aplicación se ejecute en macOS Catalina (versión 10.15) o superior, le interesará certificarla. El
archivo appHost que envíe con la aplicación para la certificación se debe usar con al menos los mismos derechos
predeterminados para .NET Core.

Pasos siguientes
Dependencias y requisitos de .NET Core.
Instalación del runtime y SDK de .NET Core.
Cómo comprobar que .NET Core ya está instalado
16/09/2020 • 4 minutes to read • Edit Online

En este artículo se explica cómo comprobar las versiones del entorno de ejecución y el SDK de .NET Core que
están instaladas en el equipo. Es posible que .NET Core ya se haya instalado si tiene un entorno de desarrollo
integrado, como Visual Studio o Visual Studio para Mac.
Al instalar un SDK, se instala el entorno de ejecución correspondiente.
Si se produce un error en alguno de los comandos de este artículo, no tendrá instalado el entorno de
ejecución o el SDK. Para obtener más información, consulte los artículos de instalación para Windows,
macOS o Linux.

Comprobación de las versiones del SDK


Se pueden ver las versiones del SDK de .NET Core que están instaladas actualmente con un terminal. Abra un
terminal y ejecute el comando siguiente.

dotnet --list-sdks

Verá un resultado similar al siguiente.

2.1.500 [C:\program files\dotnet\sdk]


2.1.502 [C:\program files\dotnet\sdk]
2.1.504 [C:\program files\dotnet\sdk]
2.1.600 [C:\program files\dotnet\sdk]
2.1.602 [C:\program files\dotnet\sdk]
2.2.101 [C:\program files\dotnet\sdk]
3.0.100 [C:\program files\dotnet\sdk]
3.1.100 [C:\program files\dotnet\sdk]

2.1.500 [/home/user/dotnet/sdk]
2.1.502 [/home/user/dotnet/sdk]
2.1.504 [/home/user/dotnet/sdk]
2.1.600 [/home/user/dotnet/sdk]
2.1.602 [/home/user/dotnet/sdk]
2.2.101 [/home/user/dotnet/sdk]
3.0.100 [/home/user/dotnet/sdk]
3.1.100 [/home/user/dotnet/sdk]

2.1.500 [/usr/local/share/dotnet/sdk]
2.1.502 [/usr/local/share/dotnet/sdk]
2.1.504 [/usr/local/share/dotnet/sdk]
2.1.600 [/usr/local/share/dotnet/sdk]
2.1.602 [/usr/local/share/dotnet/sdk]
2.2.101 [/usr/local/share/dotnet/sdk]
3.0.100 [/usr/local/share/dotnet/sdk]
3.1.100 [/usr/local/share/dotnet/sdk]

Comprobación de las versiones del entorno de ejecución


Se pueden ver las versiones del entorno de ejecución de .NET Core que están instaladas actualmente con el
comando siguiente.

dotnet --list-runtimes

Verá un resultado similar al siguiente.

Microsoft.AspNetCore.All 2.1.7 [c:\program files\dotnet\shared\Microsoft.AspNetCore.All]


Microsoft.AspNetCore.All 2.1.13 [c:\program files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.0 [c:\program files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.3 [c:\program files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.7 [c:\program files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.6 [c:\program files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.7 [c:\program files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.13 [c:\program files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.0 [c:\program files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.7 [c:\program files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.0.0 [c:\program files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.0 [c:\program files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.7 [c:\program files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.13 [c:\program files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.0 [c:\program files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.3 [c:\program files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.7 [c:\program files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0 [c:\program files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.0 [c:\program files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.0.0 [c:\program files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.0 [c:\program files\dotnet\shared\Microsoft.WindowsDesktop.App]

Microsoft.AspNetCore.All 2.1.7 [/home/user/dotnet/shared/Microsoft.AspNetCore.All]


Microsoft.AspNetCore.All 2.1.13 [/home/user/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.0 [/home/user/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.3 [/home/user/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.7 [/home/user/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.6 [/home/user/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.7 [/home/user/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.13 [/home/user/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.0 [/home/user/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.7 [/home/user/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.0.0 [/home/user/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.0 [/home/user/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.7 [/home/user/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.13 [/home/user/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.0 [/home/user/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.3 [/home/user/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.7 [/home/user/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0 [/home/user/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.0 [/home/user/dotnet/shared/Microsoft.NETCore.App]
Microsoft.AspNetCore.All 2.1.7 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.13 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.7 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.6 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.7 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.13 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.7 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.7 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.13 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.7 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

Buscar carpetas de instalación


Es posible que .NET Core esté instalado pero no se haya agregado a la variable PATH del sistema operativo o
el perfil de usuario. Es posible que no funcione la ejecución de los comandos de las secciones anteriores.
Como alternativa, puede comprobar que existen las carpetas de instalación de .NET Core.
Al instalar .NET Core desde un instalador o un script, se instala en una carpeta estándar. La mayor parte del
tiempo, el instalador o el script que usa para instalar .NET Core le ofrece la opción de instalar en una carpeta
diferente. Si decide instalar en una carpeta diferente, ajuste el inicio de la ruta de acceso de la carpeta.
archivo ejecutable de dotnet
C:\archivos de programa\dotnet\dotnet.exe
.NET SDK
C:\archivos de programa\dotnet\sdk\{versión}\
Runtime de .NET
C:\archivos de programa\dotnet\shared\{tipo de runtime}\{versión}\
archivo ejecutable de dotnet
/home/user/share/dotnet/dotnet
.NET SDK
/home/user/share/dotnet/sdk/{versión}/
Runtime de .NET
/home/user/share/dotnet/shared/{tipo de runtime}/{versión}/
archivo ejecutable de dotnet
/usr/local/share/dotnet/dotnet
.NET SDK
/usr/local/share/dotnet/sdk/{versión}/
Runtime de .NET
/usr/local/share/dotnet/shared/{tipo de runtime}/{versión}/

Más información
Se pueden ver las versiones del SDK y del entorno de ejecución con el comando dotnet --info . También
obtendrá otra información relacionada con el entorno, como la versión del sistema operativo y el
identificador del entorno de ejecución (RID).

Pasos siguientes
Instalación del runtime y SDK de .NET Core.
Procedimiento para instalar archivos de IntelliSense
localizados para .NET Core
18/03/2020 • 7 minutes to read • Edit Online

IntelliSense es una característica que ayuda a completar código y que está disponible en diferentes entornos de
desarrollo integrado (IDE), como Visual Studio. De manera predeterminada, al desarrollar proyectos de .NET Core,
el SDK solo incluye la versión en inglés de los archivos de IntelliSense. En este artículo, se explica lo siguiente:
Procedimiento para instalar la versión localizada de estos archivos.
Procedimiento para modificar la instalación de Visual Studio para usar otro idioma.

Requisitos previos
SDK de .NET Core 3.0 o una versión posterior.
Versión 16.3 de Visual Studio 2019 u otra posterior.

Descarga e instalación de los archivos de IntelliSense localizados


IMPORTANT
Para poder realizar este procedimiento, necesita tener permiso de administrador para copiar los archivos de IntelliSense en la
carpeta de instalación de .NET Core.

1. Vaya a la página Descarga de archivos de IntelliSense.


2. Descargue el archivo de IntelliSense en el idioma y la versión que prefiera.
3. Extraiga el contenido del archivo ZIP.
4. Vaya a la carpeta Intellisense de .NET Core.
a. Vaya a la carpeta de instalación de .NET Core. De manera predeterminada, se encuentra en %Archivos
de programa%\dotnet\packs.
b. Elija para qué SDK quiere instalar el archivo de IntelliSense y vaya a la ruta de acceso correspondiente.
Dispone de las siguientes opciones:

T IP O DE SDK PAT H

.NET Core Microsoft.NETCore.App.Ref

Escritorio de Windows Microsoft.WindowsDesktop.App.Ref

.NET Standard NETStandard.Library.Ref

c. Vaya a la versión para la que quiera instalar el archivo de IntelliSense localizado. Por ejemplo, la 3.1.0.
d. Abra la carpeta ref.
e. Abra la carpeta del moniker. Por ejemplo, netcoreapp3.1.
Así pues, la ruta de acceso completa tendrá un aspecto similar al siguiente: C:\Archivos de
programa\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1.
5. Cree una subcarpeta en la carpeta del moniker que acaba de abrir. El nombre de la carpeta indicará el idioma
que quiere usar. En la siguiente tabla, se especifican las diferentes opciones:

L EN GUA JE N O M B RE DE C A RP ETA

Portugués (Brasil) pt-br

Chino simplificado zh-hans

Chino tradicional zh-hant

Francés fr

Alemán de

Italiano it

Japonés ja

Coreano ko

Ruso ru

Español es

6. Copie en esta nueva carpeta los archivos .xml que ha extraído en el paso 3. Los archivos .xml se mostrarán
agrupados según las diferentes carpetas de SDK, de modo que debe copiarlos en la del SDK que haya
elegido en el paso 4.

Modificación del idioma de Visual Studio


Para que Visual Studio use otro idioma para IntelliSense, instale el paquete de idioma correspondiente. Esto se
puede realizar durante la instalación o después modificando la instalación de Visual Studio. Si ya tiene Visual Studio
configurado en el idioma que quiere, su instalación de IntelliSense está lista.
Instalación del paquete de idioma
Si no ha instalado el paquete de idioma deseado durante la configuración, actualice Visual Studio como se indica a
continuación para instalar el paquete de idioma:

IMPORTANT
Para instalar, actualizar o modificar Visual Studio, debe iniciar sesión con una cuenta que tenga permisos de administrador.
Para obtener más información, consulte Permisos de usuario y Visual Studio.

1. Busque el instalador de Visual Studio en su equipo.


Por ejemplo, en un equipo que ejecuta Windows 10, seleccione Iniciar y, después, desplácese hasta la letra I
donde lo verá como Instalador de Visual Studio .
NOTE
También pude encontrar el instalador de Visual Studio en la siguiente ubicación:
C:\Program Files (x86)\Microsoft Visual Studio\Installer\vs_installer.exe

Es posible que tenga que actualizar el instalador antes de continuar. De ser así, siga las indicaciones.
2. En el instalador, busque la edición de Visual Studio a la que quiera agregar el paquete de idioma y, luego,
elija Modificar .

IMPORTANT
Si no ve el botón Modificar , pero sí el botón Actualizar , significa que necesita actualizar su versión de Visual Studio
para poder modificar su instalación. Cuando haya finalizado la actualización, aparecerá el botón Modificar .

3. En la pestaña Paquetes de idioma , seleccione o anule la selección de los idiomas que quiera instalar o
desinstalar.

4. Elija Modificar . Se inicia la actualización.


Modificación de la configuración de idioma en Visual Studio
Una vez que haya instalado los paquete de idioma deseados, modifique la configuración de Visual Studio para usar
otro idioma:
1. Abra Visual Studio.
2. En la ventana de inicio, elija Continuar sin código .
3. En la barra de menús, seleccione Herramientas > Opciones . Se abrirá el cuadro de diálogo Opciones.
4. En el nodo Entorno , seleccione Configuración internacional .
5. En la lista desplegable Idioma , seleccione el que quiera usar. Elija Aceptar .
6. Aparecerá un cuadro de diálogo en el que se le informará de que debe reiniciar Visual Studio para que se
apliquen los cambios. Elija Aceptar .
7. Reinicie Visual Studio.
Después de esto, IntelliSense deberá funcionar según lo esperado al abrir un proyecto de .NET Core que tenga
como destino la versión de los archivos de IntelliSense que acaba de instalar.

Vea también
IntelliSense en Visual Studio
Paseo por .NET
18/03/2020 • 18 minutes to read • Edit Online

.NET es una plataforma de desarrollo de uso general. Tiene varias características clave, como la compatibilidad con
varios lenguajes de programación, modelos de programación asincrónica y simultánea e interoperabilidad nativa,
que permiten una amplia variedad de escenarios en diversas plataformas.
En este artículo, se ofrece un paseo guiado por algunas de las características clave de .NET. Consulte el tema
Componentes de la arquitectura .NET para obtener más información sobre las piezas de arquitectura de .NET y para
qué se usan.

Ejecución de ejemplos de código


Para obtener más información sobre cómo configurar un entorno de desarrollo para ejecutar los ejemplos de
código, consulte el tema Introducción. Copie y pegue los ejemplos de código de esta página en su entorno para
ejecutarlos.

Lenguajes de programación
.NET admite varios lenguajes de programación. Las implementaciones de .NET implementan Common Language
Infrastructure (CLI), que, entre otras cosas, especifica un entorno de ejecución independiente del lenguaje y la
interoperabilidad del lenguaje. Esto significa que elige cualquier lenguaje .NET para crear aplicaciones y servicios en
.NET.
Microsoft desarrolla activamente y admite tres lenguajes .NET: C#, F# y Visual Basic.
C# es simple, eficaz, incluye seguridad de tipos y está orientado a objetos, al mismo tiempo que mantiene la
expresividad y elegancia de los lenguajes de estilo C. Cualquiera que esté familiarizado con C y lenguajes
similares, encuentra pocos problemas para adaptarse a C#. Consulte la Guía de C# para más información
sobre C#.
F# es un lenguaje de programación multiplataforma, principalmente funcional, que también admite la
programación tradicional imperativa y orientada en objetos. Consulte la Guía de F# para más información
sobre F#.
Visual Basic es un lenguaje fácil de aprender que se usa para crear una gran variedad de aplicaciones que se
ejecutan en .NET. Entré los lenguajes .NET, la sintaxis de Visual Basic es la más cercana al lenguaje humano
normal, lo que a menudo facilita el trabajo a las personas sin experiencia en el desarrollo de software.

Administración de memoria automática


.NET usa la recolección de elementos no utilizados para proporcionar administración automática de memoria para
los programas. La GC funciona con un enfoque diferido para la administración de memoria y prefiere el
rendimiento de la aplicación sobre la recolección inmediata de la memoria. Para más información sobre GC de .NET,
consulte Fundamentos de la recolección de elementos no utilizados (GC).
Las dos líneas siguientes asignan memoria:

var title = ".NET Primer";


var list = new List<string>();
No hay ninguna palabra clave análoga para anular la asignación de memoria, ya que la anulación de la asignación
se realiza automáticamente cuando el recolector de elementos no utilizados reclama la memoria a través de su
ejecución programada.
El recolector de elementos no utilizados es uno de los servicios que ayudan a garantizar la protección de la
memoria. Un programa tiene protección de la memoria si tiene acceso solo a la memoria asignada. Por ejemplo, el
entorno de ejecución garantiza que una aplicación no accede a memoria sin asignar más allá de los límites de una
matriz.
En el ejemplo siguiente, el entorno de ejecución devuelve una excepción IndexOutOfRangeException para activar la
protección de la memoria:

int[] numbers = new int[42];


int number = numbers[42]; // Will throw an exception (indexes are 0-based)

Trabajar con recursos no administrados


Algunos objetos hacen referencia a recursos no administrados. Los recursos no administrados son recursos que el
entorno de ejecución .NET no mantiene de forma automática. Por ejemplo, un identificador de archivo es un
recurso no administrado. Un objeto FileStream es un objeto administrado, pero hace referencia a un identificador
de archivo, que es uno no administrado. Cuando haya acabado de usar FileStream, deberá liberar el identificador de
archivo.
En .NET, los objetos que hacen referencia a recursos no administrados implementan la interfaz de IDisposable.
Cuando haya acabado de usar el objeto, deberá llamar al método Dispose() del objeto, que es el responsable de
liberar cualquier recurso no administrado. Los lenguajes .NET ofrecen una using instrucción muy útil para esos
objetos, como se muestra en el ejemplo siguiente:

using System.IO;

using (FileStream stream = GetFileStream(context))


{
// Operations on the stream
}

Cuando el bloque using se completa, el entorno de ejecución .NET llama automáticamente al método Dispose()
del objeto stream , que libera el identificador de archivo. El entorno de ejecución también sigue el mismo
procedimiento en caso de que una excepción provoque que el control abandone el bloque.
Para obtener más información, consulte los siguientes temas:
Para C#, vea el tema using (Instrucción, Referencia de C#).
En F#, consulte Administración de recursos: la palabra clave use.
Para VB, vea el tema Using (Instrucción, Visual Basic).

Seguridad de tipos
Un objeto es una instancia de un tipo específico. Las únicas operaciones permitidas para un objeto determinado
son las de su tipo. Un tipo Dog puede tener métodos Jump y WagTail , pero no un método SumTotal . Un
programa solo llama a los métodos que pertenecen a un tipo determinado. Todas las demás llamadas producirán
un error en tiempo de compilación o una excepción en tiempo de ejecución (en el caso de usar características
dinámicas u object ).
Los lenguajes .NET están orientados a objetos, con las jerarquías de clases base y derivadas. El entorno de ejecución
.NET solo permite llamadas y conversiones de objetos que se alineen con la jerarquía de objetos. Recuerde que
cada tipo definido en cualquier lenguaje .NET se deriva del tipo Object base.

Dog dog = AnimalShelter.AdoptDog(); // Returns a Dog type.


Pet pet = (Pet)dog; // Dog derives from Pet.
pet.ActCute();
Car car = (Car)dog; // Will throw - no relationship between Car and Dog.
object temp = (object)dog; // Legal - a Dog is an object.

La seguridad de tipos también se usa para ayudar a aplicar la encapsulación a través de la garantía de la fidelidad
de las palabras clave del descriptor de acceso. Las palabras clave del descriptor de acceso son artefactos que
controlan el acceso a los miembros de un tipo determinado a través de otro código. Normalmente se usan para
distintos tipos de datos dentro de un tipo, que se usan para administrar su comportamiento.

private Dog _nextDogToBeAdopted = AnimalShelter.AdoptDog()

C#, Visual Basic y F# admiten inferencia de tipos local. La inferencia de tipos significa que el compilador deduce el
tipo de la expresión en el lado izquierdo a partir de la expresión en el lado derecho. Esto no significa que la
seguridad de tipos se divida o evite. El tipo resultante tiene un tipo seguro con todo lo que ello implica. En el
ejemplo anterior, se vuelve a escribir dog para introducir la inferencia de tipos, y el resto del ejemplo no se
modifica:

var dog = AnimalShelter.AdoptDog();


var pet = (Pet)dog;
pet.ActCute();
Car car = (Car)dog; // will throw - no relationship between Car and Dog
object temp = (object)dog; // legal - a Dog is an object
car = (Car)temp; // will throw - the runtime isn't fooled
car.Accelerate() // the dog won't like this, nor will the program get this far

F# tiene incluso más funcionalidades de inferencia de tipos que la inferencia de tipos method-local encontrada en
C# y Visual Basic. Para obtener más información, consulte Inferencia de tipos.

Delegados y expresiones lambda


Un delegado se representa mediante una firma de método. Cualquier método con esa firma puede asignarse al
delegado y se ejecuta cuando se invoca el delegado.
Los delegados son como los punteros de función de C++, pero tienen seguridad de tipos. Son un tipo de método
sin conexión en el sistema de tipos de CLR. Los métodos regulares están conectados a una clase y solo se pueden
llamar a través de convenciones de llamadas estáticas o de instancias.
En .NET, los delegados se usan habitualmente en controladores de eventos, en la definición de operaciones
asincrónicas y en las expresiones lambda, que son los pilares de LINQ. Obtenga más información en el tema
Delegados y expresiones lambda.

Genéricos
Los genéricos permiten al programador introducir un parámetro de tipo al diseñar sus clases, que permite al
código de cliente (los usuarios del tipo) especificar el tipo exacto que se debe usar en lugar del parámetro de tipo.
Los genéricos se agregaron para ayudar a los programadores a implementar estructuras de datos genéricos. Antes
de su llegada, para que un tipo como List fuera genérico, habría que trabajar con elementos que fueran de tipo
object . Como consecuencia, había problemas de rendimiento y semántica, junto con posibles errores sutiles en
tiempo de ejecución. Un error en tiempo de ejecución común es cuando una estructura de datos contiene, por
ejemplo, enteros y cadenas, y se produce una excepción InvalidCastException al procesar los miembros de la lista.
En el siguiente ejemplo, se muestra una ejecución básica de programa mediante una instancia de tipos List<T>:

using System;
using System.Collections.Generic;

namespace GenericsSampleShort
{
public static void Main(string[] args)
{
// List<string> is the client way of specifying the actual type for the type parameter T
List<string> listOfStrings = new List<string> { "First", "Second", "Third" };

// listOfStrings can accept only strings, both on read and write.


listOfStrings.Add("Fourth");

// Below will throw a compile-time error, since the type parameter


// specifies this list as containing only strings.
listOfStrings.Add(1);
}
}

Para obtener más información, consulte el tema Información general (genéricos) de tipos genéricos.

Programación asincrónica
La programación asincrónica es un concepto de primera clase en .NET, con compatibilidad asincrónica en el
entorno de ejecución, las bibliotecas del marco y las construcciones de lenguaje .NET. Internamente, se basa en
objetos (como Task ) que sacan partido del sistema operativo para realizar trabajos dependientes de E/S de la
forma más eficaz posible.
Para obtener más información sobre la programación asincrónica en .NET, comience con el tema Async en
profundidad.

Language-Integrated Query (LINQ)


LINQ es un conjunto eficaz de características para C# y Visual Basic que permiten escribir código simple y
declarativo para operar en los datos. Los datos pueden estar en muchos formatos (como objetos en memoria, una
base de datos SQL o un documento XML), pero el código LINQ que escriba normalmente no es diferente según el
origen de datos.
Para obtener más información y ver algunos ejemplos, consulte el tema LINQ (Language Integrated Query).

Interoperabilidad nativa
Cada sistema operativo incluye una interfaz de programación de aplicaciones (API) que proporciona servicios del
sistema. .NET proporciona varias maneras de llamar a esas API.
La manera principal de crear interoperabilidad nativa es a través de la "invocación de plataforma" o P/Invoke para
abreviar, que se admite en las plataformas Linux y Windows. Una manera de crear interoperabilidad nativa
exclusiva de Windows se conoce como "Interoperabilidad COM", que se usa para trabajar con componentes COM
en código administrado. Se basa en la infraestructura de P/Invoke, pero funciona de forma ligeramente diferente.
La mayoría de la compatibilidad de interoperabilidad de Mono (y, por tanto, de Xamarin) para Java y Objective-C se
compila de forma similar, es decir, usan los mismos principios.
Para obtener más información sobre la interoperabilidad nativa, consulte el artículo Interoperabilidad nativa.

Código no seguro
Según la compatibilidad con el lenguaje, CLR le permite tener acceso a memoria nativa y usar la aritmética de
punteros a través de código unsafe . Estas operaciones son necesarias para determinados algoritmos y para la
interoperabilidad del sistema. Aunque es eficaz, se desaconseja el uso de código no seguro a menos que sea
necesario para la interoperabilidad con las API del sistema o para implementar el algoritmo más eficaz. Es posible
que el código no seguro no se ejecute del mismo modo en entornos diferentes y que también pierda las ventajas
de un recolector de elementos no utilizados y de la seguridad de tipos. Se recomienda limitar y centralizar el código
no seguro lo máximo posible, y probar el código a conciencia.
El ejemplo siguiente es una versión modificada del método ToString() desde la clase StringBuilder . Ilustra cómo
mediante el código unsafe se puede implementar de forma eficiente un algoritmo desplazándose por los
fragmentos de memoria directamente:

public override String ToString()


{
if (Length == 0)
return String.Empty;

string ret = string.FastAllocateString(Length);


StringBuilder chunk = this;
unsafe
{
fixed (char* destinationPtr = ret)
{
do
{
if (chunk.m_ChunkLength > 0)
{
// Copy these into local variables so that they are stable even in the presence of ----s
(hackers might do this)
char[] sourceArray = chunk.m_ChunkChars;
int chunkOffset = chunk.m_ChunkOffset;
int chunkLength = chunk.m_ChunkLength;

// Check that we will not overrun our boundaries.


if ((uint)(chunkLength + chunkOffset) <= ret.Length && (uint)chunkLength <=
(uint)sourceArray.Length)
{
fixed (char* sourcePtr = sourceArray)
string.wstrcpy(destinationPtr + chunkOffset, sourcePtr, chunkLength);
}
else
{
throw new ArgumentOutOfRangeException("chunkLength",
Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
}
chunk = chunk.m_ChunkPrevious;
} while (chunk != null);
}
}
return ret;
}

Pasos siguientes
Si está interesado en un paseo por las características de C#, consulte Paseo por C#.
Si está interesado en un paseo por las características de F#, consulte Paseo por F#.
Si quiere empezar a escribir su propio código, consulte Introducción.
Para más información sobre los principales componentes de. NET, consulte Componentes de la arquitectura .NET.
Componentes de la arquitectura .NET
16/09/2020 • 10 minutes to read • Edit Online

Una aplicación de .NET se desarrolla y se ejecuta en una o varias implementaciones de .NET. Las implementaciones
de .NET incluyen .NET Framework, .NET Core y Mono. Hay una especificación de API común a todas las
implementaciones de .NET que se denomina .NET Standard. En este artículo, se ofrece una breve introducción a
cada uno de estos conceptos.

.NET Standard
.NET Standard es un conjunto de API que se implementan mediante la biblioteca de clases base de una
implementación de .NET. Más formalmente, es una especificación de API de .NET que constituyen un conjunto
uniforme de contratos contra los que se compila el código. Estos contratos se implementan en cada
implementación de .NET. Esto permite la portabilidad entre diferentes implementaciones de .NET, de forma que el
código se puede ejecutar en cualquier parte.
.NET Standard es también una plataforma de destino. Si el código tiene como destino una versión de .NET
Standard, se puede ejecutar en cualquier implementación de .NET que sea compatible con esa versión de .NET
Standard.
Para obtener más información sobre .NET Standard y cómo tenerlo como destino, consulte .NET Standard.

Implementaciones de .NET
Cada implementación de .NET incluye los siguientes componentes:
Uno o varios entornos de ejecución. Ejemplos: CLR para .NET Framework, CoreCLR y CoreRT para .NET Core.
Una biblioteca de clases que implementa .NET Standard y puede implementar API adicionales. Ejemplos:
biblioteca de clases base de .NET Framework, biblioteca de clases base de .NET Core.
Opcionalmente, uno o varios marcos de trabajo de la aplicación. Ejemplos: ASP.NET, Windows Forms y
Windows Presentation Foundation (WPF) se incluyen en .NET Framework y .NET Core.
Opcionalmente, herramientas de desarrollo. Algunas herramientas de desarrollo se comparten entre varias
implementaciones.
Hay cuatro implementaciones principales de .NET que Microsoft desarrolla y mantiene activamente: .NET Core,
.NET Framework, Mono y UWP.
.NET Core
.NET Core es una implementación multiplataforma de .NET diseñada para controlar cargas de trabajo de servidor
y en la nube a escala. Se ejecuta en Windows, macOS y Linux. Implementa .NET Standard, de forma que cualquier
código que tenga como destino .NET Standard se puede ejecutar en .NET Core. ASP.NET Core, Windows Forms y
Windows Presentation Foundation (WPF) se ejecutan todos en .NET Core.
Para obtener más información sobre .NET Core, consulte Guía de .NET Core y Selección entre .NET Core y
.NET Framework para aplicaciones de servidor.
.NET Framework
.NET Framework es la implementación de .NET original que existe desde 2002. Las versiones 4.5 y posteriores
implementan .NET Standard, de forma que el código que tiene como destino .NET Standard se puede ejecutar en
esas versiones de .NET Framework. Contiene API específicas de Windows adicionales, como API para el desarrollo
de escritorio de Windows con Windows Forms y WPF. .NET Framework está optimizado para crear aplicaciones de
escritorio de Windows.
Para más información sobre .NET Framework, consulte la Guía de .NET Framework.
Mono
Mono es una implementación de .NET que se usa principalmente cuando se requiere un entorno de ejecución
pequeño. Es el entorno de ejecución que activa las aplicaciones Xamarin en Android, macOS, iOS, tvOS y watchOS,
y se centra principalmente en una superficie pequeña. Mono también proporciona juegos creados con el motor de
Unity.
Admite todas las versiones de .NET Standard publicadas actualmente.
Históricamente, Mono implementaba la API de .NET Framework más grande y emulaba algunas de las funciones
más populares en Unix. A veces, se usa para ejecutar aplicaciones de .NET que se basan en estas capacidades en
Unix.
Mono se suele usar con un compilador Just-In-Time, pero también incluye un compilador estático completo
(compilación Ahead Of Time) que se usa en plataformas como iOS.
Para más información sobre Mono, consulte la documentación de Mono.
Plataforma universal de Windows (UWP)
UWP es una implementación de .NET que se usa para compilar aplicaciones Windows modernas y táctiles y
software para Internet de las cosas (IoT). Se ha diseñado para unificar los diferentes tipos de dispositivos de
destino, incluidos equipos, tabletas, teléfonos e incluso la consola Xbox. UWP proporciona muchos servicios, como
una tienda de aplicaciones centralizada, un entorno de ejecución (AppContainer) y un conjunto de API de
Windows para usar en lugar de Win32 (WinRT). Pueden escribirse aplicaciones en C++, C#, Visual Basic y
JavaScript. Al usar C# y Visual Basic, .NET Core proporciona las API de .NET.
Para obtener más información sobre UWP, vea Introducción a la Plataforma universal de Windows.

Entornos de tiempo de ejecución .NET


Un entorno de ejecución es el entorno de ejecución de un programa administrado. El sistema operativo forma
parte del entorno de ejecución, pero no del entorno de ejecución .NET. Estos son algunos ejemplos de los entornos
de ejecución .NET:
Common Language Runtime (CLR) para .NET Framework
Core Common Language Runtime (CoreCLR) para .NET Core
.NET Native para la Plataforma universal de Windows
El entorno de ejecución Mono para Xamarin.iOS, Xamarin.Android, Xamarin.Mac y el marco de escritorio de
Mono

Herramientas de .NET e infraestructura común


Tiene acceso a un amplio conjunto de herramientas y componentes de infraestructura que funcionan con todas las
implementaciones de .NET. Estas herramientas y componentes incluyen:
Los lenguajes .NET y sus compiladores
El sistema de proyectos de .NET (basado en archivos .csproj, .vbproj y .fsproj)
MSBuild, el motor de compilación usado para compilar proyectos
NuGet, administrador de paquetes de Microsoft para .NET
Herramientas de organización de compilación de código abierto, como CAKE y FAKE

Estándares aplicables
El lenguaje C# y las especificaciones de Common Language Infrastructure (CLI) se normalizan a través de Ecma
International®. Las primeras ediciones de estos estándares las publicó ECMA en diciembre de 2001.
Las revisiones posteriores de los estándares las han desarrollado los grupos de tareas TC49-TG2 (C#) y TC49-TG3
(CLI) en el Comité Técnico de Lenguajes de Programación (TC49) y adoptadas por la Asamblea general de ECMA y,
posteriormente, por ISO/IEC JTC 1 a través del proceso Fast-Track de ISO.
Estándares más recientes
Los siguientes documentos oficiales de ECMA están disponibles para C# y la CLI (TR-84):
El estándar del lenguaje C# (versión 5.0) : ECMA-334.pdf
Common Language Infrastructure : disponible en los formatos pdf y zip.
Información derivada del archivo XML de la par te IV : disponible en los formatos pdf y zip.
Los documentos ISO/IEC oficiales están disponibles en la página ISO/IEC Estándares disponibles públicamente.
Estos vínculos son directos de esa página:
Tecnología de la información: lenguajes de programación, C# : ISO/IEC 23270:2018
Tecnologías de la información: Common Language Infrastructure (CLI), par tes I a VI : ISO/IEC
23271:2012
Tecnología de la información: Common Language Infrastructure (CLI); informe técnico sobre la
información derivada del archivo XML de la par te IV : ISO/IEC TR 23272:2011

Vea también
Selección entre .NET Core y .NET Framework para aplicaciones de servidor
Introducción a .NET Standard
Introducción a .NET Core
Guía de .NET Framework
Guía de C#
Guía de F#
Guía de Visual Basic
Bibliotecas de clases de .NET
16/09/2020 • 7 minutes to read • Edit Online

Las bibliotecas de clases son el concepto de biblioteca compartida de .NET. Le permiten dividir funcionalidades
útiles en módulos que pueden usar varias aplicaciones. También se pueden usar para cargar la funcionalidad no
necesaria o no conocida al inicio de la aplicación. Las bibliotecas de clases se describen mediante el formato de
archivo de Ensamblado de .NET.
Hay tres tipos de bibliotecas de clases que puede usar:
Las bibliotecas de clases específicas de la plataforma tienen acceso a todas las API de una plataforma
determinada (por ejemplo, .NET Framework, Xamarin iOS), pero solo las pueden usar las aplicaciones y
bibliotecas destinadas a esa plataforma.
Las bibliotecas de clases por tables tienen acceso a un subconjunto de API y las pueden usar las aplicaciones y
bibliotecas que tienen como destino varias plataformas.
Las bibliotecas de clases de .NET Standard son una fusión del concepto de biblioteca específica de la
plataforma y portable en un único modelo que ofrece lo mejor de ambas.

Bibliotecas de clases específicas de la plataforma


Las bibliotecas específicas de la plataforma se enlazan a una única implementación de .NET (por ejemplo, .NET
Framework en Windows) y, por tanto, pueden tomar dependencias significativas de un entorno de ejecución
conocido. Este entorno expondrá un conjunto conocido de API (API de .NET y SO) y mantendrán y expondrán el
estado esperado (por ejemplo, Registro de Windows).
Los desarrolladores que creen bibliotecas específicas de la plataforma pueden aprovechar al máximo la plataforma
subyacente. Las bibliotecas solo se ejecutarán en esa plataforma determinada, por lo que las comprobaciones de
plataforma u otras formas de código condicional son innecesarios (código de abastecimiento único de módulo para
varias plataformas).
Las bibliotecas específicas de la plataforma han sido el tipo de biblioteca de clases principal de .NET Framework.
Incluso con la aparición de otras implementaciones de .NET, las bibliotecas específicas de la plataforma continúan
siendo el tipo de biblioteca dominante.

Bibliotecas de clases portables


Las bibliotecas portables son compatibles con varias implementaciones de .NET. Pueden tomar dependencias en un
entorno de ejecución conocido; en cambio, el entorno es sintético y está generado por la intersección de un
conjunto de implementaciones concretas de .NET. Las hipótesis de plataforma y API expuestas son un subconjunto
de lo que estaría disponible para una biblioteca específica de la plataforma.
Puede elegir una configuración de plataforma al crear una biblioteca portable. La configuración de plataforma es el
conjunto de plataformas que tiene que admitir (por ejemplo, .NET Framework 4.5+, Windows Phone 8.0+). Cuantas
más plataformas decida admitir, menos API y menos hipótesis de plataforma puede hacer, el mínimo común
denominador. Esta característica puede ser confusa al principio, ya que la gente suele pensar que "más es mejor",
pero más plataformas compatibles suponen menos API disponibles.
Muchos desarrolladores de bibliotecas han pasado de producir bibliotecas específicas de varias plataformas de un
origen (con las directivas de compilación condicionales) a bibliotecas portables. Hay varios enfoques para acceder a
la funcionalidad específica de la plataforma en las bibliotecas portables con bait-and-switch como la técnica más
aceptada en este momento.
Bibliotecas de clases .NET Standard
Las bibliotecas de .NET Standard son un reemplazo de los conceptos de bibliotecas específicas de la plataforma y
portables. Son específicas de la plataforma ya que exponen toda la funcionalidad de la plataforma subyacente (sin
plataformas sintéticas ni intersecciones de plataforma). Son portables ya que funcionan en todas las plataformas
compatibles.
.NET Standard expone un conjunto de contratos de bibliotecas. Las implementaciones de .NET deben admitir cada
contrato por completo o no admitirlo. Cada implementación, por tanto, admite un conjunto de contratos de .NET
Standard. Como consecuencia, cada biblioteca de clases de .NET Standard es compatible con las plataformas que
admiten sus dependencias del contrato.
.NET Standard no expone toda la funcionalidad de .NET Framework (ni es un objetivo); en cambio, expone muchas
más API que las bibliotecas de clases portables. Se agregarán más API con el tiempo.
Las siguientes plataformas admiten las bibliotecas de .NET Standard:
.NET Core
.NET Framework
Mono
Xamarin.iOS, Xamarin.Mac, Xamarin.Android
Plataforma universal de Windows (UWP)
Windows
Windows Phone
Windows Phone Silverlight
Para más información, consulte .NET Standard.

Bibliotecas de clases de Mono


Las bibliotecas de clases se admiten en Mono, incluidos los tres tipos de bibliotecas que se han descrito
anteriormente. A menudo, Mono se considera (correctamente) como una implementación multiplataforma de .NET
Framework. En parte, se debía a que las bibliotecas de .NET Framework específicas de la plataforma podrían
ejecutarse en el tiempo de ejecución Mono sin modificarse ni volver a compilarse. Esta característica ya existía antes
de la creación de las bibliotecas de clases portables, por lo que era una elección obvia para habilitar la portabilidad
binaria entre .NET Framework y Mono (aunque solo funcionaba en una dirección).
Introducción a .NET Core
16/09/2020 • 2 minutes to read • Edit Online

.NET Core es una plataforma de desarrollo de código abierto para uso general. Puede crear aplicaciones de
.NET Core para Windows, macOS y Linux para procesadores x64, x86, ARM32 y ARM64 mediante varios lenguajes
de programación. Se proporcionan marcos y API para la nube, IoT, la Interfaz de usuario de cliente y el aprendizaje
automático.
Descargue el SDK de .NET Core para probar .NET Core en el equipo. La última versión es .NET Core 3.1.

Descarga de .NET Core


Puede obtener .NET Core de las siguientes maneras:
Instaladores para Windows y macOS
Paquetes de Linux
Contenedores de Docker
Archivos ZIP y tar
Scripts de instalación
Notas de la versión

Creación de la primera aplicación


Después de instalar el SDK de .NET Core, abra un símbolo del sistema. Use los comandos siguientes para crear y
ejecutar una aplicación:

dotnet new console


dotnet run

Debería ver los siguientes resultados:

Hello World!

Contribuir
.NET Core es una plataforma abierta. Todo el mundo puede participar.
Registre las incidencias y preguntas sobre el producto en Developer Community.
Las contribuciones al producto se deben realizar en uno de los repositorios del proyecto, como dotnet/runtime,
dotnet/sdk, dotnet/rosyln o aspnetcore. Para obtener más información, vea Repositorios de .NET Core.

Soporte técnico
.NET Core es compatible con Microsoft en Windows, macOS y Linux, y con Red Hat en Red Hat Enterprise Linux.

Pasos siguientes
Tutoriales de .NET Core
Prueba de .NET Core en el explorador
.NET Standard
16/09/2020 • 21 minutes to read • Edit Online

.NET Standard es una especificación formal de las API de .NET que se prevé que estén disponibles en todas las
implementaciones de .NET. La finalidad de .NET Standard es establecer una mayor uniformidad en el
ecosistema de .NET. ECMA 335 sigue estableciendo uniformidad para el comportamiento de la
implementación de .NET y, aunque ECMA 335 especifica un pequeño conjunto de bibliotecas estándar, la
especificación de .NET Standard abarca una gama más amplia de API de .NET.
.NET Standard habilita los escenarios clave siguientes:
Define un conjunto uniforme de API de BCL para todas las implementaciones de .NET que se van a
implementar, independientemente de la carga de trabajo.
Permite a los desarrolladores generar bibliotecas portátiles que se pueden usar en las implementaciones
de .NET con este mismo conjunto de API.
Reduce o incluso elimina la compilación condicional de código fuente compartido debido a las API de .NET,
solo para API de sistema operativo.
Las diversas implementaciones de .NET tienen como destino versiones concretas de .NET Standard. Cada
implementación de .NET anuncia la última versión más alta de .NET Standard que admite, indicación de que
también es compatible con versiones anteriores. Por ejemplo, .NET Framework 4.6 implementa .NET Standard
1.3, lo que significa que expone todas las API definidas en las versiones de .NET Standard de 1.0 a 1.3. De
forma similar, .NET Framework 4.6.1 implementa .NET Standard 1.4, mientras que .NET Core 1.0 implementa
.NET Standard 1.6.

Compatibilidad con implementaciones de .NET


En la tabla siguiente se indican las versiones mínimas de la plataforma compatibles con cada versión de
.NET Standard. Esto significa que las versiones posteriores de una plataforma de la lista también son
compatibles con la versión correspondiente de .NET Standard. Por ejemplo, .NET Core 2.2 es compatible con
.NET Standard 2.0 y versiones anteriores.

. N ET
STA N D
A RD 1. 0 1. 1 1. 2 1. 3 1. 4 1. 5 1. 6 2. 0 2. 1

.NET 1.0 1.0 1.0 1.0 1.0 1.0 1.0 2.0 3.0
Core

.NET 4.5 4.5 4.5.1 4.6 4.6.1 4.6.1 2 4.6.1 2 4.6.1 2 N/A3
Framew
ork 1

Mono 4.6 4.6 4.6 4.6 4.6 4.6 4.6 5.4 6.4

Xamarin 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.14 12.16
.iOS

Xamarin 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.8 5.16
.Mac
. N ET
STA N D
A RD 1. 0 1. 1 1. 2 1. 3 1. 4 1. 5 1. 6 2. 0 2. 1

Xamarin 7.0 7.0 7.0 7.0 7.0 7.0 7.0 8.0 10.0
.Androi
d

Platafor 10.0 10.0 10.0 10.0 10.0 10.0.16 10.0.16 10.0.16 TBD
ma 299 299 299
univers
al de
Window
s

Unity 2018.1 2018.1 2018.1 2018.1 2018.1 2018.1 2018.1 2018.1 TBD

1 Las versiones que se muestran de .NET Framework se aplican al SDK de .NET Core 2.0 y versiones posteriores de la herramienta. Las versiones
anteriores usaban una asignación diferente para .NET Standard 1.5 y versiones posteriores. Puede descargar herramientas para .NET Core para
Visual Studio 2015 si no se puede actualizar a Visual Studio 2017 ni a una versión posterior.

2 Las versiones siguientes representan las reglas que usa NuGet para determinar si una determinada biblioteca de .NET Standard es aplicable.
Aunque NuGet considera a .NET Framework 4.6.1 compatible con .NET Standard (versiones 1.5 a 2.0) hay varios problemas con el consumo de
bibliotecas de .NET Standard que se compilaron para esas versiones desde proyectos de .NET Framework 4.6.1. Para los proyectos de .NET
Framework que tengan que usar estas bibliotecas, se recomienda actualizar el proyecto para destinarlo a .NET Framework 4.7.2 o una versión
posterior.

3 .NET Framework no admitirá .NET Standard 2.1 o versiones posteriores. Para más detalles, vea el anuncio de .NET Standard 2.1.

Las columnas representan las versiones de .NET Standard. Cada celda de encabezado es un vínculo a un
documento que muestra qué API se han agregado en esa versión de .NET Standard.
Las filas representan las diferentes implementaciones de .NET.
El número de versión de cada celda indica la versión mínima de la implementación que necesitará para
tener como destino dicha versión de .NET Standard.
Para ver una tabla interactiva, consulte Versiones de .NET Standard.
Para encontrar la versión más reciente de .NET Standard que puede usar como destino, haga lo siguiente:
1. Busque la fila en la que se indica la implementación de .NET en la que quiere realizar la ejecución.
2. Busque la columna de esa fila que indica la versión de derecha a izquierda.
3. El encabezado de columna indica la versión de .NET Standard que admite el destino. También se puede
seleccionar como destino cualquier versión anterior de .NET Standard. Las versiones superiores de .NET
Standard también serán compatibles con la implementación.
4. Repita este proceso para cada plataforma a la que quiera dirigirse. Si tiene más de una plataforma de
destino, debe elegir la versión más baja. Por ejemplo, si quiere ejecutar en .NET Framework 4.5 y .NET Core
1.0, la versión de .NET Standard más alta que puede usar es .NET Standard 1.1.
Versión de .NET Standard de destino
Al elegir una versión de .NET Standard, debe tener en cuenta lo siguiente:
Cuanto mayor sea la versión, más API tendrá disponibles.
Cuanto menor sea la versión, más plataformas la implementarán.
En general, se recomienda elegir como destino la versión menor de .NET Standard posible. Así, después de
buscar la versión de .NET Standard superior que puede elegir como destino, siga estos pasos:
1. Diríjase a la siguiente versión menor de .NET Standard y cree el proyecto.
2. Si el proyecto se crea correctamente, repita el paso 1. De lo contrario, vuelva a dirigirse a la siguiente
versión mayor, que es la que debe usar.
Sin embargo, el establecimiento como destino de las versiones inferiores de .NET Standard introduce
diferentes dependencias de compatibilidad. Si el proyecto establece como destino .NET Standard 1.x, le
recomendamos que también establezca .NET Standard 2.0 como destino. Esto simplifica el gráfico de
dependencias para los usuarios de la biblioteca que se ejecutan en los marcos compatibles de .NET Standard
2.0 y reduce el número de paquetes que necesitan descargar.
Reglas de control de versiones de .NET Standard
Hay dos reglas de control de versiones principales:
Adición: las versiones de .NET Standard son círculos lógicamente concéntricos: las versiones superiores
incorporan todas las API de las versiones anteriores. No hay ningún cambio importante entre versiones.
Inmutable: Una vez publicadas, las versiones de .NET Standard se congelan. Las nuevas API primero están
disponibles en implementaciones de .NET concretas, como .NET Core. Si el consejo de revisión de .NET
Standard cree que las nuevas API deben estar disponibles para todas las implementaciones de .NET, se
agregan en una nueva versión de .NET Standard.

Especificación
La especificación de .NET Standard es un conjunto estandarizado de API. La especificación se mantiene
mediante implementadores de .NET, específicamente Microsoft (que incluye .NET Framework, .NET Core y
Mono) y Unity. Se usa un proceso de comentarios públicos como parte del establecimiento de las versiones
nuevas de .NET Standard a través de GitHub.
Artefactos oficiales
La especificación oficial es un conjunto de archivos .cs que definen las API que forman parte del estándar. El
directorio ref en el repositorio dotnet/standard define las API de .NET Standard.
El metapaquete NETStandard.Library (código fuente) describe el conjunto de bibliotecas que definen (en
parte) una o varias versiones de .NET Standard.
Un componente determinado, como System.Runtime , describe lo siguiente:
Parte de .NET Standard (solo su ámbito).
Varias versiones de .NET Standard para ese ámbito.
Se proporcionan artefactos derivados para permitir una lectura más cómoda y habilitar ciertos escenarios de
desarrollo (por ejemplo, el uso de un compilador).
Lista de API en Markdown
Ensamblados de referencia, distribuidos como paquetes NuGet y a los que hace referencia el metapaquete
NETStandard.Library.
Representación de paquetes
El principal vehículo de distribución de los ensamblados de referencia de .NET Standard son los paquetes
NuGet. Las implementaciones se entregarán de diversas formas, adecuadas para cada implementación de
.NET.
Los paquetes NuGet tienen como destino uno o varios marcos. Los paquetes de .NET Standard tienen como
destino el marco de trabajo ".NET Standard". Puede establecer como destino el marco de .NET Standard
mediante el TFM compacto netstandard (por ejemplo, netstandard1.4 ). Las bibliotecas diseñadas para
ejecutarse en varios entornos de ejecución deben tener como destino este marco de trabajo. Para obtener el
conjunto más amplio de API, indique netstandard2.0 como destino, puesto que el número de API disponibles
se ha doblado entre .NET Standard 1.6 y 2.0.
El metapaquete NETStandard.Library hace referencia al conjunto completo de paquetes NuGet que definen
.NET Standard. La manera más común de establecer como destino netstandard consiste en hacer referencia a
este metapaquete. Describe y proporciona acceso a las aproximadamente 40 bibliotecas de .NET y las API
asociadas que definen .NET Standard. Puede hacer referencia a paquetes adicionales que tienen como destino
netstandard para obtener acceso a otras API.

Control de versiones
La especificación no es única, sino que se trata de un conjunto de API con versiones lineales y con un
crecimiento incremental. La primera versión del estándar establece un conjunto básico de API. Las versiones
posteriores agregan API y heredan las API definidas por las versiones anteriores. No se ha establecido
ninguna disposición para quitar API del estándar.
.NET Standard no es específico de ninguna implementación de .NET ni coincide con el esquema de control de
versiones de ningún entorno de ejecución.
Las API agregadas a cualquier implementación de .NET (por ejemplo, .NET Framework, .NET Core y Mono)
pueden considerarse candidatas para agregarse a la especificación, sobre todo si se consideran
fundamentales por su naturaleza. Las nuevas versiones de .NET Standard se crean en función de las versiones
de la implementación de .NET, lo que permite establecer como destino nuevas API desde una Biblioteca de
clases portable (PLC) de .NET Standard. Los mecanismos de control de versiones se describen con más detalle
en Control de versiones de .NET Core.
El control de versiones de .NET Standard es importante para su uso. Dada una versión de .NET Standard,
puede usar bibliotecas que tengan como destino esa misma versión o una inferior. En el enfoque siguiente se
describe el flujo de trabajo para el uso de PCL de .NET Standard específicas para tener como destino .NET
Standard.
Seleccione una versión de .NET Standard para usarla para la PCL.
Use bibliotecas que dependan de la misma versión de .NET Standard o de una inferior.
Si encuentra una biblioteca que depende de una versión superior de .NET Standard, deberá adoptar la
misma versión o bien optar por no usar esa biblioteca.

.NET Standard como destino


Puede compilar bibliotecas estándar de .NET mediante una combinación del marco netstandard y el
metapaquete NETStandard.Library.

Modo de compatibilidad de .NET Framework


A partir de .NET Standard 2.0 se ha introducido el modo de compatibilidad de .NET Framework. Este modo de
compatibilidad permite que los proyectos de .NET Standard hagan referencia a bibliotecas de .NET
Framework como si estuviesen compiladas para .NET Standard. Las referencias a bibliotecas de .NET
Framework no funcionan para todos los proyectos, por ejemplo, en bibliotecas que usan API de Windows
Presentation Foundation (WPF).
Para obtener más información, consulte Modo de compatibilidad de .NET Framework.

Bibliotecas de .NET standard y Visual Studio


Para crear bibliotecas de .NET Standard en Visual Studio, asegúrese de tener Visual Studio 2019 o la
versión 15.3 de Visual Studio 2017 o posterior instalada en Windows, o bien la versión 7.1 de Visual Studio
para Mac o posterior instalada en macOS.
Si solo necesita consumir bibliotecas de .NET Standard 2.0 en sus proyectos, también puede hacerlo en Visual
Studio 2015. Sin embargo, necesitará tener el cliente 3.6 de NuGet o uno posterior instalado. Puede descargar
el cliente de NuGet para Visual Studio 2015 desde la página de descargas de NuGet.

Comparación con las bibliotecas de clases portátiles


.NET Standard es el reemplazo de las bibliotecas de clases portátiles (PCL). .NET Standard mejora la
experiencia de creación de bibliotecas portátiles, ya que mantiene una BCL estándar y establece como
resultado una mayor uniformidad en las implementaciones de .NET. Una biblioteca que tiene como destino
.NET Standard es una PCL o "PCL basada en .NET Standard". Las PCL existentes son "PCL basadas en perfiles".
.NET Standard y los perfiles de PCL se crearon con propósitos parecidos, pero también presentan algunas
diferencias clave.
Similitudes:
Definen las API que se pueden usar para compartir código binario.
Diferencias:
.NET Standard es un conjunto mantenido de API, mientras que los perfiles de PCL se definen por las
intersecciones de las plataformas existentes.
.NET Standard tiene versiones lineales, al contrario de los perfiles de PCL.
Los perfiles de PCL representan plataformas de Microsoft, mientras que .NET Standard es independiente
de la plataforma.
Compatibilidad con PCL
.NET Standard es compatible con un subconjunto de perfiles de PCL. .NET Standard 1.0, 1.1 y 1.2 se
superponen con un conjunto de perfiles de PCL. Esta superposición se ha creado por dos motivos:
Habilitar las PCL basadas en .NET Standard para hacer referencia a PCL basadas en perfiles.
Habilitar las PCL basadas en perfiles para empaquetarlas como PCL basadas en .NET Standard.
Se proporciona compatibilidad con PCL basada en perfiles mediante el paquete NuGet
Microsoft.NETCore.Portable.Compatibility. Esta dependencia es necesaria cuando se hace referencia a
paquetes NuGet que contienen PCL basadas en perfiles.
Las PCL basadas en perfiles empaquetadas como netstandard son más fáciles de consumir que las PCL
basadas en perfiles empaquetadas normalmente. El empaquetado de netstandard es compatible con los
usuarios existentes.
Puede ver el conjunto de perfiles de PCL que son compatibles con .NET Standard:

P ERF IL DE P C L . N ET STA N DA RD P L ATA F O RM A S DE P C L

Profile7 1.1 .NET Framework 4.5, Windows 8

Profile31 1.0 Windows 8.1, Windows Phone


Silverlight 8.1

Profile32 1.2 Windows 8.1, Windows Phone 8.1

Profile44 1.2 .NET Framework 4.5.1, Windows 8.1

Profile49 1.0 .NET Framework 4.5, Windows Phone


Silverlight 8
P ERF IL DE P C L . N ET STA N DA RD P L ATA F O RM A S DE P C L

Profile78 1.0 .NET Framework 4.5, Windows 8,


Windows Phone Silverlight 8

Profile84 1.0 Windows Phone 8.1, Windows Phone


Silverlight 8.1

Profile111 1.1 .NET Framework 4.5, Windows 8,


Windows Phone 8.1

Profile151 1.2 .NET Framework 4.5.1, Windows 8.1,


Windows Phone 8.1

Profile157 1.0 Windows 8.1, Windows Phone 8.1,


Windows Phone Silverlight 8.1

Profile259 1.0 .NET Framework 4.5, Windows 8,


Windows Phone 8.1, Windows Phone
Silverlight 8

Vea también
Versiones de .NET Standard
Compilación de una biblioteca de .NET Standard
Destinatarios multiplataforma
Glosario de .NET
16/09/2020 • 26 minutes to read • Edit Online

El objetivo principal de este glosario es aclarar los significados de algunos de los términos y acrónimos que
aparecen frecuentemente en la documentación de .NET sin definiciones.

AOT
Compilador Ahead Of Time.
Similar a JIT, este compilador también convierte IL en código de máquina. A diferencia de la compilación JIT, la
compilación AOT ocurre antes de que la aplicación se ejecute y, normalmente, se realiza en un equipo diferente.
Dado que las cadenas de la herramienta de AOT no se compilan en tiempo de ejecución, no tienen que minimizar el
tiempo dedicado a compilar. Esto significa que pueden dedicar más tiempo a la optimización. Puesto que el
contexto de AOT es toda la aplicación, el compilador AOT también realiza vinculación entre módulos y el análisis de
todo el programa, lo que significa que se siguen todas las referencias y se genera un archivo ejecutable único.
Vea CoreRT y .NET Native.

ASP.NET
La implementación original de ASP.NET que se distribuye con .NET Framework.
A veces, ASP.NET es un término genérico que hace referencia a ambas implementaciones de ASP.NET, incluido
ASP.NET Core. El significado que lleva el término en una instancia específica se determina según el contexto. Haga
referencia a ASP.NET 4.x cuando desee dejar claro que no usa ASP.NET para indicar ambas implementaciones.
Vea la documentación de ASP.NET.

ASP.NET Core
Implementación multiplataforma, de alto rendimiento y de código abierto de ASP.NET.
Vea la documentación de ASP.NET Core.

ensamblado
Un archivo .dll/ .exe que puede contener una colección de API a la que puede llamarse mediante aplicaciones u
otros ensamblados.
Un ensamblado puede incluir tipos como interfaces, clases, estructuras, enumeraciones y delegados. A veces, se
hace referencia a los ensamblados de la carpeta bin de un proyecto como archivos binarios. Vea también biblioteca.

BCL
Biblioteca de clases base. También se conoce como biblioteca de marco.
Un conjunto de bibliotecas que conforman los espacios de nombres de System.* (y hasta cierto punto Microsoft.*).
BCL es un marco de nivel inferior de uso general donde se compilan marcos de trabajo de la aplicación de nivel
superior, como ASP.NET Core.
El código fuente de la BCL para .NET 5 (y .NET Core) y versiones posteriores se encuentra en el repositorio del
entorno de ejecución de .NET. La mayoría de las API de BCL para esta implementación más reciente de .NET
también están disponibles en .NET Framework, por lo que puede considerar este código fuente como una
bifurcación del código fuente de la BCL de .NET Framework.

CLR
Common Language Runtime.
El significado exacto depende del contexto. Common Language Runtime normalmente hace referencia al entorno
de ejecución de .NET Framework o de .NET 5 (y .NET Core) y versiones posteriores.
CLR controla la asignación y administración de memoria. CLR es también una máquina virtual que no solo ejecuta
aplicaciones, sino que también genera y compila código sobre la marcha mediante un compilador JIT.
La implementación de CLR para .NET Framework es solo para Windows.
La implementación de CLR para .NET 5 y versiones posteriores (también conocida como CoreCLR) se crea a partir
del mismo código base que el CLR de .NET Framework. Originalmente, CoreCLR era el entorno de ejecución de
Silverlight y estaba diseñado para ejecutarse en varias plataformas, concretamente Windows y OS X. Sigue siendo
un entorno de ejecución multiplataforma y ahora incluye compatibilidad con muchas distribuciones de Linux.
Vea también entorno de ejecución.

CoreCLR
Common Language Runtime para .NET 5 (y .NET Core) y versiones posteriores.
Vea CLR.

CoreRT
A diferencia de CLR, CoreRT no es una máquina virtual, lo que significa que no incluye las funciones para generar y
ejecutar código sobre la marcha porque no incluye un compilador JIT. En cambio, incluye GC, reflexión y capacidad
de identificación del tipo en tiempo de ejecución (RTTI). Con todo, su sistema de tipos está diseñado para que no
sean necesarios los metadatos para la reflexión. No requerir los metadatos permite tener una cadena de
herramientas de AOT que puede vincular metadatos superfluos y (más importante) identificar código que no usa la
aplicación. CoreRT está en desarrollo.
Consulte Introducción a .NET Native u CoreRT.

multiplataforma
La capacidad para desarrollar y ejecutar una aplicación que se puede usar en varios sistemas operativos diferentes,
como Linux, Windows e iOS, sin tener que volver a escribir específicamente para cada uno de ellos. Esto permite
reutilizar el código y posibilita la coherencia entre aplicaciones en distintas plataformas.

ecosistema
Todo el software en tiempo de ejecución, las herramientas de desarrollo y los recursos de la comunidad que se
usan para compilar y ejecutar aplicaciones de una tecnología determinada.
El término "ecosistema de .NET" se diferencia de términos parecidos como "pila de .NET" en que incluye bibliotecas
y aplicaciones de terceros. Aquí se muestra un ejemplo en una frase:
"La finalidad de .NET Standard es establecer una mayor uniformidad en el ecosistema de .NET".

marco de trabajo
En general, una colección completa de API que facilita el desarrollo y la implementación de aplicaciones que se
basan en una tecnología concreta. En este sentido general, ASP.NET Core y Windows Forms son ejemplos de
marcos de trabajo de la aplicación. Vea también biblioteca.
Los siguientes términos tienen un significado diferente:
.NET Framework
Plataforma de destino
TFM (moniker de la plataforma de destino)
Aplicación dependiente de la plataforma
En la documentación de .NET heredada, "marco de trabajo" a veces hace referencia a una implementación de .NET.
Por ejemplo, un artículo puede llamar marco de trabajo a .NET 5.

GC
Recolector de elementos no utilizados.
El recolector de elementos no utilizados es una implementación de administración de memoria automática. GC
libera la memoria ocupada por objetos que ya no se usan.
Vea Recolección de elementos no utilizados.

IL
Lenguaje intermedio.
Los lenguajes .NET de nivel alto, como C#, compilan en un conjunto de instrucciones independiente del hardware,
lo que se denomina lenguaje intermedio (IL). A veces, se hace referencia al IL como MSIL (IL de Microsoft) o CIL
(Common IL).

JIT
Compilador Just-In-Time.
Similar a AOT, este compilador convierte el IL en código de máquina que entienda el procesador. A diferencia de
AOT, la compilación JIT se produce a petición y se lleva a cabo en el mismo equipo en que debe ejecutarse el
código. Puesto que la compilación JIT tiene lugar durante la ejecución de la aplicación, el tiempo de compilación es
parte del tiempo de ejecución. Por tanto, los compiladores JIT tienen que compensar el tiempo invertido en
optimizar el código con el ahorro que puede generar el código resultante. Pero un JIT conoce el hardware real y
puede liberar a los desarrolladores de tener que enviar diferentes implementaciones.

implementación de .NET
Una implementación de .NET incluye lo siguiente:
Uno o varios entornos de ejecución. Ejemplos: CLR y CoreRT.
Una biblioteca de clases que implementa una versión de .NET Standard y puede incluir API adicionales.
Ejemplos: BCL para .NET Framework y para .NET 5 (y .NET Core) y versiones posteriores.
Opcionalmente, uno o varios marcos de trabajo de la aplicación. Ejemplos: ASP.NET, Windows Forms y WPF se
incluyen en .NET Framework y .NET 5.
Opcionalmente, herramientas de desarrollo. Algunas herramientas de desarrollo se comparten entre varias
implementaciones.
Ejemplos de implementaciones de .NET:
.NET Framework
.NET 5 y versiones posteriores (incluido .NET Core 2.1-3.1
Plataforma universal de Windows (UWP)
Mono

biblioteca
Una colección de API que pueden llamarse mediante aplicaciones u otras bibliotecas. Una biblioteca de .NET se
compone de uno o varios ensamblados.
Las palabras biblioteca y marco de trabajo se usan a menudo como sinónimos.

Mono
Mono es una implementación de .NET multiplataforma y de código abierto que se usa principalmente cuando se
requiere un entorno de ejecución pequeño. Es el entorno de ejecución que activa las aplicaciones de Xamarin en
Android, Mac, iOS, tvOS y watchOS, y se centra principalmente en aplicaciones que requieren una superficie
pequeña.
Admite todas las versiones de .NET Standard publicadas actualmente.
Históricamente, Mono implementaba la API de .NET Framework más grande y emulaba algunas de las funciones
más populares en Unix. A veces, se usa para ejecutar aplicaciones de .NET que se basan en estas capacidades en
Unix.
Mono se suele usar con un compilador Just-In-Time, pero también incluye un compilador estático completo
(compilación Ahead Of Time) que se usa en plataformas como iOS.
Consulte la documentación de Mono.

.NET
El término que engloba .NET Standard y todas las cargas de trabajo e implementaciones de .NET. Siempre
totalmente en mayúsculas, nunca ".Net".
Cuando se publique .NET 5 (actualmente en versión preliminar), será la implementación de .NET recomendada para
todo el nuevo desarrollo de .NET, por lo que en algunos contextos ".NET" implicará ".NET 5 y versiones posteriores".
Vea Aspectos básicos de .NET.

.NET 5 y versiones posteriores


Una implementación multiplataforma, de alto rendimiento y de código abierto de .NET. Incluye Common Language
Runtime (CLR), un entorno de ejecución AOT (CoreRT, en desarrollo), una biblioteca de clases base (BCL) y el SDK
de .NET.
Las versiones anteriores de esta implementación de .NET se conocen como .NET Core. .NET 5.0 es la siguiente
versión después de .NET Core 3.1. La versión 4 se ha omitido para no confundir esta nueva implementación de .NET
con la implementación anterior conocida como .NET Framework. La versión actual de .NET Framework es 4.8.
Vea Aspectos básicos de .NET.

CLI de .NET
Una cadena de herramientas multiplataforma para desarrollar aplicaciones y bibliotecas para .NET 5 (y .NET Core) y
versiones posteriores. También conocida como la CLI de .NET Core.
Vea CLI de .NET.

.NET Core
Vea .NET 5 y versiones posteriores.

.NET Framework
Una implementación de .NET que se ejecuta solo en Windows. Incluye Common Language Runtime (CLR), la
biblioteca de clases base (BCL) y las bibliotecas de marco de trabajo de la aplicación, como ASP.NET,
Windows Forms y WPF.
Vea Guía de .NET Framework.

.NET Native
Cadena de herramientas del compilador que genera código nativo Ahead Of Time (AOT), en lugar de Just-In-Time
(JIT).
La compilación se produce en el equipo del desarrollador, de forma similar a cómo funcionan el compilador y el
enlazador de C++. Quita el código que no se usa y emplea más tiempo en optimizarlo. Extrae código de bibliotecas
y lo combina en el archivo ejecutable. El resultado es un módulo único que representa toda la aplicación.
UWP fue el primer marco de trabajo de la aplicación compatible con .NET Native. Ahora, se admite la compilación
de aplicaciones de consola nativas para Windows, macOS y Linux.
Vea Intro to .NET Native and CoreRT (Introducción a .NET Native y CoreRT).

.NET SDK
Conjunto de bibliotecas y herramientas que permiten a los desarrolladores crear aplicaciones y bibliotecas de .NET
para .NET 5 (y .NET Core) y versiones posteriores. También se conoce como el SDK de .NET Core.
Incluye la CLI de .NET para compilar aplicaciones, el entorno de ejecución, y las bibliotecas de .NET para compilar y
ejecutar aplicaciones, y el ejecutable dotnet (dotnet.exe) que ejecuta comandos de la CLI y ejecuta aplicaciones.
Vea Información general sobre el SDK de .NET.

.NET Standard
Una especificación formal de las API de .NET que están disponibles en cada implementación de .NET.
La especificación de .NET Standard a veces se denomina "biblioteca" en la documentación. Dado que una biblioteca
incluye implementaciones de API, no solo especificaciones (interfaces), es confuso denominar "biblioteca" a .NET
Standard.
Vea .NET Standard.

NGEN
Generación (de imágenes) nativas.
Esta tecnología se puede considerar como un compilador JIT persistente. Normalmente, compila código en el
equipo en que se ejecuta el código, pero la compilación se suele producir durante la instalación.

paquete
Un paquete de NuGet — o simplemente un paquete — es un archivo .zip con uno o varios ensamblados del mismo
nombre junto con metadatos adicionales, como el nombre del autor.
El archivo .zip tiene una extensión .nupkg y puede contener recursos (como archivos .dll y .xml) para usar con varios
marcos de destino y versiones. Cuando se instala en una aplicación o biblioteca, se seleccionan los recursos
adecuados en función de la plataforma de destino especificada por la aplicación o biblioteca. Los recursos que
definen la interfaz se encuentran en la carpeta ref y los recursos que definen la implementación se encuentran en la
carpeta lib.

platform
Un sistema operativo y el hardware en que se ejecuta, como Windows, macOS, Linux, iOS y Android.
Aquí tiene ejemplos de uso en frases:
".NET Core es una implementación multiplataforma de .NET".
"Los perfiles de PCL representan plataformas de Microsoft, mientras que .NET Standard es independiente de la
plataforma".
La documentación heredada de .NET a veces usa el término "plataforma de .NET" para referirse a una
implementación de .NET o a la pila de .NET que incluyen todas las implementaciones. Estos dos usos tienden a
confundirse con el significado principal (sistema operativo o hardware), por tanto, tratamos de evitar estos usos.
"Plataforma" tiene un significado diferente en "plataforma del desarrollador", ya que hace referencia al software
que proporciona herramientas y bibliotecas para compilar y ejecutar aplicaciones. .NET es una plataforma para el
desarrollo de contenido de código abierto multiplataforma que permite crear una gran variedad de tipos de
aplicaciones.

motor en tiempo de ejecución


En términos generales, el entorno de ejecución de un programa administrado. El sistema operativo forma parte del
entorno de ejecución, pero no del entorno de ejecución .NET. Estos son algunos ejemplos de los entornos de
ejecución de .NET en este sentido de la palabra:
Common Language Runtime (CLR)
.NET Native (para UWP)
Entorno de ejecución Mono
El término "entorno de ejecución" tiene un significado diferente en los siguientes contextos:
Página de descarga de .NET.
Aquí, "entorno de ejecución" se refiere a CLR y a BCL (las bibliotecas de marco), que puede descargar e
instalar en una máquina para ejecutar aplicaciones dependientes de la plataforma en la máquina.
Identificador de entorno de ejecución (RID) para .NET 5 (y .NET Core) y versiones posteriores.
Aquí, "entorno de ejecución" hace referencia a la plataforma del sistema operativo y a la arquitectura de la
CPU en la que se ejecuta una aplicación .NET, por ejemplo, linux-x64 .

A veces, la documentación heredada de .NET usa "entorno de ejecución" para indicar una implementación de .NET,
como en los siguientes ejemplos:
"Los diversos entornos de ejecución .NET implementan versiones concretas de .NET Standard".
"Las bibliotecas diseñadas para ejecutarse en varios entornos de ejecución deben tener como destino este
marco de trabajo". (Hace referencia a .NET Standard).
"Los diversos entornos de ejecución .NET implementan versiones concretas de .NET Standard. … Cada versión
del entorno de ejecución de .NET anuncia la última versión de .NET Standard que admite...".

pila
Un conjunto de tecnologías de programación que se usan para compilar y ejecutar aplicaciones.
La "pila de .NET" hace referencia a .NET Standard y a todas las implementaciones de .NET. La frase "una pila de .NET"
puede hacer referencia a una implementación de .NET.

versión de .NET Framework de destino


La colección de API de las que depende una aplicación o biblioteca de .NET.
Una aplicación o biblioteca puede tener como destino una versión de .NET Standard (por ejemplo,
.NET Standard 2.0), que es la especificación de un conjunto estándar de API de todas las implementaciones de .NET.
Una aplicación o biblioteca también puede tener como destino una versión de una implementación específica de
.NET; en este caso, obtiene acceso a las API específicas de la implementación. Por ejemplo, una aplicación que tenga
como destino Xamarin.iOS obtiene acceso a contenedores de la API de iOS proporcionados por Xamarin.
Para algunas plataformas de destino (por ejemplo, .NET Framework), las API disponibles se definen mediante los
ensamblados que una implementación de .NET instala en un sistema y pueden incluir las API del marco de trabajo
de la aplicación (por ejemplo, ASP.NET o Windows Forms). Para plataformas de destino basadas en paquetes (por
ejemplo, .NET Standard y .NET Core), las API se definen mediante los paquetes instalados en la aplicación o
biblioteca. En ese caso, la plataforma de destino especifica implícitamente un paquete que hace referencia a todos
los paquetes que forman el marco de trabajo.
Vea Plataformas de destino.

TFM
Moniker de la plataforma de destino.
Un formato de token normalizado para especificar la plataforma de destino de una aplicación o biblioteca de .NET.
Se suele hacer referencia a las plataformas de destino mediante un nombre corto, como net462 . Los TFM de
formato largo (como .NETFramework,Version=4.6.2) existen, pero no se suelen usar para especificar una
plataforma de destino.
Vea Plataformas de destino.

UWP
Plataforma universal de Windows.
Una implementación de .NET que se usa para compilar aplicaciones Windows modernas y táctiles y software para
Internet de las cosas (IoT). Se ha diseñado para unificar los diferentes tipos de dispositivos de destino, incluidos
equipos, tabletas, teléfonos e incluso la consola Xbox. UWP proporciona muchos servicios, como una tienda de
aplicaciones centralizada, un entorno de ejecución (AppContainer) y un conjunto de API de Windows para usar en
lugar de Win32 (WinRT). Pueden escribirse aplicaciones en C++, C#, Visual Basic y JavaScript. Al usar C# y
Visual Basic, .NET 5 (y .NET Core), así como sus versiones posteriores, proporcionan las API de .NET.

Vea también
Aspectos básicos de .NET
Guía de .NET Framework
ASP.NET Overview (Información general de ASP.NET)
ASP.NET Core Overview (Información general de ASP.NET Core)
Tutorial: Creación de una aplicación de consola de
.NET Core con Visual Studio
16/09/2020 • 6 minutes to read • Edit Online

En este tutorial se muestra cómo crear y ejecutar una aplicación de consola de .NET Core en Visual Studio 2019.

Requisitos previos
Visual Studio 2019, versión 16.6 o posterior con la carga de trabajo Desarrollo multiplataforma de
.NET Core instalada. El SDK de .NET Core 3.1 se instala automáticamente al seleccionar esta carga de
trabajo.
Para más información, consulte Instalación del SDK de .NET Core con Visual Studio.

Creación de la aplicación
Cree un proyecto de aplicación de consola de .NET Core denominado "HelloWorld".
1. Inicie Visual Studio 2019.
2. En la página de inicio, elija Crear un proyecto nuevo .

3. En la página Crear un proyecto , escriba consola en el cuadro de búsqueda. Después, elija C# o Visual
Basic en la lista de lenguajes y luego elija Todas las plataformas en la lista de plataformas. Elija la
plantilla Aplicación de consola (.NET Core) y haga clic en Siguiente .
TIP
Si no ve las plantillas de .NET Core, es probable que falte la carga de trabajo necesaria. En el mensaje ¿No
encuentra lo que busca? , elija el vínculo Instalar más herramientas y características . Se abre el Instalador
de Visual Studio. Asegúrese de que tiene instalada la carga de trabajo Desarrollo multiplataforma de .NET
Core .

4. En el cuadro de diálogo Configurar el nuevo proyecto , escriba HelloWorld en el cuadro Nombre


del proyecto . Luego, elija Crear .
La plantilla crea una aplicación "Hola mundo" sencilla. Llama al método Console.WriteLine(String) para mostrar
"Hola mundo" en la ventana de la consola.
El código de plantilla define una clase, Program , con un solo método, Main , que toma una matriz de String como
argumento:

using System;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

Imports System

Module Program
Sub Main(args As String())
Console.WriteLine("Hello World!")
End Sub
End Module

Main es el punto de entrada de la aplicación, el método que se llama automáticamente mediante el tiempo de
ejecución cuando inicia la aplicación. Los argumentos de línea de comandos proporcionados cuando se inicia la
aplicación están disponibles en la matriz args.
Si no se muestra el idioma que quiere usar, cambie el selector de idioma en la parte superior de la página.

Ejecutar la aplicación
1. Presione Ctrl+F5 para ejecutar el programa sin depurar.
Se abre la ventana de la consola con el texto "Hello World" impreso en la pantalla y parte de la
información de depuración de Visual Studio.
2. Presione cualquier tecla para cerrar la ventana de consola.

Mejora de la aplicación
Mejore la aplicación para pedir su nombre al usuario y mostrarlo con la fecha y la hora.
1. En Program.cs o Program.vb, reemplace el contenido del método Main , que es la línea que llama a
Console.WriteLine , por el código siguiente:

Console.WriteLine("\nWhat is your name? ");


var name = Console.ReadLine();
var date = DateTime.Now;
Console.WriteLine($"\nHello, {name}, on {date:d} at {date:t}!");
Console.Write("\nPress any key to exit...");
Console.ReadKey(true);

Console.WriteLine(vbCrLf + "What is your name? ")


Dim name = Console.ReadLine()
Dim currentDate = DateTime.Now
Console.WriteLine($"{vbCrLf}Hello, {name}, on {currentDate:d} at {currentDate:t}")
Console.Write(vbCrLf + "Press any key to exit... ")
Console.ReadKey(True)

Este código muestra un mensaje en la ventana de la consola y espera a que el usuario escriba una cadena
y, luego, presione Entrar. Almacena esta cadena en una variable denominada name . También recupera el
valor de la propiedad DateTime.Now, que contiene la hora local actual, y lo asigna a una variable
denominada date ( currentDate en Visual Basic). Asimismo, muestra estos valores en la ventana de la
consola. Por último, muestra un mensaje en la ventana de la consola y llama al método
Console.ReadKey(Boolean) para esperar a la entrada del usuario.
El símbolo \n ( vbCrLf en Visual Basic) representa un carácter de nueva línea.
El signo de dólar ( $ ) delante de una cadena permite colocar expresiones como nombres de variable
entre llaves en la cadena. El valor de la expresión se inserta en la cadena en lugar de la expresión. Esta
sintaxis se conoce como cadenas interpoladas.
2. Presione Ctrl+F5 para ejecutar el programa sin depurar.
3. Responda a la solicitud escribiendo un nombre y presionando la tecla Entrar.

4. Presione cualquier tecla para cerrar la ventana de consola.


Pasos siguientes
En este tutorial, ha creado una aplicación de consola de .NET Core. En el siguiente tutorial, depurará la aplicación.
Depuración de una aplicación de consola de .NET Core con Visual Studio
Tutorial: Depuración de una aplicación de consola de
.NET Core con Visual Studio
16/09/2020 • 14 minutes to read • Edit Online

En este tutorial se presentan las herramientas de depuración que hay disponibles en Visual Studio.

Requisitos previos
Este tutorial funciona con la aplicación de consola que se crea en Creación de una aplicación de consola de
.NET Core con Visual Studio.

Uso de la configuración de compilación de depuración


Depuración y Versión son las configuraciones de compilación integradas de Visual Studio. Use la configuración de
compilación Depuración para depurar y la configuración de compilación Versión para la distribución final de la
versión.
En la configuración de depuración, el programa se compila sin optimizar y con toda la información de depuración
simbólica. La optimización complica la depuración, ya que la relación entre el código fuente y las instrucciones
generadas es más compleja. La configuración de versión del programa no contiene información de depuración
simbólica y está totalmente optimizada.
De forma predeterminada, Visual Studio usa la configuración de compilación Depuración, por lo que no es
necesario cambiarla antes de depurar.
1. Inicie Visual Studio.
2. Abra el proyecto que ha creado en Creación de una aplicación de consola de .NET Core con Visual Studio.
La configuración de compilación actual se muestra en la barra de herramientas. En la siguiente imagen de la
barra de herramientas se muestra que Visual Studio está configurado para compilar la versión de
depuración de la aplicación:

Establecer un punto de interrupción


Un punto de interrupción interrumpe temporalmente la ejecución de la aplicación antes de que se ejecute la línea
con el punto de interrupción.
1. Establezca un punto de interrupción en la línea que muestre el nombre, la fecha y la hora; para ello, haga clic
en el margen izquierdo de la ventana de código de esa línea. El margen izquierdo está a la izquierda de los
números de línea. Otras maneras de establecer un punto de interrupción consisten en colocar el cursor en la
línea de código y, después, presionar F9 o seleccionar Depurar > Alternar punto de interrupción en la
barra de menú.
En esta imagen vemos que, para indicar la línea en la que se establece el punto de interrupción,
Visual Studio lo resalta y muestra un punto rojo en el margen izquierdo.
2. Presione F5 para ejecutar el programa en modo de depuración. Otra manera de iniciar la depuración es
elegir Depuración > Iniciar depuración en el menú.
3. Cuando el sistema le pida un nombre, escriba una cadena en la ventana de consola y luego presione
Entrar.

4. La ejecución del programa se detiene cuando llega al punto de interrupción y antes de que se ejecute el
método Console.WriteLine . La ventana Variables locales muestra los valores de las variables definidas en
el método que se ejecuta actualmente.

Uso de la ventana Inmediato


La ventana Inmediato le permite interactuar con la aplicación que está depurando. Puede cambiar el valor de las
variables de forma interactiva para ver cómo afecta esto al programa.
1. Si la ventana Inmediato no está visible, muéstrela; para ello, elija Depurar > Ventanas > Inmediato .
2. Escriba name = "Gracie" en la ventana Inmediato y presione la tecla Entrar.
3. Escriba date = DateTime.Parse("2019-11-16T17:25:00Z").ToUniversalTime() en la ventana Inmediato y
presione la tecla Entrar.
La ventana Inmediato muestra el valor de la variable de cadena y las propiedades del valor DateTime.
Además, los valores de las variables se actualizan en la ventana Variables locales .

4. Presione F5 para que continúe la ejecución del programa. Otra manera de hacerlo es elegir Depuración >
Continuar en el menú.
Los valores mostrados en la ventana de la consola corresponden a los cambios realizados en la ventana
Inmediato .
5. Presione cualquier tecla para salir de la aplicación y detenga la depuración.

Establecimiento de un punto de interrupción condicional


El programa muestra la cadena que escribe el usuario. ¿Qué sucede si el usuario no escribe nada? Puede probarlo
con una característica de depuración muy útil denominada Punto de interrupción condicional.
1. Haga clic con el botón derecho en el punto rojo que representa al punto de interrupción. En el menú
contextual, seleccione Condiciones para abrir el cuadro de diálogo Configuración del punto de
interrupción . Active la casilla Condiciones si aún no está seleccionada.

2. En Expresión condicional , escriba el código siguiente en el campo que muestra el código de ejemplo que
comprueba si x es 5. Si no se muestra el idioma que quiere usar, cambie el selector de idioma en la parte
superior de la página.

String.IsNullOrEmpty(name)

String.IsNullOrEmpty(name)

Cada vez que se alcanza el punto de interrupción, el depurador llama al método


String.IsNullOrEmpty(name) y se interrumpe en esta línea solo si la llamada al método devuelve true .
En lugar de una expresión condicional, puede especificar un número de llamadas, que interrumpe la
ejecución del programa antes de que se ejecute una instrucción un número de veces especificado. Otra
opción consiste en especificar una condición de filtro, que interrumpe la ejecución del programa en función
de atributos tales como un identificador de subproceso, un nombre de proceso o un nombre de subproceso.
3. Seleccione Cerrar para cerrar el cuadro de diálogo.
4. Inicie el programa con la depuración presionando F5.
5. En la ventana de consola, cuando se le pida que escriba su nombre, presione la tecla Entrar.
6. Como se ha cumplido la condición que especificó ( name es null o String.Empty), la ejecución del
programa se detiene cuando se alcanza el punto de interrupción y antes de que se ejecute el método
Console.WriteLine .

7. Seleccione la ventana Variables locales , que muestra los valores de las variables que son locales para el
método que se ejecuta actualmente. En este caso, Main es el método que se está ejecutando actualmente.
Observe que el valor de la variable name es "" o String.Empty.
8. Confirme que el valor es una cadena vacía escribiendo la siguiente instrucción en la ventana Inmediato y
presionando Entrar. El resultado es true .

? name == String.Empty

? String.IsNullOrEmpty(name)

El signo de interrogación dirige la ventana Inmediato para evaluar una expresión.

9. Presione F5 para que continúe la ejecución del programa.


10. Presione cualquier tecla para cerrar la ventana de consola y detener la depuración.
11. Para borrar el punto de interrupción, haga clic en el punto en el margen izquierdo de la ventana de código.
Otras formas de borrar un punto de interrupción consisten en presionar F9 o elegir Depurar > Alternar
punto de interrupción mientras se selecciona la línea de código.

Ejecución paso a paso de un programa


Visual Studio también le permite recorrer línea a línea un programa y supervisar su ejecución. Normalmente,
establecería un punto de interrupción y seguiría el flujo del programa mediante una pequeña parte de su código
de programa. Como este programa es pequeño, puede ejecutar paso a paso el programa entero.
1. Elija Depurar > Depurar paso a paso por instrucciones . Otra manera de depurar una instrucción cada
vez es presionar F11.
Visual Studio resalta y muestra una flecha junto a la siguiente línea de ejecución.
C#
Visual Basic

En este punto, la ventana Variables locales muestra que la matriz args está vacía, y name y date tienen
valores predeterminados. Además, Visual Studio ha abierto una ventana de consola en blanco.
2. Presione F11. Visual Studio ahora resalta la siguiente línea de ejecución. La ventana Variables locales no
cambia y la ventana de consola permanece en blanco.
C#

Visual Basic
3. Presione F11. Visual Studio resalta la instrucción que incluye la asignación de variables name . La ventana
Variables locales muestra que name es null , y la ventana de consola muestra la cadena "What is your
name?".
4. Para responder a la solicitud, escriba una cadena en la ventana de consola y presione Entrar. La consola no
responde y la cadena que especificó no se muestra en la ventana de la consola, pero el método
Console.ReadLine capturará en cambio la entrada.
5. Presione F11. Visual Studio resalta la instrucción que incluye la asignación de la variable date (
currentDate en Visual Basic). La ventana Variables locales muestra el valor devuelto por la llamada al
método Console.ReadLine. La ventana de la consola también muestra la cadena que escribió en la solicitud.
6. Presione F11. La ventana Variables locales muestra el valor de la variable date tras la asignación desde
la propiedad DateTime.Now. La ventana de consola permanece sin cambios.
7. Presione F11. Visual Studio llama al método Console.WriteLine(String, Object, Object). La ventana de la
consola muestra la cadena con formato.
8. Elija Depurar > Depurar paso a paso para salir . Otra manera de detener la ejecución paso a paso es
presionar Mayús+F11.
La ventana de la consola muestra un mensaje y espera a que presione una tecla.
9. Presione cualquier tecla para cerrar la ventana de consola y detener la depuración.

Uso de la configuración de compilación de versión


Una vez que ha probado la versión de depuración de la aplicación, también debe compilar y probar la versión de
lanzamiento. La versión de lanzamiento incorpora optimizaciones del compilador que en ocasiones afectan
negativamente al comportamiento de una aplicación. Por ejemplo, las optimizaciones del compilador que están
diseñadas para mejorar el rendimiento pueden crear condiciones de carrera en aplicaciones multiproceso.
Para compilar y probar la versión de lanzamiento de la aplicación de la consola, cambie la configuración de
compilación en la barra de herramientas de Depurar a Versión .

Cuando presiona F5 o elije Compilar solución en el menú Compilar , Visual Studio compila la versión de
lanzamiento de la aplicación. Puede probarla como hizo con la versión de depuración.

Pasos siguientes
En este tutorial, ha usado las herramientas de depuración de Visual Studio. En el siguiente tutorial, publicará una
versión de la aplicación que se puede implementar.
Publicación de una aplicación de consola de .NET Core con Visual Studio
Tutorial: Publicación de una aplicación de consola de
.NET Core con Visual Studio
16/09/2020 • 5 minutes to read • Edit Online

En este tutorial se muestra cómo publicar una aplicación de consola para que otros usuarios puedan ejecutarla. La
publicación crea el conjunto de archivos que se necesitan para ejecutar la aplicación. Para implementar los
archivos, cópielos en el equipo de destino.

Requisitos previos
Este tutorial funciona con la aplicación de consola que se crea en Creación de una aplicación de consola de
.NET Core con Visual Studio.

Publicar la aplicación
1. Inicie Visual Studio.
2. Abra el proyecto HelloWorld que creó en Creación de una aplicación de consola de .NET Core con
Visual Studio.
3. Asegúrese de que Visual Studio usa la configuración de compilación de versión. Si es necesario, cambie la
configuración de compilación en la barra de herramientas de Depurar a Versión .

4. Haga clic con el botón derecho en el proyecto HelloWorld (no en la solución HelloWorld) y seleccione
Publicar en el menú.

5. En la pestaña Destino de la página Publicar , seleccione Carpeta y luego Siguiente .


6. En la pestaña Ubicación de la página Publicar , seleccione Finalizar .

7. En la pestaña Publicar de la ventana Publicar , seleccione Publicar .


Inspección de los archivos
De forma predeterminada, el proceso de publicación crea una implementación dependiente del marco, que es un
tipo de implementación donde la aplicación publicada se ejecuta en una máquina que tenga instalado .NET Core
Runtime. Los usuarios pueden ejecutar la aplicación publicada haciendo doble clic en el archivo ejecutable o
emitiendo el comando dotnet HelloWorld.dll desde un símbolo del sistema.
En los pasos siguientes, examinará los archivos creados por el proceso de publicación.
1. En el Explorador de soluciones , elija Mostrar todos los archivos .
2. En la carpeta del proyecto, expanda bin/Release/netcoreapp3.1/publish.
Como se muestra en la imagen, el resultado publicado incluye los siguientes archivos:
HelloWorld.deps.json
Este es el archivo de dependencias en tiempo de ejecución de la aplicación. Define los componentes y
las bibliotecas de .NET Core (incluida la biblioteca de vínculos dinámicos que contiene la aplicación)
necesarios para ejecutar la aplicación. Para obtener más información, consulte Archivos de
configuración en tiempo de ejecución.
HelloWorld.dll
Esta es la versión de implementación dependiente del marco de la aplicación. Para ejecutar esta
biblioteca de vínculos dinámicos, escriba dotnet HelloWorld.dll en un símbolo del sistema. Este
método de ejecución de la aplicación funciona en cualquier plataforma que tenga instalado .NET
Core Runtime.
HelloWorld.exe
Esta es la versión del ejecutable dependiente del marco de la aplicación. Para ejecutarlo, escriba
HelloWorld.exe en un símbolo del sistema. El archivo es específico del sistema operativo.

HelloWorld.pdb (opcional para la implementación)


Este es el archivo de símbolos de depuración. No necesita implementar este archivo junto con su
aplicación, aunque se debe guardar en caso de que necesite depurar la versión publicada de la
aplicación.
HelloWorld.runtimeconfig.json
Este es el archivo de configuración en tiempo de ejecución de la aplicación. Identifica la versión de
.NET Core en la que se ha compilado la aplicación para ejecutarse. También puede agregarle opciones
de configuración. Para obtener más información, vea Opciones de configuración en tiempo de
ejecución de .NET Core.

Ejecutar la aplicación publicada


1. En el Explorador de soluciones , haga clic con el botón derecho en la carpeta Publicar y seleccione
Copiar ruta de acceso completa .
2. Abra un símbolo del sistema y vaya a la carpeta Publicar. Para ello, escriba cd y pegue la ruta de acceso
completa. Por ejemplo:

cd C:\Projects\HelloWorld\bin\Release\netcoreapp3.1\publish\

3. Ejecute la aplicación con el archivo ejecutable:


a. Escriba HelloWorld.exe y presione ENTRAR.
b. Escriba un nombre cuando se le pida y presione cualquier tecla para salir.
4. Ejecute la aplicación mediante el comando dotnet :
a. Escriba dotnet HelloWorld.dll y presione ENTRAR.
b. Escriba un nombre cuando se le pida y presione cualquier tecla para salir.

Recursos adicionales
Implementación de aplicaciones .NET Core

Pasos siguientes
En este tutorial, ha publicado una aplicación de consola. En el siguiente tutorial, creará una biblioteca de clases.
Creación de una biblioteca .NET Standard con Visual Studio
Tutorial: Creación de una biblioteca .NET Standard
con Visual Studio
16/09/2020 • 11 minutes to read • Edit Online

En este tutorial, creará una sencilla clases de utilidades que contiene un único método de control de cadenas.
Una biblioteca de clases define los tipos y los métodos que se llaman desde una aplicación. Una biblioteca de
clases que tiene como destino .NET Standard 2.0, lo que permite que cualquier implementación .NET que admita
esa versión de .NET Standard pueda llamar a su biblioteca.
Al terminar con la biblioteca de clases, puede distribuirla como un paquete NuGet o un componente
empaquetado con la aplicación que lo utiliza.

Requisitos previos
Visual Studio 2019, versión 16.6 o posterior con la carga de trabajo Desarrollo multiplataforma de
.NET Core instalada. El SDK de .NET Core 3.1 se instala automáticamente al seleccionar esta carga de trabajo.

Crear una solución


Empiece por crear una solución en blanco para colocar el proyecto de biblioteca de clases en ella. Una solución
de Visual Studio sirve como contenedor de uno o varios proyectos. Agregará otros proyectos relacionados a la
misma solución.
Para crear la solución en blanco:
1. Inicie Visual Studio.
2. En la ventana de inicio, elija Crear un proyecto nuevo .
3. En el cuadro de búsqueda de la página Crear un nuevo proyecto , escriba solución . Elija la plantilla
Solución en blanco y luego seleccione Siguiente .
4. En la página Configure el nuevo proyecto , escriba ClassLibrar yProjects en el cuadro Nombre del
proyecto . Luego, elija Crear .

Crear un proyecto de biblioteca de clases


1. Agregue un nuevo proyecto de biblioteca de clases de .NET Standard denominado "StringLibrary" a la
solución.
a. Haga clic con el botón derecho en la solución en el Explorador de soluciones y seleccione
Agregar > Nuevo proyecto .
b. En el cuadro de búsqueda de la página Agregar un nuevo proyecto , escriba biblioteca . En la
lista de lenguajes, elija C# o Visual Basic y, luego, en la lista de plataformas, elija Todas las
plataformas . Elija la plantilla Biblioteca de clases (.NET Standard) y, luego, siguiente .
c. En la página Configure el nuevo proyecto , escriba StringLibrar y en el cuadro Nombre del
proyecto . Luego, elija Crear .
2. Asegúrese de que la biblioteca tiene como destino la versión correcta de .NET Standard. Haga clic con el
botón derecho en el proyecto de biblioteca en el Explorador de soluciones y, luego, seleccione
Propiedades . El cuadro de texto Plataforma de destino muestra que el proyecto tiene como destino
.NET Standard 2.0.
3. Si usa Visual Basic, borre el texto del cuadro de texto Espacio de nombres raíz .

En cada proyecto, Visual Basic crea automáticamente un espacio de nombres que corresponde al nombre
del proyecto. En este tutorial, definirá un espacio de nombres de nivel superior mediante la palabra clave
namespace en el archivo de código.

4. Reemplace el código de la ventana de código por Class1.cs o Class1.vb y guarde el archivo. Si no se


muestra el idioma que quiere usar, cambie el selector de idioma en la parte superior de la página.
using System;

namespace UtilityLibraries
{
public static class StringLibrary
{
public static bool StartsWithUpper(this string str)
{
if (string.IsNullOrWhiteSpace(str))
return false;

char ch = str[0];
return char.IsUpper(ch);
}
}
}

Imports System.Runtime.CompilerServices

Namespace UtilityLibraries
Public Module StringLibrary
<Extension>
Public Function StartsWithUpper(str As String) As Boolean
If String.IsNullOrWhiteSpace(str) Then
Return False
End If

Dim ch As Char = str(0)


Return Char.IsUpper(ch)
End Function
End Module
End Namespace

La biblioteca de clases, UtilityLibraries.StringLibrary , contiene un método denominado


StartsWithUpper . Este método devuelve un valor Boolean que indica si la instancia de cadena actual
comienza con un carácter en mayúscula. El estándar Unicode distingue caracteres en mayúsculas de
caracteres en minúsculas. El método Char.IsUpper(Char) devuelve true si un carácter está en mayúsculas.
se implementa como un método de extensión, de modo que se pueda llamar como si
StartsWithUpper
fuera un miembro de la clase String.
5. En la barra de menú, seleccione Compilar > Compilar solución , o bien presione Ctrl+Mayús+B, para
comprobar que el proyecto se compila sin errores.

Incorporación de una aplicación de consola a la solución


Agregue una aplicación de consola que use la biblioteca de clases. La aplicación solicitará al usuario que escriba
una cadena y notificará si la cadena comienza con un carácter en mayúsculas.
1. Agregue una nueva aplicación de consola de .NET Core denominada "Showcase" a la solución.
a. Haga clic con el botón derecho en la solución en el Explorador de soluciones y seleccione
Agregar > Nuevo proyecto .
b. En el cuadro de búsqueda de la página Agregar un nuevo proyecto , escriba consola . Elija C# o
Visual Basic en la lista de lenguajes y luego, Todas las plataformas en la lista de plataformas.
c. Elija la plantilla Aplicación de consola (.NET Core) y haga clic en Siguiente .
d. En la página Configure el nuevo proyecto , escriba ShowCase en el cuadro Nombre del
proyecto . Luego, elija Crear .
2. En la ventana de código del archivo Program.cs o Program.vb, reemplace todo el código con el siguiente
código.

using System;
using UtilityLibraries;

class Program
{
static void Main(string[] args)
{
int row = 0;

do
{
if (row == 0 || row >= 25)
ResetConsole();

string input = Console.ReadLine();


if (string.IsNullOrEmpty(input)) break;
Console.WriteLine($"Input: {input} {"Begins with uppercase? ",30}: " +
$"{(input.StartsWithUpper() ? "Yes" : "No")}\n");
row += 3;
} while (true);
return;

// Declare a ResetConsole local method


void ResetConsole()
{
if (row > 0)
{
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
Console.Clear();
Console.WriteLine("\nPress <Enter> only to exit; otherwise, enter a string and press
<Enter>:\n");
row = 3;
}
}
}
Imports UtilityLibraries

Module Program
Dim row As Integer = 0

Sub Main()
Do
If row = 0 OrElse row >= 25 Then ResetConsole()

Dim input As String = Console.ReadLine()


If String.IsNullOrEmpty(input) Then Return

Console.WriteLine($"Input: {input} {"Begins with uppercase? ",30}: " +


$"{If(input.StartsWithUpper(), "Yes", "No")} {vbCrLf}")
row += 3
Loop While True
End Sub

Private Sub ResetConsole()


If row > 0 Then
Console.WriteLine("Press any key to continue...")
Console.ReadKey()
End If
Console.Clear()
Console.WriteLine("{0}Press <Enter> only to exit; otherwise, enter a string and press <Enter>:
{0}",
vbCrLf)
row = 3
End Sub
End Module

El código usa la variable row para mantener un recuento del número de filas de datos escritas en la
ventana de consola. Siempre que sea mayor o igual a 25, el código borra la ventana de consola y muestra
un mensaje al usuario.
El programa le pide al usuario que escriba una cadena. Indica si la cadena comienza con un carácter en
mayúsculas. Si el usuario presiona la tecla Entrar sin especificar una cadena, la aplicación finaliza y la
ventana de la consola se cierra.

Agregar una referencia de proyecto


En un principio, el nuevo proyecto de aplicación de consola no tiene acceso a la biblioteca de clases. Para que
pueda llamar a los métodos de la biblioteca de clases, cree una referencia de proyecto al proyecto de biblioteca
de clases.
1. En el Explorador de soluciones , haga clic con el botón derecho en el nodo Dependencias del proyecto
ShowCase y seleccione Agregar referencia de proyecto .
2. En el cuadro de diálogo Administrador de referencias , seleccione el proyecto StringLibrar y y
después Aceptar .

Ejecutar la aplicación
1. En el Explorador de soluciones , haga clic con el botón derecho en el proyecto Presentación y
seleccione Establecer como proyecto de inicio en el menú contextual.
2. Presione CTRL+F5 para compilar y ejecutar el programa sin depuración.

3. Para probar el programa, escriba cadenas y presione Entrar y, después, presione Entrar para salir.

Recursos adicionales
Desarrollo de bibliotecas con la CLI de .NET Core
Versiones de .NET Standard y las plataformas que admiten.

Pasos siguientes
En este tutorial, ha creado una prueba unitaria de una biblioteca de clases. En el siguiente tutorial, aprenderá a
hacer una prueba unitaria de la biblioteca de clases.
Prueba unitaria a una biblioteca .NET Standard con Visual Studio
También puede omitir las pruebas unitarias automatizadas y aprender a compartir la biblioteca mediante la
creación de un paquete NuGet:
Crear y publicar un paquete con Visual Studio
También puede obtener más información sobre cómo publicar una aplicación de consola. Si publica la aplicación
de consola a partir de la solución que ha creado en este tutorial, la biblioteca de clases lo incluirá como un
archivo .dll.
Publicación de una aplicación de consola de .NET Core con Visual Studio
Tutorial: Prueba de una biblioteca de clases .NET
Standard con .NET Core mediante Visual Studio
16/09/2020 • 16 minutes to read • Edit Online

En este tutorial se muestra cómo automatizar las pruebas unitarias mediante la adición de un proyecto de prueba a
una solución.

Requisitos previos
Este tutorial funciona con la solución que se crea en Creación de una biblioteca de .NET Standard con
Visual Studio.

Crear un proyecto de prueba unitaria


Las pruebas unitarias proporcionan pruebas de software automatizadas durante el desarrollo y la publicación.
MSTest es uno de los tres marcos de pruebas que puede elegir. Los otros son xUnit y nUnit.
1. Inicie Visual Studio.
2. Abra la solución ClassLibraryProjects que creó en Creación de una biblioteca de .NET Standard con
Visual Studio.
3. Agregue un nuevo proyecto de prueba unitaria denominado "StringLibraryTest" a la solución.
a. Haga clic con el botón derecho en la solución en el Explorador de soluciones y seleccione
Agregar > Nuevo proyecto .
b. En el cuadro de búsqueda de la página Agregar un nuevo proyecto , escriba mstest . En la lista de
lenguajes, elija C# o Visual Basic y, luego, en la lista de plataformas, elija Todas las plataformas .
c. Elija la plantilla MSTest Test Project (.NET Core) [Proyecto de prueba de MSTest (.NET Core)] y,
luego, elija Siguiente .
d. En la página Configurar el nuevo proyecto , escriba StringLibrar yTest en el cuadro Nombre del
proyecto . Luego, elija Crear .
4. Visual Studio crea el proyecto y abre el archivo de clase en la ventana de código con el siguiente código. Si
no se muestra el idioma que quiere usar, cambie el selector de idioma en la parte superior de la página.

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace StringLibraryTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
}
}
}
Imports Microsoft.VisualStudio.TestTools.UnitTesting

Namespace StringLibraryTest
<TestClass>
Public Class UnitTest1
<TestMethod>
Sub TestSub()

End Sub
End Class
End Namespace

El código fuente creado por la plantilla de prueba unitaria hace lo siguiente:


Importa el espacio de nombres de Microsoft.VisualStudio.TestTools.UnitTesting, que contiene los tipos
utilizados para las pruebas unitarias.
Aplica el atributo TestClassAttribute a la clase UnitTest1 .
Aplica el atributo TestMethodAttribute para definir TestMethod1 en C# o TestSub en Visual Basic.
Cada método etiquetado con [TestMethod] en una clase de prueba etiquetada con [TestClass] se ejecuta
automáticamente cuando se ejecuta la prueba unitaria.

Agregar una referencia de proyecto


Para que el proyecto de prueba funcione con la clase StringLibrary , agregue una referencia del proyecto
StringLibrar yTest al proyecto StringLibrary .
1. En el Explorador de soluciones , haga clic con el botón derecho en el nodo Dependencias del proyecto
StringLibrar yTest y seleccione Agregar referencia de proyecto del menú contextual.
2. En el cuadro de diálogo Administrador de referencias , expanda el nodo Proyectos y active la casilla
junto a StringLibrar y . Al agregar una referencia al ensamblado StringLibrary , el compilador puede
encontrar métodos StringLibrar y al compilar el proyecto StringLibrar yTest .
3. Seleccione Aceptar .

Adición y ejecución de métodos de prueba unitaria


Cuando Visual Studio ejecuta una prueba unitaria, ejecuta cada método marcado con el atributo
TestMethodAttribute en una clase que está marcada con el atributo TestClassAttribute. Un método de prueba
finaliza cuando se encuentra el primer error, o cuando todas las pruebas contenidas en el método se han realizado
correctamente.
Las pruebas más comunes llaman a los miembros de la clase Assert. Muchos métodos de aserción incluyen al
menos dos parámetros, uno de los cuales es el resultado esperado de la prueba y el otro es el resultado real de la
prueba. Algunos de los métodos Assert a los que se llama con más frecuencia se muestran en la tabla siguiente:

M ÉTO DO S DE A SERC IÓ N F UN C IÓ N

Assert.AreEqual Comprueba que dos valores u objetos son iguales. La aserción


da error si los valores o los objetos no son iguales.

Assert.AreSame Comprueba que dos variables de objeto hacen referencia al


mismo objeto. La aserción da error si las variables hacen
referencia a objetos diferentes.
M ÉTO DO S DE A SERC IÓ N F UN C IÓ N

Assert.IsFalse Comprueba si una condición es false . La aserción produce


un error si la condición es true .

Assert.IsNotNull Comprueba que un objeto no es null . La aserción da error


si el objeto es null .

También puede usar el método Assert.ThrowsException en un método de prueba para indicar el tipo de excepción
que se espera que produzca. La prueba dará error si no se inicia la excepción especificada.
Al probar el método StringLibrary.StartsWithUpper , quiere proporcionar un número de cadenas que comiencen
con un carácter en mayúsculas. Espera que el método devuelva true en estos casos, por lo que puede llamar al
método Assert.IsTrue. Del mismo modo, quiere proporcionar un número de cadenas que comiencen con algo que
no sea un carácter en mayúsculas. Espera que el método devuelva false en estos casos, por lo que puede llamar
al método Assert.IsFalse.
Dado que el método de biblioteca administra cadenas, quiere asegurarse también de que administra
correctamente una cadena vacía ( String.Empty ), una cadena válida que no tenga caracteres y cuyo Length sea 0, y
una cadena null que no se haya inicializado. Se puede llamar a StartsWithUpper directamente como un método
estático y pasar un argumento String único. También puede llamar a StartsWithUpper como método de extensión
en una variable de string asignada a null .
Definirá tres métodos, cada uno de los cuales llama a un método Assert para cada elemento de una matriz de
cadenas. Llamará a una sobrecarga de método que le permite especificar que se muestre un mensaje de error en
caso de error en la prueba. El mensaje identifica la cadena que causó el error.
Para crear los métodos de prueba:
1. En la ventana de código UnitTest1.cs o UnitTest1.vb, reemplace el código por el siguiente:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UtilityLibraries;

namespace StringLibraryTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestStartsWithUpper()
{
// Tests that we expect to return true.
string[] words = { "Alphabet", "Zebra", "ABC", "Αθήνα", "Москва" };
foreach (var word in words)
{
bool result = word.StartsWithUpper();
Assert.IsTrue(result,
String.Format("Expected for '{0}': true; Actual: {1}",
word, result));
}
}

[TestMethod]
public void TestDoesNotStartWithUpper()
{
// Tests that we expect to return false.
string[] words = { "alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
"1234", ".", ";", " " };
foreach (var word in words)
{
bool result = word.StartsWithUpper();
Assert.IsFalse(result,
String.Format("Expected for '{0}': false; Actual: {1}",
word, result));
}
}

[TestMethod]
public void DirectCallWithNullOrEmpty()
{
// Tests that we expect to return false.
string[] words = { string.Empty, null };
foreach (var word in words)
{
bool result = StringLibrary.StartsWithUpper(word);
Assert.IsFalse(result,
String.Format("Expected for '{0}': false; Actual: {1}",
word == null ? "<null>" : word, result));
}
}
}
}
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports UtilityLibraries

Namespace StringLibraryTest
<TestClass>
Public Class UnitTest1
<TestMethod>
Public Sub TestStartsWithUpper()
' Tests that we expect to return true.
Dim words() As String = {"Alphabet", "Zebra", "ABC", "Αθήνα", "Москва"}
For Each word In words
Dim result As Boolean = word.StartsWithUpper()
Assert.IsTrue(result,
$"Expected for '{word}': true; Actual: {result}")
Next
End Sub

<TestMethod>
Public Sub TestDoesNotStartWithUpper()
' Tests that we expect to return false.
Dim words() As String = {"alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
"1234", ".", ";", " "}
For Each word In words
Dim result As Boolean = word.StartsWithUpper()
Assert.IsFalse(result,
$"Expected for '{word}': false; Actual: {result}")
Next
End Sub

<TestMethod>
Public Sub DirectCallWithNullOrEmpty()
' Tests that we expect to return false.
Dim words() As String = {String.Empty, Nothing}
For Each word In words
Dim result As Boolean = StringLibrary.StartsWithUpper(word)
Assert.IsFalse(result,
$"Expected for '{If(word Is Nothing, "<null>", word)}': false; Actual: {result}")
Next
End Sub
End Class
End Namespace

La prueba de caracteres en mayúsculas en el método TestStartsWithUpper incluye la letra mayúscula griega


alfa (U + 0391) y la letra mayúscula cirílica EM (U + 041C). La prueba de caracteres en minúsculas en el
método TestDoesNotStartWithUpper incluye la letra griega minúscula alfa (U + 03B1) y la letra cirílica
minúscula Ghe (U + 0433).
2. En la barra de menús, seleccione Archivo > Guardar UnitTest1.cs como o Archivo > Guardar
UnitTest1.vb como . En el cuadro de diálogo Guardar archivo como , seleccione la flecha junto al botón
Guardar y seleccione Guardar con codificación .
3. En el cuadro de diálogo Confirmar guardar como , seleccione el botón Sí para guardar el archivo.
4. En el cuadro de diálogo Opciones avanzadas para guardar , seleccione Unicode (UTF-8 con firma) -
Página de códigos 65001 desde la lista desplegable Codificación y seleccione Aceptar .

Si obtiene un error al guardar el código fuente en un archivo con codificación UTF-8, Visual Studio puede
guardarlo como un archivo ASCII. Cuando eso suceda, el tiempo de ejecución no descodifica correctamente
los caracteres UTF-8 del rango ASCII, y los resultados de la prueba no serán correctos.
5. En la barra de menús, seleccione Prueba > Ejecutar todas las pruebas . Si no se abre la ventana
Explorador de pruebas , ábrala mediante Prueba > Explorador de pruebas . Las tres pruebas se
muestran en la sección Pruebas superadas y en la sección Resumen se informa del resultado de la serie
de pruebas.
Administración de errores de prueba
Si está realizando el desarrollo controlado por pruebas (TDD), escribirá las pruebas, y estas generarán un error
cuando las ejecute por primera vez. Después, agregará un código a la aplicación para que la prueba se realice
correctamente. En este tutorial, ha creado la prueba después de escribir el código de la aplicación que valida, por lo
que no ha podido comprobar si la prueba genera un error. Para asegurarse de que una prueba genera un error
cuando espera que lo haga, agregue un valor no válido a la entrada de prueba.
1. Modifique la matriz words en el método TestDoesNotStartWithUpper para incluir la cadena "Error". No
necesita guardar el archivo porque Visual Studio guarda automáticamente archivos abiertos cuando se crea
una solución para ejecutar pruebas.

string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",


"1234", ".", ";", " " };

Dim words() As String = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",


"1234", ".", ";", " " }

2. Ejecute la prueba seleccionando Prueba > Ejecutar todas las pruebas de la barra de menús. En la
ventana Explorador de pruebas se indica que dos pruebas se han realizado correctamente y que una ha
finalizado con errores.
3. Seleccione la prueba con errores, TestDoesNotStartWith .
En la ventana Explorador de pruebas se muestra el mensaje generado por la instrucción Assert: "Error de
Assert.IsFalse. Se esperaba para "Error": false; real: True". Debido al error, no se probaron todas las cadenas
de la matriz después de "Error".

4. Quite la cadena "Error" que agregó en el paso 1. Vuelva a ejecutar la prueba y se superarán las pruebas.

Prueba de la versión de la biblioteca


Ahora que se han superado todas las pruebas al ejecutar la versión de depuración de la biblioteca, ejecute las
pruebas una vez más con la versión de lanzamiento de la biblioteca. Varios factores, como las optimizaciones del
compilador, a veces pueden producir un comportamiento diferente entre las compilaciones de depuración y
versión.
Para probar la compilación de versión:
1. En la barra de herramientas de Visual Studio, cambie la configuración de compilación de Depurar a
Versión .

2. En el Explorador de soluciones , haga clic con el botón derecho en el proyecto StringLibrar y y


seleccione Compilar desde el menú contextual para volver a compilar la biblioteca.

3. Ejecute las pruebas unitarias mediante Ejecutar prueba > Todas las pruebas de la barra de menús. Las
pruebas se superan.

Recursos adicionales
Conceptos básicos de las pruebas unitarias en Visual Studio
Pruebas unitarias en .NET Core y .NET Standard

Pasos siguientes
En este tutorial, ha realizado una prueba unitaria de una biblioteca de clases. Puede poner la biblioteca a
disposición de otros usuarios si la publica en NuGet como un paquete. Para obtener información sobre cómo
hacerlo, realice este tutorial de NuGet:
Creación y publicación de un paquete NuGet con Visual Studio
Si publica una biblioteca como un paquete NuGet, otros usuarios pueden instalarla y usarla. Para obtener
información sobre cómo hacerlo, realice este tutorial de NuGet:
Instalación y uso de un paquete en Visual Studio
No es necesario distribuir una biblioteca como un paquete. Se puede agrupar con una aplicación de consola que la
use. Para aprender a publicar una aplicación de consola, vea el tutorial anterior de esta serie:
Publicación de una aplicación de consola de .NET Core con Visual Studio
Tutorial: Depuración de una aplicación de consola
de .NET Core con Visual Studio Code
16/09/2020 • 6 minutes to read • Edit Online

En este tutorial se explica cómo crear y ejecutar una aplicación de consola de .NET Core mediante
Visual Studio Code y la CLI de .NET Core. Las tareas de proyecto, como crear, compilar y ejecutar un proyecto,
se realizan mediante la CLI de .NET Core. Puede seguir este tutorial con un editor de código diferente y
ejecutar comandos en un terminal si lo prefiere.

Requisitos previos
1. Visual Studio Code con la extensión de C# instalada. Para saber cómo instalar extensiones en
Visual Studio Code, vea el Marketplace de extensiones de VS Code.
2. SDK de .NET Core 3.1 o posterior

Creación de la aplicación
Cree un proyecto de aplicación de consola de .NET Core denominado "HelloWorld".
1. Inicie Visual Studio Code.
2. Seleccione Archivo > Abrir carpeta (Archivo > Abrir... en macOS) en el menú principal.
3. En el cuadro de diálogo Abrir carpeta , cree una carpeta de HelloWorld y haga clic en Seleccionar
carpeta (Abrir en macOS).
De forma predeterminada, el nombre de la carpeta se convierte en el nombre del proyecto y del
espacio de nombres. Más adelante en el tutorial, agregará código que supone que el espacio de
nombres del proyecto es HelloWorld .
4. Para abrir el Terminal en Visual Studio Code, seleccione Ver > Terminal en el menú principal.
Se abre el Terminal con el símbolo del sistema en la carpeta HelloWorld.
5. En el Terminal , escriba este comando:

dotnet new console

La plantilla crea una aplicación "Hola mundo" sencilla. Llama al método Console.WriteLine(String) para
mostrar "Hello World!" en la ventana de la consola.
El código de plantilla define una clase, Program , con un solo método, Main , que toma una matriz de String
como argumento:
using System;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

Main es el punto de entrada de la aplicación, el método que se llama automáticamente mediante el tiempo
de ejecución cuando inicia la aplicación. Los argumentos de línea de comandos proporcionados cuando se
inicia la aplicación están disponibles en la matriz args.

Ejecutar la aplicación
Ejecute este comando en el Terminal :

dotnet run

El programa muestra "Hola mundo" y finaliza.

Mejora de la aplicación
Mejore la aplicación para pedir su nombre al usuario y mostrarlo con la fecha y la hora.
1. Haga clic en el archivo Program.cs para abrirlo.
La primera vez que se abre un archivo de C# en Visual Studio Code, se carga OmniSharp en el editor.
2. Seleccione Sí cuando Visual Studio Code le pida que agregue los recursos que faltan para compilar y
depurar la aplicación.

3. Reemplace el contenido del método Main en Program.cs, que es la línea que llama a
Console.WriteLine , por este código:

Console.WriteLine("\nWhat is your name? ");


var name = Console.ReadLine();
var date = DateTime.Now;
Console.WriteLine($"\nHello, {name}, on {date:d} at {date:t}!");
Console.Write("\nPress any key to exit...");
Console.ReadKey(true);

Este código muestra un mensaje en la ventana de la consola y espera a que el usuario escriba una
cadena y, luego, presione Entrar. Almacena esta cadena en una variable denominada name . También
recupera el valor de la propiedad DateTime.Now, que contiene la hora local actual, y lo asigna a una
variable denominada date . Asimismo, muestra estos valores en la ventana de la consola. Por último,
muestra un mensaje en la ventana de la consola y llama al método Console.ReadKey(Boolean) para
esperar a la entrada del usuario.
El símbolo \n representa un carácter de nueva línea.
El signo de dólar ( $ ) delante de una cadena permite colocar expresiones como nombres de variable
entre llaves en la cadena. El valor de la expresión se inserta en la cadena en lugar de la expresión. Esta
sintaxis se conoce como cadenas interpoladas.
4. Guarde los cambios.
IMPORTANT
En Visual Studio Code, tiene que guardar los cambios explícitamente. A diferencia de Visual Studio, los cambios
de los archivos no se guardan automáticamente al compilar y ejecutar una aplicación.

5. Ejecute el programa otra vez:

dotnet run

6. Responda a la solicitud escribiendo un nombre y presionando la tecla Entrar.

7. Presione cualquier tecla para salir de la aplicación.

Recursos adicionales
Setting up Visual Studio Code (Configuración de Visual Studio Code)

Pasos siguientes
En este tutorial, ha creado una aplicación de consola de .NET Core. En el siguiente tutorial, depurará la
aplicación.
Depuración de una aplicación de consola de .NET Core con Visual Studio Code
Tutorial: Depuración de una aplicación de consola de
.NET Core con Visual Studio Code
16/09/2020 • 14 minutes to read • Edit Online

En este tutorial se presentan las herramientas de depuración disponibles en Visual Studio Code para trabajar con
aplicaciones de .NET Core.

Requisitos previos
Este tutorial funciona con la aplicación de consola que se crea en Creación de una aplicación de consola de
.NET Core con Visual Studio Code.

Uso de la configuración de compilación de depuración


Depuración y Versión son las configuraciones de compilación integradas de .NET Core. Use la configuración de
compilación Depuración para depurar y la configuración de compilación Versión para la distribución final de la
versión.
En la configuración de depuración, el programa se compila sin optimizar y con toda la información de depuración
simbólica. La optimización complica la depuración, ya que la relación entre el código fuente y las instrucciones
generadas es más compleja. La configuración de versión del programa no contiene información de depuración
simbólica y está totalmente optimizada.
De forma predeterminada, la configuración de lanzamiento de Visual Studio Code usa la configuración de
compilación Depuración, por lo que no es necesario cambiarla antes de realizar esta operación.
1. Inicie Visual Studio Code.
2. Abra la carpeta del proyecto que ha creado en Creación de una aplicación de consola de .NET Core con
Visual Studio Code.

Establecer un punto de interrupción


Un punto de interrupción interrumpe temporalmente la ejecución de la aplicación antes de que se ejecute la línea
con el punto de interrupción.
1. Abra el archivo Program.cs.
2. Establezca un punto de interrupción en la línea que muestre el nombre, la fecha y la hora; para ello, haga clic
en el margen izquierdo de la ventana de código. El margen izquierdo está a la izquierda de los números de
línea. Otras maneras de establecer un punto de interrupción consisten en presionar F9 o seleccionar
Ejecutar > Alternar punto de interrupción en el menú mientras se elige la línea de código.
Visual Studio Code marca la línea donde se establece el punto de interrupción con un punto rojo en el
margen izquierdo.
Configuración para la entrada de terminal
El punto de interrupción se encuentra después de una llamada al método Console.ReadLine . La Consola de
depuración no acepta la entrada de terminal para un programa en ejecución. Para controlar la entrada de
terminal durante la depuración, puede usar el terminal integrado (una de las ventanas de Visual Studio Code) o un
terminal externo. En este tutorial, usará el terminal integrado.
1. Abra .vscode/launch.json.
2. Cambie la opción console a integratedTerminal .
De:

"console": "internalConsole",

A:

"console": "integratedTerminal",

3. Guarde los cambios.

Iniciar depuración
1. Abra la vista Depurar mediante el icono de depuración que hay en el menú de la izquierda.
2. Seleccione la flecha verde en la parte superior del panel, junto a .NET Core Launch (console) (Inicio de
.NET Core [consola]). Otras maneras de iniciar el programa en modo de depuración son presionar F5 o
elegir Ejecutar > Iniciar depuración en el menú.

3. Seleccione la pestaña Terminal para ver el mensaje "What is your name?" que muestra el programa antes
de esperar una respuesta.

4. Escriba una cadena en la ventana Terminal para responder a la pregunta del nombre y presione Entrar.
La ejecución del programa se detiene cuando llega al punto de interrupción y antes de que se ejecute el
método Console.WriteLine . La sección Variables locales de la ventana Variables muestra los valores de
las variables definidas en el método que se ejecuta actualmente.

Uso de la consola de depuración


La ventana Consola de depuración permite interactuar con la aplicación que está depurando. Puede cambiar el
valor de las variables para ver cómo afecta esto a su programa.
1. Seleccione la pestaña Consola de depuración .
2. Escriba name = "Gracie" en el símbolo del sistema de la parte inferior de la ventana Consola de
depuración y presione la tecla Entrar.

3. Escriba date = DateTime.Parse("2019-11-16T17:25:00Z").ToUniversalTime() en la parte inferior de la ventana


Consola de depuración y presione la tecla Entrar.
La ventana Variables muestra los nuevos valores de las variables name y date .
4. Para continuar con la ejecución del programa, seleccione el botón Continuar en la barra de herramientas.
Otra manera de continuar es presionar F5.

5. Seleccione de nuevo la pestaña Terminal .


Los valores mostrados en la ventana de la consola corresponden a los cambios realizados en Consola de
depuración .

6. Presione cualquier tecla para salir de la aplicación y detenga la depuración.

Establecimiento de un punto de interrupción condicional


El programa muestra la cadena que escribe el usuario. ¿Qué sucede si el usuario no escribe nada? Puede probarlo
con una característica de depuración muy útil denominada Punto de interrupción condicional.
1. Haga clic con el botón derecho (o presione Ctrl y haga clic en macOS) sobre el punto rojo que representa
al punto de interrupción. En el menú contextual, seleccione Editar punto de interrupción y se abrirá un
cuadro de diálogo donde podrá escribir una expresión condicional.

2. Seleccione Expression en el menú desplegable, escriba esta expresión condicional y presione Entrar.

String.IsNullOrEmpty(name)
Cada vez que se alcanza el punto de interrupción, el depurador llama al método
String.IsNullOrEmpty(name) y se interrumpe en esta línea solo si la llamada al método devuelve true .
En lugar de una expresión condicional, puede especificar un número de llamadas, que interrumpe la
ejecución del programa antes de que se ejecute una instrucción un número de veces especificado. Otra
opción consiste en especificar una condición de filtro, que interrumpe la ejecución del programa en función
de atributos tales como un identificador de subproceso, un nombre de proceso o un nombre de subproceso.
3. Inicie el programa con la depuración presionando F5.
4. En la ventana Terminal, cuando se le pida que escriba su nombre, presione la tecla Entrar .
Como se ha cumplido la condición que especificó ( name es null o String.Empty), la ejecución del
programa se detiene cuando se alcanza el punto de interrupción y antes de que se ejecute el método
Console.WriteLine .

La ventana Variables muestra que el valor de la variable name es "" o String.Empty.


5. Confirme que el valor es una cadena vacía; para ello, escriba esta instrucción en la ventana Consola de
depuración y presionando Entrar. El resultado es true .

name == String.Empty
6. Seleccione el botón Continuar en la barra de herramientas para continuar la ejecución del programa.
7. Seleccione la pestaña Terminal y presione cualquier tecla para salir del programa y detener la depuración.
8. Para borrar el punto de interrupción, haga clic en el punto en el margen izquierdo de la ventana de código.
Otras formas de borrar un punto de interrupción consisten en presionar F9 o elegir Ejecutar > Alternar
punto de interrupción en el menú mientras se selecciona la línea de código.
9. Si recibe una advertencia que indica que se perderá la condición del punto de interrupción, seleccione
Quitar punto de interrupción .

Ejecución paso a paso de un programa


Visual Studio Code también permite recorrer línea a línea un programa y supervisar su ejecución. Normalmente,
establecería un punto de interrupción y seguiría el flujo del programa mediante una pequeña parte de su código
de programa. Como este programa es pequeño, puede ejecutar paso a paso el programa entero.
1. Establezca un punto de interrupción en la llave de apertura del método Main .
2. Presiona F5 para iniciar la depuración.
Visual Studio Code resalta la línea del punto de interrupción.
En este punto, la ventana Variables muestra que la matriz args está vacía, y name y date tienen valores
predeterminados.
3. Seleccione Ejecutar > Paso a paso por instrucciones o presione F11.

Visual Studio Code resalta la línea siguiente.


4. Seleccione Ejecutar > Paso a paso por instrucciones o presione F11.
Visual Studio Code ejecuta Console.WriteLine para el mensaje de nombre y resalta la siguiente línea de
ejecución. La línea siguiente es Console.ReadLine para name . La ventana Variables no cambia y la pestaña
Terminal muestra el mensaje "What is your name?" .
5. Seleccione Ejecutar > Paso a paso por instrucciones o presione F11.
Visual Studio resalta la asignación de la variable name . La ventana Variables muestra que name todavía es
null .

6. Para responder al mensaje, escriba una cadena en la ventana Terminal y presione Entrar.
Es posible que la pestaña Terminal no muestre la cadena mientras la está escribiendo, pero el método
Console.ReadLine capturará la entrada.
7. Seleccione Ejecutar > Paso a paso por instrucciones o presione F11.
Visual Studio Code resalta la asignación de la variable date . La ventana Variables muestra el valor
devuelto por la llamada al método Console.ReadLine. En la pestaña Terminal se muestra la cadena que
escribió en el símbolo del sistema.
8. Seleccione Ejecutar > Paso a paso por instrucciones o presione F11.
La ventana Variables muestra el valor de la variable date tras la asignación desde la propiedad
DateTime.Now.
9. Seleccione Ejecutar > Paso a paso por instrucciones o presione F11.
Visual Studio Code llama al método Console.WriteLine(String, Object, Object). La ventana de la consola
muestra la cadena con formato.
10. Seleccione Ejecutar > Paso a paso para salir o presione Mayús+F11.

11. Seleccione la pestaña Terminal .


El terminal muestra "Presione cualquier tecla para salir..."
12. Presione cualquier tecla para salir de la aplicación.

Uso de la configuración de compilación de versión


Una vez que ha probado la versión de depuración de la aplicación, también debe compilar y probar la versión de
lanzamiento. La versión de lanzamiento incorpora optimizaciones del compilador que pueden afectar al
comportamiento de una aplicación. Por ejemplo, las optimizaciones del compilador que están diseñadas para
mejorar el rendimiento pueden crear condiciones de carrera en aplicaciones multiproceso.
Para compilar y probar la versión de lanzamiento de la aplicación de consola, abra el Terminal y ejecute este
comando:

dotnet run --configuration Release

Recursos adicionales
Debugging in Visual Studio Code (Depuración en Visual Studio Code)

Pasos siguientes
En este tutorial, ha usado las herramientas de depuración de Visual Studio Code. En el siguiente tutorial, publicará
una versión de la aplicación que se puede implementar.
Publicación de una aplicación de consola de .NET Core con Visual Studio Code
Tutorial: Publicación de una aplicación de consola de
.NET Core con Visual Studio Code
16/09/2020 • 6 minutes to read • Edit Online

En este tutorial se muestra cómo publicar una aplicación de consola para que otros usuarios puedan ejecutarla. La
publicación crea el conjunto de archivos que se necesitan para ejecutar una aplicación. Para implementar los
archivos, cópielos en el equipo de destino.
La CLI de .NET Core se usa para publicar la aplicación, por lo que puede seguir este tutorial con un editor de código
que no sea Visual Studio Code si lo prefiere.

Requisitos previos
Este tutorial funciona con la aplicación de consola que se crea en Creación de una aplicación de consola de
.NET Core con Visual Studio Code.

Publicar la aplicación
1. Inicie Visual Studio Code.
2. Abra la carpeta de proyecto HelloWorld que ha creado en Creación de una aplicación de consola de
.NET Core con Visual Studio Code.
3. Elija Ver > Terminal en el menú principal.
Se abrirá el terminal en la carpeta HelloWorld.
4. Ejecute el siguiente comando:

dotnet publish --configuration Release

La configuración de compilación predeterminada es Depuración, por lo que este comando especifica la


versión de la configuración de compilación Versión. La salida de la configuración de compilación Versión
tiene muy poca información de depuración simbólica y está totalmente optimizada.
La salida del comando es similar al ejemplo siguiente:

Microsoft (R) Build Engine version 16.7.0+b89cb5fde for .NET


Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
All projects are up-to-date for restore.
HelloWorld -> C:\Projects\HelloWorld\bin\Release\netcoreapp3.1\HelloWorld.dll
HelloWorld -> C:\Projects\HelloWorld\bin\Release\netcoreapp3.1\publish\

Inspección de los archivos


De forma predeterminada, el proceso de publicación crea una implementación dependiente del marco, que es un
tipo de implementación donde la aplicación publicada se ejecuta en una máquina que tenga instalado .NET Core
Runtime. Para ejecutar la aplicación publicada, puede usar el archivo ejecutable o ejecutar el comando
dotnet HelloWorld.dll desde un símbolo del sistema.
En los pasos siguientes, examinará los archivos creados por el proceso de publicación.
1. Seleccione Explorador en la barra de navegación izquierda.
2. Expanda bin/Release/netcoreapp3.1/publish.

Como se muestra en la imagen, el resultado publicado incluye los siguientes archivos:


HelloWorld.deps.json
Este es el archivo de dependencias en tiempo de ejecución de la aplicación. Define los componentes y
las bibliotecas de .NET Core (incluida la biblioteca de vínculos dinámicos que contiene la aplicación)
necesarios para ejecutar la aplicación. Para obtener más información, consulte Archivos de
configuración en tiempo de ejecución.
HelloWorld.dll
Esta es la versión de implementación dependiente del marco de la aplicación. Para ejecutar esta
biblioteca de vínculos dinámicos, escriba dotnet HelloWorld.dll en un símbolo del sistema. Este
método de ejecución de la aplicación funciona en cualquier plataforma que tenga instalado .NET Core
Runtime.
HelloWorld.exe (HelloWorld en Linux, no creada en macOS).
Esta es la versión del ejecutable dependiente del marco de la aplicación. El archivo es específico del
sistema operativo.
HelloWorld.pdb (opcional para la implementación)
Este es el archivo de símbolos de depuración. No necesita implementar este archivo junto con su
aplicación, aunque se debe guardar en caso de que necesite depurar la versión publicada de la
aplicación.
HelloWorld.runtimeconfig.json
Este es el archivo de configuración en tiempo de ejecución de la aplicación. Identifica la versión de
.NET Core en la que se ha compilado la aplicación para ejecutarse. También puede agregarle opciones
de configuración. Para obtener más información, vea Opciones de configuración en tiempo de
ejecución de .NET Core.

Ejecutar la aplicación publicada


1. En el Explorador , haga clic con el botón derecho en la carpeta publish (o presione Ctrl y haga clic en
macOS) y seleccione Abrir en terminal .

2. En Windows o Linux, ejecute la aplicación con el archivo ejecutable.


a. En Windows, escriba .\HelloWorld.exe y presione Entrar.
b. En Linux, escriba ./HelloWorld y presione Entrar.
c. Escriba un nombre cuando se le pida y presione cualquier tecla para salir.
3. En cualquier plataforma, ejecute la aplicación con el comando dotnet :
a. Escriba dotnet HelloWorld.dll y presione ENTRAR.
b. Escriba un nombre cuando se le pida y presione cualquier tecla para salir.

Recursos adicionales
Implementación de aplicaciones .NET Core

Pasos siguientes
En este tutorial, ha publicado una aplicación de consola. En el siguiente tutorial, creará una biblioteca de clases.
Creación de una biblioteca .NET Standard mediante Visual Studio Code
Tutorial: Creación de una biblioteca .NET Standard
mediante Visual Studio Code
16/09/2020 • 9 minutes to read • Edit Online

En este tutorial, creará una sencilla biblioteca de utilidades que contiene un único método de control de cadenas.
Lo implementará como un método de extensión de modo que se pueda llamar como si fuera un miembro de la
clase String.
Una biblioteca de clases define los tipos y los métodos que se llaman desde una aplicación. Una biblioteca de
clases que tiene como destino .NET Standard 2.0, lo que permite que cualquier implementación .NET que admita
esa versión de .NET Standard pueda llamar a su biblioteca. Cuando termine la biblioteca de clases, puede
distribuirla como un componente de terceros o como un componente empaquetado con una o varias aplicaciones.

Requisitos previos
1. Visual Studio Code con la extensión de C# instalada. Para saber cómo instalar extensiones en
Visual Studio Code, vea el Marketplace de extensiones de VS Code.
2. SDK de .NET Core 3.1 o posterior

Crear una solución


Empiece por crear una solución en blanco para colocar el proyecto de biblioteca de clases en ella. Una solución
sirve como contenedor de uno o varios proyectos. Agregará otros proyectos relacionados a la misma solución.
1. Inicie Visual Studio Code.
2. Seleccione Archivo > Abrir carpeta (Abrir... en macOS) en el menú principal.
3. En el cuadro de diálogo Abrir carpeta , cree una carpeta ClassLibraryProjects y haga clic en Seleccionar
carpeta (Abrir en macOS).
4. Para abrir el Terminal en Visual Studio Code, seleccione Ver > Terminal en el menú principal.
Se abre el terminal con el símbolo del sistema en la carpeta ClassLibraryProjects.
5. En el Terminal , escriba este comando:

dotnet new sln

La salida del terminal se parece al ejemplo siguiente:

The template "Solution File" was created successfully.

Crear un proyecto de biblioteca de clases


Agregue un nuevo proyecto de biblioteca de clases de .NET Standard denominado "StringLibrary" a la solución.
1. En el terminal, ejecute el siguiente comando para crear un proyecto de biblioteca:
dotnet new classlib -o StringLibrary

El comando -o o --output especifica la ubicación para colocar la salida generada.


La salida del terminal se parece al ejemplo siguiente:

The template "Class library" was created successfully.


Processing post-creation actions...
Running 'dotnet restore' on StringLibrary\StringLibrary.csproj...
Determining projects to restore...
Restored C:\Projects\ClassLibraryProjects\StringLibrary\StringLibrary.csproj (in 328 ms).
Restore succeeded.

2. Ejecute el siguiente comando para agregar el proyecto de biblioteca a la solución:

dotnet sln add StringLibrary/StringLibrary.csproj

La salida del terminal se parece al ejemplo siguiente:

Project `StringLibrary\StringLibrary.csproj` added to the solution.

3. Asegúrese de que la biblioteca tiene como destino la versión correcta de .NET Standard. En Explorer , abra
el archivo StringLibrary/StringLibrary.csproj.
En el elemento TargetFramework se muestra que el proyecto tiene como destino .NET Standard 2.0.

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

</Project>

4. Abra Class1.cs y reemplace el código por el siguiente.

using System;

namespace UtilityLibraries
{
public static class StringLibrary
{
public static bool StartsWithUpper(this string str)
{
if (string.IsNullOrWhiteSpace(str))
return false;

char ch = str[0];
return char.IsUpper(ch);
}
}
}

La biblioteca de clases, UtilityLibraries.StringLibrary , contiene un método denominado StartsWithUpper .


Este método devuelve un valor Boolean que indica si la instancia de cadena actual comienza con un carácter
en mayúscula. El estándar Unicode distingue caracteres en mayúsculas de caracteres en minúsculas. El
método Char.IsUpper(Char) devuelve true si un carácter está en mayúsculas.
5. Guarde el archivo.
6. Ejecute el siguiente comando para compilar la solución y comprobar que el proyecto se compila sin errores.

dotnet build

La salida del terminal se parece al ejemplo siguiente:

Microsoft (R) Build Engine version 16.7.0+b89cb5fde for .NET


Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
All projects are up-to-date for restore.
StringLibrary ->
C:\Projects\ClassLibraryProjects\StringLibrary\bin\Debug\netstandard2.0\StringLibrary.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:02.78

Incorporación de una aplicación de consola a la solución


Agregue una aplicación de consola que use la biblioteca de clases. La aplicación solicitará al usuario que escriba
una cadena y notificará si la cadena comienza con un carácter en mayúsculas.
1. En el terminal, ejecute el siguiente comando para crear un proyecto de consola de aplicación:

dotnet new console -o ShowCase

La salida del terminal se parece al ejemplo siguiente:

The template "Console Application" was created successfully.


Processing post-creation actions...
Running 'dotnet restore' on ShowCase\ShowCase.csproj...
Determining projects to restore...
Restored C:\Projects\ClassLibraryProjects\ShowCase\ShowCase.csproj (in 210 ms).
Restore succeeded.

2. Ejecute el siguiente comando para agregar el proyecto de aplicación de consola a la solución:

dotnet sln add ShowCase/ShowCase.csproj

La salida del terminal se parece al ejemplo siguiente:

Project `ShowCase\ShowCase.csproj` added to the solution.

3. Abra ShowCase/Program.cs y reemplace todo el código por el siguiente.


using System;
using UtilityLibraries;

class Program
{
static void Main(string[] args)
{
int row = 0;

do
{
if (row == 0 || row >= 25)
ResetConsole();

string input = Console.ReadLine();


if (string.IsNullOrEmpty(input)) break;
Console.WriteLine($"Input: {input} {"Begins with uppercase? ",30}: " +
$"{(input.StartsWithUpper() ? "Yes" : "No")}\n");
row += 3;
} while (true);
return;

// Declare a ResetConsole local method


void ResetConsole()
{
if (row > 0)
{
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
Console.Clear();
Console.WriteLine("\nPress <Enter> only to exit; otherwise, enter a string and press
<Enter>:\n");
row = 3;
}
}
}

El código usa la variable row para mantener un recuento del número de filas de datos escritas en la
ventana de consola. Siempre que sea mayor o igual a 25, el código borra la ventana de consola y muestra
un mensaje al usuario.
El programa le pide al usuario que escriba una cadena. Indica si la cadena comienza con un carácter en
mayúsculas. Si el usuario presiona la tecla Entrar sin especificar una cadena, la aplicación finaliza y la
ventana de la consola se cierra.
4. Guarde los cambios.

Agregar una referencia de proyecto


En un principio, el nuevo proyecto de aplicación de consola no tiene acceso a la biblioteca de clases. Para que
pueda llamar a los métodos de la biblioteca de clases, cree una referencia de proyecto al proyecto de biblioteca de
clases.
1. Ejecute el siguiente comando:

dotnet add ShowCase/ShowCase.csproj reference StringLibrary/StringLibrary.csproj

La salida del terminal se parece al ejemplo siguiente:


Reference `..\StringLibrary\StringLibrary.csproj` added to the project.

Ejecutar la aplicación
1. Ejecute el siguiente comando en el terminal:

dotnet run --project ShowCase/ShowCase.csproj

2. Para probar el programa, escriba cadenas y presione Entrar y, después, presione Entrar para salir.
La salida del terminal se parece al ejemplo siguiente:

Press <Enter> only to exit; otherwise, enter a string and press <Enter>:

A string that starts with an uppercase letter


Input: A string that starts with an uppercase letter
Begins with uppercase? : Yes

a string that starts with a lowercase letter


Input: a string that starts with a lowercase letter
Begins with uppercase? : No

Recursos adicionales
Desarrollo de bibliotecas con la CLI de .NET Core
Versiones de .NET Standard y las plataformas que admiten.

Pasos siguientes
En este tutorial, ha creado una solución, ha agregado un proyecto de biblioteca y ha agregado un proyecto de
aplicación de consola que usa la biblioteca. En el siguiente tutorial, agregará un proyecto de prueba unitaria a la
solución.
Prueba de una biblioteca .NET Standard con .NET Core mediante Visual Studio Code
Tutorial: Prueba de una biblioteca de clases .NET
Standard con .NET Core mediante Visual Studio
Code
16/09/2020 • 12 minutes to read • Edit Online

En este tutorial se muestra cómo automatizar las pruebas unitarias mediante la adición de un proyecto de prueba a
una solución.

Requisitos previos
Este tutorial funciona con la solución que se crea en Creación de una biblioteca de .NET Standard con
Visual Studio Code.

Crear un proyecto de prueba unitaria


Las pruebas unitarias proporcionan pruebas de software automatizadas durante el desarrollo y la publicación. El
marco de trabajo de pruebas que se usa en este tutorial es MSTest. MSTest es uno de los tres marcos de pruebas
que puede elegir. Los otros son xUnit y nUnit.
1. Inicie Visual Studio Code.
2. Abra la solución ClassLibraryProjects que creó en Creación de una biblioteca .NET Standard con
Visual Studio Code.
3. Cree un proyecto de prueba unitaria denominado "StringLibraryTest".

dotnet new mstest -o StringLibraryTest

La plantilla de proyecto crea un archivo UnitTest1.cs con el código siguiente:

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace StringLibraryTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
}
}
}

El código fuente creado por la plantilla de prueba unitaria hace lo siguiente:


Importa el espacio de nombres de Microsoft.VisualStudio.TestTools.UnitTesting, que contiene los tipos
utilizados para las pruebas unitarias.
Aplica el atributo TestClassAttribute a la clase UnitTest1 .
Aplica el atributo TestMethodAttribute para definir TestMethod1 .
Cada método etiquetado con [TestMethod] en una clase de prueba etiquetada con [TestClass] se ejecuta
automáticamente cuando se ejecuta la prueba unitaria.
4. Agregue el proyecto de prueba a la solución:

dotnet sln add StringLibraryTest/StringLibraryTest.csproj

Agregar una referencia de proyecto


Para que el proyecto de prueba funcione con la clase StringLibrary , agregue una referencia del proyecto
StringLibraryTest al proyecto StringLibrary .

1. Ejecute el siguiente comando:

dotnet add StringLibraryTest/StringLibraryTest.csproj reference StringLibrary/StringLibrary.csproj

Adición y ejecución de métodos de prueba unitaria


Cuando Visual Studio ejecuta una prueba unitaria, ejecuta cada método marcado con el atributo
TestMethodAttribute en una clase que está marcada con el atributo TestClassAttribute. Un método de prueba
finaliza cuando se encuentra el primer error, o cuando todas las pruebas contenidas en el método se han realizado
correctamente.
Las pruebas más comunes llaman a los miembros de la clase Assert. Muchos métodos de aserción incluyen al
menos dos parámetros, uno de los cuales es el resultado esperado de la prueba y el otro es el resultado real de la
prueba. Algunos de los métodos Assert a los que se llama con más frecuencia se muestran en la tabla siguiente:

M ÉTO DO S DE A SERC IÓ N F UN C IÓ N

Assert.AreEqual Comprueba que dos valores u objetos son iguales. La aserción


da error si los valores o los objetos no son iguales.

Assert.AreSame Comprueba que dos variables de objeto hacen referencia al


mismo objeto. La aserción da error si las variables hacen
referencia a objetos diferentes.

Assert.IsFalse Comprueba si una condición es false . La aserción produce


un error si la condición es true .

Assert.IsNotNull Comprueba que un objeto no es null . La aserción da error


si el objeto es null .

También puede usar el método Assert.ThrowsException en un método de prueba para indicar el tipo de excepción
que se espera que produzca. La prueba dará error si no se inicia la excepción especificada.
Al probar el método StringLibrary.StartsWithUpper , quiere proporcionar un número de cadenas que comiencen
con un carácter en mayúsculas. Espera que el método devuelva true en estos casos, por lo que puede llamar al
método Assert.IsTrue. Del mismo modo, quiere proporcionar un número de cadenas que comiencen con algo que
no sea un carácter en mayúsculas. Espera que el método devuelva false en estos casos, por lo que puede llamar
al método Assert.IsFalse.
Dado que el método de biblioteca controla cadenas, también se recomienda asegurarse de que controla
correctamente una cadena vacía ( String.Empty ) y una cadena null . Una cadena vacía es aquella que no tiene
ningún carácter y cuyo valor de Length es 0. Una cadena null es aquella que no se ha inicializado. Se puede
llamar a StartsWithUpper directamente como un método estático y pasar un argumento String único. También
puede llamar a StartsWithUpper como método de extensión en una variable de string asignada a null .
Definirá tres métodos, cada uno de los cuales llama a un método Assert para cada elemento de una matriz de
cadenas. Llamará a una sobrecarga de método que le permite especificar que se muestre un mensaje de error en
caso de error en la prueba. El mensaje identifica la cadena que causó el error.
Para crear los métodos de prueba:
1. Abra StringLibraryTest/UnitTest1.cs y reemplace todo el código por el siguiente.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UtilityLibraries;

namespace StringLibraryTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestStartsWithUpper()
{
// Tests that we expect to return true.
string[] words = { "Alphabet", "Zebra", "ABC", "Αθήνα", "Москва" };
foreach (var word in words)
{
bool result = word.StartsWithUpper();
Assert.IsTrue(result,
String.Format("Expected for '{0}': true; Actual: {1}",
word, result));
}
}

[TestMethod]
public void TestDoesNotStartWithUpper()
{
// Tests that we expect to return false.
string[] words = { "alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
"1234", ".", ";", " " };
foreach (var word in words)
{
bool result = word.StartsWithUpper();
Assert.IsFalse(result,
String.Format("Expected for '{0}': false; Actual: {1}",
word, result));
}
}

[TestMethod]
public void DirectCallWithNullOrEmpty()
{
// Tests that we expect to return false.
string[] words = { string.Empty, null };
foreach (var word in words)
{
bool result = StringLibrary.StartsWithUpper(word);
Assert.IsFalse(result,
String.Format("Expected for '{0}': false; Actual: {1}",
word == null ? "<null>" : word, result));
}
}
}
}
La prueba de caracteres en mayúsculas en el método TestStartsWithUpper incluye la letra mayúscula griega
alfa (U + 0391) y la letra mayúscula cirílica EM (U + 041C). La prueba de caracteres en minúsculas en el
método TestDoesNotStartWithUpper incluye la letra griega minúscula alfa (U + 03B1) y la letra cirílica
minúscula Ghe (U + 0433).
2. Guarde los cambios.
3. Ejecute las pruebas:

dotnet test StringLibraryTest/StringLibraryTest.csproj

en la salida del terminal se indica que se han superado todas las pruebas.

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.

Test Run Successful.


Total tests: 3
Passed: 3
Total time: 5.1116 Seconds

Administración de errores de prueba


Si está realizando el desarrollo controlado por pruebas (TDD), escribirá las pruebas, y estas generarán un error
cuando las ejecute por primera vez. Después, agregará un código a la aplicación para que la prueba se realice
correctamente. En este tutorial, ha creado la prueba después de escribir el código de la aplicación que valida, por lo
que no ha podido comprobar si la prueba genera un error. Para asegurarse de que una prueba genera un error
cuando espera que lo haga, agregue un valor no válido a la entrada de prueba.
1. Modifique la matriz words en el método TestDoesNotStartWithUpper para incluir la cadena "Error".

string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",


"1234", ".", ";", " " };

2. Ejecute las pruebas:

dotnet test StringLibraryTest/StringLibraryTest.csproj

en la salida del terminal se indica que una de las pruebas no se supera y se proporciona un mensaje de
error al respecto: "Error de Assert.IsFalse. Se esperaba para "Error": false; real: True". Debido al error, no se
probaron todas las cadenas de la matriz después de "Error".
Starting test execution, please wait...

A total of 1 test files matched the specified pattern.


X TestDoesNotStartWithUpper [283ms]
Error Message:
Assert.IsFalse failed. Expected for 'Error': false; Actual: True
Stack Trace:
at StringLibraryTest.UnitTest1.TestDoesNotStartWithUpper()
in C:\Projects\ClassLibraryProjects\StringLibraryTest\UnitTest1.cs:line 33

Test Run Failed.


Total tests: 3
Passed: 2
Failed: 1
Total time: 1.7825 Seconds

3. Quite la cadena "Error" que agregó en el paso 1. Vuelva a ejecutar la prueba y se superarán las pruebas.

Prueba de la versión de la biblioteca


Ahora que se han superado todas las pruebas al ejecutar la versión de depuración de la biblioteca, ejecute las
pruebas una vez más con la versión de lanzamiento de la biblioteca. Varios factores, como las optimizaciones del
compilador, a veces pueden producir un comportamiento diferente entre las compilaciones de depuración y
versión.
1. Ejecute las pruebas con la configuración de compilación Versión:

dotnet test StringLibraryTest/StringLibraryTest.csproj --configuration Release

Las pruebas se superan.

Recursos adicionales
Pruebas unitarias en .NET Core y .NET Standard

Pasos siguientes
En este tutorial, ha realizado una prueba unitaria de una biblioteca de clases. Puede poner la biblioteca a
disposición de otros usuarios si la publica en NuGet como un paquete. Para obtener información sobre cómo
hacerlo, realice este tutorial de NuGet:
Creación y publicación de un paquete con la CLI de dotnet
Si publica una biblioteca como un paquete NuGet, otros usuarios pueden instalarla y usarla. Para obtener
información sobre cómo hacerlo, realice este tutorial de NuGet:
Instalar y usar un paquete con la CLI de dotnet
No es necesario distribuir una biblioteca como un paquete. Se puede agrupar con una aplicación de consola que la
use. Para aprender a publicar una aplicación de consola, vea el tutorial anterior de esta serie:
Publicación de una aplicación de consola de .NET Core con Visual Studio Code
Tutorial: Creación de una aplicación de consola de
.NET Core con Visual Studio para Mac
16/09/2020 • 6 minutes to read • Edit Online

En este tutorial se muestra cómo crear y ejecutar una aplicación de consola de .NET Core en Visual Studio para
Mac.

NOTE
Sus comentarios son muy importantes. Hay dos maneras de proporcionar comentarios al equipo de desarrollo de Visual
Studio para Mac:
En Visual Studio para Mac, seleccione Ayuda > Notificar un problema en el menú o Notificar un problema desde
la pantalla de bienvenida, que abre una ventana para presentar un informe de errores. Puede realizar un seguimiento
de sus comentarios en el portal de la Comunidad de desarrolladores.
Para hacer una sugerencia, seleccione Ayuda > Apor tar una sugerencia en el menú o Apor tar una sugerencia
desde la pantalla de bienvenida, que le lleva a la página web de la Comunidad de desarrolladores de Visual Studio para
Mac.

Requisitos previos
Visual Studio para Mac, versión 8.6 o posterior. Seleccione la opción para instalar .NET Core. La instalación
de Xamarin es opcional para el desarrollo de .NET Core. Para obtener más información, vea los siguientes
recursos:
Tutorial: Instalación de Visual Studio para Mac.
Versiones de macOS compatibles.
Versiones de .NET Core compatibles con Visual Studio para Mac.

Creación de la aplicación
Cree un proyecto de aplicación de consola de .NET Core denominado "HelloWorld".
1. Inicie Visual Studio para Mac:
2. Seleccione Nuevo en la ventana de inicio.

3. En el cuadro de diálogo Nuevo proyecto , seleccione Aplicación en el nodo Web and Console (Web y
consola). Seleccione la plantilla Aplicación de consola y haga clic en Siguiente .
4. En el cuadro de diálogo Plataforma de destino del cuadro de diálogo para configurar la nueva
aplicación de consola , seleccione .NET Core 3.1 y seleccione Siguiente .

5. Escriba "HelloWorld" en Nombre del proyecto y seleccione Crear .


La plantilla crea una aplicación "Hola mundo" sencilla. Llama al método Console.WriteLine(String) para mostrar
"Hola mundo" en la ventana de terminal.
El código de plantilla define una clase, Program , con un único método, Main , que toma una matriz de String
como argumento:

using System;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

Main es el punto de entrada de la aplicación, el método que se llama automáticamente mediante el tiempo de
ejecución cuando inicia la aplicación. Los argumentos de línea de comandos proporcionados cuando se inicia la
aplicación están disponibles en la matriz args .

Ejecutar la aplicación
1. Presione ⌘↵ (opción+command+entrar) para ejecutar la aplicación sin depuración.
2. Cierre la ventana de Terminal .

Mejora de la aplicación
Mejore la aplicación para pedir su nombre al usuario y mostrarlo con la fecha y la hora.
1. En Program.cs, reemplace el contenido del método Main , que es la línea que llama a Console.WriteLine ,
por el código siguiente:

Console.WriteLine("\nWhat is your name? ");


var name = Console.ReadLine();
var date = DateTime.Now;
Console.WriteLine($"\nHello, {name}, on {date:d} at {date:t}!");
Console.Write("\nPress any key to exit...");
Console.ReadKey(true);

Este código muestra un mensaje en la ventana de la consola y espera a que el usuario escriba una cadena
y, luego, presione Entrar. Almacena esta cadena en una variable denominada name . También recupera el
valor de la propiedad DateTime.Now, que contiene la hora local actual, y lo asigna a una variable
denominada date . Asimismo, muestra estos valores en la ventana de la consola. Por último, muestra un
mensaje en la ventana de la consola y llama al método Console.ReadKey(Boolean) para esperar a la
entrada del usuario.
El símbolo \n representa un carácter de nueva línea.
El signo de dólar ( $ ) delante de una cadena permite colocar expresiones como nombres de variable
entre llaves en la cadena. El valor de la expresión se inserta en la cadena en lugar de la expresión. Esta
sintaxis se conoce como cadenas interpoladas.
2. Presione ⌘↵ (opción+command+entrar) para ejecutar la aplicación.

3. Responda a la solicitud escribiendo un nombre y presionando la tecla entrar.

4. Cierre el terminal.

Pasos siguientes
En este tutorial, ha creado una aplicación de consola de .NET Core. En el siguiente tutorial, depurará la aplicación.
Depuración de una aplicación de consola de .NET Core con Visual Studio para Mac
Tutorial: Depuración de una aplicación de consola de
.NET Core con Visual Studio para Mac
16/09/2020 • 13 minutes to read • Edit Online

En este tutorial se presentan las herramientas de depuración que hay disponibles en Visual Studio para Mac.

Requisitos previos
Este tutorial funciona con la aplicación de consola que se crea en Creación de una aplicación de consola de
.NET Core con Visual Studio para Mac.

Uso de la configuración de compilación de depuración


Depuración y Versión son las configuraciones de compilación integradas de Visual Studio. Use la configuración de
compilación Depuración para depurar y la configuración de compilación Versión para la distribución final de la
versión.
En la configuración de depuración, el programa se compila sin optimizar y con toda la información de depuración
simbólica. La optimización complica la depuración, ya que la relación entre el código fuente y las instrucciones
generadas es más compleja. La configuración de versión del programa no contiene información de depuración
simbólica y está totalmente optimizada.
De forma predeterminada, Visual Studio para Mac usa la configuración de compilación Depuración, por lo que no
es necesario cambiarla antes de depurar.
1. Inicie Visual Studio para Mac:
2. Abra el proyecto que ha creado en Creación de una aplicación de consola de .NET Core con Visual Studio
para Mac.
La configuración de compilación actual se muestra en la barra de herramientas. En la siguiente imagen de la
barra de herramientas se muestra que Visual Studio está configurado para compilar la versión de
depuración de la aplicación:

Establecer un punto de interrupción


Un punto de interrupción interrumpe temporalmente la ejecución de la aplicación antes de que se ejecute la línea
con el punto de interrupción.
1. Establezca un punto de interrupción en la línea que muestra el nombre, la fecha y la hora. Para ello, coloque
el cursor en la línea de código y presione ⌘\ (command+\). Otra manera de establecer un punto de
interrupción es seleccionar Ejecutar > Alternar punto de interrupción en el menú.
Para indicar la línea en la que se establece el punto de interrupción, Visual Studio lo resalta y muestra un
punto rojo en el margen izquierdo.
2. Presione ⌘↵ (command+entrar) para iniciar el programa en modo de depuración. Otra manera de iniciar
la depuración es elegir Ejecutar > Iniciar depuración en el menú.
3. Cuando el sistema le pida un nombre, escriba una cadena en la ventana de terminar y luego presione
Entrar.

4. La ejecución del programa se detiene cuando llega al punto de interrupción y antes de que se ejecute el
método Console.WriteLine .

Uso de la ventana Inmediato


La ventana Inmediato le permite interactuar con la aplicación que está depurando. Puede cambiar el valor de las
variables de forma interactiva para ver cómo afecta esto al programa.
1. Si la ventana Inmediato no está visible, muéstrela; para ello, elija Ver > Paneles de depuración >
Inmediato .
2. Escriba name = "Gracie" en la ventana Inmediato y presione Entrar.
3. Escriba date = date.AddDays(1) en la ventana Inmediato y presione Entrar.
La ventana Inmediato muestra el nuevo valor de la variable de cadena y las propiedades del valor
DateTime.
La ventana Variables locales muestra los valores de las variables definidas en el método que se ejecuta
actualmente. Los valores de las variables que acaba de cambiar se actualizan en la ventana Variables
locales .

4. Presione ⌘↵ (command+entrar) para continuar con la depuración.


Los valores mostrados en el terminar corresponden a los cambios realizados en la ventana Inmediato .
Si no ve el terminal, seleccione Terminal - HelloWorld en la barra de navegación inferior.

5. Presione cualquier tecla para salir de la aplicación.


6. Cierre ventana de terminal.

Establecimiento de un punto de interrupción condicional


El programa muestra la cadena que escribe el usuario. ¿Qué sucede si el usuario no escribe nada? Puede probarlo
con una característica de depuración muy útil denominada Punto de interrupción condicional.
1. Haga clic pulsando control en el punto rojo que representa al punto de interrupción. En el menú
contextual, seleccione Editar punto de interrupción .
2. En el cuadro de diálogo Editar punto de interrupción , escriba el código siguiente en el campo Y se
cumpla la siguiente condición y seleccione Aplicar .

String.IsNullOrEmpty(name)
Cada vez que se alcanza el punto de interrupción, el depurador llama al método
String.IsNullOrEmpty(name) y se interrumpe en esta línea solo si la llamada al método devuelve true .
En lugar de una expresión condicional, puede especificar un número de llamadas, que interrumpe la
ejecución del programa antes de que una instrucción se ejecute un número especificado de veces.
3. Presione ⌘↵ (command+entrar) para iniciar la depuración.
4. En la ventana de terminal, presione entrar cuando se le pida que escriba su nombre.
Como se ha cumplido la condición que especificó ( name es null o String.Empty), la ejecución del
programa se detiene cuando se alcanza el punto de interrupción.
5. Seleccione la ventana Variables locales , que muestra los valores de las variables que son locales para el
método que se ejecuta actualmente. En este caso, Main es el método que se está ejecutando actualmente.
Observe que el valor de la variable name es "" ; esto es, String.Empty.
6. También puede ver que el valor es una cadena vacía escribiendo el nombre de la variable name en la
ventana Inmediato y presionando entrar.
7. Presione ⌘↵ (command+entrar) para continuar con la depuración.
8. En la ventana de terminal, presione cualquier tecla para salir del programa.
9. Cierre la ventana de terminal.
10. Para borrar el punto de interrupción, haga clic en el punto rojo en el margen izquierdo de la ventana de
código. Otra manera de hacerlo es elegir Ejecutar > Alternar punto de interrupción con la línea de
código seleccionada.

Ejecución paso a paso de un programa


Visual Studio también le permite recorrer línea a línea un programa y supervisar su ejecución. Normalmente,
establecería un punto de interrupción y seguiría el flujo del programa mediante una pequeña parte de su código
de programa. Como este programa es pequeño, puede ejecutar paso a paso el programa entero.
1. Establezca un punto de interrupción en la llave que marca el inicio del método Main (presione
command+\).

2. Presione ⌘↵ (command+entrar) para iniciar la depuración.


Visual Studio se detiene en la línea con el punto de interrupción.
3. Presione ⇧⌘I (mayús+command+l) o seleccione Ejecutar > Depurar paso a paso por instrucciones
para avanzar una línea.
Visual Studio resalta y muestra una flecha junto a la siguiente línea de ejecución.
En este punto, la ventana Variables locales muestra que la matriz args está vacía, y name y date tienen
valores predeterminados. Además, Visual Studio ha abierto una ventana de consola en blanco.
4. Presione ⇧⌘I (mayús+command+I).
Visual Studio resalta la instrucción que incluye la asignación de variables name . La ventana Variables
locales muestra que name es null , y terminal muestra la cadena "What is your name?" (¿Cómo te
llamas?).
5. Para responder a la solicitud, escriba una cadena en la ventana de consola y presione entrar.
6. Presione ⇧⌘I (mayús+command+I).
Visual Studio resalta la instrucción que incluye la asignación de variables date . La ventana Variables
locales muestra el valor devuelto por la llamada al método Console.ReadLine. En el terminal se muestra la
cadena que escribió en el símbolo del sistema.
7. Presione ⇧⌘I (mayús+command+I).
La ventana Variables locales muestra el valor de la variable date tras la asignación desde la propiedad
DateTime.Now. El terminal no se modifica.
8. Presione ⇧⌘I (mayús+command+I).
Visual Studio llama al método Console.WriteLine(String, Object, Object). El terminal muestra la cadena con
formato.
9. Presione ⇧⌘U (mayús+command+U) o seleccione Ejecutar > Paso a paso para salir .
El terminal muestra un mensaje y espera a que presione una tecla.
10. Presione cualquier tecla para salir de la aplicación.

Uso de la configuración de compilación de versión


Una vez que ha probado la versión de depuración de la aplicación, también debe compilar y probar la versión de
lanzamiento. La versión de lanzamiento incorpora optimizaciones del compilador que afectan negativamente al
comportamiento de una aplicación. Por ejemplo, las optimizaciones del compilador que están diseñadas para
mejorar el rendimiento pueden crear condiciones de carrera en aplicaciones multiproceso.
Para compilar y probar la configuración de versión de la aplicación de consola, realice los pasos siguientes:
1. Cambie la configuración de compilación en la barra de herramientas de Depurar a Versión .
2. Presione ⌘↵ (opción+command+entrar) para ejecutar sin depurar.

Pasos siguientes
En este tutorial, ha usado las herramientas de depuración de Visual Studio. En el siguiente tutorial, publicará una
versión de la aplicación que se puede implementar.
Publicación de una aplicación de consola de .NET Core con Visual Studio para Mac
Tutorial: Publicación de una aplicación de consola de
.NET Core con Visual Studio para Mac
16/09/2020 • 4 minutes to read • Edit Online

En este tutorial se muestra cómo publicar una aplicación de consola para que otros usuarios puedan ejecutarla. La
publicación crea el conjunto de archivos que se necesitan para ejecutar la aplicación. Para implementar los
archivos, cópielos en el equipo de destino.

Requisitos previos
Este tutorial funciona con la aplicación de consola que se crea en Creación de una aplicación de consola de
.NET Core con Visual Studio para Mac.

Publicar la aplicación
1. Inicie Visual Studio para Mac:
2. Abra el proyecto HelloWorld que creó en Creación de una aplicación de consola de .NET Core con
Visual Studio para Mac.
3. Asegúrese de que Visual Studio esté compilando la versión de lanzamiento de la aplicación. Si es necesario,
cambie la configuración de compilación en la barra de herramientas de Depurar a Versión .

4. En el menú principal, elija Compilación > Publicar en carpeta... .

5. En el cuadro de diálogo Publicar en carpeta , seleccione Publicar .


Se abre la carpeta de publicación, que muestra los archivos que se crearon.

6. Seleccione el icono de engranaje y elija Copy "publish" as Pathname (Copiar "publicar" como nombre de
ruta) en el menú contextual.

Inspección de los archivos


El proceso de publicación crea una implementación dependiente del marco, que es un tipo de implementación
donde la aplicación publicada se ejecuta en cualquier máquina que tenga instalado .NET Core Runtime. Los
usuarios pueden ejecutar la aplicación publicada mediante la ejecución del comando dotnet HelloWorld.dll desde
un símbolo del sistema.
Como se muestra en la imagen anterior, la salida publicada incluye los siguientes archivos:
HelloWorld.deps.json
Este es el archivo de dependencias en tiempo de ejecución de la aplicación. Define los componentes y las
bibliotecas de .NET Core (incluida la biblioteca de vínculos dinámicos que contiene la aplicación) necesarios
para ejecutar la aplicación. Para obtener más información, consulte Archivos de configuración en tiempo de
ejecución.
HelloWorld.dll
Esta es la versión de implementación dependiente del marco de la aplicación. Para ejecutar esta biblioteca
de vínculos dinámicos, escriba dotnet HelloWorld.dll en un símbolo del sistema. Este método de ejecución
de la aplicación funciona en cualquier plataforma que tenga instalado .NET Core Runtime.
HelloWorld.pdb (opcional para la implementación)
Este es el archivo de símbolos de depuración. No necesita implementar este archivo junto con su aplicación,
aunque se debe guardar en caso de que necesite depurar la versión publicada de la aplicación.
HelloWorld.runtimeconfig.json
Este es el archivo de configuración en tiempo de ejecución de la aplicación. Identifica la versión de .NET Core
en la que se ha compilado la aplicación para ejecutarse. También puede agregarle opciones de
configuración. Para obtener más información, vea Opciones de configuración en tiempo de ejecución de
.NET Core.

Ejecutar la aplicación publicada


1. Abra un terminal y vaya a la carpeta publish. Para ello, escriba cd y, luego, pegue la ruta de acceso que
copió anteriormente. Por ejemplo:

cd ~/Projects/HelloWorld/HelloWorld/bin/Release/netcoreapp3.1/publish/

2. Ejecute la aplicación mediante el comando dotnet :


a. Escriba dotnet HelloWorld.dll y presione Entrar.
b. Escriba un nombre cuando se le pida y presione cualquier tecla para salir.

Recursos adicionales
Implementación de aplicaciones .NET Core

Pasos siguientes
En este tutorial, ha publicado una aplicación de consola. En el siguiente tutorial, creará una biblioteca de clases.
Creación de una biblioteca de .NET Standard mediante Visual Studio para Mac
Tutorial: Creación de una biblioteca de .NET
Standard mediante Visual Studio para Mac
16/09/2020 • 9 minutes to read • Edit Online

En este tutorial, creará una biblioteca de clases que contiene un único método de control de cadenas. Lo
implementará como un método de extensión de modo que se pueda llamar como si fuera un miembro de la clase
String.
Una biblioteca de clases define los tipos y los métodos que se llaman desde una aplicación. Una biblioteca de
clases que tenga como destino .NET Standard 2.1 puede usarse por una aplicación que tenga como destino
cualquier implementación de .NET que admita la versión 2.1 de .NET Standard. Cuando termine la biblioteca de
clases, puede distribuirla como un componente de terceros o como un componente empaquetado con una o
varias aplicaciones.

NOTE
Sus comentarios son muy importantes. Hay dos maneras de proporcionar comentarios al equipo de desarrollo de Visual
Studio para Mac:
En Visual Studio para Mac, seleccione Ayuda > Notificar un problema en el menú o Notificar un problema desde
la pantalla de bienvenida, que abre una ventana para presentar un informe de errores. Puede realizar un seguimiento de
sus comentarios en el portal de la Comunidad de desarrolladores.
Para hacer una sugerencia, seleccione Ayuda > Apor tar una sugerencia en el menú o Apor tar una sugerencia
desde la pantalla de bienvenida, que le lleva a la página web de la Comunidad de desarrolladores de Visual Studio para
Mac.

Requisitos previos
Instale Visual Studio para Mac, versión 8.6 o posterior. Seleccione la opción para instalar .NET Core. La
instalación de Xamarin es opcional para el desarrollo de .NET Core. Para obtener más información, vea los
siguientes recursos:
Tutorial: Instalación de Visual Studio para Mac.
Versiones de macOS compatibles.
Versiones de .NET Core compatibles con Visual Studio para Mac.

Creación de una solución con un proyecto de biblioteca de clases


Una solución de Visual Studio sirve como contenedor de uno o varios proyectos. Creación de una solución y un
proyecto de biblioteca de clases en la solución. Agregará otros proyectos relacionados a la misma solución más
adelante.
1. Inicie Visual Studio para Mac:
2. En la ventana de inicio, seleccione Nuevo proyecto .
3. En el cuadro de diálogo Nuevo proyecto , en el nodo Multiplataforma , seleccione Biblioteca y luego
seleccione la plantilla Biblioteca de .NET Standard . A continuación, seleccione Siguiente .
4. En el cuadro de diálogo para configurar la nueva biblioteca de .NET Standard , elija ".NET
Standard 2.1" y seleccione Siguiente .

5. Asigne al proyecto el nombre "StringLibrary" y a la solución "ClassLibraryProjects". Deje activada la opción


Crear un directorio de proyecto dentro del directorio de la solución . Seleccione Crear .
6. En el menú principal, seleccione Ver > Paneles > Solución y seleccione el icono de acoplamiento para
mantener el panel abierto.

7. En el panel Solución , expanda el nodo StringLibrary para mostrar el archivo de clase proporcionado por
la plantilla Class1.cs. Haga clic presionando control en el archivo, seleccione Cambiar nombre en el
menú contextual y denomínelo StringLibrary.cs. Abra el archivo y reemplace el contenido por el código
siguiente:

using System;

namespace UtilityLibraries
{
public static class StringLibrary
{
public static bool StartsWithUpper(this string str)
{
if (string.IsNullOrWhiteSpace(str))
return false;

char ch = str[0];
return char.IsUpper(ch);
}
}
}
8. Presione ⌘S (command+S) para guardar el archivo.
9. Seleccione Errores en el margen inferior de la ventana de IDE para abrir el panel Errores . Seleccione el
botón Salida de la compilación .

10. Seleccione Compilar > Compilar todo en el menú.


La solución se compila. El panel de salida de la compilación muestra que la compilación es correcta.

Incorporación de una aplicación de consola a la solución


Agregue una aplicación de consola que use la biblioteca de clases. La aplicación solicitará al usuario que escriba
una cadena y notificará si la cadena comienza con un carácter en mayúsculas.
1. En el panel Solución , haga clic pulsando control en la solución ClassLibraryProjects . Agregue un nuevo
proyecto de aplicación de consola ; para ello, seleccione la plantilla de las plantillas de Web and
Console (Web y consola) > Aplicación y seleccione Siguiente .
2. Seleccione .NET Core 3.1 como Marco de destino y, a continuación, seleccione Siguiente .
3. Asigne al proyecto el nombre ShowCase . Seleccione Crear para crear el proyecto en la solución.
4. Abra el archivo Program.cs. Reemplace el código por el siguiente código:

using System;
using UtilityLibraries;

class Program
{
static void Main(string[] args)
{
int row = 0;

do
{
if (row == 0 || row >= 25)
ResetConsole();

string input = Console.ReadLine();


if (string.IsNullOrEmpty(input)) break;
Console.WriteLine($"Input: {input} {"Begins with uppercase? ",30}: " +
$"{(input.StartsWithUpper() ? "Yes" : "No")}\n");
row += 3;
} while (true);
return;

// Declare a ResetConsole local method


void ResetConsole()
{
if (row > 0)
{
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
Console.Clear();
Console.WriteLine("\nPress <Enter> only to exit; otherwise, enter a string and press
<Enter>:\n");
row = 3;
}
}
}

El programa le pide al usuario que escriba una cadena. Indica si la cadena comienza con un carácter en
mayúsculas. Si el usuario presiona la tecla Entrar sin especificar una cadena, la aplicación finaliza y la
ventana de la consola se cierra.
El código usa la variable row para mantener un recuento del número de filas de datos escritas en la
ventana de consola. Siempre que sea mayor o igual a 25, el código borra la ventana de consola y muestra
un mensaje al usuario.

Agregar una referencia de proyecto


En un principio, el nuevo proyecto de aplicación de consola no tiene acceso a la biblioteca de clases. Para que
pueda llamar a los métodos de la biblioteca de clases, cree una referencia de proyecto al proyecto de biblioteca de
clases.
1. En el panel Soluciones , haga clic presionando control en el nodo Dependencias del nuevo proyecto
ShowCase . En el menú contextual, seleccione Agregar referencia .
2. En el cuadro de diálogo Referencias , seleccione StringLibrar y y Aceptar .

Ejecutar la aplicación
1. Haga clic presionando control en el proyecto ShowCase y seleccione Ejecutar el proyecto en el menú
contextual.
2. Para probar el programa, escriba cadenas y presione Entrar y, después, presione Entrar para salir.

Recursos adicionales
Desarrollo de bibliotecas con la CLI de .NET Core
Versiones de .NET Standard y las plataformas que admiten.
Notas de la versión de Visual Studio 2019 para Mac

Pasos siguientes
En este tutorial, ha creado una solución, ha agregado un proyecto de biblioteca y ha agregado un proyecto de
aplicación de consola que usa la biblioteca. En el siguiente tutorial, agregará un proyecto de prueba unitaria a la
solución.
Prueba de una biblioteca .NET Standard con .NET Core mediante Visual Studio para Mac
Prueba de una biblioteca de clases .NET Standard
con .NET Core mediante Visual Studio
16/09/2020 • 15 minutes to read • Edit Online

En este tutorial se muestra cómo automatizar las pruebas unitarias mediante la adición de un proyecto de prueba a
una solución.

Requisitos previos
Este tutorial funciona con la solución que se crea en Creación de una biblioteca .NET Standard con Visual Studio
para Mac.

Crear un proyecto de prueba unitaria


Las pruebas unitarias proporcionan pruebas de software automatizadas durante el desarrollo y la publicación.
MSTest es uno de los tres marcos de pruebas que puede elegir. Los otros son xUnit y nUnit.
1. Inicie Visual Studio para Mac:
2. Abra la solución ClassLibraryProjects que creó en Creación de una biblioteca .NET Standard con
Visual Studio para Mac.
3. En el panel Solución , presione Ctrl, haga clic en la solución ClassLibraryProjects y seleccione Agregar >
Nuevo proyecto .
4. En el cuadro de diálogo Nuevo proyecto , seleccione Pruebas en el nodo Web and Console (Web y
consola). Seleccione el proyecto MSTest y, luego, Siguiente .
5. Seleccione .NET Core 3.1 . Asigne al nuevo proyecto el nombre "StringLibraryTest" y seleccione Crear .

Visual Studio crea un archivo de clase con el código siguiente:

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace StringLibraryTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
}
}
}

El código fuente creado por la plantilla de prueba unitaria hace lo siguiente:


Importa el espacio de nombres de Microsoft.VisualStudio.TestTools.UnitTesting, que contiene los tipos
utilizados para las pruebas unitarias.
Aplica el atributo TestClassAttribute a la clase UnitTest1 .
Aplica el atributo TestMethodAttribute a TestMethod1 .
Cada método etiquetado con [TestMethod] en una clase de prueba etiquetada con [TestClass] se ejecuta
automáticamente cuando se ejecuta la prueba unitaria.

Agregar una referencia de proyecto


Para que el proyecto de prueba funcione con la clase StringLibrary , agregue una referencia al proyecto
StringLibrary .

1. En el panel Solución , presione Ctrl y haga clic en Dependencias en StringLibrar yTest . Seleccione
Agregar referencia en el menú contextual.
2. En el cuadro de diálogo Referencias , seleccione el proyecto StringLibrar y . Seleccione Aceptar .

Adición y ejecución de métodos de prueba unitaria


Cuando Visual Studio ejecuta una prueba unitaria, ejecuta cada método marcado con el atributo
TestMethodAttribute en una clase que está marcada con el atributo TestClassAttribute. Un método de prueba
finaliza cuando se encuentra el primer error, o cuando todas las pruebas contenidas en el método se han realizado
correctamente.
Las pruebas más comunes llaman a los miembros de la clase Assert. Muchos métodos de aserción incluyen al
menos dos parámetros, uno de los cuales es el resultado esperado de la prueba y el otro es el resultado real de la
prueba. Algunos de los métodos Assert a los que se llama con más frecuencia se muestran en la tabla siguiente:

M ÉTO DO S DE A SERC IÓ N F UN C IÓ N

Assert.AreEqual Comprueba que dos valores u objetos son iguales. La aserción


da error si los valores o los objetos no son iguales.

Assert.AreSame Comprueba que dos variables de objeto hacen referencia al


mismo objeto. La aserción da error si las variables hacen
referencia a objetos diferentes.

Assert.IsFalse Comprueba si una condición es false . La aserción produce


un error si la condición es true .

Assert.IsNotNull Comprueba que un objeto no es null . La aserción da error


si el objeto es null .

También puede usar el método Assert.ThrowsException en un método de prueba para indicar el tipo de excepción
que se espera que produzca. La prueba dará error si no se inicia la excepción especificada.
Al probar el método StringLibrary.StartsWithUpper , quiere proporcionar un número de cadenas que comiencen
con un carácter en mayúsculas. Espera que el método devuelva true en estos casos, por lo que puede llamar al
método Assert.IsTrue. Del mismo modo, quiere proporcionar un número de cadenas que comiencen con algo que
no sea un carácter en mayúsculas. Espera que el método devuelva false en estos casos, por lo que puede llamar
al método Assert.IsFalse.
Dado que el método de biblioteca administra cadenas, quiere asegurarse también de que administra
correctamente una cadena vacía ( String.Empty ), una cadena válida que no tenga caracteres y cuyo Length sea 0, y
una cadena null que no se haya inicializado. Se puede llamar a StartsWithUpper directamente como un método
estático y pasar un argumento String único. También puede llamar a StartsWithUpper como método de extensión
en una variable de string asignada a null .
Definirá tres métodos, cada uno de los cuales llama a un método Assert para cada elemento de una matriz de
cadenas. Llamará a una sobrecarga de método que le permite especificar que se muestre un mensaje de error en
caso de error en la prueba. El mensaje identifica la cadena que causó el error.
Para crear los métodos de prueba:
1. Abra el archivo UnitTest1.cs y reemplace el código por el siguiente:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UtilityLibraries;

namespace StringLibraryTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestStartsWithUpper()
{
// Tests that we expect to return true.
string[] words = { "Alphabet", "Zebra", "ABC", "Αθήνα", "Москва" };
foreach (var word in words)
{
bool result = word.StartsWithUpper();
Assert.IsTrue(result,
String.Format("Expected for '{0}': true; Actual: {1}",
word, result));
}
}

[TestMethod]
public void TestDoesNotStartWithUpper()
{
// Tests that we expect to return false.
string[] words = { "alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
"1234", ".", ";", " " };
foreach (var word in words)
{
bool result = word.StartsWithUpper();
Assert.IsFalse(result,
String.Format("Expected for '{0}': false; Actual: {1}",
word, result));
}
}

[TestMethod]
public void DirectCallWithNullOrEmpty()
{
// Tests that we expect to return false.
string[] words = { string.Empty, null };
foreach (var word in words)
{
bool result = StringLibrary.StartsWithUpper(word);
Assert.IsFalse(result,
String.Format("Expected for '{0}': false; Actual: {1}",
word == null ? "<null>" : word, result));
}
}
}
}

La prueba de caracteres en mayúsculas en el método TestStartsWithUpper incluye la letra mayúscula griega


alfa (U + 0391) y la letra mayúscula cirílica EM (U + 041C). La prueba de caracteres en minúsculas en el
método TestDoesNotStartWithUpper incluye la letra griega minúscula alfa (U + 03B1) y la letra cirílica
minúscula Ghe (U + 0433).
2. En la barra de menús, seleccione Archivo > Guardar como . En el cuadro de diálogo, asegúrese de que
Codificación esté establecida en Unicode (UTF-8) .
3. Cuando se le pregunte si quiere reemplazar el archivo existente, seleccione Reemplazar .
Si obtiene un error al guardar el código fuente en un archivo con codificación UTF-8, Visual Studio puede
guardarlo como un archivo ASCII. Cuando eso suceda, el tiempo de ejecución no descodifica correctamente
los caracteres UTF-8 del rango ASCII, y los resultados de la prueba no serán correctos.
4. Abra el panel Pruebas unitarias en el lado derecho de la pantalla. En el menú, seleccione Ver > Pruebas .
5. Haga clic en el icono Acoplar para mantener abierto el panel.

6. Haga clic en el botón Ejecutar todas .


Todas las pruebas se superan.

Administración de errores de prueba


Si está realizando el desarrollo controlado por pruebas (TDD), escribirá las pruebas, y estas generarán un error
cuando las ejecute por primera vez. Después, agregará un código a la aplicación para que la prueba se realice
correctamente. En este tutorial, ha creado la prueba después de escribir el código de la aplicación que valida, por lo
que no ha podido comprobar si la prueba genera un error. Para asegurarse de que una prueba genera un error
cuando espera que lo haga, agregue un valor no válido a la entrada de prueba.
1. Modifique la matriz words en el método TestDoesNotStartWithUpper para incluir la cadena "Error". No
necesita guardar el archivo porque Visual Studio guarda automáticamente archivos abiertos cuando se crea
una solución para ejecutar pruebas.

string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",


"1234", ".", ";", " " };

2. Vuelva a ejecutar las pruebas.


Esta vez, en la ventana Explorador de pruebas se indica que dos pruebas se han realizado correctamente
y que una ha finalizado con errores.

3. Presione Ctrl y haga clic en la prueba con errores, TestDoesNotStartWithUpper , y seleccione Mostrar el
panel de resultados en el menú contextual.
El panel Resultados muestra el mensaje generado por la aserción: "Error de Assert.IsFalse. Se esperaba
para "Error": false; real: True". Debido al error, no se probaron todas las cadenas de la matriz después de
"Error".
4. Quite la cadena "Error" que agregó en el paso 1. Vuelva a ejecutar la prueba y se superarán las pruebas.

Prueba de la versión de la biblioteca


Ahora que se han superado todas las pruebas al ejecutar la versión de depuración de la biblioteca, ejecute las
pruebas una vez más con la versión de lanzamiento de la biblioteca. Varios factores, como las optimizaciones del
compilador, a veces pueden producir un comportamiento diferente entre las compilaciones de depuración y
versión.
Para probar la compilación de versión:
1. En la barra de herramientas de Visual Studio, cambie la configuración de compilación de Depurar a
Versión .

2. En el panel Solución , presione Ctrl y haga clic en el proyecto StringLibrar y y seleccione Compilación
en el menú contextual para volver a compilar la biblioteca.
3. Ejecute de nuevo las pruebas unitarias.
Las pruebas se superan.

Depuración de pruebas
Puede usar el mismo proceso que se muestra en el Tutorial: Depuración de una aplicación de consola de .NET Core
con Visual Studio para Mac para depurar el código mediante el proyecto de prueba unitaria. En lugar de iniciar el
proyecto de aplicación ShowCase, presione Ctrl y haga clic en el proyecto StringLibrar yTests y seleccione
Iniciar depuración del proyecto en el menú contextual. Visual Studio inicia el proyecto de prueba con el
depurador asociado. La ejecución se detendrá en cualquier punto de interrupción que haya agregado al proyecto
de prueba o al código de la biblioteca subyacente.

Recursos adicionales
Pruebas unitarias en .NET Core y .NET Standard

Pasos siguientes
En este tutorial, ha realizado una prueba unitaria de una biblioteca de clases. Puede poner la biblioteca a
disposición de otros usuarios si la publica en NuGet como un paquete. Para obtener información sobre cómo
hacerlo, realice este tutorial de NuGet:
Crear y publicar un paquete (CLI de dotnet)
Si publica una biblioteca como un paquete NuGet, otros usuarios pueden instalarla y usarla. Para obtener
información sobre cómo hacerlo, realice este tutorial de NuGet:
Instalación y uso de un paquete en Visual Studio para Mac
No es necesario distribuir una biblioteca como un paquete. Se puede agrupar con una aplicación de consola que la
use. Para aprender a publicar una aplicación de consola, vea el tutorial anterior de esta serie:
Publicación de una aplicación de consola de .NET Core con Visual Studio para Mac
Explore estos tutoriales para obtener información
sobre las herramientas de .NET Core y el SDK de
.NET Core
16/09/2020 • 2 minutes to read • Edit Online

Los siguientes tutoriales están disponibles para aprender sobre .NET Core.

Usar Visual Studio


Creación de una aplicación de consola
Depuración de una aplicación
Publicación de una aplicación
Creación de una biblioteca de clases
Prueba unitaria de una biblioteca de clases
Instalación y uso de un paquete
Creación y publicación de un paquete
Creación de una aplicación de consola en F#

Usar Visual Studio Code


Elija estos tutoriales si quiere usar Visual Studio Code u otro editor de código. Todos usan la CLI para tareas de
desarrollo de .NET Core, por lo que, salvo el relativo a la depuración, se pueden utilizar con cualquier editor de
código.
Creación de una aplicación de consola
Depuración de una aplicación
Publicación de una aplicación
Creación de una biblioteca de clases
Prueba unitaria de una biblioteca de clases
Instalación y uso de un paquete
Creación y publicación de un paquete
Creación de una aplicación de consola en F#

Uso de Visual Studio para Mac


Creación de una aplicación de consola
Depuración de una aplicación
Publicación de una aplicación
Creación de una biblioteca de clases
Prueba unitaria de una biblioteca de clases
Instalación y uso de un paquete
Creación de una aplicación de consola en F#

Temas avanzados
Procedimiento para crear bibliotecas
Prueba unitaria de una aplicación con xUnit
Prueba unitaria en C#, VB y F# con NUnit, xUnit o MSTest
Prueba unitaria dinámica con Visual Studio
Creación de plantillas para la CLI
Creación y uso de herramientas para la CLI
Creación de una aplicación con complementos

Creación de aplicaciones web


Para ver tutoriales sobre el desarrollo de aplicaciones web ASP.NET Core, vea la documentación de ASP.NET Core.
Novedades de .NET Core 3.1
16/09/2020 • 6 minutes to read • Edit Online

En este artículo se describen las novedades de .NET Core 3.1. Esta versión contiene ligeras mejoras de .NET Core
3.0, y se centra en pequeñas correcciones, pero importantes. La característica más importante sobre .NET Core 3.1
es que es una versión de soporte técnico a largo plazo (LTS).
Si usa Visual Studio 2019, tendrá que actualizar a Visual Studio 2019, versión 16.4 o posterior para trabajar con
proyectos de .NET Core 3.1. Para obtener información sobre las novedades de la versión 16.4 de Visual Studio, vea
Novedades de la versión 16.4 de Visual Studio 2019.
Visual Studio para Mac también admite e incluye .NET Core 3.1 en Visual Studio para Mac 8.4.
Para más información sobre la versión, consulte el anuncio de .NET Core 3.1.
Descargue .NET Core 3.1 y empiece a trabajar en Windows, macOS o Linux.

Compatibilidad a largo plazo


.NET Core 3.1 es una versión LTS con soporte técnico de Microsoft durante los próximos tres años. Se recomienda
encarecidamente mover las aplicaciones a .NET Core 3.1. El ciclo de vida actual de otras versiones principales es el
siguiente:

REL EA SE N OTA

.NET Core 3.0 Fin del ciclo de vida el 3 de marzo de 2020.

.NET Core 2.2 Fin del ciclo de vida el 23 de diciembre de 2019.

.NET Core 2.1 Fin del ciclo de vida el 21 de agosto de 2021.

Para más información, consulte la directiva de soporte técnico de .NET Core.

appHost y certificación de macOS


Solo para macOS
A partir del SDK de .NET Core 3.1 para macOS, la configuración de appHost está deshabilitada de forma
predeterminada. Para obtener más información, vea Certificación de macOS Catalina y el impacto en las descargas
y proyectos de .NET Core.
Cuando la configuración de appHost está habilitada, .NET Core genera un ejecutable Mach-O nativo al compilar o
publicar. La aplicación se ejecuta en el contexto de appHost cuando se ejecuta desde el código fuente con el
comando dotnet run o mediante el inicio directo del ejecutable Mach-O.
Sin appHost, la única manera en la que un usuario puede iniciar una aplicación dependiente del marco es con el
comando dotnet <filename.dll> . Siempre se crea un instancia de appHost al publicar la aplicación de manera
independiente.
Puede configurar appHost en el nivel de proyecto, o bien cambiar la instancia de appHost de un comando dotnet
específico con el parámetro -p:UseAppHost :
Archivo del proyecto
<PropertyGroup>
<UseAppHost>true</UseAppHost>
</PropertyGroup>

Parámetro de línea de comandos

dotnet run -p:UseAppHost=true

Para obtener más información sobre la configuración de UseAppHost , vea Propiedades de MSBuild para
Microsoft.NET.Sdk.

Windows Forms
Solo Windows

WARNING
Hay cambios importantes en Windows Forms.

Se incluyeron controles heredados en Windows Forms que llevan un tiempo sin estar disponibles en el cuadro de
herramientas del diseñador de Visual Studio. Estos controles se volvieron a reemplazar por otros nuevos en .NET
Framework 2.0 y se han quitado del SDK de escritorio para .NET Core 3.1.

C O N T RO L EL IM IN A DO REEM P L A Z O REC O M EN DA DO A P I A SO C IA DA S EL IM IN A DA S

DataGrid DataGridView DataGridCell


DataGridRow
DataGridTableCollection
DataGridColumnCollection
DataGridTableStyle
DataGridColumnStyle
DataGridLineStyle
DataGridParentRowsLabel
DataGridParentRowsLabelStyle
DataGridBoolColumn
DataGridTextBox
GridColumnStylesCollection
GridTableStylesCollection
HitTestType

ToolBar ToolStrip ToolBarAppearance

ToolBarButton ToolStripButton ToolBarButtonClickEventArgs


ToolBarButtonClickEventHandler
ToolBarButtonStyle
ToolBarTextAlign

ContextMenu ContextMenuStrip

Menu ToolStripDropDown MenuItemCollection


ToolStripDropDownMenu

MainMenu MenuStrip
C O N T RO L EL IM IN A DO REEM P L A Z O REC O M EN DA DO A P I A SO C IA DA S EL IM IN A DA S

MenuItem ToolStripMenuItem

Se recomienda actualizar las aplicaciones a .NET Core 3.1 y pasar a los controles de reemplazo. Reemplazar los
controles es un proceso sencillo; se trata básicamente de "buscar y reemplazar" el tipo.

C++/CLI
Solo Windows
Se ha agregado compatibilidad con la creación de proyectos de C++/CLI (lo que también se conoce como "C++
administrado"). Los archivos binarios generados a partir de estos proyectos son compatibles con .NET Core 3.0 y
versiones posteriores.
Para agregar compatibilidad con C++/CLI a Visual Studio 2019 versión 16.4, instale la carga de trabajo Desarrollo
para el escritorio con C++. Esta carga de trabajo agrega dos plantillas a Visual Studio:
Biblioteca de clases de CLR (.NET Core)
Proyecto vacío de CLR (.NET Core)

Pasos siguientes
Revise los cambios importantes entre .NET Core 3.0 y 3.1.
Revise los cambios importantes de .NET Core 3.1 para aplicaciones de Windows Forms.
Novedades de .NET Core 3.0
16/09/2020 • 46 minutes to read • Edit Online

En este artículo se describen las novedades de .NET Core 3.0. Una de las mejoras más importantes es la
compatibilidad con las aplicaciones de Escritorio de Windows (solo Windows). Mediante el componente Escritorio
de Windows del SDK de .NET Core 3.0, puede portar sus aplicaciones de Windows Forms y
Windows Presentation Foundation (WPF). Para ser más precisos, el componente Escritorio de Windows solo se
admite e incluye en Windows. Para obtener más información, vea la sección Escritorio de Windows más adelante en
este artículo.
.NET Core 3.0 agrega compatibilidad con C# 8.0. Se recomienda encarecidamente usar Visual Studio 2019, versión
16.3 o una versión posterior, Visual Studio para Mac 8.3 o una versión posterior, o Visual Studio Code con la última
extensión de C# .
Descargue .NET Core 3.0 y empiece a trabajar ya en Windows, macOS o Linux.
Para obtener más información acerca de la versión, consulte el anuncio de .NET Core 3.0.
Microsoft considera .NET Core RC1 como listo para producción y es totalmente compatible. Si usa una versión
preliminar, debe pasar a la versión RTM para obtener soporte técnico continuo.

Mejoras del lenguaje C# 8.0


C# 8.0 también forma parte de esta versión, que incluye la característica de tipos de referencia que aceptan valores
NULL, flujos asincrónicos y más patrones. Para obtener más información sobre las características de C# 8.0, vea
Novedades de C# 8.0.
Se han agregado mejoras del lenguaje para admitir las siguientes características de API que se detallan a
continuación:
Rangos e índices
Flujos asincrónicos

.NET Standard 2.1


.NET Core 3.0 implementa .NET Standard 2.1 . Pero la plantilla predeterminada dotnet new classlib genera un
proyecto que sigue destinado a .NET Standard 2.0 . Para destinarlo a .NET Standard 2.1 , edite el archivo de
proyecto y cambie la propiedad TargetFramework a netstandard2.1 :

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>

</Project>

Si usa Visual Studio, necesita Visual Studio 2019, ya que Visual Studio 2017 no admite .NET Standard 2.1 ni
.NET Core 3.0 .

Compilación e implementación
Archivos ejecutables predeterminados
.NET Core compila ahora archivos ejecutables dependientes del marco de forma predeterminada. Este
comportamiento es nuevo en las aplicaciones que usan una versión de .NET Core instalada globalmente.
Anteriormente, solo las implementaciones independientes generarían un archivo ejecutable.
Durante dotnet build o dotnet publish , se crea un archivo ejecutable (conocido como appHost ) que coincide
con el entorno y la plataforma del SDK que se usa. Estos ejecutables funcionan de la misma forma que los
ejecutables nativos:
Haga doble clic en el archivo ejecutable.
También puede iniciar la aplicación desde un símbolo del sistema directamente, como myapp.exe en Windows y
./myapp en Linux y macOS.

appHost y certificación de macOS


Solo para macOS
A partir del SDK de .NET Core 3.0 para macOS certificado, el valor para generar un archivo ejecutable
predeterminado (conocido como appHost) está deshabilitado de forma predeterminada. Para obtener más
información, vea Certificación de macOS Catalina y el impacto en las descargas y proyectos de .NET Core.
Cuando la configuración de appHost está habilitada, .NET Core genera un ejecutable Mach-O nativo al compilar o
publicar. La aplicación se ejecuta en el contexto de appHost cuando se ejecuta desde el código fuente con el
comando dotnet run o mediante el inicio directo del ejecutable Mach-O.
Sin appHost, la única manera en la que un usuario puede iniciar una aplicación dependiente del marco es con el
comando dotnet <filename.dll> . Siempre se crea un instancia de appHost al publicar la aplicación de manera
independiente.
Puede configurar appHost en el nivel de proyecto, o bien cambiar la instancia de appHost de un comando dotnet
específico con el parámetro -p:UseAppHost :
Archivo del proyecto

<PropertyGroup>
<UseAppHost>true</UseAppHost>
</PropertyGroup>

Parámetro de línea de comandos

dotnet run -p:UseAppHost=true

Para obtener más información sobre la configuración de UseAppHost , vea Propiedades de MSBuild para
Microsoft.NET.Sdk.
Archivos ejecutables de único archivo
El comando dotnet publish admite empaquetar la aplicación en un ejecutable de archivo único específico de la
plataforma. El archivo ejecutable es autoextraíble y contiene todas las dependencias (incluidas las nativas)
necesarias para ejecutar la aplicación. Cuando la aplicación se ejecuta por primera vez, se extrae en un directorio
que se basa en el nombre de la aplicación y el identificador de compilación. El inicio es más rápido cuando se
vuelve a ejecutar la aplicación. La aplicación no necesita extraerse por segunda vez a menos que se haya utilizado
una nueva versión.
Para publicar un ejecutable de archivo único, establezca PublishSingleFile en el proyecto o en la línea de
comandos con el comando dotnet publish :
<PropertyGroup>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>

o bien

dotnet publish -r win10-x64 -p:PublishSingleFile=true

Para obtener más información sobre la publicación de archivos únicos, vea el documento de diseño del programa
de instalación de conjunto de archivos únicos.
Vinculación de ensamblados
El SDL de .NET Core 3.0 cuenta con una herramienta que puede reducir el tamaño de las aplicaciones mediante el
análisis de IL y el recorte de los ensamblados no usados.
Las aplicaciones independientes incluyen todo lo necesario para ejecutar el código, sin necesidad de instalar .NET
en el equipo host. Sin embargo, muchas veces, la aplicación solo requiere un pequeño subconjunto de marco para
que funcione, y otras bibliotecas que no se utilizan podrían quitarse.
.NET Core incluye ahora un valor que usará la herramienta Enlazador de IL para examinar el nivel de integridad de
la aplicación. Esta herramienta detecta el código que es necesario y, después, recorta las bibliotecas no utilizadas.
Esta herramienta puede reducir significativamente el tamaño de implementación de algunas aplicaciones.
Para habilitar esta herramienta, agregue el valor <PublishTrimmed> en el proyecto y publique una aplicación
independiente:

<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>

dotnet publish -r <rid> -c Release

Por ejemplo, la nueva y básica plantilla de proyecto de consola "Hola mundo" que se incluye, cuando se publica,
tiene un tamaño aproximado de 70 MB. Mediante el uso de <PublishTrimmed> , ese tamaño se reduce a unos 30 MB.
Es importante tener en cuenta que las aplicaciones o marcos (incluidos ASP.NET Core y WPF) que usan la reflexión
o las características dinámicas relacionadas, se interrumpirán a menudo cuando se recorten. Esta interrupción se
produce porque el enlazador no conoce este comportamiento dinámico y no puede determinar qué tipos de marco
son necesarios para la reflexión. La herramienta Enlazador de IL puede configurarse para tener en cuenta este
escenario.
Por encima de todo lo demás, no olvide probar la aplicación después del recorte.
Para más información sobre la herramienta Enlazador de IL, vea la documentación o visite el repositorio
mono/linker.
Compilación en niveles
La compilación en niveles (TC) está activada de forma predeterminada con .NET Core 3.0. Esta característica permite
que el runtime use el compilador Just-In-Time (JIT) de forma más flexible para lograr un mejor rendimiento.
La principal ventaja de la compilación en niveles es que ofrece dos maneras de aplicar JIT a los métodos: con un
nivel de menor calidad, pero más rápido, o un nivel de mayor calidad, pero más lento. La calidad se refiere al grado
de optimización del método. La compilación en niveles contribuye a mejorar el rendimiento de una aplicación a
medida que pasa por distintas fases de ejecución, desde el inicio hasta el estado estable. Cuando la compilación en
niveles está deshabilitada, todos los métodos se compilan de una manera única que prima el rendimiento de
estado estable sobre el rendimiento de inicio.
Cuando la compilación en niveles está habilitada, se aplica el comportamiento siguiente a la compilación de
métodos al iniciar una aplicación:
Si el método tiene código compilado mediante Ahead-Of-Time, o ReadyToRun, se usa el código generado
previamente.
De lo contrario, el método se compila mediante JIT. Normalmente, estos métodos son genéricos con respecto a
los tipos de valor.
JIT rápido produce código de menor calidad (o menos optimizado) más rápidamente. En .NET Core 3.0,
JIT rápido está habilitado de forma predeterminada para los métodos que no contienen ningún bucle y
tiene preferencia durante el inicio.
JIT de optimización completa produce código de mayor calidad (o más optimizado) más lentamente. En el
caso de los métodos en los que no se use la compilación mediante JIT rápida (por ejemplo, si el método
tiene el atributo MethodImplOptions.AggressiveOptimization), se utilizará la compilación mediante JIT
totalmente optimizada.
En el caso de los métodos a los que se llama con frecuencia, el compilador Just-in-Time al final crea código
totalmente optimizado en segundo plano. Luego, el código optimizado reemplaza el código compilado
previamente de ese método.
El código generado con compilación mediante JIT rápida puede ejecutarse más lentamente, asignar más memoria o
usar más espacio de pila. Si hay problemas, puede deshabilitar JIT rápido con esta propiedad de MSBuild en el
archivo de proyecto:

<PropertyGroup>
<TieredCompilationQuickJit>false</TieredCompilationQuickJit>
</PropertyGroup>

Para deshabilitar completamente la compilación en niveles, use esta propiedad de MSBuild en el archivo de
proyecto:

<PropertyGroup>
<TieredCompilation>false</TieredCompilation>
</PropertyGroup>

TIP
Si cambia esta configuración en el archivo de proyecto, es posible que deba realizar una compilación limpia para que se refleje
la nueva configuración (elimine los directorios obj y bin y vuelva a compilar).

Para obtener más información sobre la configuración de la compilación en tiempo de ejecución, vea Opciones de
configuración del entorno de ejecución para compilación.
Imágenes ReadyToRun
Puede mejorar el tiempo de inicio de la aplicación .NET Core mediante la compilación de los ensamblados de
aplicación como el formato ReadyToRun (R2R). R2R es una forma de compilación Ahead Of Time (AOT).
Los binarios de R2R mejoran el rendimiento de inicio reduciendo la cantidad de trabajo que el compilador Just-In-
Time (JIT) debe llevar a cabo cuando se carga la aplicación. Los binarios contienen código nativo similar en
comparación con lo que generaría el compilador JIT. Sin embargo, los binarios de R2R son más grandes porque
contienen tanto el código de lenguaje intermedio (IL), que sigue siendo necesario para algunos escenarios, como la
versión nativa del mismo código. R2R solo está disponible cuando publica una aplicación independiente que tenga
como destino entornos de tiempo de ejecución específicos (RID), como Linux x64 o Windows x64.
Para compilar el proyecto como ReadyToRun, haga lo siguiente:
1. Agregue el valor <PublishReadyToRun> al proyecto:

<PropertyGroup>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>

2. Publique una aplicación independiente. Por ejemplo, este comando crea una aplicación independiente para
la versión de 64 bits de Windows:

dotnet publish -c Release -r win-x64 --self-contained

Restricciones multiplataforma y de arquitectura


Actualmente, el compilador ReadyToRun no admite la compatibilidad cruzada. Debe compilar en un destino dado.
Por ejemplo, si desea imágenes R2R para Windows x64, deberá ejecutar el comando de publicación en ese entorno.
Excepciones de la compatibilidad cruzada:
Windows x64 se puede usar para compilar imágenes de Windows ARM32, ARM64 y x86.
Windows x86 se puede usar para compilar imágenes de Windows ARM32.
Linux x64 se puede usar para compilar imágenes de Linux ARM32 y ARM64.

Runtime y SDK
Puesta al día del runtime de versiones principales
.NET Core 3.0 presenta una característica opcional que permite poner la aplicación al día con la versión principal
más reciente de .NET Core. Además, se agregó una nueva configuración para controlar cómo se aplica la puesta al
día a la aplicación. Esto se puede configurar de las maneras siguientes:
Propiedad de archivo del proyecto: RollForward
Propiedad de archivo de configuración en tiempo de ejecución: rollForward
Variable de entorno: DOTNET_ROLL_FORWARD
Argumento de línea de comandos: --roll-forward

Debe especificarse uno de los valores siguientes. Si se omite la configuración, Minor es el valor predeterminado.
LatestPatch
Se pone al día con la última versión de revisión. Se deshabilita la puesta al día de versiones secundarias.
Minor
Se pone al día con la versión secundaria mínima superior, si no se encuentra la versión secundaria solicitada. Si
se encuentra la versión secundaria solicitada, se utiliza la directiva LatestPatch .
Major
Se pone al día con la versión secundaria mínima superior, y la versión secundaria mínima, si no se encuentra la
versión secundaria solicitada. Si se encuentra la versión principal solicitada, se utiliza la directiva Minor .
LatestMinor
Se pone al día con la última versión secundaria, aunque la versión secundaria solicitada esté presente. Se
destina a escenarios de hospedaje de componentes.
LatestMajor
Se pone al día con la última versión principal y la última versión secundaria, aunque la versión principal
solicitada esté presente. Se destina a escenarios de hospedaje de componentes.
Disable
No se pone al día. Solo se enlaza a la versión especificada. No se recomienda esta directiva para uso general, ya
que deshabilita la capacidad de puesta al día con las revisiones más recientes. Este valor solo se recomienda a
efectos de pruebas.
Además del valor Disable , todos los valores usarán la última versión de revisión disponible.
De forma predeterminada, si la versión solicitada (como se especifica en .runtimeconfig.json para la aplicación) es
una versión de lanzamiento, solo se tienen en cuenta las versiones de lanzamiento para la puesta al día. Se omiten
las versiones preliminares. Si no hay ninguna versión de lanzamiento que coincida, se tienen en cuenta las
versiones preliminares. Este comportamiento se puede cambiar estableciendo
DOTNET_ROLL_FORWARD_TO_PRERELEASE=1 , en cuyo caso siempre se tienen en cuenta todas las versiones.

Compilación de dependencias de copias


El comando dotnet build copia ahora las dependencias de NuGet para la aplicación de la caché de NuGet a la
carpeta de salida de compilación. Anteriormente, las dependencias solo se copiaban como parte de dotnet publish
.
Hay algunas operaciones, como la publicación de páginas Razor y la vinculación, que aún es necesario publicar.
Herramientas locales
.NET Core 3.0 presenta herramientas locales. Las herramientas locales son similares a las herramientas globales
pero están asociadas a una ubicación concreta en el disco. Las herramientas locales no están disponibles
globalmente y se distribuyen como paquetes NuGet.

WARNING
Si ha probado herramientas locales en la versión preliminar 1 de .NET Core 3.0, tales como la ejecución de
dotnet tool restore o de dotnet tool install , elimine la carpeta de caché de herramientas locales. En caso contrario,
las herramientas locales no funcionan en las versiones más recientes. Esta carpeta se encuentra en:
En macOS, Linux: rm -r $HOME/.dotnet/toolResolverCache

En Windows: rmdir /s %USERPROFILE%\.dotnet\toolResolverCache

Las herramientas locales se basan en un nombre de archivo de manifiesto dotnet-tools.json del directorio actual.
Este archivo de manifiesto define las herramientas que estarán disponibles en esa carpeta y a continuación. Puede
distribuir el archivo de manifiesto con su código para asegurarse de que todo aquel que trabaje con su código
pueda restaurar y utilizar las mismas herramientas.
Para las herramientas locales y globales, se requiere una versión compatible del entorno de ejecución. Actualmente,
muchas herramientas de NuGet.org tienen como destino el entorno de ejecución de .NET Core 2.1. Para instalar
estas herramientas local o globalmente, aún tendría que instalar NET Core 2.1 Runtime.
Opciones nuevas de global.json
El archivo global.json tiene opciones nuevas que proporcionan más flexibilidad cuando se intenta definir qué
versión de la SDK de .NET Core se usa. Las opciones nuevas son:
allowPrerelease : Indica si la resolución del SDK debe tener en cuenta las versiones preliminares a la hora de
seleccionar la versión del SDK que se va a usar.
rollForward : indica la directiva de puesta al día que se va a usar al seleccionar una versión del SDK, ya sea
como reserva si falta una versión específica del SDK o como una directiva para usar una versión superior.
Para más información sobre los cambios, incluidos los valores predeterminados, los valores admitidos y reglas de
coincidencia nuevas, consulte la información general de global.json.
Tamaños del montón de recolección de elementos no utilizados más pequeños
Se ha reducido el tamaño predeterminado del montón del recolector de elementos no utilizados, lo que se traduce
en que .NET Core usa menos memoria. Este cambio se adapta mejor al presupuesto de asignación de generación 0
con tamaños de caché de procesador moderno.
Compatibilidad con Large Pages de recolección de elementos no utilizados
Large Pages (también conocida como Huge Pages en Linux) es una característica en la que el sistema operativo es
capaz de establecer regiones de memoria más grandes que el tamaño de página nativo (a menudo, 4 K) para
mejorar el rendimiento de la aplicación que solicita estas páginas grandes.
Ahora, el recolector de elementos no utilizados puede configurarse con el valor GCLargePages como
característica opcional para elegir la asignación de páginas grandes en Windows.

Escritorio de Windows y COM


Windows Installer del SDK de .NET Core
El instalador MSI para Windows ha cambiado a partir de .NET Core 3.0. Los instaladores del SDK actualizarán ahora
las versiones de la banda de características del SDK. Las bandas de características se definen en los grupos de
centenas de la sección revisión del número de versión. Por ejemplo, 3.0.101 y 3.0.201 son versiones de dos
bandas de características distintas mientras que 3.0.101 y 3.0.199 están en la misma banda de características. Y,
cuando se instale el SDK 3.0.101 de .NET Core, se quitará el SDK 3.0.100 de .NET Core de la máquina si existe.
Cuando se instale el SDK 3.0.200 de .NET Core en la misma máquina, no se quitará el SDK 3.0.101 de .NET Core.
Para obtener más información sobre las versiones, vea el artículo de introducción a la creación de versiones de
.NET Core.
Escritorio de Windows
.NET Core 3.0 admite aplicaciones de escritorio de Windows con Windows Presentation Foundation (WPF) y
Windows Forms. Estos marcos también admiten el uso de controles modernos y los estilos de Fluent de la
biblioteca de XAML de la interfaz de usuario de Windows (WinUI) a través de islas XAML.
El componente Escritorio de Windows forma parte del SDK de Windows .NET Core 3.0.
Puede crear una aplicación de Windows Forms o WPF con los siguientes comandos dotnet :

dotnet new wpf


dotnet new winforms

Visual Studio 2019 agrega plantillas de nuevo proyecto para WPF y Windows Forms de .NET Core 3.0.
Para obtener más información sobre cómo migrar una aplicación existente de .NET Framework, vea los artículos
sobre cómo portar proyectos de WPF y cómo portar proyectos de Windows Forms.
PPP alto de WinForms
En .NET Core, las aplicaciones de Windows Forms pueden establecer el modo de valores altos de PPP con
Application.SetHighDpiMode(HighDpiMode). El método SetHighDpiMode establece el modo de valores altos de PPP
correspondiente a menos que la opción se haya establecido por otros medios como App.Manifest o P/Invoke antes
de Application.Run .
Los posibles valores de highDpiMode , expresados por la enumeración System.Windows.Forms.HighDpiMode son:
DpiUnaware
SystemAware
PerMonitor
PerMonitorV2
DpiUnawareGdiScaled

Para más información sobre los modos de valores altos de PPP, consulte el artículo sobre desarrollo de aplicaciones
de escritorio con valores altos de PPP en Windows.
Creación de componentes COM
En Windows, ahora puede crear componentes COM administrados invocables. Esta capacidad es fundamental para
usar .NET Core con modelos de complemento COM, así como para ofrecer paridad con .NET Framework.
A diferencia de .NET Framework, donde se utilizó mscoree.dll como servidor COM, .NET Core agregará un archivo
dll de inicio nativo al directorio bin al compilar el componente COM.
Para ver un ejemplo de cómo crear un componente COM y usarlo, consulte la demostración de COM.
Interoperabilidad nativa de Windows
Windows ofrece una API nativa enriquecida en forma de API de C sin formato, COM y WinRT. Mientras que
.NET Core admite P/Invoke , .NET Core 3.0 agrega la capacidad de API COM CoCreate y API WinRT Activate .
Para obtener un ejemplo de código, vea la demostración de Excel.
Implementación de MSIX
MSIX es un nuevo formato de paquete de aplicación de Windows. Se puede usar para implementar aplicaciones de
escritorio de .NET Core 3.0 en Windows 10.
El proyecto de paquete de aplicación de Windows, disponible en Visual Studio 2019, le permite crear paquetes de
MSIX con aplicaciones de .NET Core independientes.
El archivo del proyecto de .NET Core debe especificar los tiempos de ejecución admitidos en la propiedad
<RuntimeIdentifiers> :

<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>

Mejoras de Linux
SerialPort para Linux
.Net Core 3.0 proporciona compatibilidad básica para System.IO.Ports.SerialPort en Linux.
Anteriormente, .NET Core solo admitía el uso de SerialPort en Windows.
Para obtener más información sobre la compatibilidad limitada para el puerto de serie en Linux, vea Problema
#33146 de GitHub.
Docker y límites de memoria de cgroup
La ejecución de .NET Core 3.0 en Linux con Docker funciona mejor con límites de memoria de cgroup. La ejecución
de un contenedor de Docker con límites de memoria, como con docker run -m , cambia el comportamiento de
.NET Core.
Tamaño predeterminado del montón del recolector de elementos no utilizados (GC): máximo de 20 MB o 75 %
del límite de memoria en el contenedor.
Puede establecerse el tamaño explícito como número absoluto o porcentaje del límite de cgroup.
El tamaño mínimo del segmento reservado por el montón de GC es de 16 MB. Con este tamaño se reduce el
número de montones que se crean en las máquinas.
Compatibilidad de GPIO con Raspberry Pi
Se han publicado dos paquetes en NuGet que puede usar para la programación de GPIO:
System.Device.Gpio
Iot.Device.Bindings
Los paquetes GPIO incluyen interfaces API para dispositivos GPIO, SPI, I2C y PWM. El paquete de enlaces de IoT
incluye enlaces de dispositivos. Para obtener más información, vea el repositorio de GitHub de los dispositivos.
Compatibilidad con ARM64 para Linux
.NET Core 3.0 agrega compatibilidad con ARM64 para Linux. El principal caso de uso de ARM64 son escenarios de
IoT. Para obtener más información, vea el artículo sobre el estado de ARM64 de .NET Core.
Hay imágenes de docker para .NET Core en ARM64 disponibles para Alpine, Debian y Ubuntu.

NOTE
Windows aún no ofrece soporte técnico para ARM64 .

Seguridad
TLS 1.3 y OpenSSL 1.1.1 en Linux
.NET Core aprovecha ahora la ventaja de la compatibilidad con TLS 1.3 en OpenSSL 1.1.1, cuando está disponible
en un entorno determinado. Con TLS 1.3:
Se han mejorado los tiempos de conexión con menores recorridos de ida y vuelta necesarios entre el cliente y
servidor.
Se ha mejorado la seguridad gracias a la eliminación de varios algoritmos criptográficos obsoletos y no seguros.
Cuando está disponible, .NET Core 3.0 utiliza OpenSSL 1.1.1 , OpenSSL 1.1.0 o OpenSSL 1.0.2 en un sistema
Linux. Si OpenSSL 1.1.1 está disponible, los tipos System.Net.Security.SslStream y System.Net.Http.HttpClient,
utilizarán TLS 1.3 (suponiendo que el cliente y el servidor admitan TLS 1.3 ).

IMPORTANT
Windows y macOS aún no admiten TLS 1.3 . .NET Core 3.0 será compatible con TLS 1.3 en estos sistemas operativos
cuando haya disponible soporte técnico.

El siguiente ejemplo de C# 8.0 muestra .NET Core 3.0 en Ubuntu 18.10 al conectarse a https://www.cloudflare.com:
using System;
using System.Net.Security;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace whats_new
{
public static class TLS
{
public static async Task ConnectCloudFlare()
{
var targetHost = "www.cloudflare.com";

using TcpClient tcpClient = new TcpClient();

await tcpClient.ConnectAsync(targetHost, 443);

using SslStream sslStream = new SslStream(tcpClient.GetStream());

await sslStream.AuthenticateAsClientAsync(targetHost);
await Console.Out.WriteLineAsync($"Connected to {targetHost} with {sslStream.SslProtocol}");
}
}
}

Cifrados de criptografía
.NET 3.0 agrega compatibilidad con los cifrados AES-GCM y AES-CCM , que se implementan con
System.Security.Cryptography.AesGcm y System.Security.Cryptography.AesCcm respectivamente. Estos dos
algoritmos son algoritmos AEAD (Authenticated Encryption with Associated Data).
El código siguiente muestra cómo utilizar cifrado AesGcm para cifrar y descifrar datos aleatorios.
using System;
using System.Linq;
using System.Security.Cryptography;

namespace whats_new
{
public static class Cipher
{
public static void Run()
{
// key should be: pre-known, derived, or transported via another channel, such as RSA encryption
byte[] key = new byte[16];
RandomNumberGenerator.Fill(key);

byte[] nonce = new byte[12];


RandomNumberGenerator.Fill(nonce);

// normally this would be your data


byte[] dataToEncrypt = new byte[1234];
byte[] associatedData = new byte[333];
RandomNumberGenerator.Fill(dataToEncrypt);
RandomNumberGenerator.Fill(associatedData);

// these will be filled during the encryption


byte[] tag = new byte[16];
byte[] ciphertext = new byte[dataToEncrypt.Length];

using (AesGcm aesGcm = new AesGcm(key))


{
aesGcm.Encrypt(nonce, dataToEncrypt, ciphertext, tag, associatedData);
}

// tag, nonce, ciphertext, associatedData should be sent to the other part

byte[] decryptedData = new byte[ciphertext.Length];

using (AesGcm aesGcm = new AesGcm(key))


{
aesGcm.Decrypt(nonce, ciphertext, tag, decryptedData, associatedData);
}

// do something with the data


// this should always print that data is the same
Console.WriteLine($"AES-GCM: Decrypted data is {(dataToEncrypt.SequenceEqual(decryptedData) ? "the
same as" : "different than")} original data.");
}
}
}

Importación y exportación de claves criptográfica


.NET Core 3.0 admite la importación y exportación de claves asimétricas públicas y privadas en formatos estándar.
No es necesario utilizar un certificado X.509.
Todos los tipos de clave, como RSA, DSA, ECDsa y ECDiffieHellman, admiten los siguientes formatos:
Clave pública
SubjectPublicKeyInfo X.509
Clave privada
PrivateKeyInfo PKCS#8
EncryptedPrivateKeyInfo PKCS#8
Las claves RSA también admiten:
Clave pública
RSAPublicKey PKCS#1
Clave privada
RSAPrivateKey PKCS#1
Los métodos de exportación generan datos binarios con codificación DER y los métodos de importación esperan lo
mismo. Si una clave se almacena en el formato PEM de texto descriptivo, el llamador debe descodificar en base64
el contenido antes de llamar a un método de importación.

using System;
using System.Security.Cryptography;

namespace whats_new
{
public static class RSATest
{
public static void Run(string keyFile)
{
using var rsa = RSA.Create();

byte[] keyBytes = System.IO.File.ReadAllBytes(keyFile);


rsa.ImportRSAPrivateKey(keyBytes, out int bytesRead);

Console.WriteLine($"Read {bytesRead} bytes, {keyBytes.Length - bytesRead} extra byte(s) in file.");


RSAParameters rsaParameters = rsa.ExportParameters(true);
Console.WriteLine(BitConverter.ToString(rsaParameters.D));
}
}
}

Los archivos PKCS#8 se pueden inspeccionar con System.Security.Cryptography.Pkcs.Pkcs8PrivateKeyInfo y los


archivos PFX/PKCS#12 se pueden inspeccionar con System.Security.Cryptography.Pkcs.Pkcs12Info. Los archivos
PFX/PKCS#12 se pueden manipular con System.Security.Cryptography.Pkcs.Pkcs12Builder.

Cambios de API en .NET Core 3.0


Rangos e índices
El nuevo tipo System.Index se puede utilizar para la indización. Puede crear uno desde un índice int que cuente
desde el principio o con un operador ^ de prefijo (C#) que cuente desde el final:

Index i1 = 3; // number 3 from beginning


Index i2 = ^4; // number 4 from end
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "3, 6"

Existe también el tipo System.Range, que consta de dos valores Index , uno para el inicio y otro para el final, y se
puede escribir con una expresión de intervalo x..y (C#). Luego puede crear un índice con un Range , lo que
genera un segmento:

var slice = a[i1..i2]; // { 3, 4, 5 }

Para obtener más información, vea el tutorial sobre intervalos e índices.


Flujos asincrónicos
El tipo IAsyncEnumerable<T> es una nueva versión asincrónica de IEnumerable<T>. El lenguaje permite ejecutar la
instrucción await foreach en IAsyncEnumerable<T> para consumir sus elementos, y usar la instrucción
yield return en ellos para generar los elementos.
En el ejemplo siguiente se muestra la producción y el consumo de flujos asincrónicos. La instrucción foreach es
asincrónica y usa yield return para generar un flujo asincrónico para los llamadores. Este patrón (que usa
yield return ) es el modelo recomendado para generar flujos asincrónicos.

async IAsyncEnumerable<int> GetBigResultsAsync()


{
await foreach (var result in GetResultsAsync())
{
if (result > 20) yield return result;
}
}

Además de poder ejecutar await foreach , también puede crear iteradores asincrónicos, por ejemplo, uno que
devuelva un enumerador IAsyncEnumerable/IAsyncEnumerator en el que pueda ejecutar await y yield . Para los
objetos que deban eliminarse, puede usar IAsyncDisposable , que implementan varios tipos BCL, como Stream y
Timer .

Para obtener más información, vea el tutorial sobre flujos asincrónicos.


Punto flotante de IEEE
Las API de punto flotante se actualizan para cumplir con la revisión IEEE 754-2008. El objetivo de estos cambios es
exponer todas operaciones requeridas y asegurarse de que cumplen con la especificación IEEE. Para obtener más
información sobre las mejoras de punto flotante, vea la entrada de blog sobre mejoras de formato y análisis de
punto flotante en .NET Core 3.0.
Entre las correcciones de análisis y formato se incluyen:
Analice y redondee entradas de cualquier longitud correctamente.
Analice y formatee el cero negativo correctamente.
Análisis correcto de Infinity y NaN al hacer una comprobación que no distingue mayúsculas de minúsculas y
permitir un + anterior opcional cuando corresponda.
Entre las nuevas API System.Math se incluyen:
BitIncrement(Double) y BitDecrement(Double)
Corresponde a las operaciones IEEE nextUp y nextDown . Devuelven el número de punto flotante más
pequeño que compara mayor o menor que la entrada (respectivamente). Por ejemplo,
Math.BitIncrement(0.0) devolvería double.Epsilon .

MaxMagnitude(Double, Double) y MinMagnitude(Double, Double)


Corresponde a las operaciones IEEE maxNumMag y minNumMag , que devuelven el valor que es mayor o menor
en magnitud de las dos entradas (respectivamente). Por ejemplo, Math.MaxMagnitude(2.0, -3.0) devolvería
-3.0 .

ILogB(Double)
Corresponde a la operación IEEE logB que devuelve un valor entero, devuelve el logaritmo en base 2
integral del parámetro de entrada. Este método es efectivamente el mismo que floor(log2(x)) , pero con
errores de redondeo mínimo.
ScaleB(Double, Int32)
Corresponde a la operación IEEE scaleB que toma un valor integral, devuelve eficazmente x * pow(2, n) ,
pero se realiza con errores de redondeo mínimo.
Log2(Double)
Corresponde a la operación IEEE log2 . Devuelve el logaritmo de base 2. Minimiza el error de redondeo.
FusedMultiplyAdd(Double, Double, Double)
Corresponde a la operación IEEE fma . Realiza una multiplicación y suma fusionadas. Es decir, realiza
(x * y) + z como operación única, de forma que se minimiza el error de redondeo. Un ejemplo es
FusedMultiplyAdd(1e308, 2.0, -1e308) , que devuelve 1e308 . La operación (1e308 * 2.0) - 1e308 regular
devuelve double.PositiveInfinity .
CopySign(Double, Double)
Corresponde a la operación IEEE copySign . Devuelve el valor de x , pero con el signo de y .
Elementos intrínsecos dependientes de la plataforma .NET
Se han agregado API que permiten el acceso a determinadas instrucciones CPU orientadas al rendimiento, como
los conjuntos de instrucciones de manipulación de bits o SIMD . Estas instrucciones pueden ayudar a
conseguir importantes mejoras de rendimiento en ciertos escenarios, como el procesamiento de datos con
eficiencia en paralelo.
En su caso, las bibliotecas de .NET han comenzado a utilizar estas instrucciones para mejorar el rendimiento.
Para obtener más información, consulte el artículo sobre elementos intrínsecos dependientes de la plataforma .NET.
API de versión mejoradas de .NET Core
A partir de .NET Core 3.0, las API de versión incluidas con .NET Core ahora devuelven la información que se espera.
Por ejemplo:

System.Console.WriteLine($"Environment.Version: {System.Environment.Version}");

// Old result
// Environment.Version: 4.0.30319.42000
//
// New result
// Environment.Version: 3.0.0

System.Console.WriteLine($"RuntimeInformation.FrameworkDescription:
{System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}");

// Old result
// RuntimeInformation.FrameworkDescription: .NET Core 4.6.27415.71
//
// New result (notice the value includes any preview release information)
// RuntimeInformation.FrameworkDescription: .NET Core 3.0.0-preview4-27615-11

WARNING
Cambio importante. Se trata técnicamente de un cambio importante, porque ha cambiado el esquema de control de
versiones.

Compatibilidad con JSON integrada con rápido rendimiento


Los usuarios de .NET se han basado en gran medida en Newtonsoft.Json y otras bibliotecas populares de JSON,
que siguen siendo buenas opciones. Newtonsoft.Json usa cadenas de .NET como tipo de datos base, que, en
esencia, es UTF-16.
La nueva compatibilidad integrada con JSON es de alto rendimiento, baja asignación y funciona con texto JSON
codificado UTF-8. Para obtener más información sobre el espacio de nombres System.Text.Json y los tipos, vea los
artículos siguientes:
Serialización JSON en .NET: información general
En Procedimiento para serializar y deserializar JSON en .NET
Migración desde Newtonsoft.json a System.Text.Json
Compatibilidad con HTTP/2
El tipo System.Net.Http.HttpClient es compatible con el protocolo HTTP/2. Si se habilita HTTP/2, la versión del
protocolo HTTP se negocia a través de TLS/ALPN y HTTP/2 solo se usa si el servidor opta por usarlo.
El protocolo predeterminado sigue siendo HTTP/1.1, pero se puede habilitar HTTP/2 de dos maneras diferentes. En
primer lugar, puede establecer el mensaje de solicitud HTTP para usar HTTP/2:

var client = new HttpClient() { BaseAddress = new Uri("https://localhost:5001") };

// HTTP/1.1 request
using (var response = await client.GetAsync("/"))
Console.WriteLine(response.Content);

// HTTP/2 request
using (var request = new HttpRequestMessage(HttpMethod.Get, "/") { Version = new Version(2, 0) })
using (var response = await client.SendAsync(request))
Console.WriteLine(response.Content);

En segundo lugar, puede cambiar HttpClient para usar HTTP/2 de forma predeterminada:

var client = new HttpClient()


{
BaseAddress = new Uri("https://localhost:5001"),
DefaultRequestVersion = new Version(2, 0)
};

// HTTP/2 is default
using (var response = await client.GetAsync("/"))
Console.WriteLine(response.Content);

Muchas veces cuando está desarrollando una aplicación, desea utilizar una conexión no cifrada. Si sabe que el
punto de conexión de destino utilizará HTTP/2, puede activar las conexiones no cifradas para HTTP/2. Puede
activarlas estableciendo la variable de entorno DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2UNENCRYPTEDSUPPORT
en 1 o habilitándolas en el contexto de la aplicación:

AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);

Pasos siguientes
Revise los cambios importantes entre .NET Core 2.2 y 3.0.
Revise los cambios importantes de .NET Core 3.0 para aplicaciones de Windows Forms.
Novedades de .NET Core 2.2
16/09/2020 • 8 minutes to read • Edit Online

.NET Core 2.2 incluye mejoras en la implementación de aplicaciones, en el control de eventos de los servicios en
tiempo de ejecución, en la autenticación de bases de datos SQL de Azure, en el rendimiento del compilador JIT y la
inyección de código antes de la ejecución del método Main .

Nuevo modo de implementación


A partir de .NET Core 2.2, puede implementar archivos ejecutables dependientes del marco, que son archivos .exe
en lugar de .dll . Los archivos ejecutable dependientes del marco (FDE), que funcionan de forma similar a las
implementaciones dependientes del marco, todavía se basan en la presencia de una versión compartida por todo el
sistema de .NET Core para ejecutar. Su aplicación contiene solo el código y cualquier dependencia de terceros. A
diferencia de las implementaciones dependientes del marco, los FDE son específicos de la plataforma.
Este nuevo modo de implementación tiene la ventaja de compilar un archivo ejecutable en lugar de una biblioteca,
lo que significa que puede ejecutar la aplicación directamente sin invocar dotnet primero.

Principal
Control de eventos en los ser vicios en tiempo de ejecución
A menudo es posible que desee supervisar el uso que hace la aplicación de los servicios de tiempo de ejecución,
como GC, JIT y ThreadPool, para comprender cómo afectan a la aplicación. En los sistemas Windows, esto se hace
normalmente mediante la supervisión de los eventos ETW del proceso actual. Aunque este método sigue
funcionando bien, no siempre es posible usar ETW si la ejecución se realiza en un entorno con pocos privilegios o
en Linux o macOS.
A partir de .NET Core 2.2, ahora se pueden consumir eventos CoreCLR utilizando la clase
System.Diagnostics.Tracing.EventListener. Estos eventos describen el comportamiento de esos servicios en tiempo
de ejecución como la interoperabilidad, ThreadPool, JIT y GC. Estos son los mismos eventos que se exponen como
parte del proveedor ETW de CoreCLR. De esta forma, las aplicaciones pueden consumir estos eventos o usar un
mecanismo de transporte para enviarlos a un servicio de agregación de telemetría. Puede ver cómo suscribirse a
eventos en el código de ejemplo siguiente:
internal sealed class SimpleEventListener : EventListener
{
// Called whenever an EventSource is created.
protected override void OnEventSourceCreated(EventSource eventSource)
{
// Watch for the .NET runtime EventSource and enable all of its events.
if (eventSource.Name.Equals("Microsoft-Windows-DotNETRuntime"))
{
EnableEvents(eventSource, EventLevel.Verbose, (EventKeywords)(-1));
}
}

// Called whenever an event is written.


protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
// Write the contents of the event to the console.
Console.WriteLine($"ThreadID = {eventData.OSThreadId} ID = {eventData.EventId} Name =
{eventData.EventName}");
for (int i = 0; i < eventData.Payload.Count; i++)
{
string payloadString = eventData.Payload[i]?.ToString() ?? string.Empty;
Console.WriteLine($"\tName = \"{eventData.PayloadNames[i]}\" Value = \"{payloadString}\"");
}
Console.WriteLine("\n");
}
}

Además, .NET Core 2.2 agrega las dos propiedades siguientes a la clase EventWrittenEventArgs para proporcionar
información adicional sobre los eventos ETW:
EventWrittenEventArgs.OSThreadId
EventWrittenEventArgs.TimeStamp

Datos
Autenticación de AAD en bases de datos SQL de Azure con la propiedad SqlConnection.AccessToken
A partir de .NET Core 2.2, puede usarse un token de acceso emitido por Azure Active Directory para autenticarse en
una base de datos SQL de Azure. Para admitir tokens de acceso, la propiedad AccessToken se ha agregado a la clase
SqlConnection. Para aprovechar las ventajas de la autenticación de AAD, descargue la versión 4.6 del paquete
System.Data.SqlClient NuGet. Para poder usar la característica, puede obtener el valor del token de acceso mediante
la biblioteca de autenticación de Active Directory para .NET contenida en el paquete NuGet
Microsoft.IdentityModel.Clients.ActiveDirectory .

Mejoras del compilador JIT


La compilación en niveles sigue siendo una característica opcional
En .NET Core 2.1, el compilador JIT implementó una nueva tecnología de compilador, la compilación en niveles,
como característica opcional. El objetivo de la compilación en niveles es mejorar el rendimiento. Una de las tareas
importantes realizadas por el compilador JIT es optimizar la ejecución de código. Sin embargo, para las rutas de
código poco utilizadas, el compilador puede dedicar más tiempo a la optimización del código del que dedica el
tiempo de ejecución al ejecutar el código no optimizado. La compilación por niveles incluye dos fases en la
compilación JIT:
Un primer nivel , que genera código tan pronto como sea posible.
Un segundo nivel , que genera código optimizado para los métodos que se ejecutan con frecuencia. El
segundo nivel de la compilación se realiza en paralelo para mejorar el rendimiento.
Para obtener información sobre la mejora del rendimiento que puede obtenerse gracias a la compilación en niveles,
vea Announcing .NET Core 2.2 Preview 2 (Anuncio de la versión preliminar 2 de .NET Core 2.2).
En la versión preliminar 2 de .NET Core 2.2, la compilación en niveles se habilitó de forma predeterminada. Sin
embargo, hemos decidido que aún no estamos listos para habilitar la compilación en niveles de forma
predeterminada. Por tanto, en .NET Core 2.2, la compilación en nieves sigue siendo una característica opcional. Para
obtener información sobre cómo usar la compilación en niveles, vea Mejoras del compilador JIT en Novedades de
.NET Core 2.1.

Tiempo de ejecución
Inyección de código antes de ejecutar el método Main
A partir de .NET Core 2.2, puede usar un enlace de inicio para inyectar código antes de ejecutar el método Main de
la aplicación. Los enlaces de inicio permiten a un host personalizar el comportamiento de las aplicaciones una vez
que se hayan implementado sin necesidad de volver a compilar o cambiar la aplicación.
Los proveedores de hospedaje deberían definir la directiva y la configuración personalizadas, incluida la
configuración que posiblemente influya en el comportamiento de carga del punto de entrada principal, como el
comportamiento System.Runtime.Loader.AssemblyLoadContext . El enlace puede usarse para configurar la
inyección de telemetría o el seguimiento, configurar las devoluciones de llamada para el control, así como definir
otros comportamientos dependientes del entorno. El enlace es independiente del punto de entrada, por lo que no
es necesario modificar el código de usuario.
Consulte Host startup hook (Hospedaje del enlace de inicio) para obtener más información.

Vea también
Novedades de .NET Core 3.1
Novedades de ASP.NET Core 2.2
Novedades de EF Core 2.2
Novedades de .NET Core 2.1
16/09/2020 • 21 minutes to read • Edit Online

.NET Core 2.1 incluye mejoras y características nuevas en las áreas siguientes:
Herramientas
Puesta al día
Implementación
Paquete de compatibilidad de Windows
Mejoras de la compilación JIT
Cambios en la API

Tooling
El SDK de .NET Core 2.1 (v 2.1.300), el conjunto de herramientas incluidas con .NET Core 2.1, incluye los siguientes
cambios y mejoras:
Mejoras en el rendimiento de la compilación
Un aspecto fundamental de .NET Core 2.1 es mejorar el rendimiento del tiempo de compilación, especialmente
para compilaciones incrementales. Estas mejoras de rendimiento se aplican a las compilaciones de línea de
comandos mediante dotnet build y a las compilaciones de Visual Studio. Algunas áreas individuales de mejora
incluyen:
Para la resolución de activos del paquete, solo se resuelven los activos utilizados por una compilación en
lugar de todos los activos.
Almacenamiento en caché de referencias de ensamblado.
Uso de servidores de compilación de SDK de larga ejecución, que son procesos que se extienden a través de
invocaciones dotnet build individuales. Eliminan la necesidad de compilar mediante JIT grandes bloques
de código cada vez que se ejecuta dotnet build . Los procesos del servidor de compilación se pueden
terminar automáticamente con el siguiente comando:

dotnet buildserver shutdown

Nuevos comandos de la CLI


Una serie de herramientas que estaban disponibles solo en función del proyecto mediante
DotnetCliToolReference ahora están disponibles como parte del SDK de .NET Core. Estas herramientas incluyen:

dotnet watch proporciona un monitor del sistema de archivos que espera que un archivo cambie antes de
ejecutar un conjunto designado de comandos. Por ejemplo, el siguiente comando vuelve a generar
automáticamente el proyecto actual y genera un resultado detallado cada vez que cambie un archivo en él:

dotnet watch -- --verbose build

Tenga en cuenta que la opción -- precede a la opción --verbose . Delimita las opciones pasadas
directamente al comando dotnet watch de los argumentos que se pasan al proceso dotnet secundario.
Sin él, la opción --verbose se aplica al comando dotnet watch , no al comando dotnet build .
Para más información, consulte Desarrollar aplicaciones ASP.NET Core con un monitor de archivos.
dotnet dev-certs genera y administra los certificados que se usan durante el desarrollo de aplicaciones de
ASP.NET Core.
dotnet user-secrets administra los secretos en un almacén de secretos de usuario en aplicaciones de
ASP.NET Core.
dotnet sql-cachecrea una tabla e índices en una base de datos de Microsoft SQL Server que se usará para
el almacenamiento en caché distribuido.
dotnet ef es una herramienta para administrar bases de datos, objetos DbContext y migraciones en las
aplicaciones de Entity Framework Core. Para obtener más información, vea EF Core .NET Command-line
Tools (Herramienta de la línea de comandos de .NET de EF Core).
Herramientas globales
.NET core 2.1 es compatible con Herramientas globales: es decir, las herramientas personalizadas que están
disponibles globalmente desde la línea de comandos. El modelo de extensibilidad en versiones previas de
.NET Core permitió que las herramientas personalizadas estuvieran disponibles en cada proyecto solo utilizando
DotnetCliToolReference .

Para instalar una herramienta global, use el comando dotnet tool install. Por ejemplo:

dotnet tool install -g dotnetsay

Una vez instalada, la herramienta se puede ejecutar desde la línea de comandos especificando el nombre de la
herramienta. Para más información, vea Información general sobre las herramientas globales de .NET Core.
Administración de herramientas con el comando dotnet tool

En el SDK de .NET Core 2.1, todas las operaciones de herramientas utilizan el comando dotnet tool . Están
disponibles las siguientes opciones:
dotnet tool install para instalar una herramienta.
dotnet tool update para desinstalar y reinstalar una herramienta, lo cual la actualiza eficazmente.
dotnet tool list para enumerar las herramientas instaladas actualmente.
dotnet tool uninstall para desinstalar las herramientas instaladas actualmente.

Puesta al día
Todas las aplicaciones de .NET Core a partir de .NET Core 2.0 se ponen al día automáticamente a la versión
secundaria más reciente instalada en un sistema.
A partir de .NET Core 2.0, si la versión de .NET Core que se creó una aplicación no está presente en tiempo de
ejecución, la aplicación se ejecuta automáticamente en la versión secundaria instalada más reciente de .NET Core.
En otras palabras, si una aplicación se compila con .NET Core 2.0, y .NET Core 2.0 no está presente en el sistema
host pero sí lo está .NET Core 2.1, la aplicación se ejecuta con .NET Core 2.1.

IMPORTANT
Este comportamiento de puesta al día no se aplica para versiones preliminares. De forma predeterminada, tampoco se aplica
a las versiones principales, pero se puede cambiar con las opciones siguientes.

Este comportamiento se puede modificar si se cambia la configuración para la puesta al día en los marcos de
trabajo compartidos que no sean candidatos. Los valores disponibles son los siguientes:
0 : se deshabilita el comportamiento de puesta al día de las versiones secundarias. Con este valor, una
aplicación compilada para .NET Core 2.0.0 se pondrá al día a .NET Core 2.0.1, pero no a .NET Core 2.2.0 ni .NET
Core 3.0.0.
1 : se habilita el comportamiento de puesta al día de las versiones secundarias. Este es el valor
predeterminado de la opción. Con este valor, una aplicación compilada para .NET Core 2.0.0 se pondrá al día a
.NET Core 2.0.1 o .NET Core 2.2.0, en función de la versión instalada, pero no a .NET Core 3.0.0.
2 : se habilita el comportamiento de puesta al día de las versiones principales y secundarias. Si se establece, se
tienen en cuenta incluso versiones principales diferentes, por lo que una aplicación compilada para .NET Core
2.0.0 se pondrá al día a .NET Core 3.0.0.
Esta configuración se puede modificar de estas tres maneras:
Si se establece la variable de entorno DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX en el valor deseado.
Agregue la línea siguiente con el valor deseado al archivo .runtimeconfig.json:

"rollForwardOnNoCandidateFx" : 0

Cuando se usa la CLI de .NET Core, si se agrega la opción siguiente con el valor deseado a un comando de
.NET Core como run :

dotnet run --rollForwardOnNoCandidateFx=0

La puesta al día de versiones de revisión es independiente de esta configuración y se realiza después de aplicar la
puesta al día de cualquier versión principal o secundaria posible.

Implementación
Mantenimiento de aplicaciones independientes
dotnet publish ahora publica aplicaciones independientes con una versión en tiempo de ejecución con
mantenimiento. Cuando publica una aplicación independiente con el SDK 2.1 de .NET Core (v. 2.1.300), su
aplicación incluye la última versión de tiempo de ejecución con mantenimiento conocida por ese SDK. Cuando
actualice a la versión más reciente del SDK, publicará con la versión más reciente del tiempo de ejecución de
.NET Core. Esto se aplica para tiempos de ejecución de .NET Core 1.0 y versiones posteriores.
La publicación independiente se basa en las versiones del tiempo de ejecución en NuGet.org. No necesita tener el
tiempo de ejecución con mantenimiento en su máquina.
Con el SDK de .NET Core 2.0, las aplicaciones independientes se publican con el tiempo de ejecución de .NET Core
2.0.0 a menos que se especifique una versión diferente a través de la propiedad RuntimeFrameworkVersion . Con
este nuevo comportamiento, ya no será necesario configurar esta propiedad para seleccionar una versión de
tiempo de ejecución más alta para una aplicación independiente. El enfoque más sencillo a partir de ahora es
publicar siempre con el SDK de .NET Core 2.1 (v. 2.1.300).
Para obtener más información, vea Puesta al día del runtime de implementación autocontenida.

Paquete de compatibilidad de Windows


Al trasladar el código existente de .NET Framework a .NET Core, puede usar el paquete de compatibilidad de
Windows. Esto proporciona acceso a 20 000 API más de las que están disponibles en .NET Core. Estas API incluyen
tipos en el espacio de nombres System.Drawing, la clase EventLog, WMI, contadores de rendimiento, servicios de
Windows y los tipos y miembros de Registro de Windows.
Mejoras del compilador JIT
.NET Core incorpora una nueva tecnología de compilador JIT denominada compilación por niveles (también
conocida como optimización adaptable) que puede mejorar significativamente el rendimiento. La compilación por
niveles es una configuración opcional.
Una de las tareas importantes realizadas por el compilador JIT es optimizar la ejecución de código. Sin embargo,
para las rutas de código poco utilizadas, el compilador puede dedicar más tiempo a la optimización del código del
que dedica el tiempo de ejecución a ejecutar el código no optimizado. La compilación por niveles incluye dos fases
en la compilación JIT:
Un primer nivel , que genera código tan pronto como sea posible.
Un segundo nivel , que genera código optimizado para los métodos que se ejecutan con frecuencia. El
segundo nivel de la compilación se realiza en paralelo para mejorar el rendimiento.
Puede participar en la compilación en niveles de cualquiera de estas dos maneras.
Para utilizar la compilación en capas en todos los proyectos que utilizan el SDK de .NET Core 2.1, establezca
la variable de entorno siguiente:

COMPlus_TieredCompilation="1"

Para utilizar la compilación por niveles por proyecto, agregue la propiedad <TieredCompilation> a la
sección <PropertyGroup> del archivo de proyecto de MSBuild, como se muestra en el ejemplo siguiente:

<PropertyGroup>
<!-- other property definitions -->

<TieredCompilation>true</TieredCompilation>
</PropertyGroup>

Cambios en la API
Span<T> y Memory<T>

.NET Core 2.1 incluye algunos tipos nuevos que hacen que trabajar con matrices y otros tipos de memoria sea
mucho más eficiente. Estos nuevos tipos incluyen:
System.Span<T> y System.ReadOnlySpan<T>.
System.Memory<T> y System.ReadOnlyMemory<T>.
Sin estos tipos, al pasar tales elementos como una porción de una matriz o una sección de un búfer de memoria,
debe hacer una copia de una parte de los datos antes de pasarlo a un método. Estos tipos proporcionan una vista
virtual de los datos que elimina la necesidad de ejecutar operaciones adicionales de copia y asignación de
memoria.
En el ejemplo siguiente se usa una instancia de Span<T> y Memory<T> para proporcionar una vista virtual de 10
elementos de una matriz.
using System;

class Program
{
static void Main()
{
int[] numbers = new int[100];
for (int i = 0; i < 100; i++)
{
numbers[i] = i * 2;
}

var part = new Span<int>(numbers, start: 10, length: 10);


foreach (var value in part)
Console.Write($"{value} ");
}
}
// The example displays the following output:
// 20 22 24 26 28 30 32 34 36 38

Module Program
Sub Main()
Dim numbers As Integer() = New Integer(99) {}

For i As Integer = 0 To 99
numbers(i) = i * 2
Next

Dim part = New Memory(Of Integer)(numbers, start:=10, length:=10)

For Each value In part.Span


Console.Write($"{value} ")
Next
End Sub
End Module
' The example displays the following output:
' 20 22 24 26 28 30 32 34 36 38

Compresión de Brotli
.NET Core 2.1 agrega compatibilidad con la compresión y descompresión de Brotli. Brotli es un algoritmo de
compresión sin pérdida de datos de uso general que se define en RFC 7932 y es compatible con la mayoría de los
exploradores web y servidores web principales. Puede usar la clase System.IO.Compression.BrotliStream basada
en secuencias o las clases System.IO.Compression.BrotliEncoder y System.IO.Compression.BrotliDecoder basadas
en intervalos de alto rendimiento. En el ejemplo siguiente se muestra la compresión con la clase BrotliStream:

public static Stream DecompressWithBrotli(Stream toDecompress)


{
MemoryStream decompressedStream = new MemoryStream();
using (BrotliStream decompressionStream = new BrotliStream(toDecompress, CompressionMode.Decompress))
{
decompressionStream.CopyTo(decompressedStream);
}
decompressedStream.Position = 0;
return decompressedStream;
}
Public Function DecompressWithBrotli(toDecompress As Stream) As Stream
Dim decompressedStream As New MemoryStream()
Using decompressionStream As New BrotliStream(toDecompress, CompressionMode.Decompress)
decompressionStream.CopyTo(decompressedStream)
End Using
decompressedStream.Position = 0
Return decompressedStream
End Function

El comportamiento de BrotliStream es el mismo que DeflateStream y GZipStream, lo que facilita la conversión de


código que llama a estas API a BrotliStream.
Nuevas API de criptografía y mejoras de criptografía
.NET Core 2.1 incluye numerosas mejoras para las API de criptografía:
System.Security.Cryptography.Pkcs.SignedCms está disponible en el paquete
System.Security.Cryptography.Pkcs. La implementación es la misma que la clase SignedCms en .NET
Framework.
Las nuevas sobrecargas de los métodos X509Certificate.GetCertHash y X509Certificate.GetCertHashString
aceptan un identificador de algoritmo hash para permitir que los autores de la llamada obtengan valores de
huella digital de certificado utilizando algoritmos distintos de SHA-1.
Las nuevas API de criptografía basadas en Span<T> están disponibles para crear valores hash, HMAC,
generación de números aleatorios criptográficos, generación de firmas asimétricas, procesamiento de
firmas asimétricas y cifrado RSA.
El rendimiento de System.Security.Cryptography.Rfc2898DeriveBytes mejoró aproximadamente un 15 %
mediante el uso de una implementación basada en Span<T>.
La nueva clase System.Security.Cryptography.CryptographicOperations incluye dos nuevos métodos:
FixedTimeEquals tarda una cantidad fija de tiempo en devolver dos entradas cualesquiera de la
misma longitud, siendo así adecuada para su uso en la comprobación criptográfica para evitar
contribuir al control de tiempo de la información en el canal.
ZeroMemory es una rutina de borrado de memoria que no se puede optimizar.
El método estático RandomNumberGenerator.Fill rellena un Span<T> con valores aleatorios.
System.Security.Cryptography.Pkcs.EnvelopedCms ahora es compatible con Linux y macOS.
La curva elíptica Diffie-Hellman (ECDH) ahora está disponible en la familia de clases
System.Security.Cryptography.ECDiffieHellman. El área expuesta es la misma que en .NET Framework.
La instancia devuelta por RSA.Create puede cifrar o descifrar con OAEP con un resumen de SHA-2, así como
generar o validar firmas mediante RSA-PSS.
Mejoras en los sockets
.NET Core incluye un nuevo tipo, System.Net.Http.SocketsHttpHandler y una reescritura
System.Net.Http.HttpMessageHandler, que forman la base de las API de red de nivel superior.
System.Net.Http.SocketsHttpHandler, por ejemplo, es la base de la implementación HttpClient. En versiones
anteriores de .NET Core, las API de nivel superior se basaban en implementaciones de redes nativas.
La implementación de sockets introducida en .NET Core 2.1 presenta una serie de ventajas:
Una mejora significativa del rendimiento en comparación con la implementación anterior.
Eliminación de las dependencias de plataforma, lo que simplifica la implementación y el mantenimiento.
Comportamiento coherente en todas las plataformas de .NET Core.
SocketsHttpHandler es la implementación predeterminada en .NET Core 2.1. Sin embargo, puede configurar su
aplicación para usar la clase anterior HttpClientHandler llamando al método AppContext.SetSwitch:

AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);

AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", False)

También puede usar una variable de entorno para optar por no usar implementaciones de sockets basadas en
SocketsHttpHandler. Para ello, configure DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER en false o en 0.
En Windows, también puede optar por usar System.Net.Http.WinHttpHandler, que se basa en una implementación
nativa, o la clase SocketsHttpHandler pasando una instancia de la clase al constructor HttpClient.
En Linux y macOS, solo se puede configurar HttpClient para cada proceso. En Linux, deberá implementar libcurl si
desea usar la antigua implementación de HttpClient. (Se instala con .NET Core 2.0).
Cambios importantes
Para obtener más información sobre los cambios importantes, vea Cambios importantes para la migración de la
versión 2.0 a la 2.1.

Vea también
Novedades de .NET Core 3.1
Novedades de EF Core 2.1
Novedades de ASP.NET Core 2.1
Novedades de .NET Core 2.0
18/03/2020 • 13 minutes to read • Edit Online

.NET Core 2.0 incluye mejoras y características nuevas en las áreas siguientes:
Herramientas
Compatibilidad con lenguajes
Mejoras en la plataforma
Cambios en la API
Integración de Visual Studio
Mejoras en la documentación

Tooling
dotnet restore se ejecuta de manera implícita
En las versiones anteriores de .NET Core, era necesario ejecutar el comando dotnet restore para descargar
dependencias inmediatamente después de crear un proyecto nuevo con el comando dotnet new, así como también
cada vez que se agregaba una dependencia nueva al proyecto.
No es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que necesitan que
se produzca una restauración, como dotnet new , dotnet build , dotnet run , dotnet test , dotnet publish y
dotnet pack . Para deshabilitar la restauración implícita, use la opción --no-restore .

El comando dotnet restore sigue siendo válido en algunos escenarios donde tiene sentido realizar una
restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los sistemas
de compilación que necesitan controlar explícitamente cuándo se produce la restauración.
Para obtener información sobre cómo administrar fuentes de NuGet, vea la documentación de dotnet restore .
También puede deshabilitar la invocación automática de dotnet restore si pasa el modificador --no-restore a los
comandos new , run , build , publish , pack y test .
Redestinación a .NET Core 2.0
Si el SDK de .NET Core 2.0 SDK está instalado, los proyectos que tienen .NET Core 1.x como destino se pueden
redestinar a .NET Core 2.0.
Para redestinar a .NET Core 2.0, edite el archivo del proyecto cambiando el valor del elemento <TargetFramework>
(o del elemento <TargetFrameworks> , si tiene más de un destino en el archivo del proyecto) de 1.x a 2.0:

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>

También puede redestinar las bibliotecas de .NET Standard a .NET Standard 2.0 del mismo modo:

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

Para más información sobre cómo migrar el proyecto a .NET Core 2.0, consulte el artículo sobre migración de
ASP.NET Core 1.x a ASP.NET Core 2.0.

Compatibilidad con lenguajes


Además de admitir C# y F#, .NET Core 2.0 también es compatible con Visual Basic.
Visual Basic
Con la versión 2.0, .NET Core ahora es compatible con Visual Basic 2017. Puede usar Visual Basic para crear los
tipos de proyecto siguientes:
Aplicaciones de consola .NET Core
Bibliotecas de clases .NET Core
Bibliotecas de clases .NET Standard
Proyectos de prueba unitaria .NET Core
Proyectos de prueba xUnit .NET Core
Por ejemplo, para crear una aplicación "Hola mundo" de Visual Basic, haga estos pasos en la línea de comandos:
1. En una ventana de la consola, cree un directorio para el proyecto y conviértalo en el directorio actual.
2. Escriba el comando dotnet new console -lang vb .
El comando crea un archivo del proyecto con una extensión de archivo .vbproj , además de un archivo de
código fuente de Visual Basic llamado Program.vb. Este archivo contiene el código fuente para escribir la
cadena "Hola mundo" en la ventana de consola.
3. Escriba el comando dotnet run . La CLI de .NET Core compila y ejecuta automáticamente la aplicación, con lo
que se muestra el mensaje "Hola mundo" en la ventana de la consola.
Compatibilidad con C# 7.1
.NET Core 2.0 es compatible con C# 7.1, que agrega varias características nuevas, entre las que se incluyen las
siguientes:
El método Main , el punto de entrada de la aplicación, se puede marcar con la palabra clave async.
Nombres de tupla deducidos.
Expresiones predeterminadas.

Mejoras en la plataforma
.NET Core 2.0 incluye varias características que facilitan la instalación de .NET Core y su uso en sistemas operativos
compatibles.
.NET Core para Linux es una sola implementación
.NET Core 2.0 ofrece una sola implementación de Linux que funciona en varias distribuciones de Linux. .NET Core
1.x requería que descargara una implementación de Linux específica para la distribución.
También puede desarrollar aplicaciones que tienen Linux como destino como un solo sistema operativo. .NET Core
1.x requería que se tuviera cada distribución de Linux como destino de forma independiente.
Compatibilidad con las bibliotecas de cifrado de Apple
.NET Core 1.x en macOS requería la biblioteca de cifrado del kit de herramientas OpenSSL. .NET Core 2.0 usa las
bibliotecas de cifrado de Apple y no requiere OpenSSL, por lo que ya no es necesario instalarlo.

Cambios en la API y compatibilidad con bibliotecas


Compatibilidad con .NET Standard 2.0
.NET Standard define un conjunto de API con control de versiones que debe estar disponible en las
implementaciones de .NET que cumplen con esa versión del estándar. .NET Standard está dirigido a los
desarrolladores de bibliotecas. Pretende garantizar la funcionalidad que está disponible a una biblioteca que tiene
como destino una versión de .NET Standard en cada implementación de .NET. .NET Core 1.x es compatible con la
versión 1.6 de .NET Standard; .NET Core 2.0 es compatible con la versión más reciente, .NET Standard 2.0. Para más
información, consulte .NET Standard.
.NET Standard 2.0 incluye más de 20 000 API más que las disponibles en .NET Standard 1.6. Gran parte de esta área
expuesta expandida es resultado de la incorporación de las API comunes de .NET Framework y Xamarin en .NET
Standard.
Las bibliotecas de clases .NET Standard 2.0 también pueden hacer referencia a bibliotecas de clases .NET
Framework siempre que llamen a las API que existen en .NET Standard 2.0. No es necesario realizar una nueva
compilación de las bibliotecas .NET Framework.
Si desea ver una lista de las API que se agregaron a .NET Standard desde la última versión, .NET Standard 1.6,
consulte el artículo de comparación entre .NET Standard 2.0 y 1.6.
Área expuesta expandida
La cantidad total de API disponibles en .NET Core 2.0 aumentó más del doble en comparación con .NET Core 1.1.
Además, con el paquete de compatibilidad de Windows, la migración desde .NET Framework ahora es mucho más
fácil.
Compatibilidad con las bibliotecas .NET Framework
El código de .NET Core puede hacer referencia a bibliotecas .NET Framework existentes, incluidos paquetes NuGet
existentes. Tenga en cuenta que las bibliotecas deben usar las API que se encuentran en .NET Standard.

integración de Visual Studio


Visual Studio 2017 versión 15.3 y, en algunos casos, Visual Studio para Mac, ofrecen varias mejoras importantes
para los desarrolladores de .NET Core.
Redestinación de aplicaciones .NET Core y bibliotecas .NET Standard
Si el SDK de .NET Core 2.0 SDK está instalado, puede redestinar los proyectos de .NET Core 1.x a .NET Core 2.0 y las
bibliotecas .NET Standard 1.x a .NET Standard 2.0.
Para redestinar el proyecto en Visual Studio, abra la pestaña Aplicación del cuadro de diálogo de propiedades del
proyecto y cambie el valor de plataforma de destino a .NET Core 2.0 o .NET Standard 2.0 . También puede
cambiarlo si hace clic con el botón derecho en el proyecto y selecciona la opción Edit *.csproj file (Editar archivo
.csproj). Para más información, consulte la sección Herramientas anteriormente en este tema.
Compatibilidad con Live Unit Testing en .NET Core
Cada vez que modifique el código, Live Unit Testing ejecuta automáticamente y en segundo plano cualquier prueba
unitaria afectada y presenta los resultados y la cobertura de código en vivo en el entorno de Visual Studio. .NET
Core 2.0 ahora admite Live Unit Testing. Anteriormente, Live Unit Testing solo estaba disponible para aplicaciones
.NET Framework.
Para obtener más información, consulte Live Unit Testing con Visual Studio y las preguntas más frecuentes de Live
Unit Testing.
Mejor compatibilidad con varias plataformas de destino
Si compila un proyecto para varias plataformas de destino, ahora puede seleccionar la plataforma de destino en el
menú de nivel superior. En la figura siguiente, un proyecto llamado SCD1 tiene como destino macOS X 10.11 (
osx.10.11-x64 ) de 64 bits y Windows 10/Windows Server 2016 ( win10-x64 ) de 64 bits. Puede seleccionar la
plataforma de destino antes de seleccionar el botón del proyecto, en este caso para ejecutar una compilación de
depuración.

Compatibilidad en paralelo con SDK de .NET Core


Ahora es posible instalar el SDK de .NET Core de manera independiente de Visual Studio. Esto permite que una
versión única de Visual Studio compile proyectos que tienen como destino distintas versiones de .NET Core.
Anteriormente, Visual Studio y el SDK de .NET Core estaban estrechamente relacionados; una versión específica del
SDK acompañaba a una versión específica de Visual Studio.

Mejoras en la documentación
Arquitectura de aplicación de .NET
Arquitectura de aplicación de .NET le permite acceder a un conjunto de libros electrónicos que ofrecen orientación,
procedimientos recomendados y aplicaciones de ejemplo cuando use .NET para compilar:
Microservicios y contenedores Docker
Aplicaciones web con ASP.NET
Aplicaciones móviles con Xamarin
Aplicaciones que se implementan en la nube con Azure

Vea también
Novedades de ASP.NET Core 2.0
Novedades de .NET Standard
09/04/2020 • 7 minutes to read • Edit Online

.NET Standard es una especificación formal que define un conjunto de API con control de versiones que debe estar
disponible en las implementaciones de .NET que cumplen con esa versión del estándar. .NET Standard está dirigido
a los desarrolladores de bibliotecas. Una biblioteca que tenga como destino una versión estándar de .NET Standard
se puede usar en cualquier implementación de .NET Framework, .NET Core o Xamarin que admita esa versión del
estándar.
.NET Standard se incluye con el SDK de .NET Core, así como con Visual Studio cuando se selecciona la carga de
trabajo de .NET Core.

Implementaciones de .NET compatibles


.NET Standard 2.0 es compatible con las implementaciones siguientes de .NET:
.NET Core 2.0 o versiones posteriores
.NET Framework 4.6.1 o versiones posteriores
Mono 5.4 o versiones posteriores
Xamarin.iOS 10.14 o versiones posteriores
Xamarin.Mac 3.8 o versiones posteriores
Xamarin.Android 8.0 o versiones posteriores
Plataforma universal de Windows 10.0.16299 o versiones posteriores

Novedades de .NET Standard 2.0


.NET Standard 2.0 incluye las siguientes características nuevas:
Un conjunto de API ampliamente expandido
Con la versión 1.6, .NET Standard incluía un subconjunto relativamente pequeño de API. Entre las excluidas estaban
muchas API utilizadas habitualmente en .NET Framework o Xamarin. Esto complica el desarrollo, ya que los
desarrolladores tienen que encontrar sustitutas adecuadas para API familiares para desarrollar aplicaciones y
bibliotecas destinadas a varias implementaciones de .NET. .NET Standard 2.0 soluciona esta limitación con la
incorporación de más de 20 000 API más de las que estaban disponibles en .NET Standard 1.6, la versión anterior
del estándar. Si quiere obtener una lista de las API agregadas a .NET Standard 2.0, vea comparación entre .NET
Standard 2.0 y 1.6.
Alguna de las adiciones al espacio de nombres System de .NET Standard 2.0 incluyen:
Compatibilidad con la clase AppDomain.
Mayor compatibilidad para trabajar con matrices de miembros adicionales en la clase Array.
Mayor compatibilidad para trabajar con atributos de miembros adicionales en la clase Attribute.
Mayor compatibilidad del calendario y opciones de formato adicionales para los valores DateTime.
Funcionalidad de redondeo Decimal adicional.
Funcionalidad adicional en la clase Environment.
Control mejorado sobre el recolector de elementos no utilizados en la clase GC.
Compatibilidad mejorada para la comparación de cadenas, la enumeración y la normalización en la clase String.
Compatibilidad para los tiempos de transición y los ajustes del horario de verano en las clases
TimeZoneInfo.AdjustmentRule y TimeZoneInfo.TransitionTime.
Funcionalidad mejorada significativamente en la clase Type.
Mejor compatibilidad para la deserialización de objetos de excepción mediante la adición de un constructor de
excepción con los parámetros SerializationInfo y StreamingContext.
Compatibilidad con las bibliotecas .NET Framework
La gran mayoría de las bibliotecas tienen como destino .NET Framework en lugar de .NET Standard. Sin embargo, la
mayoría de las llamadas de esas bibliotecas se realizan a las API incluidas en .NET Standard 2.0. A partir de .NET
Standard 2.0, puede acceder a las bibliotecas de .NET Framework desde una biblioteca de .NET Standard usando
una corrección de compatibilidad. Este nivel de compatibilidad es transparente para los desarrolladores; no tiene
que hacer nada para aprovechar las ventajas de las bibliotecas de .NET Framework.
El único requisito es que las API a las que llaman las bibliotecas de clases .NET Framework estén incluidas en .NET
Standard 2.0.
Compatibilidad con Visual Basic
Ahora puede desarrollar bibliotecas de .NET Standard en Visual Basic. Visual Studio 2019 y Visual Studio 2017,
versión 15.3 o una posterior con la carga de trabajo de .NET Core instalada, incluyen una plantilla de la biblioteca
de clases .NET Standard. Para los desarrolladores de Visual Basic que usan otras herramientas y entornos de
desarrollo, puede usar el comando dotnet new para crear un proyecto de biblioteca de .NET Standard. Para más
información, vea Compatibilidad con herramientas de bibliotecas estándar de .NET.
Compatibilidad con herramientas de bibliotecas de .NET Standard
Con la versión de .NET Core 2.0 y .NET Standard 2.0, Visual Studio 2017 y la CLI de .NET Core incluyen
compatibilidad con herramientas para crear bibliotecas de .NET Standard.
Si instala Visual Studio con la carga de trabajo de desarrollo multiplataforma de .NET Core , puede crear un
proyecto de biblioteca de .NET Standard 2.0 al usar una plantilla de proyecto, como se muestra en la ilustración
siguiente:
C#
Visual Basic
Si usa la CLI de .NET Core, el comando dotnet new siguiente crea un proyecto de biblioteca de clases que tiene
como destino .NET Standard 2.0:

dotnet new classlib

Vea también
.NET Standard
Introducing .NET Standard (Introducción a .NET Standard)
Información general sobre el SDK de .NET Core
04/05/2020 • 3 minutes to read • Edit Online

El SDK de .NET Core es un conjunto de bibliotecas y herramientas que permiten a los desarrolladores crear
aplicaciones y bibliotecas de .NET Core. Contiene los siguientes componentes que se usan para compilar y ejecutar
aplicaciones:
La CLI de .NET Core.
Bibliotecas y runtime de .NET Core.
El controlador dotnet .

Adquisición del SDK de .NET Core


Del mismo modo que ocurre con todas las herramientas, lo primero que debe hacer es instalar las herramientas
en su máquina. Según el escenario, puede instalar el SDK mediante uno de los métodos siguientes:
Los instaladores nativos.
El script de shell de instalación.
Los instaladores nativos están pensados principalmente para las máquinas de los desarrolladores. El SDK se
distribuye mediante el mecanismo de instalación nativo de cada plataforma compatible, como los paquetes DEB
en Ubuntu o los conjuntos de MSI en Windows. Estos instaladores instalan y configuran el entorno según sea
necesario para que el usuario use el SDK inmediatamente después de la instalación. Sin embargo, también se
necesitan privilegios administrativos en la máquina. Puede encontrar el SDK para instalar en la página de
descargas de .NET.
Por el contrario, los scripts de instalación no requieren privilegios administrativos, aunque tampoco instalan
ningún requisito previo en el equipo; debe instalarlos todos manualmente el usuario. Los scripts están pensados
principalmente para configurar servidores de compilación o cuando desee instalar las herramientas sin privilegios
de administración (tenga en cuenta la salvedad con respecto a los requisitos previos que ya se mencionó). Puede
encontrar más información en el artículo referencia de scripts de dotnet-install. Si está interesado en cómo
configurar el SDK en el servidor de compilación de CI, vea el artículo Uso de .NET Core SDK y herramientas de
integración continua (CI).
De forma predeterminada, el SDK se instala en paralelo (SxS), lo que significa que varias versiones pueden
coexistir en un único equipo en cualquier momento. La forma en que se detecta la versión al ejecutar los
comandos de la CLI se explica más detalladamente en el artículo Selección de la versión de .NET Core que se va a
usar.

Vea también
Información general sobre la CLI de .NET Core
Introducción a la creación de versiones de .NET Core
Cómo quitar los componentes .NET Core Runtime y SDK
Selección de la versión de .NET Core que se va a usar
Información general sobre la CLI de .NET Core
16/09/2020 • 5 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores
La interfaz de la línea de comandos (CLI) de .NET Core es una cadena de herramientas multiplataforma
para desarrollar, compilar, ejecutar y publicar aplicaciones .NET Core.
La CLI de .NET Core se incluye con el SDK de .NET Core. Para más información sobre cómo instalar el SDK
de .NET Core, consulte Instalación de .NET Core.

Comandos de la CLI
De forma predeterminada, se instalan los siguientes comandos:
Comandos básicos
new
restore
build
publish
run
test
vstest
pack
migrate
clean
sln
help
store

Comandos de modificación del proyecto


add package
add reference
remove package
remove reference
list reference

Comandos avanzados
nuget delete
nuget locals
nuget push
msbuild
dotnet install script

Comandos de administración de herramientas


tool install
tool list
tool update
tool restore Disponible a partir del SDK de .NET Core 3.0.
tool run Disponible a partir del SDK de .NET Core 3.0.
tool uninstall

Las herramientas son aplicaciones de consola que se instalan mediante paquetes NuGet y se invocan desde
el símbolo del sistema. Puede encargarse de escribir las herramientas o instalar las escritas por terceros.
Las herramientas también se denominan herramientas globales, herramientas de ruta de acceso de
herramientas y herramientas locales. Para obtener más información, vea Información general sobre las
herramientas de .NET Core.

Estructura de comandos
La estructura de comandos de la CLI consta del controlador ("dotnet"), el comando y posiblemente de los
argumentos de comandos y otras opciones. Este patrón se puede ver en la mayoría de las operaciones de
la CLI, como la creación de una nueva aplicación de consola y su ejecución desde la línea de comandos,
como muestran los siguientes comandos cuando se ejecutan desde un directorio denominado my_app:

dotnet new console


dotnet build --output /build_output
dotnet /build_output/my_app.dll

Controlador
El controlador se denomina dotnet y tiene dos responsabilidades, ejecutar una aplicación dependiente del
marco o ejecutar un comando.
Para ejecutar una aplicación dependiente del marco, especifique la aplicación después del controlador, por
ejemplo, dotnet /path/to/my_app.dll . Cuando ejecute el comando desde la carpeta donde reside la DLL de
la aplicación, simplemente ejecute dotnet my_app.dll . Si quiere usar una versión específica del entorno de
ejecución .NET Core, use la opción --fx-version <VERSION> (consulte la referencia del comando dotnet).
Cuando se proporciona un comando para el controlador, dotnet.exe inicia el proceso de ejecución del
comando de la CLI. Por ejemplo:

dotnet build

En primer lugar, el controlador determina la versión de SDK que se debe usar. Si no hay ningún archivo
global.json, se usa la última versión del SDK disponible. Podría tratarse de una versión preliminar o de una
versión estable, en función de lo último que esté disponible en el equipo. Una vez determinada la versión
del SDK, se ejecutará el comando.
Comando
El comando realiza una acción. Por ejemplo, dotnet build compila código. dotnet publish publica el
código. Los comandos se implementan como una aplicación de consola usando una convención
dotnet {command} .

Argumentos
Los argumentos que se pasan en la línea de comandos son los argumentos para el comando invocado. Por
ejemplo, cuando ejecuta dotnet publish my_app.csproj , el argumento my_app.csproj indica el proyecto
que se publicará y se pasa al comando publish .
Opciones
Las opciones que se pasan en la línea de comandos son las opciones para el comando que se invoca. Por
ejemplo, cuando ejecuta dotnet publish --output /build_output , la opción --output y su valor se pasan al
comando publish .

Vea también
Repositorio de GitHub dotnet/sdk
.NET Core installation guide (Guía de instalación de .NET Core)
comando dotnet
16/09/2020 • 23 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores

NOMBRE
dotnet : controlador genérico de la CLI de .NET Core.

Sinopsis
Para obtener información sobre los comandos disponibles y el entorno:

dotnet [--version] [--info] [--list-runtimes] [--list-sdks]

dotnet -h|--help

Para ejecutar un comando (se requiere la instalación de un SDK):

dotnet <COMMAND> [-d|--diagnostics] [-h|--help] [--verbosity <LEVEL>]


[command-options] [arguments]

Para ejecutar una aplicación:

dotnet [--additionalprobingpath <PATH>] [--additional-deps <PATH>]


[--fx-version <VERSION>] [--roll-forward <SETTING>]
<PATH_TO_APPLICATION> [arguments]

dotnet exec [--additionalprobingpath] [--additional-deps <PATH>]


[--fx-version <VERSION>] [--roll-forward <SETTING>]
<PATH_TO_APPLICATION> [arguments]

--roll-forward está disponible a partir de .NET Core 3.x. En .NET Core 2.x, use
--roll-forward-on-no-candidate-fx .

Descripción
El comando dotnet tiene dos funciones:
Proporciona comandos para trabajar con proyectos de .NET Core.
Por ejemplo, dotnet build compila un proyecto. Cada comando define sus propias opciones y
argumentos. Todos los comandos admiten la opción --help para ver una breve documentación sobre
cómo usar el comando.
Ejecuta aplicaciones de .NET Core.
Hay que especificar la ruta de acceso al archivo .dll de una aplicación para poder ejecutarla. Ejecutar la
aplicación significa buscar y ejecutar el punto de entrada, que en el caso de las aplicaciones de consola es
el método Main . Por ejemplo, dotnet myapp.dll ejecuta la aplicación myapp . Vea Implementación de
aplicaciones .NET Core para obtener más información sobre las opciones de implementación.
Opciones
Existen distintas opciones disponibles para usar dotnet por sí mismo, para ejecutar un comando y para ejecutar
una aplicación.
Opciones de dotnet por sí mismo
Las siguientes opciones son para usar dotnet por sí mismo. Por ejemplo: dotnet --info . Muestran información
sobre el entorno.
--info

Imprime información detallada sobre una instalación de .NET Core y el entorno informático, por ejemplo,
el sistema operativo actual y el SHA de confirmación de la versión de .NET Core.
--version

Imprime la versión del SDK de .NET Core en uso.


--list-runtimes

Muestra una lista de los runtime de .NET Core instalados. Una versión x86 del SDK muestra solo los
runtime x86 y una versión x64 solo los runtime x64.
--list-sdks

Muestra una lista de los SDK de .NET Core instalados.


-h|--help

Muestra una lista de los comandos disponibles.


Opciones de SDK para ejecutar un comando
Las siguientes opciones son para usar dotnet con un comando. Por ejemplo: dotnet build --help .
-d|--diagnostics

Habilita la salida de diagnóstico.


-v|--verbosity <LEVEL>

Establece el nivel de detalle del comando. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] ,
d[etailed] y diag[nostic] . Estos no se admiten en todos los comandos. Vea la página específica de cada
comando para asegurarse de que la opción está disponible.
-h|--help

Imprime la documentación de un comando determinado, como dotnet build --help .


command options

Cada comando define opciones específicas de ese comando. Vea la página de comandos específica para
obtener una lista de las opciones disponibles.
Definición de tiempo de ejecución
Las siguientes opciones están disponibles cuando dotnet ejecuta una aplicación. Por ejemplo:
dotnet myapp.dll --roll-forward Major .

--additionalprobingpath <PATH>

Ruta de acceso que contiene directivas de sondeo y ensamblados para sondear.


--additional-deps <PATH>

Ruta de acceso a un archivo .deps.json adicional. Un archivo deps.json contiene una lista de dependencias,
dependencias de compilación e información de versión que se usa para resolver conflictos de ensamblado.
Para más información, vea Runtime Configuration Files (Archivos de configuración en tiempo de
ejecución) en GitHub.
--depsfile <PATH_TO_DEPSFILE>

Ruta de acceso al archivo deps.json. Un archivo deps.json es un archivo de configuración que contiene
información sobre las dependencias necesarias para ejecutar la aplicación. El SDK de .NET Core genera este
archivo.
--runtimeconfig

Ruta de acceso a un archivo runtimeconfig.json. Un archivo runtimeconfig.json es un archivo de


configuración que contiene opciones de configuración en tiempo de ejecución. Para obtener más
información, vea Opciones de configuración en tiempo de ejecución de .NET Core.
--roll-forward <SETTING> Disponible a par tir del SDK de .NET Core 3.0.
Controla cómo se aplica la puesta al día en la aplicación. SETTING puede tener uno de los valores
siguientes. Si no se especifica, el valor predeterminado es Minor .
LatestPatch : la aplicación se pone al día con la última versión de revisión. Se deshabilita la puesta al
día de versiones secundarias.
Minor : la aplicación se pone al día con la versión secundaria mínima superior, si no se encuentra la
versión secundaria solicitada. Si se encuentra la versión secundaria solicitada, se utiliza la directiva
LatestPatch.
Major : la aplicación se pone al día con la versión secundaria mínima superior, y con la versión
secundaria mínima si no se encuentra la versión secundaria solicitada. Si se encuentra la versión
principal solicitada, se utiliza la directiva Minor.
LatestMinor : la aplicación e pone al día con la última versión secundaria, aunque la versión secundaria
solicitada esté presente. Se destina a escenarios de hospedaje de componentes.
LatestMajor : la aplicación se pone al día con la última versión principal y la última versión secundaria,
aunque la versión principal solicitada esté presente. Se destina a escenarios de hospedaje de
componentes.
Disable : la aplicación no se pone al día. Solo se enlaza a la versión especificada. No se recomienda esta
directiva para uso general, ya que deshabilita la capacidad de puesta al día con las revisiones más
recientes. Este valor solo se recomienda a efectos de pruebas.
A excepción de Disable , todos los valores usarán la última versión de revisión disponible.
El comportamiento de puesta al día también se puede configurar en una propiedad de archivo de
proyecto, en una propiedad de archivo de configuración de runtime y en una variable de entorno. Para
obtener más información, vea Puesta al día del runtime de versiones principales.
--roll-forward-on-no-candidate-fx <N> Disponible en el SDK de .NET Core 2.x.
Define el comportamiento cuando el marco de trabajo compartido necesario no está disponible. N puede
ser:
0 : se deshabilita la puesta al día incluso de las versiones secundarias.
1 : puesta al día de la versión secundaria, pero no de la versión principal. Éste es el comportamiento
predeterminado.
2 : puesta al día de las versiones principales y secundarias.
Para obtener más información, vea Roll forward (Puesta al día).
A partir de .NET Core 3.0, esta opción se sustituye por --roll-forward y es la que debe usarse.
--fx-version <VERSION>

Versión del runtime de .NET Core que se va a usar para ejecutar la aplicación.
Esta opción reemplaza la versión de la primera referencia de marco en el archivo .runtimeconfig.json de
la aplicación. Esto significa que solo funciona según lo esperado si solo hay una referencia de marco. Si la
aplicación tiene más de una referencia de marco, el uso de esta opción puede hacer que se produzcan
errores.

comandos de dotnet
General
C O M A N DO F UN C IÓ N

dotnet build Compila una aplicación .NET Core.

dotnet build-server Interactúa con servidores iniciados por una compilación.

dotnet clean Limpia las salidas de la compilación.

dotnet help Muestra documentación más detallada en línea sobre el


comando.

dotnet migrate Migra un proyecto de Preview 2 válido a un proyecto del SDK


1.0 de .NET Core.

dotnet msbuild Proporciona acceso a la línea de comandos de MSBuild.

dotnet new Inicializa un proyecto de C# o F# para una plantilla


determinada.

dotnet pack Crea un paquete de NuGet de su código.

dotnet publish Publica una aplicación dependiente del maco .NET o


autocontenida.

dotnet restore Restaura las dependencias de una aplicación determinada.

dotnet run Ejecuta la aplicación desde el origen.

dotnet sln Opciones para agregar, quitar y mostrar proyectos en un


archivo de solución.

dotnet store Almacena ensamblados en el almacenamiento de paquetes


en tiempo de ejecución.

dotnet test Ejecuta pruebas usando un ejecutor de pruebas.

Referencias de proyecto
C O M A N DO F UN C IÓ N

dotnet add reference Agrega una referencia de proyecto.

dotnet list reference Enumera referencias de proyecto.

dotnet remove reference Quita una referencia de proyecto.

Paquetes NuGet
C O M A N DO F UN C IÓ N

dotnet add package Agrega un paquete NuGet.

dotnet remove package Quita un paquete NuGet.

Comandos NuGet
C O M A N DO F UN C IÓ N

dotnet nuget delete Elimina o quita de la lista un paquete del servidor.

dotnet nuget push Inserta un paquete en el servidor y lo publica.

dotnet nuget locals Borra o muestra los recursos de NuGet locales, como la caché
de solicitudes http, la caché temporal o la carpeta de
paquetes global de toda la máquina.

dotnet nuget add source Agrega un origen de NuGet.

dotnet nuget disable source Deshabilita un origen de NuGet.

dotnet nuget enable source Habilita un origen de NuGet.

dotnet nuget list source Enumera todos los orígenes de NuGet configurados.

dotnet nuget remove source Quita un origen de NuGet.

dotnet nuget update source Actualiza un origen de NuGet.

Comandos de herramientas locales, globales y de ruta de acceso de herramientas


Las herramientas son aplicaciones de consola que se instalan mediante paquetes NuGet y se invocan desde el
símbolo del sistema. Puede encargarse de escribir las herramientas o instalar las escritas por terceros. Las
herramientas también se denominan herramientas globales, herramientas de ruta de acceso de herramientas y
herramientas locales. Para obtener más información, vea Información general sobre las herramientas de
.NET Core. Las herramientas globales y de ruta de acceso de herramientas están disponibles a partir del SDK de
.NET Core 2.1. Las herramientas locales están disponibles a partir del SDK de .NET Core 3.0.

C O M A N DO F UN C IÓ N

dotnet tool install Instala una herramienta en el equipo.


C O M A N DO F UN C IÓ N

dotnet tool list Muestra todas las herramientas globales, de ruta de acceso
de herramientas o locales instaladas actualmente en el
equipo.

dotnet tool uninstall Desinstala una herramienta del equipo.

dotnet tool update Actualiza una herramienta que está instalada en el equipo.

Herramientas adicionales
A partir del SDK de .NET Core 2.1.300, una serie de herramientas que estaban disponibles solo en función del
proyecto mediante DotnetCliToolReference ahora están disponibles como parte del SDK de .NET Core. Estas
herramientas se muestran en la tabla siguiente:

H ERRA M IEN TA F UN C IÓ N

dev-certs Crea y administra certificados de desarrollo.

ef Herramientas de línea de comandos de Entity Framework


Core.

sql-cache Herramientas de línea de comandos de la caché de SQL


Server.

user-secrets Administra los secretos del usuario de desarrollo.

watch Inicia un monitor de archivos que ejecuta un comando


cuando cambian los archivos.

Para obtener más información sobre cada herramienta, escriba dotnet <tool-name> --help .

Ejemplos
Cree una aplicación de consola de .NET Core:

dotnet new console

Compilación de un proyecto y sus dependencias en un directorio determinado:

dotnet build

Ejecute una aplicación:

dotnet myapp.dll

Variables de entorno
DOTNET_ROOT , DOTNET_ROOT(x86)

Especifica la ubicación de los runtime de .NET Core, si no están instalados en la ubicación predeterminada.
La ubicación predeterminada en Windows es C:\Program Files\dotnet . La ubicación predeterminada en
Linux y macOS es /usr/share/dotnet . Esta variable de entorno solo se usa cuando se ejecutan aplicaciones
a través de archivos ejecutables generados (hosts de aplicaciones). DOTNET_ROOT(x86) se usa en su lugar
cuando se ejecuta un archivo ejecutable de 32 bits en un sistema operativo de 64 bits.
DOTNET_PACKAGES

La carpeta de paquetes globales. Si no se establece, el valor predeterminado es ~/.nuget/packages en Unix


o %userprofile%\.nuget\packages en Windows.
DOTNET_SERVICING

Especifica la ubicación del índice de mantenimiento que usará el host compartido al cargar el entorno de
tiempo de ejecución.
DOTNET_NOLOGO

Especifica si los mensajes de bienvenida y de telemetría de .NET Core se muestran en la primera ejecución.
Establézcala en true para silenciar estos mensajes (valores true , 1 o yes aceptados) o en false para
permitirlos (valores false , 0 o no aceptados). Si no se establece, el valor predeterminado es false y
los mensajes se mostrarán en la primera ejecución. Esta marca no tiene ningún efecto en la telemetría
(consulte DOTNET_CLI_TELEMETRY_OPTOUT para excluirse del envío de telemetría).
DOTNET_CLI_TELEMETRY_OPTOUT

Especifica si se recopilan datos sobre el uso de herramientas de .NET Core y se envían a Microsoft.
Establézcalo en true para no participar de la característica de la telemetría (se aceptan los valores true ,
1 o yes ). De lo contrario, establézcalo en false para participar de la característica de la telemetría (se
aceptan los valores false , 0 o no ). Si no se establece, el valor predeterminado es false , y la
característica de telemetría está activa.
DOTNET_MULTILEVEL_LOOKUP

Especifica si desde la ubicación global se resuelve .NET Core Runtime, el marco de trabajo compartido o el
SDK. Si no se define, establece el valor predeterminado de 1 ( true lógico). Establézcalo en 0 ( false
lógico) para no resolver desde la ubicación global y tener instalaciones de .NET Core aisladas. Para más
información sobre las búsquedas de varios niveles, vea Multi-level SharedFX Lookup (Búsqueda SharedFX
de varios niveles).
DOTNET_ROLL_FORWARD Disponible a par tir de .NET Core 3.x.
Determina el comportamiento de puesta al día. Para obtener más información, vea la opción
--roll-forward más arriba en este mismo artículo.

DOTNET_ROLL_FORWARD_TO_PRERELEASE Disponible a par tir de .NET Core 3.x.


Si se establece en 1 (habilitado), permite la puesta al día a una versión preliminar desde una versión de
lanzamiento. De forma predeterminada ( 0 : deshabilitado), cuando se solicita una versión de lanzamiento
de .NET Core en tiempo de ejecución, la puesta al día solo tendrá en cuenta las versiones de lanzamiento
instaladas.
Para obtener más información, vea Roll forward (Puesta al día).
DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX Disponible en .NET Core 2.x.
Deshabilita la puesta al día de versiones secundarias, si está establecido en 0 . Para obtener más
información, vea Roll forward (Puesta al día).
Este valor se ha sustituido en .NET Core 3.0 por DOTNET_ROLL_FORWARD , y esta es la configuración que debe
usarse.
DOTNET_CLI_UI_LANGUAGE

Establece el idioma de la interfaz de usuario de la CLI mediante un valor de configuración regional como
en-us . Los valores admitidos son los mismos que en Visual Studio. Para obtener más información, vea la
sección sobre cómo cambiar el idioma del instalador en la documentación de instalación de Visual Studio.
Se aplican las reglas del administrador de recursos de .NET, por lo que no hay que elegir una coincidencia
exacta—(también se pueden elegir descendientes en el árbol CultureInfo ). Por ejemplo, si se establece en
fr-CA , la CLI buscará y usará las traducciones de fr . Si se establece en un idioma que no se admite, la
CLI revertirá al inglés.
DOTNET_DISABLE_GUI_ERRORS

En el caso de los archivos ejecutables generados habilitados para GUI, se deshabilita el cuadro de diálogo
emergente que suele aparecer con ciertos tipos de errores. Solo escribe en stderr y se cierra en esos
casos.
DOTNET_ADDITIONAL_DEPS

Equivale a la opción --additional-deps de la CLI.


DOTNET_RUNTIME_ID

Invalida el RID detectado.


DOTNET_SHARED_STORE

Ubicación del "almacén compartido" a la que la resolución de ensamblado revierte en algunos casos.
DOTNET_STARTUP_HOOKS

Lista de ensamblados de los que cargar y ejecutar enlaces de inicio.


DOTNET_BUNDLE_EXTRACT_BASE_DIR Disponible a par tir de .NET Core 3.x.
Especifica un directorio en el que se extrae una aplicación de un solo archivo antes de ejecutarse.
Para más información, consulte Archivos ejecutables de único archivo.
COREHOST_TRACE , COREHOST_TRACEFILE , COREHOST_TRACE_VERBOSITY

Controla el seguimiento de diagnósticos de los componentes de hospedaje, como dotnet.exe , hostfxr y


hostpolicy .

COREHOST_TRACE=[0/1] : el valor predeterminado es 0 (el seguimiento está deshabilitado). Si se


establece en 1 , se habilita el seguimiento de diagnósticos.
COREHOST_TRACEFILE=<file path> : solo tiene efecto si el seguimiento se habilita a través de
COREHOST_TRACE=1 . Cuando se establece, la información de seguimiento se escribe en el archivo
especificado; en caso contrario, la información de seguimiento se escribe en stderr . Disponible a
par tir de .NET Core 3.x.
COREHOST_TRACE_VERBOSITY=[1/2/3/4] : el valor predeterminado es 4 . La configuración solo se usa
cuando el seguimiento está habilitado a través de COREHOST_TRACE=1 . Disponible a par tir de .NET
Core 3.x.
4 : se escribe toda la información de seguimiento.
3 : solo se escriben mensajes informativos, de advertencia y de error.
2 : solo se escriben mensajes de advertencia y de error.
1 : solo se escriben mensajes de error.
La forma habitual de obtener información de seguimiento detallada sobre el inicio de la aplicación es
establecer COREHOST_TRACE=1 y COREHOST_TRACEFILE=host_trace.txt y, luego, ejecutar la aplicación. Se creará
un nuevo archivo host_trace.txt en el directorio actual con la información detallada.

Vea también
Runtime Configuration Files (Archivos de configuración en tiempo de ejecución)
Opciones de configuración en tiempo de ejecución de .NET Core
dotnet build
16/09/2020 • 10 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

NOMBRE
dotnet build : compila un proyecto y todas sus dependencias.

Sinopsis
dotnet build [<PROJECT>|<SOLUTION>] [-c|--configuration <CONFIGURATION>]
[-f|--framework <FRAMEWORK>] [--force] [--interactive] [--no-dependencies]
[--no-incremental] [--no-restore] [--nologo] [-o|--output <OUTPUT_DIRECTORY>]
[-r|--runtime <RUNTIME_IDENTIFIER>] [--source <SOURCE>]
[-v|--verbosity <LEVEL>] [--version-suffix <VERSION_SUFFIX>]

dotnet build -h|--help

Descripción
El comando dotnet build crea el proyecto y sus dependencias en un conjunto de archivos binarios. Los
archivos binarios incluyen el código del proyecto en archivos de lenguaje intermedio (IL) con una extensión .dll.
Según el tipo de proyecto y la configuración, se pueden incluir otros archivos, como los siguientes:
Un archivo ejecutable que se pueda usar para ejecutar la aplicación, si el tipo de proyecto es un archivo
ejecutable que tiene como destino .NET Core 3.0 o versiones posteriores.
Archivos de símbolos usados para la depuración con una extensión .pdb.
Un archivo .deps.json, que muestra las dependencias de la aplicación o la biblioteca.
Un archivo .runtimeconfig.json, que especifica el runtime compartido y su versión de una aplicación.
Otras bibliotecas de las que depende el proyecto (a través de referencias de proyecto o referencias de
paquetes NuGet).
En el caso de los proyectos ejecutables que tienen como destino versiones anteriores a .NET Core 3.0, las
dependencias de biblioteca de NuGet normalmente no se copian en la carpeta de salida. Se resuelven desde la
carpeta de paquetes globales NuGet en tiempo de ejecución. Teniendo eso en cuenta, el producto de
dotnet build no está listo para transferirse a otra máquina para ejecutarse. Para crear una versión de la
aplicación que se pueda implementar, se debe publicar (por ejemplo, con el comando dotnet publish). Para
obtener más información, consulte el tema Implementación de aplicaciones .NET Core.
En el caso de los proyectos ejecutables que tienen como destino .NET Core 3.0 y versiones posteriores, las
dependencias de biblioteca se copian en la carpeta de salida. Esto significa que, si no hay ninguna otra lógica
específica de la publicación (como los proyectos web), se debería poder implementar la salida de la compilación.
Restauración implícita
La compilación requiere el archivo project.assets.json, que muestra las dependencias de la aplicación. El archivo
se crea cuando se ejecuta dotnet restore . Sin el archivo de recursos en su lugar, las herramientas no pueden
resolver los ensamblados de referencia, lo que se traduce en errores.
No es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que necesitan
que se produzca una restauración, como dotnet new , dotnet build , dotnet run , dotnet test , dotnet publish
y dotnet pack . Para deshabilitar la restauración implícita, use la opción --no-restore .
El comando dotnet restore sigue siendo válido en algunos escenarios donde tiene sentido realizar una
restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los
sistemas de compilación que necesitan controlar explícitamente cuándo se produce la restauración.
Para obtener información sobre cómo administrar fuentes de NuGet, vea la documentación de dotnet restore .
Este comando admite las opciones de dotnet restore cuando se pasan con el formato largo (por ejemplo,
--source ). No se admiten las opciones de formato corto, como -s .

Salida de biblioteca o ejecutable


Si el proyecto es ejecutable o no viene determinado por la propiedad <OutputType> en el archivo del proyecto.
En el ejemplo siguiente se muestra un proyecto en el que se genera código ejecutable:

<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>

Para generar una biblioteca, omita la propiedad <OutputType> o cambie su valor a Library . La DLL de IL para
una biblioteca no contiene puntos de entrada y no se puede ejecutar.
MSBuild
dotnet build usa MSBuild para compilar el proyecto, por lo que admite las compilaciones en paralelo e
incrementales. Para obtener más información, consulte Compilaciones incrementales.
Además de sus opciones, el comando dotnet build acepta opciones de MSBuild, como -p para establecer
propiedades o -l para definir un registrador. Para obtener más información sobre estas opciones, vea la
Referencia de la línea de comandos de MSBuild. O también puede usar el comando dotnet msbuild.
Ejecutar dotnet build es equivalente a ejecutar dotnet msbuild -restore ; sin embargo, el nivel de detalle
predeterminado de la salida es distinto.

Argumentos
PROJECT | SOLUTION

El archivo de proyecto o solución para compilar. Si no se especifica un archivo de proyecto o solución, MSBuild
busca en el directorio de trabajo actual un archivo que tiene una extensión de archivo que termina por proj o sln
y usa ese archivo.

Opciones
-c|--configuration <CONFIGURATION>

Define la configuración de compilación. El valor predeterminado para la mayoría de los proyectos es


Debug , pero puede invalidar los valores de configuración de compilación en el proyecto.

-f|--framework <FRAMEWORK>

Compila para un marco de trabajo específico. El marco se debe definir en el archivo de proyecto.
--force

Fuerza la resolución de todas las dependencias, incluso si la última restauración se realizó correctamente.
Especificar esta marca es lo mismo que eliminar el archivo project.assets.json.
-h|--help

Imprime una corta ayuda para el comando.


--interactive

Permite que el comando se detenga y espere una entrada o una acción del usuario. Por ejemplo, para
completar la autenticación. Disponible desde el SDK de .NET Core 3.0.
--no-dependencies

Omite las referencias de proyecto a proyecto (P2P) y solo compila el proyecto raíz especificado.
--no-incremental

Marca la compilación como no segura para la compilación incremental. Esta marca desactiva la
compilación incremental y fuerza una recompilación limpia del gráfico de dependencias del proyecto.
--no-restore

No ejecuta una restauración implícita durante la compilación.


--nologo

No se muestra la pancarta de inicio ni el mensaje de copyright. Disponible desde el SDK de .NET Core 3.0.
-o|--output <OUTPUT_DIRECTORY>

Directorio donde se colocan los archivos binarios compilados. Si no se especifica la ruta de acceso
predeterminada es ./bin/<configuration>/<framework>/ . En el caso de los proyectos con varias
plataformas de destino (a través de la propiedad TargetFrameworks ), también debe definir --framework al
especificar esta opción.
-r|--runtime <RUNTIME_IDENTIFIER>

Especifica el tiempo de ejecución de destino. Para obtener una lista de identificadores de tiempo de
ejecución (RID), consulte el catálogo de RID.
--source <SOURCE>

URI del origen del paquete NuGet que se usará durante la operación de restauración.
-v|--verbosity <LEVEL>

Establece el nivel de detalle de MSBuild. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] ,
d[etailed] y diag[nostic] . De manera predeterminada, es minimal .

--version-suffix <VERSION_SUFFIX>

Establece el valor de la propiedad $(VersionSuffix) que se va a usar al compilar el proyecto. Solo


funciona si no se establece la propiedad $(Version) . A continuación, $(Version) se establece en
$(VersionPrefix) combinada con $(VersionSuffix) , separadas por un guion.

Ejemplos
Creación de un proyecto y sus dependencias:

dotnet build

Creación de un proyecto y sus dependencias mediante la configuración de lanzamiento:


dotnet build --configuration Release

Compilación de un proyecto y sus dependencias para un tiempo de ejecución concreto (en este ejemplo,
Ubuntu 18.04):

dotnet build --runtime ubuntu.18.04-x64

Compile el proyecto y use origen del paquete NuGet especificado durante la operación de restauración
(SDK de .NET Core 2.0 y versiones posteriores):

dotnet build --source c:\packages\mypackages

Compile el proyecto y establezca la versión 1.2.3.4 como un parámetro de compilación mediante la


opción de MSBuild -p :

dotnet build -p:Version=1.2.3.4


dotnet build-server
20/04/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores

Name
dotnet build-server : interactúa con servidores iniciados por una compilación.

Sinopsis
dotnet build-server shutdown [--msbuild] [--razor] [--vbcscompiler]

dotnet build-server shutdown -h|--help

dotnet build-server -h|--help

Comandos
shutdown

Cierra los servidores de la compilación que se inician desde dotnet. De forma predeterminada, se cierran
todos los servidores.

Opciones
-h|--help

Imprime una corta ayuda para el comando.


--msbuild

Cierra el servidor de compilación de MSBuild.


--razor

Cierra el servidor de compilación de Razor.


--vbcscompiler

Cierra el servidor de compilación del compilador de VB/C#.


dotnet clean
20/04/2020 • 3 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

Name
dotnet clean : limpia la salida de un proyecto.

Sinopsis
dotnet clean [<PROJECT>|<SOLUTION>] [-c|--configuration <CONFIGURATION>]
[-f|--framework <FRAMEWORK>] [--interactive]
[--nologo] [-o|--output <OUTPUT_DIRECTORY>]
[-r|--runtime <RUNTIME_IDENTIFIER>] [-v|--verbosity <LEVEL>]

dotnet clean -h|--help

Description
El comando dotnet clean limpia la salida de la compilación anterior. Se implementa como un destino MSBuild,
por lo que el proyecto se evalúa cuando se ejecuta el comando. Solo se limpian las salidas que se crearon durante
la compilación. Se limpian las carpetas intermedias (obj) y de la salida final (bin).

Argumentos
PROJECT | SOLUTION

Proyecto o solución de MSBuild que se va a limpiar. Si no se especifica un archivo de proyecto o solución, MSBuild
busca en el directorio de trabajo actual un archivo que tenga una extensión de archivo que termine en proj o sln y
lo usa.

Opciones
-c|--configuration <CONFIGURATION>

Define la configuración de compilación. El valor predeterminado para la mayoría de los proyectos es Debug ,
pero puede invalidar los valores de configuración de compilación en el proyecto. Esta opción solo es
necesaria al realizar la limpieza si la especificó durante el tiempo de compilación.
-f|--framework <FRAMEWORK>

El marco que se especificó en tiempo de compilación. El marco se debe definir en el archivo de proyecto. Si
especificó el marco en tiempo de compilación, debe especificar el marco al realizar la limpieza.
-h|--help

Imprime una corta ayuda para el comando.


--interactive

Permite que el comando se detenga y espere una entrada o una acción del usuario. Por ejemplo, para
completar la autenticación. Disponible desde el SDK de .NET Core 3.0.
--nologo

No se muestra la pancarta de inicio ni el mensaje de copyright. Disponible desde el SDK de .NET Core 3.0.
-o|--output <OUTPUT_DIRECTORY>

Directorio que contiene los artefactos compilados que se van a limpiar. Especifique el modificador
-f|--framework <FRAMEWORK> con el modificador del directorio de salida si especificó el marco cuando se
compiló el proyecto.
-r|--runtime <RUNTIME_IDENTIFIER>

Limpia la carpeta de salida del tiempo de ejecución especificado. Esto se usa si se ha creado una
implementación autocontenida.
-v|--verbosity <LEVEL>

Establece el nivel de detalle de MSBuild. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] ,
d[etailed] y diag[nostic] . El valor predeterminado es normal .

Ejemplos
Limpie una compilación predeterminada del proyecto:

dotnet clean

Limpie un proyecto creado con la configuración de lanzamiento:

dotnet clean --configuration Release


Referencia de dotnet help
20/04/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.0 y versiones posteriores

Name
dotnet help : muestra documentación más detallada en línea para el comando especificado.

Sinopsis
dotnet help <COMMAND_NAME> [-h|--help]

Description
El comando dotnet help abre la página de referencia para obtener información más detallada sobre el comando
especificado en docs.microsoft.com.

Argumentos
COMMAND_NAME

Nombre del comando de la CLI de .NET Core. Para obtener una lista de los comandos de la CLI válidos, vea
Comandos de la CLI.

Opciones
-h|--help

Imprime una corta ayuda para el comando.

Ejemplos
Se abre la página de documentación del comando dotnet new:

dotnet help new


dotnet migrate
16/09/2020 • 5 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x

NOMBRE
dotnet migrate : migra un proyecto .NET Core de la versión preliminar 2 a un proyecto del estilo de SDK de .NET
Core.

Sinopsis
dotnet migrate [<SOLUTION_FILE|PROJECT_DIR>] [--format-report-file-json <REPORT_FILE>]
[-r|--report-file <REPORT_FILE>] [-s|--skip-project-references [Debug|Release]]
[--skip-backup] [-t|--template-file <TEMPLATE_FILE>] [-v|--sdk-package-version]
[-x|--xproj-file]

dotnet migrate -h|--help

Descripción
Este comando está en desuso. El comando dotnet migrate ya no está disponible a partir del SDK de .NET Core 3.0.
Solo puede migrar un proyecto de .NET Core de la versión preliminar 2 a un proyecto de .NET Core 1.x, para el que
no hay soporte técnico.
De forma predeterminada, el comando migra el proyecto raíz y todas las referencias de proyecto que contiene.
Este comportamiento se deshabilita mediante la opción --skip-project-references en tiempo de ejecución.
La migración se puede realizar en los recursos siguientes:
Un único proyecto mediante la especificación del archivo project.json que quiere migrar.
Todos los directorios especificados en el archivo global.json pasando una ruta al archivo global.json.
Un archivo solution.sln, donde se migran los proyectos a los que se hace referencia en la solución.
Todos los subdirectorios del directorio dado de manera recursiva.
El comando dotnet migrate mantiene el archivo project.json migrado dentro de un directorio backup , que se crea
en caso de que no exista. Este comportamiento se invalida con la opción --skip-backup .
De forma predeterminada, la operación de migración genera el estado del proceso de migración a la salida
estándar (STDOUT). Si usa la opción --report-file <REPORT_FILE> , la salida se guarda en el archivo especificado.
El comando dotnet migrate solo admite proyectos válidos basados en project.json de la versión preliminar 2. Esto
significa que no se puede usar para migrar DNX o los proyectos basados en project.json de la versión preliminar 1
directamente a proyectos de MSBuild/csproj. Primero debe migrar manualmente el proyecto a un proyecto
basado en project.json de la versión preliminar 2 y luego usar el comando dotnet migrate para migrar el
proyecto.

Argumentos
PROJECT_JSON/GLOBAL_JSON/SOLUTION_FILE/PROJECT_DIR
La ruta de acceso a uno de los siguientes elementos:
Un archivo project.json para migrar.
Un archivo global.json: se migran las carpetas especificadas en global.json.
Un archivo solution.sln: se migran los proyectos a los que se hace referencia en la solución.
Un directorio para migrar: se buscan de forma recursiva los archivos project.json que se van a migrar dentro
del directorio especificado.
Si no se especifica nada, se toma como valor predeterminado el directorio actual.

Opciones
--format-report-file-json <REPORT_FILE>

Salida del archivo de informe de migración como JSON en lugar de mensajes de usuario.
-h|--help

Imprime una corta ayuda para el comando.


-r|--report-file <REPORT_FILE>

Salida del informe de migración a un archivo además de a la consola.


-s|--skip-project-references [Debug|Release]

Omite la migración de referencias de proyecto. De forma predeterminada, las referencias de proyecto se migran
de forma recursiva.
--skip-backup

Omite el traslado de project.json, global.json y *.xproj a un directorio backup tras la realización correcta de la
migración.
-t|--template-file <TEMPLATE_FILE>

Archivo csproj de plantilla que se utilizará para la migración. De forma predeterminada, se usa la misma plantilla
que la descartada por dotnet new console .
-v|--sdk-package-version <VERSION>

La versión del paquete sdk a la que se hace referencia en la aplicación migrada. El valor predeterminado es la
versión del SDK en dotnet new .
-x|--xproj-file <FILE>

La ruta de acceso al archivo xproj que se usará. Necesario cuando hay más de un archivo xproj en un directorio de
proyecto.

Ejemplos
Migrar un proyecto del directorio actual y todas sus dependencias de proyecto a proyecto:
dotnet migrate

Migrar todos los proyectos que incluyen el archivo global.json:


dotnet migrate path/to/global.json

Migrar solo el proyecto actual y ninguna dependencia de proyecto a proyecto (P2P). Además, usar una versión
específica de SDK:
dotnet migrate -s -v 1.0.0-preview4
dotnet msbuild
16/09/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

NOMBRE
dotnet msbuild : compila un proyecto y todas sus dependencias. Nota: Si hay varios, es posible que sea necesario
especificar una solución o un archivo de proyecto.

Sinopsis
dotnet msbuild <MSBUILD_ARGUMENTS>

dotnet msbuild -h

Descripción
El comando dotnet msbuild permite el acceso a una instancia de MSBuild completamente funcional.
El comando tiene exactamente las mismas funcionalidades que el cliente de línea de comandos de MSBuild
existente solo para proyectos de estilo SDK. Las opciones son las mismas. Para obtener más información sobre
las opciones disponibles, vea Referencia de la línea de comandos de MSBuild.
El comando dotnet build es equivalente al comando dotnet msbuild -restore . Si no quiere compilar el proyecto
y hay un destino concreto que quiere ejecutar, use dotnet build o dotnet msbuild y especifique el destino.

Ejemplos
Creación de un proyecto y sus dependencias:

dotnet msbuild

Creación de un proyecto y sus dependencias mediante la configuración de lanzamiento:

dotnet msbuild -property:Configuration=Release

Ejecuta el destino de publicación y publica para el RID osx.10.11-x64 :

dotnet msbuild -target:Publish -property:RuntimeIdentifiers=osx.10.11-x64

Visualización del proyecto completo con todos los destinos incluidos en el SDK:

dotnet msbuild -preprocess


dotnet msbuild -preprocess:<fileName>.xml
dotnet new
16/09/2020 • 30 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.0 y versiones posteriores

NOMBRE
dotnet new : crea un nuevo proyecto, archivo de configuración o solución según la plantilla especificada.

Sinopsis
dotnet new <TEMPLATE> [--dry-run] [--force] [-i|--install {PATH|NUGET_ID}]
[-lang|--language {"C#"|"F#"|VB}] [-n|--name <OUTPUT_NAME>]
[--nuget-source <SOURCE>] [-o|--output <OUTPUT_DIRECTORY>]
[-u|--uninstall] [--update-apply] [--update-check] [Template options]

dotnet new <TEMPLATE> [-l|--list] [--type <TYPE>]

dotnet new -h|--help

Descripción
El comando dotnet new crea un proyecto de .NET Core u otros artefactos basados en una plantilla.
El comando llama al motor de plantillas para crear los artefactos en el disco basándose en las opciones y
la plantilla especificadas.
Restauración implícita
No es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que
necesitan que se produzca una restauración, como dotnet new , dotnet build , dotnet run , dotnet test ,
dotnet publish y dotnet pack . Para deshabilitar la restauración implícita, use la opción --no-restore .

El comando dotnet restore sigue siendo válido en algunos escenarios donde tiene sentido realizar una
restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en
los sistemas de compilación que necesitan controlar explícitamente cuándo se produce la restauración.
Para obtener información sobre cómo administrar fuentes de NuGet, vea la documentación de
dotnet restore .

Argumentos
TEMPLATE

La plantilla de la que se va a crear una instancia cuando se invoca el comando. Cada plantilla
puede tener opciones específicas que puede pasar. Para obtener más información, vea Opciones
de plantilla.
Puede ejecutar dotnet new --list o dotnet new -l para ver una lista de todas las plantillas
instaladas. Si el valor TEMPLATE no es una coincidencia exacta con el texto de la columna
Plantillas o Nombre cor to de la tabla devuelta, se realiza una coincidencia de subcadena con
esas dos columnas.
A partir del SDK de .NET Core 3.0, la CLI busca plantillas en NuGet.org al invocar el comando
dotnet new en las siguientes condiciones:

Si la CLI no encuentra ninguna coincidencia de plantilla al invocar dotnet new , ni siquiera


parcial.
Si hay disponible una versión más reciente de la plantilla. En este caso, se crea el proyecto o el
artefacto, pero la CLI le advierte de que hay una versión actualizada de la plantilla.
En la tabla siguiente se muestran las plantillas que vienen preinstaladas en el SDK de .NET Core. El
lenguaje predeterminado de la plantilla se muestra entre corchetes. Haga clic en el vínculo del
nombre corto para ver las opciones específicas de la plantilla.

P L A N T IL L A S N O M B RE C O RTO L EN GUA JE ET IQ UETA S IN C L USIÓ N

Aplicación de console [C#], F#, VB Común/Consola 1.0


consola

Biblioteca de clases classlib [C#], F#, VB Común/Biblioteca 1.0

Aplicación WPF wpf [C#], VB Común/WPF 3.0 (5.0 para VB)

Biblioteca de clases wpflib [C#], VB Común/WPF 3.0 (5.0 para VB)


de WPF

Biblioteca de wpfcustomcontrolli [C#], VB Común/WPF 3.0 (5.0 para VB)


controles b
personalizados WPF

Biblioteca de wpfusercontrollib [C#], VB Común/WPF 3.0 (5.0 para VB)


controles de
usuario de WPF

Aplicación de winforms [C#], VB Común/WinForms 3.0 (5.0 para VB)


Windows Forms
(WinForms)

Biblioteca de clases winformslib [C#], VB Común/WinForms 3.0 (5.0 para VB)


de Windows Forms
(WinForms)

Servicio Worker worker [C#] Común/Worker/We 3.0


b

Proyecto de prueba mstest [C#], F#, VB Prueba/MSTest 1.0


unitaria

Proyecto de prueba nunit [C#], F#, VB Prueba/NUnit 2.1.400


de NUnit 3

Elemento de nunit-test [C#], F#, VB Prueba/NUnit 2.2


prueba de NUnit 3

Proyecto de prueba xunit [C#], F#, VB Prueba/xUnit 1.0


de xUnit

Componente Razor razorcomponent [C#] Web/ASP.NET 3.0


P L A N T IL L A S N O M B RE C O RTO L EN GUA JE ET IQ UETA S IN C L USIÓ N

Página de Razor page [C#] Web/ASP.NET 2.0

MVC ViewImports viewimports [C#] Web/ASP.NET 2.0

MVC ViewStart viewstart [C#] Web/ASP.NET 2.0

Blazor Aplicación de blazorserver [C#] Web/Blazor 3.0


servidor

Aplicación de Blazor blazorwasm [C#] Web/Blazor/WebAs 3.1.300


WebAssembly sembly

Vacío de ASP.NET web [C#], F# Web/Vacío 1.0


Core

Aplicación web de mvc [C#], F# Web/MVC 1.0


ASP.NET Core
(Model-View-
Controller)

Aplicación web de webapp, razor [C#] Web/MVC/Razor 2.2, 2.0


ASP.NET Core Pages

ASP.NET Core con angular [C#] Web/MVC/SPA 2.0


Angular

ASP.NET Core con react [C#] Web/MVC/SPA 2.0


React.js

ASP.NET Core con reactredux [C#] Web/MVC/SPA 2.0


React.js y Redux

Biblioteca de clases razorclasslib [C#] Web/Razor/Bibliote 2.1


de Razor ca/Biblioteca de
clases de Razor

API web de ASP.NET webapi [C#], F# Web/WebAPI 1.0


Core

Servicio gRPC de grpc [C#] Web/gRPC 3.0


ASP.NET Core

Archivo dotnet gitignore Configuración 3.0


gitignore

archivo global.json globaljson Configuración 2.0

Configuración de nugetconfig Configuración 1.0


NuGet

Archivo de tool-manifest Configuración 3.0


manifiesto de la
herramienta local
dotnet
P L A N T IL L A S N O M B RE C O RTO L EN GUA JE ET IQ UETA S IN C L USIÓ N

Configuración web webconfig Configuración 1.0

Archivo de solución sln Soluciones 1.0

Archivo de búfer de proto Web/gRPC 3.0


protocolo

Opciones
--dry-run

Muestra un resumen de lo que sucedería si se ejecutara el comando determinado y el resultado


fuera la creación de una plantilla. Disponible a partir del SDK de .NET Core 2.2.
--force

Fuerza la generación de contenido incluso aunque se vayan a cambiar los archivos existentes. Es
necesario si la plantilla elegida invalidará los archivos existentes en el directorio de salida.
-h|--help

Imprime la ayuda para el comando. Puede invocarse para el propio comando dotnet new o para
cualquier plantilla. Por ejemplo: dotnet new mvc --help .
-i|--install <PATH|NUGET_ID>

Instala un paquete de plantillas desde los parámetros PATH o NUGET_ID proporcionados. Si quiere
instalar una versión preliminar de un paquete de plantilla, tendrá que especificar la versión en el
formato de <package-name>::<package-version> . De forma predeterminada, dotnet new pasa *
para la versión, que representa la última versión estable del paquete. Vea un ejemplo en la sección
Ejemplos.
Si ya hay instalada una versión de la plantilla al ejecutar este comando, la plantilla se actualizará a
la versión especificada o a la versión estable más reciente si no se ha especificado ninguna
versión.
Para obtener información sobre cómo crear plantillas personalizadas, consulte Plantillas
personalizadas para dotnet new.
-l|--list

Muestra las plantillas que contienen el nombre especificado. Si no se especifica ningún nombre,
enumera todas las plantillas.
-lang|--language {C#|F#|VB}

El lenguaje de la plantilla que se va a crear. El lenguaje aceptado cambia según la plantilla (vea los
valores predeterminados en la sección argumentos). No es válido para algunas plantillas.
NOTE
Algunos shells interpretan # como un carácter especial. En esos casos, incluya el valor del parámetro de
lenguaje entre comillas. Por ejemplo: dotnet new console -lang "F#" .

-n|--name <OUTPUT_NAME>

El nombre de la salida creada. Si no se especifica ningún nombre, se usa el nombre del directorio
actual.
--nuget-source <SOURCE>

Especifica un origen de NuGet para usarlo durante la instalación. Disponible a partir del SDK de
.NET Core 2.1.
-o|--output <OUTPUT_DIRECTORY>

La ubicación para colocar la salida generada. El valor predeterminado es el directorio actual.


--type <TYPE>

Filtra plantillas en función de los tipos disponibles. Los valores predefinidos son project , item y
other .

-u|--uninstall [PATH|NUGET_ID]

Desinstala un paquete de plantillas en los parámetros PATH o NUGET_ID proporcionados. Si no se


especifica el valor <PATH|NUGET_ID> , se muestran todos los paquetes de plantillas instalados
actualmente y sus plantillas asociadas. Al especificar NUGET_ID , no incluya el número de versión.
Si no especifica un parámetro en esta opción, el comando muestra las plantillas instaladas y
detalles sobre ellas.

NOTE
Para desinstalar una plantilla mediante PATH , debe usar el nombre completo de la ruta de acceso. Por
ejemplo, C:/Users/<USER>/Documents/Templates/GarciaSoftware.ConsoleTemplate.CSharp funcionará,
pero ./GarciaSoftware.ConsoleTemplate.CSharp desde la carpeta contenedora no lo hará. No incluya
ninguna barra diagonal para finalizar el directorio en la ruta de acceso a la plantilla.

--update-apply

Comprueba si hay actualizaciones disponibles de los paquetes de plantillas instalados


actualmente y las instala. Disponible desde el SDK de .NET Core 3.0.
--update-check

Comprueba si hay actualizaciones disponibles de los paquetes de plantillas instalados


actualmente. Disponible desde el SDK de .NET Core 3.0.

Opciones de plantilla
Cada plantilla de proyecto puede tener opciones adicionales disponibles. Las plantillas principales tienen
las siguientes opciones adicionales:
consola
-f|--framework <FRAMEWORK>
Especifica el marco de destino. Disponible desde el SDK de .NET Core 3.0.
En la tabla siguiente se enumeran los valores predeterminados según el número de versión del
SDK que esté usando:

VERSIÓ N DEL SDK VA LO R P REDET ERM IN A DO

3.1 netcoreapp3.1

3.0 netcoreapp3.0

--langVersion <VERSION_NUMBER>

Establece la propiedad LangVersion en el archivo del proyecto creado. Por ejemplo, use
--langVersion 7.3 para emplear C# 7.3. No es compatible con F#. Disponible a partir del SDK de
.NET Core 2.2.
Para obtener una lista de las versiones predeterminadas de C#, vea Valores predeterminados.
--no-restore

Si se especifica, no se ejecuta ninguna restauración implícita durante la creación del proyecto.


Disponible a partir del SDK de .NET Core 2.2.

classlib
-f|--framework <FRAMEWORK>

Especifica el marco de destino. Valores: netcoreapp<version> para crear una biblioteca de clases
de .NET Core o netstandard<version> para crear una biblioteca de clases de .NET Standard. El
valor predeterminado es netstandard2.0 .
--langVersion <VERSION_NUMBER>

Establece la propiedad LangVersion en el archivo del proyecto creado. Por ejemplo, use
--langVersion 7.3 para emplear C# 7.3. No es compatible con F#. Disponible a partir del SDK de
.NET Core 2.2.
Para obtener una lista de las versiones predeterminadas de C#, vea Valores predeterminados.
--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.

wpf, wpflib, wpfcustomcontrollib, wpfusercontrollib


-f|--framework <FRAMEWORK>

Especifica el marco de destino. El valor predeterminado es netcoreapp3.1 . Disponible a partir del


SDK de .NET Core 3.1.
--langVersion <VERSION_NUMBER>

Establece la propiedad LangVersion en el archivo del proyecto creado. Por ejemplo, use
--langVersion 7.3 para emplear C# 7.3.

Para obtener una lista de las versiones predeterminadas de C#, vea Valores predeterminados.
--no-restore
No se ejecuta ninguna restauración implícita durante la creación del proyecto.

winforms, winformslib
--langVersion <VERSION_NUMBER>

Establece la propiedad LangVersion en el archivo del proyecto creado. Por ejemplo, use
--langVersion 7.3 para emplear C# 7.3.

Para obtener una lista de las versiones predeterminadas de C#, vea Valores predeterminados.
--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.

worker, grpc
-f|--framework <FRAMEWORK>

Especifica el marco de destino. El valor predeterminado es netcoreapp3.1 . Disponible a partir del


SDK de .NET Core 3.1.
--exclude-launch-settings

Excluye launchSettings.json de la plantilla generada.


--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.

mstest, xunit
-f|--framework <FRAMEWORK>

Especifica el marco de destino. Opción disponible a partir del SDK de .NET Core 3.0.
En la tabla siguiente se enumeran los valores predeterminados según el número de versión del
SDK que esté usando:

VERSIÓ N DEL SDK VA LO R P REDET ERM IN A DO

3.1 netcoreapp3.1

3.0 netcoreapp3.0

-p|--enable-pack

Habilita el empaquetado del proyecto mediante dotnet pack.


--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.

nunit
-f|--framework <FRAMEWORK>

Especifica el marco de destino.


En la tabla siguiente se enumeran los valores predeterminados según el número de versión del
SDK que esté usando:
VERSIÓ N DEL SDK VA LO R P REDET ERM IN A DO

3.1 netcoreapp3.1

3.0 netcoreapp3.0

2.2 netcoreapp2.2

2.1 netcoreapp2.1

-p|--enable-pack

Habilita el empaquetado del proyecto mediante dotnet pack.


--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.

página
-na|--namespace <NAMESPACE_NAME>

Espacio de nombres del código generado. El valor predeterminado es MyApp.Namespace .


-np|--no-pagemodel

Crea la página sin PageModel.

viewimports, proto
-na|--namespace <NAMESPACE_NAME>

Espacio de nombres del código generado. El valor predeterminado es MyApp.Namespace .

blazorserver
-au|--auth <AUTHENTICATION_TYPE>

Tipo de autenticación que se va a usar. Los valores posibles son:


None : sin autenticación (valor predeterminado).
Individual : autenticación individual.
IndividualB2C : autenticación individual con Azure AD B2C.
SingleOrg : autenticación organizativa para un solo inquilino.
MultiOrg : autenticación organizativa para varios inquilinos.
Windows : autenticación de Windows.
--aad-b2c-instance <INSTANCE>

Instancia de Azure Active Directory B2C con la que se realiza la conexión. Úsela con la
autenticación IndividualB2C . El valor predeterminado es https://login.microsoftonline.com/tfp/ .
-ssp|--susi-policy-id <ID>

Identificador de la directiva de registro e inicio de sesión de este proyecto. Úsela con la


autenticación IndividualB2C .
-rp|--reset-password-policy-id <ID>
Identificador de la directiva de restablecimiento de contraseñas de este proyecto. Úsela con la
autenticación IndividualB2C .
-ep|--edit-profile-policy-id <ID>

Identificador de la directiva de edición de perfiles de este proyecto. Úsela con la autenticación


IndividualB2C .

--aad-instance <INSTANCE>

Instancia de Azure Active Directory con la que se realiza la conexión. Úsela con las autenticaciones
SingleOrg o MultiOrg . El valor predeterminado es https://login.microsoftonline.com/ .

--client-id <ID>

Identificador de cliente de este proyecto. Úsela con las autenticaciones IndividualB2C , SingleOrg
o MultiOrg . El valor predeterminado es 11111111-1111-1111-11111111111111111 .
--domain <DOMAIN>

Dominio del inquilino del directorio. Úsela con las autenticaciones SingleOrg o IndividualB2C . El
valor predeterminado es qualified.domain.name .
--tenant-id <ID>

Identificador de inquilino del directorio con el que se realiza la conexión. Úsela con la
autenticación SingleOrg . El valor predeterminado es 22222222-2222-2222-2222-222222222222 .
--callback-path <PATH>

Ruta de acceso de solicitud de la ruta de acceso de la base de la aplicación del URI de redirección.
Úsela con las autenticaciones SingleOrg o IndividualB2C . El valor predeterminado es
/signin-oidc .

-r|--org-read-access

Concede a esta aplicación acceso de lectura al directorio. Solo se aplica a las autenticaciones
SingleOrg y MultiOrg .

--exclude-launch-settings

Excluye launchSettings.json de la plantilla generada.


--no-https

Desactiva HTTPS. Esta opción solo se aplica si no se usan Individual , IndividualB2C , SingleOrg
o MultiOrg en --auth .
-uld|--use-local-db

Especifica que se debería usar LocalDB en vez de SQLite. Solo se aplica a las autenticaciones
Individual y IndividualB2C .

--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.

web
--exclude-launch-settings

Excluye launchSettings.json de la plantilla generada.


-f|--framework <FRAMEWORK>

Especifica el marco de destino. Opción no disponible en el SDK de .NET Core 2.2.


En la tabla siguiente se enumeran los valores predeterminados según el número de versión del
SDK que esté usando:

VERSIÓ N DEL SDK VA LO R P REDET ERM IN A DO

3.1 netcoreapp3.1

3.0 netcoreapp3.0

2.1 netcoreapp2.1

--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.


--no-https

Desactiva HTTPS.

mvc, webapp
-au|--auth <AUTHENTICATION_TYPE>

Tipo de autenticación que se va a usar. Los valores posibles son:


None : sin autenticación (valor predeterminado).
Individual : autenticación individual.
IndividualB2C : autenticación individual con Azure AD B2C.
SingleOrg : autenticación organizativa para un solo inquilino.
MultiOrg : autenticación organizativa para varios inquilinos.
Windows : autenticación de Windows.
--aad-b2c-instance <INSTANCE>

Instancia de Azure Active Directory B2C con la que se realiza la conexión. Úsela con la
autenticación IndividualB2C . El valor predeterminado es https://login.microsoftonline.com/tfp/ .
-ssp|--susi-policy-id <ID>

Identificador de la directiva de registro e inicio de sesión de este proyecto. Úsela con la


autenticación IndividualB2C .
-rp|--reset-password-policy-id <ID>

Identificador de la directiva de restablecimiento de contraseñas de este proyecto. Úsela con la


autenticación IndividualB2C .
-ep|--edit-profile-policy-id <ID>

Identificador de la directiva de edición de perfiles de este proyecto. Úsela con la autenticación


IndividualB2C .

--aad-instance <INSTANCE>
Instancia de Azure Active Directory con la que se realiza la conexión. Úsela con las autenticaciones
SingleOrg o MultiOrg . El valor predeterminado es https://login.microsoftonline.com/ .

--client-id <ID>

Identificador de cliente de este proyecto. Úsela con las autenticaciones IndividualB2C , SingleOrg
o MultiOrg . El valor predeterminado es 11111111-1111-1111-11111111111111111 .
--domain <DOMAIN>

Dominio del inquilino del directorio. Úsela con las autenticaciones SingleOrg o IndividualB2C . El
valor predeterminado es qualified.domain.name .
--tenant-id <ID>

Identificador de inquilino del directorio con el que se realiza la conexión. Úsela con la
autenticación SingleOrg . El valor predeterminado es 22222222-2222-2222-2222-222222222222 .
--callback-path <PATH>

Ruta de acceso de solicitud de la ruta de acceso de la base de la aplicación del URI de redirección.
Úsela con las autenticaciones SingleOrg o IndividualB2C . El valor predeterminado es
/signin-oidc .

-r|--org-read-access

Concede a esta aplicación acceso de lectura al directorio. Solo se aplica a las autenticaciones
SingleOrg y MultiOrg .

--exclude-launch-settings

Excluye launchSettings.json de la plantilla generada.


--no-https

Desactiva HTTPS. Esta opción solo se aplica si no se usan Individual , IndividualB2C , SingleOrg
o MultiOrg .
-uld|--use-local-db

Especifica que se debería usar LocalDB en vez de SQLite. Solo se aplica a las autenticaciones
Individual y IndividualB2C .

-f|--framework <FRAMEWORK>

Especifica el marco de destino. Opción disponible a partir del SDK de .NET Core 3.0.
En la tabla siguiente se enumeran los valores predeterminados según el número de versión del
SDK que esté usando:

VERSIÓ N DEL SDK VA LO R P REDET ERM IN A DO

3.1 netcoreapp3.1

3.0 netcoreapp3.0

--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.


--use-browserlink

Incluye BrowserLink en el proyecto. Opción no disponible en el SDK de .NET Core 2.2 y 3.1.
-rrc|--razor-runtime-compilation

Determina si el proyecto está configurado para usar la compilación en tiempo de ejecución de


Razor en las compilaciones de depuración. Opción disponible a partir del SDK de
.NET Core 3.1.201.

angular, react
-au|--auth <AUTHENTICATION_TYPE>

Tipo de autenticación que se va a usar. Disponible desde el SDK de .NET Core 3.0.
Los valores posibles son:
None : sin autenticación (valor predeterminado).
Individual : autenticación individual.
--exclude-launch-settings

Excluye launchSettings.json de la plantilla generada.


--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.


--no-https

Desactiva HTTPS. Esta opción solo se aplica si la autenticación es None .


-uld|--use-local-db

Especifica que se debería usar LocalDB en vez de SQLite. Solo se aplica a las autenticaciones
Individual y IndividualB2C . Disponible desde el SDK de .NET Core 3.0.

-f|--framework <FRAMEWORK>

Especifica el marco de destino. Opción no disponible en el SDK de .NET Core 2.2.


En la tabla siguiente se enumeran los valores predeterminados según el número de versión del
SDK que esté usando:

VERSIÓ N DEL SDK VA LO R P REDET ERM IN A DO

3.1 netcoreapp3.1

3.0 netcoreapp3.0

2.1 netcoreapp2.0

reactredux
--exclude-launch-settings

Excluye launchSettings.json de la plantilla generada.


-f|--framework <FRAMEWORK>
Especifica el marco de destino. Opción no disponible en el SDK de .NET Core 2.2.
En la tabla siguiente se enumeran los valores predeterminados según el número de versión del
SDK que esté usando:

VERSIÓ N DEL SDK VA LO R P REDET ERM IN A DO

3.1 netcoreapp3.1

3.0 netcoreapp3.0

2.1 netcoreapp2.0

--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.


--no-https

Desactiva HTTPS.

razorclasslib
--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.


-s|--support-pages-and-views

Permite agregar vistas y páginas de Razor tradicionales además de los componentes a esta
biblioteca. Disponible desde el SDK de .NET Core 3.0.

webapi
-au|--auth <AUTHENTICATION_TYPE>

Tipo de autenticación que se va a usar. Los valores posibles son:


None : sin autenticación (valor predeterminado).
IndividualB2C : autenticación individual con Azure AD B2C.
SingleOrg : autenticación organizativa para un solo inquilino.
Windows : autenticación de Windows.
--aad-b2c-instance <INSTANCE>

Instancia de Azure Active Directory B2C con la que se realiza la conexión. Úsela con la
autenticación IndividualB2C . El valor predeterminado es https://login.microsoftonline.com/tfp/ .
-ssp|--susi-policy-id <ID>

Identificador de la directiva de registro e inicio de sesión de este proyecto. Úsela con la


autenticación IndividualB2C .
--aad-instance <INSTANCE>

Instancia de Azure Active Directory con la que se realiza la conexión. Úsela con la autenticación
SingleOrg . El valor predeterminado es https://login.microsoftonline.com/ .

--client-id <ID>
Identificador de cliente de este proyecto. Úsela con las autenticaciones IndividualB2C o
SingleOrg . El valor predeterminado es 11111111-1111-1111-11111111111111111 .

--domain <DOMAIN>

Dominio del inquilino del directorio. Úsela con las autenticaciones IndividualB2C o SingleOrg . El
valor predeterminado es qualified.domain.name .
--tenant-id <ID>

Identificador de inquilino del directorio con el que se realiza la conexión. Úsela con la
autenticación SingleOrg . El valor predeterminado es 22222222-2222-2222-2222-222222222222 .
-r|--org-read-access

Concede a esta aplicación acceso de lectura al directorio. Solo se aplica a la autenticación


SingleOrg .

--exclude-launch-settings

Excluye launchSettings.json de la plantilla generada.


--no-https

Desactiva HTTPS. app.UseHsts y app.UseHttpsRedirection no se agregan a Startup.Configure .


Esta opción solo se aplica si no se usan IndividualB2C o SingleOrg en la autenticación.
-uld|--use-local-db

Especifica que se debería usar LocalDB en vez de SQLite. Solo se aplica a la autenticación
IndividualB2C .

-f|--framework <FRAMEWORK>

Especifica el marco de destino. Opción no disponible en el SDK de .NET Core 2.2.


En la tabla siguiente se enumeran los valores predeterminados según el número de versión del
SDK que esté usando:

VERSIÓ N DEL SDK VA LO R P REDET ERM IN A DO

3.1 netcoreapp3.1

3.0 netcoreapp3.0

2.1 netcoreapp2.1

--no-restore

No se ejecuta ninguna restauración implícita durante la creación del proyecto.

globaljson
--sdk-version <VERSION_NUMBER>

Especifica la versión del SDK de .NET Core que se usará en el archivo global.json.

Ejemplos
Creación de un proyecto de aplicación de consola de C# mediante la especificación del nombre de
plantilla:

dotnet new "Console Application"

Creación de un proyecto de aplicación de consola con F# en el directorio actual:

dotnet new console -lang "F#"

Creación de un proyecto de biblioteca de clases de .NET Standard en el directorio especificado:

dotnet new classlib -lang VB -o MyLibrary

Creación de un proyecto MVC de ASP.NET Core C# en el directorio actual sin autenticación:

dotnet new mvc -au None

Creación de un proyecto de xUnit:

dotnet new xunit

Enumeración de todas las plantillas disponibles en las plantillas de aplicación de página única
(SPA):

dotnet new spa -l

Enumeración de todas las plantillas que coinciden con la subcadena we. No se encuentra ninguna
coincidencia exacta, por lo que se ejecuta la coincidencia de subcadena con las columnas de
nombre corto y de nombre.

dotnet new we -l

Intento de invocar a la plantilla que coincide con ng. Si no se puede determinar una única
coincidencia, se enumeran las plantillas que son coincidencias parciales.

dotnet new ng

Instalación de la versión 2.0 de las plantillas de SPA de ASP.NET Core:

dotnet new -i Microsoft.DotNet.Web.Spa.ProjectTemplates::2.0.0

Enumeración de las plantillas instaladas y detalles sobre ellas, incluido cómo desinstalarlas:

dotnet new -u

Creación de un archivo global.json en el directorio actual al establecer la versión del SDK en


3.1.101:
dotnet new globaljson --sdk-version 3.1.101

Vea también
Plantillas personalizadas para dotnet new
Creación de una plantilla personalizada para dotnet new
Repositorio de GitHub dotnet/dotnet-template-samples
Available templates for dotnet new (Plantillas disponibles para dotnet new)
dotnet nuget delete
20/04/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 1.x y versiones posteriores

Name
dotnet nuget delete : elimina o quita de la lista un paquete del servidor.

Sinopsis
dotnet nuget delete [<PACKAGE_NAME> <PACKAGE_VERSION>] [--force-english-output]
[--interactive] [-k|--api-key <API_KEY>] [--no-service-endpoint]
[--non-interactive] [-s|--source <SOURCE>]

dotnet nuget delete -h|--help

Description
El comando dotnet nuget delete elimina o quita de la lista un paquete del servidor. Para nuget.org, la acción es
quitar de la lista el paquete.

Argumentos
PACKAGE_NAME

Nombre o id. del paquete que se va a eliminar.


PACKAGE_VERSION

Versión del paquete que se va a eliminar.

Opciones
--force-english-output

Fuerza la ejecución de la aplicación mediante una referencia cultural en inglés invariable.


-h|--help

Imprime una corta ayuda para el comando.


--interactive

Permite que el comando se bloquee y requiere una acción manual para operaciones tales como la
autenticación. Opción disponible a partir del SDK de .NET Core 2.2.
-k|--api-key <API_KEY>

La clave de API para el servidor.


--no-service-endpoint

No agrega "api/v2/paquete" a la dirección URL de origen. Opción disponible a partir del SDK de .NET Core
2.1.
--non-interactive

No pide confirmaciones o entradas de usuario.


-s|--source <SOURCE>

Especifica la dirección URL del servidor. Las direcciones URL admitidas para nuget.org incluyen
https://www.nuget.org , https://www.nuget.org/api/v3 y https://www.nuget.org/api/v2/package . Para fuentes
privadas, reemplace el nombre de host (por ejemplo, %hostname%/api/v3 ).

Ejemplos
Elimina la versión 1.0 del paquete Microsoft.AspNetCore.Mvc :

dotnet nuget delete Microsoft.AspNetCore.Mvc 1.0

Elimina la versión 1.0 del paquete Microsoft.AspNetCore.Mvc , sin pedir al usuario credenciales u otra
información:

dotnet nuget delete Microsoft.AspNetCore.Mvc 1.0 --non-interactive


dotnet nuget locals
20/04/2020 • 3 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

Name
dotnet nuget locals : borra o enumera recursos locales de NuGet.

Sinopsis
dotnet nuget locals <CACHE_LOCATION> [(-c|--clear)|(-l|--list)] [--force-english-output]

dotnet nuget locals -h|--help

Description
El comando dotnet nuget locals borra o enumera los recursos locales de NuGet en la caché de solicitudes http, la
caché temporal o la carpeta de paquetes globales de toda la máquina.

Argumentos
CACHE_LOCATION

La ubicación de caché que se va a mostrar o borrar. Acepta uno de los valores siguientes:
all : indica que la operación especificada se debe aplicar a todos los tipos de caché: caché de solicitudes
http, caché de paquetes globales y caché temporal.
http-cache : indica que la operación especificada se aplica solo a la caché de solicitudes http. Las otras
ubicaciones de caché no se ven afectadas.
global-packages : indica que la operación especificada se aplica solo a la caché de paquetes globales. Las
otras ubicaciones de caché no se ven afectadas.
temp : indica que la operación especificada se aplica solo a la caché temporal. Las otras ubicaciones de
caché no se ven afectadas.

Opciones
--force-english-output

Fuerza la ejecución de la aplicación mediante una referencia cultural en inglés invariable.


-h|--help

Imprime una corta ayuda para el comando.


-c|--clear

La opción de borrado ejecuta una operación de borrado sobre el tipo de caché especificado. El contenido de
los directorios de caché se elimina de forma recursiva. El usuario o grupo de ejecución deben tener permiso
para los archivos en los directorios de la caché. En caso contrario, se muestra un error para indicar los
archivos o las carpetas que no se han borrado.
-l|--list

La opción de lista se usa para mostrar la ubicación del tipo de caché especificado.

Ejemplos
Muestra las rutas de acceso de todos los directorios de caché locales (el directorio de caché http, el
directorio de caché de paquetes globales y el directorio de caché temporal):

dotnet nuget locals all –l

Muestra la ruta de acceso del directorio de la caché de solicitudes http:

dotnet nuget locals http-cache --list

Borra todos los archivos de todos los directorios de caché locales (directorio de caché http, directorio de
caché de paquetes globales y directorio de caché temporal):

dotnet nuget locals all --clear

Borra todos los archivos del directorio local de la caché de paquetes globales:

dotnet nuget locals global-packages -c

Borra todos los archivos del directorio local de la caché temporal:

dotnet nuget locals temp -c

Solución de problemas
Para más información sobre problemas y errores comunes encontrados al usar el comando dotnet nuget locals ,
consulte Managing the NuGet cache (Administración de la caché de NuGet).
dotnet nuget push
16/09/2020 • 5 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

NOMBRE
dotnet nuget push : inserta un paquete en el servidor y lo publica.

Sinopsis
dotnet nuget push [<ROOT>] [-d|--disable-buffering] [--force-english-output]
[--interactive] [-k|--api-key <API_KEY>] [-n|--no-symbols true]
[--no-service-endpoint] [-s|--source <SOURCE>] [--skip-duplicate]
[-sk|--symbol-api-key <API_KEY>] [-ss|--symbol-source <SOURCE>]
[-t|--timeout <TIMEOUT>]

dotnet nuget push -h|--help

Descripción
El comando dotnet nuget push inserta un paquete en el servidor y lo publica. El comando push usa los detalles del
servidor y de las credenciales encontrados en el archivo de configuración NuGet del sistema o en la cadena de
archivos de configuración. Para más información sobre los archivos de configuración, consulte Configuring NuGet
Behavior (Configuración del comportamiento de NuGet). La configuración predeterminada de NuGet se obtiene
mediante la carga de %AppData%\NuGet\NuGet.config (Windows) o $HOME/.local/share (Linux/macOS), y luego
la carga de cualquier archivo nuget.config o .nuget\nuget.config comenzando desde la raíz de la unidad y
finalizando en el directorio actual.
El comando inserta un paquete existente. No crea un paquete. Para crear un paquete, use dotnet pack .

Argumentos
ROOT

Especifica la ruta de acceso al archivo en la que se debe insertar el paquete.

Opciones
-d|--disable-buffering

Deshabilita el almacenamiento en búfer al realizar inserciones en un servidor HTTP(S) para reducir el uso de
memoria.
--force-english-output

Fuerza la ejecución de la aplicación mediante una referencia cultural en inglés invariable.


-h|--help

Imprime una corta ayuda para el comando.


--interactive
Permite que el comando se bloquee y requiere una acción manual para operaciones tales como la
autenticación. Opción disponible a partir del SDK de .NET Core 2.2.
-k|--api-key <API_KEY>

La clave de API para el servidor.


-n|--no-symbols true

No inserta símbolos (incluso si está presente).


--no-service-endpoint

No agrega "api/v2/paquete" a la dirección URL de origen. Opción disponible desde el SDK de .NET Core 2.1.
-s|--source <SOURCE>

Especifica la dirección URL del servidor. Esta opción es necesaria a menos que el valor de configuración
DefaultPushSource esté establecido en el archivo de configuración de NuGet.

--skip-duplicate

Al insertar varios paquetes en un servidor HTTP(S), trata cualquier respuesta de conflicto 409 como una
advertencia para que la inserción pueda continuar. Disponible a partir del SDK de .NET Core 3.1.
-sk|--symbol-api-key <API_KEY>

La clave de API para el servidor de símbolos.


-ss|--symbol-source <SOURCE>

Especifica la dirección URL del servidor de símbolos.


-t|--timeout <TIMEOUT>

Especifica el tiempo de espera para la inserción en un servidor en segundos. El valor predeterminado es 300
segundos (5 minutos). Si se especifica 0 (cero segundos), se aplica el valor predeterminado.

Ejemplos
Inserte foo.nupkg en el origen de inserción predeterminado y especifique una clave de API:

dotnet nuget push foo.nupkg -k 4003d786-cc37-4004-bfdf-c4f3e8ef9b3a

Inserte foo.nupkg en el servidor de NuGet oficial y especifique una clave de API:

dotnet nuget push foo.nupkg -k 4003d786-cc37-4004-bfdf-c4f3e8ef9b3a -s


https://api.nuget.org/v3/index.json

Inserta foo.nupkg en el origen de inserción personalizado https://customsource , y especifica una clave


de API:

dotnet nuget push foo.nupkg -k 4003d786-cc37-4004-bfdf-c4f3e8ef9b3a -s https://customsource/

Inserte foo.nupkg en el origen de inserción predeterminado:

dotnet nuget push foo.nupkg


Inserte foo.symbols.nupkp en el origen de símbolos predeterminado:

dotnet nuget push foo.symbols.nupkg

Inserte foo.nupkg en el origen de inserción predeterminado y especifique un tiempo de espera de


360 segundos:

dotnet nuget push foo.nupkg --timeout 360

Inserte todos los archivos .nupkg del directorio actual en el origen de inserción predeterminado:

dotnet nuget push "*.nupkg"

NOTE
Si este comando no funciona, es posible que se deba a un error presente en versiones anteriores del SDK (SDK de
.NET Core 2.1 y versiones anteriores). Para solucionar este problema, actualice la versión de su SDK o ejecute el
siguiente comando en su lugar: dotnet nuget push "**/*.nupkg"

NOTE
Las comillas de inclusión son necesarias para los shells como bash que usan comodines de archivo. Para obtener más
información, vea NuGet/Home#4393.

Inserte todos los archivos .nupkg, aunque un servidor HTTP(S) devuelva una respuesta de conflicto 409:

dotnet nuget push "*.nupkg" --skip-duplicate

Inserte todos los archivos .nupkg del directorio actual en un directorio de fuente local:

dotnet nuget push "*.nupkg" -s c:\mydir

Este comando no almacena paquetes en una estructura de carpetas jerárquica, lo que se recomienda para
optimizar el rendimiento. Para obtener más información, vea Fuentes locales.
dotnet nuget add source
20/04/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.1.200 y versiones posteriores

NOMBRE
dotnet nuget add source : agrega un origen de NuGet.

Sinopsis
dotnet nuget add source <PACKAGE_SOURCE_PATH> [--name <SOURCE_NAME>] [--username <USER>]
[--password <PASSWORD>] [--store-password-in-clear-text]
[--valid-authentication-types <TYPES>] [--configfile <FILE>]

dotnet nuget add source -h|--help

Descripción
El comando dotnet nuget add source agrega un nuevo origen de paquete a los archivos de configuración de
NuGet.

Argumentos
PACKAGE_SOURCE_PATH

Ruta de acceso al origen del paquete.

Opciones
--configfile <FILE>

Archivo de configuración de NuGet. Si se especifica, solo se usará la configuración de este archivo. Si no se


especifica, se utilizará la jerarquía de archivos de configuración del directorio actual. Para más información,
consulte Configuraciones comunes de NuGet.
-n|--name <SOURCE_NAME>

Nombre del origen.


-p|--password <PASSWORD>

Contraseña que se debe usar al conectarse a un origen autenticado.


--store-password-in-clear-text

Deshabilita el cifrado de la contraseña para permitir el almacenamiento de las credenciales de origen del
paquete portátil.
-u|--username <USER>

Nombre de usuario que se usará al conectarse a un origen autenticado.


--valid-authentication-types <TYPES>

Lista separada por comas de tipos de autenticación válidos para este origen. Establézcalo en basic si el
servidor anuncia NTLM o Negotiate y las credenciales deben enviarse mediante el mecanismo básico, por
ejemplo, cuando se usa una instancia de PAT con Azure DevOps Server local. Otros valores válidos son
negotiate , kerberos , ntlm y digest , pero es poco probable que estos valores sean útiles.

Ejemplos
Agregue nuget.org como origen:

dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org

Agregue c:\packages como origen local:

dotnet nuget add source c:\packages

Agregue un origen que necesite autenticación:

dotnet nuget add source https://someServer/myTeam -n myTeam -u myUsername -p myPassword --store-


password-in-clear-text

Agregue un origen que necesite autenticación (luego, pase a la instalación del proveedor de credenciales):

dotnet nuget add source https://azureartifacts.microsoft.com/myTeam -n myTeam

Vea también
Secciones de origen del paquete en archivos NuGet.config
Comando sources (nuget.exe)
dotnet nuget disable source
20/04/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.1.200 y versiones posteriores

NOMBRE
dotnet nuget disable source : deshabilita un origen de NuGet.

Sinopsis
dotnet nuget disable source <NAME> [--configfile <FILE>]

dotnet nuget disable source -h|--help

Descripción
El comando dotnet nuget disable source deshabilita un origen existente en los archivos de configuración de
NuGet.

Argumentos
NAME

Nombre del origen.

Opciones
--configfile <FILE>

Archivo de configuración de NuGet. Si se especifica, solo se usará la configuración de este archivo. Si no se


especifica, se utilizará la jerarquía de archivos de configuración del directorio actual. Para más información,
consulte Configuraciones comunes de NuGet.

Ejemplos
Deshabilite un origen con el nombre de mySource :

dotnet nuget disable source mySource

Vea también
Secciones de origen del paquete en archivos NuGet.config
Comando sources (nuget.exe)
dotnet nuget enable source
20/04/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.1.200 y versiones posteriores

NOMBRE
dotnet nuget enable source : habilita un origen de NuGet.

Sinopsis
dotnet nuget enable source <NAME> [--configfile <FILE>]

dotnet nuget enable source -h|--help

Descripción
El comando dotnet nuget enable source habilita un origen existente en los archivos de configuración de NuGet.

Argumentos
NAME

Nombre del origen.

Opciones
--configfile <FILE>

Archivo de configuración de NuGet. Si se especifica, solo se usará la configuración de este archivo. Si no se


especifica, se utilizará la jerarquía de archivos de configuración del directorio actual. Para más información,
consulte Configuraciones comunes de NuGet.

Ejemplos
Habilite un origen con el nombre de mySource :

dotnet nuget enable source mySource

Vea también
Secciones de origen del paquete en archivos NuGet.config
Comando sources (nuget.exe)
dotnet nuget list source
20/04/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.1.200 y versiones posteriores

NOMBRE
dotnet nuget list source : enumera todos los orígenes de NuGet configurados.

Sinopsis
dotnet nuget list source [--format [Detailed|Short]] [--configfile <FILE>]

dotnet nuget list source -h|--help

Descripción
El comando dotnet nuget list source muestra todos los orígenes existentes de los archivos de configuración de
NuGet.

Opciones
--configfile <FILE>

Archivo de configuración de NuGet. Si se especifica, solo se usará la configuración de este archivo. Si no se


especifica, se utilizará la jerarquía de archivos de configuración del directorio actual. Para más información,
consulte Configuraciones comunes de NuGet.
--format [Detailed|Short]

Formato de la salida del comando de lista: Detailed (valor predeterminado) y Short .

Ejemplos
Enumeración de los orígenes configurados del directorio actual:

dotnet nuget list source

Vea también
Secciones de origen del paquete en archivos NuGet.config
Comando sources (nuget.exe)
dotnet nuget remove source
20/04/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.1.200 y versiones posteriores

NOMBRE
dotnet nuget remove source : quita un origen de NuGet.

Sinopsis
dotnet nuget remove source <NAME> [--configfile <FILE>]

dotnet nuget remove source -h|--help

Descripción
El comando dotnet nuget remove source quita un origen existente de los archivos de configuración de NuGet.

Argumentos
NAME

Nombre del origen.

Opciones
--configfile

Archivo de configuración de NuGet. Si se especifica, solo se usará la configuración de este archivo. Si no se


especifica, se utilizará la jerarquía de archivos de configuración del directorio actual. Para más información,
consulte Configuraciones comunes de NuGet.

Ejemplos
Quite un origen con el nombre de mySource :

dotnet nuget remove source mySource

Vea también
Secciones de origen del paquete en archivos NuGet.config
Comando sources (nuget.exe)
dotnet nuget update source
20/04/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.1.200 y versiones posteriores

NOMBRE
dotnet nuget update source : actualiza un origen de NuGet.

Sinopsis
dotnet nuget update source <NAME> [--source <SOURCE>] [--username <USER>]
[--password <PASSWORD>] [--store-password-in-clear-text]
[--valid-authentication-types <TYPES>] [--configfile <FILE>]

dotnet nuget update source -h|--help

Descripción
El comando dotnet nuget update source actualiza un origen existente en los archivos de configuración de NuGet.

Argumentos
NAME

Nombre del origen.

Opciones
--configfile <FILE>

Archivo de configuración de NuGet. Si se especifica, solo se usará la configuración de este archivo. Si no se


especifica, se utilizará la jerarquía de archivos de configuración del directorio actual. Para más información,
consulte Configuraciones comunes de NuGet.
-p|--password <PASSWORD>

Contraseña que se debe usar al conectarse a un origen autenticado.


-s|--source <SOURCE>

Ruta de acceso al origen del paquete.


--store-password-in-clear-text

Deshabilita el cifrado de la contraseña para permitir el almacenamiento de las credenciales de origen del
paquete portátil.
-u|--username <USER>

Nombre de usuario que se usará al conectarse a un origen autenticado.


--valid-authentication-types <TYPES>
Lista separada por comas de tipos de autenticación válidos para este origen. Establézcalo en basic si el
servidor anuncia NTLM o Negotiate y las credenciales deben enviarse mediante el mecanismo básico, por
ejemplo, cuando se usa una instancia de PAT con Azure DevOps Server local. Otros valores válidos son
negotiate , kerberos , ntlm y digest , pero es poco probable que estos valores sean útiles.

Ejemplos
Actualice un origen con el nombre de mySource :

dotnet nuget update source mySource --source c:\packages

Vea también
Secciones de origen del paquete en archivos NuGet.config
Comando sources (nuget.exe)
dotnet pack
16/09/2020 • 9 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

NOMBRE
dotnet pack : empaqueta el código en un paquete de NuGet.

Sinopsis
dotnet pack [<PROJECT>|<SOLUTION>] [-c|--configuration <CONFIGURATION>]
[--force] [--include-source] [--include-symbols] [--interactive]
[--no-build] [--no-dependencies] [--no-restore] [--nologo]
[-o|--output <OUTPUT_DIRECTORY>] [--runtime <RUNTIME_IDENTIFIER>]
[-s|--serviceable] [-v|--verbosity <LEVEL>]
[--version-suffix <VERSION_SUFFIX>]

dotnet pack -h|--help

Descripción
El comando dotnet pack compila el proyecto y crea paquetes de NuGet. El resultado de este comando es un
paquete de NuGet (es decir, un archivo .nupkg).
Si quiere generar un paquete que contenga los símbolos de depuración, tiene dos opciones a su disposición:
--include-symbols : crea el paquete de símbolos.
--include-source : crea el paquete de símbolos con una carpeta src dentro que contiene los archivos de
origen.
Las dependencias de NuGet del proyecto empaquetado se agregan al archivo .nuspec, por lo que se pueden
resolver adecuadamente cuando se instala el paquete. Las referencias de proyecto a proyecto no se empaquetan
dentro del proyecto. Actualmente, debe disponer de un paquete por proyecto si tiene dependencias de proyecto
a proyecto.
De forma predeterminada, dotnet pack compila primero el proyecto. Si desea evitar este comportamiento, pase
la opción --no-build . Esta opción a menudo resulta útil en escenarios de compilación de integración continua
(CI) donde se conoce el código que se compiló anteriormente.

NOTE
En algunos casos, no se puede realizar la compilación implícita. Esto puede ocurrir cuando se establece
GeneratePackageOnBuild , para evitar una dependencia cíclica entre los destinos de compilación y de paquete. La
compilación también puede producir un error si hay un archivo bloqueado u otro problema.

Puede proporcionar propiedades de MSBuild en el comando dotnet pack para el proceso de empaquetado.
Para obtener más información, vea Propiedades de metadatos de NuGet y la Referencia de la línea de comandos
de MSBuild. La sección Ejemplos muestra cómo utilizar el modificador -p de MSBuild en un par de escenarios
diferentes.
Los proyectos web no están empaquetados de forma predeterminada. Para invalidar el comportamiento
predeterminado, agregue la siguiente propiedad a su archivo .csproj:

<PropertyGroup>
<IsPackable>true</IsPackable>
</PropertyGroup>

Restauración implícita
No es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que necesitan
que se produzca una restauración, como dotnet new , dotnet build , dotnet run , dotnet test , dotnet publish
y dotnet pack . Para deshabilitar la restauración implícita, use la opción --no-restore .
El comando dotnet restore sigue siendo válido en algunos escenarios donde tiene sentido realizar una
restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los
sistemas de compilación que necesitan controlar explícitamente cuándo se produce la restauración.
Para obtener información sobre cómo administrar fuentes de NuGet, vea la documentación de dotnet restore .
Este comando admite las opciones de dotnet restore cuando se pasan con el formato largo (por ejemplo,
--source ). No se admiten las opciones de formato corto, como -s .

Argumentos
PROJECT | SOLUTION

El archivo de proyecto o solución para empaquetar. Es una ruta de acceso a un archivo csproj, un archivo vbproj,
un archivo fsproj, un archivo de solución o un directorio. Si no se especifica, el comando busca un archivo del
proyecto o de la solución en el directorio actual.

Opciones
-c|--configuration <CONFIGURATION>

Define la configuración de compilación. El valor predeterminado para la mayoría de los proyectos es


Debug , pero puede invalidar los valores de configuración de compilación en el proyecto.

--force

Fuerza la resolución de todas las dependencias, incluso si la última restauración se realizó correctamente.
Especificar esta marca es lo mismo que eliminar el archivo project.assets.json.
-h|--help

Imprime una corta ayuda para el comando.


--include-source

Incluye los paquetes NuGet de símbolos de depuración, además de los paquetes NuGet normales en el
directorio de salida. Los archivos de origen se incluyen en la carpeta src dentro del paquete de
símbolos.
--include-symbols

Incluye los paquetes NuGet de símbolos de depuración, además de los paquetes NuGet normales en el
directorio de salida.
--interactive
Permite que el comando se detenga y espere la entrada o acción del usuario (por ejemplo, completar la
autenticación). Disponible desde el SDK de .NET Core 3.0.
--no-build

No compila el proyecto antes de empaquetarlo. También establece la marca --no-restore de forma


implícita.
--no-dependencies

Omite las referencias de proyecto a proyecto y solo restaura el proyecto raíz.


--no-restore

No ejecuta una restauración implícita al ejecutar el comando.


--nologo

No se muestra la pancarta de inicio ni el mensaje de copyright. Disponible desde el SDK de .NET Core 3.0.
-o|--output <OUTPUT_DIRECTORY>

Coloca los paquetes compilados en el directorio especificado.


--runtime <RUNTIME_IDENTIFIER>

Especifica el tiempo de ejecución de destino para el que restaurar los paquetes. Para obtener una lista de
identificadores de tiempo de ejecución (RID), consulte el catálogo de RID.
-s|--serviceable

Establece la marca de servicio en el paquete. Para más información, consulte .NET Blog: .NET 4.5.1
Supports Microsoft Security Updates for .NET NuGet Libraries (Blog de .NET: .NET 4.5.1 admite
actualizaciones de seguridad de Microsoft para bibliotecas NuGet de .NET).
--version-suffix <VERSION_SUFFIX>

Define el valor de la propiedad de $(VersionSuffix) en el proyecto.


-v|--verbosity <LEVEL>

Establece el nivel de detalle del comando. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] ,
d[etailed] y diag[nostic] .

Ejemplos
Empaquetado del proyecto en el directorio actual:

dotnet pack

Empaquetar el proyecto app1 :

dotnet pack ~/projects/app1/project.csproj

Empaquetar el proyecto en el directorio actual y colocar los paquetes resultantes en la carpeta nupkgs :

dotnet pack --output nupkgs


Empaquetar el proyecto en el directorio actual en la carpeta nupkgs y omitir del paso de compilación:

dotnet pack --no-build --output nupkgs

Con el sufijo de la versión del proyecto configurado como


<VersionSuffix>$(VersionSuffix)</VersionSuffix> en el archivo .csproj, empaquetar el proyecto actual y
actualizar la versión del paquete resultante con el sufijo dado:

dotnet pack --version-suffix "ci-1234"

Establecer la versión del paquete en 2.1.0 con la propiedad de MSBuild PackageVersion :

dotnet pack -p:PackageVersion=2.1.0

Empaquete el proyecto para un determinado marco de destino:

dotnet pack -p:TargetFrameworks=net45

Empaquete el proyecto y use un entorno de ejecución específico (Windows 10) para la operación de
restauración:

dotnet pack --runtime win10-x64

Empaquete el proyecto mediante un archivo .nuspec:

dotnet pack ~/projects/app1/project.csproj -p:NuspecFile=~/projects/app1/project.nuspec -


p:NuspecBasePath=~/projects/app1/nuget

Para obtener información sobre cómo usar NuspecFile , NuspecBasePath y NuspecProperties , vea los
siguientes recursos:
Empaquetado mediante un archivo .nuspec
Puntos de extensión avanzados para crear un paquete personalizado
Propiedades globales
dotnet publish
16/09/2020 • 18 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores

NOMBRE
dotnet publish : publica la aplicación y sus dependencias en una carpeta para la implementación en un
sistema de hospedaje.

Sinopsis
dotnet publish [<PROJECT>|<SOLUTION>] [-c|--configuration <CONFIGURATION>]
[-f|--framework <FRAMEWORK>] [--force] [--interactive]
[--manifest <PATH_TO_MANIFEST_FILE>] [--no-build] [--no-dependencies]
[--no-restore] [--nologo] [-o|--output <OUTPUT_DIRECTORY>]
[-p:PublishReadyToRun=true] [-p:PublishSingleFile=true] [-p:PublishTrimmed=true]
[-r|--runtime <RUNTIME_IDENTIFIER>] [--self-contained [true|false]]
[--no-self-contained] [-v|--verbosity <LEVEL>]
[--version-suffix <VERSION_SUFFIX>]

dotnet publish -h|--help

Descripción
dotnet publish : compila la aplicación, lee sus dependencias especificadas en el archivo de proyecto y
publica el conjunto resultante de archivos en un directorio. La salida incluye los recursos siguientes:
Código de lenguaje intermedio (IL) en un ensamblado con una extensión dll.
Un archivo .deps.json que incluye todas las dependencias del proyecto.
Un archivo .runtime.config.json en el que se especifica el tiempo de ejecución compartido que espera la
aplicación, así como otras opciones de configuración para el tiempo de ejecución (por ejemplo, el tipo de
recolección de elementos no utilizados).
Las dependencias de la aplicación, que se copian de la caché de NuGet a la carpeta de salida.
La salida del comando dotnet publish está lista para la implementación en un sistema de hospedaje (por
ejemplo, un servidor, un equipo PC o Mac, un portátil) para la ejecución. Es la única manera admitida
oficialmente para preparar la aplicación para la implementación. Dependiendo del tipo de implementación
que especifique el proyecto, el sistema de hospedaje puede o no tener instalado el entorno de tiempo de
ejecución compartido de .NET Core. Para obtener más información, vea Publicación de aplicaciones .NET
Core con la CLI de .NET Core.
Restauración implícita
No es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que
necesitan que se produzca una restauración, como dotnet new , dotnet build , dotnet run , dotnet test ,
dotnet publish y dotnet pack . Para deshabilitar la restauración implícita, use la opción --no-restore .

El comando dotnet restore sigue siendo válido en algunos escenarios donde tiene sentido realizar una
restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los
sistemas de compilación que necesitan controlar explícitamente cuándo se produce la restauración.
Para obtener información sobre cómo administrar fuentes de NuGet, vea la documentación de
dotnet restore .

MSBuild
Con el comando dotnet publish se llama a MSBuild, lo que invoca el destino Publish . Todos los
parámetros pasados a dotnet publish se pasan a MSBuild. Los parámetros -c y -o se asignan
respectivamente a las propiedades Configuration y PublishDir de MSBuild.
El comando dotnet publish acepta opciones de MSBuild, como -p para establecer propiedades y -l para
definir un registrador. Por ejemplo, se puede establecer una propiedad de MSBuild mediante el uso del
formato: -p:<NAME>=<VALUE> .
También se pueden establecer las propiedades relacionadas con la publicación si se hace referencia a un
archivo .pubxml (disponible a partir del SDK de .NET Core 3.1). Por ejemplo:

dotnet publish -p:PublishProfile=FolderProfile

En el ejemplo anterior se usa el archivo FolderProfile.pubxml que se encuentra en la carpeta


<project_folder>/Properties/PublishProfiles. Si especifica una ruta de acceso y una extensión de archivo al
establecer la propiedad PublishProfile , estas se omiten. De forma predeterminada, MSBuild busca en la
carpeta Properties/PublishProfiles y da por hecho la extensión de archivo pubxml. Para especificar la ruta de
acceso y el nombre de archivo, incluida la extensión, establezca la propiedad PublishProfileFullPath en
lugar de la propiedad PublishProfile .
Para obtener más información, vea los siguientes recursos:
Referencia de la línea de comandos de MSBuild
Perfiles de publicación (.pubxml) de Visual Studio para la implementación de aplicaciones ASP.NET Core
dotnet msbuild

Argumentos
PROJECT|SOLUTION

El archivo de proyecto o solución que se va a publicar.


PROJECT es la ruta de acceso y el nombre de archivo de un archivo de proyecto de C#, F# o
Visual Basic, o bien la ruta de acceso a un directorio que contiene un archivo de proyecto de
C#, F# o Visual Basic. Si no se especifica el directorio, se toma como predeterminado el actual.
SOLUTION es la ruta de acceso y el nombre de archivo de un archivo de solución (extensión
.sln), o bien la ruta de acceso a un directorio que contiene un archivo de solución. Si no se
especifica el directorio, se toma como predeterminado el actual. Disponible desde el SDK de
.NET Core 3.0.

Opciones
-c|--configuration <CONFIGURATION>

Define la configuración de compilación. El valor predeterminado para la mayoría de los proyectos es


Debug , pero puede invalidar los valores de configuración de compilación en el proyecto.

-f|--framework <FRAMEWORK>

Publica la aplicación para el marco de trabajo de destino especificado. Debe especificar el marco de
trabajo de destino en el archivo de proyecto.
--force

Fuerza la resolución de todas las dependencias, incluso si la última restauración se realizó


correctamente. Especificar esta marca es lo mismo que eliminar el archivo project.assets.json.
-h|--help

Imprime una corta ayuda para el comando.


--interactive

Permite que el comando se detenga y espere una entrada o una acción del usuario. Por ejemplo, para
completar la autenticación. Disponible desde el SDK de .NET Core 3.0.
--manifest <PATH_TO_MANIFEST_FILE>

Especifica uno o varios manifiestos de destino que se usarán para recortar el conjunto de paquetes
publicados con la aplicación. El archivo de manifiesto es parte de la salida del comando dotnet store
. Para especificar varios manifiestos, agregue la opción --manifest para cada manifiesto.
--no-build

No compila el proyecto antes de publicarlo. También establece la marca --no-restore de forma


implícita.
--no-dependencies

Omite las referencias de proyecto a proyecto y solo restaura el proyecto raíz.


--nologo

No se muestra la pancarta de inicio ni el mensaje de copyright. Disponible desde el SDK de


.NET Core 3.0.
--no-restore

No ejecuta una restauración implícita al ejecutar el comando.


-o|--output <OUTPUT_DIRECTORY>

Especifica la ruta de acceso del directorio de salida.


Si no se especifica, el valor predeterminado es [carpeta_de_archivo_de-
_proyecto]./bin/[configuración]/[marco]/publish/ para un archivo ejecutable dependiente del marco y
archivos binarios multiplataforma. El valor predeterminado es
[project_file_folder]/bin/[configuration]/[framework]/[runtime]/publish/ para un archivo ejecutable
autocontenido.
En un proyecto web, si la carpeta de salida se encuentra en la carpeta del proyecto, los comandos
dotnet publish posteriores dan como resultado carpetas de salida anidadas. Por ejemplo, si la
carpeta del proyecto es myproject y la carpeta de salida de la publicación es myproject/publish, y
ejecuta dotnet publish dos veces, la segunda ejecución coloca los archivos de contenido, como
.config y .json, en myproject/publish/publish. Para evitar el anidamiento de carpetas de publicación,
especifique una que no esté directamente en la carpeta del proyecto, o bien excluya la carpeta de
publicación del proyecto. Para excluir una carpeta de publicación denominada publishoutput, agregue
el elemento siguiente a un elemento PropertyGroup en el archivo .csproj:

<DefaultItemExcludes>$(DefaultItemExcludes);publishoutput**</DefaultItemExcludes>
SDK de .NET Core 3.x y versiones posteriores
Si se especifica una ruta de acceso relativa al publicar un proyecto, el directorio de salida
generado es relativo al directorio de trabajo actual, no a la ubicación del archivo del proyecto.
Si se especifica una ruta de acceso relativa al publicar una solución, todas las salidas de todos
los proyectos van en la carpeta especificada relativa al directorio de trabajo actual. A fin de que
la salida de la publicación vaya a carpetas independientes para cada proyecto, especifique una
ruta de acceso relativa mediante el uso de la propiedad PublishDir de MSBuild en lugar de la
opción --output . Por ejemplo, dotnet publish -p:PublishDir=.\publish envía la salida de
publicación de cada proyecto a una carpeta publish en la carpeta que contiene el archivo del
proyecto.
SDK de .NET Core 2.x
Si se especifica una ruta de acceso relativa al publicar un proyecto, el directorio de salida
generado es relativo a la ubicación del archivo del proyecto, no al directorio de trabajo actual.
Si se especifica una ruta de acceso relativa al publicar una solución, la salida de cada proyecto
va a una carpeta independiente relativa a la ubicación del archivo del proyecto. Si se especifica
una ruta de acceso absoluta al publicar una solución, la salida de las publicaciones de todos los
proyectos van a la carpeta especificada.
-p:PublishReadyToRun=true

Compila los ensamblados de aplicación con el formato ReadyToRun (R2R). R2R es una forma de
compilación Ahead Of Time (AOT). Para obtener más información, vea Imágenes ReadyToRun.
Disponible desde el SDK de .NET Core 3.0.
Se recomienda especificar esta opción en un perfil de publicación en lugar de hacerlo en la línea de
comandos. Para obtener más información, vea MSBuild.
-p:PublishSingleFile=true

Empaqueta la aplicación en un ejecutable de archivo único específico de la plataforma. El archivo


ejecutable es autoextraíble y contiene todas las dependencias (incluidas las nativas) necesarias para
ejecutar la aplicación. Cuando la aplicación se ejecuta por primera vez, se extrae en un directorio que
se basa en el nombre de la aplicación y el identificador de compilación. El inicio es más rápido cuando
se vuelve a ejecutar la aplicación. No es necesario extraer la aplicación por segunda vez a menos que
se haya usado una versión nueva. Disponible desde el SDK de .NET Core 3.0.
Para obtener más información sobre la publicación de archivos únicos, vea el documento de diseño
del programa de instalación de conjunto de archivos únicos.
Se recomienda especificar esta opción en un perfil de publicación en lugar de hacerlo en la línea de
comandos. Para obtener más información, vea MSBuild.
-p:PublishTrimmed=true

Recorta las bibliotecas no utilizadas para reducir el tamaño de implementación de una aplicación
cuando se publica un ejecutable independiente. Para obtener más información, vea Recorte de
implementaciones autocontenidas y ejecutables. Disponible a partir del SDK de .NET Core 3.0 como
una característica en versión preliminar.
Se recomienda especificar esta opción en un perfil de publicación en lugar de hacerlo en la línea de
comandos. Para obtener más información, vea MSBuild.
--self-contained [true|false]
Publica el tiempo de ejecución de .NET Core con la aplicación para que no sea necesario tener
instalado el tiempo de ejecución en la máquina de destino. El valor predeterminado es true si se
especifica un identificador en tiempo de ejecución y el proyecto es de tipo ejecutable (no un proyecto
de biblioteca). Para obtener más información, vea Publicación de aplicaciones .NET Core y Publicación
de aplicaciones .NET Core con la CLI de .NET Core.
Si se usa esta opción sin especificar true o false , el valor predeterminado es true . En ese caso, no
coloque el argumento de la solución o el proyecto inmediatamente después de --self-contained ,
porque se espera que true o false estén en esa posición.
--no-self-contained

Equivalente a --self-contained false . Disponible desde el SDK de .NET Core 3.0.


-r|--runtime <RUNTIME_IDENTIFIER>

Publica la aplicación para un determinado entorno de tiempo de ejecución. Para obtener una lista de
identificadores de tiempo de ejecución (RID), consulte el catálogo de RID. Para obtener más
información, vea Publicación de aplicaciones .NET Core y Publicación de aplicaciones .NET Core con la
CLI de .NET Core.
-v|--verbosity <LEVEL>

Establece el nivel de detalle del comando. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] ,
d[etailed] y diag[nostic] . El valor predeterminado es minimal .

--version-suffix <VERSION_SUFFIX>

Define el sufijo de versión para reemplazar el asterisco ( * ) en el campo de versión del archivo de
proyecto.

Ejemplos
Cree un archivo binario multiplataforma dependiente del marco para el proyecto en el directorio
actual:

dotnet publish

A partir del SDK de .NET Core 3.0, en este ejemplo también se crea un ejecutable dependiente del
marco para la plataforma actual.
Cree un ejecutable independiente para el proyecto en el directorio actual, para un tiempo de
ejecución específico:

dotnet publish --runtime osx.10.11-x64

El RID debe estar en el archivo del proyecto.


Cree un ejecutable dependiente del marco para el proyecto en el directorio actual, para una
plataforma específica:

dotnet publish --runtime osx.10.11-x64 --self-contained false

El RID debe estar en el archivo del proyecto. Este ejemplo se aplica al SDK de .NET Core 3.0 y
versiones posteriores.
Publique el proyecto en el directorio actual, para un tiempo de ejecución específico y una plataforma
de destino:

dotnet publish --framework netcoreapp3.1 --runtime osx.10.11-x64

Publique el archivo del proyecto especificado:

dotnet publish ~/projects/app1/app1.csproj

Publique la aplicación actual pero sin restaurar las referencias de proyecto a proyecto (P2P), solo el
proyecto raíz, durante la operación de restauración:

dotnet publish --no-dependencies

Vea también
Información general sobre la publicación de aplicaciones de .NET Core
Publicación de aplicaciones .NET Core con la CLI de .NET Core
Marcos de trabajo de destino
Catálogo de identificadores de tiempo de ejecución (RID)
Trabajo con la certificación de macOS Catalina
Estructura de directorios de una aplicación publicada
Referencia de la línea de comandos de MSBuild
Perfiles de publicación (.pubxml) de Visual Studio para la implementación de aplicaciones ASP.NET Core
dotnet msbuild
ILLInk.Tasks
dotnet restore
16/09/2020 • 9 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores

NOMBRE
dotnet restore : restaura las dependencias y las herramientas de un proyecto.

Sinopsis
dotnet restore [<ROOT>] [--configfile <FILE>] [--disable-parallel]
[-f|--force] [--force-evaluate] [--ignore-failed-sources]
[--interactive] [--lock-file-path <LOCK_FILE_PATH>] [--locked-mode]
[--no-cache] [--no-dependencies] [--packages <PACKAGES_DIRECTORY>]
[-r|--runtime <RUNTIME_IDENTIFIER>] [-s|--source <SOURCE>]
[--use-lock-file] [-v|--verbosity <LEVEL>]

dotnet restore -h|--help

Descripción
El comando dotnet restore usa NuGet para restaurar las dependencias, así como las herramientas
específicas del proyecto que se especifican en el archivo project.json. En la mayoría de los casos, no es
necesario usar explícitamente el comando dotnet restore , ya que una restauración de NuGet se
ejecuta implícitamente si es necesario al ejecutar los siguientes comandos:
dotnet new
dotnet build
dotnet build-server
dotnet run
dotnet test
dotnet publish
dotnet pack

A veces, puede que no sea conveniente ejecutar la restauración de NuGet implícita con estos
comandos. Por ejemplo, algunos sistemas automatizados, como los sistemas de compilación, deben
llamar a dotnet restore explícitamente para controlar cuándo se produce la restauración a fin de
controlar el uso de la red. Para evitar la restauración de NuGet implícita, puede usar la marca
--no-restore con cualquiera de estos comandos para deshabilitar la restauración implícita.

Especificación de fuentes
Para restaurar las dependencias, NuGet necesita las fuentes donde se encuentran los paquetes. Las
fuente se proporcionan normalmente mediante el archivo de configuración nuget.config. Cuando se
instala el SDK de .NET Core, se proporciona un archivo de configuración predeterminado. Para
especificar fuentes adicionales, realice una de las acciones siguientes:
Cree su propio archivo nuget.config en el directorio del proyecto. Para obtener más información,
vea Configuraciones comunes de NuGet y Diferencias de nuget.config más adelante en este
artículo.
Use comandos de dotnet nuget como dotnet nuget add source .
Puede invalidar las fuentes nuget.config con la opción -s .
Para obtener información sobre cómo usar las fuentes autenticadas, vea Consumir paquetes desde
fuentes autenticadas.
Carpeta de paquetes globales
Para las dependencias, puede especificar dónde se colocan los paquetes restaurados durante la
operación de restauración mediante el argumento --packages . Si no se especifica, se usa la caché de
paquetes NuGet predeterminada, que se encuentra en el directorio .nuget/packages del directorio de
inicio del usuario en todos los sistemas operativos. Por ejemplo, /home/usuario1 en Linux o
C:\Usuarios\usuario1 en Windows.
Herramientas específicas del proyecto
Para herramientas específicas del proyecto, dotnet restore restaura primero el paquete en el que se
empaqueta la herramienta y, a continuación, continúa con la restauración de las dependencias de la
herramienta especificadas en su project.json.
Diferencias de nuget.config
El comportamiento del comando dotnet restore depende de las opciones de configuración del
archivo nuget.config, si existe. Por ejemplo, establecer globalPackagesFolder en nuget.config coloca los
paquetes NuGet restaurados en la carpeta especificada. Esta es una alternativa para especificar la
opción --packages en el comando dotnet restore . Para más información, consulte la referencia de
nuget.config.
Hay tres configuraciones específicas que dotnet restore omite:
bindingRedirects
Los redireccionamientos de enlace no funcionan con elementos de <PackageReference> y
.NET Core solo admite elementos de <PackageReference> para los paquetes NuGet.
solution
Esta configuración es específica para Visual Studio y no se aplica a .NET Core. .NET Core no usa
un archivo packages.config y, en su lugar, usa elementos de <PackageReference> para los
paquetes NuGet.
trustedSigners
Esta configuración no es aplicable porque NuGet ya no admite la comprobación
multiplataforma de los paquetes de confianza.

Argumentos
ROOT

Ruta de acceso opcional del archivo de proyecto para restaurar.

Opciones
--configfile <FILE>

El archivo de configuración de NuGet (nuget.config) que se usa para la operación de


restauración.
--disable-parallel
Deshabilita la restauración de varios proyectos en paralelo.
--force

Fuerza la resolución de todas las dependencias, incluso si la última restauración se realizó


correctamente. Especificar esta marca es lo mismo que eliminar el archivo project.assets.json.
--force-evaluate

Fuerza la restauración para volver a evaluar todas las dependencias aunque ya exista un archivo
de bloqueo.
-h|--help

Imprime una corta ayuda para el comando.


--ignore-failed-sources

Solo se advierte sobre los orígenes con error si hay paquetes que satisfagan el requisito de
versión.
--interactive

Permite que el comando se detenga y espere la entrada o acción del usuario (por ejemplo,
completar la autenticación). Desde .NET Core 2.1.400.
--lock-file-path <LOCK_FILE_PATH>

Ubicación de salida donde se escribe el archivo de bloqueo del proyecto. De forma


predeterminada es PROJECT_ROOT\packages.lock.json.
--locked-mode

No permite actualizar el archivo de bloqueo del proyecto.


--no-cache

Especifica que no se almacenen en caché las solicitudes HTTP.


--no-dependencies

Al restaurar un proyecto con referencias de proyecto a proyecto (P2P), se restaura el proyecto


raíz y no las referencias.
--packages <PACKAGES_DIRECTORY>

Especifica el directorio de los paquetes restaurados.


-r|--runtime <RUNTIME_IDENTIFIER>

Especifica un tiempo de ejecución para la restauración del paquete. Se usa para restaurar los
paquetes con tiempos de ejecución que no se enumeran explícitamente en la etiqueta
<RuntimeIdentifiers> del archivo .csproj. Para obtener una lista de identificadores de tiempo de
ejecución (RID), consulte el catálogo de RID. Para proporcionar varios RID; especifique esta
opción varias veces.
-s|--source <SOURCE>

Especifica el URI del origen del paquete NuGet que se usará durante la operación de
restauración. Este valor invalida todos los orígenes especificados en los archivos nuget.config.
Al especificar esta opción varias veces, se pueden proporcionar varios orígenes.
--use-lock-file

Habilita la generación del archivo de bloqueo del proyecto y su uso con la restauración.
-v|--verbosity <LEVEL>

Establece el nivel de detalle del comando. Los valores permitidos son q[uiet] , m[inimal] ,
n[ormal] , d[etailed] y diag[nostic] . El valor predeterminado es minimal .

Ejemplos
Restauración de dependencias y herramientas para el proyecto en el directorio actual:

dotnet restore

Restauración de dependencias y herramientas para el proyecto app1 encontrado en la ruta de


acceso dada:

dotnet restore ./projects/app1/app1.csproj

Restauración de dependencias y herramientas para el proyecto en el directorio actual con la


ruta de acceso de archivo proporcionada como origen:

dotnet restore -s c:\packages\mypackages

Restauración de dependencias y herramientas para el proyecto en el directorio actual mediante


las dos rutas de acceso de archivo proporcionadas como orígenes:

dotnet restore -s c:\packages\mypackages -s c:\packages\myotherpackages

Restauración de dependencias y herramientas para el proyecto en el directorio actual que


muestra una salida detallada:

dotnet restore --verbosity detailed


dotnet run
27/04/2020 • 8 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

NOMBRE
dotnet run : ejecuta el código fuente sin comandos explícitos de compilación o inicio.

Sinopsis
dotnet run [-c|--configuration <CONFIGURATION>] [-f|--framework <FRAMEWORK>]
[--force] [--interactive] [--launch-profile <NAME>] [--no-build]
[--no-dependencies] [--no-launch-profile] [--no-restore]
[-p|--project <PATH>] [-r|--runtime <RUNTIME_IDENTIFIER>]
[-v|--verbosity <LEVEL>] [[--] [application arguments]]

dotnet run -h|--help

Descripción
El comando dotnet run proporciona una opción conveniente para ejecutar la aplicación desde el código fuente
con un comando. Es útil para un desarrollo iterativo rápido desde la línea de comandos. El comando depende del
comando dotnet build para compilar el código. Los requisitos para la compilación, como que el cliente se deba
restaurar primero, también se aplican a dotnet run .
Los archivos de salida se escriben en la ubicación predeterminada, que es bin/<configuration>/<target> . Por
ejemplo, si tiene una aplicación netcoreapp2.1 y ejecuta dotnet run , la salida se colocará en
bin/Debug/netcoreapp2.1 . Los archivos se sobrescriben según sea necesario. Los archivos temporales se colocan
en el directorio obj .
Si el proyecto especifica varios marcos, al ejecutar dotnet run se produce un error a menos que se use la opción
-f|--framework <FRAMEWORK> para especificar el marco.

El comando dotnet run debe usarse en el contexto de proyectos, no de ensamblados compilados. Si, por el
contrario, está intentando ejecutar una DLL de aplicación dependiente del marco de trabajo, debe usar dotnet sin
un comando. Por ejemplo, para ejecutar myapp.dll , use:

dotnet myapp.dll

Para más información sobre el controlador dotnet , consulte el tema Herramientas de la interfaz de la línea de
comandos (CLI) de .NET Core .
Para ejecutar la aplicación, el comando dotnet run resuelve las dependencias de la aplicación que se encuentran
fuera del entorno de tiempo de ejecución compartido desde la caché de NuGet. Dado que se usan dependencias
almacenadas en caché, no se recomienda utilizar dotnet run para ejecutar aplicaciones en producción. En su
lugar, cree una implementación mediante el comando dotnet publish e implemente la salida publicada.
Restauración implícita
No es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que necesitan
que se produzca una restauración, como dotnet new , dotnet build , dotnet run , dotnet test , dotnet publish
y dotnet pack . Para deshabilitar la restauración implícita, use la opción --no-restore .
El comando dotnet restore sigue siendo válido en algunos escenarios donde tiene sentido realizar una
restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los
sistemas de compilación que necesitan controlar explícitamente cuándo se produce la restauración.
Para obtener información sobre cómo administrar fuentes de NuGet, vea la documentación de dotnet restore .
Este comando admite las opciones de dotnet restore cuando se pasan con el formato largo (por ejemplo,
--source ). No se admiten las opciones de formato corto, como -s .

Opciones
--

Delimita los argumentos a dotnet run a partir de argumentos de la aplicación que se va a ejecutar. Todos
los argumentos después de este delimitador se pasan a la aplicación que se ejecuta.
-c|--configuration <CONFIGURATION>

Define la configuración de compilación. El valor predeterminado para la mayoría de los proyectos es


Debug , pero puede invalidar los valores de configuración de compilación en el proyecto.

-f|--framework <FRAMEWORK>

Compila y ejecuta la aplicación con el marco especificado. El marco debe especificarse en el archivo de
proyecto.
--force

Fuerza la resolución de todas las dependencias, incluso si la última restauración se realizó correctamente.
Especificar esta marca es lo mismo que eliminar el archivo project.assets.json.
-h|--help

Imprime una corta ayuda para el comando.


--interactive

Permite que el comando se detenga y espere la entrada o acción del usuario (por ejemplo, completar la
autenticación). Disponible desde el SDK de .NET Core 3.0.
--launch-profile <NAME>

El nombre del perfil de inicio (si lo hay) que se usará al iniciar la aplicación. Los perfiles de inicio se
definen en el archivo launchSettings.json y se suelen denominar Development , Staging y Production .
Para obtener más información, consulte Working with multiple environments (Trabajo con varios
entornos).
--no-build

No compila el proyecto antes de ejecutarlo. También establece la marca --no-restore de forma implícita.
--no-dependencies

Al restaurar un proyecto con referencias de proyecto a proyecto (P2P), se restaura el proyecto raíz y no las
referencias.
--no-launch-profile
No intenta usar launchSettings.json para configurar la aplicación.
--no-restore

No ejecuta una restauración implícita al ejecutar el comando.


-p|--project <PATH>

Especifica la ruta de acceso del archivo del proyecto que se va a ejecutar (nombre de la carpeta o ruta de
acceso completa). Si no se especifica, se toma como predeterminado el directorio actual.
-r|--runtime <RUNTIME_IDENTIFIER>

Especifica el tiempo de ejecución de destino para el que restaurar los paquetes. Para obtener una lista de
identificadores de tiempo de ejecución (RID), consulte el catálogo de RID. Opción corta -r disponible a
partir del SDK de .NET Core 3.0.
-v|--verbosity <LEVEL>

Establece el nivel de detalle del comando. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] ,
d[etailed] y diag[nostic] . El valor predeterminado es m . Disponible a partir del SDK de .NET Core 2.1.

Ejemplos
Ejecución del proyecto en el directorio actual:

dotnet run

Ejecución del proyecto especificado:

dotnet run --project ./projects/proj1/proj1.csproj

Ejecute el proyecto en el directorio actual (el argumento --help en este ejemplo se pasa a la aplicación,
dado que se usa la opción -- en blanco):

dotnet run --configuration Release -- --help

Restaure las dependencias y herramientas del proyecto en el directorio actual mostrando solo la salida
mínima y, después, ejecute el proyecto: (SDK de .NET Core 2.0 y versiones superiores):

dotnet run --verbosity m


dotnet sln
16/09/2020 • 5 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

NOMBRE
dotnet sln : enumera o modifica los proyectos en un archivo de solución de .NET Core.

Sinopsis
dotnet sln [<SOLUTION_FILE>] [command]

dotnet sln [command] -h|--help

Descripción
El comando dotnet sln proporciona una opción conveniente para enumerar y modificar los proyectos en un
archivo de solución.
Para usar el comando dotnet sln , debe existir el archivo de solución. Si necesita crear uno, use el comando
dotnet new, como en el ejemplo siguiente:

dotnet new sln

Argumentos
SOLUTION_FILE

El archivo de solución que se va a usar. Si se omite este argumento, el comando busca uno en el directorio
actual. Si encuentra varios archivos de solución o no encuentra ninguno, se produce un error en el
comando.

Opciones
-h|--help

Imprime una descripción de cómo usar el comando.

Comandos
list

Enumera todos los proyectos en un archivo de solución.


Sinopsis

dotnet sln list [-h|--help]

Argumentos
SOLUTION_FILE

El archivo de solución que se va a usar. Si se omite este argumento, el comando busca uno en el directorio
actual. Si encuentra varios archivos de solución o no encuentra ninguno, se produce un error en el
comando.
Opciones
-h|--help

Imprime una descripción de cómo usar el comando.


add

Agrega uno o varios proyectos al archivo de solución.


Sinopsis

dotnet sln [<SOLUTION_FILE>] add [--in-root] [-s|--solution-folder <PATH>] <PROJECT_PATH>


[<PROJECT_PATH>...]
dotnet sln add [-h|--help]

Argumentos
SOLUTION_FILE

El archivo de solución que se va a usar. Si no se especifica, el comando busca uno en el directorio actual y
produce un error si hay varios archivos de solución.
PROJECT_PATH

La ruta de acceso al proyecto o los proyectos que se van a agregar a la solución. Las expansiones del
patrón comodines de shell de Unix y Linux se procesan correctamente mediante el comando dotnet sln .
Opciones
-h|--help

Imprime una descripción de cómo usar el comando.


--in-root

Coloca el proyecto en la raíz de la solución, en lugar de crear una carpeta de la solución. Disponible desde
el SDK de .NET Core 3.0.
-s|--solution-folder <PATH>

La ruta de acceso de la carpeta de la solución de destino a la que se van a agregar los proyectos.
Disponible desde el SDK de .NET Core 3.0.
remove

Quita un proyecto o varios proyectos del archivo de solución.


Sinopsis

dotnet sln [<SOLUTION_FILE>] remove <PROJECT_PATH> [<PROJECT_PATH>...]


dotnet sln [<SOLUTION_FILE>] remove [-h|--help]

Argumentos
SOLUTION_FILE

El archivo de solución que se va a usar. Si se deja sin especificar, el comando busca uno en el directorio
actual y produce un error si hay varios archivos de solución.
PROJECT_PATH

La ruta de acceso al proyecto o los proyectos que se van a agregar a la solución. Las expansiones del
patrón comodines de shell de Unix y Linux se procesan correctamente mediante el comando dotnet sln .
Opciones
-h|--help

Imprime una descripción de cómo usar el comando.

Ejemplos
Enumere los proyectos en una solución:

dotnet sln todo.sln list

Agregue un proyecto de C# a una solución:

dotnet sln add todo-app/todo-app.csproj

Quite un proyecto de C# de una solución:

dotnet sln remove todo-app/todo-app.csproj

Agregue varios proyectos de C# a la raíz de una solución:

dotnet sln todo.sln add todo-app/todo-app.csproj back-end/back-end.csproj --in-root

Agregue varios proyectos de C# a una solución:

dotnet sln todo.sln add todo-app/todo-app.csproj back-end/back-end.csproj

Quite varios proyectos de C# de una solución:

dotnet sln todo.sln remove todo-app/todo-app.csproj back-end/back-end.csproj

Agregue varios proyectos de C# a una solución mediante un patrón de comodines (solo para Unix y
Linux):

dotnet sln todo.sln add **/*.csproj

Agregue varios proyectos de C# a una solución mediante un patrón de comodines (solo


Windows PowerShell):

dotnet sln todo.sln add (ls -r **/*.csproj)

Quite varios proyectos de C# de una solución mediante un patrón de comodines (solo para Unix y Linux):

dotnet sln todo.sln remove **/*.csproj


Quite varios proyectos de C# a una solución mediante un patrón de comodines (solo
Windows PowerShell):

dotnet sln todo.sln remove (ls -r **/*.csproj)


dotnet store
20/04/2020 • 3 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

Name
dotnet store : almacena los ensamblados especificados en el almacenamiento de paquetes en tiempo de
ejecución.

Sinopsis
dotnet store -m|--manifest <PATH_TO_MANIFEST_FILE>
-f|--framework <FRAMEWORK_VERSION> -r|--runtime <RUNTIME_IDENTIFIER>
[--framework-version <FRAMEWORK_VERSION>] [--output <OUTPUT_DIRECTORY>]
[--skip-optimization] [--skip-symbols] [-v|--verbosity <LEVEL>]
[--working-dir <WORKING_DIRECTORY>]

dotnet store -h|--help

Description
dotnet store almacena los ensamblados especificados en el almacenamiento de paquetes en tiempo de
ejecución. De forma predeterminada, los ensamblados están optimizados para el tiempo de ejecución y el marco
de trabajo de destino. Para obtener más información, consulte el tema Almacenamiento de paquetes en tiempo de
ejecución.

Opciones necesarias
-f|--framework <FRAMEWORK>

Especifica la plataforma de destino. La plataforma de destino tiene que especificarse en el archivo del
proyecto.
-m|--manifest <PATH_TO_MANIFEST_FILE>

El archivo de manifiesto de almacenamiento de paquetes es un archivo XML que contiene la lista de


paquetes que se va a almacenar. El formato del archivo de manifiesto es compatible con el formato de
proyecto de estilo de SDK. Por tanto, se puede usar un archivo de proyecto que haga referencia a los
paquetes deseados con la opción -m|--manifest para almacenar los ensamblados en el almacenamiento
de paquetes en tiempo de ejecución. Para especificar varios archivos de manifiesto, repita la opción y la
ruta de acceso para cada archivo. Por ejemplo: --manifest packages1.csproj --manifest packages2.csproj .
-r|--runtime <RUNTIME_IDENTIFIER>

El identificador en tiempo de ejecución de destino.

Opciones no necesarias
--framework-version <FRAMEWORK_VERSION>

Especifica la versión del SDK de .NET Core. Esta opción le permite seleccionar una versión de un marco
concreto más allá del marco de trabajo especificado en la opción -f|--framework .
-h|--help

Muestra información de ayuda.


-o|--output <OUTPUT_DIRECTORY>

Especifica la ruta de acceso al almacenamiento de paquetes en tiempo de ejecución. Si no se especifica, el


valor predeterminado es el subdirectorio store del directorio de instalación de .NET Core de perfil de
usuario.
--skip-optimization

Omite la fase de optimización.


--skip-symbols

Omite la generación de símbolos. Actualmente, solo se pueden generar símbolos en Windows y Linux.
-v|--verbosity <LEVEL>

Establece el nivel de detalle del comando. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] ,
d[etailed] y diag[nostic] .

-w|--working-dir <WORKING_DIRECTORY>

El directorio de trabajo que usa el comando. Si no se especifica, usa el subdirectorio obj del directorio
actual.

Ejemplos
Almacenamiento de los paquetes especificados en el archivo de proyecto packages.csproj para .NET Core
2.0.0:

dotnet store --manifest packages.csproj --framework-version 2.0.0

Almacenamiento de los paquetes especificados en packages.csproj sin optimización:

dotnet store --manifest packages.csproj --skip-optimization

Vea también
Runtime package store (Almacenamiento de paquetes en tiempo de ejecución)
dotnet test
16/09/2020 • 17 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores

NOMBRE
dotnet test : controlador de prueba de .NET usado para ejecutar pruebas unitarias.

Sinopsis
dotnet test [<PROJECT> | <SOLUTION> | <DIRECTORY> | <DLL>]
[-a|--test-adapter-path <ADAPTER_PATH>] [--blame] [--blame-crash]
[--blame-crash-dump-type <DUMP_TYPE>] [--blame-crash-collect-always]
[--blame-hang] [--blame-hang-dump-type <DUMP_TYPE>]
[--blame-hang-timeout <TIMESPAN>]
[-c|--configuration <CONFIGURATION>]
[--collect <DATA_COLLECTOR_NAME>]
[-d|--diag <LOG_FILE>] [-f|--framework <FRAMEWORK>]
[--filter <EXPRESSION>] [--interactive]
[-l|--logger <LOGGER>] [--no-build]
[--nologo] [--no-restore] [-o|--output <OUTPUT_DIRECTORY>]
[-r|--results-directory <RESULTS_DIR>] [--runtime <RUNTIME_IDENTIFIER>]
[-s|--settings <SETTINGS_FILE>] [-t|--list-tests]
[-v|--verbosity <LEVEL>] [[--] <RunSettings arguments>]

dotnet test -h|--help

Descripción
El comando dotnet test se usa para ejecutar pruebas unitarias en una solución determinada. El comando
dotnet test compila la solución y ejecuta una aplicación host de prueba para cada proyecto de prueba de la
solución. El host de prueba ejecuta las pruebas en el proyecto especificado mediante un marco de pruebas (por
ejemplo, MSTest, NUnit o xUnit) e informa del éxito o el error de cada prueba. Si todas las pruebas son
correctas, el ejecutor de pruebas devuelve 0 como un código de salida; en caso contrario, si se produce algún
error en una prueba, devuelve 1.
En el caso de los proyectos con varios destinos, las pruebas se ejecutan para cada plataforma de destino. El
host de pruebas y el marco de pruebas unitarias se empaquetan como paquetes NuGet y se restauran como
dependencias ordinarias para el proyecto.
Los proyectos de prueba especifican el ejecutor de pruebas usando un elemento <PackageReference> ordinario,
como se puede ver en este archivo de proyecto de ejemplo:
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
</ItemGroup>

</Project>

Donde Microsoft.NET.Test.Sdk es el host de prueba y xunit es el marco de pruebas. Y


xunit.runner.visualstudio es un adaptador de prueba, que permite que el marco xUnit funcione con el host de
prueba.
Restauración implícita
No es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que necesitan
que se produzca una restauración, como dotnet new , dotnet build , dotnet run , dotnet test ,
dotnet publish y dotnet pack . Para deshabilitar la restauración implícita, use la opción --no-restore .

El comando dotnet restore sigue siendo válido en algunos escenarios donde tiene sentido realizar una
restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los
sistemas de compilación que necesitan controlar explícitamente cuándo se produce la restauración.
Para obtener información sobre cómo administrar fuentes de NuGet, vea la documentación de dotnet restore .

Argumentos
PROJECT | SOLUTION | DIRECTORY | DLL

Ruta de acceso al proyecto de prueba.


Ruta de acceso a la solución.
Ruta de acceso a un directorio que contiene un proyecto o una solución.
Ruta de acceso a un archivo .dll del proyecto de prueba.
Si no se especifica, se busca un proyecto o una solución en el directorio actual.

Opciones
-a|--test-adapter-path <ADAPTER_PATH>

Ruta de acceso a un directorio en el que se van hacer búsquedas de adaptadores de prueba adicionales.
Solo se inspeccionan los archivos .dll con el sufijo .TestAdapter.dll . Si no se especifica, la búsqueda se
realiza en el directorio del archivo .dll de la prueba.
--blame

Ejecuta las pruebas en el modo de culpabilidad. Esta opción es útil para aislar las pruebas problemáticas
que hacen que el host de prueba se bloquee. Cuando se detecta un bloqueo, crea un archivo de
secuencia en TestResults/<Guid>/<Guid>_Sequence.xml que captura el orden de las pruebas ejecutadas
antes del bloqueo.
--blame-crash (Disponible desde el SDK de versión preliminar de .NET 5.0)
Ejecuta las pruebas en modo de culpa y recopila un volcado de memoria cuando el host de prueba se
cierra de forma inesperada. Esta opción solo se admite en Windows. Un directorio que contenga
procdump.exe y procdump64.exe debe estar en la variable de entorno PATH o PROCDUMP_PATH.
Descargue las herramientas. Implica --blame .
--blame-crash-dump-type <DUMP_TYPE> (Disponible desde el SDK de versión preliminar de .NET 5.0)
El tipo de volcado de memoria que se va a recopilar. Implica --blame-crash .
--blame-crash-collect-always (Disponible desde el SDK de versión preliminar de .NET 5.0)
Recopila un volcado de memoria en la salida de host de prueba esperada e inesperada.
--blame-hang (Disponible desde el SDK de versión preliminar de .NET 5.0)
Ejecute las pruebas en modo de culpa y recopile un volcado de bloqueo cuando una prueba supere el
tiempo de espera especificado.
--blame-hang-dump-type <DUMP_TYPE> (Disponible desde el SDK de versión preliminar de .NET 5.0)
El tipo de volcado de memoria que se va a recopilar. Debe ser full , mini o none . Cuando se especifica
none , el host de prueba finaliza en el tiempo de espera, pero no se recopila ningún volcado. Implica
--blame-hang .

--blame-hang-timeout <TIMESPAN> (Disponible desde el SDK de versión preliminar de .NET 5.0)


Tiempo de espera por prueba, después del cual se desencadena un volcado de bloqueo y finaliza el
proceso de host de prueba. El valor de tiempo de espera se especifica en uno de los siguientes formatos:
1,5 h
90 m
5400 s
5 400 000 ms
Cuando no se usa ninguna unidad (por ejemplo, 5 400 000), se supone que el valor está en
milisegundos. Cuando se usa junto con las pruebas basadas en datos, el comportamiento de tiempo de
espera depende del adaptador de prueba usado. En xUnit y NUnit, el tiempo de espera se renueva
después de cada caso de prueba. En MSTest, el tiempo de espera se usa en todos los casos de prueba.
Esta opción se admite en Windows con netcoreapp2.1 y versiones posteriores, y en Linux con
netcoreapp3.1 y versiones posteriores. macOS no se admite.
-c|--configuration <CONFIGURATION>

Define la configuración de compilación. El valor predeterminado es Debug , pero la configuración del


proyecto podría invalidar esta configuración predeterminada del SDK.
--collect <DATA_COLLECTOR_NAME>

Habilita el recopilador de datos para la ejecución de pruebas. Para obtener más información, consulte
Monitor and analyze test run (Supervisar y analizar ejecuciones de pruebas).
Para recopilar la cobertura de código en cualquier plataforma compatible con .NET Core, instale Coverlet
y use la opción --collect:"XPlat Code Coverage" .
En Windows, puede recopilar la cobertura de código mediante la opción --collect "Code Coverage" .
Esta opción genera un archivo .coverage, que se puede abrir en Visual Studio 2019 Enterprise. Para más
información, vea Uso de cobertura de código y Personalización del análisis de cobertura de código.
-d|--diag <LOG_FILE>

Habilita el modo de diagnóstico de la plataforma de prueba y escribe mensajes de diagnóstico en el


archivo especificado y en los archivos que hay junto a él. El proceso que registra los mensajes determina
qué archivos se crean, como *.host_<date>.txt para el registro del host de prueba y
*.datacollector_<date>.txt para el registro del recopilador de datos.

-f|--framework <FRAMEWORK>

Fuerza el uso del host de prueba de dotnet o .NET Framework para los archivos binarios de prueba.
Esta opción solo determina el tipo de host que se va a usar. La versión de Framework real que se va a
usar viene determinada por runtimeConfig.json del proyecto de prueba. Si no se especifica, se usa el
atributo de ensamblado TargetFramework para determinar el tipo de host. Si ese atributo se quita de .dll,
se usa el host de .NET Framework.
--filter <EXPRESSION>

Filtra las pruebas del proyecto actual con la expresión dada. Para más información, consulte la sección
Detalles de la opción de filtro. Para obtener más información y ejemplos sobre cómo usar el filtrado de
pruebas unitarias selectivas, vea Ejecución de pruebas unitarias selectivas.
-h|--help

Imprime una corta ayuda para el comando.


--interactive

Permite que el comando se detenga y espere una entrada o una acción del usuario. Por ejemplo, para
completar la autenticación. Disponible desde el SDK de .NET Core 3.0.
-l|--logger <LOGGER>

Especifica un registrador para los resultados de pruebas. A diferencia de MSBuild, la prueba de dotnet
no acepta abreviaturas: en lugar de -l "console;v=d" use -l "console;verbosity=detailed" .
--no-build

No compila el proyecto de prueba antes de ejecutarlo. También establece la marca - --no-restore de


forma implícita.
--nologo

Ejecuta pruebas sin mostrar la pancarta de Microsoft TestPlatform. Disponible desde el SDK de
.NET Core 3.0.
--no-restore

No ejecuta una restauración implícita al ejecutar el comando.


-o|--output <OUTPUT_DIRECTORY>

Directorio donde se encuentran los archivos binarios que se ejecutarán. Si no se especifica la ruta de
acceso predeterminada es ./bin/<configuration>/<framework>/ . En el caso de los proyectos con varias
plataformas de destino (a través de la propiedad TargetFrameworks ), también debe definir --framework
al especificar esta opción. dotnet test siempre ejecuta las pruebas desde el directorio de salida. Puede
usar AppDomain.BaseDirectory para consumir recursos de prueba en el directorio de salida.
-r|--results-directory <RESULTS_DIR>

El directorio donde se guardarán los resultados de pruebas. Si el directorio especificado no existe, se


crea. El valor predeterminado es TestResults en el directorio que contiene el archivo del proyecto.
--runtime <RUNTIME_IDENTIFIER>
Runtime de destino que probar.
-s|--settings <SETTINGS_FILE>

El archivo .runsettings que se usará para ejecutar las pruebas. El elemento TargetPlatform (x86|x64)
no tiene ningún efecto en dotnet test . Para ejecutar pruebas destinadas a x86, instale la versión x86 de
.NET Core. El valor de bits de dotnet.exe que se encuentra en la ruta de acceso es lo que se usará para
ejecutar las pruebas. Para obtener más información, vea los siguientes recursos:
Configuración de pruebas unitarias con un archivo .runsettings .
Configuración de una serie de pruebas
-t|--list-tests

Enumera las pruebas detectadas en vez de ejecutar las pruebas.


-v|--verbosity <LEVEL>

Establece el nivel de detalle del comando. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] ,
d[etailed] y diag[nostic] . De manera predeterminada, es minimal . Para obtener más información,
vea LoggerVerbosity.
Argumentos de RunSettings

Los argumentos RunSettings insertados se pasan como los últimos argumentos en la línea de comandos
después de "-- " (fíjese en el espacio después de --). Los argumentos RunSettings insertados se especifican
como pares de [name]=[value] . Se usa un espacio para separar varios pares de [name]=[value] .
Ejemplo: dotnet test -- MSTest.DeploymentEnabled=false MSTest.MapInconclusiveToFailed=True

Para obtener más información, vea Paso de argumentos de RunSettings a través de la línea de comandos.

Ejemplos
Ejecución de las pruebas en el proyecto en el directorio actual:

dotnet test

Ejecute las pruebas en el proyecto test1 :

dotnet test ~/projects/test1/test1.csproj

Ejecute las pruebas del proyecto en el directorio actual y genere un archivo de resultados de prueba en
formato trx:

dotnet test --logger trx

Ejecute las pruebas del proyecto en el directorio actual y genere un archivo de cobertura de código
(después de instalar la integración de recopiladores de Coverlet):

dotnet test --collect:"XPlat Code Coverage"

Ejecute las pruebas en el proyecto en el directorio actual y genere un archivo de cobertura de código
(solo en Windows):
dotnet test --collect "Code Coverage"

Ejecute las pruebas del proyecto en el directorio actual y regístrese con el nivel de detalle
pormenorizado en la consola:

dotnet test --logger "console;verbosity=detailed"

Ejecute las pruebas del proyecto en el directorio actual e informe de las pruebas que estaban en curso
cuando se bloqueó el host de prueba:

dotnet test --blame

Detalles de la opción de filtro


--filter <EXPRESSION>

<Expression> tiene el formato <property><operator><value>[|&<Expression>] .


<property> es un atributo del tipo Test Case . Las siguientes son las propiedades admitidas por los marcos de
pruebas unitarias populares:

M A RC O DE P RUEB A P RO P IEDA DES A DM IT IDA S

MSTest FullyQualifiedName
NOMBRE
ClassName
Prioridad
TestCategory

xUnit FullyQualifiedName
DisplayName
Rasgos

NUnit FullyQualifiedName
NOMBRE
TestCategory
Prioridad

<operator> describe la relación entre la propiedad y el valor:

O P ERA DO R F UN C IÓ N

= Coincidencia exacta

!= Coincidencia no exacta

~ Contiene

!~ No contiene
<value> es una cadena. Todas las búsquedas distinguen mayúsculas de minúsculas.
Una expresión sin <operator>automáticamente se considera un contains en la propiedad
FullyQualifiedName (por ejemplo, dotnet test --filter xyz es lo mismo que
dotnet test --filter FullyQualifiedName~xyz ).

Las expresiones se pueden combinar con operadores condicionales:

O P ERA DO R F UN C IÓ N

| O

& AND

Si usa operadores condicionales (por ejemplo, (Name~TestMethod1) | (Name~TestMethod2) ), puede incluir las
expresiones entre paréntesis.
Para obtener más información y ejemplos sobre cómo usar el filtrado de pruebas unitarias selectivas, vea
Ejecución de pruebas unitarias selectivas.

Vea también
Marcos y destinos
Catálogo de identificadores de entorno de ejecución (RID) de .NET Core
Paso de argumentos runsettings a través de la línea de comandos
dotnet tool install
16/09/2020 • 4 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores

NOMBRE
dotnet tool install : instala la herramienta de .NET Core especificada en el equipo.

Sinopsis
dotnet tool install <PACKAGE_NAME> -g|--global
[--add-source <SOURCE>] [--configfile <FILE>]
[--framework <FRAMEWORK>] [-v|--verbosity <LEVEL>]
[--version <VERSION_NUMBER>]

dotnet tool install <PACKAGE_NAME> --tool-path <PATH>


[--add-source <SOURCE>] [--configfile <FILE>]
[--framework <FRAMEWORK>] [-v|--verbosity <LEVEL>]
[--version <VERSION_NUMBER>]

dotnet tool install <PACKAGE_NAME>


[--add-source <SOURCE>] [--configfile <FILE>]
[--framework <FRAMEWORK>] [-v|--verbosity <LEVEL>]
[--version <VERSION_NUMBER>]

dotnet tool install -h|--help

Descripción
El comando dotnet tool install permite instalar en el equipo herramientas de .NET Core. Para usar el
comando, especifique una de las siguientes opciones de instalación:
Para instalar una herramienta global en la ubicación predeterminada, use la opción --global .
Para instalar una herramienta global en una ubicación personalizada, use la opción --tool-path .
Para instalar una herramienta local, omita las opciones --global y --tool-path .

Las herramientas locales están disponibles a par tir del SDK de .NET Core 3.0.
Las herramientas globales se instalan en los siguientes directorios de forma predeterminada cuando se
especifica la opción -g o --global :

SO RUTA DE A C C ESO

Linux/macOS $HOME/.dotnet/tools

Windows %USERPROFILE%\.dotnet\tools

Las herramientas locales se agregan a un archivo dotnet-tools.json en un directorio .config en el directorio


actual. Si aún no existe un archivo de manifiesto, créelo ejecutando el siguiente comando:
dotnet new tool-manifest

Para obtener más información, vea Instalación de una herramienta local.

Argumentos
PACKAGE_NAME

Nombre o identificador del paquete NuGet que contiene la herramienta de .NET Core que se quiere
instalar.

Opciones
add-source <SOURCE>

Agrega un origen de paquete NuGet adicional que se usará durante la instalación.


configfile <FILE>

El archivo de configuración de NuGet (nuget.config) que se usará.


framework <FRAMEWORK>

Especifica el marco de destino para instalar la herramienta. De forma predeterminada, el SDK de .NET
Core intenta elegir la plataforma de destino más apropiada.
-g|--global

Especifica que la instalación se realiza en todos los usuarios. No se puede combinar con la opción
--tool-path . Al omitir --global y --tool-path , se especifica la instalación de una herramienta local.

-h|--help

Imprime una corta ayuda para el comando.


tool-path <PATH>

Especifica la ubicación de donde se tiene que instalar la herramienta global. PATH puede ser una ruta
absoluta o relativa. Si la ruta no existe, el comando intenta crearla. Al omitir --global y --tool-path , se
especifica la instalación de una herramienta local.
-v|--verbosity <LEVEL>

Establece el nivel de detalle del comando. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] ,
d[etailed] y diag[nostic] .

--version <VERSION_NUMBER>

La versión de la herramienta que se va instalar. De forma predeterminada, se instala la versión estable


más reciente del paquete. Utilice esta opción para instalar la versión preliminar o versiones anteriores
de la herramienta.

Ejemplos
dotnet tool install -g dotnetsay

Instala dotnetsay como herramienta global en la ubicación predeterminada.


dotnet tool install dotnetsay --tool-path c:\global-tools
Instala dotnetsay como herramienta global en un directorio específico de Windows.
dotnet tool install dotnetsay --tool-path ~/bin

Instala dotnetsay como herramienta global en un directorio específico de Linux/macOS.


dotnet tool install -g dotnetsay --version 2.0.0

Instala la versión 2.0.0 de dotnetsay como la herramienta global.


dotnet tool install dotnetsay

Instala dotnetsay como herramienta local del directorio actual.

Vea también
Herramientas de .NET Core
Tutorial: Instalación y uso de una herramienta global de .NET Core mediante la CLI de .NET Core
Tutorial: Instalación y uso de una herramienta local de .NET Core mediante la CLI de .NET Core
dotnet tool list
16/09/2020 • 3 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores

NOMBRE
dotnet tool list : enumera todas las herramientas de .NET Core del tipo especificado actualmente instalado en
el equipo.

Sinopsis
dotnet tool list -g|--global

dotnet tool list --tool-path <PATH>

dotnet tool list --local

dotnet tool list

dotnet tool list -h|--help

Descripción
El comando dotnet tool list permite enumerar todas las herramientas locales, de ruta de acceso de
herramientas y globales de .NET Core instaladas en el equipo. El comando enumera el nombre del paquete, la
versión instalada y el comando de la herramienta. Para usar el comando, especifique una de las siguientes
opciones:
Para enumerar las herramientas globales instaladas en la ubicación predeterminada, use la opción --global .
Para enumerar las herramientas globales instaladas en la ubicación personalizada, use la opción --tool-path .
Para enumerar las herramientas locales, use la opción --local u omita las opciones --global , --tool-path y
--local .

Las herramientas locales están disponibles a par tir del SDK de .NET Core 3.0.

Opciones
-g|--global

Enumera las herramientas globales de los usuarios. No se puede combinar con la opción --tool-path . Al
omitir --global y --tool-path , se muestran las herramientas locales.
-h|--help

Imprime una corta ayuda para el comando.


--local

Enumera las herramientas locales del directorio actual. No se puede combinar con las opciones --global
o --tool-path . Al omitir --global y --tool-path , se enumeran las herramientas locales, aunque no se
haya especificado --local .
--tool-path <PATH>

Especifica una ubicación personalizada para las herramientas globales. PATH puede ser una ruta absoluta o
relativa. No se puede combinar con la opción --global . Al omitir --global y --tool-path , se muestran
las herramientas locales.

Ejemplos
dotnet tool list -g

Enumera todas las herramientas globales instaladas para todos los usuarios en su equipo (perfil de
usuario actual).
dotnet tool list --tool-path c:\global-tools

Enumera las herramientas globales de un directorio específico de Windows.


dotnet tool list --tool-path ~/bin

Enumera las herramientas globales de un directorio específico de Linux/macOS.


dotnet tool list o dotnet tool list --local

Enumera todas las herramientas locales disponibles en el directorio actual.

Vea también
Herramientas de .NET Core
Tutorial: Instalación y uso de una herramienta global de .NET Core mediante la CLI de .NET Core
Tutorial: Instalación y uso de una herramienta local de .NET Core mediante la CLI de .NET Core
dotnet tool restore
16/09/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.0 y versiones posteriores

NOMBRE
dotnet tool restore : instala en el equipo las herramientas locales de .NET Core que se encuentran en el ámbito del
directorio actual.

Sinopsis
dotnet tool restore
[--configfile <FILE>] [--add-source <SOURCE>]
[tool-manifest <PATH_TO_MANIFEST_FILE>] [--disable-parallel]
[--ignore-failed-sources] [--no-cache] [--interactive]
[-v|--verbosity <LEVEL>]

dotnet tool restore -h|--help

Descripción
El comando dotnet tool restore busca el archivo de manifiesto de las herramientas que está en el ámbito del
directorio actual e instala las herramientas que se indican en él. Para obtener información sobre los archivos de
manifiesto, vea Instalación de una herramienta local e Invocación de una herramienta local.

Opciones
--configfile <FILE>

El archivo de configuración de NuGet (nuget.config) que se usará.


--add-source <SOURCE>

Agrega un origen de paquete NuGet adicional que se usará durante la instalación.


--tool-manifest <PATH>

Ruta de acceso al archivo de manifiesto.


--disable-parallel

Impide que se restauren varios proyectos en paralelo.


--ignore-failed-sources

Indica que los errores de origen de paquete se traten como advertencias.


--no-cache

Indica que no se almacenen en caché los paquetes ni las solicitudes HTTP.


--interactive

Permite que el comando se detenga y espere la entrada o acción del usuario (por ejemplo, completar la
autenticación).
-h|--help

Imprime una corta ayuda para el comando.


-v|--verbosity <LEVEL>

Establece el nivel de detalle del comando. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] ,
d[etailed] y diag[nostic] .

Ejemplo
dotnet tool restore

Restaura las herramientas locales del directorio actual.

Vea también
Herramientas de .NET Core
Tutorial: Instalación y uso de una herramienta local de .NET Core mediante la CLI de .NET Core
dotnet tool run
20/04/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.0 y versiones posteriores

NOMBRE
dotnet tool run : invoca una herramienta local.

Sinopsis
dotnet tool run <COMMAND NAME>

dotnet tool run -h|--help

Descripción
El comando dotnet tool run busca los archivos de manifiesto de las herramientas que se encuentran en el ámbito
del directorio actual. Cuando encuentra una referencia a la herramienta especificada, ejecuta la herramienta. Para
obtener más información, vea Invocación de una herramienta local.

Argumentos
COMMAND_NAME

El nombre del comando de la herramienta que se va a ejecutar.

Opciones
-h|--help

Imprime una corta ayuda para el comando.

Ejemplo
dotnet tool run dotnetsay

Ejecuta la herramienta local dotnetsay .

Vea también
Herramientas de .NET Core
Tutorial: Instalación y uso de una herramienta local de .NET Core mediante la CLI de .NET Core
dotnet tool uninstall
20/04/2020 • 3 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores

NOMBRE
dotnet tool uninstall : desinstala la herramienta de .NET Core especificada del equipo.

Sinopsis
dotnet tool uninstall <PACKAGE_NAME> -g|--global

dotnet tool uninstall <PACKAGE_NAME> --tool-path <PATH>

dotnet tool uninstall <PACKAGE_NAME>

dotnet tool uninstall -h|--help

Descripción
El comando dotnet tool uninstall permite desinstalar del equipo herramientas de .NET Core. Para usar el
comando, especifique una de las siguientes opciones:
Para desinstalar una herramienta global que se instaló en la ubicación predeterminada, use la opción
--global .
Para desinstalar una herramienta global que se instaló en una ubicación personalizada, use la opción
--tool-path .
Para desinstalar una herramienta local, omita las opciones --global y --tool-path .
Las herramientas locales están disponibles a par tir del SDK de .NET Core 3.0.

Argumentos
PACKAGE_NAME

Nombre o identificador del paquete NuGet que contiene la herramienta de .NET Core que se quiere
desinstalar. Para conocer el nombre el paquete, use el comando dotnet tool list.

Opciones
-g|--global

Especifica que la herramienta que se va a quitar es de una instalación en el ámbito de los usuarios. No se
puede combinar con la opción --tool-path . Al omitir --global y --tool-path , se especifica que la
herramienta que se va a quitar es una herramienta local.
-h|--help

Imprime una corta ayuda para el comando.


--tool-path <PATH>
Especifica la ubicación de donde se tiene que desinstalar la herramienta. PATH puede ser una ruta absoluta
o relativa. No se puede combinar con la opción --global . Al omitir --global y --tool-path , se especifica
que la herramienta que se va a quitar es una herramienta local.

Ejemplos
dotnet tool uninstall -g dotnetsay

Desinstala la herramienta global dotnetsay.


dotnet tool uninstall dotnetsay --tool-path c:\global-tools

Desinstala la herramienta global dotnetsay de un directorio específico de Windows.


dotnet tool uninstall dotnetsay --tool-path ~/bin

Desinstala la herramienta global dotnetsay de un directorio específico de Linux/macOS.


dotnet tool uninstall dotnetsay

Desinstala la herramienta local dotnetsay del directorio actual.

Vea también
Herramientas de .NET Core
Tutorial: Instalación y uso de una herramienta global de .NET Core mediante la CLI de .NET Core
Tutorial: Instalación y uso de una herramienta local de .NET Core mediante la CLI de .NET Core
dotnet tool update
16/09/2020 • 5 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores

NOMBRE
dotnet tool update : actualiza la herramienta de .NET Core especificada en el equipo.

Sinopsis
dotnet tool update <PACKAGE_ID> -g|--global
[--add-source <SOURCE>] [--configfile <FILE>]
[--disable-parallel] [--framework <FRAMEWORK>]
[--ignore-failed-sources] [--interactive] [--no-cache]
[-v|--verbosity <LEVEL>] [--version <VERSION>]

dotnet tool update <PACKAGE_ID> --tool-path <PATH>


[--add-source <SOURCE>] [--configfile <FILE>]
[--disable-parallel] [--framework <FRAMEWORK>]
[--ignore-failed-sources] [--interactive] [--no-cache]
[-v|--verbosity <LEVEL>] [--version <VERSION>]

dotnet tool update <PACKAGE_ID> --local


[--add-source <SOURCE>] [--configfile <FILE>]
[--disable-parallel] [--framework <FRAMEWORK>]
[--ignore-failed-sources] [--interactive] [--no-cache]
[--tool-manifest <PATH>]
[-v|--verbosity <LEVEL>] [--version <VERSION>]

dotnet tool update -h|--help

Descripción
El comando dotnet tool update permite actualizar las herramientas de .NET Core en su equipo a la versión
estable más reciente del paquete. El comando desinstala y vuelve a instalar una herramienta, actualizándola de
facto. Para usar el comando, especifique una de las siguientes opciones:
Para actualizar una herramienta global que se instaló en la ubicación predeterminada, use la opción --global .
Para actualizar una herramienta global que se instaló en una ubicación personalizada, use la opción
--tool-path .
Para actualizar una herramienta local, use la opción --local .
Las herramientas locales están disponibles a par tir del SDK de .NET Core 3.0.

Argumentos
PACKAGE_ID

Nombre o identificador del paquete de NuGet que contiene la herramienta global de .NET Core que se
quiere actualizar. Para conocer el nombre el paquete, use el comando dotnet tool list.

Opciones
--add-source <SOURCE>

Agrega un origen de paquete NuGet adicional que se usará durante la instalación.


--configfile <FILE>

El archivo de configuración de NuGet (nuget.config) que se usará.


--disable-parallel

Impide que se restauren varios proyectos en paralelo.


--framework <FRAMEWORK>

Especifica la plataforma de destino para la que se actualiza la herramienta.


--ignore-failed-sources

Indica que los errores de origen de paquete se traten como advertencias.


--interactive

Permite que el comando se detenga y espere la entrada o acción del usuario (por ejemplo, completar la
autenticación).
--local

Actualice la herramienta y el manifiesto de la herramienta local. No se puede combinar con las opciones
--global o --tool-path .

--no-cache

Indica que no se almacenen en caché los paquetes ni las solicitudes HTTP.


--tool-manifest <PATH>

Ruta de acceso al archivo de manifiesto.


--tool-path <PATH>

Especifica la ubicación en la que está instalada la herramienta global. PATH puede ser una ruta absoluta o
relativa. No se puede combinar con la opción --global . Al omitir --global y --tool-path , se especifica
que la herramienta que se va a actualizar es una herramienta local.
--version <VERSION>

El intervalo de versiones del paquete de herramientas al que se actualiza. Esto no se puede usar para
degradar versiones, primero debe uninstall versiones más recientes.
-g|--global

Especifica que la actualización es para una herramienta del ámbito de los usuarios. No se puede combinar
con la opción --tool-path . Al omitir --global y --tool-path , se especifica que la herramienta que se va a
actualizar es una herramienta local.
-h|--help

Imprime una corta ayuda para el comando.


-v|--verbosity <LEVEL>

Establece el nivel de detalle del comando. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] ,
d[etailed] y diag[nostic] .
Ejemplos
dotnet tool update -g dotnetsay

Actualiza la herramienta global dotnetsay.


dotnet tool update dotnetsay --tool-path c:\global-tools

Actualiza la herramienta global dotnetsay ubicada en un directorio específico de Windows.


dotnet tool update dotnetsay --tool-path ~/bin

Actualiza la herramienta global dotnetsay ubicada en un directorio específico de Linux/macOS.


dotnet tool update dotnetsay

Actualiza la herramienta local dotnetsay instalada para el directorio actual.


dotnet tool update -g dotnetsay --version 2.0.*

Actualiza la herramienta global dotnetsay a la última versión de revisión, con una versión principal de 2 y
una versión secundaria de 0 .
dotnet tool update -g dotnetsay --version (2.0.*,2.1.4)

Actualiza la herramienta global dotnetsay a la versión más baja del intervalo especificado
(> 2.0.0 && < 2.1.4) ; se instalará la versión 2.1.0 . Para obtener más información sobre los intervalos de
versiones semánticas, consulte Intervalos de versiones de empaquetado de NuGet.

Vea también
Herramientas de .NET Core
Versionamiento semántico
Tutorial: Instalación y uso de una herramienta global de .NET Core mediante la CLI de .NET Core
Tutorial: Instalación y uso de una herramienta local de .NET Core mediante la CLI de .NET Core
dotnet vstest
12/05/2020 • 6 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores

IMPORTANT
El comando dotnet vstest se sustituye por dotnet test , que ahora se puede usar para ejecutar ensamblados. Vea
dotnet test .

NOMBRE
dotnet-vstest : permite ejecutar pruebas a partir de los ensamblados especificados.

Sinopsis
dotnet vstest [<TEST_FILE_NAMES>] [--Blame] [--Diag <PATH_TO_LOG_FILE>]
[--Framework <FRAMEWORK>] [--InIsolation] [-lt|--ListTests <FILE_NAME>]
[--logger <LOGGER_URI/FRIENDLY_NAME>] [--Parallel]
[--ParentProcessId <PROCESS_ID>] [--Platform] <PLATFORM_TYPE>
[--Port <PORT>] [--ResultsDirectory<PATH>] [--Settings <SETTINGS_FILE>]
[--TestAdapterPath <PATH>] [--TestCaseFilter <EXPRESSION>]
[--Tests <TEST_NAMES>] [[--] <args>...]]

dotnet vstest -?|--Help

Descripción
El comando dotnet-vstest ejecuta la aplicación de línea de comandos VSTest.Console para ejecutar pruebas
unitarias automatizadas.

Argumentos
TEST_FILE_NAMES

Ejecutar pruebas desde los ensamblados especificados. Separar varios nombres de ensamblado de prueba
con espacios. Se admite caracteres comodín.

Opciones
--Blame

Ejecuta las pruebas en el modo de culpabilidad. Esta opción es útil para aislar las pruebas problemáticas que
hacen que el host de prueba se bloquee. Crea un archivo de salida en el directorio actual como
Sequence.xml que captura el orden de ejecución de pruebas antes del bloqueo.
--Diag <PATH_TO_LOG_FILE>

Permite registros detallados para la plataforma de prueba. Los registros se escriben en el archivo
proporcionado.
--Framework <FRAMEWORK>

Identificar la versión de .NET Framework usada en la ejecución de pruebas. Ejemplos de valores válidos son
.NETFramework,Version=v4.6 o .NETCoreApp,Version=v1.0 . Otros valores admitidos son Framework40 ,
Framework45 , FrameworkCore10 y FrameworkUap10 .

--InIsolation

Ejecuta las pruebas en un proceso aislado. De este modo, es menos probable que el proceso
vstest.console.exe se detenga por un error de las pruebas, pero es posible que las pruebas se ejecuten más
despacio.
-lt|--ListTests <FILE_NAME>

Muestra todas las pruebas detectadas del contenedor de pruebas especificado.


--logger <LOGGER_URI/FRIENDLY_NAME>

Especifica un registrador para resultados de pruebas.


Para publicar resultados de pruebas en Team Foundation Server, use el proveedor de registrador
TfsPublisher :

/logger:TfsPublisher;
Collection=<team project collection url>;
BuildName=<build name>;
TeamProject=<team project name>
[;Platform=<Defaults to "Any CPU">]
[;Flavor=<Defaults to "Debug">]
[;RunTitle=<title>]

Para registrar los resultados en un archivo de resultados de pruebas (TRX) de Visual Studio, use el
proveedor de registrador trx . Este modificador, crea un archivo en el directorio de resultados de
pruebas con un nombre de archivo de registro dado. Si no se proporciona LogFileName , se crea un
nombre de archivo único para contener los resultados de las pruebas.

/logger:trx [;LogFileName=<Defaults to unique file name>]

--Parallel

Ejecuta pruebas en paralelo. De forma predeterminada, todos los núcleos disponibles en el equipo están
disponibles para su uso. Especifique un número explícito de núcleos mediante la configuración de la
propiedad MaxCpuCount en el nodo RunConfiguration del archivo runsettings.
--ParentProcessId <PROCESS_ID>

Id. de proceso del proceso principal responsable de iniciar el proceso actual.


--Platform <PLATFORM_TYPE>

Identificar la arquitectura de la plataforma usada en la ejecución de pruebas. Valores válidos son x86 , x64
y ARM .
--Port <PORT>

Especifica el puerto para la conexión de socket y la recepción de mensajes de eventos.


--ResultsDirectory:<PATH>
Si no existe, el directorio de los resultados de la prueba se creará en la ruta de acceso especificada.
--Settings <SETTINGS_FILE>

Configuración que se usará al ejecutar las pruebas.


--TestAdapterPath <PATH>

Usar adaptadores de prueba personalizados desde una ruta de acceso especificada (si existe) en la serie de
pruebas.
--TestCaseFilter <EXPRESSION>

Ejecuta pruebas que coinciden con la expresión dada. <EXPRESSION> tiene el formato
<property>Operator<value>[|&<EXPRESSION>] , donde Operator es = , != o ~ . El operador ~ tiene
semántica "contains" y se aplica a las propiedades de cadena como DisplayName . Los paréntesis () se usan
para agrupar subexpresiones. Para obtener más información, vea Filtro TestCase.
--Tests <TEST_NAMES>

Ejecuta las pruebas con nombres que coinciden con los Separar varios valores con comas.
-?|--Help

Imprime una corta ayuda para el comando.


@<file>

Lee el archivo de respuesta para ver más opciones.


args

Especifica argumentos adicionales para pasar al adaptador. Los argumentos se especifican como pares de
nombre-valor en el formato <n>=<v> , donde <n> es el nombre del argumento y <v> es el valor del
argumento. Use un espacio para separar varios argumentos.

Ejemplos
Ejecución de pruebas en mytestproject.dll:

dotnet vstest mytestproject.dll

Ejecución de pruebas en mytestproject.dll, exportación a una carpeta personalizada con nombre personalizado:

dotnet vstest mytestproject.dll --logger:"trx;LogFileName=custom_file_name.trx" --


ResultsDirectory:custom/file/path

Ejecución de pruebas en mytestproject.dll y myothertestproject.exe:

dotnet vstest mytestproject.dll myothertestproject.exe

Ejecutar pruebas TestMethod1 :

dotnet vstest /Tests:TestMethod1

Ejecutar pruebas TestMethod1 y TestMethod2 :


dotnet vstest /Tests:TestMethod1,TestMethod2

Vea también
Opciones de la línea de comandos para VSTest.Console.exe
referencia de scripts de dotnet-install
16/09/2020 • 11 minutes to read • Edit Online

NOMBRE
dotnet-install.ps1 | dotnet-install.sh : script usado para instalar el SDK de .NET Core y el entorno de
ejecución compartido.

Sinopsis
Windows:

dotnet-install.ps1 [-Architecture <ARCHITECTURE>] [-AzureFeed]


[-Channel <CHANNEL>] [-DryRun] [-FeedCredential]
[-InstallDir <DIRECTORY>] [-JSonFile <JSONFILE>]
[-NoCdn] [-NoPath] [-ProxyAddress] [-ProxyBypassList <LIST_OF_URLS>]
[-ProxyUseDefaultCredentials] [-Runtime <RUNTIME>]
[-SkipNonVersionedFiles] [-UncachedFeed] [-Verbose]
[-Version <VERSION>]

Get-Help ./dotnet-install.ps1

Linux/macOS:

dotnet-install.sh [--architecture <ARCHITECTURE>] [--azure-feed]


[--channel <CHANNEL>] [--dry-run] [--feed-credential]
[--install-dir <DIRECTORY>] [--jsonfile <JSONFILE>]
[--no-cdn] [--no-path] [--runtime <RUNTIME>] [--runtime-id <RID>]
[--skip-non-versioned-files] [--uncached-feed] [--verbose]
[--version <VERSION>]

dotnet-install.sh --help

El script de bash también lee modificadores de PowerShell, por lo que puede usar modificadores de
PowerShell con el script en sistemas Linux y macOS.

Descripción
Los scripts dotnet-install realizan una instalación sin derechos administrativos del SDK de .NET Core,
que incluye la CLI de .NET Core y el entorno de tiempo de ejecución compartido. Hay dos scripts:
un script de PowerShell que funciona en Windows;
un script de Bash que funciona en Linux y macOS.
Propósito
El uso previsto de los scripts es en escenarios de integración continua (CI), donde:
el SDK debe instalarse sin interacción del usuario y sin derechos de administrador;
no es necesario que la instalación del SDK se mantenga en varias ejecuciones de CI.
Esta es la secuencia típica de eventos:
Se desencadena la CI.
CI instala el SDK mediante uno de estos scripts.
CI finaliza su trabajo y borra los datos temporales, incluida la instalación del SDK.
Para configurar un entorno de desarrollo o ejecutar aplicaciones, use los instaladores en lugar de estos
scripts.
Versión recomendada
Se recomienda usar la versión estable de los scripts:
Bash (Linux/macOS): https://dot.net/v1/dotnet-install.sh
PowerShell (Windows): https://dot.net/v1/dotnet-install.ps1
Comportamiento de los scripts
Ambos scripts tienen el mismo comportamiento. Descargan el archivo ZIP o tarball desde las entregas
de compilación de la CLI y proceden a instalarlo en la ubicación predeterminada o en una ubicación
especificada por -InstallDir|--install-dir .
De forma predeterminada, los scripts de instalación descargan el SDK y lo instalan. Si desea obtener
solo el tiempo de ejecución compartido, especifique el argumento -Runtime|--runtime .
De forma predeterminada, el script agrega la ubicación de instalación a $PATH para la sesión actual.
Para invalidar este comportamiento, especifique el argumento -NoPath|--no-path . El script no establece
la variable de entorno DOTNET_ROOT .
Antes de ejecutar el script, instale las dependencias necesarias.
Puede instalar una versión específica mediante el argumento -Version|--version . La versión debe
especificarse como un número de versión de tres partes, por ejemplo, 2.1.0 . Si no se especifica la
versión, el script instala la versión latest .
Los scripts de instalación no actualizan el Registro en Windows. Solo descargan los archivos binarios
comprimidos y los copian en una carpeta. Si desea que se actualicen los valores de las claves del
Registro, use los instaladores de .NET Core.

Opciones
-Architecture|--architecture <ARCHITECTURE>

Arquitectura de los archivos binarios de .NET Core para instalar. Los valores posibles son <auto>
, amd64 , x64 , x86 , arm64 y arm . El valor predeterminado es <auto> , que representa la
arquitectura de SO que se ejecuta en ese momento.
-AzureFeed|--azure-feed

Especifica la dirección URL de la fuente de Azure al instalador. Le recomendamos que no cambie


este valor. El valor predeterminado es https://dotnetcli.azureedge.net/dotnet .
-Channel|--channel <CHANNEL>

Especifica el canal de origen para la instalación. Los valores posibles son:


Current : versión más actual.
LTS : canal de soporte técnico a largo plazo (versión compatible más actual).
Versión de dos partes en formato X.Y que representa una versión específica (por ejemplo,
2.1 o 3.0 ).
Nombre de rama: por ejemplo, release/3.1.1xx o master (para versiones nocturnas). Use
esta opción para instalar una versión de un canal de versión preliminar. Use el nombre del
canal como aparece en Instaladores y binarios.
El valor predeterminado es LTS . Para más información sobre los canales de soporte técnico de
.NET, vea la página .NET Support Policy (Directiva de soporte técnico de .NET Core).
-DryRun|--dry-run

Si se establece, el script no realizará la instalación. En su lugar, mostrará qué línea de comandos


se va a usar para instalar de manera coherente la versión solicitada actualmente de la CLI de
.NET Core. Por ejemplo, si especifica la versión latest , se muestra un vínculo con la versión
específica, de manera que este comando puede usarse de manera determinista en un script de
compilación. También se muestra la ubicación de los archivos binarios si prefiere instalarla o
descargarla por su cuenta.
-FeedCredential|--feed-credential

Se utiliza como una cadena de consulta para anexar a la fuente de Azure. Permite cambiar la
dirección URL para usar cuentas de almacenamiento de blobs no público.
--help

Imprime la ayuda para el script. Solo se aplica al script de Bash. Para PowerShell, use
Get-Help ./dotnet-install.ps1 .

-InstallDir|--install-dir <DIRECTORY>

Especifica la ruta de instalación. Si no existe el directorio, se crea. El valor predeterminado es


%LocalAppData%\Microsoft\dotnet en Windows y /usr/share/dotnet en Linux/macOS. Los
archivos binarios se colocan directamente en el directorio.
-JSonFile|--jsonfile <JSONFILE>

Especifica una ruta de acceso a un archivo global.json que se va a usar para determinar la
versión del SDK. El archivo global.json debe tener un valor para sdk:version .
-NoCdn|--no-cdn

Deshabilita la descarga desde Azure Content Delivery Network (CDN) y usa la fuente no
almacenada en caché directamente.
-NoPath|--no-path

Si se establece, la carpeta de instalación no se exporta a la ruta de acceso para la sesión actual.


De manera predeterminada, el script modifica la variable de entorno PATH, que hace que la CLI
esté disponible inmediatamente después de la instalación.
-ProxyAddress

Si se establece, el instalador usa el proxy al realizar solicitudes web. (Solo es válido para
Windows).
-ProxyBypassList <LIST_OF_URLS>

Si se establece con ProxyAddress , proporciona una lista de direcciones URL separadas por
comas que omiten el proxy. (Solo es válido para Windows).
ProxyUseDefaultCredentials

Si se establece, el instalador usa las credenciales del usuario actual cuando se usa la dirección del
proxy. (Solo es válido para Windows).
-Runtime|--runtime <RUNTIME>

Se instala simplemente el entorno de tiempo de ejecución compartido, no el SDK completo. Los


valores posibles son:
dotnet : el entorno de tiempo de ejecución compartido Microsoft.NETCore.App .
aspnetcore : el entorno de tiempo de ejecución compartido Microsoft.AspNetCore.App .
windowsdesktop : el entorno de tiempo de ejecución compartido
Microsoft.WindowsDesktop.App .

--runtime-id <RID>

Especifica el identificador de entorno de ejecución para el que se van a instalar las herramientas.
Use linux-x64 para Linux portátil. (Solo es válido para Linux/macOS).
-SharedRuntime|--shared-runtime

NOTE
Este parámetro está obsoleto y puede quitarse en una versión futura del script. La alternativa
recomendada es la opción -Runtime|--runtime .

Se instalan simplemente los bits del entorno de tiempo de ejecución compartido, no el SDK
completo. Esta opción equivale a especificar -Runtime|--runtime dotnet .
-SkipNonVersionedFiles|--skip-non-versioned-files

Omite la instalación de archivos sin control de versiones, como dotnet.exe, si ya existen.


-UncachedFeed|--uncached-feed

Permite cambiar la dirección URL de la fuente no almacenada en caché que este instalador
utiliza. Le recomendamos que no cambie este valor.
-Verbose|--verbose

Muestra la información de diagnóstico.


-Version|--version <VERSION>

Representa una versión de compilación concreta. Los valores posibles son:


latest : compilación más reciente en el canal (utilizado con la opción -Channel ).
coherent : compilación coherente más reciente en el canal; usa la última combinación de
paquetes estables (usados con las opciones -Channel del nombre de la rama).
Versión de tres partes en formato X.Y.Z que representa una determinada versión de
compilación; reemplaza a la opción -Channel . Por ejemplo: 2.0.0-preview2-006120 .
Si no se especifica, el valor predeterminado de -Version es latest .

Ejemplos
Instale la versión compatible a largo plazo más reciente en la ubicación predeterminada:
Windows:

./dotnet-install.ps1 -Channel LTS


macOS y Linux:

./dotnet-install.sh --channel LTS

Instale la versión más reciente del canal 3.1 en la ubicación especificada:


Windows:

./dotnet-install.ps1 -Channel 3.1 -InstallDir C:\cli

macOS y Linux:

./dotnet-install.sh --channel 3.1 --install-dir ~/cli

Instale la versión 3.0.0 del entorno de ejecución compartido:


Windows:

./dotnet-install.ps1 -Runtime dotnet -Version 3.0.0

macOS y Linux:

./dotnet-install.sh --runtime dotnet --version 3.0.0

Obtenga el script e instale la versión 2.1.2 detrás de un proxy corporativo (solo Windows):

Invoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1' -Proxy $env:HTTP_PROXY -


ProxyUseDefaultCredentials -OutFile 'dotnet-install.ps1';
./dotnet-install.ps1 -InstallDir '~/.dotnet' -Version '2.1.2' -ProxyAddress $env:HTTP_PROXY
-ProxyUseDefaultCredentials;

Obtenga el script e instale ejemplos de una línea para la CLI de .NET Core:
Windows:

# Run a separate PowerShell process because the script calls exit, so it will end the
current PowerShell session.
&powershell -NoProfile -ExecutionPolicy unrestricted -Command "
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &
([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-
install.ps1'))) <additional install-script args>"

macOS y Linux:

curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin <additional install-script


args>

Vea también
Versiones de .NET Core
Archivo de descarga del SDK y .NET Core Runtime
dotnet add reference
04/05/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

NOMBRE
dotnet add reference Agrega referencias entre proyectos (P2P) .

Sinopsis
dotnet add [<PROJECT>] reference [-f|--framework <FRAMEWORK>]
[--interactive] <PROJECT_REFERENCES>

dotnet add reference -h|--help

Descripción
El comando dotnet add reference constituye una opción práctica para agregar referencias de proyecto a un
proyecto. Después de ejecutar el comando, los elementos <ProjectReference> se agregan al archivo del
proyecto.

<ItemGroup>
<ProjectReference Include="app.csproj" />
<ProjectReference Include="..\lib2\lib2.csproj" />
<ProjectReference Include="..\lib1\lib1.csproj" />
</ItemGroup>

Argumentos
PROJECT

Especifica el archivo del proyecto. Si no se especifica, el comando busca uno en el directorio actual.
PROJECT_REFERENCES

Referencias entre proyectos (P2P) que se van a agregar. Especifique uno o más proyectos. El patrón glob
se admite en sistemas basados en Unix/Linux.

Opciones
-f|--framework <FRAMEWORK>

Agrega referencias de proyecto solo cuando apunta a un marco específico con el formato TFM.
-h|--help

Imprime una corta ayuda para el comando.


--interactive

Permite que el comando se detenga y espere la entrada o acción del usuario (se suele utilizar para
completar la autenticación). Disponible desde el SDK de .NET Core 3.0.

Ejemplos
Agregar una referencia de proyecto:

dotnet add app/app.csproj reference lib/lib.csproj

Agregar varias referencias de proyecto al proyecto en el directorio actual:

dotnet add reference lib1/lib1.csproj lib2/lib2.csproj

Agregar varias referencias de proyecto usando el patrón global en Linux/Unix:

dotnet add app/app.csproj reference **/*.csproj


dotnet list reference
16/09/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

NOMBRE
dotnet list reference : enumera las referencias entre proyectos.

Sinopsis
dotnet list [<PROJECT>] reference

dotnet list -h|--help

Descripción
El comando dotnet list reference constituye una opción práctica para enumerar las referencias de proyecto de
un proyecto concreto.

Argumentos
PROJECT

El archivo del proyecto sobre el que actuar. Si no se especifica un archivo, el comando buscará uno en el
directorio actual.

Opciones
-h|--help

Imprime una corta ayuda para el comando.

Ejemplos
Enumerar las referencias del proyecto para el proyecto especificado:

dotnet list app/app.csproj reference

Enumerar las referencias del proyecto para el proyecto en el directorio actual:

dotnet list reference


dotnet remove reference
04/05/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

NOMBRE
dotnet remove reference : quita las referencias entre proyectos (P2P).

Sinopsis
dotnet remove [<PROJECT>] reference [-f|--framework <FRAMEWORK>]
<PROJECT_REFERENCES>

dotnet remove reference -h|--help

Descripción
El comando dotnet remove reference constituye una opción práctica para quitar referencias de proyecto de un
proyecto.

Argumentos
PROJECT

Archivo de proyecto de destino. Si no se especifica, el comando busca uno en el directorio actual.


PROJECT_REFERENCES

Referencias de proyecto a proyecto (P2P) que se van a quitar. Puede especificar uno o varios proyectos. Se admiten
patrones globales en terminales basados en Unix o Linux.

Opciones
-h|--help

Imprime una corta ayuda para el comando.


-f|--framework <FRAMEWORK>

Solo quita la referencia cuando el destino es un marco de trabajo específico con el formato TFM.

Ejemplos
Quitar una referencia de proyecto del proyecto especificado:

dotnet remove app/app.csproj reference lib/lib.csproj

Quitar varias referencias de proyecto del proyecto en el directorio actual:


dotnet remove reference lib1/lib1.csproj lib2/lib2.csproj

Quitar varias referencias de proyecto con un patrón global en Unix/Linux:

dotnet remove app/app.csproj reference **/*.csproj`


dotnet add package
16/09/2020 • 4 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

NOMBRE
dotnet add package : agrega una referencia de paquete a un archivo del proyecto.

Sinopsis
dotnet add [<PROJECT>] package <PACKAGE_NAME>
[-f|--framework <FRAMEWORK>] [--interactive]
[-n|--no-restore] [--package-directory <PACKAGE_DIRECTORY>]
[-s|--source <SOURCE>] [-v|--version <VERSION>]

dotnet add package -h|--help

Descripción
El comando dotnet add package constituye una opción práctica para agregar una referencia de paquete a un
archivo del proyecto. Después de ejecutar el comando, existe una comprobación de compatibilidad para
garantizar que el paquete es compatible con los marcos del proyecto. Si se pasa la comprobación, un elemento
<PackageReference> se agrega al archivo del proyecto y dotnet restore se ejecuta.

Por ejemplo, si agrega Newtonsoft.Json a ToDo.csproj se producirá un resultado similar al del siguiente ejemplo:

Writing C:\Users\me\AppData\Local\Temp\tmp95A8.tmp
info : Adding PackageReference for package 'Newtonsoft.Json' into project 'C:\projects\ToDo\ToDo.csproj'.
log : Restoring packages for C:\Temp\projects\consoleproj\consoleproj.csproj...
info : GET https://api.nuget.org/v3-flatcontainer/newtonsoft.json/index.json
info : OK https://api.nuget.org/v3-flatcontainer/newtonsoft.json/index.json 79ms
info : GET https://api.nuget.org/v3-flatcontainer/newtonsoft.json/12.0.1/newtonsoft.json.12.0.1.nupkg
info : OK https://api.nuget.org/v3-flatcontainer/newtonsoft.json/12.0.1/newtonsoft.json.12.0.1.nupkg 232ms
log : Installing Newtonsoft.Json 12.0.1.
info : Package 'Newtonsoft.Json' is compatible with all the specified frameworks in project
'C:\projects\ToDo\ToDo.csproj'.
info : PackageReference for package 'Newtonsoft.Json' version '12.0.1' added to file
'C:\projects\ToDo\ToDo.csproj'.

El archivo ToDo.csproj contiene ahora un elemento <PackageReference> para el paquete al que hace referencia.

<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />

Restauración implícita
No es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que necesitan
que se produzca una restauración, como dotnet new , dotnet build , dotnet run , dotnet test , dotnet publish y
dotnet pack . Para deshabilitar la restauración implícita, use la opción --no-restore .

El comando dotnet restore sigue siendo válido en algunos escenarios donde tiene sentido realizar una
restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los
sistemas de compilación que necesitan controlar explícitamente cuándo se produce la restauración.
Para obtener información sobre cómo administrar fuentes de NuGet, vea la documentación de dotnet restore .

Argumentos
PROJECT

Especifica el archivo del proyecto. Si no se especifica, el comando busca uno en el directorio actual.
PACKAGE_NAME

La referencia de paquete que se va a agregar.

Opciones
-f|--framework <FRAMEWORK>

Agrega una referencia de paquete solo cuando se destina a un marco específico.


-h|--help

Imprime una corta ayuda para el comando.


--interactive

Permite que el comando se detenga y espere la entrada o acción del usuario (por ejemplo, completar la
autenticación). Disponible desde el SDK de .NET Core 2.1, versión 2.1.400 o posterior.
-n|--no-restore

Agrega una referencia de paquete sin realizar una vista previa de restauración y una comprobación de
compatibilidad.
--package-directory <PACKAGE_DIRECTORY>

Directorio donde quiere restaurar los paquetes. La ubicación predeterminada de restauración de paquetes
es %userprofile%\.nuget\packages en Windows y ~/.nuget/packages en macOS y Linux. Para obtener más
información, vea Administración de las carpetas de paquetes globales, de caché y temporales in NuGet.
-s|--source <SOURCE>

URI del origen del paquete NuGet que se usará durante la operación de restauración.
-v|--version <VERSION>

Versión del paquete. Consulte NuGet package versioning (Control de versiones de paquetes NuGet).

Ejemplos
Agregar un paquete de NuGet Newtonsoft.Json a un proyecto:

dotnet add package Newtonsoft.Json

Agregar una versión específica de un paquete a un proyecto:

dotnet add ToDo.csproj package Microsoft.Azure.DocumentDB.Core -v 1.0.0

Agregar un paquete con un origen de NuGet específico:


dotnet add package Microsoft.AspNetCore.StaticFiles -s https://dotnet.myget.org/F/dotnet-
core/api/v3/index.json

Vea también
Administración de las carpetas de paquetes globales, de caché y temporales en NuGet
Control de versiones de paquetes NuGet
dotnet list package
16/09/2020 • 6 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.2 y versiones posteriores

NOMBRE
dotnet list package : muestra las referencias de paquete de un proyecto o una solución.

Sinopsis
dotnet list [<PROJECT>|<SOLUTION>] package [--config <SOURCE>]
[--deprecated]
[--framework <FRAMEWORK>] [--highest-minor] [--highest-patch]
[--include-prerelease] [--include-transitive] [--interactive]
[--outdated] [--source <SOURCE>]

dotnet list package -h|--help

Descripción
El comando dotnet list package ofrece una opción práctica para mostrar todas las referencias de paquete de
NuGet de una solución o un proyecto específico. Primero deberá crear el proyecto para tener los recursos
necesarios para que este comando se procese. En el ejemplo siguiente se muestra la salida del comando
dotnet list package para el proyecto SentimentAnalysis:

Project 'SentimentAnalysis' has the following package references


[netcoreapp2.1]:
Top-level Package Requested Resolved
> Microsoft.ML 1.4.0 1.4.0
> Microsoft.NETCore.App (A) [2.1.0, ) 2.1.0

(A) : Auto-referenced package.

La columna Requested hace referencia a la versión de paquete especificada en el archivo del proyecto y puede ser
un intervalo. La columna Resolved muestra la versión que el proyecto usa actualmente y siempre se trata de un
valor único. Los paquetes que tienen (A) junto al nombre representan referencias implícitas de paquete que se
deducen de la configuración del proyecto (tipo de Sdk , propiedad <TargetFramework> o <TargetFrameworks> , etc.).
Use la opción --outdated para averiguar si hay disponibles más recientes de los paquetes que usa en los
proyectos. De manera predeterminada, --outdated muestra los paquetes estables más recientes, a menos que la
versión resuelta también sea una versión preliminar. Para incluir versiones preliminares cuando se muestren
versiones más recientes, especifique también la opción --include-prerelease . En los ejemplos siguientes se
muestra la salida del comando dotnet list package --outdated --include-prerelease para el mismo proyecto, tal
como en el ejemplo anterior:
The following sources were used:
https://api.nuget.org/v3/index.json
C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\

Project `SentimentAnalysis` has the following updates to its packages


[netcoreapp2.1]:
Top-level Package Requested Resolved Latest
> Microsoft.ML 1.4.0 1.4.0 1.5.0-preview

Si tiene que averiguar si el proyecto tiene dependencias transitivas, use la opción --include-transitive . Las
dependencias transitivas se producen cuando se agrega un paquete al proyecto que, a su vez, se basa en otro
paquete. En el ejemplo siguiente se muestra la salida de la ejecución del comando
dotnet list package --include-transitive en el proyecto HelloPlugin, que muestra paquetes de nivel superior y los
paquetes de los que dependen:

Project 'HelloPlugin' has the following package references


[netcoreapp3.0]:
Transitive Package Resolved
> PluginBase 1.0.0

Argumentos
PROJECT | SOLUTION

El archivo de proyecto o solución donde se operará. Si no se especifica, el comando busca uno en el directorio
actual. Si se encuentra más de una solución o proyecto, se genera un error.

Opciones
--config <SOURCE>

Los orígenes de NuGet que se usarán al buscar paquetes más recientes. Requiere la opción --outdated .
--deprecated

Muestra los paquetes que han quedado en desuso.


--framework <FRAMEWORK>

Muestra solo los paquetes aplicables al marco de destino especificado. Para especificar varios marcos, repita
la opción varias veces. Por ejemplo: --framework netcoreapp2.2 --framework netstandard2.0 .
-h|--help

Imprime una corta ayuda para el comando.


--highest-minor

Considere solo los paquetes con un número de versión principal coincidente al buscar paquetes más
recientes. Requiere la opción --outdated o --deprecated .
--highest-patch

Considere solo los paquetes con número de versión principal y secundario coincidente al buscar paquetes
más recientes. Requiere la opción --outdated o --deprecated .
--include-prerelease

Considere los paquetes con versiones preliminares al buscar paquetes más recientes. Requiere la opción
--outdated o --deprecated .
--include-transitive

Enumera los paquetes transitivos, además de los paquetes de nivel superior. Al especificar esta opción,
recibe una lista de paquetes de los que dependen los paquetes de nivel superior.
--interactive

Permite que el comando se detenga y espere una entrada o una acción del usuario. Por ejemplo, para
completar la autenticación. Disponible desde el SDK de .NET Core 3.0.
--outdated

Enumera los paquetes con versiones más recientes disponibles.


-s|--source <SOURCE>

Los orígenes de NuGet que se usarán al buscar paquetes más recientes. Requiere la opción --outdated o
--deprecated .

Ejemplos
Muestre las referencias de paquete de un proyecto específico:

dotnet list SentimentAnalysis.csproj package

Muestre las referencias de paquete que tienen versiones más recientes disponibles, incluidas versiones
preliminares:

dotnet list package --outdated --include-prerelease

Muestre las referencias de paquete para un marco de destino específico:

dotnet list package --framework netcoreapp3.0


dotnet remove package
20/04/2020 • 2 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.x y versiones posteriores

Name
dotnet remove package : quita la referencia de paquete de un archivo de proyecto.

Sinopsis
dotnet remove [<PROJECT>] package <PACKAGE_NAME>

dotnet remove package -h|--help

Description
El comando dotnet remove package constituye una opción práctica para quitar una referencia de paquete NuGet
de un proyecto.

Argumentos
PROJECT

Especifica el archivo del proyecto. Si no se especifica, el comando busca uno en el directorio actual.
PACKAGE_NAME

La referencia de paquete que hay que quitar.

Opciones
-h|--help

Imprime una corta ayuda para el comando.

Ejemplos
Quite el paquete NuGet Newtonsoft.Json de un proyecto en el directorio actual:

dotnet remove package Newtonsoft.Json


Cómo administrar las herramientas de .NET Core
16/09/2020 • 15 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores
Una herramienta de .NET Core es un paquete especial de NuGet que contiene una aplicación de consola.
Una herramienta se puede instalar en el equipo de las siguientes maneras:
Como herramienta global.
Los archivos binarios de la herramienta se instalan en un directorio predeterminado que se agrega a
la variable de entorno PATH. Puede invocar la herramienta desde cualquier directorio del equipo sin
especificar su ubicación. Una versión de una herramienta se usa para todos los directorios del
equipo.
Como herramienta global en una ubicación personalizada (también conocida como herramienta de
ruta de acceso de herramientas).
Los archivos binarios de la herramienta se instalan en la ubicación especificada. Puede invocar la
herramienta desde el directorio de instalación o proporcionando el directorio con el nombre del
comando o agregando el directorio a la variable de entorno PATH. Una versión de una herramienta
se usa para todos los directorios del equipo.
Como herramienta local (se aplica al SDK de .NET Core 3.0 y versiones posteriores).
Los archivos binarios de la herramienta se instalan en un directorio predeterminado. Puede invocar
la herramienta desde el directorio de instalación o con cualquiera de sus subdirectorios. Distintos
directorios pueden usar versiones diferentes de la misma herramienta.
La CLI de .NET usa archivos de manifiesto para realizar un seguimiento de las herramientas que se
instalan como locales en un directorio. Cuando el archivo de manifiesto se guarda en el directorio
raíz de un repositorio de código fuente, un colaborador puede clonar el repositorio e invocar un solo
comando de CLI de .NET Core que instale todas las herramientas enumeradas en los archivos de
manifiesto.

IMPORTANT
Las herramientas de .NET Core se ejecutan con plena confianza. No instale una herramienta de .NET Core a menos
que confíe en el autor.

Búsqueda de una herramienta


Actualmente, .NET Core no tiene una característica de búsqueda de herramientas. Estas son algunas formas
de encontrar herramientas:
Busque en el sitio web de NuGet mediante el filtro de tipo de paquete "herramienta .NET". Para más
información, vea Búsqueda y selección de paquetes.
Consulte la lista de herramientas del repositorio GitHub natemcmaster/dotnet-tools.
Use ToolGet para buscar herramientas de .NET.
Puede ver el código fuente de las herramientas creado por el equipo de ASP.NET Core en el directorio de
herramientas del repositorio de GitHub dotnet/aspnetcore.
Obtenga más información sobre las herramientas de diagnóstico en herramientas de diagnóstico de
dotnet de .NET Core .

Comprobar el autor y las estadísticas


Dado que las herramientas de .NET Core se ejecutan con plena confianza y las herramientas globales se
agregan a la variable de entorno PATH, pueden ser muy eficaces. No descargue herramientas de personas
en las que no confíe.
Si la herramienta está hospedada en NuGet, busque la herramienta para comprobar el autor y las
estadísticas.

Instalación de una herramienta global


Para instalar una herramienta como una herramienta global, use la opción -g o --global del comando
dotnet tool install, tal como se muestra en el ejemplo siguiente:

dotnet tool install -g dotnetsay

La salida muestra el comando que se usa para invocar la herramienta y la versión instalada, de forma
similar al ejemplo siguiente:

You can invoke the tool using the following command: dotnetsay
Tool 'dotnetsay' (version '2.1.4') was successfully installed.

La ubicación predeterminada de los archivos binarios de una herramienta depende del sistema operativo:

SO RUTA DE A C C ESO

Linux/macOS $HOME/.dotnet/tools

Windows %USERPROFILE%\.dotnet\tools

Esta ubicación se agrega a la ruta de acceso del usuario cuando se ejecuta el SDK por primera vez, por lo
que las herramientas globales se pueden invocar desde cualquier directorio sin especificar la ubicación de la
herramienta.
El acceso a las herramientas es específico del usuario, no de la máquina global. Una herramienta global solo
está disponible para el usuario que ha instalado la herramienta.
Instalación de una herramienta global en una ubicación personalizada
Para instalar una herramienta como una herramienta global, use la opción --tool-path del comando
dotnet tool install, tal como se muestra en los ejemplos siguientes.
En Windows:

dotnet tool install dotnetsay --tool-path c:\dotnet-tools

En Linux o macOS:

dotnet tool install dotnetsay --tool-path ~/bin

El SDK de .NET Core no agrega esta ubicación automáticamente a la variable de entorno PATH. Para invocar
una herramienta de ruta de acceso de herramientas, tiene que asegurarse de que el comando está
disponible mediante uno de los métodos siguientes:
Agregue el directorio de instalación a la variable de entorno PATH.
Especifique la ruta de acceso completa a la herramienta al invocarla.
Invoque la herramienta desde el directorio de instalación.

Instalación de una herramienta local


Se aplica al SDK de .NET Core 3.0 y versiones posteriores.
Para instalar una herramienta solo para el acceso local (del directorio y los subdirectorios actuales), debe
agregarse a un archivo de manifiesto de la herramienta. Para crear un archivo de manifiesto de
herramienta, ejecute el comando dotnet new tool-manifest :

dotnet new tool-manifest

Este comando crea un archivo de manifiesto denominado dotnet-tools.json en el directorio .config. Para
agregar una herramienta local al archivo de manifiesto, use el comando dotnet tool install y omita las
opciones --global y --tool-path , tal como se muestra en el ejemplo siguiente:

dotnet tool install dotnetsay

En la salida del comando se muestra el archivo de manifiesto en el que se encuentra la herramienta que
acaba de instalar, de manera similar al siguiente ejemplo:

You can invoke the tool from this directory using the following command:
dotnet tool run dotnetsay
Tool 'dotnetsay' (version '2.1.4') was successfully installed.
Entry is added to the manifest file /home/name/botsay/.config/dotnet-tools.json.

En el ejemplo siguiente se muestra un archivo de manifiesto con dos herramientas locales instaladas:

{
"version": 1,
"isRoot": true,
"tools": {
"botsay": {
"version": "1.0.0",
"commands": [
"botsay"
]
},
"dotnetsay": {
"version": "2.1.3",
"commands": [
"dotnetsay"
]
}
}
}

Normalmente, una herramienta local se agrega al directorio raíz del repositorio. Después de insertar el
archivo de manifiesto en el repositorio, los desarrolladores que extraen código del repositorio obtienen el
archivo de manifiesto más reciente. Para instalar todas las herramientas enumeradas en el archivo de
manifiesto, ejecutan el comando dotnet tool restore :
dotnet tool restore

La salida indica qué herramientas se han restaurado:

Tool 'botsay' (version '1.0.0') was restored. Available commands: botsay


Tool 'dotnetsay' (version '2.1.3') was restored. Available commands: dotnetsay
Restore was successful.

Instalación de una versión específica de la herramienta


Para instalar una versión preliminar o una versión específica de la herramienta, especifique el número de
versión con la opción --version , tal como se muestra en el ejemplo siguiente:

dotnet tool install dotnetsay --version 2.1.3

Uso de una herramienta


El comando que se usa para invocar una herramienta puede ser diferente del nombre del paquete que se
instala. Para mostrar todas las herramientas instaladas actualmente en el equipo para el usuario actual, use
el comando dotnet tool list:

dotnet tool list

La salida muestra la versión y el comando de cada herramienta, de forma similar al ejemplo siguiente:

Package Id Version Commands Manifest


-------------------------------------------------------------------------------------------
botsay 1.0.0 botsay /home/name/repository/.config/dotnet-tools.json
dotnetsay 2.1.3 dotnetsay /home/name/repository/.config/dotnet-tools.json

Tal como se muestra en este ejemplo, la lista muestra las herramientas locales. Para ver las herramientas
globales, use la opción --global y, para ver herramientas de ruta de acceso de herramientas, use la opción
--tool-path .

Invocación de una herramienta global


En el caso de las herramientas globales, use el comando de la herramienta por sí solo. Por ejemplo, si el
comando es dotnetsay o dotnet-doc , eso es lo que se usa para invocar el comando:

dotnetsay
dotnet-doc

Si el comando comienza con el prefijo dotnet- , una manera alternativa de invocar la herramienta es usar el
comando dotnet y omitir el prefijo del comando de la herramienta. Por ejemplo, si el comando es
dotnet-doc , el siguiente comando invoca la herramienta:

dotnet doc

Sin embargo, en el siguiente escenario no se puede usar el comando dotnet para invocar una herramienta
global:
Una herramienta global y una herramienta local tienen el mismo comando con el prefijo dotnet- .
Quiere invocar la herramienta global desde un directorio que está en el ámbito de la herramienta local.
En este escenario, dotnet doc y dotnet dotnet-doc invocan a la herramienta local. Para invocar la
herramienta global, use el comando por sí solo:

dotnet-doc

Invocación de una herramienta de ruta de acceso de herramientas


Para invocar una herramienta global que se instala mediante la opción tool-path , asegúrese de que el
comando está disponible, tal como se explicó anteriormente en este artículo.
Invocación de una herramienta local
Para invocar una herramienta local, tiene que usar el comando dotnet desde el directorio de instalación.
Puede usar el formato largo ( dotnet tool run <COMMAND_NAME> ) o el formato abreviado (
dotnet <COMMAND_NAME> ), tal como se muestra en los ejemplos siguientes:

dotnet tool run dotnetsay


dotnet dotnetsay

Si el comando tiene el prefijo dotnet- , puede incluir u omitir el prefijo al invocar la herramienta. Por
ejemplo, si el comando es dotnet-doc , cualquiera de los siguientes ejemplos invoca la herramienta local:

dotnet tool run dotnet-doc


dotnet dotnet-doc
dotnet doc

Actualización de una herramienta


La actualización de una herramienta implica desinstalarla y reinstalarla con la versión estable más reciente.
Para actualizar una herramienta, use el comando dotnet tool update con la misma opción que usó para
instalar la herramienta:

dotnet tool update --global <packagename>


dotnet tool update --tool-path <packagename>
dotnet tool update <packagename>

En el caso de una herramienta local, el SDK encuentra el primer archivo de manifiesto que contiene el
identificador de paquete mediante la búsqueda en el directorio actual y en los directorios principales. Si no
hay ningún identificador del paquete en ningún archivo de manifiesto, el SDK agrega una nueva entrada al
archivo de manifiesto más cercano.

Desinstalación de una herramienta


Quite una herramienta mediante el comando dotnet tool uninstall con la misma opción que usó para
instalar la herramienta:

dotnet tool uninstall --global <packagename>


dotnet tool uninstall --tool-path <packagename>
dotnet tool uninstall <packagename>
En el caso de una herramienta local, el SDK encuentra el primer archivo de manifiesto que contiene el
identificador de paquete mediante la búsqueda en el directorio actual y en los directorios principales.

Ayuda y solución de problemas


Para obtener una lista de los comandos de dotnet tool disponibles, escriba el siguiente comando:

dotnet tool --help

Para obtener instrucciones sobre el uso de la herramienta, escriba uno de los siguientes comandos o vea el
sitio web de la herramienta:

<command> --help
dotnet <command> --help

Si una herramienta no se puede instalar o ejecutar, consulte Solución de problemas de uso de herramientas
de .NET Core.

Vea también
Tutorial: Creación de una herramienta de .NET Core mediante la CLI de .NET Core
Tutorial: Instalación y uso de una herramienta global de .NET Core mediante la CLI de .NET Core
Tutorial: Instalación y uso de una herramienta local de .NET Core mediante la CLI de .NET Core
Solución de problemas de uso de herramientas de
.NET Core
16/09/2020 • 14 minutes to read • Edit Online

Es posible que surjan problemas al intentar instalar o ejecutar una herramienta de .NET Core, la cual puede ser
global o local. En este artículo se describen las causas principales más comunes y algunas posibles soluciones.

No se puede ejecutar la herramienta de .NET Core instalada


Cuando una herramienta de .NET Core no se ejecuta, lo más probable es que se haya producido uno de los
siguientes problemas:
No se encontró el archivo ejecutable de la herramienta.
No se encontró la versión correcta del runtime de .NET Core.
No se encontró el archivo ejecutable
Si no se encuentra el archivo ejecutable, verá un mensaje similar al siguiente:

Could not execute because the specified command or file was not found.
Possible reasons for this include:
* You misspelled a built-in dotnet command.
* You intended to execute a .NET Core program, but dotnet-xyz does not exist.
* You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on
the PATH.

El nombre del archivo ejecutable determina cómo se invoca la herramienta. En la siguiente tabla se describe el
formato:

F O RM ATO DEL N O M B RE DEL A RC H IVO E JEC UTA B L E F O RM ATO DE IN VO C A C IÓ N

dotnet-<toolName>.exe dotnet <toolName>

<toolName>.exe <toolName>

Herramientas globales
Las herramientas globales pueden instalarse en el directorio predeterminado o en una ubicación específica.
Los directorios predeterminados son:

SO RUTA DE A C C ESO

Linux/macOS $HOME/.dotnet/tools

Windows %USERPROFILE%\.dotnet\tools

Si intenta ejecutar una herramienta global, compruebe que la variable del entorno PATH de su máquina
contiene la ruta de acceso donde instaló la herramienta global y que el archivo ejecutable está en esa ruta
de acceso.
La primera vez que se usa, la CLI de .NET Core intenta agregar la ubicación predeterminada a la variable de
entorno PATH. Pero hay algunos escenarios en los que la ubicación podría no agregarse a PATH
automáticamente:
Si usa Linux y ha instalado el SDK de .NET Core mediante el uso de archivos .tar.gz en lugar de “apt-get”
o “rpm”.
Si usa macOS 10.15 “Catalina” o versiones posteriores.
Si usa macOS 10.14 “Mojave” o versiones anteriores y ha instalado el SDK de .NET Core mediante el uso
de archivos .tar.gz en lugar de .pkg.
Si ha instalado el SDK de .NET Core 3.0 y ha establecido la variable de entorno
DOTNET_ADD_GLOBAL_TOOLS_TO_PATH en false .
Si ha instalado el SDK de .NET Core 2.2 o versiones anteriores y ha establecido la variable de entorno
DOTNET_SKIP_FIRST_TIME_EXPERIENCE en true .
En estos casos, o si especificó la opción --tool-path , la variable de entorno PATH del equipo no contiene
automáticamente la ruta de acceso donde se instaló la herramienta global. En tal caso, anexe la ubicación
de la herramienta (por ejemplo, $HOME/.dotnet/tools ) a la variable de entorno PATH usando el método
que el shell proporcione para actualizar variables de entorno. Para obtener más información, vea
Herramientas de .NET Core.
Herramientas locales
Si intenta ejecutar una herramienta local, compruebe que hay un archivo de manifiesto denominado
dotnet-tools.json en el directorio actual o en cualquiera de sus directorios principales. Este archivo también
puede encontrarse en una carpeta denominada .config en cualquier lugar de la jerarquía de carpetas del
proyecto, en lugar de en la carpeta raíz. Si dotnet-tools.json existe, ábralo y busque la herramienta que
intenta ejecutar. Si el archivo no contiene una entrada para "isRoot": true , busque más arriba en la
jerarquía de archivos otros archivos de manifiesto de la herramienta.
Si intenta ejecutar una herramienta de .NET Core que se instaló con una ruta de acceso especificada, debe
incluir dicha ruta de acceso al usar la herramienta. Un ejemplo de uso de una herramienta instalada en una
ruta de acceso de herramientas es:

..\<toolDirectory>\dotnet-<toolName>

No se encontró el runtime
Las herramientas de .NET Core son aplicaciones dependientes del marco, lo que significa que se basan en un
runtime de .NET Core instalado en el equipo. Si no se encuentra el runtime esperado, se siguen las reglas
normales de puesta al día de runtime de .NET Core:
Una aplicación avanza a la versión de revisión más alta de las versiones principal y secundaria especificadas.
Si no hay ningún runtime que coincida con un número de versión principal y secundaria, se usa la siguiente
versión secundaria más alta.
Esta puesta al día no se produce entre versiones preliminares del runtime ni entre versiones preliminares y
versiones de lanzamiento. Por lo tanto, las herramientas de .NET Core creadas con versiones preliminares
deben ser recompiladas, publicadas nuevamente y reinstaladas por el autor.
La puesta al día no se producirá de forma predeterminada en dos escenarios comunes:
Solo están disponibles las versiones anteriores del runtime. La puesta al día solo selecciona versiones
posteriores del runtime.
Solo están disponibles las versiones posteriores principales del runtime. La puesta al día no traspasa los límites
de la versión principal.
Si una aplicación no encuentra el runtime apropiado, no se puede ejecutar y notifica un error.
Puede averiguar qué runtimes de .NET Core están instalados en su máquina con uno de los comandos siguientes:

dotnet --list-runtimes
dotnet --info

Si cree que la herramienta debería ser compatible con la versión de runtime que tiene instalada actualmente,
puede ponerse en contacto con el autor de la herramienta para ver si puede actualizar el número de versión o la
compatibilidad con varios destinos. Una vez que se vuelva a compilar y a publicar el paquete de herramientas en
NuGet con un número de versión actualizado, podrá actualizar su copia. Mientras tanto, la solución más rápida es
instalar una versión del runtime que funcione con la herramienta que intenta ejecutar. Para descargar una versión
específica del runtime de .NET Core, visite la página de descarga de .NET Core.
Si instala el SDK de .NET Core en una ubicación que no es la predeterminada, debe establecer la variable de
entorno DOTNET_ROOT en el directorio que contiene el archivo ejecutable dotnet .

Error en la instalación de una herramienta de .NET Core


Hay varias razones por las que se puede producir un error en la instalación de una herramienta local o global de
.NET Core. Cuando se produzca un error en la instalación de la herramienta, verá un mensaje similar al siguiente:

Tool '{0}' failed to install. This failure may have been caused by:

* You are attempting to install a preview release and did not use the --version option to specify the
version.
* A package by this name was found, but it was not a .NET Core tool.
* The required NuGet feed cannot be accessed, perhaps because of an Internet connection problem.
* You mistyped the name of the tool.

For more reasons, including package naming enforcement, visit https://aka.ms/failure-installing-tool

Para ayudar a diagnosticar estos errores, los mensajes de NuGet se muestran directamente al usuario, junto con el
mensaje anterior. El mensaje de NuGet puede ayudarle a identificar el problema.
Cumplimiento de la nomenclatura de los paquetes
Microsoft ha cambiado sus instrucciones sobre los identificadores de paquetes para las herramientas, lo que ha
dado lugar a que no se encuentren diversas herramientas con el nombre previsto. Según las nuevas instrucciones,
todas las herramientas de Microsoft deben tener el prefijo “Microsoft”. Este prefijo está reservado y solo se puede
usar para los paquetes firmados con un certificado autorizado de Microsoft.
Durante la transición, algunas herramientas de Microsoft tendrán el formato anterior del identificador de paquete,
mientras que otras tendrán el nuevo formato:

dotnet tool install -g Microsoft.<toolName>


dotnet tool install -g <toolName>

A medida que se actualicen los identificadores de paquetes, deberá usar el nuevo identificador de paquete para
obtener las actualizaciones más recientes. Los paquetes con el nombre de herramienta simplificado estarán en
desuso.
Versiones preliminares
Intenta instalar una versión preliminar y no ha usado la opción --version para especificar la versión.

Las herramientas de .NET Core que se encuentran en versión preliminar deben especificarse con una parte del
nombre para indicar que están en versión preliminar. No es necesario incluir todo el nombre de la versión
preliminar. Si los números de versión tienen el formato esperado, puede usar algo parecido al ejemplo siguiente:
dotnet tool install -g --version 1.1.0-pre <toolName>

El paquete no es una herramienta de .NET Core


Se encontró un paquete NuGet con este nombre, pero no era una herramienta .NET Core.
Si intenta instalar un paquete normal de NuGet que no es una herramienta de .NET Core, verá un error similar al
siguiente:

NU1212: combinación de paquete de proyecto no válida para <ToolName> . El estilo de proyecto


DotnetToolReference solo puede contener referencias de tipo DotnetTool.

No se puede acceder a la fuente NuGet


No se puede acceder a la fuente NuGet necesaria, quizás debido a un problema con la conexión a Internet.
La instalación de la herramienta requiere acceso a la fuente NuGet que contiene el paquete de la herramienta. Se
produce un error si la fuente no está disponible. Puede modificar las fuentes con nuget.config , solicitar un
archivo nuget.config determinado o especificar fuentes adicionales con el modificador --add-source . De forma
predeterminada, NuGet genera un error para cualquier fuente con la que no pueda conectar. La marca
--ignore-failed-sources puede omitir estos orígenes no accesibles.

Identificador de paquete incorrecto


No ha escrito correctamente el nombre de la herramienta.
Un motivo habitual de error es que el nombre de la herramienta no es correcto. Esto puede ocurrir cuando se
produce un error de escritura, o porque la herramienta se ha movido o está en desuso. En el caso de las
herramientas de NuGet.org, una manera de asegurarse de que tiene el nombre correcto es buscar la herramienta
en NuGet.org y copiar el comando de instalación.

Vea también
Herramientas de .NET Core
Tutorial: Creación de una herramienta de .NET Core
mediante la CLI de .NET Core
16/09/2020 • 7 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores
En este tutorial se explica cómo crear y empaquetar una herramienta en .NET Core. La CLI de .NET Core permite
crear una aplicación de consola como una herramienta, que otros usuarios pueden instalar y ejecutar. Las
herramientas de .NET Core son paquetes de NuGet que se instalan desde la CLI de .NET Core. Para obtener más
información sobre las herramientas, vea Información general sobre las herramientas de .NET Core.
La herramienta que se va a crear es una aplicación de consola que toma un mensaje como entrada y muestra el
mensaje junto con líneas de texto que crean la imagen de un robot.
Este es el primero en una serie de tres tutoriales. En este tutorial, creará y empaquetará una herramienta. En los
dos tutoriales siguientes, usará la herramienta como una herramienta global y usará la herramienta como una
herramientas local.

Requisitos previos
SDK 3.1 de NET Core o una versión posterior.
Este tutorial y el siguiente tutorial para las herramientas globales se aplican al SDK de .NET Core 2.1 y
versiones posteriores, porque las herramientas globales están disponibles a partir de esa versión. Pero en
este tutorial se da por supuesto que tiene instalada la versión 3.1 o posterior para que tenga la opción de
continuar con el tutorial de herramientas locales. Las herramientas locales están disponibles a partir del
SDK de .NET Core 3.0. Los procedimientos para crear una herramienta son los mismos tanto si se usan
como una herramienta global o como una herramienta local.
Un editor de texto o un editor de código de su elección.

Crear un proyecto
1. Abra un símbolo del sistema y cree una carpeta denominada repositorio.
2. Desplácese hasta la carpeta repository y escriba el comando siguiente:

dotnet new console -n microsoft.botsay

El comando crea una carpeta denominada microsoft.botsay en la carpeta repository.


3. Navegue hasta la carpeta microsoft.botsay.

cd microsoft.botsay

Agregar el código
1. Abra el archivo Program.cs con el editor de código.
2. Agregue la siguiente directiva using al principio del archivo:
using System.Reflection;

3. Reemplace el método Main con el siguiente código para procesar los argumentos de la línea de comandos
para la aplicación.

static void Main(string[] args)


{
if (args.Length == 0)
{
var versionString = Assembly.GetEntryAssembly()
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
.InformationalVersion
.ToString();

Console.WriteLine($"botsay v{versionString}");
Console.WriteLine("-------------");
Console.WriteLine("\nUsage:");
Console.WriteLine(" botsay <message>");
return;
}

ShowBot(string.Join(' ', args));


}

Si no se pasó ningún argumento, se muestra un mensaje de ayuda breve. De lo contrario, todos los
argumentos se concatenan en una sola cadena y se imprimen llamando al método ShowBot que se crea en
el paso siguiente.
4. Agregue un nuevo método denominado ShowBot que toma un parámetro de cadena. El método imprime
el mensaje y una imagen de un robot usando líneas de texto.
static void ShowBot(string message)
{
string bot = $"\n {message}";
bot += @"
__________________
\
\
....
....'
....
..........
.............'..'..
................'..'.....
.......'..........'..'..'....
........'..........'..'..'.....
.'....'..'..........'..'.......'.
.'..................'... ......
. ......'......... .....
. _ __ ......
.. # ## ......
.... . .......
...... ....... ............
................ ......................
........................'................
......................'..'...... .......
.........................'..'..... .......
........ ..'.............'..'.... ..........
..'..'... ...............'....... ..........
...'...... ...... .......... ...... .......
........... ....... ........ ......
....... '...'.'. '.'.'.' ....
....... .....'.. ..'.....
.. .......... ..'........
............ ..............
............. '..............
...........'.. .'.'............
............... .'.'.............
.............'.. ..'..'...........
............... .'..............
......... ..............
.....
";
Console.WriteLine(bot);
}

5. Guarde los cambios.

Probar la aplicación
Ejecute el proyecto y observe la salida. Pruebe estas variaciones en la línea de comandos para ver resultados
diferentes:

dotnet run
dotnet run -- "Hello from the bot"
dotnet run -- Hello from the bot

Todos los argumentos después del delimitador -- se pasan a la aplicación.

Empaquetado de la herramienta
Antes de que pueda empaquetar y distribuir la aplicación como una herramienta, debe modificar el archivo de
proyecto.
1. Abra el archivo microsoft.botsay.csproj y agregue tres nuevos nodos XML al final del nodo
<PropertyGroup> :

<PackAsTool>true</PackAsTool>
<ToolCommandName>botsay</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>

<ToolCommandName> es un elemento opcional que especifica el comando que invocará a la herramienta una
vez instalada. Si no se proporciona este elemento, el nombre de comando para la herramienta es el
nombre del archivo de proyecto sin la extensión .csproj.
<PackageOutputPath> es un elemento opcional que determina dónde se generará el paquete NuGet. El
paquete NuGet es el que la CLI de .NET Core utiliza para instalar la herramienta.
El archivo del proyecto debe ser similar al siguiente ejemplo:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>

<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>

<PackAsTool>true</PackAsTool>
<ToolCommandName>botsay</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>

</PropertyGroup>

</Project>

2. Cree un paquete NuGet ejecutando el comando dotnet pack:

dotnet pack

El archivo microsoft.botsay.1.0.0.nupkg se crea en la carpeta identificada por el valor <PackageOutputPath>


del archivo microsoft.botsay.csproj, que en este ejemplo es la carpeta ./nupkg.
Si quiere lanzar una herramienta públicamente, puede cargarla en https://www.nuget.org . Una vez que la
herramienta está disponible en NuGet, los desarrolladores pueden instalar la herramienta mediante el
comando dotnet tool install. En este tutorial, se instalará el paquete directamente desde la carpeta local
nupkg, por lo que no es necesario cargar el paquete en NuGet.

Solucionar problemas
Si recibe un mensaje de error al seguir el tutorial, vea Solución de problemas de uso de herramientas de
.NET Core.

Pasos siguientes
En este tutorial, ha creado una aplicación de consola y la ha empaquetado como una herramienta. Para aprender a
usar la herramienta como una herramienta global, avance al siguiente tutorial.
Instalación y uso de una herramienta global
Si lo prefiere, puede omitir el tutorial de herramientas globales y pasar directamente al tutorial de herramientas
locales.
Instalación y uso de una herramienta local
Tutorial: Instalación y uso de una herramienta global
de .NET Core mediante la CLI de .NET Core
16/09/2020 • 4 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores
En este tutorial se enseña cómo instalar y usar una herramienta global. Usará una herramienta que ha creado en
el primer tutorial de esta serie.

Requisitos previos
Complete el primer tutorial de esta serie.

Uso de la herramienta como una herramienta global


1. Instale la herramienta desde el paquete; para ello, ejecute el comando dotnet tool install en la carpeta del
proyecto microsoft.botsay:

dotnet tool install --global --add-source ./nupkg microsoft.botsay

El parámetro --global indica a la CLI de .NET Core que instale los archivos binarios de la herramienta en
una ubicación predeterminada que se agrega automáticamente a la variable de entorno PATH.
El parámetro --add-source indica a la CLI de .NET Core que use temporalmente el directorio ./nupkg
como una fuente de origen adicional para los paquetes NuGet. Ha asignado un nombre único al paquete
para asegurarse de que solo se encontrará en el directorio ./nupkg, no en el sitio de Nuget.org.
En la salida se muestra el comando que se ha usado para llamar a la herramienta y la versión instalada:

You can invoke the tool using the following command: botsay
Tool 'microsoft.botsay' (version '1.0.0') was successfully installed.

2. Invoque la herramienta:

botsay hello from the bot

NOTE
Si se produce un error en este comando, es posible que tenga que abrir un nuevo terminal para actualizar el valor
de PATH.

3. Ejecute el comando dotnet tool uninstall para quitar la herramienta:

dotnet tool uninstall -g microsoft.botsay

Uso de la herramienta como una herramienta global instalada en una


ubicación personalizada
1. Instale la herramienta desde el paquete.
En Windows:

dotnet tool install --tool-path c:\dotnet-tools --add-source ./nupkg microsoft.botsay

En Linux o macOS:

dotnet tool install --tool-path ~/bin --add-source ./nupkg microsoft.botsay

El parámetro --tool-path indica a la CLI de .NET Core que instale los archivos binarios de la herramienta
en la ubicación especificada. Si el directorio no existe, se crea. Este directorio no se agrega
automáticamente a la variable de entorno PATH.
En la salida se muestra el comando que se ha usado para llamar a la herramienta y la versión instalada:

You can invoke the tool using the following command: botsay
Tool 'microsoft.botsay' (version '1.0.0') was successfully installed.

2. Invoque la herramienta:
En Windows:

c:\dotnet-tools\botsay hello from the bot

En Linux o macOS:

~/bin/botsay hello from the bot

3. Ejecute el comando dotnet tool uninstall para quitar la herramienta:


En Windows:

dotnet tool uninstall --tool-path c:\dotnet-tools microsoft.botsay

En Linux o macOS:

dotnet tool uninstall --tool-path ~/bin microsoft.botsay

Solucionar problemas
Si recibe un mensaje de error al seguir el tutorial, vea Solución de problemas de uso de herramientas de
.NET Core.

Pasos siguientes
En este tutorial, ha instalado y usado una herramienta como una herramienta global. Para instalar y usar la
misma herramienta como una herramienta local, vaya al siguiente tutorial.
Instalación y uso de herramientas locales
Tutorial: Instalación y uso de una herramienta local
de .NET Core mediante la CLI de .NET Core
16/09/2020 • 7 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.0 y versiones posteriores
En este tutorial se enseña cómo instalar y usar una herramienta local. Usará una herramienta que ha creado en
el primer tutorial de esta serie.

Requisitos previos
Complete el primer tutorial de esta serie.
Instale el entorno de ejecución de .NET Core 2.1.
En este tutorial, instalará y usará una herramienta que tiene como destino .NET Core 2.1, por lo que debe
tener ese entorno de ejecución instalado en el equipo. Para instalar la versión 2.1 del entorno de
ejecución, vaya a la página de descarga de .NET Core 2.1 y busque el vínculo de instalación del entorno
de ejecución en la columna Ejecutar aplicaciones: entorno de ejecución .

Crear un archivo de manifiesto


Para instalar una herramienta solo para el acceso local (del directorio y los subdirectorios actuales), debe
agregarse a un archivo de manifiesto.
Desde la carpeta microsoft.botsay, suba un nivel hasta la carpeta repository:

cd ..

Cree un archivo de manifiesto; para ello, ejecute el comando dotnet new:

dotnet new tool-manifest

En la salida se indica que el archivo se ha creado correctamente.

The template "Dotnet local tool manifest file" was created successfully.

El archivo .config/dotnet-tools.json aún no contiene ninguna herramienta:

{
"version": 1,
"isRoot": true,
"tools": {}
}

Las herramientas enumeradas en un archivo de manifiesto están disponibles en el directorio y los


subdirectorios actuales. El directorio actual es el que contiene el directorio .config con el archivo de manifiesto.
Si se usa un comando de la CLI que hace referencia a una herramienta local, el SDK busca un archivo de
manifiesto en el directorio actual y los directorios principales. Si encuentra un archivo de manifiesto, pero el
archivo no incluye la herramienta a la que se hace referencia, continúa con la búsqueda en los directorios
principales. La búsqueda finaliza cuando encuentra la herramienta a la que se hace referencia o un archivo de
manifiesto con isRoot establecido en true .

Instalación de botsay como una herramienta local


Instale la herramienta desde el paquete que ha creado en el primer tutorial:

dotnet tool install --add-source ./microsoft.botsay/nupkg microsoft.botsay

Este comando agrega la herramienta al archivo de manifiesto que ha creado en el paso anterior. En la salida del
comando se muestra el archivo de manifiesto en el que se encuentra la herramienta que acaba de instalar:

You can invoke the tool from this directory using the following command:
'dotnet tool run botsay' or 'dotnet botsay'
Tool 'microsoft.botsay' (version '1.0.0') was successfully installed.
Entry is added to the manifest file /home/name/repository/.config/dotnet-tools.json

Ahora, el archivo .config/dotnet-tools.json tiene una herramienta:

{
"version": 1,
"isRoot": true,
"tools": {
"microsoft.botsay": {
"version": "1.0.0",
"commands": [
"botsay"
]
}
}
}

Usar la herramienta
Para invocar la herramienta, ejecute el comando dotnet tool run desde la carpeta repository:

dotnet tool run botsay hello from the bot

Restauración de una herramienta local instalada por otros usuarios


Normalmente, una herramienta local se instala en el directorio raíz del repositorio. Después de insertar el
archivo de manifiesto en el repositorio, otros desarrolladores pueden obtener el archivo de manifiesto más
reciente. Para instalar todas las herramientas enumeradas en el archivo de manifiesto, pueden ejecutar un único
comando dotnet tool restore .
1. Abra el archivo .config/dotnet-tools.json y reemplace el contenido con el siguiente código JSON:
{
"version": 1,
"isRoot": true,
"tools": {
"microsoft.botsay": {
"version": "1.0.0",
"commands": [
"botsay"
]
},
"dotnetsay": {
"version": "2.1.3",
"commands": [
"dotnetsay"
]
}
}
}

2. Reemplace <name> con el nombre que ha usado para crear el proyecto.


3. Guarde los cambios.
Cambiar esto tiene el mismo resultado que obtener la versión más reciente del repositorio después de
que otra persona haya instalado el paquete dotnetsay en el directorio del proyecto.
4. Ejecute el comando dotnet tool restore .

dotnet tool restore

El comando genera una salida como la del siguiente ejemplo:

Tool 'microsoft.botsay' (version '1.0.0') was restored. Available commands: botsay


Tool 'dotnetsay' (version '2.1.3') was restored. Available commands: dotnetsay
Restore was successful.

5. Compruebe que las herramientas están disponibles:

dotnet tool list

La salida es una lista de paquetes y comandos, similar al ejemplo siguiente:

Package Id Version Commands Manifest


--------------------------------------------------------------------------------------------
microsoft.botsay 1.0.0 botsay /home/name/repository/.config/dotnet-tools.json
dotnetsay 2.1.3 dotnetsay /home/name/repository/.config/dotnet-tools.json

6. Pruebe las herramientas:

dotnet tool run dotnetsay hello from dotnetsay


dotnet tool run botsay hello from botsay

Actualización de una herramienta local


La versión instalada de la herramienta local dotnetsay es 2.1.3. La versión más reciente es 2.1.4. Use el
comando dotnet tool update para actualizar la herramienta a la versión más reciente.

dotnet tool update dotnetsay

En la salida se indica el nuevo número de versión:

Tool 'dotnetsay' was successfully updated from version '2.1.3' to version '2.1.4'
(manifest file /home/name/repository/.config/dotnet-tools.json).

El comando update busca el primer archivo de manifiesto que contiene el identificador del paquete y lo
actualiza. Si no hay ningún identificador del paquete en ningún archivo de manifiesto que esté en el ámbito de
la búsqueda, el SDK agrega una nueva entrada al archivo de manifiesto más cercano. El ámbito de búsqueda va
hacia los directorios principales hasta que se encuentra un archivo de manifiesto con isRoot = true .

Eliminación de las herramientas locales


Ejecute el comando dotnet tool uninstall para quitar las herramientas instaladas:

dotnet tool uninstall microsoft.botsay

dotnet tool uninstall dotnetsay

Solucionar problemas
Si recibe un mensaje de error al seguir el tutorial, consulte Solución de problemas de uso de herramientas de
.NET Core.

Vea también
Para obtener más información, vea Herramientas de .NET Core.
Información general de global.json
16/09/2020 • 20 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.0 y versiones posteriores
El archivo global.json permite definir qué versión del SDK de .NET Core se usa al ejecutar comandos de la CLI de
.NET Core. La selección del SDK de .NET Core es independiente de especificar el runtime al que está destinado el
proyecto. La versión del SDK de .NET Core indica qué versiones de la CLI de .NET Core se usan.
En general, se quiere usar la versión más reciente de las herramientas del SDK, por lo que no es necesario ningún
archivo global.json. En algunos escenarios avanzados, es posible que quiera controlar la versión de las
herramientas del SDK, así que en este artículo se explica cómo.
Para obtener más información sobre cómo especificar el runtime en su lugar, vea Plataformas de destino.
El SDK de .NET Core busca un archivo global.json en el directorio de trabajo actual (que no es necesariamente el
mismo que el directorio del proyecto) o en uno de sus directorios principales.

Esquema de global.JSON
sdk
Tipo: object

Especifica información sobre el SDK de .NET Core que se va a seleccionar.


version
Tipo: string

Disponible a partir del SDK de .NET Core 1.0.


La versión del SDK de .NET Core que se va a usar.
Este campo:
No admite caracteres comodín, es decir, se debe especificar el número de versión completo.
No admite intervalos de versiones.
allowPrerelease
Tipo: boolean

Disponible a partir del SDK de .NET Core 3.0.


Indica si la resolución del SDK debe tener en cuenta las versiones preliminares a la hora de seleccionar la versión
del SDK que se va a usar.
Si no se establece este valor de forma explícita, el valor predeterminado depende de si se está ejecutando desde
Visual Studio:
Si no se está en Visual Studio, el valor predeterminado es true .
Si se está en Visual Studio, se usa el estado de versión preliminar solicitado. Es decir, si se usa una versión
preliminar de Visual Studio o se establece la opción Usar versiones preliminares del SDK de .NET Core
(en Herramientas > Opciones > Entorno > Características en versión preliminar ), el valor
predeterminado es true ; de lo contrario, false .
rollForward
Tipo: string

Disponible a partir del SDK de .NET Core 3.0.


Directiva de puesta al día que se va a usar al seleccionar una versión del SDK, ya sea como reserva si falta una
versión específica del SDK o como una directiva para usar una versión superior. Se debe especificar una versión
con un valor rollForward , a menos que se esté estableciendo en latestMajor .
Para comprender las directivas disponibles y su comportamiento, tenga en cuenta las siguientes definiciones de
una versión del SDK en el formato x.y.znn :
x es la versión principal.
y es la versión secundaria.
z es la banda de características.
nn es la versión de la revisión.

En la siguiente tabla se muestran los posibles valores de la clave rollForward :

VA LO R C O M P O RTA M IEN TO

patch Usa la versión especificada.


Si no se encuentra, se pone al día hasta el nivel de revisión
más reciente.
Si no se encuentra, se produce un error.

Este valor es el comportamiento heredado de las versiones


anteriores del SDK.

feature Usa el nivel de revisión más reciente para las versiones


principal y secundaria y la banda de características
especificadas.
Si no se encuentra, se pone al día hasta la siguiente banda de
características superior dentro de la misma versión
principal/secundaria y usa el nivel de revisión más reciente
para esa banda de características.
Si no se encuentra, se produce un error.

minor Usa el nivel de revisión más reciente para las versiones


principal y secundaria y la banda de características
especificadas.
Si no se encuentra, se pone al día hasta la siguiente banda de
características superior dentro de la misma versión
principal/secundaria y usa el nivel de revisión más reciente
para esa banda de características.
Si no se encuentra, se pone al día hasta la siguiente versión
secundaria y banda de características superiores dentro de la
misma versión principal y usa el nivel de revisión más reciente
para esa banda de características.
Si no se encuentra, se produce un error.
VA LO R C O M P O RTA M IEN TO

major Usa el nivel de revisión más reciente para las versiones


principal y secundaria y la banda de características
especificadas.
Si no se encuentra, se pone al día hasta la siguiente banda de
características superior dentro de la misma versión
principal/secundaria y usa el nivel de revisión más reciente
para esa banda de características.
Si no se encuentra, se pone al día hasta la siguiente versión
secundaria y banda de características superiores dentro de la
misma versión principal y usa el nivel de revisión más reciente
para esa banda de características.
Si no se encuentra, se pone al día hasta la siguiente versión
principal, secundaria y banda de características superiores y
usa el nivel de revisión más reciente para esa banda de
características.
Si no se encuentra, se produce un error.

latestPatch Usa el nivel de revisión instalado más reciente que coincide


con la versión principal, secundaria y banda de características
solicitadas con un nivel de revisión y que es mayor o igual
que el valor especificado.
Si no se encuentra, se produce un error.

latestFeature Usa la banda de características instalada superior y el nivel de


revisión que coincide con la versión principal y secundaria
solicitadas con una banda de características que es mayor o
igual que el valor especificado.
Si no se encuentra, se produce un error.

latestMinor Usa la versión secundaria y la banda de características


instaladas superiores y el nivel de revisión que coincide con la
versión principal solicitada con una versión secundaria que es
mayor o igual que el valor especificado.
Si no se encuentra, se produce un error.

latestMajor Usa el SDK de .NET Core instalado superior con una versión
principal que es mayor o igual que el valor especificado.
Si no se encuentra, se produce un error.

disable No se pone al día. Se requiere una coincidencia exacta.

msbuild-sdks
Tipo: object
Permite controlar la versión del SDK del proyecto en un lugar, en vez de hacerlo en cada proyecto individual. Para
más información, vea Resolución de los SDK de proyecto.

Ejemplos
En el ejemplo siguiente se muestra cómo no usar versiones preliminares:

{
"sdk": {
"allowPrerelease": false
}
}
En este ejemplo se muestra cómo usar la versión superior instalada que es mayor o igual que la versión
especificada. El JSON mostrado no permite ninguna versión del SDK anterior a 2.2.200 y permite 2.2.200 o
cualquier versión posterior, incluidos 3.0.xxx y 3.1.xxx.

{
"sdk": {
"version": "2.2.200",
"rollForward": "latestMajor"
}
}

En el ejemplo siguiente se muestra cómo usar la versión exacta especificada:

{
"sdk": {
"version": "3.1.100",
"rollForward": "disable"
}
}

En este ejemplo se muestra cómo usar la versión de revisión y banda de características más reciente instalada de
una versión principal y secundaria concreta. El JSON mostrado no permite ninguna versión del SDK anterior a
3.1.102 y permite 3.1.102 o cualquier versión 3.1.xxx posterior, como 3.1.103 o 3.1.200.

{
"sdk": {
"version": "3.1.102",
"rollForward": "latestFeature"
}
}

En este ejemplo se muestra cómo usar la versión de revisión superior instalada de una versión específica. El JSON
mostrado no permite ninguna versión del SDK anterior a 3.1.102 y permite 3.1.102 o cualquier versión 3.1.1xx
posterior, como 3.1.103 o 3.1.199.

{
"sdk": {
"version": "3.1.102",
"rollForward": "latestPatch"
}
}

global.json y la CLI de .NET Core


Resulta útil saber qué versiones del SDK están instaladas en el equipo con el fin de establecer una en el archivo
global.json. Para obtener más información sobre cómo hacerlo, vea Cómo comprobar que .NET Core ya está
instalado.
Para instalar otras versiones del SDK de .NET Core en el equipo, visite la página de descargas de .NET Core.
Puede crear un archivo global.json en el directorio actual mediante la ejecución del comando dotnet new, similar
al ejemplo siguiente:

dotnet new globaljson --sdk-version 3.0.100


Reglas de coincidencia
NOTE
Las reglas de coincidencia se rigen por el punto de entrada de dotnet.exe , que es común en todos los runtime instalados
de .NET Core. Las reglas de coincidencia de la última versión instalada del runtime de .NET Core se usan cuando se tienen
varios runtime instalados en paralelo.

.NET Core 3.x


.NET Core 2.x
A partir de .NET Core 3.0, se aplican las reglas siguientes al determinar qué versión del SDK se va a usar:
Si no se encuentra ningún archivo global.json o en global.json no se especifica una versión del SDK ni un
valor allowPrerelease , se usa la versión del SDK instalada más reciente (lo que equivale a establecer
rollForward en latestMajor ). El que se consideren o no las versiones preliminares del SDK depende de
cómo se invoque a dotnet .
Si no se está en Visual Studio, se tienen en cuenta las versiones preliminares.
Si se está en Visual Studio, se usa el estado de versión preliminar solicitado. Es decir, si se usa una
versión preliminar de Visual Studio o se establece la opción Usar versiones preliminares del SDK
de .NET Core (en Herramientas > Opciones > Entorno > Características en versión
preliminar ), se tienen en cuenta las versiones preliminares; de lo contrario, solo se consideran las
versiones publicadas.
Si se encuentra un archivo global.json que no especifica una versión del SDK pero sí un valor
allowPrerelease , se usa la versión del SDK instalada superior (lo que equivale a establecer rollForward
en latestMajor ). El que la versión más reciente del SDK pueda ser publicada o preliminar, depende del
valor de allowPrerelease . true indica que se tienen en cuenta las versiones preliminares; false indica
que solo se tienen en cuenta las versiones publicadas.
Si se encuentra un archivo global.json y especifica una versión del SDK:
Si no hay ningún valor rollFoward establecido, se usa latestPatch como directiva de rollForward
predeterminada. De lo contrario, vea cada valor y su comportamiento en la sección rollForward.
En la sección allowPrerelease se explica si se tienen en cuenta las versiones preliminares y cuál es el
comportamiento predeterminado cuando allowPrerelease no está establecido.

Solución de problemas de advertencias de compilación


La advertencia siguiente indica que el proyecto se ha compilado con una versión preliminar del SDK de
.NET Core:

Trabaja con una versión preliminar del SDK de .NET Core. Puede definir la versión del SDK a través de
un archivo global.json en el proyecto actual. Más información en https://go.microsoft.com/fwlink/?
linkid=869452.

Las versiones del SDK de .NET Core tienen un historial y el compromiso de ser de alta calidad. Pero si no
quiere usar una versión preliminar, vea las distintas estrategias que puede aplicar con el SDK de .NET Core
3.0 o una versión posterior en la sección allowPrerelease. En el caso de los equipos que nunca han tenido
instalado un SDK o un runtime de .NET Core 3.0 o superior, debe crear un archivo global.json y especificar
la versión exacta que quiere usar.
La advertencia siguiente indica que el proyecto tiene como destino EF Core 1.0 ó 1.1, que no es compatible
con el SDK de .NET Core 2.1 y versiones posteriores:

El proyecto de inicio "{proyectoDeInicio}" está destinado a la versión "{versiónPlataformaDeDestino}"


de ".NETCoreApp". Esta versión de las herramientas de línea de comandos de Entity Framework Core
.NET solo admite la versión 2.0 o superior. Para obtener información sobre el uso de versiones
anteriores de las herramientas, vea https://go.microsoft.com/fwlink/?linkid=871254.

A partir del SDK de .NET Core 2.1 (versión 2.1.300), el comando dotnet ef se incluye en el SDK. Para
compilar el proyecto, instale el SDK de .NET Core 2.0 (versión 2.1.201) o versiones anteriores en el equipo
y defina la versión del SDK deseada mediante el archivo global.json. Para obtener más información sobre
el comando dotnet ef , vea EF Core .NET Command-line Tools (Herramientas de línea de comandos de EF
Core .NET).

Vea también
Resolución de los SDK de proyecto
Telemetría del SDK de .NET Core
16/09/2020 • 10 minutes to read • Edit Online

El SDK de .NET Core incluye una característica de telemetría que recopila datos de uso e información de excepción
cuando se bloquea el CLI de .NET Core. El CLI de .NET Core incluye el SDK de .NET Core y es el conjunto de verbos
que permiten compilar, probar y publicar las aplicaciones de .NET Core. Es importante que el equipo de .NET
entienda cómo se usan las herramientas con el fin de mejorarlas. Con la información sobre los errores, el equipo
consigue resolver problemas y corregir errores.
Los datos recopilados son anónimos y se publican de forma agregada bajo la licencia de atribución de Creative
Commons.

Ámbito
dotnet tiene dos funciones: ejecutar aplicaciones y ejecutar comandos de la CLI. La telemetría no se recopila
cuando se usa dotnet para iniciar una aplicación con el siguiente formato:
dotnet [path-to-app].dll

La telemetría se recopila cuando se usa cualquiera de los comandos de la CLI de .NET Core, como:
dotnet build
dotnet pack
dotnet run

Cómo desactivar la característica


La característica de telemetría del SDK de .NET Core está habilitada de manera predeterminada. Para desactivar la
característica de telemetría, establezca la variable de entorno DOTNET_CLI_TELEMETRY_OPTOUT en 1 o true .
El instalador del SDK de .NET Core también envía una única entrada de telemetría cuando se produce una
instalación correcta. Para desactivarla, establezca la variable de entorno de DOTNET_CLI_TELEMETRY_OPTOUT antes de
instalar el SDK de .NET Core.

Divulgación
El SDK de .NET Core muestra texto similar al siguiente cuando se ejecuta por primera vez uno de los comandos de
la CLI de .NET Core (por ejemplo, dotnet build ). El texto puede variar ligeramente según la versión del SDK que
ejecute. Esta experiencia de "primera vista" es la forma en que Microsoft le notifica sobre la recopilación de datos.

Telemetry
---------
The .NET Core tools collect usage data in order to help us improve your experience. The data is anonymous. It
is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the
DOTNET_CLI_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell.

Read more about .NET Core CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry

Para deshabilitar este mensaje y el mensaje de bienvenida de .NET Core, establezca la variable de entorno
DOTNET_NOLOGO en true . Tenga en cuenta que esta variable no tiene ningún efecto sobre la exclusión de la
telemetría.
Puntos de datos
La característica de telemetría no recopila datos personales, como direcciones de correo electrónico o nombres de
usuario. No examina el código ni extrae datos de nivel de proyecto, como el nombre, el repositorio o el autor. Los
datos se envían de forma segura a los servidores de Microsoft con tecnología de Azure Monitor, se conservan bajo
acceso restringido y se publican bajo controles de seguridad estrictos de sistemas seguros de Azure Storage.
La protección de su privacidad es importante para nosotros. Si sospecha que la telemetría está recopilando datos
confidenciales o que los datos se están tratando de forma no segura o inapropiada, informe de un problema en el
repositorio de dotnet/cli o envíenos un correo electrónico a dotnet@microsoft.com para que lo investiguemos.
La característica de telemetría recopila los siguientes datos:

VERSIO N ES DEL SDK DATO S

Todas Marca de tiempo de la invocación.

Todas Comando invocado (por ejemplo, "build"), con hash a partir de


2.1.

Todas Dirección IP de tres octetos usada para determinar la


ubicación geográfica.

Todas Sistema operativo y versión.

Todas Identificador de tiempo de ejecución (RID) en el que se ejecuta


el SDK.

Todas Versión del SDK de .NET Core.

Todas Perfil de telemetría: valor opcional usado internamente en


Microsoft y que solo se usa con la participación explícita del
usuario.

>=2.0 Opciones y argumentos del comando: se recopilan varias


opciones y argumentos (no cadenas arbitrarias). Vea opciones
recopiladas. Con hash a partir de la versión 2.1.300.

>=2.0 Si el SDK se ejecuta en un contenedor.

>=2.0 Plataformas de destino (desde el evento TargetFramework ),


con hash a partir de 2.1.

>=2.0 Dirección MAC con hash: identificador único y anónimo


criptográficamente (SHA256) para una máquina.

>=2.0 Directorio de trabajo actual con hash.

>=2.0 Informe de instalación correcta, con el nombre de archivo exe


del instalador con hash.

>=2.1.300 Versión de kernel.

>=2.1.300 Versión/versión de libc.


VERSIO N ES DEL SDK DATO S

>=3.0.100 Indica si la salida se redirigió (true o false).

>=3.0.100 En un bloqueo de CLI/SDK, el tipo de excepción y su


seguimiento de pila (solo el código de CLI/SDK se incluye en el
seguimiento de la pila enviado). Para más información, vea
Telemetría de la excepción de bloqueo de SDK/CLI de .NET
Core recopilada.

Opciones recopiladas
Determinados comandos envían datos adicionales. Un subconjunto de comandos envía el primer argumento:

C O M A N DO DATO S DEL P RIM ER A RGUM EN TO EN VIA DO S

dotnet help <arg> Se está consultando la ayuda del comando.

dotnet new <arg> Nombre de la plantilla (con hash).

dotnet add <arg> La palabra package o reference .

dotnet remove <arg> La palabra package o reference .

dotnet list <arg> La palabra package o reference .

dotnet sln <arg> La palabra add , list o remove .

dotnet nuget <arg> La palabra delete , locals o push .

Un subconjunto de comandos envía las opciones seleccionadas, si se usan, junto con sus valores:

O P C IÓ N C O M A N DO S

--verbosity Todos los comandos

--language dotnet new

--configuration dotnet build , dotnet clean , dotnet publish ,


dotnet run , dotnet test

--framework dotnet build , dotnet clean , dotnet publish ,


dotnet run , dotnet test , dotnet vstest

--runtime dotnet build , dotnet publish

--platform dotnet vstest

--logger dotnet vstest

--sdk-package-version dotnet migrate

A excepción de --verbosity y --sdk-package-version , se aplica un algoritmo hash a los demás valores a partir del
SDK de .NET Core 2.1.100.

Telemetría de la excepción de bloqueo de SDK/CLI de .NET Core


recopilada
Si el SDK/CLI de .NET Core se bloquea, se recopila el nombre de la excepción y el seguimiento de la pila del código
de CLI/SDK. Esta información se recopila para evaluar los problemas y mejorar la calidad del SDK de .NET Core y la
CLI. En este artículo se proporciona información sobre los datos que se recopilan. También se ofrecen sugerencias
para que los usuarios que compilan su propia versión del SDK de .NET Core eviten la divulgación involuntaria de
información personal o confidencial.
Tipos de datos recopilados
El CLI de .NET Core recopila información solo de las excepciones de CLI/SDK, pero no de las excepciones de la
aplicación. Los datos recopilados contienen el nombre de la excepción y el seguimiento de la pila. Este seguimiento
de la pila es del código de CLI/SDK.
En este ejemplo se muestra el tipo de datos que se recopilan:

System.IO.IOException
at System.ConsolePal.WindowsConsoleStream.Write(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.StreamWriter.Flush(Boolean flushStream, Boolean flushEncoder)
at System.IO.StreamWriter.Write(Char[] buffer)
at System.IO.TextWriter.WriteLine()
at System.IO.TextWriter.SyncTextWriter.WriteLine()
at Microsoft.DotNet.Cli.Utils.Reporter.WriteLine()
at Microsoft.DotNet.Tools.Run.RunCommand.EnsureProjectIsBuilt()
at Microsoft.DotNet.Tools.Run.RunCommand.Execute()
at Microsoft.DotNet.Tools.Run.RunCommand.Run(String[] args)
at Microsoft.DotNet.Cli.Program.ProcessArgs(String[] args, ITelemetry telemetryClient)
at Microsoft.DotNet.Cli.Program.Main(String[] args)

Evasión de la divulgación involuntaria de información


Los colaboradores de .NET Core y cualquier otro usuario que ejecute una versión del SDK de .NET Core compilada
por ellos mismos deben tener en cuenta la ruta de acceso al código fuente del SDK. Si se produce un bloqueo
mientras se usa un SDK de .NET Core que es una compilación de depuración personalizada o está configurado con
archivos de símbolos de compilación personalizados, la ruta de acceso del archivo de origen del SDK desde el
equipo de compilación se recopila como parte del seguimiento de la pila y no tiene un algoritmo hash.
Por este motivo, las compilaciones personalizadas del SDK de .NET Core no deben almacenarse en directorios
cuyos nombres de ruta de acceso expongan información personal o confidencial.

Vea también
Datos de telemetría de la CLI de .NET Core
Fuente de referencia de telemetría (repositorio dotnet/sdk)
Acceso con privilegios elevados para comandos de
dotnet
13/04/2020 • 8 minutes to read • Edit Online

Los procedimientos recomendados de desarrollo de software sirven de guía a los desarrolladores para escribir
software que requiera la menor cantidad de privilegios. Sin embargo, algunos programas, como las herramientas
de supervisión de rendimiento, requieren permiso del administrador debido a las reglas del sistema operativo. En
las siguientes instrucciones se describen los escenarios admitidos para escribir dicho software con .NET Core.
Los siguientes comandos se pueden ejecutar con privilegios elevados:
Comandos dotnet tool , como dotnet tool install.
dotnet run --no-build
dotnet-core-uninstall

No se recomienda ejecutar otros comandos con privilegios elevados. En concreto, no se recomienda usar
privilegios elevados con los comandos que usa MSBuild, como dotnet restore, dotnet build y dotnet run. Los
problemas de administración de permisos son el principal problema cuando un usuario cambia varias veces entre
una cuenta restringida y otra raíz después de emitir comandos de dotnet. Como usuario restringido, es posible que
no tenga acceso al archivo generado por un usuario raíz. Hay maneras de resolver esta situación, pero, para
empezar, no es necesario que surjan.
Puede ejecutar comandos como raíz siempre y cuando no cambie repetidas veces entre una cuenta restringida y
otra raíz. Por ejemplo, los contenedores de Docker se ejecutan como raíz de forma predeterminada, por lo que
tienen esta característica.

Instalación de herramienta global


Las instrucciones siguientes muestran la manera recomendada para instalar, ejecutar y desinstalar las herramientas
de .NET Core que requieren privilegios elevados para ejecutarse.
Windows
Linux
macOS
Instalación de la herramienta
Si la carpeta %ProgramFiles%\dotnet-tools ya existe, siga este procedimiento para comprobar si el grupo "Usuarios"
tiene permiso para escribir o modificar ese directorio:
Haga clic con el botón derecho en la carpeta %ProgramFiles%\dotnet-tools y seleccione Propiedades . Se abrirá
el cuadro de diálogo Propiedades comunes .
Seleccione la pestaña Seguridad . En Nombres de grupos o usuarios , compruebe si el grupo "Usuarios"
tiene permiso para escribir o modificar el directorio.
Si el grupo "Usuarios" puede escribir o modificar el directorio, al instalar las herramientas, use otro nombre de
directorio de dotnet-tools.
Para instalar las herramientas, ejecute el siguiente comando en un símbolo del sistema con privilegios elevados.
Creará la carpeta dotnet-tools durante la instalación.
dotnet tool install PACKAGEID --tool-path "%ProgramFiles%\dotnet-tools".

Ejecución de la herramienta global


Opción 1 Use la ruta de acceso completa con privilegios elevados:

"%ProgramFiles%\dotnet-tools\TOOLCOMMAND"

Opción 2 Agregue la carpeta recién creada a %Path% . Solo necesita realizar esta operación una vez.

setx Path "%Path%;%ProgramFiles%\dotnet-tools\"

Y ejecute con:

TOOLCOMMAND

Desinstalación de la herramienta global


En un símbolo del sistema con privilegios elevados, escriba el siguiente comando:

dotnet tool uninstall PACKAGEID --tool-path "%ProgramFiles%\dotnet-tools"

Herramientas locales
Las herramientas locales se limitan al árbol de subdirectorio, por usuario. Cuando se ejecutan con privilegios
elevados, las herramientas locales comparten un entorno de usuario con privilegios restringidos en el entorno con
privilegios elevados. En Linux y macOS, esto da como resultado archivos que establecen con acceso de solo usuario
raíz. Si el usuario cambia a una cuenta restringida, el usuario ya no podrá acceder a los archivos ni escribir en ellos.
Por tanto, no se recomienda instalar las herramientas que necesiten permisos elevados como herramientas locales.
En su lugar, use la opción --tool-path y las instrucciones anteriores para las herramientas globales.

Elevación durante el desarrollo


Durante el desarrollo, puede que necesite acceso con privilegios elevados para probar la aplicación. Este escenario
es común para las aplicaciones de IoT, por ejemplo. Se recomienda que compile la aplicación sin la elevación y, a
continuación, la ejecute con privilegios elevados. Hay unos pocos patrones, como los siguientes:
Uso de archivo ejecutable generado (proporciona el mejor rendimiento de inicio):

dotnet build
sudo ./bin/Debug/netcoreapp3.0/APPLICATIONNAME

Mediante el comando dotnet run con la marca —no-build para evitar que se generen nuevos archivos
binarios:

dotnet build
sudo dotnet run --no-build

Vea también
Información general sobre las herramientas globales de .NET Core
Cómo habilitar la finalización con tabulación para la
CLI de .NET Core
16/09/2020 • 4 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 2.1 y versiones posteriores
En este artículo se describe cómo configurar la finalización con tabulación para tres shells: PowerShell, Bash y zsh.
En el caso de otros shells, consulte la documentación para saber cómo configurar la finalización con tabulación.
Una vez configurada, para desencadenar la finalización con tabulación para la CLI de .NET Core, escriba un
comando dotnet en el shell y, después, presione el tabulador. La línea de comandos que se envía al comando
dotnet complete y los resultados se procesan mediante el shell. Puede probar los resultados sin habilitar la
finalización con tabulación si envía algo directamente al comando dotnet complete . Por ejemplo:

> dotnet complete "dotnet a"


add
clean
--diagnostics
migrate
pack

Si ese comando no funciona, asegúrese de que está instalado el SDK de .NET Core 2.0 o una versión superior. Si
está instalado, pero el comando sigue sin funcionar, asegúrese de que dotnet se resuelva como mínimo en la
versión 2.0 del SDK de .NET Core. Use el comando dotnet --version para ver en qué versión de dotnet se
resuelve la ruta de acceso actual. Para obtener más información, vea Selección de la versión de .NET Core que se va
a usar.
Ejemplos
Estos son algunos ejemplos de lo que proporciona la finalización con tabulación:

EN T RA DA SE C O N VIERT E EN P O RQ UE

dotnet a ⇥ dotnet add add es el primer subcomando, por


orden alfabético.

dotnet add p ⇥ dotnet add --help La finalización con tabulación hace


coincidir las subcadenas y --help
aparece primero alfabéticamente.

dotnet add p ⇥⇥ dotnet add package Al presionar la tecla Tab una segunda
vez aparece la siguiente sugerencia.

dotnet add package Microsoft ⇥ dotnet add package Los resultados se devuelven por orden
Microsoft.ApplicationInsights.Web alfabético.

dotnet remove reference ⇥ dotnet remove reference La finalización con tabulación es


..\..\src\OmniSharp.DotNet\OmniSharp.DotNet.csproj
compatible con archivos de proyecto.

PowerShell
Para agregar finalización con tabulación a PowerShell para la CLI de .NET Core, cree o edite el perfil almacenado
en la variable $PROFILE . Para obtener más información, vea Cómo crear el perfil y Los perfiles y la directiva de
ejecución.
Agregue el código siguiente al perfil:

# PowerShell parameter completion shim for the dotnet CLI


Register-ArgumentCompleter -Native -CommandName dotnet -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition)
dotnet complete --position $cursorPosition "$wordToComplete" | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}

Bash
Para agregar finalización con tabulación al shell de bash para la CLI de .NET Core, agregue el código siguiente al
archivo .bashrc :

# bash parameter completion for the dotnet CLI

_dotnet_bash_complete()
{
local word=${COMP_WORDS[COMP_CWORD]}

local completions
completions="$(dotnet complete --position "${COMP_POINT}" "${COMP_LINE}" 2>/dev/null)"
if [ $? -ne 0 ]; then
completions=""
fi

COMPREPLY=( $(compgen -W "$completions" -- "$word") )


}

complete -f -F _dotnet_bash_complete dotnet

zsh
Para agregar finalización con tabulación al shell de zsh para la CLI de .NET Core, agregue el código siguiente al
archivo .zshrc :

# zsh parameter completion for the dotnet CLI

_dotnet_zsh_complete()
{
local completions=("$(dotnet complete "$words")")

reply=( "${(ps:\n:)completions}" )
}

compctl -K _dotnet_zsh_complete dotnet


Uso de .NET Core SDK y herramientas de integración
continua (CI)
16/09/2020 • 16 minutes to read • Edit Online

En este documento se describe el uso del SDK de .NET Core y sus herramientas en un servidor de compilación. El
conjunto de herramientas de .NET Core funciona tanto de forma interactiva, donde un desarrollador escribe
comandos en un símbolo del sistema, como de manera automática, donde un servidor de integración continua (CI)
ejecuta un script de compilación. Los comandos, las opciones, las entradas y las salidas son los mismos, y solo lo
que el usuario especifica sirve para adquirir las herramientas y un sistema para compilar la aplicación. Este
documento se centra en escenarios de adquisición de herramientas de integración continua, donde además se
ofrecen recomendaciones sobre cómo diseñar y estructurar los scripts de compilación.

Opciones de instalación para los servidores de compilación de CI


Uso de instaladores nativos
Los instaladores nativos están disponibles para macOS, Linux y Windows. Los instaladores requieren acceso de
administrador (sudo) para el servidor de compilación. El instalador nativo ofrece la ventaja de que instala todas las
dependencias nativas necesarias para la ejecución de las herramientas. Además, los instaladores nativos instalan el
SDK en todo el sistema.
Los usuarios de macOS deben usar los instaladores PKG. En Linux, se ofrece la posibilidad de usar un
administrador de paquetes basado en fuentes, como apt-get para Ubuntu o yum para CentOS, o de usar los
mismos paquetes (DEB o RPM). En Windows, utilice el instalador MSI.
Los archivos binarios estables más recientes se encuentran en Descargas de .NET. Si desea utilizar las herramientas
de la última versión preliminar, que posiblemente sean inestables, use los vínculos proporcionados en el
repositorio de GitHub dotnet/core-sdk. Para las distribuciones de Linux, se encuentran disponibles los archivos
tar.gz (conocidos también como tarballs ); use los scripts de instalación dentro de los archivos para instalar
.NET Core.
Uso del script del instalador
El uso del script del instalador permite la instalación sin derechos administrativos en el servidor de compilación y
una sencilla automatización para obtener las herramientas. El script se encarga de descargar las herramientas y
extraerlas en una ubicación predeterminada o especificada para su uso. También puede especificar la versión de las
herramientas que desea instalar y si prefiere instalar el SDK completo o solo el runtime compartido.
El script del instalador se puede automatizar para que se ejecute al principio de la compilación, a fin de obtener e
instalar la versión deseada del SDK. La versión deseada se corresponde con cualquier versión del SKD que los
proyectos necesitan para la compilación. El script permite instalar el SDK en un directorio local del servidor,
ejecutar las herramientas desde la ubicación de instalación y limpiar después de la compilación, o bien dejar que el
servicio de CI realice dicha limpieza. Esto permite encapsular y aislar todo el proceso de compilación. La referencia
del script de instalación se encuentra en el artículo dotnet-install.

NOTE
Azure DevOps Ser vices
Cuando se utiliza el script del instalador, las dependencias nativas no se instalan automáticamente. Debe instalarlas en caso
de el sistema operativo no las incluya. Para obtener más información, vea Dependencias y requisitos de .NET Core.
Ejemplos de configuración de CI
En esta sección se explica un procedimiento de instalación manual con un script de PowerShell o de Bash, además
de incluir una descripción de varias soluciones de CI de software como servicio (SaaS). Las soluciones de CI de
SaaS tratadas son Travis CI, AppVeyor y Azure Pipelines.
Instalación manual
Cada servicio de SaaS tiene sus propios métodos para crear y configurar un proceso de compilación. Si usa una
solución de SaaS distinta a las que indican o necesita realizar alguna personalización aparte de la compatibilidad
preconfigurada, debe realizar al menos alguna configuración manual.
En general, para realizar una instalación manual, es necesario que adquiera una versión de las herramientas o las
últimas compilaciones nocturnas de las herramientas y que, después, ejecute el script de compilación. Puede usar
un script de PowerShell o de Bash para orquestar los comandos de .NET Core o utilizar un archivo del proyecto en
el que se describa el proceso de compilación. En la sección de orquestación se ofrece más información sobre estas
opciones.
Después de crear un script que realiza una instalación manual del servidor de compilación de CI, úselo en el
equipo de desarrollo para compilar el código de forma local con fines de pruebas. Cuando confirme que el script
se ejecuta de forma correcta en el entorno local, impleméntelo en el servidor de compilación de CI. Un script de
PowerShell relativamente sencillo demuestra cómo obtener el SDK de NET Core e instalarlo en un servidor de
compilación de Windows:
$ErrorActionPreference="Stop"
$ProgressPreference="SilentlyContinue"

# $LocalDotnet is the path to the locally-installed SDK to ensure the


# correct version of the tools are executed.
$LocalDotnet=""
# $InstallDir and $CliVersion variables can come from options to the
# script.
$InstallDir = "./cli-tools"
$CliVersion = "1.0.1"

# Test the path provided by $InstallDir to confirm it exists. If it


# does, it's removed. This is not strictly required, but it's a
# good way to reset the environment.
if (Test-Path $InstallDir)
{
rm -Recurse $InstallDir
}
New-Item -Type "directory" -Path $InstallDir

Write-Host "Downloading the CLI installer..."

# Use the Invoke-WebRequest PowerShell cmdlet to obtain the


# installation script and save it into the installation directory.
Invoke-WebRequest `
-Uri "https://dot.net/v1/dotnet-install.ps1" `
-OutFile "$InstallDir/dotnet-install.ps1"

Write-Host "Installing the CLI requested version ($CliVersion) ..."

# Install the SDK of the version specified in $CliVersion into the


# specified location ($InstallDir).
& $InstallDir/dotnet-install.ps1 -Version $CliVersion `
-InstallDir $InstallDir

Write-Host "Downloading and installation of the SDK is complete."

# $LocalDotnet holds the path to dotnet.exe for future use by the


# script.
$LocalDotnet = "$InstallDir/dotnet"

# Run the build process now. Implement your build script here.

Debe proporcionar la implementación para el proceso de compilación al final del script. El script adquiere las
herramientas y, después, ejecuta el proceso de compilación. En los equipos UNIX, el siguiente script de Bash realiza
las acciones descritas en el script de PowerShell de forma similar:
#!/bin/bash
INSTALLDIR="cli-tools"
CLI_VERSION=1.0.1
DOWNLOADER=$(which curl)
if [ -d "$INSTALLDIR" ]
then
rm -rf "$INSTALLDIR"
fi
mkdir -p "$INSTALLDIR"
echo Downloading the CLI installer.
$DOWNLOADER https://dot.net/v1/dotnet-install.sh > "$INSTALLDIR/dotnet-install.sh"
chmod +x "$INSTALLDIR/dotnet-install.sh"
echo Installing the CLI requested version $CLI_VERSION. Please wait, installation may take a few minutes.
"$INSTALLDIR/dotnet-install.sh" --install-dir "$INSTALLDIR" --version $CLI_VERSION
if [ $? -ne 0 ]
then
echo Download of $CLI_VERSION version of the CLI failed. Exiting now.
exit 0
fi
echo The CLI has been installed.
LOCALDOTNET="$INSTALLDIR/dotnet"
# Run the build process now. Implement your build script here.

Travis CI
Puede configurar Travis CI para instalar el SDK de .NET Core con el lenguaje csharp y la clave dotnet . Para más
información, consulte los documentos oficiales de Travis CI en Building a C#, F#, or Visual Basic Project
(Compilación de un proyecto de C#, F# o Visual Basic). Al acceder a la información de Travis CI, observará que el
identificador de lenguaje language: csharp de cuyo mantenimiento se encarga la comunidad funciona con todos
los lenguajes de .NET, incluidos F# y Mono.
Travis CI se ejecuta tanto en trabajos de macOS como de Linux en una matriz de compilación, donde debe
especificar una combinación de runtime, entorno y exclusiones/inclusiones para aceptar las combinaciones de
compilación de la aplicación. Para más información, vea el artículo Customizing the Build (Personalización de la
compilación) en la documentación de Travis CI. Las herramientas basadas en MSBuild incluyen los runtimes LTS
(1.0.x) y Current (1.1.x) en el paquete; por tanto, cuando instala el SDK, recibe todo lo que necesita para la
compilación.
AppVeyor
AppVeyor instala el SDK de .NET Core 1.0.1 con la imagen de trabajo de compilación de Visual Studio 2017 . Hay
disponibles otras imágenes de compilación con distintas versiones del SDK de .NET Core. Para más información,
consulte el ejemplo appveyor.yml y el artículo Build worker images (Imágenes de trabajo de compilación) en los
documentos de AppVeyor.
Los archivos binarios del SDK de .NET Core se descargan y descomprimen en un subdirectorio con la utilización
del script de instalación y, después, se agregan a la variable de entorno PATH . Agregue una matriz de compilación
para ejecutar las pruebas de integración con varias versiones del SDK de .NET Core:

environment:
matrix:
- CLI_VERSION: 1.0.1
- CLI_VERSION: Latest

install:
# See appveyor.yml example for install script

Azure DevOps Services


Configure Azure DevOps Services para compilar proyectos de .NET Core con alguno de estos enfoques:
1. Ejecute el script del paso de instalación manual con sus comandos.
2. Cree una compilación compuesta de varias tareas de compilación integradas en Azure DevOps Services que
están configuradas para usar herramientas de .NET Core.
Ambas soluciones son válidas. Con la utilización de un script de instalación manual, puede controlar la versión de
las herramientas que recibe, ya que las descarga como parte de la compilación. La compilación se ejecuta desde un
script que debe crear. En este artículo solo se trata la opción manual. Para más información sobre cómo elaborar
una compilación con las tareas de compilación de Azure DevOps Services, consulte la documentación de Azure
Pipelines.
Para usar un script de instalación manual en Azure DevOps Services, cree una definición de compilación y
especifique el script que va a ejecutar para el paso de compilación. Esto se realiza en la interfaz de usuario de Azure
DevOps Services:
1. Empiece por crear una definición de compilación. Cuando llegue a la pantalla en la que se ofrece la opción
de definir el tipo de compilación que desea crear, seleccione la opción Vacío .

2. Después de configurar el repositorio que va a compilar, se le remite a las definiciones de compilación.


Seleccione Agregar paso de compilación :
3. Se abre el Catálogo de tareas . El catálogo contiene tareas que se utilizan en la compilación. Como ya tiene
un script, seleccione el botón Agregar en PowerShell: Ejecute un script de PowerShell .

4. Configure el paso de compilación. Agregue el script desde el repositorio que se va a compilar:

Orquestación de la compilación
En la mayor parte de este documento se describe cómo adquirir las herramientas de .NET Core y configurar varios
servicios de CI sin ofrecer información sobre cómo orquestar o compilar realmente el código con .NET Core. Las
opciones para estructurar el proceso de compilación dependen de muchos factores que no se pueden tratar aquí
en términos generales. Para más información sobre cómo orquestar las compilaciones con cada tecnología,
explore los recursos y los ejemplos que se proporciona en los conjuntos de documentación de Travis CI, AppVeyor
y Azure Pipelines.
Dos enfoques generales que se aplican para estructurar el proceso de compilación del código de .NET Core con
herramientas de .NET Core consisten en utilizar directamente MSBuild o en usar los comandos de la línea de
comandos de .NET Core. El enfoque que debe adoptar depende de lo cómo que se sienta con cada uno de ellos y
de los inconvenientes que presente su complejidad. MSBuild ofrece la posibilidad de expresar el proceso de
compilación como tareas y objetivos, pero presenta la complejidad añadida de tener que aprender la sintaxis del
archivo de proyecto de MSBuild. Quizá sea más sencillo usar las herramientas de línea de comandos de .NET Core,
pero, en este caso, es necesario escribir la lógica de orquestación en un lenguaje de scripting como bash o
PowerShell.

Vea también
Descargas de .NET: Linux
Desarrollo de bibliotecas con la CLI de .NET Core
16/09/2020 • 22 minutes to read • Edit Online

En este artículo se explica cómo escribir bibliotecas para .NET con la CLI de .NET Core. La CLI proporciona una
experiencia eficaz y de bajo nivel que funciona en todos los SO compatibles. De todos modos puede seguir
compilando bibliotecas con Visual Studio; si esa es la experiencia de su preferencia, consulte la guía sobre Visual
Studio.

Requisitos previos
Necesita la CLI y el SDK de .NET Core que están instalados en la máquina.
En las secciones de este documento que se refieren a las versiones de .NET Framework, necesita tener instalado
.NET Framework en una máquina con Windows.
Además, si desea admitir destinos de .NET Framework anteriores, deberá instalar paquetes de destino o
desarrollador desde la página de archivos de descarga de .NET. Consulte la tabla siguiente:

VERSIÓ N DE . N ET F RA M EW O RK Q UÉ DEB E DESC A RGA R

4.6.1 Paquete de compatibilidad de .NET Framework 4.6.1

4.6 Paquete de compatibilidad de .NET Framework 4.6

4.5.2 Paquete de desarrollador de .NET Framework 4.5.2

4.5.1 Paquete de desarrollador de .NET Framework 4.5.1

4.5 Kit de desarrollo de software de Windows para Windows 8

4.0 Windows SDK para Windows 7 y .NET Framework 4

2.0, 3.0 y 3.5 Entorno de ejecución de .NET Framework 3.5 SP1 (o una
versión posterior a Windows 8)

Estándar .NET como destino


Si no conoce .NET Standard, consulte el tema .NET Standard para más información.
En ese artículo podrá ver una tabla que asigna las versiones de .NET Standard a diversas implementaciones:

. N ET
STA N DA
RD 1. 0 1. 1 1. 2 1. 3 1. 4 1. 5 1. 6 2. 0 2. 1

.NET 1.0 1.0 1.0 1.0 1.0 1.0 1.0 2.0 3.0
Core

.NET 4.5 4.5 4.5.1 4.6 4.6.1 4.6.1 2 4.6.1 2 4.6.1 2 N/A3
Framew
ork 1
. N ET
STA N DA
RD 1. 0 1. 1 1. 2 1. 3 1. 4 1. 5 1. 6 2. 0 2. 1

Mono 4.6 4.6 4.6 4.6 4.6 4.6 4.6 5.4 6.4

Xamarin 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.14 12.16
.iOS

Xamarin 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.8 5.16
.Mac

Xamarin 7.0 7.0 7.0 7.0 7.0 7.0 7.0 8.0 10.0
.Android

Platafor 10.0 10.0 10.0 10.0 10.0 10.0.16 10.0.16 10.0.16 TBD
ma 299 299 299
universa
l de
Window
s

Unity 2018.1 2018.1 2018.1 2018.1 2018.1 2018.1 2018.1 2018.1 TBD

1 Las versiones que se muestran de .NET Framework se aplican al SDK de .NET Core 2.0 y versiones posteriores de la herramienta. Las versiones
anteriores usaban una asignación diferente para .NET Standard 1.5 y versiones posteriores. Puede descargar herramientas para .NET Core para Visual
Studio 2015 si no se puede actualizar a Visual Studio 2017 ni a una versión posterior.

2 Las versiones siguientes representan las reglas que usa NuGet para determinar si una determinada biblioteca de .NET Standard es aplicable. Aunque
NuGet considera a .NET Framework 4.6.1 compatible con .NET Standard (versiones 1.5 a 2.0) hay varios problemas con el consumo de bibliotecas de
.NET Standard que se compilaron para esas versiones desde proyectos de .NET Framework 4.6.1. Para los proyectos de .NET Framework que tengan
que usar estas bibliotecas, se recomienda actualizar el proyecto para destinarlo a .NET Framework 4.7.2 o una versión posterior.

3 .NET Framework no admitirá .NET Standard 2.1 o versiones posteriores. Para más detalles, vea el anuncio de .NET Standard 2.1.

Las columnas representan las versiones de .NET Standard. Cada celda de encabezado es un vínculo a un
documento que muestra qué API se han agregado en esa versión de .NET Standard.
Las filas representan las diferentes implementaciones de .NET.
El número de versión de cada celda indica la versión mínima de la implementación que necesitará para tener
como destino dicha versión de .NET Standard.
Para ver una tabla interactiva, consulte Versiones de .NET Standard.
Este es el significado de la tabla para crear una biblioteca:
La versión de .NET Standard que elija será un equilibrio entre el acceso a las API más recientes y la capacidad de
apuntar a más implementaciones .NET y más versiones de .NET Standard. Puede controlar el intervalo de
versiones y plataformas de destino si elige una versión de netstandardX.X (donde X.X es un número de
versión) y la agrega al archivo del proyecto ( .csproj o .fsproj ).
En función de sus necesidades, tiene tres opciones principales cuando el destino es .NET Standard.
1. Puede usar la versión predeterminada de .NET Standard que se proporciona con plantillas, netstandard1.4
, que le permite acceder a la mayoría de API en .NET Standard mientras sigue siendo compatible con UWP,
.NET Framework 4.6.1 y .NET Standard 2.0.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>
</Project>

2. Puede usar una versión anterior o posterior de .NET Standard modificando el valor en el nodo
TargetFramework de su archivo del proyecto.

Las versiones del estándar .NET son compatibles con versiones anteriores. Eso significa que las bibliotecas
netstandard1.0 se pueden ejecutar en plataformas netstandard1.1 y versiones superiores. Sin embargo,
no hay compatibilidad con versiones posteriores. Las plataformas de .NET Standard anteriores no pueden
hacer referencia a las posteriores. Esto significa que las bibliotecas netstandard1.0 no pueden hacer
referencia a las bibliotecas que tienen como destino netstandard1.1 o una versión superior. Seleccione la
versión estándar que tiene la combinación adecuada de compatibilidad con API y plataformas que
necesita. Por ahora le recomendamos netstandard1.4 .
3. Si desea tener como destino .NET Framework versión 4.0 o inferior, o bien desea usar una API disponible
en .NET Framework pero no disponible en el estándar .NET (por ejemplo, System.Drawing ), lea las
secciones siguientes y sepa cómo tener compatibilidad con múltiples versiones.

Uso de .NET Framework como destino


NOTE
En estas instrucciones se supone que tiene instalado .NET Framework en su máquina. Consulte los requisitos previos para
instalar las dependencias.

Tenga en cuenta que algunas de las versiones de .NET Framework que se usan aquí ya no cuentan con soporte
técnico. Consulte las P+F sobre la directiva del ciclo de vida de soporte técnico de .NET Framework sobre las
versiones sin soporte técnico.
Si quiere llegar a la mayor cantidad posible de desarrolladores y proyectos, use .NET Framework 4.0 como el
destino de línea base. Para tener .NET Framework como destino, comience utilizando el moniker de la plataforma
de destino (TFM) correcto que corresponda a la versión de .NET Framework que desea admitir.

VERSIÓ N DE . N ET F RA M EW O RK T FM

.NET Framework 2.0 net20

.NET Framework 3.0 net30

.NET Framework 3,5 net35

.NET Framework 4.0 net40

.NET Framework 4.5 net45

.NET Framework 4.5.1 net451

.NET Framework 4.5.2 net452


VERSIÓ N DE . N ET F RA M EW O RK T FM

.NET Framework 4.6 net46

.NET Framework 4.6.1 net461

.NET Framework 4.6.2 net462

.NET Framework 4.7 net47

.NET Framework 4.8 net48

Después, inserte el TFM en la sección TargetFramework de su archivo del proyecto. El siguiente es un ejemplo de
cómo podría escribir una biblioteca que tenga como destino .NET Framework 4.0:

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net40</TargetFramework>
</PropertyGroup>
</Project>

Y listo. Aunque esto solo hace la compilación para .NET Framework 4, puede usar la biblioteca en las versiones
más recientes de .NET Framework.

Cómo lograr la compatibilidad con varias versiones


NOTE
Las instrucciones siguientes suponen que tiene instalado .NET Framework en su máquina. Consulte la sección de requisitos
previos para información sobre las dependencias que debe instalar y dónde descargarlas.

Es posible que deba tener como destino versiones anteriores de .NET Framework cuando el proyecto admite .NET
Framework y .NET Core. En este escenario, si desea usar API más recientes y construcciones de lenguaje para los
destinos más recientes, use las directivas #if en el código. También es posible que tenga que agregar distintos
paquetes y dependencias para cada plataforma que tiene como destino para incluir las distintas API necesarias
para cada caso.
Por ejemplo, digamos que tiene una biblioteca que realiza operaciones de red a través de HTTP. En el estándar
.NET y .NET Framework versión 4.5 o superiores, puede usar la clase HttpClient del espacio de nombres
System.Net.Http . Sin embargo, las versiones anteriores de .NET Framework no tienen la clase HttpClient , por lo
que, en su lugar, podría usar la clase WebClient del espacio de nombres System.Net para esas versiones.
Su archivo del proyecto podría tener la siguiente apariencia:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.4;net40;net45</TargetFrameworks>
</PropertyGroup>

<!-- Need to conditionally bring in references for the .NET Framework 4.0 target -->
<ItemGroup Condition="'$(TargetFramework)' == 'net40'">
<Reference Include="System.Net" />
</ItemGroup>

<!-- Need to conditionally bring in references for the .NET Framework 4.5 target -->
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="System.Net.Http" />
<Reference Include="System.Threading.Tasks" />
</ItemGroup>
</Project>

Observará tres cambios principales aquí:


1. El nodo TargetFramework se ha reemplazado por TargetFrameworks , y se expresan tres TFM dentro.
2. Existe un nodo <ItemGroup> para el destino net40 que se dirige a una referencia de .NET Framework.
3. Existe un nodo <ItemGroup> para el destino net45 que se dirige a dos referencias de .NET Framework.

El sistema de compilación conoce los siguientes símbolos del preprocesador que se usan en las directivas #if :

VERSIO N ES DE . N ET F RA M EW O RK DE DEST IN O SÍM B O LO S

.NET Framework NETFRAMEWORK , NET20 , NET35 , NET40 , NET45 , NET451 ,


NET452 , NET46 , NET461 , NET462 , NET47 , NET471 ,
NET472 , NET48

.NET Standard NETSTANDARD , NETSTANDARD1_0 , NETSTANDARD1_1 ,


NETSTANDARD1_2 , NETSTANDARD1_3 , NETSTANDARD1_4 ,
NETSTANDARD1_5 , NETSTANDARD1_6 , NETSTANDARD2_0 ,
NETSTANDARD2_1

.NET Core NETCOREAPP , NETCOREAPP1_0 , NETCOREAPP1_1 ,


NETCOREAPP2_0 , NETCOREAPP2_1 , NETCOREAPP2_2 ,
NETCOREAPP3_0 , NETCOREAPP3_1

Aquí se muestra un ejemplo en el que se usa la compilación condicional por destino:


using System;
using System.Text.RegularExpressions;
#if NET40
// This only compiles for the .NET Framework 4 targets
using System.Net;
#else
// This compiles for all other targets
using System.Net.Http;
using System.Threading.Tasks;
#endif

namespace MultitargetLib
{
public class Library
{
#if NET40
private readonly WebClient _client = new WebClient();
private readonly object _locker = new object();
#else
private readonly HttpClient _client = new HttpClient();
#endif

#if NET40
// .NET Framework 4.0 does not have async/await
public string GetDotNetCount()
{
string url = "https://www.dotnetfoundation.org/";

var uri = new Uri(url);

string result = "";

// Lock here to provide thread-safety.


lock(_locker)
{
result = _client.DownloadString(uri);
}

int dotNetCount = Regex.Matches(result, ".NET").Count;

return $"Dotnet Foundation mentions .NET {dotNetCount} times!";


}
#else
// .NET 4.5+ can use async/await!
public async Task<string> GetDotNetCountAsync()
{
string url = "https://www.dotnetfoundation.org/";

// HttpClient is thread-safe, so no need to explicitly lock here


var result = await _client.GetStringAsync(url);

int dotNetCount = Regex.Matches(result, ".NET").Count;

return $"dotnetfoundation.org mentions .NET {dotNetCount} times in its HTML!";


}
#endif
}
}

Si crea este proyecto con dotnet build , observará tres directorios en la carpeta bin/ :

net40/
net45/
netstandard1.4/
Cada uno de ellos contiene los archivos .dll para cada destino.

Prueba de las bibliotecas en .NET Core


Es importante poder probar las plataformas. Puede usar xUnit o MSTest de fábrica. Ambos son perfectamente
adecuados para las pruebas unitarias de su biblioteca en .NET Core. Cómo configurar la solución con proyectos
de prueba dependerá de la estructura de la solución. En el ejemplo siguiente se presupone que los directorios de
origen y de prueba residen en el mismo directorio de nivel superior.

NOTE
Esto usa algunos comandos de la CLI de .NET Core. Vea dotnet new y dotnet sln para obtener más información.

1. Configure la solución. Puede hacerlo con los siguientes comandos:

mkdir SolutionWithSrcAndTest
cd SolutionWithSrcAndTest
dotnet new sln
dotnet new classlib -o MyProject
dotnet new xunit -o MyProject.Test
dotnet sln add MyProject/MyProject.csproj
dotnet sln add MyProject.Test/MyProject.Test.csproj

Esto creará proyectos y se vincularán conjuntamente en una solución. Su directorio para


SolutionWithSrcAndTest debe tener el siguiente aspecto:

/SolutionWithSrcAndTest
|__SolutionWithSrcAndTest.sln
|__MyProject/
|__MyProject.Test/

2. Vaya al directorio del proyecto de prueba y agregue una referencia a MyProject.Test desde MyProject .

cd MyProject.Test
dotnet add reference ../MyProject/MyProject.csproj

3. Restaurar paquetes y crear proyectos:

dotnet restore
dotnet build

No es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que
necesitan que se produzca una restauración, como dotnet new , dotnet build , dotnet run , dotnet test ,
dotnet publish y dotnet pack . Para deshabilitar la restauración implícita, use la opción --no-restore .

El comando dotnet restore sigue siendo válido en algunos escenarios donde tiene sentido realizar una
restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los
sistemas de compilación que necesitan controlar explícitamente cuándo se produce la restauración.
Para obtener información sobre cómo administrar fuentes de NuGet, vea la documentación de
dotnet restore .

4. Compruebe que xUnit se ejecuta mediante la ejecución del comando dotnet test . Si decide usar MSTest,
entonces debe ejecutarse en su lugar el ejecutor de la consola de MSTest.
Y listo. Ahora puede probar la biblioteca en todas las plataformas; para ello, use herramientas de línea de
comandos. Para seguir con las pruebas ahora que ya está todo configurado, probar la biblioteca es un proceso
muy simple:
1. Haga los cambios en la biblioteca.
2. Ejecute las pruebas desde la línea de comandos, en el directorio de prueba, con el comando dotnet test .

El código se recompilará automáticamente cuando invoque el comando dotnet test .

Uso de varios proyectos


Una necesidad en común de las bibliotecas de mayor tamaño es ubicar la funcionalidad en distintos proyectos.
Imagine que desea compilar una biblioteca que se pudiera consumir en C# y F# idiomático. Eso significaría que
los usuarios de las bibliotecas las consumirían de manera natural para C# o F#. Por ejemplo, en C#, podría
consumir la biblioteca de la siguiente manera:

using AwesomeLibrary.CSharp;

public Task DoThings(Data data)


{
var convertResult = await AwesomeLibrary.ConvertAsync(data);
var result = AwesomeLibrary.Process(convertResult);
// do something with result
}

En F#, sería de la siguiente manera:

open AwesomeLibrary.FSharp

let doWork data = async {


let! result = AwesomeLibrary.AsyncConvert data // Uses an F# async function rather than C# async method
// do something with result
}

Escenarios de consumo similares a este significan que las API a las que se tiene acceso deben tener una
estructura distinta para C# y para F#. Un enfoque común para lograrlo es factorizar toda la lógica de una
biblioteca en un proyecto central, con los proyectos de C# y F# definiendo los niveles de API que hacen llamadas
a ese proyecto central. En el resto de la sección se usarán los siguientes nombres:
AwesomeLibrar y.Core : un proyecto central que contiene toda la lógica de la biblioteca
AwesomeLibrar y.CSharp : un proyecto con API públicas pensado para el consumo en C#
AwesomeLibrar y.FSharp : un proyecto con API públicas pensado para el consumo en F#
Puede ejecutar los siguientes comandos en su terminal para generar la misma estructura de esta guía:

mkdir AwesomeLibrary && cd AwesomeLibrary


dotnet new sln
mkdir AwesomeLibrary.Core && cd AwesomeLibrary.Core && dotnet new classlib
cd ..
mkdir AwesomeLibrary.CSharp && cd AwesomeLibrary.CSharp && dotnet new classlib
cd ..
mkdir AwesomeLibrary.FSharp && cd AwesomeLibrary.FSharp && dotnet new classlib -lang "F#"
cd ..
dotnet sln add AwesomeLibrary.Core/AwesomeLibrary.Core.csproj
dotnet sln add AwesomeLibrary.CSharp/AwesomeLibrary.CSharp.csproj
dotnet sln add AwesomeLibrary.FSharp/AwesomeLibrary.FSharp.fsproj
Esto agregará los tres proyectos anteriores y un archivo de solución que los vincula conjuntamente. Crear el
archivo de solución y vincular los proyectos le permitirá restaurar y crear proyectos desde un nivel superior.
Referencias entre proyectos
La mejor manera de hacer referencia a un proyecto es usar la CLI de .NET Core para agregar una referencia de
proyecto. Desde los directorios del proyecto AwesomeLibrar y.CSharp y AwesomeLibrar y.FSharp , puede
ejecutar el siguiente comando:

dotnet add reference ../AwesomeLibrary.Core/AwesomeLibrary.Core.csproj

Los archivos del proyecto para AwesomeLibrar y.CSharp y AwesomeLibrar y.FSharp ahora harán referencia
a AwesomeLibrar y.Core como un destino ProjectReference . Puede comprobar esto inspeccionando los
archivos del proyecto y observando lo siguiente en ellos:

<ItemGroup>
<ProjectReference Include="..\AwesomeLibrary.Core\AwesomeLibrary.Core.csproj" />
</ItemGroup>

Puede agregar esta sección a cada archivo del proyecto manualmente si prefiere no usar la CLI de .NET Core.
Estructura de una solución
Otro aspecto importante de las soluciones de varios proyectos es establecer una buena estructura de proyecto
general. Puede organizar el código de la manera que quiera, y siempre y cuando vincule cada proyecto a su
archivo de solución con dotnet sln add , podrá ejecutar dotnet restore y dotnet build en el nivel de solución.
Plantillas personalizadas para dotnet new
16/09/2020 • 18 minutes to read • Edit Online

El SDK de .NET Core cuenta con muchas plantillas ya instaladas y listas para su uso. El comando dotnet new no es
solo la forma de usar una plantilla, sino también cómo instalarlas y desinstalarlas. A partir de .NET Core 2.0, puede
crear sus propias plantillas personalizadas para cualquier tipo de proyecto, como una aplicación, un servicio, una
herramienta o una biblioteca de clases. Incluso puede crear una plantilla que genere uno o más archivos
independientes, como un archivo de configuración.
Puede instalar plantillas personalizadas desde un paquete NuGet en cualquier fuente NuGet, haciendo referencia a
un archivo .nupkg de NuGet directamente o especificando un directorio del sistema de archivos que contenga la
plantilla. El motor de plantillas ofrece características que le permiten reemplazar valores, incluir y excluir archivos
y ejecutar operaciones de procesamiento personalizadas cuando se usa la plantilla.
El motor de plantillas es de código abierto, y el repositorio de código en línea está en dotnet/templating en
GitHub. Puede encontrar más plantillas, incluidas las plantillas de terceros, en Plantillas disponibles para dotnet
new en GitHub. Para obtener información sobre cómo crear y usar plantillas personalizadas, vea Cómo crear sus
propias plantillas para dotnet new y la Wiki del repositorio de GitHub dotnet/templating.

NOTE
Los ejemplos de plantillas están disponibles en el repositorio de GitHub dotnet/dotnet-template-samples. Sin embargo,
aunque estos ejemplos son un buen recurso para aprender cómo funcionan las plantillas, el repositorio está archivado y no
recibe mantenimiento. Los ejemplos pueden no estar actualizados y ya no funcionan.

Para seguir un tutorial y crear una plantilla, vea el tutorial Creación de una plantilla personalizada para dotnet
new.
Plantillas predeterminadas .NET
Cuando instala el SDK de .NET Core, recibe más de una docena de plantillas integradas para crear proyectos y
archivos, incluidas las aplicaciones de consola, bibliotecas de clases, proyectos de prueba unitaria, aplicaciones de
ASP.NET Core (incluidos los proyectos Angular y React), y archivos de configuración. Para enumerar las plantillas
integradas, ejecute el comando dotnet new con la opción -l|--list :

dotnet new --list

Configuración
Una plantilla consta de las siguientes partes:
Archivos de origen y carpetas.
Un archivo de configuración (template.json).
Archivos de origen y carpetas
Los archivos de origen y las carpetas incluyen todos los archivos y carpetas que quiera que el motor de plantilla
use cuando se ejecuta el comando dotnet new <TEMPLATE> . El motor de plantillas está diseñado para usar
proyectos ejecutables como código fuente para generar proyectos. Esto presenta varias ventajas:
El motor de plantillas no necesita que inserte tokens especiales en su código de origen del proyecto.
Los archivos de código no son archivos especiales ni se modifican de ninguna manera para que funcionen con
el motor de plantillas. Por lo tanto, las herramientas que usa normalmente para trabajar con los proyectos
también funcionan con el contenido de la plantilla.
Compile, ejecute y depure sus proyectos de plantilla de la forma en que lo hace para cualquiera de sus otros
proyectos.
Puede crear rápidamente una plantilla de un proyecto existente simplemente agregando un archivo de
configuración ./.template.config/template.json al proyecto.
Los archivos y las carpetas que se almacenan en la plantilla no se limitan a tipos de proyectos .NET formales. Los
archivos de origen y las carpetas pueden constar de cualquier contenido que quiera crear cuando se use la
plantilla, incluso si el motor de plantillas genera solo un archivo como su salida.
Los archivos que genera la plantilla se pueden modificar según la lógica y la configuración que ha proporcionado
en el archivo de configuración template.json. El usuario puede invalidar esta configuración pasando las opciones
al comando dotnet new <TEMPLATE> . Un ejemplo común de una lógica personalizada es proporcionar un nombre
para una clase o variable en el archivo de código que se implementa mediante una plantilla.
template.json
El archivo template.json se coloca en una carpeta .template.config en el directorio raíz de la plantilla. El archivo
proporciona información de configuración al motor de plantillas. La configuración mínima necesita los miembros
que se muestran en la tabla siguiente, que es suficiente para crear una plantilla funcional.

M IEM B RO T IP O DESC RIP C IÓ N

$schema Identificador URI El esquema JSON para el archivo


template.json. Los editores que
admiten los esquemas JSON habilitan
las características de edición JSON
cuando se especifica el esquema. Por
ejemplo, Visual Studio Code necesita
este miembro para habilitar IntelliSense.
Use un valor de
http://json.schemastore.org/template
.

author cadena El autor de la plantilla.

classifications array(string) Cero o más características de la plantilla


que un usuario puede usar para buscar
la plantilla al buscarla. Las clasificaciones
también aparecen en la columna
Etiquetas cuando aparece en una lista
de plantillas que se han generado
mediante el comando
dotnet new -l|--list .

identity cadena Un nombre único para esta plantilla.

name cadena El nombre de la plantilla que los


usuarios deben ver.
M IEM B RO T IP O DESC RIP C IÓ N

shortName cadena Un nombre abreviado predeterminado


para seleccionar la plantilla que se
aplica a entornos donde el usuario
especifica el nombre de la plantilla; no
se selecciona mediante una GUI. Por
ejemplo, un nombre abreviado es útil al
usar plantillas desde un símbolo del
sistema con comandos de la CLI.

El esquema completo del archivo template.json puede encontrarse en el Almacenamiento del esquema JSON. Para
más información sobre el archivo template.json, consulte la wiki de plantillas dotnet.
Ejemplo
Por ejemplo, esta es una carpeta de plantillas que tiene dos archivos de contenido: console.cs y readme.txt. Tenga
en cuenta que existe la carpeta requerida denominada .template.config que contiene el archivo template.json.

└───mytemplate
│ console.cs
│ readme.txt

└───.template.config
template.json

El archivo template.json tiene un aspecto parecido al siguiente:

{
"$schema": "http://json.schemastore.org/template",
"author": "Travis Chau",
"classifications": [ "Common", "Console" ],
"identity": "AdatumCorporation.ConsoleTemplate.CSharp",
"name": "Adatum Corporation Console Application",
"shortName": "adatumconsole"
}

La carpeta mytemplate es un paquete de plantilla instalable. Una vez instalado el paquete, shortName se puede
utilizar con el comando dotnet new . Por ejemplo, dotnet new adatumconsole generaría los archivos console.cs y
readme.txt en la carpeta actual.

Empaquetar una plantilla en un paquete NuGet (archivo nupkg)


Una plantilla personalizada se empaqueta con el comando la dotnet pack y un archivo .csproj. Como alternativa,
se puede usar NuGet con el comando nuget pack junto con un archivo .nuspec. Pero NuGet necesita .NET
Framework en Windows y Mono en Linux y macOS.
El archivo .csproj es ligeramente diferente al archivo .csproj de un proyecto de código tradicional. Tenga en cuenta
la siguiente configuración:
1. El valor <PackageType> se agrega y se establece en Template .
2. El valor <PackageVersion> se agrega y establece en un número de versión de NuGet.
3. El valor <PackageId> se agrega y se establece en un identificador único. Este identificador se usa para
desinstalar el paquete de plantillas y lo usan las fuentes NuGet para registrar su paquete de plantillas.
4. Se debe establecer la configuración de metadatos genérica: <Title> , <Authors> , <Description> y
<PackageTags> .
5. Debe establecerse la configuración <TargetFramework> , aunque no se usen los datos binarios generados por el
proceso de la plantilla. En el ejemplo siguiente se establece en netstandard2.0 .

Un paquete de plantillas, en forma de un paquete NuGet .nupkg, requiere que todas las plantillas se almacenan en
la carpeta content dentro del paquete. Hay algunas opciones de configuración más para agregar a un archivo
.csproj para asegurarse de que el paquete .nupkg generado se puede instalar como un paquete de plantilla:
1. El valor <IncludeContentInPack> se establece en true para incluir cualquier archivo que el proyecto establece
como content en el paquete NuGet.
2. El valor <IncludeBuildOutput> se establece en false para excluir todos los archivos binarios generados por el
compilador desde el paquete NuGet.
3. El valor <ContentTargetFolders> se establece en content . Esto garantiza que los archivos establecidos como
content se almacenan en la carpeta content carpeta en el paquete NuGet. Esta carpeta del paquete NuGet la
analiza el sistema de plantillas dotnet.
Una manera sencilla de excluir todos los archivos de código para que el proyecto de su plantilla no los compile es
usando el elemento <Compile Remove="**\*" /> del archivo de proyecto, dentro de un elemento <ItemGroup> .
Una manera sencilla de estructurar su paquete de plantillas es colocar todas las plantillas en carpetas individuales
y luego cada carpeta de plantillas dentro de una carpeta templates que se encuentra en el mismo directorio que el
archivo .csproj . De esta manera, puede usar un solo elemento del proyecto para incluir todos los archivos y
carpetas en templates como content . Dentro de un elemento <ItemGroup> , cree un elemento
<Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**" /> .

Este es un archivo .csproj de ejemplo que sigue todas las pautas anteriores. Empaqueta la carpeta secundaria
templates en la carpeta del paquete content y excluye cualquier archivo de código de la compilación.

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageType>Template</PackageType>
<PackageVersion>1.0</PackageVersion>
<PackageId>AdatumCorporation.Utility.Templates</PackageId>
<Title>AdatumCorporation Templates</Title>
<Authors>Me</Authors>
<Description>Templates to use when creating an application for Adatum Corporation.</Description>
<PackageTags>dotnet-new;templates;contoso</PackageTags>
<TargetFramework>netstandard2.0</TargetFramework>

<IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>
</PropertyGroup>

<ItemGroup>
<Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**" />
<Compile Remove="**\*" />
</ItemGroup>

</Project>

En el siguiente ejemplo se muestra la estructura de archivos y carpetas de usar .csproj para crear un paquete de
plantillas. El archivo MyDotnetTemplates.csproj y la carpeta templates se encuentran en la raíz de un directorio
denominado project_folder. La carpeta templates contiene dos plantillas: mytemplate1 y mytemplate2. Cada
plantilla tiene archivos de contenido y una carpeta .template.config con un archivo de configuración template.json.
project_folder
│ MyDotnetTemplates.csproj

└───templates
├───mytemplate1
│ │ console.cs
│ │ readme.txt
│ │
│ └───.template.config
│ template.json

└───mytemplate2
│ otherfile.cs

└───.template.config
template.json

Instalar una plantilla


Use el comando dotnet new -i|--install para instalar un paquete.
Para instalar una plantilla de un paquete NuGet almacenado en nuget.org
Use el identificador del paquete NuGet para instalar un paquete de plantillas.

dotnet new -i <NUGET_PACKAGE_ID>

Para instalar una plantilla de un archivo nupkg local


Proporcione la ruta de acceso a un archivo de paquete NuGet .nupkg.

dotnet new -i <PATH_TO_NUPKG_FILE>

Para instalar una plantilla desde un directorio de sistema de archivos


Las plantillas se pueden instalar desde una carpeta de plantillas, como la carpeta mytemplate1 del ejemplo
anterior. Especifique la ruta de acceso de la carpeta .template.config. La ruta de acceso al directorio de plantillas no
es necesario que sea absoluto. Sin embargo, se requiere una ruta de acceso absoluta para desinstalar una plantilla
que se instala desde una carpeta.

dotnet new -i <FILE_SYSTEM_DIRECTORY>

Obtención de una lista de plantillas instaladas


El comando de desinstalación, sin ningún otro parámetro, enumerará todas las plantillas instaladas.

dotnet new -u

Ese comando devuelve algo similar a la salida siguiente:


Template Instantiation Commands for .NET Core CLI

Currently installed items:


Microsoft.DotNet.Common.ItemTemplates
Templates:
global.json file (globaljson)
NuGet Config (nugetconfig)
Solution File (sln)
Dotnet local tool manifest file (tool-manifest)
Web Config (webconfig)
Microsoft.DotNet.Common.ProjectTemplates.3.0
Templates:
Class library (classlib) C#
Class library (classlib) F#
Class library (classlib) VB
Console Application (console) C#
Console Application (console) F#
Console Application (console) VB
...

El primer nivel de los elementos situados después de Currently installed items: son los identificadores usados
en la desinstalación de una plantilla. Y en el ejemplo anterior, se enumeran Microsoft.DotNet.Common.ItemTemplates
y Microsoft.DotNet.Common.ProjectTemplates.3.0 . Si la plantilla se instaló mediante una ruta de acceso del sistema
de archivos, este identificador será la ruta de acceso de la carpeta .template.config.

Desinstalación de una plantilla


Use el comando dotnet new -u|--uninstall para desinstalar un paquete.
Si el paquete lo instaló una fuente de NuGet o un archivo .nupkg directamente, proporcione el identificador.

dotnet new -u <NUGET_PACKAGE_ID>

Si el paquete se instaló mediante la especificación de una ruta de acceso a la carpeta .template.config, use esa ruta
de acceso absoluta para desinstalar el paquete. Puede ver la ruta de acceso absoluta de la plantilla en el resultado
proporcionado por el comando dotnet new -u . Para obtener más información, consulte la sección Obtención de
una lista de plantillas instaladas anterior.

dotnet new -u <ABSOLUTE_FILE_SYSTEM_DIRECTORY>

Crear un proyecto con una plantilla personalizada


Después de que se instale una plantilla, use la plantilla ejecutando el comando dotnet new <TEMPLATE> como lo
haría con cualquier otra plantilla preinstalada. También puede especificar opciones en el comando dotnet new ,
incluidas las opciones específicas de plantilla que ha definido en la configuración de la plantilla. Proporcione el
nombre breve de la plantilla directamente al comando:

dotnet new <TEMPLATE>

Vea también
Creación de una plantilla personalizada para dotnet new (tutorial)
Wiki del repositorio de GitHub dotnet/templating
Repositorio de GitHub dotnet/dotnet-template-samples
How to create your own templates for dotnet new (Cómo crear sus propias plantillas para dotnet new)
Esquema template.json en el Almacenamiento del esquema JSON
Tutorial: Creación de una plantilla de elemento
16/09/2020 • 11 minutes to read • Edit Online

Con .NET Core, puede crear e implementar plantillas que generan proyectos, archivos e inclusos recursos. Este
tutorial es el primero de una serie que enseña a crear, instalar y desinstalar plantillas para usarlas con el
comando dotnet new .
En esta parte de la serie, aprenderá a:
Crear una clase para una plantilla de elemento.
Crear el archivo y la carpeta de configuración de la plantilla.
Instalar una plantilla desde una ruta de acceso de archivo.
Probar una plantilla de elemento.
Desinstalar una plantilla de elemento.

Requisitos previos
SDK de .NET Core 2.2 o versiones posteriores.
Leer el artículo de referencia Plantillas personalizadas para dotnet new.
En el artículo de referencia se explican los aspectos básicos de las plantillas y cómo se unen. Parte de esta
información se repetirá en este tutorial.
Abra un terminal y vaya a la carpeta working\templates.

Creación de las carpetas necesarias


Esta serie usa una "carpeta de trabajo", donde se encuentra el origen de la plantilla, y una "carpeta de prueba"
que se usa para probar las plantillas. La carpeta de trabajo y la carpeta de prueba deben estar en la misma
carpeta.
En primer lugar, cree la carpeta principal, no importa con qué nombre. Luego, cree una subcarpeta denominada
working (trabajo). Dentro de la carpeta working, cree una subcarpeta con el nombre templates (plantillas).
A continuación, cree una carpeta dentro de la carpeta principal con el nombre test (prueba). La estructura de
carpetas debe tener el siguiente aspecto.

parent_folder
├───test
└───working
└───templates

Creación de una plantilla de elemento


Una plantilla de elemento es un tipo de plantilla específico que contiene uno o varios archivos. Estos tipos de
plantillas son útiles cuando se quiere generar algún contenido como un archivo de solución, código o
configuración. En este ejemplo, creará una clase que agrega un método de extensión al tipo de cadena.
En el terminal, vaya a la carpeta working\templates y cree una subcarpeta llamada extensions. Entre a la carpeta.
working
└───templates
└───extensions

Cree un archivo nuevo denominado CommonExtensions.cs y ábralo con el editor de texto que prefiera. Esta
clase proporcionará un método de extensión denominado Reverse que invierte el contenido de una cadena.
Pegue el código siguiente y guarde el archivo:

using System;

namespace System
{
public static class StringExtensions
{
public static string Reverse(this string value)
{
var tempArray = value.ToCharArray();
Array.Reverse(tempArray);
return new string(tempArray);
}
}
}

Ahora que creó el contenido de la plantilla, debe crear su configuración en la carpeta raíz de la plantilla.

Creación de la configuración de una plantilla


En .NET Core, las plantillas se reconocen con una carpeta especial y un archivo de configuración que existen en
la raíz de la plantilla. En este tutorial, la carpeta de la plantilla se encuentra en working\templates\extensions.
Cuando se crea una plantilla, todos los archivos y las carpetas de la carpeta de la plantilla se incluyen como
parte de la plantilla, a excepción de la carpeta de configuración especial. Esta carpeta de configuración se
denomina .template.config.
En primer lugar, cree una subcarpeta con el nombre .template.config y entre en ella. Luego, cree un archivo
denominado template.json. La estructura de la carpeta debe verse así:

working
└───templates
└───extensions
└───.template.config
template.json

Abra template.json con el editor de texto que prefiera, pegue el código JSON siguiente y guárdelo.

{
"$schema": "http://json.schemastore.org/template",
"author": "Me",
"classifications": [ "Common", "Code" ],
"identity": "ExampleTemplate.StringExtensions",
"name": "Example templates: string extensions",
"shortName": "stringext",
"tags": {
"language": "C#",
"type": "item"
}
}
Este archivo de configuración contiene todos los valores de la plantilla. Puede ver los valores básicos, como
name y shortName , pero también hay un valor tags/type que está establecido en item . De este modo, la
plantilla se clasifica como una plantilla de elemento. No hay ninguna restricción en el tipo de plantilla que crea.
Los valores item y project son nombres comunes que .NET Core recomienda para que los usuarios puedan
filtrar fácilmente el tipo de plantilla que buscan.
El elemento classifications representa la columna tags que ve cuando ejecuta dotnet new y obtiene una lista
de plantillas. Los usuarios también pueden hacer una búsqueda según las etiquetas de clasificación. No
confunda la propiedad tags del archivo *.json con la lista de etiquetas classifications . Lamentablemente, son
dos elementos que tienen nombres similares. El esquema completo del archivo template.json puede encontrarse
en el Almacenamiento del esquema JSON. Para más información sobre el archivo template.json, consulte la wiki
de plantillas dotnet.
Ahora que tiene un archivo .template.config/template.json válido, la plantilla está lista para instalarla. En el
terminal, vaya a la carpeta extensions y ejecute el comando siguiente para instalar la plantilla ubicada en la
carpeta actual:
En Windows : dotnet new -i .\
En Linux o macOS : dotnet new -i ./

Este comando genera la lista de las plantillas instaladas, que debería incluir la suya.

C:\working\templates\extensions> dotnet new -i .\


Usage: new [options]

Options:
-h, --help Displays help for this command.
-l, --list Lists templates containing the specified name. If no name is specified, lists all
templates.

... cut to save space ...

Templates Short Name Language Tags


------------------------------------------------------------------------------------------------------------
-------------------
Example templates: string extensions stringext [C#] Common/Code
Console Application console [C#], F#, VB Common/Console
Class library classlib [C#], F#, VB Common/Library
WPF Application wpf [C#], VB Common/WPF
Windows Forms (WinForms) Application winforms [C#], VB Common/WinForms
Worker Service worker [C#] Common/Worker/Web

Prueba de la plantilla de elemento


Ahora que tiene instalada una plantilla de elemento, pruébela. Vaya a la carpeta test/ y cree una aplicación de
consola con dotnet new console . Esto genera un proyecto de trabajo que puede probar fácilmente con el
comando dotnet run .

dotnet new console

Verá un resultado similar al siguiente.


The template "Console Application" was created successfully.

Processing post-creation actions...


Running 'dotnet restore' on C:\test\test.csproj...
Restore completed in 54.82 ms for C:\test\test.csproj.

Restore succeeded.

Ejecute el proyecto.

dotnet run

Obtendrá la siguiente salida.

Hello World!

Luego, ejecute dotnet new stringext para generar CommonExtensions.cs desde la plantilla.

dotnet new stringext

Obtendrá la siguiente salida.

The template "Example templates: string extensions" was created successfully.

Cambie el código en Program.cs para invertir la cadena "Hello World" con el método de extensión que se
proporciona en la plantilla.

Console.WriteLine("Hello World!".Reverse());

Vuelva a ejecutar el programa y verá que el resultado se invirtió.

dotnet run

Obtendrá la siguiente salida.

!dlroW olleH

¡Enhorabuena! Ha creado e implementado una plantilla de elemento con .NET Core. Como preparación para la
próxima parte de esta serie de tutoriales, debe desinstalar la plantilla que creó. Asegúrese de eliminar también
todos los archivos de la carpeta test. Esto le permitirá volver a un estado limpio listo para la próxima sección
importante de este tutorial.

Desinstalación de la plantilla
Como instaló la plantilla a través de la ruta de acceso de archivo, debe desinstalarla con la ruta de acceso de
archivo absoluta . Ejecute el comando dotnet new -u para ver una lista de las plantillas instaladas. Su plantilla
debe aparecer en último lugar. Use la ruta de acceso mostrada para desinstalar la plantilla con el comando
dotnet new -u <ABSOLUTE PATH TO TEMPLATE DIRECTORY> .
dotnet new -u

Verá un resultado similar al siguiente.

Template Instantiation Commands for .NET Core CLI

Currently installed items:


Microsoft.DotNet.Common.ItemTemplates
Templates:
dotnet gitignore file (gitignore)
global.json file (globaljson)
NuGet Config (nugetconfig)
Solution File (sln)
Dotnet local tool manifest file (tool-manifest)
Web Config (webconfig)

... cut to save space ...

NUnit3.DotNetNew.Template
Templates:
NUnit 3 Test Project (nunit) C#
NUnit 3 Test Item (nunit-test) C#
NUnit 3 Test Project (nunit) F#
NUnit 3 Test Item (nunit-test) F#
NUnit 3 Test Project (nunit) VB
NUnit 3 Test Item (nunit-test) VB
C:\working\templates\extensions
Templates:
Example templates: string extensions (stringext) C#

Para desinstalar una plantilla, ejecute el siguiente comando.

dotnet new -u C:\working\templates\extensions

Pasos siguientes
En este tutorial, creó una plantilla de elemento. Para aprender a crear una plantilla de proyecto, siga con esta
serie de tutoriales.
Creación de una plantilla de proyecto
Tutorial: Creación de una plantilla de proyecto
16/09/2020 • 10 minutes to read • Edit Online

Con .NET Core, puede crear e implementar plantillas que generan proyectos, archivos e inclusos recursos. Este
tutorial es el segundo de una serie que enseña a crear, instalar y desinstalar plantillas para usarlas con el comando
dotnet new .

En esta parte de la serie, aprenderá a:


Crear los recursos de una plantilla de proyecto.
Crear el archivo y la carpeta de configuración de la plantilla.
Instalar una plantilla desde una ruta de acceso de archivo.
Probar una plantilla de elemento.
Desinstalar una plantilla de elemento.

Requisitos previos
Complete la parte 1 de esta serie de tutoriales.
Abra un terminal y vaya a la carpeta working\templates.

Creación de una plantilla de proyecto


Las plantillas de proyecto generan proyectos listos para ejecutarse que facilita a los usuarios empezar a trabajar
con un espacio de trabajo de código. .NET Core incluye algunas plantillas de proyecto, como una aplicación de
consola o una biblioteca de clases. En este ejemplo, creará un proyecto de consola nuevo que habilita C# 8.0 y
genera un punto de entrada async main .
En el terminal, vaya a la carpeta working\templates y cree una subcarpeta denominada consoleasync. Entre a la
subcarpeta y ejecute dotnet new console para generar la aplicación de consola estándar. Para crear una plantilla
nueva, tendrá que editar los archivos que genere esta plantilla.

working
└───templates
└───consoleasync
consoleasync.csproj
Program.cs

Modificación de Program.cs
Abra el archivo program.cs. El proyecto de la consola no usa un punto de entrada asincrónico, por lo que vamos a
agregarlo. Cambie el código por lo siguiente y guarde el archivo.
using System;
using System.Threading.Tasks;

namespace consoleasync
{
class Program
{
static async Task Main(string[] args)
{
await Console.Out.WriteAsync("Hello World with C# 8.0!");
}
}
}

Modificación de consoleasync.csproj
Actualicemos la versión del lenguaje C# que usa el proyecto a la versión 8.0. Edite el archivo consoleasync.csproj y
agregue el valor <LangVersion> a un nodo <PropertyGroup> .

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>

<LangVersion>8.0</LangVersion>

</PropertyGroup>

</Project>

Compilar el proyecto
Antes de completar una plantilla de proyecto, debe probarla para asegurarse de que se compila y ejecuta
correctamente.
En el terminal, ejecute el comando siguiente.

dotnet run

Obtendrá la siguiente salida.

Hello World with C# 8.0!

Puede eliminar las carpetas obj y bin creadas si usa dotnet run . La eliminación de estos archivos garantizar que la
plantilla solo incluya los archivos relacionados con la plantilla y no cualquier archivo que resulte de una acción de
compilación.
Ahora que creó el contenido de la plantilla, debe crear su configuración en la carpeta raíz de la plantilla.

Creación de la configuración de una plantilla


En .NET Core, las plantillas se reconocen con una carpeta especial y un archivo de configuración que existen en la
raíz de la plantilla. En este tutorial, la carpeta de la plantilla se encuentra en working\templates\consoleasync.
Cuando se crea una plantilla, todos los archivos y las carpetas de la carpeta de la plantilla se incluyen como parte
de la plantilla, a excepción de la carpeta de configuración especial. Esta carpeta de configuración se denomina
.template.config.
En primer lugar, cree una subcarpeta con el nombre .template.config y entre en ella. Luego, cree un archivo
denominado template.json. La estructura de carpetas debe tener este aspecto.

working
└───templates
└───consoleasync
└───.template.config
template.json

Abra template.json con el editor de texto que prefiera, pegue el código JSON siguiente y guárdelo.

{
"$schema": "http://json.schemastore.org/template",
"author": "Me",
"classifications": [ "Common", "Console", "C#8" ],
"identity": "ExampleTemplate.AsyncProject",
"name": "Example templates: async project",
"shortName": "consoleasync",
"tags": {
"language": "C#",
"type": "project"
}
}

Este archivo de configuración contiene todos los valores de la plantilla. Puede ver los valores básicos, como name
y shortName , pero también hay un valor tags/type que está establecido en project . Esto diseña la plantilla como
una plantilla de proyecto. No hay ninguna restricción en el tipo de plantilla que crea. Los valores item y project
son nombres comunes que .NET Core recomienda para que los usuarios puedan filtrar fácilmente el tipo de
plantilla que buscan.
El elemento classifications representa la columna tags que ve cuando ejecuta dotnet new y obtiene una lista
de plantillas. Los usuarios también pueden hacer una búsqueda según las etiquetas de clasificación. No confunda
la propiedad tags del archivo .json con la lista de etiquetas classifications . Lamentablemente, son dos
elementos que tienen nombres similares. El esquema completo del archivo template.json puede encontrarse en el
Almacenamiento del esquema JSON. Para más información sobre el archivo template.json, consulte la wiki de
plantillas dotnet.
Ahora que tiene un archivo .template.config/template.json válido, la plantilla está lista para instalarla. Antes de
instalar la plantilla, asegúrese de eliminar cualquier archivo o carpeta de archivos adicional que no quiere que se
incluya en la plantilla, como las carpetas bin o obj. En el terminal, vaya a la carpeta consoleasync y ejecute
dotnet new -i .\ para instalar la plantilla ubicada en la carpeta actual. Si usa un sistema operativo Linux o
macOS, use una barra diagonal: dotnet new -i ./ .
Este comando genera la lista de las plantillas instaladas, que debería incluir la suya.

dotnet new -i .\

Verá un resultado similar al siguiente.


Usage: new [options]

Options:
-h, --help Displays help for this command.
-l, --list Lists templates containing the specified name. If no name is specified, lists all
templates.

... cut to save space ...

Templates Short Name Language Tags


--------------------------------------------------------------------------------------------------------------
-----------------
Console Application console [C#], F#, VB Common/Console
Example templates: async project consoleasync [C#] Common/Console/C#8
Class library classlib [C#], F#, VB Common/Library
WPF Application wpf [C#], VB Common/WPF
Windows Forms (WinForms) Application winforms [C#], VB Common/WinForms
Worker Service worker [C#] Common/Worker/Web

Prueba de la plantilla de proyecto


Ahora que tiene instalada una plantilla de elemento, pruébela.
1. Vaya a la carpeta test.
2. Cree una aplicación de consola con el siguiente comando que genera un proyecto de trabajo que puede
probar fácilmente con el comando dotnet run .

dotnet new consoleasync

Obtendrá la siguiente salida.

The template "Example templates: async project" was created successfully.

3. Ejecute el proyecto con el comando siguiente.

dotnet run

Obtendrá la siguiente salida.

Hello World with C# 8.0!

¡Enhorabuena! Ha creado e implementado una plantilla de proyecto con .NET Core. Como preparación para la
próxima parte de esta serie de tutoriales, debe desinstalar la plantilla que creó. Asegúrese de eliminar también
todos los archivos de la carpeta test. Esto le permitirá volver a un estado limpio listo para la próxima sección
importante de este tutorial.
Desinstalación de la plantilla
Como instaló la plantilla a través de una ruta de acceso de archivo, debe desinstalarla con la ruta de acceso de
archivo absoluta . Ejecute el comando dotnet new -u para ver una lista de las plantillas instaladas. Su plantilla
debe aparecer en último lugar. Use la ruta de acceso mostrada para desinstalar la plantilla con el comando
dotnet new -u <ABSOLUTE PATH TO TEMPLATE DIRECTORY> .

dotnet new -u
Verá un resultado similar al siguiente.

Template Instantiation Commands for .NET Core CLI

Currently installed items:


Microsoft.DotNet.Common.ItemTemplates
Templates:
dotnet gitignore file (gitignore)
global.json file (globaljson)
NuGet Config (nugetconfig)
Solution File (sln)
Dotnet local tool manifest file (tool-manifest)
Web Config (webconfig)

... cut to save space ...

NUnit3.DotNetNew.Template
Templates:
NUnit 3 Test Project (nunit) C#
NUnit 3 Test Item (nunit-test) C#
NUnit 3 Test Project (nunit) F#
NUnit 3 Test Item (nunit-test) F#
NUnit 3 Test Project (nunit) VB
NUnit 3 Test Item (nunit-test) VB
C:\working\templates\consoleasync
Templates:
Example templates: async project (consoleasync) C#

Para desinstalar una plantilla, ejecute el siguiente comando.

dotnet new -u C:\working\templates\consoleasync

Pasos siguientes
En este tutorial creó una plantilla de proyecto. Para información sobre cómo empaquetar la plantilla de elemento y
la de proyecto en un archivo fácil de usar, continúe con esta serie de tutoriales.
Creación de un paquete de plantillas
Tutorial: Creación de un paquete de plantillas
16/09/2020 • 10 minutes to read • Edit Online

Con .NET Core, puede crear e implementar plantillas que generan proyectos, archivos e inclusos recursos. Este
tutorial es el tercero de una serie que enseña a crear, instalar y desinstalar plantillas para usarlas con el comando
dotnet new .

En esta parte de la serie, aprenderá a:


Crear un proyecto *.csproj para compilar un paquete de plantillas
Configurar el archivo del proyecto para el empaquetado.
Instalar una plantilla a partir de un archivo de paquete de NuGet.
Desinstalar una plantilla por el identificador del paquete.

Requisitos previos
Complete la parte 1 y la parte 2 de esta serie de tutoriales.
En este tutorial se usan las dos plantillas que se crearon en las dos primeras partes de este tutorial. Puede
usar otra plantilla siempre que la copie como una carpeta en la carpeta working\templates\ .
Abra un terminal y vaya a la carpeta working\ .

Creación de un proyecto de paquete de plantillas


Cuando se habla un paquete de plantillas, nos referimos a una o más plantillas empaquetadas en un archivo.
Cuando instala o desinstala un paquete, se agregan o quitan todas las plantillas del paquete, respectivamente. Las
partes anteriores de esta serie de tutoriales solo funcionan con plantillas individuales. Para compartir una plantilla
no empaquetada, tiene que copiar la carpeta de plantilla y realizar la instalación mediante esa carpeta. Como un
paquete de plantillas puede tener más de una plantilla y se trata de un solo archivo, compartirlo resulta sencillo.
Los paquetes de plantillas se representan con un archivo ( .nupkg) de paquete de NuGet. Y, al igual que lo que
ocurre con cualquier paquete de NuGet, puede cargar el paquete de plantillas a una fuente NuGet. El comando
dotnet new -i permite instalar un paquete de plantillas desde una fuente NuGet. Además, puede instalar un
paquete de plantillas directamente desde un archivo .nupkg.
Por lo general, se usa un archivo del proyecto de C# para compilar el código y generar un archivo binario. Pero el
proyecto también se puede usar para generar un paquete de plantillas. Si cambia la configuración del archivo
.csproj, puede impedir que compile código y, en su lugar, incluir todos los recursos de las plantillas como recursos.
Cuando se compila este proyecto, genera un paquete de NuGet de paquete de plantillas.
El paquete que va a crear incluirá la plantilla de elemento y la plantilla de paquete que creó anteriormente. Como
agrupamos ambas plantillas en la carpeta working\templates\ , podemos usar la carpeta working para el archivo
.csproj.
En el terminal, vaya a la carpeta working. Cree un proyecto nuevo y establezca el nombre en templatepack y la
carpeta de salida en la carpeta actual.

dotnet new console -n templatepack -o .

El parámetro -n establece el nombre de archivo .csproj en templatepack.csproj. El parámetro -o crea los


archivos en el directorio actual. Verá un resultado similar a la salida siguiente.

dotnet new console -n templatepack -o .

The template "Console Application" was created successfully.

Processing post-creation actions...


Running 'dotnet restore' on .\templatepack.csproj...
Restore completed in 52.38 ms for C:\working\templatepack.csproj.

Restore succeeded.

A continuación, abra el archivo templatepack.csproj en su editor favorito y reemplace el contenido por el XML
siguiente:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageType>Template</PackageType>
<PackageVersion>1.0</PackageVersion>
<PackageId>AdatumCorporation.Utility.Templates</PackageId>
<Title>AdatumCorporation Templates</Title>
<Authors>Me</Authors>
<Description>Templates to use when creating an application for Adatum Corporation.</Description>
<PackageTags>dotnet-new;templates;contoso</PackageTags>

<TargetFramework>netstandard2.0</TargetFramework>

<IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>
</PropertyGroup>

<ItemGroup>
<Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**" />
<Compile Remove="**\*" />
</ItemGroup>

</Project>

El valor <PropertyGroup> del XML anterior se divide en tres grupos. El primer grupo trata con las propiedades
requeridas para un paquete de NuGet. Los tres valores <Package están relacionados con las propiedades del
paquete de NuGet para identificar el paquete en una fuente NuGet. En concreto, el valor <PackageId> se usa para
desinstalar el paquete de plantillas con un solo nombre en lugar de una ruta de acceso a un directorio. También se
puede usar para instalar el paquete de plantillas desde una fuente NuGet. Los valores restantes, como <Title> y
<PackageTags> , están relacionados con los metadatos que aparecen en la fuente NuGet. Para más información
sobre la configuración de NuGet, consulte el artículo sobre propiedades de NuGet y MSBuild.
La configuración <TargetFramework> se debe establecer de manera que MSBuild se ejecute correctamente al
ejecutar el comando pack para compilar y empaquetar el proyecto.
Los tres últimos valores están relacionados con la configuración correcta del proyecto para incluir las plantillas en
la carpeta correspondiente del paquete de NuGet cuando se crea.
<ItemGroup> contiene dos valores. En primer lugar, el valor <Content> incluye todo lo que hay en la carpeta
templates como contenido. También se establece para excluir cualquier carpeta bin o carpeta obj para evitar que se
incluya cualquier código compilado (si probó y compiló las plantillas). En segundo lugar, el valor <Compile> excluye
todos los archivos de código de la compilación, independientemente de dónde estén ubicados. Esto evita que el
proyecto que se usa para crear un paquete de plantillas intente compilar el código en la jerarquía de carpetas
templates.

Compilación e instalación
Guarde este archivo y, a continuación, ejecute el comando pack.

dotnet pack

Este comando compilará el proyecto y creará un paquete NuGet en la carpeta working\bin\Debug.

dotnet pack

Microsoft (R) Build Engine version 16.2.0-preview-19278-01+d635043bd for .NET Core


Copyright (C) Microsoft Corporation. All rights reserved.

Restore completed in 123.86 ms for C:\working\templatepack.csproj.

templatepack -> C:\working\bin\Debug\netstandard2.0\templatepack.dll


Successfully created package 'C:\working\bin\Debug\AdatumCorporation.Utility.Templates.1.0.0.nupkg'.

A continuación, instale el archivo del paquete de plantillas con el comando dotnet new -i PATH_TO_NUPKG_FILE .

C:\working> dotnet new -i C:\working\bin\Debug\AdatumCorporation.Utility.Templates.1.0.0.nupkg


Usage: new [options]

Options:
-h, --help Displays help for this command.
-l, --list Lists templates containing the specified name. If no name is specified, lists all
templates.

... cut to save space ...

Templates Short Name Language Tags


--------------------------------------------------------------------------------------------------------------
-----------------
Example templates: string extensions stringext [C#] Common/Code
Console Application console [C#], F#, VB Common/Console
Example templates: async project consoleasync [C#] Common/Console/C#8
Class library classlib [C#], F#, VB Common/Library

Si cargó el paquete de NuGet en una fuente NuGet, puede usar el comando dotnet new -i PACKAGEID , donde
PACKAGEID es igual que el valor <PackageId> del archivo .csproj. Este identificador de paquete es igual que el
identificador del paquete de NuGet.

Desinstalación del paquete de plantillas


Independientemente de cómo instaló el paquete de plantillas, ya sea directamente con el archivo .nupkg o
mediante la fuente NuGet, el proceso de quitar un paquete de plantillas es el mismo. Use el <PackageId> de la
plantilla que quiere desinstalar. Ejecute el comando dotnet new -u para ver una lista de las plantillas que están
instaladas.

dotnet new -u
Template Instantiation Commands for .NET Core CLI

Currently installed items:


Microsoft.DotNet.Common.ItemTemplates
Templates:
dotnet gitignore file (gitignore)
global.json file (globaljson)
NuGet Config (nugetconfig)
Solution File (sln)
Dotnet local tool manifest file (tool-manifest)
Web Config (webconfig)

... cut to save space ...

NUnit3.DotNetNew.Template
Templates:
NUnit 3 Test Project (nunit) C#
NUnit 3 Test Item (nunit-test) C#
NUnit 3 Test Project (nunit) F#
NUnit 3 Test Item (nunit-test) F#
NUnit 3 Test Project (nunit) VB
NUnit 3 Test Item (nunit-test) VB
AdatumCorporation.Utility.Templates
Templates:
Example templates: async project (consoleasync) C#
Example templates: string extensions (stringext) C#

Ejecute dotnet new -u AdatumCorporation.Utility.Templates para desinstalar la plantilla. El comando dotnet new
generará información de ayuda sobre que debe omitir las plantillas que instaló previamente.
¡Enhorabuena! Ya instaló y desinstaló un paquete de plantillas.

Pasos siguientes
Para más información sobre las plantillas, que en gran parte ya conoce, consulte el artículo Plantillas
personalizadas para dotnet new.
Wiki del repositorio de GitHub dotnet/templating
Repositorio de GitHub dotnet/dotnet-template-samples
Esquema template.json en el Almacenamiento del esquema JSON
SDK de proyectos de .NET Core
16/09/2020 • 12 minutes to read • Edit Online

Los proyectos de .NET Core están asociados a un kit de desarrollo de software (SDK). Cada SDK de proyecto es un
conjunto de destinos de MSBuild y tareas asociadas que se encarga de compilar, empaquetar y publicar código. Un
proyecto que hace referencia a un SDK de proyecto en ocasiones se denomina proyecto de estilo SDK.

SDK disponibles
Los siguientes SDK están disponibles para .NET Core:

ID DESC RIP C IÓ N REP O SITO RIO

Microsoft.NET.Sdk El SDK de .NET Core https://github.com/dotnet/sdk

Microsoft.NET.Sdk.Web El SDK web de .NET Core https://github.com/dotnet/sdk

Microsoft.NET.Sdk.Razor El SDK de Razor de .NET Core

Microsoft.NET.Sdk.Worker El SDK del servicio de trabajo de


.NET Core

Microsoft.NET.Sdk.WindowsDesktop El SDK WinForms y WPF de .NET Core

El SDK de .NET Core es el SDK base de .NET Core. Los otros SDK hacen referencia al SDK de .NET Core, y los
proyectos asociados a los demás SDK disponen de todas las propiedades del SDK de .NET Core. El SDK Web, por
ejemplo, depende de los SDK de .NET Core y de Razor.
También puede crear un SDK propio que se puede distribuir a través de NuGet.

Archivos de proyecto
Los proyectos de .NET Core se basan en el formato MSBuild. Los archivos de proyecto, que tienen extensiones
como .csproj para los proyectos de C# y .fsproj para los de F#, están en formato XML. El elemento raíz de un
archivo de proyecto de MSBuild es Project. El elemento Project tiene un atributo Sdk opcional que especifica qué
SDK (y versión) se van a usar. Para usar las herramientas de .NET Core y compilar el código, establezca el atributo
Sdk en uno de los identificadores de la tabla SDK disponibles.

<Project Sdk="Microsoft.NET.Sdk">
...
</Project>

Para especificar un SDK que proviene de NuGet, incluya la versión al final del nombre, o bien especifique el nombre
y la versión en el archivo global.json.

<Project Sdk="MSBuild.Sdk.Extras/2.0.54">
...
</Project>

Otra manera de especificar el SDK es mediante el elemento Sdk de nivel superior:


<Project>
<Sdk Name="Microsoft.NET.Sdk" />
...
</Project>

Al hacer referencia a un SDK de una de estas formas, se simplifican enormemente los archivos de proyecto de
.NET Core. Durante la evaluación del proyecto, MSBuild agrega importaciones implícitas para Sdk.props en la
parte superior del archivo del proyecto y para Sdk.targets en la inferior.

<Project>
<!-- Implicit top import -->
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
...
<!-- Implicit bottom import -->
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>

TIP
En un equipo con Windows, los archivos Sdk.props y Sdk.targets se pueden encontrar en la carpeta
%ProgramFiles%\dotnet\sdk\[versión]\Sdks\Microsoft.NET.Sdk\Sdk.

Procesamiento previo del archivo del proyecto


Puede ver el proyecto totalmente expandido como MSBuild lo ve después de incluir el SDK y sus destinos mediante
el comando dotnet msbuild -preprocess . El conmutador preprocess del comando dotnet msbuild muestra qué
archivos se han importado, sus orígenes y sus contribuciones a la compilación sin tener que compilar el proyecto
realmente.
Si el proyecto tiene varias plataformas de destino, para centrar los resultados del comando en una sola, debe
especificarla como una propiedad de MSBuild. Por ejemplo:
dotnet msbuild -property:TargetFramework=netcoreapp2.0 -preprocess:output.xml

Inclusiones de compilación predeterminadas


Las inclusiones y exclusiones predeterminadas de los elementos de compilación, los recursos insertados y los
elementos None se definen en el SDK. A diferencia de los proyectos de .NET Framework que no son de SDK, no es
necesario especificar estos elementos en el archivo del proyecto, ya que los valores predeterminados cubren los
casos de uso más comunes. Como resultado, el archivo de proyecto es más pequeño y más fácil de entender y
editar manualmente, si es necesario.
En la tabla siguiente se muestra qué elementos y qué globs se incluyen y excluyen en el SDK de .NET Core:

EL EM EN TO GLO B PA RA IN C L UIR GLO B PA RA EXC L UIR GLO B PA RA Q UITA R

Compile **/*.cs (u otras extensiones **/*.user; **/*.*proj; **/*.sln; N/D


de lenguaje) **/*.vssscc

EmbeddedResource **/*.resx **/*.user; **/*.*proj; **/*.sln; N/D


**/*.vssscc

None **/* **/*.user; **/*.*proj; **/*.sln; **/*.cs; **/*.resx


**/*.vssscc
NOTE
De forma predeterminada, las carpetas ./bin y ./obj (representadas por las propiedades de MSBuild
$(BaseOutputPath) y $(BaseIntermediateOutputPath) ) se excluyen de los globs. Las exclusiones se representan por
medio de la propiedad $(DefaultItemExcludes) .

Errores de compilación
Si define explícitamente cualquiera de estos elementos en el archivo del proyecto, es probable que obtenga un
error de compilación "NETSDK1022" similar al siguiente:

Se han incluido elementos de compilación duplicados. El SDK de .NET incluye elementos de compilación del
directorio del proyecto de forma predeterminada. Puede quitar estos elementos del archivo de proyecto o
establecer la propiedad “EnableDefaultCompileItems” en “false” si quiere incluirlos explícitamente en el archivo
del proyecto.

Se incluyeron elementos "EmbeddedResource" duplicados. El SDK de .NET incluye elementos


"EmbeddedResource" del directorio del proyecto de forma predeterminada. Puede quitar estos elementos del
archivo del proyecto, o bien establecer la propiedad "EnableDefaultEmbeddedResourceItems" en "false" si
quiere incluirlos en él de forma explícita.

Para resolver los errores, lleve a cabo una de las siguientes acciones:
Quite los elementos Compile , EmbeddedResource o None explícitos que coincidan con los implícitos
enumerados en la tabla anterior.
Establezca la propiedad EnableDefaultItems en false para deshabilitar toda la inclusión de archivos
implícita:

<PropertyGroup>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>

Si quiere especificar que se publiquen archivos con la aplicación, puede seguir usando los mecanismos de
MSBuild conocidos para ello, por ejemplo, el elemento Content .
Deshabilite de forma selectiva solo los globs Compile , EmbeddedResource o None estableciendo la propiedad
EnableDefaultCompileItems , EnableDefaultEmbeddedResourceItems o EnableDefaultNoneItems en false :

<PropertyGroup>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
<EnableDefaultNoneItems>false</EnableDefaultNoneItems>
</PropertyGroup>

Si solo deshabilita los globs Compile , el Explorador de soluciones de Visual Studio sigue mostrando
elementos *.cs como parte del proyecto, incluidos como elementos None . Para deshabilitar el glob None
implícito, establezca EnableDefaultNoneItems en false .

Personalización de la compilación
Hay varias maneras de personalizar una compilación. Es posible que quiera invalidar una propiedad y pasarla como
argumento a un comando msbuild o dotnet. También puede agregar la propiedad al archivo del proyecto o a un
archivo Directory.Build.props. Para obtener una lista de propiedades útiles para los proyectos de .NET Core,
consulte Referencia de MSBuild para proyectos de SDK de .NET Core.
Destinos personalizados
Los proyectos de .NET Core pueden empaquetar propiedades y destinos de MSBuild personalizados para su uso en
proyectos que consuman el paquete. Use este tipo de extensibilidad cuando quiera:
Extender el proceso de compilación.
Acceder a artefactos del proceso de compilación, como los archivos generados.
Inspeccionar una configuración en la que se va a invocar la compilación.
Para agregar propiedades o destinos de compilación personalizados, coloque los archivos con el formato
<package_id>.targets o <package_id>.props (por ejemplo, Contoso.Utility.UsefulStuff.targets ) en la carpeta
build del proyecto.
El fragmento de código XML siguiente es de un archivo .csproj que indica al comando dotnet pack lo que debe
empaquetar. El elemento <ItemGroup Label="dotnet pack instructions"> coloca los archivos de destino en la carpeta
build dentro del paquete. El elemento <Target Name="CollectRuntimeOutputs" BeforeTargets="_GetPackageFiles">
coloca los ensamblados y los archivos .json en la carpeta build.

<Project Sdk="Microsoft.NET.Sdk">

...
<ItemGroup Label="dotnet pack instructions">
<Content Include="build\*.targets">
<Pack>true</Pack>
<PackagePath>build\</PackagePath>
</Content>
</ItemGroup>
<Target Name="CollectRuntimeOutputs" BeforeTargets="_GetPackageFiles">
<!-- Collect these items inside a target that runs after build but before packaging. -->
<ItemGroup>
<Content Include="$(OutputPath)\*.dll;$(OutputPath)\*.json">
<Pack>true</Pack>
<PackagePath>build\</PackagePath>
</Content>
</ItemGroup>
</Target>
...

</Project>

Para consumir un destino personalizado en el proyecto, agregue un elemento PackageReference que apunte al
paquete y su versión. A diferencia de las herramientas, el paquete de destinos personalizados se incluye en el cierre
de dependencia del proyecto que lo usa.
Puede configurar cómo se usa el destino personalizado. Como es un destino de MSBuild, puede depender de un
destino concreto, ejecutarse después de otro destino o bien invocarse de forma manual mediante el comando
dotnet msbuild -t:<target-name> . Pero para proporcionar una mejor experiencia de usuario, puede combinar las
herramientas y los destinos personalizados por proyecto. En este escenario, la herramienta por proyecto acepta los
parámetros necesarios y los traduce en la invocación de dotnet msbuild necesaria que ejecuta el destino. Puede
ver una muestra de esta clase de sinergia en el repositorio de ejemplos de MVP Summit 2016 Hackathon del
proyecto dotnet-packer .

Vea también
Instalación de .NET Core
Procedimiento para usar los SDK de proyecto de MSBuild
Empaquetado de destinos y propiedades de MSBuild personalizados con NuGet
Referencia de MSBuild para proyectos del SDK de
.NET Core
16/09/2020 • 17 minutes to read • Edit Online

Esta página es una referencia de las propiedades y los elementos de MSBuild que puede usar para configurar
proyectos de .NET Core.

NOTE
Esta página es un trabajo en curso y no muestra todas las propiedades de MSBuild útiles para el SDK de .NET Core. Para
obtener una lista de las propiedades comunes de MSBuild, vea Propiedades comunes de MSBuild.

Propiedades del marco de trabajo


TargetFramework
TargetFrameworks
NetStandardImplicitPackageVersion
TargetFramework
La propiedad TargetFramework especifica la versión de la plataforma de destino de la aplicación. Para obtener una
lista de los monikers de plataforma de destino válidos, vea Plataformas de destino en proyectos de estilo SDK.

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

Para obtener más información, vea Plataformas de destino en proyectos de estilo SDK.
TargetFrameworks
Use la propiedad TargetFrameworks cuando quiera que la aplicación tenga varias plataformas como destino. Para
obtener una lista de los monikers de plataforma de destino válidos, vea Plataformas de destino en proyectos de
estilo SDK.

NOTE
Esta propiedad se omite si se especifica TargetFramework (singular).

<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net462</TargetFrameworks>
</PropertyGroup>

Para obtener más información, vea Plataformas de destino en proyectos de estilo SDK.
NetStandardImplicitPackageVersion
NOTE
Esta propiedad solo se aplica a los proyectos que usan netstandard1.x . No se aplica a los que usan netstandard2.x .

Use la propiedad NetStandardImplicitPackageVersion si quiere especificar una versión del marco que sea inferior a
la de la versión del metapaquete. El archivo del proyecto del ejemplo siguiente tiene como destino netstandard1.3
pero usa la versión 1.6.0 de NETStandard.Library .

<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<NetStandardImplicitPackageVersion>1.6.0</NetStandardImplicitPackageVersion>
</PropertyGroup>

Propiedades del paquete


Puede especificar propiedades como PackageId , PackageVersion , PackageIcon , Title y Description para
describir el paquete que se crea a partir del proyecto. Para más información sobre estas y otras propiedades, vea
Destino de pack.

<PropertyGroup>
...
<PackageId>ClassLibDotNetStandard</PackageId>
<Version>1.0.0</Version>
<Authors>John Doe</Authors>
<Company>Contoso</Company>
</PropertyGroup>

Publicación de propiedades y elementos


RuntimeIdentifier
RuntimeIdentifiers
TrimmerRootAssembly
UseAppHost
RuntimeIdentifier
La propiedad RuntimeIdentifier permite especificar un único identificador de tiempo de ejecución (RID) para el
proyecto. El RID permite publicar una implementación autocontenida.

<PropertyGroup>
<RuntimeIdentifier>ubuntu.16.04-x64</RuntimeIdentifier>
</PropertyGroup>

RuntimeIdentifiers
La propiedad RuntimeIdentifiers permite especificar una lista delimitada por puntos y coma de identificadores de
tiempo ejecución (RID) para el proyecto. Use esta propiedad si tiene que publicar para varios entornos de
ejecución. RuntimeIdentifiers se usa en el momento de la restauración para asegurarse de que los recursos
adecuados están en el gráfico.
TIP
RuntimeIdentifier (singular) puede proporcionar compilaciones más rápidas cuando solo se requiere un entorno de
ejecución.

<PropertyGroup>
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;ubuntu.16.04-x64</RuntimeIdentifiers>
</PropertyGroup>

TrimmerRootAssembly
El elemento TrimmerRootAssembly permite excluir del recorte un ensamblado. El recorte es el proceso de quitar de
una aplicación empaquetada las partes que no se han usado del tiempo de ejecución. En algunos casos, el recorte
podría quitar de forma incorrecta las referencias necesarias.
El siguiente código XML excluye del recorte el ensamblado System.Security .

<ItemGroup>
<TrimmerRootAssembly Include="System.Security" />
</ItemGroup>

UseAppHost
La propiedad UseAppHost se presentó en la versión 2.1.400 del SDK de .NET Core. Controla si se crea o no un
archivo ejecutable nativo para una implementación. Un archivo ejecutable nativo es obligatorio para las
implementaciones independientes.
En .NET Core 3.0 y versiones posteriores, se crea de forma predeterminada un ejecutable dependiente del marco
de trabajo. Establezca la propiedad UseAppHost en false para deshabilitar la generación del archivo ejecutable.

<PropertyGroup>
<UseAppHost>false</UseAppHost>
</PropertyGroup>

Para obtener más información sobre la implementación, vea Implementación de aplicaciones .NET Core.

Propiedades de compilación
EmbeddedResourceUseDependentUponConvention
LangVersion
EmbeddedResourceUseDependentUponConvention
La propiedad EmbeddedResourceUseDependentUponConvention define si los nombres del archivo de manifiesto del
recurso se generan a partir de la información de tipo de los archivos de código fuente que se ubican
conjuntamente con archivos de recursos. Por ejemplo, si Form1.resx está en la misma carpera que Form1.cs, y
EmbeddedResourceUseDependentUponConvention se establece en true , el archivo .resources generado toma su
nombre del primer tipo que se define en Form1.cs. Por ejemplo, si MyNamespace.Form1 es el primer tipo definido en
Form1.cs, el nombre de archivo generado es myNameSpace.Form1.Resources.

NOTE
Si se especifican los metadatos LogicalName , ManifestResourceName o DependentUpon para un elemento
EmbeddedResource , el nombre del archivo de manifiesto generado para ese archivo de recurso se basa en esos metadatos.
De forma predeterminada, en un nuevo proyecto de .NET Core, esta propiedad se establece en true . Si se
establece en false y no se especifica ningún metadato LogicalName , ManifestResourceName o DependentUpon para
el elemento EmbeddedResource del archivo de proyecto, el nombre del archivo de manifiesto del recurso se basa en
el espacio de nombres raíz del proyecto y en la ruta de acceso relativa al archivo .resx. Para más información,
consulte Denominación de los archivos de manifiesto de recurso.

<PropertyGroup>
<EmbeddedResourceUseDependentUponConvention>true</EmbeddedResourceUseDependentUponConvention>
</PropertyGroup>

LangVersion
La propiedad LangVersion permite especificar una versión concreta del lenguaje de programación. Por ejemplo, si
quiere acceder a las características en vista previa de C#, establezca LangVersion en preview .

<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>

Para obtener más información, vea Control de versiones del lenguaje C#.

Propiedades de análisis de código


AnalysisLevel
La propiedad AnalysisLevel permite especificar un nivel de análisis de código. Por ejemplo, si quiere acceder a la
versión preliminar de los analizadores de código, establezca AnalysisLevel en preview . El valor predeterminado
es latest .

<PropertyGroup>
<AnalysisLevel>preview</AnalysisLevel>
</PropertyGroup>

En la siguiente tabla se muestran las opciones disponibles.

VA L UE SIGN IF IC A DO

latest Se usan los analizadores de código que se han publicado más


recientemente. Este es el valor predeterminado.

preview Se usan los analizadores de código más recientes, incluso si


están en versión preliminar.

5.0 Se usa el conjunto de reglas que se habilitó para .NET 5,0,


incluso si hay reglas más recientes disponibles.

5 Se usa el conjunto de reglas que se habilitó para .NET 5,0,


incluso si hay reglas más recientes disponibles.

CodeAnalysisTreatWarningsAsErrors
La propiedad CodeAnalysisTreatWarningsAsErrors le permite configurar si las advertencias de análisis de código se
deben tratar como advertencias e interrumpir la compilación. Si usa la marca -warnaserror al compilar los
proyectos, las advertencias de análisis de código de .NET también se tratan como errores. Si solo quiere que las
advertencias del compilador se traten como errores, puede establecer la propiedad
CodeAnalysisTreatWarningsAsErrors de MSBuild en false en el archivo del proyecto.

<PropertyGroup>
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
</PropertyGroup>

EnableNETAnalyzers
De forma predeterminada, el análisis de código de .NET está habilitado para los proyectos que tienen como destino
.NET 5.0 o una versión posterior. Puede habilitar el análisis de código de .NET para los proyectos que tienen como
destino versiones anteriores de .NET estableciendo la propiedad EnableNETAnalyzers en "true". Para deshabilitar el
análisis de código en cualquier proyecto, establezca esta propiedad en false .

<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>

TIP
Otra forma de habilitar el análisis de código de .NET en proyectos que tienen como destino versiones de .NET anteriores a
.NET 5.0 es establecer la propiedad AnalysisLevel en latest .

Propiedades de configuración del tiempo de ejecución


Puede configurar algunos comportamientos del tiempo de ejecución si especifica las propiedades de MSBuild en el
archivo de proyecto de la aplicación. Para obtener información sobre otras formas de configurar el
comportamiento del tiempo de ejecución, consulte Opciones de configuración del tiempo de ejecución de .NET
Core.
ConcurrentGarbageCollection
InvariantGlobalization
RetainVMGarbageCollection
ServerGarbageCollection
ThreadPoolMaxThreads
ThreadPoolMinThreads
TieredCompilation
TieredCompilationQuickJit
TieredCompilationQuickJitForLoops
ConcurrentGarbageCollection
La propiedad ConcurrentGarbageCollection configura si está habilitada la recolección de elementos no utilizados en
segundo plano (simultánea). Establezca el valor en false para deshabilitar la recolección de elementos no
utilizados en segundo plano. Para obtener más información, vea Recolección de elementos no utilizados en
segundo plano.

<PropertyGroup>
<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
</PropertyGroup>

InvariantGlobalization
La propiedad InvariantGlobalization configura si la aplicación se ejecuta en modo invariable de globalización, lo
que significa que no tiene acceso a datos específicos de la referencia cultural. Establezca el valor en true para
ejecutar en el modo invariable de globalización. Para obtener más información, consulte Modo invariable.

<PropertyGroup>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

RetainVMGarbageCollection
La propiedad RetainVMGarbageCollection configura el recolector de elementos no utilizados para colocar los
segmentos de memoria eliminados en una lista en espera para su uso futuro o para liberarlos. Al establecer el
valor en true , se indica al recolector de elementos no utilizados que coloque los segmentos en una lista en
espera. Para obtener más información, vea Retain VM (Conservar VM).

<PropertyGroup>
<RetainVMGarbageCollection>true</RetainVMGarbageCollection>
</PropertyGroup>

ServerGarbageCollection
La propiedad ServerGarbageCollection configura si la aplicación usa la recolección de elementos no utilizados de
estación de trabajo o la de servidor. Establezca el valor en true para usar la recolección de elementos no
utilizados de servidor. Para obtener más información, vea Estación de trabajo frente a servidor.

<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

ThreadPoolMaxThreads
La propiedad ThreadPoolMaxThreads configura el número máximo de subprocesos para el grupo de subprocesos
de trabajo. Para obtener más información, consulte Máximo de subprocesos.

<PropertyGroup>
<ThreadPoolMaxThreads>20</ThreadPoolMaxThreads>
</PropertyGroup>

ThreadPoolMinThreads
La propiedad ThreadPoolMinThreads configura el número mínimo de subprocesos para el grupo de subprocesos de
trabajo. Para obtener más información, consulte Mínimo de subprocesos.

<PropertyGroup>
<ThreadPoolMinThreads>4</ThreadPoolMinThreads>
</PropertyGroup>

TieredCompilation
La propiedad TieredCompilation configura si el compilador Just-In-Time (JIT) usa la compilación en niveles.
Establezca el valor en false para deshabilitar la compilación en niveles. Para obtener más información, vea
Compilación en niveles.

<PropertyGroup>
<TieredCompilation>false</TieredCompilation>
</PropertyGroup>
TieredCompilationQuickJit
La propiedad TieredCompilationQuickJit configura si el compilador JIT usa JIT rápido. Establezca el valor en false
para deshabilitar JIT rápido. Para obtener más información, vea JIT rápido.

<PropertyGroup>
<TieredCompilationQuickJit>false</TieredCompilationQuickJit>
</PropertyGroup>

TieredCompilationQuickJitForLoops
La propiedad TieredCompilationQuickJitForLoops configura si el compilador JIT usa JIT rápido en métodos que
contienen bucles. Establezca el valor en true para habilitar JIT rápido en métodos que contienen bucles. Para
obtener más información, vea JIT rápido para bucles.

<PropertyGroup>
<TieredCompilationQuickJitForLoops>true</TieredCompilationQuickJitForLoops>
</PropertyGroup>

Referencia de propiedades y elementos


AssetTargetFallback
PackageReference
ProjectReference
Referencia
Restaurar las propiedades
AssetTargetFallback
La propiedad AssetTargetFallback permite especificar versiones de la plataforma compatibles adicionales para las
referencias de proyectos y los paquetes NuGet. Por ejemplo, si se especifica una dependencia de paquete mediante
PackageReference pero ese paquete no contiene recursos compatibles con el valor TargetFramework del proyecto,
entra en juego la propiedad AssetTargetFallback . La compatibilidad del paquete al que se hace referencia se
vuelve a comprobar con cada plataforma de destino que se especifica en AssetTargetFallback .
Puede establecer la propiedad AssetTargetFallback en una o varias versiones de plataforma de destino.

<PropertyGroup>
<AssetTargetFallback>net461</AssetTargetFallback>
</PropertyGroup>

PackageReference
El elemento PackageReference define una referencia a un paquete NuGet.
El atributo Include especifica el identificador del paquete. El atributo Version especifica la versión o el intervalo
de versiones. Para obtener más información sobre cómo especificar una versión mínima, una máxima, un intervalo
o una coincidencia exacta, vea Intervalos de versiones. También puede agregar los metadatos IncludeAssets ,
ExcludeAssets y PrivateAssets a una referencia de proyecto.

El fragmento del archivo del proyecto del ejemplo siguiente hace referencia al paquete System.Runtime.

<ItemGroup>
<PackageReference Include="System.Runtime" Version="4.3.0" />
</ItemGroup>
Para obtener más información, vea Referencias de paquete en un archivo del proyecto.
ProjectReference
El elemento ProjectReference define una referencia a otro proyecto. El proyecto al que se hace referencia se
agrega como una dependencia del paquete NuGet, es decir, se trata como PackageReference .
El atributo Includeespecifica la ruta de acceso al proyecto. También puede agregar los metadatos IncludeAssets ,
ExcludeAssets y PrivateAssets a una referencia de proyecto.
El fragmento del archivo de proyecto de este ejemplo hace referencia a un proyecto denominado Project2 .

<ItemGroup>
<ProjectReference Include="..\Project2.csproj" />
</ItemGroup>

Referencia
El elemento Reference define una referencia a un archivo de ensamblado.
El atributo Include especifica el nombre del archivo y los metadatos HintPath especifican la ruta de acceso al
ensamblado.

<ItemGroup>
<Reference Include="MyAssembly">
<HintPath>..\..\Assemblies\MyAssembly.dll</HintPath>
</Reference>
</ItemGroup>

Restaurar las propiedades


La restauración de un paquete al que se hace referencia instala todas sus dependencias directas y todas las
dependencias de esas dependencias. Puede personalizar la restauración de paquetes especificando propiedades
como RestorePackagesPath y RestoreIgnoreFailedSources . Para más información sobre estas y otras propiedades,
vea Destino de restore.

<PropertyGroup>
<RestoreIgnoreFailedSource>true</RestoreIgnoreFailedSource>
</PropertyGroup>

Vea también
Referencia del esquema de MSBuild
Propiedades comunes de MSBuild
Propiedades de MSBuild para pack de NuGet
Propiedades de MSBuild para restore de NuGet
Personalización de una compilación
Plataformas de destino en proyectos de
estilo SDK
16/09/2020 • 8 minutes to read • Edit Online

Cuando se dirige a un marco en una aplicación o biblioteca, debe especificar el conjunto de API
que quiere poner a disposición de la aplicación o biblioteca. La plataforma de destino se
especifica en el archivo del proyecto, mediante monikers de la plataforma de destino (TFM).
Una aplicación o biblioteca puede tener como destino una versión de .NET Standard. Las
versiones de .NET Standard representan conjuntos estandarizados de API en todas las
implementaciones de .NET. Por ejemplo, una biblioteca puede tener como destino .NET Standard
1.6 y obtener acceso a las API que funcionan en .NET Core y .NET Framework con el mismo
código base.
Una aplicación o biblioteca también puede tener como destino una implementación específica de
.NET para obtener acceso a las API específicas de la implementación. Por ejemplo, una aplicación
que tenga como destino Xamarin.iOS (por ejemplo, Xamarin.iOS10 ) obtiene acceso a
contenedores de API de iOS proporcionados por Xamarin para iOS 10, o una aplicación que
tenga como destino la Plataforma universal de Windows (UWP, uap10.0 ) tiene acceso a las API
que compilan para dispositivos que ejecutan Windows 10.
Para algunas plataformas de destino (por ejemplo, .NET Framework), las API se definen mediante
los ensamblados que el marco instala en un sistema y pueden incluir las API del marco de trabajo
de la aplicación (por ejemplo, ASP.NET).
Para plataformas de destino basadas en paquetes (por ejemplo, .NET Standard y .NET Core), las
API se definen mediante los paquetes incluidos en la aplicación o biblioteca. Un metapaquete es
un paquete de NuGet que no tiene contenido propio, pero que es una lista de dependencias
(otros paquetes). Una plataforma de destino basada en paquetes de NuGet especifica
implícitamente un metapaquete que hace referencia a todos los paquetes que forman el marco
de trabajo.

Versiones más recientes de las plataformas de destino


En la tabla siguiente, se definen las plataformas de destino más usadas, cómo se hace referencia
a ellas y la versión de .NET Standard que implementan. Estas versiones de plataformas de destino
son las últimas versiones estables. No se muestran las versiones preliminares. Un moniker de la
plataforma de destino (TFM) es un formato de token normalizado para especificar la plataforma
de destino de una aplicación o biblioteca de .NET.

VERSIÓ N DE . N ET M O N IK ER DE L A IM P L EM EN TA DO
F RA M EW O RK DE L AT EST P L ATA F O RM A DE VERSIÓ N DE . N ET
DEST IN O VERSIÓ N ESTA B L E DEST IN O ( T F M ) STA N DA RD

.NET Standard 2.1 netstandard2.1 N/D

.NET Core 3.1 netcoreapp3.1 2.1

.NET Framework 4.8 net48 2.0


Versiones compatibles de las plataformas de destino
Normalmente, un TFM hace referencia a una plataforma de destino. En la tabla siguiente, se
muestran las plataformas de destino compatibles con el SDK de .NET Core y el cliente de NuGet.
Los equivalentes se muestran entre corchetes. Por ejemplo, win81 es un TFM equivalente a
netcore451 .

VERSIÓ N DE . N ET F RA M EW O RK DE DEST IN O T FM

.NET Standard netstandard1.0


netstandard1.1
netstandard1.2
netstandard1.3
netstandard1.4
netstandard1.5
netstandard1.6
netstandard2.0
netstandard2.1

.NET Core netcoreapp1.0


netcoreapp1.1
netcoreapp2.0
netcoreapp2.1
netcoreapp2.2
netcoreapp3.0
netcoreapp3.1

.NET Framework net11


net20
net35
net40
net403
net45
net451
net452
net46
net461
net462
net47
net471
net472
net48

Tienda Windows netcore [netcore45]


netcore45 [win] [win8]
netcore451 [win81]

.NET Micro Framework netmf

Silverlight sl4
sl5

Windows Phone wp [wp7]


wp7
wp75
wp8
wp81
wpa81
VERSIÓ N DE . N ET F RA M EW O RK DE DEST IN O T FM

Plataforma universal de Windows uap [uap10.0]


uap10.0 [win10] [netcore50]

Cómo especificar plataformas de destino


Las plataformas de destino se especifican en el archivo del proyecto. Cuando especifique una
única plataforma de destino, use el elemento TargetFramework . En el siguiente archivo de
proyecto de aplicación de consola se muestra cómo elegir como destino .NET Core 3.0:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>

</Project>

Al especificar varias plataformas de destino, puede hacer referencia de forma condicional a


ensamblados para cada plataforma de destino. En el código, puede compilar de forma
condicional en esos ensamblados utilizando símbolos de preprocesador con lógica if-then-else.
El siguiente archivo de proyecto de biblioteca tiene como destino las API de .NET Standard (
netstandard1.4 ) y las API de .NET Framework ( net40 y net45 ). Use el elemento
TargetFrameworks plural con varias plataformas de destino. Tenga en cuenta cómo los
atributos Condition incluyen paquetes específicos de la implementación cuando se compila la
biblioteca para los dos TFM de .NET Framework:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard1.4;net40;net45</TargetFrameworks>
</PropertyGroup>

<!-- Conditionally obtain references for the .NET Framework 4.0 target -->
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">
<Reference Include="System.Net" />
</ItemGroup>

<!-- Conditionally obtain references for the .NET Framework 4.5 target -->
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="System.Net.Http" />
<Reference Include="System.Threading.Tasks" />
</ItemGroup>

</Project>

Dentro de su aplicación o biblioteca, puede escribir código condicional para compilar para cada
plataforma de destino:
public class MyClass
{
static void Main()
{
#if NET40
Console.WriteLine("Target framework: .NET Framework 4.0");
#elif NET45
Console.WriteLine("Target framework: .NET Framework 4.5");
#else
Console.WriteLine("Target framework: .NET Standard 1.4");
#endif
}
}

El sistema de compilación es consciente de los símbolos de preprocesador que representan las


plataformas de destino que se muestran en la tabla Versiones compatibles de las plataformas de
destino cuando se usan proyectos de estilo SDK. Cuando use un símbolo que representa un TFM
de .NET Standard o .NET Core, reemplace el punto por un carácter de subrayado y cambie las
letras minúsculas por mayúsculas (por ejemplo, el símbolo de netstandard1.4 es
NETSTANDARD1_4 ).

La lista completa de símbolos de preprocesador para plataformas de destino de .NET Core es:

VERSIO N ES DE . N ET F RA M EW O RK DE DEST IN O SÍM B O LO S

.NET Framework NETFRAMEWORK , NET20 , NET35 , NET40 , NET45 ,


NET451 , NET452 , NET46 , NET461 , NET462 ,
NET47 , NET471 , NET472 , NET48

.NET Standard NETSTANDARD , NETSTANDARD1_0 ,


NETSTANDARD1_1 , NETSTANDARD1_2 ,
NETSTANDARD1_3 , NETSTANDARD1_4 ,
NETSTANDARD1_5 , NETSTANDARD1_6 ,
NETSTANDARD2_0 , NETSTANDARD2_1

.NET Core NETCOREAPP , NETCOREAPP1_0 , NETCOREAPP1_1 ,


NETCOREAPP2_0 , NETCOREAPP2_1 ,
NETCOREAPP2_2 , NETCOREAPP3_0 ,
NETCOREAPP3_1

Plataformas de destino en desuso


Las siguientes plataformas de destino están en desuso. Los paquetes destinados a estas
plataformas de destino deben migrarse a los reemplazos indicados.

T F M EN DESUSO REP L A C EM EN T

aspnet50 netcoreapp
aspnetcore50
dnxcore50
dnx
dnx45
dnx451
dnx452
T F M EN DESUSO REP L A C EM EN T

dotnet netstandard
dotnet50
dotnet51
dotnet52
dotnet53
dotnet54
dotnet55
dotnet56

netcore50 uap10.0

win netcore45

win8 netcore45

win81 netcore451

win10 uap10.0

winrt netcore45

Vea también
Desarrollo de bibliotecas con herramientas multiplataforma
.NET Standard
Control de versiones de .NET Core
Repositorio de GitHub dotnet/standard
Repositorio de GitHub de herramientas de NuGet
Framework Profiles in .NET (Perfiles de marco de trabajo en .NET)
Adiciones al formato csproj para .NET Core
16/09/2020 • 32 minutes to read • Edit Online

En este documento se describen los cambios que se han agregado a los archivos de proyecto como parte del cambio
de project.json a csproj y MSBuild. Para obtener más información sobre la sintaxis y la referencia del archivo de
proyecto general, consulte la documentación del archivo de proyecto de MSBuild.

Referencias implícitas del paquete


Se hace una referencia implícita a los metapaquetes basándose en los marcos de trabajo de destino especificados en
la propiedad <TargetFramework> o <TargetFrameworks> del archivo del proyecto. <TargetFrameworks> se ignora si
<TargetFramework> se especifica, independientemente del orden.

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net462</TargetFrameworks>
</PropertyGroup>

Recomendaciones
Como se hace referencia implícitamente a los metapaquetes Microsoft.NETCore.App o NETStandard.Library , estos
son los procedimientos recomendados:
Si el destino es .NET Core o .NET Standard, nunca incluya una referencia explícita a los metapaquetes
Microsoft.NETCore.App o NETStandard.Library mediante un elemento <PackageReference> en el archivo de
proyecto.
Si necesita una versión concreta del runtime cuando el destino es .NET Core, debe usar la propiedad
<RuntimeFrameworkVersion> del proyecto (por ejemplo, 1.0.4 ) en lugar de hacer referencia al metapaquete.
Esto puede ocurrir si está usando implementaciones autocontenidas y necesita una versión de revisión
específica del tiempo de ejecución de LTS 1.0.0, por ejemplo.
Si necesita una versión concreta del metapaquete NETStandard.Library cuando el destino es .NET Standard, puede
usar la propiedad <NetStandardImplicitPackageVersion> y establecer la versión necesaria.
No agregue referencias a los metapaquetes Microsoft.NETCore.App y NETStandard.Library ni las actualice
explícitamente en proyectos de .NET Framework. Si se necesita alguna versión de NETStandard.Library al usar un
paquete NuGet basado en .NET Standard, NuGet instala automáticamente esa versión.

Versión implícita para algunas referencias de paquete


La mayoría de los usos de <PackageReference> requieren establecer el atributo Version para especificar la versión
del paquete de NuGet que se va a usar. Sin embargo, el atributo es innecesario si se usa .NET Core 2.1 o 2.2 y se hace
referencia a Microsoft.AspNetCore.App o a Microsoft.AspNetCore.All. El SDK de .NET Core puede seleccionar
automáticamente la versión que se debe usar de estos paquetes.
Recomendación
Cuando haga referencia al paquete Microsoft.AspNetCore.App o al paquete Microsoft.AspNetCore.All , no especifique
la versión. Si se especifica una versión, el SDK podría generar la advertencia NETSDK1071. Para corregir esta
advertencia, quite la versión de paquete como en el ejemplo siguiente:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

Problema conocido: el SDK de .NET Core 2.1 solo admite esta sintaxis cuando el proyecto también usa
Microsoft.NET.Sdk.Web. Esto se resuelve en el SDK de .NET Core 2.2.

Estas referencias a los metapaquetes de ASP.NET Core tienen un comportamiento ligeramente distinto de los
paquetes más habituales de NuGet. Las implementaciones dependientes del marco de las aplicaciones que usan
estos metapaquetes aprovechan automáticamente el marco de uso compartido de ASP.NET Core. Al usar los
metapaquetes, no se implementa ningún recurso de los paquetes NuGet de ASP.NET Core a los que se hace
referencia con la aplicación, porque el marco de uso compartido de ASP.NET Core ya contiene estos recursos. Los
recursos del marco de uso compartido están optimizados para que la plataforma de destino mejore el tiempo de
inicio de la aplicación. Para más información sobre el marco de uso compartido, consulte Empaquetado de
distribución de .NET Core.
Si se especifica una versión, se trata como la versión mínima del marco de uso compartido de ASP.NET Core para las
implementaciones dependientes del marco y como una versión exacta de las implementaciones autocontenidas. Esto
puede deberse a las siguientes consecuencias:
Si la versión de ASP.NET Core instalada en el servidor es anterior a la versión especificada en PackageReference,
no se iniciará el proceso de .NET Core. Por lo general, las actualizaciones del metapaquete están disponibles en
NuGet.org antes de que se aparezcan en entornos de hospedaje como Azure. Actualizar la versión de
PackageReference a ASP.NET Core podría provocar un error en una aplicación implementada.
Si la aplicación se implementa como una implementación autocontenida, es posible que no contenga las
actualizaciones de seguridad más recientes a .NET Core. Cuando no se especifica una versión, el SDK puede incluir
automáticamente la versión más reciente de ASP.NET Core en la implementación autocontenida.

Inclusiones de compilación predeterminadas en proyectos .NET Core


Con el cambio al formato csproj en las últimas versiones del SDK, hemos trasladado las inclusiones y exclusiones
predeterminadas para los elementos de compilación y los recursos incrustados a los archivos de propiedades del
SDK. Esto implica que ya no tiene que especificar dichos elementos en el archivo del proyecto.
El principal motivo de este cambio consiste en reducir el desorden en el archivo del proyecto. Los valores
predeterminados presentes en el SDK deberían abarcar los casos de uso más habituales, por lo que no resulta
necesario repetirlos en todos los proyectos que cree. Esto da lugar a archivos de proyecto más pequeños que
resultan mucho más fáciles de entender, así como de editar manualmente si fuera necesario.
En la siguiente tabla se muestra qué elementos y qué globs se incluyen y excluyen en el SDK:

EL EM EN TO GLO B PA RA IN C L UIR GLO B PA RA EXC L UIR GLO B PA RA Q UITA R

Compile **/*.cs (u otras extensiones **/*.user; **/*.*proj; **/*.sln; N/D


de lenguaje) **/*.vssscc

EmbeddedResource **/*.resx **/*.user; **/*.*proj; **/*.sln; N/D


**/*.vssscc

None **/* **/*.user; **/*.*proj; **/*.sln; **/*.cs; **/*.resx


**/*.vssscc
NOTE
Glob para excluir siempre excluye las carpetas ./bin y ./obj , que se representan mediante las propiedades
$(BaseOutputPath) y $(BaseIntermediateOutputPath) de MSBuild, respectivamente. En su conjunto, todos los "exclude" se
representan mediante $(DefaultItemExcludes) .

Si tiene globs en el proyecto e intenta crearlo usando el SDK más reciente, le aparecerá el siguiente error:

Se han incluido elementos de compilación duplicados. El SDK de .NET incluye elementos de compilación del
directorio del proyecto de forma predeterminada. Puede quitar estos elementos del archivo de proyecto o
establecer la propiedad “EnableDefaultCompileItems” en “false” si quiere incluirlos explícitamente en el archivo
del proyecto.

Para evitar este error, puede quitar los elementos Compile explícitos que coinciden con los que aparecen en la tabla
anterior o establecer la propiedad <EnableDefaultCompileItems> en false de esta forma:

<PropertyGroup>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>

Al establecer esta propiedad en false , se deshabilita la inclusión implícita y se revierte el comportamiento de SDK
anteriores en los que había que especificar los globs predeterminados del proyecto.
Este cambio no modifica los mecanismos principales de otras inclusiones. En cambio, si quiere especificar, por
ejemplo, que algunos archivos se publiquen con la aplicación, puede seguir usando los mecanismos con los que está
familiarizado en csproj (por ejemplo, el elemento <Content> ).
<EnableDefaultCompileItems> solo deshabilita globs Compile , pero no afecta a otros globs, como el glob None
implícito, que también se aplica a elementos *.cs. Por eso, el Explorador de soluciones sigue mostrando elementos
*.cs como parte del proyecto, incluso como elementos None . Del mismo modo, puede establecer
<EnableDefaultNoneItems> en false para deshabilitar el glob None implícito, de esta forma:

<PropertyGroup>
<EnableDefaultNoneItems>false</EnableDefaultNoneItems>
</PropertyGroup>

Para deshabilitar todos los globs implícitos , puede establecer la propiedad <EnableDefaultItems> en false como
en el ejemplo siguiente:

<PropertyGroup>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>

Visualización del proyecto completo tal como MSBuild lo ve


Aunque dichos cambios de csproj simplifican considerablemente los archivos de proyecto, quizá desee visualizar el
proyecto totalmente expandido tal y como MSBuild lo ve después de haber incluido el SDK y sus destinos. Preprocese
el proyecto con el conmutador /pp del comando dotnet msbuild , que muestra qué archivos se han importado, sus
orígenes y sus contribuciones a la compilación sin tener que compilar el proyecto realmente:

dotnet msbuild -pp:fullproject.xml

Si el proyecto tiene varios marcos de destino, los resultados del comando deben centrarse solo en uno de ellos,
especificándolo como una propiedad de MSBuild:
dotnet msbuild -p:TargetFramework=netcoreapp3.1 -pp:fullproject.xml

Adiciones
Atributo Sdk
El elemento raíz <Project> del archivo .csproj tiene un nuevo atributo denominado Sdk . Sdk especifica qué SDK
usará el proyecto. El SDK, como se describe en el documento sobre capas, es un conjunto de tareas y destinos de
MSBuild que pueden generar código de .NET Core. Los siguientes SDK están disponibles para .NET Core:
1. El SDK de .NET Core con el id. de Microsoft.NET.Sdk
2. El SDK web de .NET Core con el id. de Microsoft.NET.Sdk.Web
3. El SDK de la biblioteca de clases de Razor de .NET Core con el id. Microsoft.NET.Sdk.Razor
4. El servicio de trabajo de .NET Core con el id. de Microsoft.NET.Sdk.Worker (a partir de .NET Core 3.0)
5. WinForms y WPF de .NET Core con el id. de Microsoft.NET.Sdk.WindowsDesktop (a partir de .NET Core 3.0)

Debe tener el conjunto de atributos Sdk establecido en uno de esos id. del elemento <Project> para poder usar las
herramientas de .NET Core y generar el código.
PackageReference
Elemento <PackageReference> que especifica una dependencia de NuGet en el proyecto. El atributo Include
especifica el identificador del paquete.

<PackageReference Include="package-id" Version="" PrivateAssets="" IncludeAssets="" ExcludeAssets="" />

Versión
El atributo Version necesario especifica la versión del paquete que se va a restaurar. El atributo respeta las reglas del
esquema de intervalo de versiones de NuGet. El comportamiento predeterminado es una versión mínima, incluida
una coincidencia. Por ejemplo, la especificación de Version="1.2.3" es equivalente a la notación de NuGet [1.2.3, )
y significa que el paquete resuelto tendrá la versión 1.2.3 si está disponible o, de lo contrario, una superior.
IncludeAssets, ExcludeAssets y PrivateAssets
El atributo IncludeAssets especifica qué recursos que pertenecen al paquete especificado por <PackageReference> se
deben consumir. De forma predeterminada, se incluyen todos los recursos del paquete.
El atributo ExcludeAssets especifica qué recursos que pertenecen al paquete especificado por <PackageReference>
no se deben consumir.
El atributo PrivateAssets especifica qué recursos que pertenecen al paquete especificado por <PackageReference> se
deben consumir, pero no pasar al proyecto siguiente. Cuando este atributo no existe, los recursos Analyzers , Build
y ContentFiles son privados de forma predeterminada.

NOTE
PrivateAssets es equivalente al elemento project.json/xproj SuppressParent .

Estos atributos pueden contener uno o varios de los siguientes elementos, separados por punto y coma ; si aparece
más de uno:
Compile : el contenido de la carpeta lib está disponible para compilación.
Runtime : el contenido de la carpeta runtime está distribuido.
ContentFiles : se usa el contenido de la carpeta contentfiles.
Build : se usan los archivos props/targets de la carpeta build.
Native : el contenido de recursos nativos se copia en la carpeta output en runtime.
Analyzers : se usan los analizadores.

Como alternativa, el atributo puede contener:


None : no se usa ninguno de los recursos.
All : se usan todos los recursos.

DotnetCliToolReference
Un elemento <DotNetCliToolReference> especifica la herramienta de la CLI que el usuario quiere restaurar en el
contexto del proyecto. Es un sustituto del nodo tools de project.json.

<DotNetCliToolReference Include="<package-id>" Version="" />

Tenga en cuenta que DotNetCliToolReference está ahora en desuso en favor de las herramientas locales de .NET Core.
Versión
Version especifica la versión del paquete que se va a restaurar. El atributo respeta las reglas del esquema de
versiones de NuGet. El comportamiento predeterminado es una versión mínima, incluida una coincidencia. Por
ejemplo, la especificación de Version="1.2.3" es equivalente a la notación de NuGet [1.2.3, ) y significa que el
paquete resuelto tendrá la versión 1.2.3 si está disponible o, de lo contrario, una superior.
RuntimeIdentifiers
El elemento de propiedad <RuntimeIdentifiers> permite especificar una lista delimitada por puntos y coma de
identificadores de tiempo ejecución (RID) para el proyecto. Los RID permiten publicar implementaciones
autocontenidas.

<RuntimeIdentifiers>win10-x64;osx.10.11-x64;ubuntu.16.04-x64</RuntimeIdentifiers>

RuntimeIdentifier
El elemento de propiedad <RuntimeIdentifier> permite especificar solo un identificador de tiempo ejecución (RID)
para el proyecto. El RID permite publicar una implementación autocontenida.

<RuntimeIdentifier>ubuntu.16.04-x64</RuntimeIdentifier>

Use <RuntimeIdentifiers> (en plural) en su lugar si tiene que publicar para varios entornos de ejecución.
<RuntimeIdentifier> puede proporcionar compilaciones más rápidas cuando solo se requiere un entorno de
ejecución.
PackageTargetFallback
El elemento de propiedad <PackageTargetFallback> permite especificar un conjunto de destinos compatibles que se
van a usar al restaurar paquetes. Está diseñado para permitir que los paquetes que usan la dotnet TxM (destino x
moniker) funcionen con paquetes que no declaran una dotnet TxM. Si el proyecto usa la dotnet TxM, todos los
paquetes de los que depende también deben tener una dotnet TxM, a menos que agregue el elemento
<PackageTargetFallback> a su proyecto a fin de permitir que las plataformas sin dotnet sean compatibles con dotnet.

En el ejemplo siguiente se proporcionan los elementos Fallback para todos los destinos del proyecto:

<PackageTargetFallback>
$(PackageTargetFallback);portable-net45+win8+wpa81+wp8
</PackageTargetFallback >

En el ejemplo siguiente se especifican los elementos Fallback solo para el destino netcoreapp3.1 :
<PackageTargetFallback Condition="'$(TargetFramework)'=='netcoreapp3.1'">
$(PackageTargetFallback);portable-net45+win8+wpa81+wp8
</PackageTargetFallback >

Eventos de compilación
La forma en que se especifican los eventos anteriores y posteriores a la compilación en el archivo de proyecto ha
cambiado. No se recomiendan las propiedades PreBuildEvent y PostBuildEvent en el formato de proyecto de estilo
SDK, ya que las macros como $(ProjectDir) no se resuelven. Por ejemplo, el código siguiente ya no se admite:

<PropertyGroup>
<PreBuildEvent>"$(ProjectDir)PreBuildEvent.bat" "$(ProjectDir)..\" "$(ProjectDir)" "$(TargetDir)"
</PreBuildEvent>
</PropertyGroup>

En los proyectos de estilo SDK, use un destino de MSBuild denominado PreBuild o PostBuild , y establezca la
propiedad BeforeTargets para PreBuild , o bien la propiedad AfterTargets para PostBuild . Para el ejemplo
anterior, use el código siguiente:

<Target Name="PreBuild" BeforeTargets="PreBuildEvent">


<Exec Command="&quot;$(ProjectDir)PreBuildEvent.bat&quot; &quot;$(ProjectDir)..\&quot;
&quot;$(ProjectDir)&quot; &quot;$(TargetDir)&quot;" />
</Target>

<Target Name="PostBuild" AfterTargets="PostBuildEvent">


<Exec Command="echo Output written to $(TargetDir)" />
</Target>

NOTE
Puede usar cualquier nombre para los destinos de MSBuild, pero el IDE de Visual Studio reconoce los destinos PreBuild y
PostBuild , por lo que se recomienda usar esos nombres para poder editar los comandos en el IDE de Visual Studio.

Propiedades de metadatos de NuGet


Con el paso a MSBuild, hemos trasladado los metadatos de entrada que se usan cuando se empaqueta un paquete
NuGet de archivos project.json a archivos .csproj. Las entradas son propiedades de MSBuild, por lo que deben ir
dentro de un grupo <PropertyGroup> . La siguiente es la lista de propiedades que se utilizan como entradas para el
proceso de empaquetado cuando se usa el comando dotnet pack o el destino de MSBuild Pack , que forma parte
del SDK:
IsPackable
Un valor booleano que especifica si se puede empaquetar el proyecto. El valor predeterminado es true .
PackageVersion
Especifica la versión que tendrá el paquete resultante. Acepta todos los formatos de la cadena de versión de NuGet. El
valor predeterminado es $(Version) , es decir, de la propiedad Version del proyecto.
PackageId
Especifica el nombre para el paquete resultante. Si no se especifica, la operación pack usará de forma
predeterminada el elemento AssemblyName o el nombre del directorio como el nombre del paquete.
Title
Un título fácil de usar del paquete, que se usa normalmente en las visualizaciones de la interfaz de usuario, como en
nuget.org, y el Administrador de paquetes de Visual Studio. Si no se especifica, se usa el identificador del paquete en
su lugar.
Authors
Una lista separada por punto y coma de los autores de los paquetes, que coinciden con los nombres de perfil de
nuget.org. Estos se muestran en la galería de NuGet, en nuget.org, y se usan para hacer referencias cruzadas a
paquetes de los mismos autores.
PackageDescription
Una descripción larga del paquete para su visualización en la interfaz de usuario.
Descripción
Una descripción larga del ensamblado. Si PackageDescription no se especifica, esta propiedad también se utiliza
como la descripción del paquete.
Copyright
Detalles de copyright del paquete.
PackageRequireLicenseAcceptance
Un valor booleano que especifica si el cliente debe pedir al consumidor que acepte la licencia del paquete antes de
instalarlo. De manera predeterminada, es false .
DevelopmentDependency
Valor booleano que especifica si el paquete se debe marcar como una dependencia de solo desarrollo, que impide
que el paquete se incluya como una dependencia en otros paquetes. Con PackageReference (NuGet 4.8 y versiones
posteriores), esta marca también significa que se excluirán los recursos en tiempo de compilación de la compilación.
Para más información, consulte Compatibilidad de DevelopmentDependency para PackageReference.
PackageLicenseExpression
Identificador de licencia SPDX o expresión. Por ejemplo: Apache-2.0 .
Esta es la lista completa de identificadores de licencia SPDX. NuGet.org acepta solo licencias aprobadas de OSI o FSF
cuando se usa la expresión de tipo de licencia.
La sintaxis exacta de las expresiones de licencia se describe a continuación en ABNF.

license-id = <short form license identifier from https://spdx.org/spdx-specification-21-web-


version#h.luq9dgcle9mo>

license-exception-id = <short form license exception identifier from https://spdx.org/spdx-specification-21-web-


version#h.ruv3yl8g6czd>

simple-expression = license-id / license-id”+”

compound-expression = 1*1(simple-expression /
simple-expression "WITH" license-exception-id /
compound-expression "AND" compound-expression /
compound-expression "OR" compound-expression ) /
"(" compound-expression ")" )

license-expression = 1*1(simple-expression / compound-expression / UNLICENSED)

NOTE
Solo se puede especificar una de estos elementos cada vez: PackageLicenseExpression , PackageLicenseFile o
PackageLicenseUrl .

PackageLicenseFile
Ruta de acceso a un archivo de licencia dentro del paquete si usa una licencia que no tiene asignado un identificador
SPDX, o se trata de una licencia personalizada (en caso contrario, se prefiere PackageLicenseExpression )
Reemplaza PackageLicenseUrl , no se puede combinar con PackageLicenseExpression y requiere Visual Studio
versión 15.9.4 y el SDK de .NET 2.1.502, 2.2.101 o una versión posterior.
Deberá asegurarse de que el archivo de licencia está empaquetado; para ello, agréguelo explícitamente al proyecto.
Ejemplo de uso:

<PropertyGroup>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
</PropertyGroup>
<ItemGroup>
<None Include="licenses\LICENSE.txt" Pack="true" PackagePath="$(PackageLicenseFile)"/>
</ItemGroup>

PackageLicenseUrl
Una dirección URL a la licencia que se aplica al paquete. (en desuso desde Visual Studio 15.9.4, SDK de .NET 2.1.502 y
2.2.101)
PackageIcon
Ruta de acceso a una imagen del paquete que se va a usar como icono del paquete. Más información sobre los
metadatos de icon . PackageIconUrl está en desuso en favor de PackageIcon.
PackageReleaseNotes
Notas de la versión para el paquete.
PackageTags
Una lista de etiquetas delimitada por punto y coma que designa el paquete.
PackageOutputPath
Determina la ruta de acceso de salida en la que se va a quitar el paquete empaquetado. El valor predeterminado es
$(OutputPath) .

IncludeSymbols
Este valor booleano indica si el paquete debe crear un paquete de símbolos adicionales cuando se empaqueta el
proyecto. El formato del paquete de símbolos se controla mediante la propiedad SymbolPackageFormat .
SymbolPackageFormat
Especifica el formato del paquete de símbolos. Si es "symbols.nupkg", se crea un paquete de símbolos heredado con
una extensión .symbols.nupkg que contiene archivos PDB, DLL y otros archivos de salida. Si es "snupkg", se crea un
paquete de símbolos snupkg que contiene los archivos PDB portátiles. El valor predeterminado es "symbols.nupkg".
IncludeSource
Este valor booleano indica si el proceso de empaquetado debe crear un paquete de origen. El paquete de origen
contiene el código fuente de la biblioteca, así como archivos PDB. Los archivos de origen se colocan en el directorio
src/ProjectName , en el archivo de paquete resultante.

IsTool
Especifica si se copian todos los archivos de salida en la carpeta tools en lugar de la carpeta lib. Esto es diferente de
un elemento DotNetCliTool , que se especifica estableciendo el elemento PackageType en el archivo .csproj.
RepositoryUrl
Especifica la dirección URL del repositorio donde reside el código fuente del paquete o desde el que se está creando.
RepositoryType
Especifica el tipo del repositorio. El valor predeterminado es “git”.
RepositoryBranch
Especifica el nombre de la rama de origen en el repositorio. Cuando el proyecto se empaqueta en un paquete NuGet,
se agrega a los metadatos del paquete.
RepositoryCommit
Confirmación o conjunto de cambios opcionales de repositorio para indicar en qué origen se ha compilado el
paquete. RepositoryUrl también se debe especificar para que esta propiedad se incluya. Cuando el proyecto se
empaqueta en un paquete NuGet, esta confirmación o conjunto de cambios se agrega a los metadatos del paquete.
NoPackageAnalysis
Especifica que el paquete no debe ejecutar el análisis de paquetes después de crear el paquete.
MinClientVersion
Especifica la versión mínima del cliente de NuGet que puede instalar este paquete, aplicada por nuget.exe y el
Administrador de paquetes de Visual Studio.
IncludeBuildOutput
Este valor booleano especifica si se deben empaquetar los ensamblados de salida de la compilación en el archivo
.nupkg o no.
IncludeContentInPack
Este valor booleano especifica si los elementos del tipo Content se incluirán automáticamente en el paquete
resultante. De manera predeterminada, es true .
BuildOutputTargetFolder
Especifica la carpeta en la que se colocarán los ensamblados de salida. Los ensamblados de salida (y otros archivos
de salida) se copian en sus respectivas carpetas de marco.
ContentTargetFolders
Esta propiedad especifica la ubicación predeterminada a la que deben ir todos los archivos de contenido si no se
especifica PackagePath para ellos. El valor predeterminado es “content;contentFiles”.
NuspecFile
Ruta de acceso relativa o absoluta al archivo .nuspec que se usa para el empaquetado.

NOTE
Si se especifica el archivo .nuspec, se usa exclusivamente para la información de empaquetado y no se usa ninguna de la
información de los proyectos.

NuspecBasePath
Ruta de acceso base para el archivo .nuspec.
NuspecProperties
Lista separada por punto y coma de pares clave=valor.

Propiedades de AssemblyInfo
Los atributos de ensamblado que solían estar presentes en un archivo AssemblyInfo ahora se generan
automáticamente a partir de las propiedades.
Propiedades por atributo
Como se muestra en la tabla siguiente, cada atributo tiene una propiedad que controla el contenido y otra que
deshabilita su generación:

AT RIB UTO P RO P IEDA D. P RO P IEDA D Q UE SE VA A DESH A B IL ITA R

AssemblyCompanyAttribute Company GenerateAssemblyCompanyAttribute


AT RIB UTO P RO P IEDA D. P RO P IEDA D Q UE SE VA A DESH A B IL ITA R

AssemblyConfigurationAttribute Configuration GenerateAssemblyConfigurationAttribute

AssemblyCopyrightAttribute Copyright GenerateAssemblyCopyrightAttribute

AssemblyDescriptionAttribute Description GenerateAssemblyDescriptionAttribute

AssemblyFileVersionAttribute FileVersion GenerateAssemblyFileVersionAttribute

AssemblyInformationalVersionAttribute InformationalVersion GenerateAssemblyInformationalVersionAttribute

AssemblyProductAttribute Product GenerateAssemblyProductAttribute

AssemblyTitleAttribute AssemblyTitle GenerateAssemblyTitleAttribute

AssemblyVersionAttribute AssemblyVersion GenerateAssemblyVersionAttribute

NeutralResourcesLanguageAttribute NeutralLanguage GenerateNeutralResourcesLanguageAttribute

Notas:
El comportamiento predeterminado de AssemblyVersion y FileVersion consiste en adoptar el valor de
$(Version) sin sufijo. Por ejemplo, si $(Version) es 1.2.3-beta.4 , entonces el valor sería 1.2.3 .
El valor predeterminado de InformationalVersion es el de $(Version) .
InformationalVersion tiene $(SourceRevisionId) anexado si la propiedad está presente. Puede deshabilitarse
mediante IncludeSourceRevisionInInformationalVersion .
Las propiedades Copyright y Description también se utilizan para metadatos de NuGet.
Configuration se comparte con todos los procesos de compilación y se establece mediante el parámetro
--configuration de los comandos dotnet .

GenerateAssemblyInfo
Un valor booleano que habilita o deshabilita toda la generación de AssemblyInfo. El valor predeterminado es true .
GeneratedAssemblyInfoFile
La ruta de acceso del archivo de información del ensamblado generado. De forma predeterminada, se trata de un
archivo del directorio $(IntermediateOutputPath) (obj).
Administración de dependencias en aplicaciones
.NET Core
16/09/2020 • 2 minutes to read • Edit Online

En este artículo se explica cómo agregar y quitar dependencias mediante la modificación del archivo de proyecto o
mediante la CLI.

El elemento <PackageReference>
El archivo del proyecto <PackageReference> tiene la estructura siguiente:

<PackageReference Include="PACKAGE_ID" Version="PACKAGE_VERSION" />

El atributo Include especifica el identificador del paquete que se va a agregar al proyecto. El atributo Version
especifica la versión que se va a obtener. Las versiones se especifican en función de las reglas de versión de NuGet.

NOTE
Si no está familiarizado con la sintaxis del archivo del proyecto, vea la documentación de Referencia de proyectos de MSBuild
para obtener más información.

Use condiciones para agregar una dependencia que solo está disponible en un destino específico, tal como se
muestra en el ejemplo siguiente:

<PackageReference Include="PACKAGE_ID" Version="PACKAGE_VERSION" Condition="'$(TargetFramework)' ==


'netcoreapp2.1'" />

En el ejemplo anterior, la dependencia solo será válida si la compilación sucede para ese destino dado. El elemento
$(TargetFramework) de la condición es una propiedad de MSBuild que se está configurando en el proyecto. En las
aplicaciones .NET Core más comunes, no es necesario hacer esto.

Adición de una dependencia mediante la edición del archivo del


proyecto
Para agregar una dependencia, agregue un elemento <PackageReference> dentro de un elemento <ItemGroup> .
Puede agregar a un elemento <ItemGroup> existente o crear uno. En el ejemplo siguiente se usa el proyecto de
aplicación de consola predeterminado creado por dotnet new console :
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.2" />
</ItemGroup>

</Project>

Adición de una dependencia mediante la CLI


Para agregar una dependencia, ejecute el comando dotnet add package, como se muestra en el ejemplo siguiente:

dotnet add package Microsoft.EntityFrameworkCore

Eliminación de una dependencia mediante la edición del archivo del


proyecto
Para quitar una dependencia, quite su elemento <PackageReference> del archivo del proyecto.

Eliminación de una dependencia mediante la CLI


Para quitar una dependencia, ejecute el comando dotnet remove package, como se muestra en el ejemplo
siguiente:

dotnet remove package Microsoft.EntityFrameworkCore

Vea también
Referencias de paquete en archivos de proyecto
Comando dotnet list package
Herramientas adicionales de .NET Core
16/09/2020 • 4 minutes to read • Edit Online

En esta sección se recopila una lista de herramientas compatibles con las funcionalidades de .NET Core y que las
amplían, además de las herramientas de la CLI de .NET Core.

Herramienta de desinstalación de .NET Core


La herramienta de desinstalación de .NET Core ( dotnet-core-uninstall ) permite limpiar los SDK y los entornos de
ejecución de .NET Core en un sistema, de modo que solo queden las versiones especificadas. Hay una colección de
opciones disponible para especificar las versiones que se van a desinstalar.

Herramientas de diagnóstico de .NET Core


dotnet-counters es una herramienta diseñada para la investigación del rendimiento y la supervisión del estado de
primer nivel.
dotnet-dump permite recopilar y analizar los volcados de Windows y Linux sin necesidad de un depurador nativo.
dotnet-gcdump permite recopilar volcados de memoria de GC (recolector de elementos no utilizados) de procesos
de .NET dinámicos.
dotnet-trace recopila datos de generación de perfiles de la aplicación que pueden ayudar en escenarios en los que
es necesario averiguar lo que causa que una aplicación se ejecute lentamente.

Herramienta WCF Web Service Reference


La herramienta WCF (Windows Communication Foundation) Web Service Reference es un proveedor de servicios
conectados de Visual Studio que se estrenó en la versión 15.5 de Visual Studio 2017. Esta herramienta recupera
metadatos de un servicio web en la solución actual, en una ubicación de red o desde un archivo WSDL. Genera un
archivo de código fuente compatible con .NET Core, que define una clase de proxy de WCF con métodos que se
pueden usar para acceder a las operaciones del servicio web.

Herramienta dotnet-svcutil de WCF


La herramienta dotnet-svcutil de WCF es una herramienta de .NET que recupera los metadatos de un servicio web
en una ubicación de red o en un archivo WSDL. Genera un archivo de código fuente compatible con .NET Core, que
define una clase de proxy de WCF con métodos que se pueden usar para acceder a las operaciones del servicio
web.
La herramienta dotnet-svcutil es una alternativa al proveedor de servicios conectados de Visual Studio WCF
Web Ser vice Reference , que se distribuyó por primera vez en la versión 15.5 de Visual Studio 2017. La
herramienta dotnet-svcutil , como herramienta de .NET, está disponible en Linux, macOS y Windows.

Herramienta dotnet-svcutil.xmlserializer de WCF


En .NET Framework, puede generar previamente un ensamblado de serialización con la herramienta svcutil. La
herramienta dotnet-svcutil.xmlserializer de WCF proporciona una funcionalidad parecida en .NET Core. Genera
previamente código de serialización de C# para los tipos de la aplicación cliente que usa el contrato de servicio de
WCF y que puede serializar XmlSerializer. Esto mejora el rendimiento de inicio de la serialización de XML al
serializar o deserializar objetos de esos tipos.
Generador de serializador XML
Tal y como sucede con el generador serializador de XML (sgen.exe) para .NET Framework, el paquete NuGet
Microsoft.XmlSerializer.Generator es la solución para las bibliotecas .NET Core y .NET Standard. Crea un
ensamblado de serialización de XML para los tipos contenidos en un ensamblado para mejorar el rendimiento de
inicio de la serialización XML al serializar o deserializar objetos de esos tipos con XmlSerializer.
Herramienta de desinstalación de .NET Core
16/09/2020 • 25 minutes to read • Edit Online

La herramienta de desinstalación de .NET Core ( dotnet-core-uninstall ) permite quitar los SDK y los entornos en
tiempo de ejecución de .NET Core de un sistema. Hay una colección de opciones disponible para especificar las
versiones que desea desinstalar.
La herramienta es compatible con Windows y macOS. Linux no se admite actualmente.
En Windows, la herramienta solo puede desinstalar los SDK y entornos en tiempo de ejecución que se instalaron
mediante uno de los siguientes instaladores:
El instalador del SDK y del entorno en tiempo de ejecución de .NET Core.
El instalador de Visual Studio en versiones anteriores a Visual Studio 2019, versión 16.3.
En macOS, la herramienta solo puede desinstalar los SDK y entornos en tiempo de ejecución ubicados en la
carpeta /usr/local/share/dotnet.
Debido a estas limitaciones, es posible que la herramienta no pueda desinstalar todos los SDK y los entornos en
tiempo de ejecución de .NET Core de la máquina. Puede usar el comando dotnet --info para buscar todos los
SDK y los entornos en tiempo de ejecución de .NET Core instalados, incluidos los entornos en tiempo de ejecución
y SDK que esta herramienta no puede quitar. El comando dotnet-core-uninstall list muestra qué SDK se pueden
desinstalar con la herramienta.

Instalación de la herramienta
Puede descargar la herramienta de desinstalación de .NET Core desde la página de versiones de la herramienta y
encontrar el código fuente en el repositorio dotnet/cli-lab de GitHub.

NOTE
La herramienta requiere elevación para desinstalar los SDK y los entornos en tiempo de ejecución de .NET Core. Por lo tanto,
debe instalarse en un directorio protegido contra escritura, como C:\Archivos de programa en Windows o /usr/local/bin en
macOS. Consulte también Acceso con privilegios elevados para comandos de dotnet. Para obtener más información, vea las
instrucciones de instalación detalladas.

Ejecución de la herramienta
En los pasos siguientes se muestra el enfoque recomendado para ejecutar la herramienta de desinstalación:
Paso 1: Mostrar los SDK y los entornos en tiempo de ejecución de .NET Core instalados
Paso 2: Realizar un simulacro
Paso 3: Desinstalar los SDK y los entornos en tiempo de ejecución de .NET Core
Paso 4: Eliminar la carpeta de reserva de NuGet (opcional)
Paso 1: Mostrar los SDK y los entornos en tiempo de ejecución de .NET Core instalados
El comando dotnet-core-uninstall list enumera los SDK y los entornos en tiempo de ejecución de .NET Core
instalados que se pueden quitar con esta herramienta. Visual Studio puede necesitar algunos SDK y entornos en
tiempo de ejecución, que se muestran con una nota de por qué no se recomienda desinstalarlos.
NOTE
En la mayoría de los casos, la salida del comando dotnet-core-uninstall list no coincidirá con la lista de versiones
instaladas en la salida de dotnet --info . En concreto, esta herramienta no mostrará las versiones instaladas mediante
archivos ZIP ni administradas por Visual Studio (cualquier versión instalada con Visual Studio 2019 16.3 o posterior). Una
manera de comprobar si una versión está administrada por Visual Studio es verla en Add or Remove Programs , donde las
versiones administradas de Visual Studio se marcan como tales en sus nombres para mostrar.

dotnet-core-uninstall list
Sinopsis

dotnet-core-uninstall list [options]

Opciones
Windows
macOS
--aspnet-runtime

Enumera todos los entornos en tiempo de ejecución de ASP.NET Core que se pueden desinstalar con esta
herramienta.
--hosting-bundle

Enumera todos los conjuntos de hospedaje de .NET Core que se pueden desinstalar con esta herramienta.
--runtime

Enumera todos los entornos en tiempo de ejecución de .NET Core que se pueden desinstalar con esta
herramienta.
--sdk

Enumera todos los SDK de .NET Core que se pueden desinstalar con esta herramienta.
-v, --verbosity <LEVEL>

Establece el nivel de detalle. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] , d[etailed] y
diag[nostic] . El valor predeterminado es normal .

--x64

Enumera todos los SDK y entornos en tiempo de ejecución de .NET Core x64 que se pueden desinstalar con
esta herramienta.
--x86

Enumera todos los SDK y entornos en tiempo de ejecución de .NET Core x86 que se pueden desinstalar con
esta herramienta.
Ejemplos
Enumerar todos los SDK y entornos en tiempo de ejecución de .NET Core que se pueden quitar con esta
herramienta:

dotnet-core-uninstall list

Enumerar todos los SDK y entornos en tiempo de ejecución de .NET Core x64:
dotnet-core-uninstall list --x64

Enumerar todos los SDK de .NET Core x86:

dotnet-core-uninstall list --sdk --x86

Paso 2: Realizar un simulacro


Los comandos dotnet-core-uninstall dry-run y dotnet-core-uninstall whatif muestran los SDK y los entornos en
tiempo de ejecución de .NET Core que se quitarán en función de las opciones proporcionadas sin realizar la
desinstalación. Estos comandos son sinónimos.
dotnet-core-uninstall dr y-run y dotnet-core-uninstall whatif
Sinopsis

dotnet-core-uninstall dry-run [options] [<VERSION>...]

dotnet-core-uninstall whatif [options] [<VERSION>...]

Argumentos
VERSION

La versión especificada que se va a desinstalar. Puede enumerar varias versiones una detrás de la otra,
separadas por espacios. También se admiten los archivos de respuesta.

TIP
Los archivos de respuesta son una alternativa a la colocación de todas las versiones en la línea de comandos. Son
archivos de texto, normalmente con una extensión *.rsp y cada versión aparece en una línea independiente. Para
especificar un archivo de respuesta para el argumento VERSION , use el carácter @ seguido inmediatamente del
nombre del archivo de respuesta.

Opciones
Windows
macOS
--all

Quita todos los SDK y entornos en tiempo de ejecución de .NET Core.


--all-below <VERSION>[ <VERSION>...]

Quita solo los SDK y los entornos en tiempo de ejecución de .NET Core que tienen una versión menor que la
versión especificada. La versión especificada permanece instalada.
--all-but <VERSIONS>[ <VERSION>...]

Quita todos los SDK y entornos en tiempo de ejecución de .NET Core, excepto las versiones especificadas.
--all-but-latest

Quita los SDK y los entornos en tiempo de ejecución de .NET Core, excepto la versión más alta.
--all-lower-patches

Quita los SDK y los entornos en tiempo de ejecución de .NET Core reemplazados por revisiones superiores.
Esta opción protege el archivo global.json.
--all-previews

Quita los SDK y los entornos en tiempo de ejecución de .NET Core marcados como versiones preliminares.
--all-previews-but-latest

Quita los SDK y los entornos en tiempo de ejecución de .NET Core marcados como versiones preliminares,
excepto la versión preliminar más alta.
--aspnet-runtime

Solo quita los entornos en tiempos de ejecución de ASP.NET Core.


--hosting-bundle

Quita el entorno en tiempo de ejecución de .NET Core y los conjuntos de hospedaje.


--major-minor <MAJOR_MINOR>

Quita los SDK y los entornos en tiempo de ejecución de .NET Core que coinciden con la versión
major.minor especificada.

--runtime

Solo quita los entornos en tiempo de ejecución de .NET Core.


--sdk

Solo quita los SDK de .NET Core.


-v, --verbosity <LEVEL>

Establece el nivel de detalle. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] , d[etailed] y
diag[nostic] . El valor predeterminado es normal .

--x64

Se debe usar con --sdk , --runtime y --aspnet-runtime para quitar los SDK o los entornos en tiempo de
ejecución x64.
--x86

Se debe usar con --sdk , --runtime y --aspnet-runtime para quitar los SDK o los entornos en tiempo de
ejecución x86.
--force Fuerza la eliminación de las versiones que Visual Studio puede usar.

Notas:
1. Se requiere exactamente uno de los valores --sdk , --runtime , --aspnet-runtime y --hosting-bundle .
2. --all , --all-below , --all-but , --all-but-latest , --all-lower-patches , --all-previews ,
--all-previews-but-latest , --major-minor y [<VERSION>...] son valores exclusivos.
3. Si no se especifica --x64 o --x86 , se quitarán tanto x64 como x86.
Ejemplos
NOTE
De forma predeterminada, los SDK y los entornos en tiempo de ejecución de .NET Core que puede necesitar Visual Studio u
otros SDK no se incluyen en la salida de dotnet-core-uninstall dry-run . En los siguientes ejemplos, es posible que
algunos de los SDK y entornos en tiempo de ejecución especificados no se incluyan en la salida, según el estado de la
máquina. Para incluir todos los SDK y entornos en tiempo de ejecución, agréguelos explícitamente como argumentos o use la
opción --force .

Realizar un simulacro de la eliminación de todos entornos en tiempo de ejecución de .NET Core que se han
reemplazado por revisiones superiores:

dotnet-core-uninstall dry-run --all-lower-patches --runtime

Realizar un simulacro de la eliminación de todos los SDK de .NET Core inferiores a la versión 2.2.301 :

dotnet-core-uninstall whatif --all-below 2.2.301 --sdk

Paso 3: Desinstalar los SDK y los entornos en tiempo de ejecución de .NET Core
dotnet-core-uninstall remove desinstala los SDK y los entornos en tiempo de ejecución de .NET Core especificados
por una colección de opciones. La herramienta no se puede usar para desinstalar los SDK y entornos en tiempo de
ejecución con la versión 5.0 o posterior.
Dado que esta herramienta tiene un comportamiento destructivo, es altamente recomendable que realice un
simulacro antes de ejecutar el comando remove. El simulacro le mostrará qué SDK y entornos en tiempo de
ejecución de .NET Core se quitarán cuando use el comando remove . Consulte ¿Puedo quitar una versión? para
saber qué SDK y entornos en tiempo de ejecución se pueden quitar de forma segura.
Cau t i on

Tenga en cuenta las siguientes advertencias:


Esta herramienta puede desinstalar las versiones del SDK de .NET Core que necesitan los archivos global.json
de la máquina. Puede volver a instalar los SDK de .NET Core desde la página Descarga de .NET Core.
Esta herramienta puede desinstalar las versiones del entorno en tiempo de ejecución de .NET Core que
necesitan las aplicaciones que dependen del marco de trabajo de la máquina. Puede volver a instalar los
entornos en tiempo de ejecución de .NET Core desde la página Descarga de .NET Core.
Esta herramienta puede desinstalar las versiones del SDK y del entorno en tiempo de ejecución de .NET Core en
las que se basa Visual Studio. Si interrumpe la instalación de Visual Studio, ejecute "Reparar" en el instalador de
Visual Studio para volver a un estado de funcionamiento.
De forma predeterminada, todos los comandos mantienen los SDK y los entornos en tiempo de ejecución de .NET
Core que puede necesitar Visual Studio u otros SDK. Estos SDK y entornos en tiempos de ejecución se pueden
desinstalar si se enumeran explícitamente como argumentos o mediante la opción --force .
La herramienta requiere elevación para desinstalar los SDK y los entornos en tiempo de ejecución de .NET Core.
Ejecute la herramienta en un símbolo del sistema de administrador en Windows y con sudo en macOS. Los
comandos dry-run y whatif no requieren elevación.
dotnet-core-uninstall remove
Sinopsis

dotnet-core-uninstall remove [options] [<VERSION>...]

Argumentos
VERSION

La versión especificada que se va a desinstalar. Puede enumerar varias versiones una detrás de la otra,
separadas por espacios. También se admiten los archivos de respuesta.

TIP
Los archivos de respuesta son una alternativa a la colocación de todas las versiones en la línea de comandos. Son
archivos de texto, normalmente con una extensión *.rsp y cada versión aparece en una línea independiente. Para
especificar un archivo de respuesta para el argumento VERSION , use el carácter @ seguido inmediatamente del
nombre del archivo de respuesta.

Opciones
Windows
macOS
--all

Quita todos los SDK y entornos en tiempo de ejecución de .NET Core.


--all-below <VERSION>[ <VERSION>...]

Quita solo los SDK y los entornos en tiempo de ejecución de .NET Core que tienen una versión menor que la
versión especificada. La versión especificada permanece instalada.
--all-but <VERSIONS>[ <VERSION>...]

Quita todos los SDK y entornos en tiempo de ejecución de .NET Core, excepto las versiones especificadas.
--all-but-latest

Quita los SDK y los entornos en tiempo de ejecución de .NET Core, excepto la versión más alta.
--all-lower-patches

Quita los SDK y los entornos en tiempo de ejecución de .NET Core reemplazados por revisiones superiores.
Esta opción protege el archivo global.json.
--all-previews

Quita los SDK y los entornos en tiempo de ejecución de .NET Core marcados como versiones preliminares.
--all-previews-but-latest

Quita los SDK y los entornos en tiempo de ejecución de .NET Core marcados como versiones preliminares,
excepto la versión preliminar más alta.
--aspnet-runtime

Solo quita los entornos en tiempos de ejecución de ASP.NET Core.


--hosting-bundle

Solo quita conjuntos de hospedaje de .NET Core.


--major-minor <MAJOR_MINOR>

Quita los SDK y los entornos en tiempo de ejecución de .NET Core que coinciden con la versión
major.minor especificada.

--runtime
Solo quita los entornos en tiempo de ejecución de .NET Core.
--sdk

Solo quita los SDK de .NET Core.


-v, --verbosity <LEVEL>

Establece el nivel de detalle. Los valores permitidos son q[uiet] , m[inimal] , n[ormal] , d[etailed] y
diag[nostic] . El valor predeterminado es normal .

--x64

Se debe usar con --sdk , --runtime y --aspnet-runtime para quitar los SDK o los entornos en tiempo de
ejecución x64.
--x86

Se debe usar con --sdk , --runtime y --aspnet-runtime para quitar los SDK o los entornos en tiempo de
ejecución x86.
-y, --yes Ejecuta el comando sin requerir una confirmación sí o no.
--force Fuerza la eliminación de las versiones que Visual Studio puede usar.
Notas:
1. Se requiere exactamente uno de los valores --sdk , --runtime , --aspnet-runtime y --hosting-bundle .
2. --all , --all-below , --all-but , --all-but-latest , --all-lower-patches , --all-previews ,
--all-previews-but-latest , --major-minor y [<VERSION>...] son valores exclusivos.
3. Si no se especifica --x64 o --x86 , se quitarán tanto x64 como x86.
Ejemplos

NOTE
De forma predeterminada, los SDK y los entornos en tiempo de ejecución de .NET Core que puede necesitar Visual Studio u
otros SDK se mantienen. En los siguientes ejemplos, es posible que algunos de los SDK y entornos en tiempo de ejecución
especificados se mantengan, según el estado de la máquina. Para quitar todos los SDK y entornos en tiempo de ejecución,
agréguelos explícitamente como argumentos o use la opción --force .

Quitar todos los entornos en tiempo de ejecución de .NET Core excepto la versión 3.0.0-preview6-27804-01
sin necesidad de la confirmación S/N:

dotnet-core-uninstall remove --all-but 3.0.0-preview6-27804-01 --runtime --yes

Quitar todos los SDK de .NET Core 1.1 sin necesidad de la confirmación de S/N:

dotnet-core-uninstall remove --sdk --major-minor 1.1 -y

Quitar el SDK de .NET Core 1.1.11 sin salida de consola:

dotnet-core-uninstall remove 1.1.11 --sdk --yes --verbosity q

Quitar todos los SDK de .NET Core que se puedan quitar con seguridad con esta herramienta:
dotnet-core-uninstall remove --all --sdk

Quitar todos los SDK de .NET Core que puede quitar esta herramienta, incluidos los SDK que puede
necesitar Visual Studio (no recomendado):

dotnet-core-uninstall remove --all --sdk --force

Quitar todos los SDK de .NET Core que se especifican en el archivo de respuesta versions.rsp

dotnet-core-uninstall remove --sdk @versions.rsp

El contenido de versions.rsp es el siguiente:

2.2.300
2.1.700

Paso 4: Eliminar la carpeta de reserva de NuGet (opcional)


En algunos casos, ya no necesita NuGetFallbackFolder y puede que desee eliminarlo. Para más información sobre
cómo eliminar esta carpeta, consulte Quitar NuGetFallbackFolder.

Desinstalación de la herramienta.
Windows
macOS
1. Abra Agregar o quitar programas .
2. Busque Microsoft .NET Core SDK Uninstall Tool .
3. Seleccione Desinstalar .
Uso de la herramienta WCF Web Service Reference
Provider
18/03/2020 • 6 minutes to read • Edit Online

Durante años, muchos desarrolladores de Visual Studio han disfrutado de la productividad que ofrecía la
herramienta Agregar referencia de ser vicio cuando sus proyectos de .NET Framework necesitaban acceder a
servicios web. La herramienta WCF Web Ser vice Reference Provider es una extensión de servicio conectada
de Visual Studio que proporciona una experiencia similar a la función Agregar referencia de servicio para
proyectos de .NET Core y ASP.NET Core. Esta herramienta recupera metadatos de un servicio web en la solución
actual, en una ubicación de red o desde un archivo WSDL, y genera un archivo de origen compatible con .NET
Core. El archivo contiene el código de proxy de cliente e Windows Communication Foundation (WCF) y podrá
usarlo para acceder al servicio web.

IMPORTANT
Solo debe hacer referencia a servicios desde un origen de confianza. Si agrega referencias desde un origen que no es de
confianza podría poner en peligro la seguridad.

Requisitos previos
Visual Studio 2017, versión 15.5 o versiones posteriores

Cómo utilizar la extensión


NOTE
La opción WCF Web Ser vice Reference (Referencia de servicio web de WCF) se puede aplicar a proyectos creados
mediante las siguientes plantillas de proyecto:
Visual C# > .NET Core
Visual C# > .NET Standard
Visual C# > Web > Aplicación web de ASP.NET Core

En este artículo se usa la plantilla de proyecto Aplicación web de ASP.NET Core para explicar cómo agregar
una referencia de servicio web de WCF al proyecto:
1. En el Explorador de soluciones, haga doble clic en el nodo Ser vicios conectados del proyecto (para un
proyecto de .NET Core o .NET Standard, esta opción está disponible cuando hace clic con el botón derecho
en el nodo Dependencias del proyecto en el Explorador de soluciones).
Aparece la página Ser vicios conectados , como se muestra en esta imagen:
2. En la página Ser vicios conectados , haga clic en Microsoft WCF Web Ser vice Reference Provider .
Se abrirá el asistente para Configurar referencia de ser vicio web de WCF :

3. Seleccione un servicio.
3a. Hay varias opciones de búsqueda de servicios disponibles en el asistente para Configurar referencia
de ser vicio web de WCF :
Para buscar servicios definidos en la solución actual, haga clic en el botón Detectar .
Para buscar servicios hospedados en una dirección específica, escriba la dirección URL del servicio en el
cuadro Dirección y haga clic en el botón Ir .
Para seleccionar un archivo WSDL que contiene la información de metadatos del servicio web, haga clic
en el botón Examinar .
3b. Seleccione el servicio de la lista de resultados de búsqueda en el cuadro Ser vicios . Si es necesario,
escriba el espacio de nombres para el código generado en el cuadro de texto Espacio de nombres
correspondiente.
3c. Haga clic en el botón Siguiente para abrir las páginas Opciones de tipo de datos y Opciones de
cliente . O bien, haga clic en el botón Finalizar para usar las opciones predeterminadas.
4. El formulario Opciones de tipo de datos permite ajustar los valores de configuración de la referencia de
servicio generada:

NOTE
La opción de la casilla Reutilizar tipos en los ensamblados a los que se hace referencia es útil cuando se
definen los tipos de datos necesarios para la generación de código de referencia de servicio en uno de los
ensamblados de referencia del proyecto. Es importante volver a usar esos tipos de datos existentes para evitar
problemas de tiempo de ejecución o conflictos de tipo de tiempo de compilación.

Puede haber un retraso mientras se carga la información de tipo, en función del número de dependencias
del proyecto y otros factores de rendimiento del sistema. El botón Finalizar está deshabilitado durante la
carga, a menos que la casilla Reutilizar tipos en los ensamblados a los que se hace referencia esté
desactivada.
5. Haga clic en Finalizar cuando haya terminado.
Mientras muestra el progreso, la herramienta:
Descarga los metadatos del servicio WCF.
Genera el código de referencia de servicio en un archivo con el nombre reference.cs y lo agrega al proyecto
bajo el nodo Ser vicios conectados .
Actualiza el archivo de proyecto (.csproj) con las referencias del paquete NuGet necesarias para compilarlo y
ejecutarlo en la plataforma de destino.
Cuando se completan estos procesos, puede crear una instancia del tipo de cliente WCF generado e invocar las
operaciones del servicio.

Vea también
Introducción a las aplicaciones de Windows Communication Foundation
Servicios de Windows Communication Foundation y servicios de datos WCF en Visual Studio
Características compatibles con WCF en .NET Core

Preguntas y comentarios
Si tiene alguna pregunta o comentario, notifíquelo en la Comunidad de desarrolladores mediante la herramienta
Notificar un problema.

Notas de la versión
Eche un vistazo a las notas de la versión para obtener información actualizada sobre la versión, incluidos los
problemas conocidos.
Herramienta dotnet-svcutil de WCF para .NET Core
16/09/2020 • 6 minutes to read • Edit Online

La herramienta dotnet-svcutil de Windows Communication Foundation (WCF) es una herramienta de .NET que
recupera metadatos de un servicio web en una ubicación de red o de un archivo WSDL, y genera una clase de WCF
que contiene métodos de proxy de cliente que acceden a las operaciones del servicio web.
Similar a la herramienta de utilidad de metadatos de Ser viceModel (Svcutil.exe) para proyectos de .NET
Framework, dotnet-svcutil es una herramienta de línea de comandos para generar una referencia de servicio
web compatible con proyectos de .NET Core y .NET Standard.
La herramienta dotnet-svcutil es una alternativa al proveedor de servicios conectados de Visual Studio WCF
Web Ser vice Reference que se distribuyó por primera vez en la versión 15.5 de Visual Studio 2017. La
herramienta multiplataforma dotnet-svcutil , como herramienta de .NET, está disponible en Linux, macOS y
Windows.

IMPORTANT
Solo debe hacer referencia a servicios desde un origen de confianza. Si agrega referencias desde un origen que no es de
confianza podría poner en peligro la seguridad.

Requisitos previos
dotnet-svcutil 2.x
dotnet-svcutil 1.x
SDK de .NET Core 2.1 o versiones posteriores
Su editor de código favorito

Introducción
En el ejemplo siguiente se le guía por los pasos necesarios para agregar una referencia de servicio web a un
proyecto web de .NET Core e invocar el servicio. Creará una aplicación web de .NET Core denominada HelloSvcutil
y agregará una referencia a un servicio web que implementa el siguiente contrato:

[ServiceContract]
public interface ISayHello
{
[OperationContract]
string Hello(string name);
}

En este ejemplo, se da por hecho que el servicio web se hospedará en la siguiente dirección:
http://contoso.com/SayHello.svc

Desde una ventana de comandos de Windows, Mac OS o Linux, siga estos pasos:
1. Cree un directorio denominado HelloSvcutil para el proyecto y hágalo su directorio actual, como en el
ejemplo siguiente:
mkdir HelloSvcutil
cd HelloSvcutil

2. Cree un nuevo proyecto web de C# en ese directorio mediante el comando dotnet new del modo siguiente:

dotnet new web

3. Instale el paquete NuGet dotnet-svcutil como herramienta CLI:


dotnet-svcutil 2.x
dotnet-svcutil 1.x

dotnet tool install --global dotnet-svcutil

4. Ejecute el comando dotnet-svcutil para generar el archivo de referencia del servicio web de la siguiente
manera:
dotnet-svcutil 2.x
dotnet-svcutil 1.x

dotnet-svcutil http://contoso.com/SayHello.svc

El archivo generado se guarda como HelloSvcutil/ServiceReference1/Reference.cs. La herramienta dotnet_svcutil


también agrega al proyecto los paquetes de WCF adecuados que necesita el código de proxy como referencias del
paquete.

Uso de la referencia de servicio


1. Restaure los paquetes de WCF mediante el comando dotnet restore como sigue:

dotnet restore

2. Busque el nombre de la clase de cliente y la operación que quiera usar. Reference.cs contendrá una clase
que se hereda de System.ServiceModel.ClientBase , con métodos que pueden usarse para llamar a las
operaciones del servicio. En este ejemplo, quiere llamar a la operación Hello del servicio SayHello.
ServiceReference.SayHelloClient es el nombre de la clase de cliente, y tiene un método llamado
HelloAsync que se puede usar para llamar a la operación.

3. Abra el archivo Startup.cs en el editor y agregue una directiva using al espacio de nombres de la
referencia de servicio en la parte superior:

using ServiceReference;

4. Edite el método Configure para invocar el servicio web. Para ello, cree una instancia de la clase que se
hereda de ClientBase y llame al método en el objeto de cliente:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.Run(async (context) =>


{
var client = new SayHelloClient();
var response = await client.HelloAsync();
await context.Response.WriteAsync(response);
});
}

5. Ejecute la aplicación con el comando dotnet run como sigue:

dotnet run

6. Vaya a la dirección URL indicada en la consola (por ejemplo, http://localhost:5000 ) en el explorador web.
Debería ver los siguientes resultados: "Hello dotnet-svcutil!"
Para obtener una descripción detallada de los parámetros de la herramienta dotnet-svcutil , invoque la
herramienta pasando el parámetro de ayuda del siguiente modo:
dotnet-svcutil 2.x
dotnet-svcutil 1.x

dotnet-svcutil --help

Preguntas y comentarios
Si tiene preguntas o comentarios, abra un problema en GitHub. También puede revisar las preguntas o problemas
que ya se han planteado en el repositorio de WCF en GitHub.

Notas de la versión
Eche un vistazo a las notas de la versión para obtener información actualizada sobre la versión, incluidos los
problemas conocidos.

Información
Paquete NuGet svcutil dotnet
Uso de dotnet-svcutil.xmlserializer en .NET Core
16/09/2020 • 3 minutes to read • Edit Online

El paquete NuGet dotnet-svcutil.xmlserializer puede generar previamente un ensamblado de serialización para


los proyectos de .NET Core. Genera previamente código de serialización de C# para los tipos de la aplicación
cliente que usa el contrato de servicio de WCF y que puede serializar XmlSerializer. Esto mejora el rendimiento de
inicio de la serialización de XML al serializar o deserializar objetos de esos tipos.

Requisitos previos
SDK de .NET Core 2.1 o versiones posteriores
Su editor de código favorito
Puede usar el comando dotnet --info para comprobar qué versiones del SDK de .NET Core y del Runtime ya ha
instalado.

Introducción
Para usar dotnet-svcutil.xmlserializer en una aplicación de consola de .NET Core:
1. Crea un servicio WCF denominado "MyWCFService" mediante la plantilla predeterminada "Aplicación del
servicio WCF" en .NET Framework. Agregue el atributo [XmlSerializerFormat] al método de servicio similar
al siguiente:

[ServiceContract]
public interface IService1
{
[XmlSerializerFormat]
[OperationContract(Action = "http://tempuri.org/IService1/GetData", ReplyAction =
"http://tempuri.org/IService1/GetDataResponse")]
string GetData(int value);
}

2. Cree una aplicación de consola de .NET Core como aplicación cliente de WCF que tenga como destino .NET
Core 2.1 o versiones posteriores. Por ejemplo, cree una aplicación denominada "MyWCFClient" con el
comando siguiente:

dotnet new console --name MyWCFClient

Para asegurarse de que el proyecto está destinado a .NET Core 2.1 o una versión posterior, inspeccione el
elemento XML TargetFramework del archivo de proyecto:

<TargetFramework>netcoreapp2.1</TargetFramework>

3. Ejecute el comando siguiente para agregar una referencia de paquete a System.ServiceModel.Http :

dotnet add package System.ServiceModel.Http

4. Agregue el código del cliente WCF:


using System.ServiceModel;

class Program
{
static void Main(string[] args)
{
var myBinding = new BasicHttpBinding();
var myEndpoint = new EndpointAddress("http://localhost:2561/Service1.svc"); //Fill your
service url here
var myChannelFactory = new ChannelFactory<IService1>(myBinding, myEndpoint);
IService1 client = myChannelFactory.CreateChannel();
string s = client.GetData(1);
((ICommunicationObject)client).Close();
}
}

[ServiceContract]
public interface IService1
{
[XmlSerializerFormat]
[OperationContract(Action = "http://tempuri.org/IService1/GetData", ReplyAction =
"http://tempuri.org/IService1/GetDataResponse")]
string GetData(int value);
}

5. Agregue una referencia al paquete dotnet-svcutil.xmlserializer mediante la ejecución del comando


siguiente:

dotnet add package dotnet-svcutil.xmlserializer

Al ejecutar el comando se debería agregar una entrada al archivo de proyecto similar a la siguiente:

<ItemGroup>
<DotNetCliToolReference Include="dotnet-svcutil.xmlserializer" Version="1.0.0" />
</ItemGroup>

6. Compile la aplicación ejecutando dotnet build . Si todo se realiza correctamente, se genera un ensamblado
denominado MyWCFClient.XmlSerializers.dll en la carpeta de salida. Si la herramienta no pudo generar el
ensamblado, verá advertencias en la salida de la compilación.
7. Inicie el servicio WCF, por ejemplo, mediante la ejecución de http://localhost:2561/Service1.svc en el
explorador. Después, inicie la aplicación cliente, que se cargará automáticamente y utilizará los
serializadores generados previamente en tiempo de ejecución.
Usar el generador de serializador XML de Microsoft
en .NET Core
16/09/2020 • 5 minutes to read • Edit Online

Este tutorial muestra cómo usar el generador de serializador XML de Microsoft en una aplicación .NET Core de C#.
Este tutorial ayuda a:
Cómo crear una aplicación .NET Core
Cómo agregar una referencia al paquete Microsoft.XmlSerializer.Generator
Cómo editar MyApp.csproj para agregar dependencias
Cómo agregar una clase y un XmlSerializer
Cómo compilar y ejecutar la aplicación
Tal y como sucede con el generador de serializador de XML (sgen.exe) para .NET Framework, el paquete
Microsoft.XmlSerializer.Generator de NuGet es el equivalente para proyectos de .NET Core y .NET Standard. Crea un
ensamblado de serialización de XML para los tipos contenidos en un ensamblado para mejorar el rendimiento de
inicio de la serialización XML al serializar o deserializar objetos de esos tipos con XmlSerializer.

Requisitos previos
Para realizar este tutorial:
SDK de .NET Core 2.1 o versiones posteriores.
Su editor de código favorito.

TIP
¿Es necesario instalar un editor de código? Pruebe Visual Studio.

Usar el generador de serializador de XML de Microsoft en una


aplicación de consola .NET Core
Las instrucciones siguientes muestran cómo usar el generador de serializador XML en una aplicación de consola
.NET Core.
Creación de una aplicación de consola .NET Core
Abra un símbolo del sistema y cree una carpeta denominada MyApp. Vaya a la carpeta que creó y escriba estos
comandos:

dotnet new console

Agregue una referencia al paquete Microsoft.XmlSerializer.Generator en el proyecto de MyApp


Use el comando dotnet add package para agregar la referencia en el proyecto.
Tipo:

dotnet add package Microsoft.XmlSerializer.Generator -v 1.0.0


Comprobar los cambios en MyApp.csproj después de agregar el paquete
Abra el editor de código para empezar. Todavía estamos trabajando desde el directorio MyApp en el que hemos
compilado la aplicación.
Abra MyApp.csproj en el editor de texto.
Después de ejecutar el comando dotnet add package , se agregan estas líneas al archivo de proyecto MyApp.csproj:

<ItemGroup>
<PackageReference Include="Microsoft.XmlSerializer.Generator" Version="1.0.0" />
</ItemGroup>

Agregar otra sección ItemGroup para admitir la herramienta CLI de .NET Core
Agregue estas líneas después de la sección ItemGroup que hemos inspeccionado:

<ItemGroup>
<DotNetCliToolReference Include="Microsoft.XmlSerializer.Generator" Version="1.0.0" />
</ItemGroup>

Agregar una clase en la aplicación


Abra Program.cs en el editor de texto. Agregue la clase con el nombre MyClass en Program.cs.

public class MyClass


{
public int Value;
}

Crear un XmlSerializer para MyClass


Agregue esta línea dentro de Main para crear un XmlSerializer para MyClass:

var serializer = new System.Xml.Serialization.XmlSerializer(typeof(MyClass));

Compilar y ejecutar la aplicación


Seguimos en la carpeta MyApp, desde donde vamos a ejecutar la aplicación mediante dotnet run . Se carga
automáticamente y usa los serializadores generados previamente en tiempo de ejecución.
Escriba el siguiente comando en la ventana de consola:

dotnet run

NOTE
dotnet run llama a dotnet build para asegurarse de que los destinos de la compilación se han creado y, después, llama a
dotnet <assembly.dll> para ejecutar la aplicación de destino.

IMPORTANT
Los comandos y los pasos que se muestran en este tutorial para ejecutar la aplicación se usan solo durante el desarrollo. Una
vez que esté listo para implementar la aplicación, eche un vistazo a las diferentes estrategias de implementación para
aplicaciones .NET Core y al comando dotnet publish .
Si todo se realiza correctamente, se genera un ensamblado con el nombre .dll MyApp.XmlSerializers.dll en la
carpeta de salida.
¡Enhorabuena! Acaba de:
Crear una aplicación .NET Core.
Agregar una referencia al paquete Microsoft.XmlSerializer.Generator.
Editar MyApp.csproj para agregar dependencias.
Agregue una clase y un XmlSerializer.
Compilar y ejecutar la aplicación.

Recursos relacionados
Introducción a la serialización XML
Serialización con XmlSerializer (C#)
Cómo: Serializar con XmlSerializer (Visual Basic)
¿Qué herramientas de diagnóstico están disponibles
en .NET Core?
16/09/2020 • 5 minutes to read • Edit Online

El comportamiento del software no siempre es el esperado, pero .NET Core tiene herramientas y API que lo
ayudarán a diagnosticar estos problemas de manera rápida y eficaz.
Este artículo lo ayuda a encontrar las distintas herramientas que necesita.

Depuradores administrados
Los depuradores administrados le permiten interactuar con el programa. Pausar, ejecutar de manera incremental,
examinar y reanudar le proporcionan información sobre el comportamiento del código. Un depurador es la
primera opción para diagnosticar problemas funcionales que se pueden reproducir de manera fácil.

Registro y seguimiento
El registro y seguimiento son técnicas relacionadas. Hacen referencia a la instrumentación del código para crear
archivos de registro. Los archivos registran los detalles de lo que hace un programa. Estos detalles se pueden usar
para diagnosticar los problemas más complejos. Cuando se combinan con marcas de tiempo, estas técnicas
también son valiosas para investigaciones de rendimiento.

Pruebas unitarias
Las pruebas unitarias son un componente clave de la integración continua y la implementación de software de alta
calidad. Las pruebas unitarias están diseñadas para brindarle una advertencia temprana cuando se daña algo.

Depuración de volcados de Linux


Depuración de volcados de Linux explica cómo recopilar y analizar volcados en Linux.

Herramientas globales de diagnóstico de .NET Core


dotnet-counters
dotnet-counters es una herramienta diseñada para la investigación del rendimiento y la supervisión del estado de
primer nivel. Permite observar los valores del contador de rendimiento que se publican a través de la API
EventCounter. Por ejemplo, puede supervisar rápidamente elementos como el uso de la CPU o la tasa de
excepciones que se generan en su aplicación .NET Core.
dotnet-dump
La herramienta dotnet-dump permite recopilar y analizar los volcados de Windows y Linux sin necesidad de un
depurador nativo.
dotnet-gcdump
La herramienta dotnet-gcdump permite recopilar volcados de memoria de GC (recolector de elementos no
utilizados) de procesos de .NET dinámicos.
dotnet-trace
.NET Core incluye lo que se denomina EventPipe , a través del cual se exponen los datos de diagnóstico. La
herramienta dotnet-trace permite consumir datos interesantes sobre la generación de perfiles a partir de su
aplicación, lo cual puede resultar útil para analizar la causa principal de que una aplicación se ejecute con lentitud.
dotnet-symbol
dotnet-symbol descarga archivos (símbolos, DAC/DBI, archivos de host, etc.) necesarios para abrir un volcado de
núcleo o minivolcado. Use esta herramienta si necesita símbolos y módulos para depurar un archivo de volcado
capturado en otro equipo.
dotnet-sos
dotnet-SOS se usa para instalar la extensión de depuración de SOS en Linux o MacOS (o en Windows si se usan
herramientas de depuración anteriores).

Tutoriales de diagnóstico de .NET Core


Depuración de una fuga de memoria
Tutorial: Depuración de una fuga de memoria le guía a través de la búsqueda de una fuga de memoria. La
herramienta dotnet-counters se usa para confirmar la fuga, y la herramienta dotnet-dump, para diagnosticarla.
Depuración del uso elevado de CPU
Tutorial: Depuración del uso elevado de CPU le guía a través de la investigación del uso elevado de CPU. Utiliza la
herramienta dotnet-counters para confirmar ese uso elevado. A continuación, se explica el uso del seguimiento de
la utilidad de análisis del rendimiento ( dotnet-trace ) o de Linux perf para recopilar y ver el perfil de uso de la
CPU.
Depuración de interbloqueo
Tutorial: Depuración de interbloqueo muestra cómo usar la herramienta dotnet-dump para investigar los
subprocesos y bloqueos.
Depuradores administrados de .Net Core
16/09/2020 • 2 minutes to read • Edit Online

Los depuradores permiten a los programas pausarse o ejecutarse paso a paso. Cuando se pausa, se puede ver el
estado actual del proceso. Al recorrer las secciones clave, se entiende mejor el código y el motivo por el que genera
determinado resultado.
Microsoft proporciona depuradores para código administrado en Visual Studio y Visual Studio Code .

Depurador administrado de Visual Studio


Visual Studio es un entorno de desarrollo integrado con el depurador más completo disponible. Visual Studio es
una opción excelente para los desarrolladores que trabajan en Windows.
Tutorial: Depuración de una aplicación .NET Core en Windows con Visual Studio
Aunque Visual Studio es una aplicación de Windows, se puede usar para depurar aplicaciones de Linux y macOS
de forma remota.
Depuración de una aplicación .NET Core en Linux/OSX con Visual Studio
La depuración de aplicaciones ASP.NET Core requiere instrucciones ligeramente distintas.
Depuración de aplicaciones ASP.NET Core en Visual Studio

Depurador administrado de Visual Studio Code


Visual Studio Code es un editor de código ligero y multiplataforma. Usa la misma implementación de depurador
de .NET Core que Visual Studio, pero con una interfaz de usuario simplificada.
Tutorial: Depuración de una aplicación .NET Core con Visual Studio
Debugging in Visual Studio Code (Depuración en Visual Studio Code)
EventCounters de .NET Core
16/09/2020 • 26 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.0 y versiones posteriores
Los EventCounters son las API de .NET Core que se usan para la recopilación ligera, multiplataforma y casi en
tiempo real de métricas de rendimiento. Los EventCounters se agregaron como una alternativa multiplataforma a
los "contadores de rendimiento" de .NET Framework en Windows. En este artículo se aprende qué son los
EventCounters, cómo se implementan y cómo se usan.
El entorno de ejecución de .NET Core y algunas bibliotecas de .NET publican información de diagnóstico básica
mediante EventCounters a partir de .NET Core 3.0. Además de los EventCounters proporcionados por el entorno de
ejecución de .NET, puede implementar sus propios EventCounters. Los EventCounters se pueden usar para realizar
el seguimiento de diversas métricas.
Los EventCounters existen como parte de EventSource y se insertan automáticamente en las herramientas de
escucha de forma periódica. Al igual que todos los demás eventos de EventSource, se pueden usar en proceso y
fuera de proceso mediante EventListener y EventPipe. Este artículo se centra en las capacidades multiplataforma de
los EventCounters y excluye de forma deliberada a PerfView y ETW (seguimiento de eventos para Windows),
aunque ambos se pueden usar con los EventCounters.

Out-of-proc

dotnet-counters
EventSource
EventPipe
dotnet-trace
EventCounter

EventCounter In-proc

EventCounter EventListener

Contadores disponibles
En los distintos paquetes de .NET, las métricas básicas sobre la recolección de elementos no utilizados (GC), Just-in-
Time (JIT), los ensamblados, las excepciones, los subprocesos, las redes y las solicitudes web se publican mediante
EventCounters.
Contadores "System.Runtime"
Los siguientes contadores se publican como parte del entorno de ejecución de .NET y se mantienen en
RuntimeEventSource.cs .

C O N TA DO R DESC RIP C IÓ N

% Time in GC since last GC ( time-in-gc ) Porcentaje de tiempo de GC desde la última GC

Allocation Rate ( alloc-rate ) Tasa de asignación en bytes


C O N TA DO R DESC RIP C IÓ N

CPU Usage ( cpu-usage ) Porcentaje de uso de CPU

Exception Count ( exception-count ) Número de excepciones que se han producido

GC Heap Size ( gc-heap-size ) Número de bytes que se considera que están asignados según
GC.GetTotalMemory(Boolean)

Gen 0 GC Count ( gen-0-gc-count ) Número de veces que se ha producido una GC para Gen 0

Gen 0 Size ( gen-0-size ) Número de bytes para la GC de Gen 0

Gen 1 GC Count ( gen-1-gc-count ) Número de veces que se ha producido una GC para Gen 1

Gen 1 Size ( gen-1-size ) Número de bytes para la GC de Gen 1

Gen 2 GC Count ( gen-2-gc-count ) Número de veces que se ha producido una GC para Gen 2

Gen 2 Size ( gen-2-size ) Número de bytes para la GC de Gen 2

LOH Size ( loh-size ) Número de bytes para la GC de Gen 3

Monitor Lock Contention Count ( Número de veces que ha habido contención al intentar tomar
monitor-lock-contention-count ) el bloqueo del monitor, según Monitor.LockContentionCount

Number of Active Timers ( active-timer-count ) Número de instancias de Timer que están activas actualmente,
según Timer.ActiveCount

Number of Assemblies Loaded ( assembly-count ) Número de instancias de Assembly cargadas en un proceso en


un momento dado

ThreadPool Completed Work Item Count ( Número de elementos de trabajo que se han procesado hasta
threadpool-completed-items-count ) ahora en ThreadPool

ThreadPool Queue Length ( threadpool-queue-length ) Número de elementos de trabajo que se encuentran


actualmente en cola para procesarse en ThreadPool

ThreadPool Thread Count ( threadpool-thread-count ) Número de subprocesos del grupo de subprocesos que
existen actualmente en ThreadPool, según
ThreadPool.ThreadCount

Working Set ( working-set ) Cantidad de memoria física asignada al contexto del proceso
en un momento dado según Environment.WorkingSet

Contadores "Microsoft.AspNetCore.Hosting"
Los siguientes contadores se publican como parte de ASP.NET Core y se mantienen en HostingEventSource.cs .

C O N TA DO R DESC RIP C IÓ N

Current Requests ( current-requests ) Número total de solicitudes que se han iniciado, pero que aún
no se han detenido
C O N TA DO R DESC RIP C IÓ N

Failed Requests ( failed-requests ) Número total de solicitudes erróneas que se han producido
durante la vida de la aplicación

Request Rate ( requests-per-second ) Número de solicitudes que se han producido por segundo

Total Requests ( total-requests ) Número total de solicitudes que se han producido durante la
vida de la aplicación

Contadores "Microsoft.AspNetCore.Http.Connections"
Los siguientes contadores se publican como parte de ASP.NET Core SignalR y se mantienen en
HttpConnectionsEventSource.cs .

C O N TA DO R DESC RIP C IÓ N

Average Connection Duration ( connections-duration ) Duración media de una conexión en milisegundos

Current Connections ( current-connections ) Número de conexiones activas que se han iniciado, pero que
aún no se han detenido

Total Connections Started ( connections-started ) Número total de conexiones que se han iniciado

Total Connections Stopped ( connections-stopped ) Número total de conexiones que se han detenido

Total Connections Timed Out ( connections-timed-out ) Número total de conexiones cuyo tiempo de espera se ha
agotado

Contadores "Microsoft-AspNetCore -Server-Kestrel"


Los siguientes contadores se publican como parte del servidor web Kestrel de ASP.NET Core y se mantienen en
KestrelEventSource.cs .

C O N TA DO R DESC RIP C IÓ N

Connection Queue Length ( connection-queue-length ) Longitud actual de la cola de conexión

Connection Rate ( connections-per-second ) Número de conexiones por segundo al servidor web

Current Connections ( current-connections ) Número actual de conexiones activas al servidor web

Current TLS Handshakes ( current-tls-handshakes ) Número actual de protocolos de enlace TLS

Current Upgraded Requests (WebSockets) ( Número actual de solicitudes actualizadas (WebSockets)


current-upgraded-requests )

Failed TLS Handshakes ( failed-tls-handshakes ) Número total de protocolos de enlace TLS erróneos

Request Queue Length ( request-queue-length ) Longitud actual de la cola de solicitudes

TLS Handshake Rate ( tls-handshakes-per-second ) Número de protocolos de enlace TLS por segundo
C O N TA DO R DESC RIP C IÓ N

Total Connections ( total-connections ) Número total de conexiones al servidor web

Total TLS Handshakes ( total-tls-handshakes ) Número total de protocolos de enlace TLS con el servidor web

Información general sobre la API EventCounter


Hay dos categorías principales de contadores. Algunos contadores son para valores de tipo "tasa", como el número
total de excepciones, el número total de GC y el número total de solicitudes. Otros contadores son valores de tipo
"instantánea", como el uso del montón, el uso de la CPU y el tamaño del espacio de trabajo. Dentro de cada una de
estas categorías de contadores, hay dos tipos de contadores que varían según el modo en que obtienen su valor.
Los contadores de sondeo recuperan su valor por medio de una devolución de llamada, mientras que los
contadores que no son de sondeo tienen sus valores establecidos directamente en la instancia de contador.
Los contadores se representan mediante las siguientes implementaciones:
EventCounter
IncrementingEventCounter
IncrementingPollingCounter
PollingCounter
Una escucha de eventos especifica la duración de los intervalos de medición. Al final de cada intervalo se transmite
un valor a la escucha de cada contador. Las implementaciones de un contador determinan las API y los cálculos que
se usan para generar el valor de cada intervalo.
1. EventCounter registra un conjunto de valores. El método EventCounter.WriteMetric agrega un nuevo valor al
conjunto. Con cada intervalo se calcula un resumen estadístico del conjunto, como valor mínimo, máximo y
medio. La herramienta dotnet-counters siempre muestra el valor medio. EventCounter es útil para describir
un conjunto discreto de operaciones. Su uso habitual puede incluir la supervisión del tamaño medio en
bytes de operaciones de E/S recientes o el valor monetario medio de un conjunto de transacciones
financieras.
2. IncrementingEventCounter registra un total acumulado de cada intervalo de tiempo. El método
IncrementingEventCounter.Increment agrega al total. Por ejemplo, si se llama a Increment() tres veces
durante un intervalo con valores 1 , 2 y 5 , el total acumulado de 8 se comunica como valor del
contador de este intervalo. La herramienta dotnet-counters muestra la tasa como el total / tiempo
registrado. IncrementingEventCounter resulta útil para medir la frecuencia con que se produce una acción,
como el número de solicitudes procesadas por segundo.
3. PollingCounter usa una devolución de llamada para determinar el valor que se comunica. Con cada intervalo
de tiempo se invoca a la función de devolución de llamada proporcionada por el usuario y se usa el valor
devuelto como valor del contador. PollingCounter se puede usar para consultar una métrica de un origen
externo, por ejemplo, para obtener los bytes libres actuales en un disco. También se puede usar para
notificar estadísticas personalizadas que una aplicación puede calcular a petición. Los ejemplos incluyen los
informes del percentil 95 de las latencias de solicitud recientes o la proporción de aciertos o errores actual
de una caché.
4. IncrementingPollingCounter usa una devolución de llamada para determinar el valor de incremento
comunicado. Con cada intervalo de tiempo se invoca a la devolución de llamada y la diferencia entre la
invocación actual y la última es el valor comunicado. La herramienta dotnet-counters siempre muestra la
diferencia como una tasa, el valor / tiempo comunicado. Este contador es útil cuando no resulta factible
llamar a una API en cada repetición, pero es posible consultar el número total de repeticiones. Por ejemplo,
puede notificar el número de bytes escritos en un archivo por segundo, incluso sin una notificación cada vez
que se escribe un byte.

Implementación de un EventSource
En el código siguiente se implementa un ejemplo EventSource expuesto como proveedor
"Sample.EventCounter.Minimal" con nombre. Este origen contiene un EventCounter que representa el tiempo de
procesamiento de la solicitud. Un contador de este tipo tiene un nombre (es decir, su identificador único en el
origen) y un nombre para mostrar, ambos usados por herramientas de escucha como dotnet-counter.

using System.Diagnostics.Tracing;

[EventSource(Name = "Sample.EventCounter.Minimal")]
public sealed class MinimalEventCounterSource : EventSource
{
public static readonly MinimalEventCounterSource Log = new MinimalEventCounterSource();

private EventCounter _requestCounter;

private MinimalEventCounterSource() =>


_requestCounter = new EventCounter("request-time", this)
{
DisplayName = "Request Processing Time",
DisplayUnits = "ms"
};

public void Request(string url, float elapsedMilliseconds)


{
WriteEvent(1, url, elapsedMilliseconds);
_requestCounter?.WriteMetric(elapsedMilliseconds);
}

protected override void Dispose(bool disposing)


{
_requestCounter?.Dispose();
_requestCounter = null;

base.Dispose(disposing);
}
}

Use dotnet-counters ps para mostrar una lista de los procesos de .NET que se pueden supervisar:

dotnet-counters ps
1398652 dotnet C:\Program Files\dotnet\dotnet.exe
1399072 dotnet C:\Program Files\dotnet\dotnet.exe
1399112 dotnet C:\Program Files\dotnet\dotnet.exe
1401880 dotnet C:\Program Files\dotnet\dotnet.exe
1400180 sample-counters C:\sample-counters\bin\Debug\netcoreapp3.1\sample-counters.exe

Pase el nombre del EventSource al modificador counter_list para iniciar la supervisión del contador:

dotnet-counters monitor --process-id 1400180 Sample.EventCounter.Minimal

En el siguiente ejemplo se muestra la salida de la supervisión:


Press p to pause, r to resume, q to quit.
Status: Running

[Samples-EventCounterDemos-Minimal]
Request Processing Time (ms) 0.445

Presione q para detener el comando de supervisión.


Contadores condicionales
Al implementar un EventSource, se puede crear de forma condicional una instancia de los contadores contenedores
al llamar al método EventSource.OnEventCommand con un valor Command de EventCommand.Enable . Para crear
una instancia de un contador de forma segura solo si es null , use el operador de asignación de uso combinado de
NULL. Además, los métodos personalizados pueden evaluar el método IsEnabled para determinar si el origen del
evento actual está habilitado o no.

using System.Diagnostics.Tracing;

[EventSource(Name = "Sample.EventCounter.Conditional")]
public sealed class ConditionalEventCounterSource : EventSource
{
public static readonly ConditionalEventCounterSource Log = new ConditionalEventCounterSource();

private EventCounter _requestCounter;

private ConditionalEventCounterSource() { }

protected override void OnEventCommand(EventCommandEventArgs args)


{
if (args.Command == EventCommand.Enable)
{
_requestCounter ??= new EventCounter("request-time", this)
{
DisplayName = "Request Processing Time",
DisplayUnits = "ms"
};
}
}

public void Request(string url, float elapsedMilliseconds)


{
if (IsEnabled())
{
_requestCounter?.WriteMetric(elapsedMilliseconds);
}
}

protected override void Dispose(bool disposing)


{
_requestCounter?.Dispose();
_requestCounter = null;

base.Dispose(disposing);
}
}

TIP
Los contadores condicionales son contadores de los que se crean instancias de forma condicional, una microoptimización. El
entorno de ejecución adopta este patrón para los escenarios en los que normalmente no se usan contadores, para ahorrar
una fracción de un milisegundo.
Contadores de ejemplo del entorno de ejecución de .NET Core
Hay muchas implementaciones de ejemplo excelentes del entorno de ejecución de .NET Core. Esta es la
implementación del entorno de ejecución del contador que realiza el seguimiento del tamaño del espacio de
trabajo de la aplicación.

var workingSetCounter = new PollingCounter(


"working-set",
this,
() => (double)(Environment.WorkingSet / 1_000_000))
{
DisplayName = "Working Set",
DisplayUnits = "MB"
};

PollingCounter comunica la cantidad actual de memoria física asignada al proceso (espacio de trabajo) de la
aplicación, ya que captura una métrica en un momento dado. La devolución de llamada del sondeo de un valor es
la expresión lambda proporcionada, que es simplemente una llamada a la API System.Environment.WorkingSet.
DisplayName y DisplayUnits son propiedades opcionales que se pueden establecer para ayudar al lado del
consumidor del contador a mostrar el valor con más claridad. Por ejemplo, dotnet-counters usa estas propiedades
para mostrar la versión más descriptiva de los nombres de contador.

IMPORTANT
Las propiedades DisplayName no están traducidas.

En el caso de PollingCounter e IncrementingPollingCounter, no es necesario hacer nada más. Ambos sondean los
valores por sí mismos en un intervalo solicitado por el consumidor.
Este es un ejemplo de un contador de entorno de ejecución implementado mediante IncrementingPollingCounter.

var monitorContentionCounter = new IncrementingPollingCounter(


"monitor-lock-contention-count",
this,
() => Monitor.LockContentionCount
)
{
DisplayName = "Monitor Lock Contention Count",
DisplayRateTimeScale = TimeSpan.FromSeconds(1)
};

IncrementingPollingCounter usa la API Monitor.LockContentionCount para comunicar el incremento del recuento


total de contenciones de bloqueo. La propiedad DisplayRateTimeScale es opcional pero, cuando se usa, puede
proporcionar una sugerencia sobre el intervalo de tiempo en el que se muestra mejor el contador. Por ejemplo, el
recuento de contenciones de bloqueo se muestra mejor como recuento por segundo, por lo que
DisplayRateTimeScale se establece en un segundo. La tasa de presentación se puede ajustar a diferentes tipos de
contadores de tasa.

NOTE
dotnet-counters no usa DisplayRateTimeScale, y no es necesario que las escuchas de eventos lo usen.

Hay más implementaciones de contador que se pueden usar como referencia en el repositorio del entorno de
ejecución de .NET.
Simultaneidad
TIP
La API de EventCounters no garantiza la seguridad para subprocesos. Cuando varios subprocesos llaman a los delegados
pasados a instancias PollingCounter o IncrementingPollingCounter, es responsabilidad suya garantizar la seguridad para
subprocesos de los delegados.

Por ejemplo, considere el siguiente EventSource para realizar un seguimiento de las solicitudes.

using System;
using System.Diagnostics.Tracing;

public class RequestEventSource : EventSource


{
public static readonly RequestEventSource Log = new RequestEventSource();

private IncrementingPollingCounter _requestRateCounter;


private int _requestCount = 0;

private RequestEventSource() =>


_requestRateCounter = new IncrementingPollingCounter("request-rate", this, () => _requestCount)
{
DisplayName = "Request Rate",
DisplayRateTimeScale = TimeSpan.FromSeconds(1)
};

public void AddRequest() => ++ _requestCount;

protected override void Dispose(bool disposing)


{
_requestRateCounter?.Dispose();
_requestRateCounter = null;

base.Dispose(disposing);
}
}

Se puede llamar al método AddRequest() desde un controlador de solicitudes y RequestRateCounter sondea el


valor en el intervalo especificado por el consumidor del contador. Pero varios subprocesos pueden llamar al
método AddRequest() a la vez, con lo que se coloca una condición de carrera sobre _requestCount . Una manera
alternativa segura para subprocesos de incrementar _requestCount es usar Interlocked.Increment.

public void AddRequest() => Interlocked.Increment(ref _requestCount);

Uso de EventCounters
Hay dos formas principales de usar EventCounters, en proceso o fuera de proceso. El uso de EventCounters se
puede clasificar en tres capas de distintas tecnologías de uso.
Transporte de eventos en una secuencia sin formato a través de ETW o EventPipe:
Las API de ETW están incluidas en el sistema operativo Windows y se puede acceder a EventPipe como
una API de .NET o el protocolo IPC de diagnóstico.
Descodificación del flujo de eventos binario en eventos:
La biblioteca TraceEvent controla los formatos de flujo de ETW y EventPipe.
Herramientas de línea de comandos y GUI:
Herramientas como PerfView (ETW o EventPipe), dotnet-counters (solo EventPipe) y dotnet-monitor (solo
EventPipe).
Uso fuera de proceso
El uso de EventCounters fuera de proceso es un enfoque muy común. Puede usar dotnet-counters para
consumirlos a modo multiplataforma a través de un EventPipe. La herramienta dotnet-counters es una
herramienta global de CLI de dotnet multiplataforma que se puede usar para supervisar los valores de los
contadores. Para obtener información sobre cómo usar dotnet-counters para supervisar los contadores, vea
dotnet-counters o trabaje con el tutorial Medición del rendimiento mediante EventCounters.
dotnet-trace
La herramienta dotnet-trace se puede usar para consumir los datos del contador a través de EventPipe. Este es un
ejemplo de uso de dotnet-trace para recopilar datos del contador.

dotnet-trace collect --process-id <pid> Sample.EventCounter.Minimal:0:0:EventCounterIntervalSec=1

Para obtener más información sobre cómo recopilar valores de un contador a lo largo del tiempo, vea la
documentación de dotnet-trace.
Azure Application Insights
Azure Monitor puede usar EventCounters, en concreto Azure Application Insights. Los contadores se pueden
agregar y quitar; además, el usuario puede especificar contadores personalizados o contadores conocidos. Para
obtener más información, vea Personalización de los contadores que se van a recopilar.
dotnet-monitor
dotnet-monitor es una herramienta experimental que facilita el acceso a la información de diagnóstico de un
proceso de .NET. Esta herramienta sirve como superconjunto de todas las herramientas de diagnóstico. Además de
ofrecer seguimientos, permite supervisar métricas, así como recopilar volcados de memoria y de memoria GC. Se
distribuye tanto como herramienta de la CLI como imagen de Docker. Expone una API de REST, y la recopilación de
artefactos de diagnóstico se produce a través de llamadas REST.
Para obtener más información, vea Presentación de dotnet-monitor, una herramienta experimental.
Uso en proceso
Puede usar los valores de un contador por medio de la API EventListener. EventListener es una forma en proceso
de usar los eventos escritos por todas las instancias de un EventSource en la aplicación. Para obtener más
información sobre cómo usar la API EventListener , vea EventListener.
En primer lugar, es necesario habilitar el EventSource que genera el valor del contador. Invalide el método
EventListener.OnEventSourceCreated para obtener una notificación cuando se cree un EventSource y, si es el
EventSource correcto con los EventCounters, puede llamar a EventListener.EnableEvents en él. Esta es una
invalidación de ejemplo:

protected override void OnEventSourceCreated(EventSource source)


{
if (!source.Name.Equals("System.Runtime"))
{
return;
}

EnableEvents(source, EventLevel.Verbose, EventKeywords.All, new Dictionary<string, string>()


{
["EventCounterIntervalSec"] = _intervalSec.ToString()
});
}

Código de ejemplo
Esta es una clase EventListener de ejemplo que imprime todos los nombres y valores de contador del EventSource
del entorno de ejecución de .NET, para publicar sus contadores internos ( System.Runtime ) en algún intervalo.
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;

public class SimpleEventListener : EventListener


{
private readonly int _intervalSec;

public int EventCount { get; private set; }

public SimpleEventListener(int intervalSec = 1) =>


_intervalSec = intervalSec <= 0
? throw new ArgumentException("Interval must be at least 1 second.", nameof(intervalSec))
: intervalSec;

protected override void OnEventSourceCreated(EventSource source)


{
if (!source.Name.Equals("System.Runtime"))
{
return;
}

EnableEvents(source, EventLevel.Verbose, EventKeywords.All, new Dictionary<string, string>()


{
["EventCounterIntervalSec"] = _intervalSec.ToString()
});
}

protected override void OnEventWritten(EventWrittenEventArgs eventData)


{
if (!eventData.EventName.Equals("EventCounters"))
{
return;
}

for (int i = 0; i < eventData.Payload.Count; ++ i)


{
if (eventData.Payload[i] is IDictionary<string, object> eventPayload)
{
var (counterName, counterValue) = GetRelevantMetric(eventPayload);
Console.WriteLine($"{counterName} : {counterValue}");
}
}
}

private static (string counterName, string counterValue) GetRelevantMetric(


IDictionary<string, object> eventPayload)
{
var counterName = "";
var counterValue = "";

if (eventPayload.TryGetValue("DisplayName", out object displayValue))


{
counterName = displayValue.ToString();
}
if (eventPayload.TryGetValue("Mean", out object value) ||
eventPayload.TryGetValue("Increment", out value))
{
counterValue = value.ToString();
}

return (counterName, counterValue);


}
}

Como se ha mostrado anteriormente, debe asegurarse de que el argumento "EventCounterIntervalSec" esté


establecido en el argumento filterPayload al llamar a EnableEvents. De lo contrario, los contadores no pueden
vaciar valores, ya que no saben en qué intervalo se debe hacer.

Consulte también
dotnet-counters
dotnet-trace
EventCounter
EventListener
EventSource
Registro y seguimiento de .NET Core
18/03/2020 • 7 minutes to read • Edit Online

El registro y el seguimiento son en realidad dos nombres para la misma técnica. Esta sencilla técnica se ha usado
desde el inicio de la era de la informática. Simplemente implica instrumentar una aplicación para escribir la salida
que se va a consumir más adelante.

Motivos para usar el registro y seguimiento


Esta técnica sencilla es sorprendentemente eficaz. Se puede usar en situaciones en las que se produzca un error en
un depurador:
Las incidencias que se producen durante largos períodos de tiempo pueden ser difíciles de depurar con un
depurador tradicional. Los registros permiten una revisión detallada de evaluación que abarca períodos largos
de tiempo. En cambio, los depuradores están restringidos a análisis en tiempo real.
Las aplicaciones multiproceso y las aplicaciones distribuidas a menudo son difíciles de depurar. Adjuntar un
depurador tiende a modificar los comportamientos. Los registros detallados se pueden analizar, según sea
necesario, para comprender sistemas complejos.
Las incidencias en las aplicaciones distribuidas pueden surgir de una interacción compleja entre muchos
componentes y puede que no sea razonable conectar un depurador en todas las partes del sistema.
Muchos servicios no deben estar detenidos. Al adjuntar un depurador a menudo se producen errores de
tiempo de expiración.
Las incidencias no siempre están previstas. El registro y seguimiento están diseñados para una baja sobrecarga,
de modo que los programas siempre pueden grabarse en caso de que se produzca una incidencia.

API de .NET Core


API de estilo de impresión
Cada una de las clases System.Console, System.Diagnostics.Trace y System.Diagnostics.Debug proporcionan API de
estilo de impresión parecidas para el registro.
La elección de la API de estilo de impresión que se va a usar depende de usted. Las diferencias clave son:
System.Console
Siempre está habilitada y siempre escribe en la consola.
Resulta útil para la información que es posible que el cliente necesite ver en la versión.
Dado que es el enfoque más sencillo, a menudo se usa para la depuración temporal ad hoc. Este código
de depuración a veces no se registra nunca en el control de código fuente.
System.Diagnostics.Trace
Solo se habilita cuando se define TRACE .
Escribe en el elemento Listeners adjuntado, de forma predeterminada, DefaultTraceListener.
Use esta API cuando cree registros que se vayan a habilitar en la mayoría de compilaciones.
System.Diagnostics.Debug
Solo se habilita cuando se define DEBUG .
Escribe en un depurador adjuntado.
En *nix , escribe en stderr si se establece COMPlus_DebugWriteToStdErr .
Use esta API cuando cree registros que se vayan a habilitar solo en las compilaciones de depuración.
Eventos de registro
Las siguientes API están más orientadas a eventos. En lugar de registrar cadenas sencillas, registran objetos de
evento.
System.Diagnostics.Tracing.EventSource
EventSource es la API de seguimiento de .NET Core de raíz principal.
Disponible en todas las versiones .NET Standard.
Solo permite el seguimiento de objetos serializables.
Escribe en los clientes de escucha de evento adjuntados.
.NET Core proporciona clientes de escucha para:
EventPipe de .NET Core en todas las plataformas
Seguimiento de eventos para Windows (ETW)
Marco de seguimiento de LTTng para Linux
System.Diagnostics.DiagnosticSource
Se incluye en .NET Core y como un paquete NuGet para .NET Framework.
Permite el seguimiento en curso de objetos no serializables.
Incluye un puente para permitir que los campos seleccionados de objetos registrados se escriban en un
elemento EventSource.
System.Diagnostics.Activity
Proporciona una manera definitiva de identificar los mensajes de registro resultantes de una actividad o
transacción específica. Este objeto se puede utilizar para correlacionar los registros entre distintos
servicios.
System.Diagnostics.EventLog
Solo Windows.
Escribe mensajes en el registro de eventos de Windows.
Los administradores del sistema esperan que aparezcan mensajes de error grave de aplicación en el
registro de eventos de Windows.

Plataformas de registro y de ILogger


Es posible que las API de bajo nivel no sean la opción adecuada para sus necesidades de registro. Puede que quiera
considerar la posibilidad de usar una plataforma de registro.
La interfaz de ILogger se ha utilizado para crear una interfaz de registro común en la que se pueden insertar los
registradores mediante la inserción de dependencias.
Por ejemplo, para que pueda elegir la mejor opción para la aplicación, ASP.NET ofrece compatibilidad con una
selección de marcos integrados y de terceros:
Proveedores de registro integrados de ASP.NET
Proveedores de registro de terceros de ASP.NET

Referencias relacionadas de registro


Compilación condicional con Trace y Debug
Adición de instrucciones de seguimiento al código de la aplicación
El registro de ASP.NET proporciona información general sobre las técnicas de registro que admite.
La interpolación de cadenas de C# puede simplificar la escritura de código de registro.
La propiedad Exception.Message es útil para las excepciones de registro.
La clase System.Diagnostics.StackTrace puede ser útil para proporcionar información de pila en los registros.

Consideraciones sobre el rendimiento


El formato de cadena puede tomar un tiempo de procesamiento de la CPU considerable.
En las aplicaciones críticas de rendimiento, se recomienda lo siguiente:
Evitar muchos registros cuando no haya nadie escuchando. Evitar la construcción de mensajes de registro
costosos comprobando en primer lugar si el registro está habilitado.
Registrar únicamente lo que sea útil.
Aplazar el formato sofisticado a la fase de análisis.
dotnet-counters
16/09/2020 • 5 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.0 y versiones posteriores

Instalación de dotnet-counters
Para instalar la versión de lanzamiento más reciente del paquete NuGet de dotnet-counters , use el
comando dotnet tool install:

dotnet tool install --global dotnet-counters

Sinopsis
dotnet-counters [-h|--help] [--version] <command>

Descripción
dotnet-counters es una herramienta de supervisión de rendimiento diseñada para la investigación del
rendimiento y la supervisión del estado de primer nivel ad hoc. Puede observar los valores del contador
de rendimiento que se publican a través de la API EventCounter. Por ejemplo, se pueden supervisar
rápidamente cosas como el uso de la CPU o la velocidad de las excepciones que se producen en la
aplicación .NET Core para ver si hay algo sospechoso antes de profundizar en una investigación de
rendimiento más seria mediante PerfView o dotnet-trace .

Opciones
--version

Muestra la versión de la utilidad dotnet-counters.


-h|--help

Muestra la ayuda de la línea de comandos.

Comandos
C O M A N DO

dotnet-counters collect

dotnet-counters list

dotnet-counters monitor

dotnet-counters ps
dotnet-counters collect
Recopila periódicamente los valores de contador seleccionados y los exporta a un formato de archivo
especificado para su posterior procesamiento.
Sinopsis

dotnet-counters collect [-h|--help] [-p|--process-id] [--refreshInterval] [counter_list] [--format]


[-o|--output]

Opciones
-p|--process-id <PID>

Id. del proceso que se va a supervisar.


--refresh-interval <SECONDS>

Número de segundos de retraso entre la actualización de los contadores mostrados.


counter_list <COUNTERS>

Una lista de contadores separados por espacios. Los contadores pueden ser
provider_name[:counter_name] especificados. Si provider_name se usa sin un elemento
counter_name calificado, se mostrarán todos los contadores. Para descubrir los nombres del
proveedor y del contador, use el comando dotnet-counters list.
--format <csv|json>

Formato que se va a exportar. Actualmente disponibles: csv, json.


-o|--output <output>

Nombre del archivo de salida.


Ejemplos
Recopilación de todos los contadores en un intervalo de actualización de tres segundos y
generación de un archivo CSV como salida:

> dotnet-counters collect --process-id 1902 --refresh-interval 3 --format csv

counter_list is unspecified. Monitoring all counters by default.


Starting a counter session. Press Q to quit.

dotnet-counters list
Muestra una lista de nombres y descripciones de contador, agrupada por proveedor.
Sinopsis

dotnet-counters list [-h|--help]

Ejemplo
> dotnet-counters list
Showing well-known counters only. Specific processes may support additional counters.

System.Runtime
cpu-usage Amount of time the process has utilized the CPU
(ms)
working-set Amount of working set used by the process (MB)
gc-heap-size Total heap size reported by the GC (MB)
gen-0-gc-count Number of Gen 0 GCs / min
gen-1-gc-count Number of Gen 1 GCs / min
gen-2-gc-count Number of Gen 2 GCs / min
time-in-gc % time in GC since the last GC
gen-0-size Gen 0 Heap Size
gen-1-size Gen 1 Heap Size
gen-2-size Gen 2 Heap Size
loh-size LOH Heap Size
alloc-rate Allocation Rate
assembly-count Number of Assemblies Loaded
exception-count Number of Exceptions / sec
threadpool-thread-count Number of ThreadPool Threads
monitor-lock-contention-count Monitor Lock Contention Count
threadpool-queue-length ThreadPool Work Items Queue Length
threadpool-completed-items-count ThreadPool Completed Work Items Count
active-timer-count Active Timers Count

Microsoft.AspNetCore.Hosting
requests-per-second Request rate
total-requests Total number of requests
current-requests Current number of requests
failed-requests Failed number of requests

NOTE
Los contadores de Microsoft.AspNetCore.Hosting se muestran cuando hay procesos identificados que
admiten estos contadores, por ejemplo, cuando una aplicación ASP.NET Core se está ejecutando en el equipo host.

dotnet-counters monitor
Muestra la actualización periódica de los valores de los contadores seleccionados.
Sinopsis

dotnet-counters monitor [-h|--help] [-p|--process-id] [--refreshInterval] [counter_list]

Opciones
-p|--process-id <PID>

Id. del proceso que se va a supervisar.


--refresh-interval <SECONDS>

Número de segundos de retraso entre la actualización de los contadores mostrados.


counter_list <COUNTERS>

Una lista de contadores separados por espacios. Los contadores pueden ser
provider_name[:counter_name] especificados. Si provider_name se usa sin un elemento
counter_name calificado, se mostrarán todos los contadores. Para descubrir los nombres del
proveedor y del contador, use el comando dotnet-counters list.
Ejemplos
Supervisión de todos los contadores de System.Runtime con un intervalo de actualización de 3
segundos:

> dotnet-counters monitor --process-id 1902 --refresh-interval 3 System.Runtime

Press p to pause, r to resume, q to quit.


System.Runtime:
CPU Usage (%) 24
Working Set (MB) 1982
GC Heap Size (MB) 811
Gen 0 GC / second 20
Gen 1 GC / second 4
Gen 2 GC / second 1
Number of Exceptions / sec 4

Supervisión de únicamente el uso de la CPU y el tamaño del montón de GC de System.Runtime :

> dotnet-counters monitor --process-id 1902 System.Runtime[cpu-usage,gc-heap-size]

Press p to pause, r to resume, q to quit.


System.Runtime:
CPU Usage (%) 24
GC Heap Size (MB) 811

Supervisión de los valores EventCounter del elemento EventSource definido por el usuario. Para
obtener más información, consulte Tutorial: Medición del rendimiento mediante EventCounters en
.NET Core.

> dotnet-counters monitor --process-id 1902 Samples-EventCounterDemos-Minimal

Press p to pause, r to resume, q to quit.


request 100

dotnet-counters ps
Muestra una lista de los procesos de dotnet que se pueden supervisar.
Sinopsis

dotnet-counters ps [-h|--help]

Ejemplo

> dotnet-counters ps

15683 WebApi /home/suwhang/repos/WebApi/WebApi


16324 dotnet /usr/local/share/dotnet/dotnet
Utilidad de recopilación y análisis de volcado de
memoria (dotnet-dump)
16/09/2020 • 9 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.0 y versiones posteriores

NOTE
dotnet-dump no se admite en macOS.

Instalación de dotnet-dump
Para instalar la versión de lanzamiento más reciente del paquete NuGet de dotnet-dump , use el comando
dotnet tool install:

dotnet tool install -g dotnet-dump

Sinopsis
dotnet-dump [-h|--help] [--version] <command>

Descripción
La herramienta global dotnet-dump es una forma de recopilar y analizar los volcados de Windows y Linux sin
necesidad de un depurador nativo implicado, como lldb en Linux. Esta herramienta es importante en
plataformas como Alpine Linux, donde no está disponible una versión de lldb totalmente operativa. La
herramienta dotnet-dump permite ejecutar comandos SOS para analizar bloqueos y el recolector de elementos
no utilizados (GC), pero no es un depurador nativo, por lo que no se admiten elementos como la visualización
de marcos de pila nativos.

Opciones
--version

Muestra la versión de la utilidad dotnet-dump.


-h|--help

Muestra la ayuda de la línea de comandos.

Comandos
C O M A N DO

dotnet-dump collect
C O M A N DO

dotnet-dump analyze

dotnet-dump collect
Captura un volcado de un proceso.
Sinopsis

dotnet-dump collect [-h|--help] [-p|--process-id] [--type] [-o|--output] [--diag]

Opciones
-h|--help

Muestra la ayuda de la línea de comandos.


-p|--process-id <PID>

Especifica el número de id. de proceso del que se va a recopilar un volcado de memoria.


--type <Heap|Mini>

Especifica el tipo de volcado, que determina los tipos de información que se recopilan del proceso. Hay
dos tipos:
Heap : un volcado grande y relativamente completo que contiene listas de módulos, listas de
subprocesos, todas las pilas, información de excepción, información de control y toda la memoria
excepto las imágenes asignadas.
Mini : un volcado pequeño que contiene listas de módulos, listas de subprocesos, información de
excepción y todas las pilas.
Si no se especifica, el valor predeterminado es Heap .
-o|--output <output_dump_path>

La ruta de acceso completa y el nombre de archivo donde se debe escribir el volcado recopilado.
Si no se especifica:
El valor predeterminado es .\dump_AAAAMMDD_HHMMSS.dmp en Windows.
El valor predeterminado es ./core_AAAAMMDD_HHMMSS en Linux.
AAAAMMDD es año/mes/día y HHMMSS es hora/minuto/segundo.
--diag

Habilita el registro de diagnóstico de la recopilación de volcado.

dotnet-dump analyze
Inicia un shell interactivo para explorar un volcado. El shell acepta varios comandos SOS.
Sinopsis

dotnet-dump analyze <dump_path> [-h|--help] [-c|--command]

Argumentos
<dump_path>

Especifica la ruta de acceso al archivo de volcado que se va a analizar.


Opciones
-c|--command <debug_command>

Especifica el comando que se va a ejecutar en el shell al inicio.


Análisis de comandos SOS
C O M A N DO F UN C IÓ N

soshelp Muestra todos los comandos disponibles.

soshelp|help <command> Muestra el comando especificado.

exit|quit Sale del modo interactivo.

clrstack <arguments> Proporciona un seguimiento de pila del código administrado


únicamente.

clrthreads <arguments> Enumera los subprocesos administrados que se ejecutan.

dumpasync <arguments> Muestra información sobre las máquinas de estado


asincrónicas en el montón de recolección de elementos no
utilizados.

dumpassembly <arguments> Muestra detalles sobre un ensamblado.

dumpclass <arguments> Muestra información sobre una estructura de clase EE en la


dirección especificada.

dumpdelegate <arguments> Muestra información sobre un delegado.

dumpdomain <arguments> Muestra información sobre todos los dominios de aplicación


y sobre todos los ensamblados en los dominios.

dumpheap <arguments> Muestra información sobre el montón de recolección de


elementos no utilizados y estadísticas de recolección de los
objetos.

dumpil <arguments> Muestra el Lenguaje intermedio de Microsoft (MSIL) que


está asociado a un método administrado.

dumplog <arguments> Escribe el contenido de un registro de esfuerzo existente en


memoria en el archivo especificado.

dumpmd <arguments> Muestra información sobre una estructura MethodDesc en


la dirección especificada.

dumpmodule <arguments> Muestra información sobre una estructura de módulo EE en


la dirección especificada.

dumpmt <arguments> Muestra información sobre una tabla de métodos en la


dirección especificada.
C O M A N DO F UN C IÓ N

dumpobj <arguments> Muestra información sobre un objeto en la dirección


especificada.

dso|dumpstackobjects <arguments> Muestra todos los objetos administrados que se han


encontrado dentro de los límites de la pila actual.

eeheap <arguments> Muestra información sobre la memoria de proceso que usan


las estructuras de datos internas del runtime.

finalizequeue <arguments> Muestra todos los objetos registrados para su finalización.

gcroot <arguments> Muestra información acerca de las referencias (o raíces) a un


objeto en la dirección especificada.

gcwhere <arguments> Muestra la ubicación en el montón de recolección de


elementos no utilizados del argumento que se ha pasado.

ip2md <arguments> Muestra la estructura MethodDesc en la dirección


especificada en código JIT.

histclear <arguments> Libera los recursos usados por la familia de comandos


hist* .

histinit <arguments> Inicializa las estructuras SOS del registro de esfuerzo


guardado en el código que se está depurando.

histobj <arguments> Muestra las reubicaciones de registro de esfuerzo de la


recolección de elementos no utilizados relacionadas con
<arguments> .

histobjfind <arguments> Muestra todas las entradas de registro que hacen referencia
a un objeto en la dirección especificada.

histroot <arguments> Muestra información relacionada con las promociones y las


reubicaciones de la raíz especificada.

lm|modules Muestra los módulos nativos del proceso.

name2ee <arguments> Muestra la estructura MethodTable y la estructura EEClass


para <argument> .

pe|printexception <arguments> Muestra cualquier objeto que se deriva de la clase Exception


en la dirección <argument> .

setsymbolserver <arguments> Habilita la compatibilidad con el servidor de símbolos.

syncblk <arguments> Muestra la información del contenedor de SyncBlock.

threads|setthread <threadid> Establece o muestra el identificador del subproceso actual


para los comandos SOS.

Uso de dotnet-dump
El primer paso es recopilar un volcado. Este paso se puede omitir si ya se ha generado un volcado principal. El
sistema operativo o la característica de generación de volcado integrada del runtime de .NET Core pueden crear
volcados principales.

$ dotnet-dump collect --process-id 1902


Writing minidump to file ./core_20190226_135837
Written 98983936 bytes (24166 pages) to core file
Complete

Ahora analice el volcado principal con el comando analyze :

$ dotnet-dump analyze ./core_20190226_135850


Loading core dump: ./core_20190226_135850
Ready to process analysis commands. Type 'help' to list available commands or 'help [command]' to get
detailed help on a command.
Type 'quit' or 'exit' to exit the session.
>

Esta acción abre una sesión interactiva que acepta comandos como los siguientes:

> clrstack
OS Thread Id: 0x573d (0)
Child SP IP Call Site
00007FFD28B42C58 00007fb22c1a8ed9 [HelperMethodFrame_PROTECTOBJ: 00007ffd28b42c58]
System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Object[], System.Signature, Boolean, Boolean)
00007FFD28B42DD0 00007FB1B1334F67 System.Reflection.RuntimeMethodInfo.Invoke(System.Object,
System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[],
System.Globalization.CultureInfo) [/root/coreclr/src/mscorlib/src/System/Reflection/RuntimeMethodInfo.cs @
472]
00007FFD28B42E20 00007FB1B18D33ED SymbolTestApp.Program.Foo4(System.String)
[/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 54]
00007FFD28B42ED0 00007FB1B18D2FC4 SymbolTestApp.Program.Foo2(Int32, System.String)
[/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 29]
00007FFD28B42F00 00007FB1B18D2F5A SymbolTestApp.Program.Foo1(Int32, System.String)
[/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 24]
00007FFD28B42F30 00007FB1B18D168E SymbolTestApp.Program.Main(System.String[])
[/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 19]
00007FFD28B43210 00007fb22aa9cedf [GCFrame: 00007ffd28b43210]
00007FFD28B43610 00007fb22aa9cedf [GCFrame: 00007ffd28b43610]

Para ver una excepción no controlada que ha terminado la aplicación:


> pe -lines
Exception object: 00007fb18c038590
Exception type: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
InnerException: System.Exception, Use !PrintException 00007FB18C038368 to see more.
StackTrace (generated):
SP IP Function
00007FFD28B42DD0 0000000000000000
System.Private.CoreLib.dll!System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Object[],
System.Signature, Boolean, Boolean)
00007FFD28B42DD0 00007FB1B1334F67
System.Private.CoreLib.dll!System.Reflection.RuntimeMethodInfo.Invoke(System.Object,
System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[],
System.Globalization.CultureInfo)+0xa7
[/root/coreclr/src/mscorlib/src/System/Reflection/RuntimeMethodInfo.cs @ 472]
00007FFD28B42E20 00007FB1B18D33ED SymbolTestApp.dll!SymbolTestApp.Program.Foo4(System.String)+0x15d
[/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 54]
00007FFD28B42ED0 00007FB1B18D2FC4 SymbolTestApp.dll!SymbolTestApp.Program.Foo2(Int32, System.String)+0x34
[/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 29]
00007FFD28B42F00 00007FB1B18D2F5A SymbolTestApp.dll!SymbolTestApp.Program.Foo1(Int32, System.String)+0x3a
[/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 24]
00007FFD28B42F30 00007FB1B18D168E SymbolTestApp.dll!SymbolTestApp.Program.Main(System.String[])+0x6e
[/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 19]

StackTraceString: <none>
HResult: 80131604

Instrucciones especiales para Docker


Si ejecuta en Docker, la recopilación de volcados requiere capacidades de SYS_PTRACE ( --cap-add=SYS_PTRACE o
--privileged ).

En Microsoft SDK de .NET Core imágenes de Docker de SDK de Linux, algunos comandos dotnet-dump pueden
producir la siguiente excepción:

Excepción no controlada: System.DllNotFoundException: No se puede cargar la biblioteca compartida


"libdl.so" o una de su excepción de dependencias.

Para solucionar este problema, instale el paquete "libc6-dev".

Vea también
Entrada de blog sobre la recopilación y el análisis de volcados de memoria
Herramienta de análisis del montón (dotnet-gcdump)
Herramienta de análisis del montón (dotnet-gcdump)
16/09/2020 • 5 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.1 y versiones posteriores

Instalación de dotnet-gcdump
Para instalar la versión de lanzamiento más reciente del paquete NuGet de dotnet-gcdump , use el comando dotnet
tool install:

dotnet tool install -g dotnet-gcdump

Sinopsis
dotnet-gcdump [-h|--help] [--version] <command>

Descripción
La herramienta global dotnet-gcdump permite recopilar volcados de memoria de GC (recolector de elementos no
utilizados) de procesos de .NET dinámicos. Usa la tecnología EventPipe, que es una alternativa multiplataforma a
ETW en Windows. Los volcados de memoria de GC se crean desencadenando un GC en el proceso de destino,
activando eventos especiales y regenerando el gráfico de raíces de objeto a partir del flujo de eventos. Este
proceso permite recopilar volcados de memoria de GC mientras el proceso se está ejecutando y con una
sobrecarga mínima. Estos volcados de memoria son útiles para varios escenarios:
Comparar el número de objetos del montón en varios puntos en el tiempo.
Analizar raíces de objetos (responder a preguntas como "¿qué sigue teniendo una referencia a este tipo?").
Recopilar estadísticas generales sobre los recuentos de objetos en el montón.
Ver el volcado de memoria de GC capturado por dotnet-gcdump
En Windows, los archivos .gcdump se pueden ver en PerfView o en Visual Studio para analizarlos. Actualmente, no
es posible abrir un archivo .gcdump en plataformas que no sean de Windows.
Puede recopilar varios archivos .gcdump y abrirlos simultáneamente en Visual Studio para obtener una
comparativa.

Opciones
--version

Muestra la versión del servicio dotnet-gcdump .


-h|--help

Muestra la ayuda de la línea de comandos.

dotnet-gcdump collect
Recopila un volcado de memoria de GC de un proceso que se está ejecutando actualmente.
Sinopsis

dotnet-gcdump collect [-h|--help] [-p|--process-id <pid>] [-o|--output <gcdump-file-path>] [-v|--verbose] [-


t|--timeout <timeout>] [-n|--name <name>]

Opciones
-h|--help

Muestra la ayuda de la línea de comandos.


-p|--process-id <pid>

Identificador del proceso del que se va a recopilar el volcado de memoria de GC.


-o|--output <gcdump-file-path>

Ruta de acceso donde se deben escribir los volcados de memoria de GC recopilados. El valor
predeterminado es .\AAAAMMDD_HHMMSS_<pid>.gcdump.
-v|--verbose

Obtener resultados del registro mientras recopila el volcado de memoria de GC.


-t|--timeout <timeout>

Dejar de recopilar el volcado de memoria de GC si tarda más de la cantidad de segundos indicada. El valor
predeterminado es 30.
-n|--name <name>

Nombre del proceso del que se va a recopilar el volcado de memoria de GC.

dotnet-gcdump ps
Enumera los procesos de dotnet de los que se pueden recopilar volcados de memoria de GC.
Sinopsis

dotnet-gcdump ps

dotnet-gcdump report <gcdump_filename>


Crear un informe a partir de un volcado de memoria de GC generado anteriormente o de un proceso en ejecución
y escribir en stdout .
Sinopsis

dotnet-gcdump report [-h|--help] [-p|--process-id <pid>] [-t|--report-type <HeapStat>]

Opciones
-h|--help

Muestra la ayuda de la línea de comandos.


-p|--process-id <pid>

Identificador del proceso del que se va a recopilar el volcado de memoria de GC.


-t|--report-type <HeapStat>

Tipo de informe que se va a generar. Opciones disponibles: heapstat (predeterminado).

Solución de problemas
No hay información de tipo en gcdump.
Antes de .NET Core 3.1, se producía un problema por el que una memoria caché de tipos no se borraba
entre varios gcdump cuando se invocaba con EventPipe. El resultado fue que los eventos necesarios para
determinar la información de tipo no se enviaban al segundo gcdump y a los siguientes. Esto se corrigió en
la versión preliminar 2 de .NET Core 3.1.
Los tipos COM y estáticos no se encuentran en el volcado de memoria de GC.
Antes de la versión preliminar 2 de .NET Core 3.1, se producía un problema por el que los tipos estáticos y
COM no se enviaban cuando se invocaba el volcado de memoria de GC a través de EventPipe. Esto se ha
corregido en la versión preliminar 2 de .NET Core 3.1.
Utilidad de análisis de rendimiento dotnet-trace
16/09/2020 • 8 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.0 y versiones posteriores

Instalación de dotnet-trace
Instale el paquete NuGet dotnet-trace con el comando dotnet tool install:

dotnet tool install --global dotnet-trace

Sinopsis
dotnet-trace [-h, --help] [--version] <command>

Descripción
La herramienta dotnet-trace :
Es una herramienta de .NET Core para varias plataformas.
Habilita la recolección de seguimientos de .NET Core de un proceso en ejecución sin un generador de
perfiles nativo.
Se basa en la tecnología EventPipe multiplataforma del entorno de ejecución de .NET Core.
Ofrece la misma experiencia en Windows, Linux o macOS.

Opciones
-h|--help

Muestra la ayuda de la línea de comandos.


--version

Muestra la versión de la utilidad dotnet-trace.

Comandos
C O M A N DO

dotnet-trace collect

dotnet-trace convert

dotnet-trace ps

dotnet-trace list-profiles
dotnet-trace collect
Recopila un seguimiento de diagnóstico de un proceso en ejecución.
Sinopsis

dotnet-trace collect [--buffersize <size>] [--clreventlevel <clreventlevel>] [--clrevents <clrevents>]


[--format <Chromium|NetTrace|Speedscope>] [-h|--help]
[-n, --name <name>] [-o|--output <trace-file-path>] [-p|--process-id <pid>]
[--profile <profile-name>] [--providers <list-of-comma-separated-providers>]

Opciones
--buffersize <size>

Establece el tamaño del búfer circular en memoria, en megabytes. Valor predeterminado de 256 MB.
--clreventlevel <clreventlevel>

Nivel de detalle de los eventos CLR que se van a emitir.


--clrevents <clrevents>

Lista de eventos de tiempo de ejecución de CLR que se van a emitir.


--format {Chromium|NetTrace|Speedscope}

Establece el formato de salida para la conversión del archivo de seguimiento. De manera


predeterminada, es NetTrace .
-n, --name <name>

Nombre del proceso del que se va a recopilar el seguimiento.


-o|--output <trace-file-path>

Ruta de acceso de salida para los datos de seguimiento recopilados. Si no se especifica, el valor
predeterminado es trace.nettrace .
-p|--process-id <PID>

Identificador del proceso del que se va a recopilar el seguimiento.


--profile <profile-name>

Un conjunto con nombre predefinido de configuraciones de proveedor que permite especificar


sucintamente los escenarios de seguimiento comunes.
--providers <list-of-comma-separated-providers>

Lista separada por comas de proveedores de EventPipe que se van a habilitar. Estos proveedores
complementan a los proveedores implícitos en --profile <profile-name> . Si hay alguna incoherencia
para un proveedor determinado, esta configuración tiene prioridad sobre la configuración implícita del
perfil.
Esta lista de proveedores tiene el siguiente formato:
Provider[,Provider]
Provider tiene el formato: KnownProviderName[:Flags[:Level][:KeyValueArgs]] .
KeyValueArgs tiene el formato: [key1=value1][;key2=value2] .

dotnet-trace convert
dotnet-trace convert
Convierte los seguimientos de nettrace en formatos alternativos para usarlos con herramientas de análisis de
seguimiento alternativas.
Sinopsis

dotnet-trace convert [<input-filename>] [--format <Chromium|NetTrace|Speedscope>] [-h|--help] [-o|--output


<output-filename>]

Argumentos
<input-filename>

Archivo de seguimiento de entrada que se va a convertir. El valor predeterminado es trace.nettrace.


Opciones
--format <Chromium|NetTrace|Speedscope>

Establece el formato de salida para la conversión del archivo de seguimiento.


-o|--output <output-filename>

Nombre de archivo de salida. Se agregará la extensión del formato de destino.

dotnet-trace ps
Enumera los procesos de dotnet de los que se pueden recopilar seguimientos.
Sinopsis

dotnet-trace ps [-h|--help]

dotnet-trace list-profiles
Muestra los perfiles de seguimiento pregenerados con una descripción de los proveedores y filtros que hay en
cada perfil.
Sinopsis

dotnet-trace list-profiles [-h|--help]

Recopilación de un seguimiento con dotnet-trace


Para recopilar seguimientos mediante dotnet-trace :
Averigüe el identificador de proceso (PID) de la aplicación .NET Core del que se van a recopilar
seguimientos.
En Windows, puede usar el administrador de tareas o el comando tasklist , por ejemplo.
En Linux, por ejemplo, el comando ps .
dotnet-trace ps
Ejecute el siguiente comando:

dotnet-trace collect --process-id <PID>


El comando anterior genera una salida similar a la siguiente:

Press <Enter> to exit...


Connecting to process: <Full-Path-To-Process-Being-Profiled>/dotnet.exe
Collecting to file: <Full-Path-To-Trace>/trace.nettrace
Session Id: <SessionId>
Recording trace 721.025 (KB)

Detenga la recolección presionando la tecla <Enter> . dotnet-trace finalizará el registro de eventos en


el archivo trace.nettrace.

Vista del seguimiento capturado de dotnet-trace


En Windows, los archivos .nettrace se pueden ver en PerfView para el análisis: En el caso de los seguimientos
recopilados en otras plataformas, el archivo de seguimiento se puede trasladar a una máquina Windows para
verlo en PerfView.
En Linux, el seguimiento se puede ver cambiando el formato de salida de dotnet-trace a speedscope . Puede
cambiar el formato de archivo de salida mediante la opción -f|--format : -f speedscope hará que
dotnet-trace genere un archivo speedscope . Puede elegir entre nettrace (opción predeterminada) y
speedscope . Loa archivos Speedscope se pueden abrir en https://www.speedscope.app.

NOTE
El tiempo de ejecución de .NET Core genera seguimientos en el formato nettrace . Los seguimientos se convierten a
formato speedscope (si se especifica) una vez completado el seguimiento. Dado que algunas conversiones pueden
provocar la pérdida de datos, el archivo nettrace original se conserva junto al archivo convertido.

Uso de dotnet-trace para recopilar valores de contador a lo largo del


tiempo
dotnet-trace puede:
Use EventCounter para la supervisión de estado básica en entornos con distinción de rendimiento. Por
ejemplo, en producción.
Recopile seguimientos para que no sea necesario visualizarlos en tiempo real.
Por ejemplo, para recopilar valores de contador de rendimiento en tiempo de ejecución, puede usar el
comando siguiente:

dotnet-trace collect --process-id <PID> --providers System.Runtime:0:1:EventCounterIntervalSec=1

El comando anterior indica a los contadores en tiempo de ejecución que se deben notificar una vez por
segundo para la supervisión ligera del estado. Reemplazar EventCounterIntervalSec=1 por un valor mayor (por
ejemplo, 60) permite recopilar un seguimiento más pequeño con menos granularidad en los datos de
contador.
El comando siguiente reduce la sobrecarga y el tamaño de seguimiento más que el anterior:

dotnet-trace collect --process-id <PID> --providers


System.Runtime:0:1:EventCounterIntervalSec=1,Microsoft-Windows-DotNETRuntime:0:1,Microsoft-DotNETCore-
SampleProfiler:0:1
El comando anterior deshabilita los eventos en tiempo de ejecución y el generador de perfiles de pila
administrado.

Proveedores .NET
El runtime de .NET Core admite los siguientes proveedores .NET. .NET Core usa las mismas palabras clave para
habilitar los seguimientos de Event Tracing for Windows (ETW) y EventPipe .

N O M B RE DEL P RO VEEDO R IN F O RM A C IÓ N

Microsoft-Windows-DotNETRuntime El proveedor de runtime


Palabras clave de runtime de CLR

Microsoft-Windows-DotNETRuntimeRundown El proveedor de informe detallado


Palabras clave de informe detallado de CLR

Microsoft-DotNETCore-SampleProfiler Habilita el generador de perfiles de ejemplo.


Tutorial: Medición del rendimiento mediante
EventCounters en .NET Core
16/09/2020 • 8 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.0 y versiones posteriores
En este tutorial se aprende a usar un EventCounter para medir el rendimiento con una alta frecuencia de eventos.
Puede usar los contadores disponibles publicados por diversos paquetes oficiales de .NET Core, proveedores de
terceros o crear sus propias métricas para la supervisión.
En este tutorial va a:
Implementar un EventSource.
Supervisar contadores con dotnet-counters.

Requisitos previos
En el tutorial se usa:
SDK de .NET Core 3.1 o una versión posterior
dotnet-counters para supervisar contadores de eventos.
Una aplicación de destino de depuración de ejemplo que se va a diagnosticar.

Obtención del origen


Se va a usar la aplicación de ejemplo como base para la supervisión. El repositorio de ASP.NET Core de ejemplo
está disponible en el explorador de ejemplos. Descargue el archivo zip, extráigalo una vez descargado y ábralo en
el IDE que prefiera. Compile y ejecute la aplicación para asegurarse de que funciona correctamente y luego
deténgala.

Implementación de un EventSource
En el caso de los eventos que se producen cada pocos milisegundos, se recomienda que la sobrecarga por evento
sea baja (menos de un milisegundo). De lo contrario, el impacto sobre el rendimiento es considerable. El registro
de un evento significa que se va a escribir algo en el disco. Si el disco no es lo suficientemente rápido, se pierden
eventos. Necesita una solución que no sea el registro del propio evento.
Al trabajar con un gran número de eventos, conocer la medida por evento tampoco es útil. La mayor parte del
tiempo, todo lo que se necesita son algunas estadísticas. Por lo tanto, podría obtener las estadísticas dentro del
propio proceso y luego escribir un evento de vez en cuando para comunicar las estadísticas, que es lo que hace
EventCounter.
A continuación se muestra un ejemplo de cómo implementar un System.Diagnostics.Tracing.EventSource. Cree un
nuevo archivo denominado MinimalEventCounterSource.cs y use el fragmento de código como su origen:
using System.Diagnostics.Tracing;

[EventSource(Name = "Sample.EventCounter.Minimal")]
public sealed class MinimalEventCounterSource : EventSource
{
public static readonly MinimalEventCounterSource Log = new MinimalEventCounterSource();

private EventCounter _requestCounter;

private MinimalEventCounterSource() =>


_requestCounter = new EventCounter("request-time", this)
{
DisplayName = "Request Processing Time",
DisplayUnits = "ms"
};

public void Request(string url, float elapsedMilliseconds)


{
WriteEvent(1, url, elapsedMilliseconds);
_requestCounter?.WriteMetric(elapsedMilliseconds);
}

protected override void Dispose(bool disposing)


{
_requestCounter?.Dispose();
_requestCounter = null;

base.Dispose(disposing);
}
}

La línea EventSource.WriteEvent es la parte EventSource y no forma parte de EventCounter, se ha escrito para


mostrar que se puede registrar un mensaje junto con el contador de eventos.

Incorporación de un filtro de acción


El código fuente de ejemplo es un proyecto de ASP.NET Core. Puede agregar un filtro de acción globalmente que
registre el tiempo total de las solicitudes. Cree un nuevo archivo denominado LogRequestTimeFilterAttribute.cs y
use el código siguiente:

using System.Diagnostics;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc.Filters;

namespace DiagnosticScenarios
{
public class LogRequestTimeFilterAttribute : ActionFilterAttribute
{
readonly Stopwatch _stopwatch = new Stopwatch();

public override void OnActionExecuting(ActionExecutingContext context) => _stopwatch.Start();

public override void OnActionExecuted(ActionExecutedContext context)


{
_stopwatch.Stop();

MinimalEventCounterSource.Log.Request(
context.HttpContext.Request.GetDisplayUrl(), _stopwatch.ElapsedMilliseconds);
}
}
}

El filtro de acción inicia Stopwatch cuando comienza la solicitud y detiene cuando termina, capturando el tiempo
transcurrido. Los milisegundos totales se registran en la instancia singleton MinimalEventCounterSource . Para
aplicar este filtro, debe agregarlo a la colección de filtros. En el archivo Startup.cs, actualice el método
ConfigureServices al incluir este filtro.

public void ConfigureServices(IServiceCollection services) =>


services.AddControllers(options => options.Filters.Add<LogRequestTimeFilterAttribute>())
.AddNewtonsoftJson();

Supervisión del contador de eventos


Con la implementación en un EventSource y el filtro de acción personalizado, compile e inicie la aplicación. Ha
registrado la métrica en el EventCounter, pero a menos que acceda a las estadísticas de este, esto no es útil. Para
obtener las estadísticas, debe habilitar el EventCounter mediante la creación de un temporizador que se active con
la frecuencia con que quiera que los eventos,así como una escucha, capturen los eventos. Para ello, puede usar
dotnet-counters.
Use el comando dotnet-counters ps para mostrar una lista de procesos de .NET que se pueden supervisar.

dotnet-counters ps

Con el identificador de proceso de la salida del comando dotnet-counters ps , puede empezar a supervisar el
contador de eventos con el siguiente comando dotnet-counters monitor :

dotnet-counters monitor --process-id 2196 Sample.EventCounter.Minimal Microsoft.AspNetCore.Hosting[total-


requests,requests-per-second] System.Runtime[cpu-usage]

Mientras se ejecuta el comando dotnet-counters monitor , mantenga presionada la tecla F5 en el explorador para
iniciar la emisión de solicitudes continuas al punto de conexión https://localhost:5001/api/values . Tras unos
segundos, detenga al presionar q.

Press p to pause, r to resume, q to quit.


Status: Running

[Microsoft.AspNetCore.Hosting]
Request Rate / 1 sec 9
Total Requests 134
[System.Runtime]
CPU Usage (%) 13
[Sample.EventCounter.Minimal]
Request Processing Time (ms) 34.5

El comando dotnet-counters monitor es excelente para la supervisión activa. Pero se recomienda recopilar estas
métricas de diagnóstico para el procesamiento y el análisis posteriores. Para ello,use el comando
dotnet-counters collect . El comando modificador collect es similar al comando monitor , pero acepta algunos
parámetros adicionales. Puede especificar el nombre de archivo de salida y el formato que quiera. En el caso de un
archivo JSON denominado diagnostics.json, use el siguiente comando:

dotnet-counters collect --process-id 2196 --format json -o diagnostics.json Sample.EventCounter.Minimal


Microsoft.AspNetCore.Hosting[total-requests,requests-per-second] System.Runtime[cpu-usage]

Una vez más, mientras se ejecuta el comando, mantenga presionada la tecla F5 en el explorador para iniciar la
emisión de solicitudes continuas al punto de conexión https://localhost:5001/api/values . Tras unos segundos,
detenga al presionar q. Se escribe el archivo diagnostics.json. Pero no se aplica sangría al archivo JSON escrito;
para mejorar la legibilidad, la sangría se aplica aquí.

{
"TargetProcess": "DiagnosticScenarios",
"StartTime": "8/5/2020 3:02:45 PM",
"Events": [
{
"timestamp": "2020-08-05 15:02:47Z",
"provider": "System.Runtime",
"name": "CPU Usage (%)",
"counterType": "Metric",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:47Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Request Rate / 1 sec",
"counterType": "Rate",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:47Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Total Requests",
"counterType": "Metric",
"value": 134
},
{
"timestamp": "2020-08-05 15:02:47Z",
"provider": "Sample.EventCounter.Minimal",
"name": "Request Processing Time (ms)",
"counterType": "Metric",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:47Z",
"provider": "System.Runtime",
"name": "CPU Usage (%)",
"counterType": "Metric",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:48Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Request Rate / 1 sec",
"counterType": "Rate",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:48Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Total Requests",
"counterType": "Metric",
"value": 134
},
{
"timestamp": "2020-08-05 15:02:48Z",
"provider": "Sample.EventCounter.Minimal",
"name": "Request Processing Time (ms)",
"counterType": "Metric",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:48Z",
"provider": "System.Runtime",
"name": "CPU Usage (%)",
"counterType": "Metric",
"value": 0
"value": 0
},
{
"timestamp": "2020-08-05 15:02:50Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Request Rate / 1 sec",
"counterType": "Rate",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:50Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Total Requests",
"counterType": "Metric",
"value": 134
},
{
"timestamp": "2020-08-05 15:02:50Z",
"provider": "Sample.EventCounter.Minimal",
"name": "Request Processing Time (ms)",
"counterType": "Metric",
"value": 0
}
]
}

Pasos siguientes
EventCounters
Depuración de una fuga de memoria en .NET Core
16/09/2020 • 9 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.1 y versiones posteriores
En este tutorial se muestran las herramientas para analizar una fuga de memoria de .NET Core.
En este tutorial se usa una aplicación de ejemplo, diseñada para perder memoria de manera intencionada. El
ejemplo se proporciona como ejercicio. Asimismo, puede analizar una aplicación que pierda memoria
involuntariamente.
En este tutorial va a:
Examinar el uso de memoria administrada con dotnet-counters.
Generar un archivo de volcado de memoria.
Analizar el uso de memoria mediante el archivo de volcado de memoria.

Requisitos previos
En el tutorial se usa:
SDK de .NET Core 3.1 o una versión posterior
dotnet-trace para mostrar procesos.
dotnet-counters para comprobar el uso de memoria administrada.
dotnet-dump para recopilar y analizar un archivo de volcado de memoria.
Una aplicación de destino de depuración de ejemplo que se va a diagnosticar.
En el tutorial se da por supuesto que el ejemplo y las herramientas están instalados y listos para usarse.

Análisis del uso de memoria administrada


Antes de empezar a recopilar datos de diagnóstico que nos ayuden a establecer la causa principal de este
escenario, debe asegurarse de que realmente está viendo una fuga de memoria (crecimiento de memoria). Puede
usar la herramienta dotnet-counters para confirmar eso.
Abra una ventana de consola y vaya al directorio donde descargó y descomprimió el destino de depuración de
ejemplo. Ejecute el destino:

dotnet run

En una consola independiente, busque el identificador del proceso mediante la herramienta dotnet-trace:

dotnet-trace ps

La salida debe ser similar a:

4807 DiagnosticScena
/home/user/git/samples/core/diagnostics/DiagnosticScenarios/bin/Debug/netcoreapp3.0/DiagnosticScenarios

Ahora, compruebe el uso de memoria administrada con la herramienta dotnet-counters. --refresh-interval


especifica el número de segundos entre las actualizaciones:

dotnet-counters monitor --refresh-interval 1 -p 4807

La salida en directo debe ser similar a:

Press p to pause, r to resume, q to quit.


Status: Running

[System.Runtime]
# of Assemblies Loaded 118
% Time in GC (since last GC) 0
Allocation Rate (Bytes / sec) 37,896
CPU Usage (%) 0
Exceptions / sec 0
GC Heap Size (MB) 4
Gen 0 GC / sec 0
Gen 0 Size (B) 0
Gen 1 GC / sec 0
Gen 1 Size (B) 0
Gen 2 GC / sec 0
Gen 2 Size (B) 0
LOH Size (B) 0
Monitor Lock Contention Count / sec 0
Number of Active Timers 1
ThreadPool Completed Work Items / sec 10
ThreadPool Queue Length 0
ThreadPool Threads Count 1
Working Set (MB) 83

Establecimiento del foco en esta línea:

GC Heap Size (MB) 4

Puede ver que la memoria de montón administrado es de 4 MB justo después del inicio.
Ahora, visite la dirección URL https://localhost:5001/api/diagscenario/memleak/20000 .
Observe que el uso de memoria ha aumentado a 30 MB.

GC Heap Size (MB) 30

Al observar el uso de memoria, puede indicar con seguridad el aumento o la fuga de memoria. El siguiente paso
consiste en recopilar los datos adecuados para el análisis de memoria.
Generación de un volcado de memoria
Al analizar posibles fugas de memoria, debe tener acceso al montón de memoria de la aplicación. A continuación,
puede analizar el contenido de la memoria. Al observarse las relaciones entre los objetos, se crean teorías de por
qué no se libera la memoria. Un origen de datos de diagnóstico habitual es un volcado de memoria en Windows o
el volcado de memoria principal equivalente en Linux. Para generar un volcado de memoria de una aplicación .NET
Core, puede usar la herramienta dotnet-dump).
Con el destino de depuración de ejemplo iniciado anteriormente, ejecute el siguiente comando para generar un
volcado de memoria principal de Linux:

dotnet-dump collect -p 4807


El resultado es un volcado de memoria principal ubicado en la misma carpeta.

Writing minidump with heap to ./core_20190430_185145


Complete

Reinicio del proceso con errores


Una vez recopilado el volcado de memoria, debe tener suficiente información para diagnosticar el proceso con
errores. Si el proceso con errores se ejecuta en un servidor de producción, ahora es el momento ideal para la
corrección a corto plazo reiniciando el proceso.
En este tutorial, ya ha terminado con el destino de depuración de ejemplo y puede cerrarlo. Vaya al terminal que
inició el servidor y presione CTRL+C.
Análisis del volcado de memoria principal
Ahora que ha generado un volcado de memoria principal, use la herramienta dotnet-dump para analizar el volcado
de memoria:

dotnet-dump analyze core_20190430_185145

Donde core_20190430_185145 es el nombre del volcado de memoria principal que desea analizar.

NOTE
Si ve un error que indica que no se encuentra libdl.so, es posible que tenga que instalar el paquete libc6-dev. Para más
información, consulte Requisitos previos para .NET Core en Linux.

Se le mostrará un mensaje en el que puede escribir comandos SOS. Normalmente, lo primero que desea ver es el
estado general del montón administrado:

> dumpheap -stat

Statistics:
MT Count TotalSize Class Name
...
00007f6c1eeefba8 576 59904 System.Reflection.RuntimeMethodInfo
00007f6c1dc021c8 1749 95696 System.SByte[]
00000000008c9db0 3847 116080 Free
00007f6c1e784a18 175 128640 System.Char[]
00007f6c1dbf5510 217 133504 System.Object[]
00007f6c1dc014c0 467 416464 System.Byte[]
00007f6c21625038 6 4063376 testwebapi.Controllers.Customer[]
00007f6c20a67498 200000 4800000 testwebapi.Controllers.Customer
00007f6c1dc00f90 206770 19494060 System.String
Total 428516 objects

Aquí puede ver que la mayoría de los objetos son objetos String o Customer .
Puede volver a usar el comando dumpheap con la tabla del método (MT) para obtener una lista de todas las
instancias de String :
> dumpheap -mt 00007faddaa50f90

Address MT Size
...
00007f6ad09421f8 00007faddaa50f90 94
...
00007f6ad0965b20 00007f6c1dc00f90 80
00007f6ad0965c10 00007f6c1dc00f90 80
00007f6ad0965d00 00007f6c1dc00f90 80
00007f6ad0965df0 00007f6c1dc00f90 80
00007f6ad0965ee0 00007f6c1dc00f90 80

Statistics:
MT Count TotalSize Class Name
00007f6c1dc00f90 206770 19494060 System.String
Total 206770 objects

Ahora puede usar el comando gcroot en una instancia de System.String para ver cómo y por qué se considera
raíz el objeto. Tenga paciencia, ya que este comando tarda varios minutos con un montón de 30 MB:

> gcroot -all 00007f6ad09421f8

Thread 3f68:
00007F6795BB58A0 00007F6C1D7D0745 System.Diagnostics.Tracing.CounterGroup.PollForValues()
[/_/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/CounterGroup.cs @ 260]
rbx: (interior)
-> 00007F6BDFFFF038 System.Object[]
-> 00007F69D0033570 testwebapi.Controllers.Processor
-> 00007F69D0033588 testwebapi.Controllers.CustomerCache
-> 00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer,
DiagnosticScenarios]]
-> 00007F6C000148A0 testwebapi.Controllers.Customer[]
-> 00007F6AD0942258 testwebapi.Controllers.Customer
-> 00007F6AD09421F8 System.String

HandleTable:
00007F6C98BB15F8 (pinned handle)
-> 00007F6BDFFFF038 System.Object[]
-> 00007F69D0033570 testwebapi.Controllers.Processor
-> 00007F69D0033588 testwebapi.Controllers.CustomerCache
-> 00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer,
DiagnosticScenarios]]
-> 00007F6C000148A0 testwebapi.Controllers.Customer[]
-> 00007F6AD0942258 testwebapi.Controllers.Customer
-> 00007F6AD09421F8 System.String

Found 2 roots.

Puede ver que el objeto Customer mantiene directamente String y un objeto CustomerCache lo hace
indirectamente.
Puede seguir volcando objetos para ver que la mayoría de los objetos String siguen un patrón similar. En este
punto, la investigación proporcionó suficiente información para identificar la causa principal en su código.
Este procedimiento general le permite identificar el origen de las principales fugas de memoria.

Limpiar los recursos


En este tutorial, inició un servidor web de ejemplo. Este servidor debería haberse apagado como se explica en la
sección Reinicio del proceso con errores.
También puede eliminar el archivo de volcado de memoria que se creó.
Consulte también
dotnet-trace para mostrar procesos
dotnet-counters para comprobar el uso de memoria administrada
dotnet-dump para recopilar y analizar un archivo de volcado de memoria
dotnet/diagnostics

Pasos siguientes
Depuración del uso elevado de CPU en .NET Core
Depuración del uso elevado de CPU en .NET Core
16/09/2020 • 7 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.1 y versiones posteriores
En este tutorial, aprenderá a depurar un escenario de uso excesivo de CPU. Con el repositorio de código fuente de
ejemplo proporcionado, Aplicación web de ASP.NET Core, puede provocar un interbloqueo de forma intencionada.
El punto de conexión experimentará un bloqueo y una acumulación de subprocesos. Aprenderá a usar diversas
herramientas para diagnosticar este escenario con varios elementos clave de datos de diagnóstico.
En este tutorial va a:
Investigar el uso elevado de CPU
Determinar el uso de CPU con dotnet-counters
Usar dotnet-trace para la generación de seguimiento
Realizar perfiles de rendimiento en PerfView
Diagnosticar y resolver el uso excesivo de CPU

Requisitos previos
En el tutorial se usa:
SDK de .NET Core 3.1 o una versión posterior
Destino de depuración de ejemplo para desencadenar el escenario
dotnet-trace para enumerar los procesos y generar un perfil
dotnet-counters para supervisar el uso de la CPU

Contadores de CPU
Antes de intentar recopilar datos de diagnóstico, debe observar una condición de CPU alta. Ejecute la aplicación de
ejemplo mediante el siguiente comando desde el directorio raíz del proyecto.

dotnet run

Para encontrar el identificador de proceso, use el comando siguiente:

dotnet-trace ps

Anote el identificador de proceso de la salida del comando. El identificador de proceso era 22884 , pero el suyo
será diferente. Para comprobar el uso de CPU actual, use el comando de la herramienta dotnet-counters:

dotnet-counters monitor --refresh-interval 1 -p 22884

refresh-interval es el número de segundos entre los valores de CPU de sondeo de contador. La salida debe ser
similar a la siguiente:
Press p to pause, r to resume, q to quit.
Status: Running

[System.Runtime]
% Time in GC since last GC (%) 0
Allocation Rate / 1 sec (B) 0
CPU Usage (%) 0
Exception Count / 1 sec 0
GC Heap Size (MB) 4
Gen 0 GC Count / 60 sec 0
Gen 0 Size (B) 0
Gen 1 GC Count / 60 sec 0
Gen 1 Size (B) 0
Gen 2 GC Count / 60 sec 0
Gen 2 Size (B) 0
LOH Size (B) 0
Monitor Lock Contention Count / 1 sec 0
Number of Active Timers 1
Number of Assemblies Loaded 140
ThreadPool Completed Work Item Count / 1 sec 3
ThreadPool Queue Length 0
ThreadPool Thread Count 7
Working Set (MB) 63

Con la aplicación web en ejecución, inmediatamente después del inicio, no se consume CPU en absoluto y se
muestra 0% . Vaya a la ruta api/diagscenario/highcpu con 60000 como parámetro de ruta:
https://localhost:5001/api/diagscenario/highcpu/60000

Ahora, vuelva a ejecutar el comando dotnet-counters. Para supervisar solo cpu-usage , especifique
System.Runtime[cpu-usage] como parte del comando.

dotnet-counters monitor System.Runtime[cpu-usage] -p 22884 --refresh-interval 1

Debería ver un aumento en el uso de CPU, como se muestra a continuación:

Press p to pause, r to resume, q to quit.


Status: Running

[System.Runtime]
CPU Usage (%) 25

Durante toda la solicitud, el uso de CPU rondará en torno al 25 %. En función del equipo host, se espera un uso de
CPU variable.

TIP
Para visualizar un uso de CPU incluso mayor, puede ejecutar este punto de conexión simultáneamente en varias pestañas del
explorador.

En este momento, puede afirmar con seguridad que el uso de CPU es mayor del esperado.

Generación de seguimiento
Al analizar una solicitud lenta, necesita una herramienta de diagnóstico que pueda proporcionar información
sobre lo que hace el código. Lo habitual es optar por un generador de perfiles. Existen diferentes opciones de
generador de perfiles entre las que elegir.
Linux
Windows
La herramienta perf se puede usar para generar perfiles de aplicaciones .NET Core. Salga de la instancia anterior
del destino de depuración de ejemplo.
Establezca la variable de entorno COMPlus_PerfMapEnabled para que la aplicación .NET Core cree un archivo map en
el directorio /tmp . perf usa este archivo map para asignar la dirección de CPU a las funciones generadas por JIT
por nombre. Para obtener más información, consulte Escritura del mapa de rendimiento.
Ejecute el destino de depuración de ejemplo en la misma sesión de terminal.

export COMPlus_PerfMapEnabled=1
dotnet run

Ejecute de nuevo el punto de conexión de la API de uso elevado de CPU (


https://localhost:5001/api/diagscenario/highcpu/60000 ). Mientras se ejecuta en la solicitud de 1 minuto, ejecute el
comando perf con el identificador de proceso:

sudo perf record -p 2266 -g

El comando perf inicia el proceso de recopilación de rendimiento. Deje que se ejecute durante unos 20 o
30 segundos y, luego, presione Ctrl+C para salir del proceso de recopilación. Puede usar el mismo comando
perf para ver la salida del seguimiento.

sudo perf report -f

También puede generar un gráfico de llamas mediante los comandos siguientes:

git clone --depth=1 https://github.com/BrendanGregg/FlameGraph


sudo perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg

Este comando genera un archivo flamegraph.svg que puede ver en el explorador para investigar el problema de
rendimiento:
Consulte también
dotnet-trace para mostrar procesos
dotnet-counters para comprobar el uso de memoria administrada
dotnet-dump para recopilar y analizar un archivo de volcado de memoria
dotnet/diagnostics

Pasos siguientes
Depuración de un interbloqueo en .NET Core
Depuración de un interbloqueo en .NET Core
16/09/2020 • 11 minutes to read • Edit Online

Este ar tículo se aplica a: ✔


️ SDK de .NET Core 3.1 y versiones posteriores
En este tutorial se explica cómo depurar un escenario de interbloqueo. Con el repositorio de código fuente de
ejemplo proporcionado, Aplicación web de ASP.NET Core, puede provocar un interbloqueo de forma intencionada.
El punto de conexión experimentará un bloqueo y una acumulación de subprocesos. Así, aprenderá a usar diversas
herramientas para analizar el problema, como los volcados de núcleo, el análisis de volcados de núcleo y el
seguimiento de procesos.
En este tutorial va a:
Investigar el bloqueo de una aplicación
Generar un archivo de volcado de núcleo
Analizar los subprocesos del proceso en el archivo de volcado de memoria
Analizar pilas de llamadas y bloques de sincronización
Diagnosticar y solucionar un interbloqueo

Requisitos previos
En el tutorial se usa:
SDK de .NET Core 3.1 o una versión posterior
Destino de depuración de ejemplo: aplicación web para desencadenar el escenario
dotnet-trace para mostrar procesos
dotnet-dump para recopilar y analizar un archivo de volcado de memoria

Generación de volcado de núcleo


A la hora de investigar la falta de respuesta de una aplicación, un volcado de núcleo o memoria permite
inspeccionar el estado de sus subprocesos y cualquier posible bloqueo que pueda tener problemas de contención.
Ejecute la aplicación de depuración de ejemplo mediante el siguiente comando desde el directorio raíz de ejemplo:

dotnet run

Para encontrar el identificador de proceso, use el comando siguiente:

dotnet-trace ps

Anote el identificador de proceso de la salida del comando. El identificador de proceso era 4807 , pero el suyo será
diferente. Vaya a la siguiente dirección URL, que es un punto de conexión de API en el sitio de ejemplo:
https://localhost:5001/api/diagscenario/deadlock

La solicitud de API al sitio se bloquea y no responde. Deje que la solicitud se ejecute durante unos 10 o
15 segundos. Luego, cree el volcado de núcleo mediante el siguiente comando:
Linux
Windows
sudo dotnet-dump collect -p 4807

Análisis del volcado de memoria principal


Para iniciar el análisis de volcado de núcleo, ábralo con el siguiente comando dotnet-dump analyze . El argumento
es la ruta de acceso al archivo de volcado de núcleo recopilado anteriormente.

dotnet-dump analyze ~/.dotnet/tools/core_20190513_143916

Puesto que está examinando un posible bloqueo, quiere obtener una idea general de la actividad de los
subprocesos del proceso. Puede usar el comando threads como se muestra a continuación:

> threads
*0 0x1DBFF (121855)
1 0x1DC01 (121857)
2 0x1DC02 (121858)
3 0x1DC03 (121859)
4 0x1DC04 (121860)
5 0x1DC05 (121861)
6 0x1DC06 (121862)
7 0x1DC07 (121863)
8 0x1DC08 (121864)
9 0x1DC09 (121865)
10 0x1DC0A (121866)
11 0x1DC0D (121869)
12 0x1DC0E (121870)
13 0x1DC10 (121872)
14 0x1DC11 (121873)
15 0x1DC12 (121874)
16 0x1DC13 (121875)
17 0x1DC14 (121876)
18 0x1DC15 (121877)
19 0x1DC1C (121884)
20 0x1DC1D (121885)
21 0x1DC1E (121886)
22 0x1DC21 (121889)
23 0x1DC22 (121890)
24 0x1DC23 (121891)
25 0x1DC24 (121892)
26 0x1DC25 (121893)
...
...
317 0x1DD48 (122184)
318 0x1DD49 (122185)
319 0x1DD4A (122186)
320 0x1DD4B (122187)
321 0x1DD4C (122188)

La salida muestra todos los subprocesos que se están ejecutando en el proceso con su identificador de subproceso
de depurador asociado y su identificador de subproceso de sistema operativo. Hay más de 300 subprocesos en
función de la salida.
El siguiente paso es entender mejor lo que los subprocesos están haciendo mediante la obtención de la pila de
llamadas de cada subproceso. El comando clrstack se puede usar para generar pilas de llamadas. Puede generar
una sola pila de llamadas o todas. Use el siguiente comando para generar todas las pilas de llamadas de todos los
subprocesos del proceso:
clrstack -all

Una parte representativa de la salida tiene el siguiente aspecto:

...
...
...
Child SP IP Call Site
00007F2AE37B5680 00007f305abc6360 [GCFrame: 00007f2ae37b5680]
00007F2AE37B5770 00007f305abc6360 [GCFrame: 00007f2ae37b5770]
00007F2AE37B57D0 00007f305abc6360 [HelperMethodFrame_1OBJ: 00007f2ae37b57d0]
System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
00007F2AE37B5920 00007F2FE392B31F testwebapi.Controllers.DiagScenarioController.<deadlock>b__3_1()
[/home/marioh/webapi/Controllers/diagscenario.cs @ 36]
00007F2AE37B5950 00007F2FE392B46D
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,
System.Threading.ContextCallback, System.Object)
[/__w/3/s/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 201]
00007F2AE37B5CA0 00007f30593044af [GCFrame: 00007f2ae37b5ca0]
00007F2AE37B5D70 00007f30593044af [DebuggerU2MCatchHandlerFrame: 00007f2ae37b5d70]
OS Thread Id: 0x1dc82
Child SP IP Call Site
00007F2AE2FB4680 00007f305abc6360 [GCFrame: 00007f2ae2fb4680]
00007F2AE2FB4770 00007f305abc6360 [GCFrame: 00007f2ae2fb4770]
00007F2AE2FB47D0 00007f305abc6360 [HelperMethodFrame_1OBJ: 00007f2ae2fb47d0]
System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
00007F2AE2FB4920 00007F2FE392B31F testwebapi.Controllers.DiagScenarioController.<deadlock>b__3_1()
[/home/marioh/webapi/Controllers/diagscenario.cs @ 36]
00007F2AE2FB4950 00007F2FE392B46D
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,
System.Threading.ContextCallback, System.Object)
[/__w/3/s/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 201]
00007F2AE2FB4CA0 00007f30593044af [GCFrame: 00007f2ae2fb4ca0]
00007F2AE2FB4D70 00007f30593044af [DebuggerU2MCatchHandlerFrame: 00007f2ae2fb4d70]
OS Thread Id: 0x1dc83
Child SP IP Call Site
00007F2AE27B3680 00007f305abc6360 [GCFrame: 00007f2ae27b3680]
00007F2AE27B3770 00007f305abc6360 [GCFrame: 00007f2ae27b3770]
00007F2AE27B37D0 00007f305abc6360 [HelperMethodFrame_1OBJ: 00007f2ae27b37d0]
System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
00007F2AE27B3920 00007F2FE392B31F testwebapi.Controllers.DiagScenarioController.<deadlock>b__3_1()
[/home/marioh/webapi/Controllers/diagscenario.cs @ 36]
00007F2AE27B3950 00007F2FE392B46D
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,
System.Threading.ContextCallback, System.Object)
[/__w/3/s/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 201]
00007F2AE27B3CA0 00007f30593044af [GCFrame: 00007f2ae27b3ca0]
00007F2AE27B3D70 00007f30593044af [DebuggerU2MCatchHandlerFrame: 00007f2ae27b3d70]
OS Thread Id: 0x1dc84
Child SP IP Call Site
00007F2AE1FB2680 00007f305abc6360 [GCFrame: 00007f2ae1fb2680]
00007F2AE1FB2770 00007f305abc6360 [GCFrame: 00007f2ae1fb2770]
00007F2AE1FB27D0 00007f305abc6360 [HelperMethodFrame_1OBJ: 00007f2ae1fb27d0]
System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
00007F2AE1FB2920 00007F2FE392B31F testwebapi.Controllers.DiagScenarioController.<deadlock>b__3_1()
[/home/marioh/webapi/Controllers/diagscenario.cs @ 36]
00007F2AE1FB2950 00007F2FE392B46D
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,
System.Threading.ContextCallback, System.Object)
[/__w/3/s/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 201]
00007F2AE1FB2CA0 00007f30593044af [GCFrame: 00007f2ae1fb2ca0]
00007F2AE1FB2D70 00007f30593044af [DebuggerU2MCatchHandlerFrame: 00007f2ae1fb2d70]
OS Thread Id: 0x1dc85
Child SP IP Call Site
00007F2AE17B1680 00007f305abc6360 [GCFrame: 00007f2ae17b1680]
00007F2AE17B1770 00007f305abc6360 [GCFrame: 00007f2ae17b1770]
00007F2AE17B17D0 00007f305abc6360 [HelperMethodFrame_1OBJ: 00007f2ae17b17d0]
System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
00007F2AE17B1920 00007F2FE392B31F testwebapi.Controllers.DiagScenarioController.<deadlock>b__3_1()
[/home/marioh/webapi/Controllers/diagscenario.cs @ 36]
00007F2AE17B1950 00007F2FE392B46D
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,
System.Threading.ContextCallback, System.Object)
[/__w/3/s/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 201]
00007F2AE17B1CA0 00007f30593044af [GCFrame: 00007f2ae17b1ca0]
00007F2AE17B1D70 00007f30593044af [DebuggerU2MCatchHandlerFrame: 00007f2ae17b1d70]
OS Thread Id: 0x1dc86
Child SP IP Call Site
00007F2AE0FB0680 00007f305abc6360 [GCFrame: 00007f2ae0fb0680]
00007F2AE0FB0770 00007f305abc6360 [GCFrame: 00007f2ae0fb0770]
00007F2AE0FB07D0 00007f305abc6360 [HelperMethodFrame_1OBJ: 00007f2ae0fb07d0]
System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
00007F2AE0FB0920 00007F2FE392B31F testwebapi.Controllers.DiagScenarioController.<deadlock>b__3_1()
[/home/marioh/webapi/Controllers/diagscenario.cs @ 36]
00007F2AE0FB0950 00007F2FE392B46D
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,
System.Threading.ContextCallback, System.Object)
[/__w/3/s/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 201]
00007F2AE0FB0CA0 00007f30593044af [GCFrame: 00007f2ae0fb0ca0]
00007F2AE0FB0D70 00007f30593044af [DebuggerU2MCatchHandlerFrame: 00007f2ae0fb0d70]
OS Thread Id: 0x1dc87
Child SP IP Call Site
00007F2AE07AF680 00007f305abc6360 [GCFrame: 00007f2ae07af680]
00007F2AE07AF770 00007f305abc6360 [GCFrame: 00007f2ae07af770]
00007F2AE07AF7D0 00007f305abc6360 [HelperMethodFrame_1OBJ: 00007f2ae07af7d0]
System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
00007F2AE07AF920 00007F2FE392B31F testwebapi.Controllers.DiagScenarioController.<deadlock>b__3_1()
[/home/marioh/webapi/Controllers/diagscenario.cs @ 36]
00007F2AE07AF950 00007F2FE392B46D
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,
System.Threading.ContextCallback, System.Object)
[/__w/3/s/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 201]
00007F2AE07AFCA0 00007f30593044af [GCFrame: 00007f2ae07afca0]
00007F2AE07AFD70 00007f30593044af [DebuggerU2MCatchHandlerFrame: 00007f2ae07afd70]
OS Thread Id: 0x1dc88
Child SP IP Call Site
00007F2ADFFAE680 00007f305abc6360 [GCFrame: 00007f2adffae680]
00007F2ADFFAE770 00007f305abc6360 [GCFrame: 00007f2adffae770]
00007F2ADFFAE7D0 00007f305abc6360 [HelperMethodFrame_1OBJ: 00007f2adffae7d0]
System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
00007F2ADFFAE920 00007F2FE392B31F testwebapi.Controllers.DiagScenarioController.<deadlock>b__3_1()
[/home/marioh/webapi/Controllers/diagscenario.cs @ 36]
00007F2ADFFAE950 00007F2FE392B46D
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,
System.Threading.ContextCallback, System.Object)
[/__w/3/s/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 201]
00007F2ADFFAECA0 00007f30593044af [GCFrame: 00007f2adffaeca0]
00007F2ADFFAED70 00007f30593044af [DebuggerU2MCatchHandlerFrame: 00007f2adffaed70]
...
...

Si se observan las pilas de llamadas de los más de 300 subprocesos, se muestra un patrón en el que una mayoría
de los subprocesos comparten una pila de llamadas común:
OS Thread Id: 0x1dc88
Child SP IP Call Site
00007F2ADFFAE680 00007f305abc6360 [GCFrame: 00007f2adffae680]
00007F2ADFFAE770 00007f305abc6360 [GCFrame: 00007f2adffae770]
00007F2ADFFAE7D0 00007f305abc6360 [HelperMethodFrame_1OBJ: 00007f2adffae7d0]
System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
00007F2ADFFAE920 00007F2FE392B31F testwebapi.Controllers.DiagScenarioController.<deadlock>b__3_1()
[/home/marioh/webapi/Controllers/diagscenario.cs @ 36]
00007F2ADFFAE950 00007F2FE392B46D
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,
System.Threading.ContextCallback, System.Object)
[/__w/3/s/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 201]
00007F2ADFFAECA0 00007f30593044af [GCFrame: 00007f2adffaeca0]
00007F2ADFFAED70 00007f30593044af [DebuggerU2MCatchHandlerFrame: 00007f2adffaed70]

La pila de llamadas parece mostrar que la solicitud ha llegado en el método de interbloqueo que, a su vez, realiza
una llamada a Monitor.ReliableEnter . Este método indica que los subprocesos están intentando especificar un
bloqueo de monitor. Están esperando a que el bloqueo esté disponible. Probablemente otro subproceso lo haya
bloqueado.
El siguiente paso es averiguar qué subproceso está manteniendo realmente el bloqueo de monitor. Como los
monitores normalmente almacenan información de bloqueo en la tabla de bloques de sincronización, se puede
usar el comando syncblk para obtener más información:

> syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner
43 00000246E51268B8 603 1 0000024B713F4E30 5634 28 00000249654b14c0 System.Object
44 00000246E5126908 3 1 0000024B713F47E0 51d4 29 00000249654b14d8 System.Object
-----------------------------
Total 344
CCW 1
RCW 2
ComClassFactory 0
Free 0

Las dos columnas que interesan son MonitorHeld y Owning Thread Info . La columna MonitorHeld muestra si
un subproceso ha adquirido un bloqueo de monitor y el número de subprocesos en espera. La columna Owning
Thread Info muestra el subproceso que posee actualmente el bloqueo de monitor. La información del subproceso
tiene tres subcolumnas diferentes. La segunda subcolumna muestra el identificador de subproceso de sistema
operativo.
En este punto, se sabe que dos subprocesos diferentes (0x5634 y 0x51d4) mantienen un bloqueo de monitor. El
siguiente paso consiste en echar un vistazo a lo que están haciendo esos subprocesos. Hay que comprobar si
mantienen el bloqueo indefinidamente. Vamos a usar los comandos setthread y clrstack para alternar entre
cada uno de los subprocesos y mostrar las pilas de llamadas.
Para ver el primer subproceso, ejecute el comando setthread y busque el índice del subproceso 0x5634 (el índice
era 28). La función de interbloqueo está esperando a adquirir un bloqueo, pero ya posee dicho bloqueo. Está en
interbloqueo, a la espera del bloqueo que ya mantiene.
> setthread 28
> clrstack
OS Thread Id: 0x5634 (28)
Child SP IP Call Site
0000004E46AFEAA8 00007fff43a5cbc4 [GCFrame: 0000004e46afeaa8]
0000004E46AFEC18 00007fff43a5cbc4 [GCFrame: 0000004e46afec18]
0000004E46AFEC68 00007fff43a5cbc4 [HelperMethodFrame_1OBJ: 0000004e46afec68]
System.Threading.Monitor.Enter(System.Object)
0000004E46AFEDC0 00007FFE5EAF9C80 testwebapi.Controllers.DiagScenarioController.DeadlockFunc()
[C:\Users\dapine\Downloads\Diagnostic_scenarios_sample_debug_target\Controllers\DiagnosticScenarios.cs @ 58]
0000004E46AFEE30 00007FFE5EAF9B8C testwebapi.Controllers.DiagScenarioController.<deadlock>b__3_0()
[C:\Users\dapine\Downloads\Diagnostic_scenarios_sample_debug_target\Controllers\DiagnosticScenarios.cs @ 26]
0000004E46AFEE80 00007FFEBB251A5B System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
[/_/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 44]
0000004E46AFEEB0 00007FFE5EAEEEEC
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,
System.Threading.ContextCallback, System.Object)
[/_/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 201]
0000004E46AFEF20 00007FFEBB234EAB System.Threading.ThreadHelper.ThreadStart()
[/_/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 93]
0000004E46AFF138 00007ffebdcc6b63 [GCFrame: 0000004e46aff138]
0000004E46AFF3A0 00007ffebdcc6b63 [DebuggerU2MCatchHandlerFrame: 0000004e46aff3a0]

El segundo subproceso es similar. También está intentando adquirir un bloqueo que ya posee. Lo más probable es
que los más de 300 subprocesos restantes en espera estén esperando también a uno de los bloqueos que han
causado el interbloqueo.

Consulte también
dotnet-trace para mostrar procesos
dotnet-counters para comprobar el uso de memoria administrada
dotnet-dump para recopilar y analizar un archivo de volcado de memoria
dotnet/diagnostics

Pasos siguientes
¿Qué herramientas de diagnóstico están disponibles en .NET Core?
Analizador de API en .NET
18/03/2020 • 10 minutes to read • Edit Online

El analizador de API de .NET es un analizador Roslyn que detecta posibles riesgos de compatibilidad de API de C#
en distintas plataformas y detecta llamadas a API en desuso. Puede ser útil para todos los programadores de C#
en cualquier fase de desarrollo.
El analizador de API está disponible como un paquete NuGet Microsoft.DotNet.Analyzers.Compatibility. Tras hacer
referencia a él en un proyecto, supervisa automáticamente el código e indica el uso de API problemático. También
puede obtener sugerencias sobre posibles soluciones; para ello, haga clic en la bombilla. El menú desplegable
incluye una opción para suprimir las advertencias.

NOTE
El analizador de API de .NET aún está en versión preliminar.

Requisitos previos
Visual Studio 2017 o Visual Studio para Mac (todas las versiones).

Detección de API en desuso


¿Qué son las API en desuso?
La familia de .NET es un conjunto de productos de gran tamaño que se actualizan constantemente para satisfacer
mejor las necesidades del cliente. Es natural dejar de utilizar algunas API y sustituirlas por otras nuevas. Se
considera que una API está en desuso cuando existen alternativas mejores. Una manera de notificar que una API
está en desuso y que no debe utilizarse consiste en marcarla con el atributo ObsoleteAttribute. El inconveniente de
este enfoque es que hay un único identificador de diagnóstico para todas las API en desuso (en C#, CS0612). Esto
significa que:
Es imposible dedicar documentos a cada caso.
Es imposible suprimir determinadas categorías de advertencias. Puede suprimirlas todas o ninguna.
Para informar a los usuarios de una nueva degradación, es necesario actualizar un paquete de destino o un
ensamblado de referencia.
El analizador de API usa códigos de error específicos de API que empiezan por DE (que significa error de
degradación), que permite controlar la visualización de advertencias individuales. Las API en desuso identificadas
por el analizador se definen en el repositorio dotnet/platform-compat.
Adición del analizador de API al proyecto
1. Abra Visual Studio.
2. Abra el proyecto en el que quiera ejecutar el analizador.
3. En el Explorador de soluciones , haga clic con el botón derecho en el proyecto y seleccione Administrar
paquetes NuGet . (Esta opción también está disponible en el menú Proyecto ).
4. En la pestaña Administrador de paquetes NuGet:
a. Elija "nuget.org" como origen del paquete.
b. Vaya a la pestaña Examinar .
c. Seleccione Incluir versión preliminar .
d. Busque Microsoft.DotNet.Analyzers.Compatibility .
e. Seleccione ese paquete de la lista.
f. Seleccione el botón Instalar .
g. Seleccione el botón Aceptar en el cuadro de diálogo Vista previa de cambios y, a continuación,
seleccione el botón Acepto del cuadro de diálogo Aceptación de la licencia en caso de que esté de
acuerdo con los términos de licencia de los paquetes mostrados.
Uso del analizador de API
Cuando una API en desuso, como WebClient, se utiliza en un código, el analizador de API la destaca con una línea
ondulada de color verde. Si mantiene el puntero sobre la llamada API, se muestra una bombilla con información
sobre la degradación de la API, como en el ejemplo siguiente:

La ventana Lista de errores contiene advertencias con un identificador exclusivo por cada API en desuso, como
se muestra en el ejemplo siguiente ( DE004 ):

Al hacer clic en el identificador, se le remite a una página web que contiene información detallada sobre la API en
desuso y sugerencias sobre las API alternativas que pueden usarse.
Las advertencias pueden suprimirse si se hace clic con el botón derecho del ratón en el miembro resaltado y se
selecciona Suprimir <Id. de diagnóstico> . Hay dos maneras de suprimir las advertencias:
localmente (en el origen)
globalmente (en un archivo de supresión); se trata de la opción recomendada
Supresión de advertencias localmente
Para suprimir advertencias localmente, haga clic con el botón derecho en el miembro del que desea suprimir las
advertencias y luego seleccione Acciones rápidas y refactorizaciones > Suprimir Id. de diagnóstico <Id.
de diagnóstico> > en origen . La directiva del preprocesador de advertencias #pragma se agrega al código
fuente en el ámbito definido:
.
Supresión de advertencias globalmente
Para suprimir advertencias globalmente, haga clic con el botón derecho en el miembro del que desea suprimir las
advertencias y luego seleccione Acciones rápidas y refactorizaciones > Suprimir Id. de diagnóstico <Id.
de diagnóstico> > en archivo de supresión .

Se agrega un archivo GlobalSuppressions.cs al proyecto después de su primera supresión. Las nuevas supresiones
globales se anexan a este archivo.

La supresión global es el método recomendado para garantizar la coherencia del uso de API en los proyectos.

Detección de problemas multiplataforma


De forma similar a las API en desuso, el analizador identifica todas las API que no son multiplataforma. Por
ejemplo, Console.WindowWidth funciona en Windows, pero no en Linux y macOS. El Id. de diagnóstico se muestra
en la ventana Lista de errores . Puede suprimir dicha advertencia si hace clic con el botón derecho y selecciona
Acciones rápidas y refactorizaciones . A diferencia de los casos de degradación donde tiene dos opciones
(seguir usando el miembro en desuso y suprimir advertencias o no utilizarlo en absoluto), en este caso, si va a
desarrollar el código solo para algunas plataformas, puede suprimir todas las advertencias para todas las demás
plataformas en las que no pretende ejecutar el código. Para ello, solo tiene que editar el archivo de proyecto y
agregar la propiedad PlatformCompatIgnore que enumera todas las plataformas que se deben ignorar. Los valores
permitidos son: Linux , macOS y Windows .

<PropertyGroup>
<PlatformCompatIgnore>Linux;macOS</PlatformCompatIgnore>
</PropertyGroup>

Si el código tiene como destino varias plataformas y desea beneficiarse de una API que no es compatible en
algunas de ellas, puede proteger esa parte del código con una instrucción if :

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var w = Console.WindowWidth;
// More code
}

También puede compilar de forma condicional pro sistema operativo/marco de destino, pero actualmente necesita
hacerlo manualmente.

Diagnóstico admitido
Actualmente, el analizador controla los casos siguientes:
Uso de una API de .NET Standard que genera PlatformNotSupportedException (PC001).
Uso de una API de .NET Standard que no está disponible en .NET Framework 4.6.1 (PC002).
Uso de una API nativa que no existe en UWP (PC003).
Uso de las API Delegate.BeginInvoke y EndInvoke (PC004).
Uso de una API que está marcada como en desuso (DEXXXX).

Equipo de integración continua


Todos estos diagnósticos están disponibles no solo en el IDE, sino también en la línea de comandos como parte de
la creación del proyecto, que incluye el servidor de integración continua.

Configuración
El usuario decide cómo se deben tratar los diagnósticos: como advertencias, errores, sugerencias o estar
desactivados. Por ejemplo, como un arquitecto, puede decidir que los problemas de compatibilidad deben tratarse
como errores y que las llamadas a algunas API en desuso generen advertencias, mientras que otras solo generan
sugerencias. Puede configurar esto por separado por identificador de diagnóstico y por proyecto. Para ello, en el
Explorador de soluciones , vaya al nodo Dependencias del proyecto. Expanda los nodos Dependencias >
Analizadores > Microsoft.DotNet.Analyzers.Compatibility . Haga clic con el botón derecho en el
identificador de diagnóstico, seleccione Configurar gravedad del conjunto de reglas y elija la opción
deseada.
Vea también
Entrada de blog de introducción al analizador de API.
Vídeo de demostración del analizador de API de YouTube.
Analizador de portabilidad de .NET
16/09/2020 • 11 minutes to read • Edit Online

¿Quiere que sus bibliotecas sean multiplataforma? ¿Quiere ver cuánto trabajo se requiere para que su aplicación
de .NET Framework se ejecute en .NET Core? El Analizador de portabilidad de .NET es una herramienta que
analiza ensamblados y proporciona un informe detallado sobre las API de .NET que faltan para que las
aplicaciones o bibliotecas sean portátiles en las plataformas .NET de destino. El analizador de portabilidad se
ofrece como una extensión de Visual Studio, que analiza un ensamblado por proyecto y, como una aplicación de
consola ApiPort, que analiza los ensamblados por archivos especificados o directorio.
Cuando haya convertido el proyecto para que tenga como destino la nueva plataforma, como .NET Core, puede
usar la herramienta del analizador de API basada en Roslyn para identificar las API que producen excepciones
PlatformNotSupportedException y otras incidencias de compatibilidad.

Destinos comunes
.NET Core: tiene un diseño modular, admite la instalación en paralelo y está dirigido a escenarios
multiplataforma. La instalación en paralelo permite adoptar nuevas versiones de .NET Core sin que ello afecte
a otras aplicaciones. Si su objetivo es portar su aplicación a .NET Core para que sea compatible con varias
plataformas, este es el destino recomendado.
.NET Standard: Incluye las API de .NET Standard disponibles en todas las implementaciones de .NET. Si su
objetivo es que la biblioteca se ejecute en todas las plataformas compatibles con .NET, este es el destino
recomendado.
ASP.NET Core: Un marco web moderno basado en .NET Core. Si su objetivo es portar su aplicación web a .NET
Core para que sea compatible con varias plataformas, éste es el destino recomendado.
.NET Core + extensiones de plataforma: Incluye las API de .NET Core además del paquete de compatibilidad
de Windows, que proporciona muchas de las tecnologías disponibles de .NET Framework. Se trata de un
destino recomendado para la portabilidad de la aplicación de .NET Framework a .NET Core en Windows.
.NET standard + extensiones de la plataforma: Incluye las API de .NET Standard además del paquete de
compatibilidad de Windows, que proporciona muchas de las tecnologías disponibles de .NET Framework. Se
trata de un destino recomendado para la portabilidad de la biblioteca de .NET Framework a .NET Core en
Windows.

Cómo usar el Analizador de portabilidad de .NET


Para empezar a usar el Analizador de portabilidad de .NET en Visual Studio, primero debe descargar e instalar la
extensión desde Visual Studio Marketplace. Funciona en Visual Studio 2017 y versiones posteriores. Configúrelo
en Visual Studio, en Analizar > Por tability Analyzer Settings (Configuración del Analizador de portabilidad),
y seleccionando las plataformas de destino, que son las plataformas y versiones de .NET en las que se quiere
evaluar las brechas de portabilidad en comparación con la plataforma y versión con la que se ha creado el
ensamblado actual.
También puede usar la aplicación de consola ApiPort, descárguela desde el repositorio de ApiPort. Puede usar la
opción de comando listTargets para mostrar la lista de destinos disponible y elegir las plataformas de destino
especificando la opción de comando -t o --target .
Vista de toda la solución
Un paso útil en el análisis de una solución con muchos proyectos sería visualizar las dependencias para
comprender qué subconjunto de ensamblados depende de qué. La recomendación general es aplicar los
resultados del análisis en un enfoque de orden ascendente a partir de los nodos hoja de un gráfico de
dependencias.
Para recuperarlo, ejecute el siguiente comando:

ApiPort.exe analyze -r DGML -f [directory or file]

El resultado sería similar al siguiente al abrirse en Visual Studio:


Análisis de portabilidad
Para analizar todo el proyecto en Visual Studio, haga clic con el botón derecho en el proyecto en Explorador de
soluciones y seleccione Analyze Assembly Por tability (Analizar la portabilidad del ensamblado). También
puede ir al menú Analizar y seleccionar Analyze Assembly Por tability (Analizar la portabilidad del
ensamblado). Desde allí, seleccione el ejecutable o DLL del proyecto.

También puede usar la aplicación de consola ApiPort.


Escriba el comando siguiente para analizar el directorio actual: ApiPort.exe analyze -f .
Para analizar una lista específica de archivos .dll, escriba el comando siguiente:
ApiPort.exe analyze -f first.dll -f second.dll -f third.dll
Ejecute ApiPort.exe -? para obtener más ayuda.

Se recomienda que incluya todos los archivos exe y dll relacionados que posea y desea portar, y que excluya los
archivos de los que depende la aplicación, pero que no posee y no puede portar. Esto le proporcionará un
informe de portabilidad más relevante.
Vista e interpretación de los resultados de portabilidad
Solo las API que no son compatibles con una plataforma de destino aparecen en el informe. Después de ejecutar
el análisis en Visual Studio, verá que aparecerá el vínculo del archivo de informe de portabilidad de .NET. Si ha
usado la aplicación de consola ApiPort, el informe de portabilidad de .NET se guardará como archivo en el
formato especificado. El valor predeterminado es en un archivo de Excel ( .xlsx) ubicado en el directorio actual.
Resumen de portabilidad

En la sección Resumen de portabilidad del informe se muestra el porcentaje de portabilidad para cada
ensamblado incluido en la ejecución. En el ejemplo anterior, el 71,24 % de las API de .NET Framework utilizadas
en la aplicación svcutil están disponibles en .NET Core + extensiones de la plataforma. Si ejecuta la
herramienta Analizador de portabilidad de .NET en varios ensamblados, cada ensamblado debe tener una fila en
el informe de Resumen de portabilidad.
Detalles

La sección Detalles del informe enumera las API que faltan desde cualquiera de las plataformas de destino
seleccionadas.
Tipo de destino: al tipo le falta la API desde una plataforma de destino
Miembro de destino: el método no está presente en una plataforma de destino
Nombre del ensamblado: el ensamblado de .NET Framework en el que se encuentra la API que falta.
Cada una de las plataformas de destino seleccionada es una columna, como ".NET Core": El valor de "No
compatible" significa que la API no se admite en esta plataforma de destino.
Cambios recomendados: la API o tecnología recomendada a la que realizar el cambio. Actualmente, este
campo está vacío o no está actualizado para muchas de las API. Debido al gran número de API, nos
enfrentamos a un gran desafío para mantenerlas actualizadas. Estamos examinando soluciones alternativas
para proporcionar información útil a los clientes.
Ensamblados que faltan

Puede encontrar la sección Ensamblados que faltan en el informe. Esta sección contiene una lista de
ensamblados a los que hacen referencia los ensamblados analizados y que no se han analizado. Si se trata un
ensamblado que posee, inclúyalo en la ejecución del analizador de portabilidad de API para que pueda obtener
un informe detallado de portabilidad a nivel de API. Si se trata de una biblioteca de terceros, compruebe si hay
una versión más reciente que admita la plataforma de destino y considere la posibilidad de usar dicha versión
más reciente. Finalmente, la lista debe incluir todos los ensamblados de terceros de los que depende la
aplicación que tengan una versión compatible con la plataforma de destino.
Para obtener más información sobre el Analizador de portabilidad de .NET, visite la documentación de GitHub y
el vídeo de Channel 9 A Brief Look at the .NET Portability Analyzer (Información breve sobre el Analizador de
portabilidad de .NET).
Introducción a Common Language Runtime
16/09/2020 • 9 minutes to read • Edit Online

.NET Framework proporciona un entorno en tiempo de ejecución denominado Common Language Runtime, que
ejecuta el código y proporciona servicios que facilitan el proceso de desarrollo.
Los compiladores y las herramientas exponen la funcionalidad de Common Language Runtime y permiten escribir
código con las ventajas que proporciona este entorno de ejecución administrado. El código desarrollado con un
compilador de lenguaje orientado al tiempo de ejecución se denomina código administrado. Este código se
beneficia de características como: la integración entre lenguajes, el control de excepciones entre lenguajes, la
seguridad mejorada, la compatibilidad con la implementación y las versiones, un modelo simplificado de
interacción y servicios de generación de perfiles y depuración.

NOTE
Los compiladores y las herramientas pueden generar resultados que Common Language Runtime puede consumir porque el
sistema de tipos, el formato de metadatos y el entorno en tiempo de ejecución (el sistema de ejecución virtual) están todos
definidos según un estándar público, la especificación Common Language Infrastructure de ECMA. Para obtener más
información, consulte ECMA C# and Common Language Infrastructure Specifications (Especificaciones de ECMA C# y
Common Language Infrastructure).

Para permitir al motor en tiempo de ejecución proporcionar servicios al código administrado, los compiladores de
lenguajes deben emitir metadatos que describen los tipos, los miembros y las referencias del código. Los
metadatos se almacenan con el código; cada archivo ejecutable portable (PE) de Common Language Runtime
cargable contiene metadatos. El motor en tiempo de ejecución utiliza los metadatos para localizar y cargar clases,
colocar instancias en memoria, resolver invocaciones a métodos, generar código nativo, exigir mecanismos de
seguridad y establecer los límites del contexto en tiempo de ejecución.
El tiempo de ejecución controla automáticamente la disposición de los objetos y administra las referencias a éstos,
liberándolos cuando ya no se utilizan. Los objetos cuya duración se administra de esta forma se denominan datos
administrados. La recolección de elementos no utilizados elimina pérdidas de memoria así como otros errores
habituales de programación. Con un código administrado se pueden utilizar datos administrados, datos no
administrados o estos dos tipos de datos en una aplicación .NET Framework. Como los compiladores de lenguajes
proporcionan sus propios tipos, como tipos primitivos, no siempre se sabe (o no es necesario saber) si los datos se
están administrando.
Common Language Runtime facilita el diseño de los componentes y de las aplicaciones cuyos objetos interactúan
entre lenguajes distintos. Los objetos escritos en lenguajes diferentes pueden comunicarse entre sí, lo que permite
integrar sus comportamientos de forma precisa. Por ejemplo, puede definir una clase y, a continuación, utilizar un
lenguaje diferente para derivar una clase de la clase original o llamar a un método de la clase original. También se
puede pasar al método de una clase una instancia de una clase escrita en un lenguaje diferente. Esta integración
entre lenguajes diferentes es posible porque los compiladores y las herramientas de lenguajes orientados al motor
en tiempo de ejecución utilizan un sistema de tipos común definido por el motor en tiempo de ejecución, y los
lenguajes siguen las reglas en tiempo de ejecución para definir nuevos tipos, así como para crear, utilizar,
almacenar y enlazar tipos.
Como parte de los metadatos, todos los componentes administrados contienen información sobre los
componentes y los recursos utilizados en su compilación. El motor en tiempo de ejecución utiliza esta información
para garantizar que el componente o la aplicación contiene las versiones especificadas de todo lo necesario, por lo
que hay menos posibilidades de que la ejecución del código se interrumpa debido a una dependencia inadecuada.
La información de registro y los datos de estado ya no se almacenan en el Registro, donde puede ser difícil
establecer y mantener datos. En su lugar, la información sobre tipos definidos por el usuario (y sus dependencias)
se almacena con el código como metadatos y, de este modo, las tareas de replicación y eliminación de
componentes es mucho menos complicada.
Las herramientas y los compiladores de lenguajes exponen la funcionalidad del motor en tiempo de ejecución de
forma que resulte útil e intuitiva para los programadores. Esto significa que algunas características en tiempo de
ejecución pueden ser más evidentes en un entorno que en otro. El funcionamiento del motor en tiempo de
ejecución depende de las herramientas y los compiladores utilizados. Por ejemplo, un programador de Visual Basic
observará que con Common Language Runtime, el lenguaje Visual Basic contiene más características orientadas a
objetos que antes. El motor en tiempo de ejecución ofrece las siguientes ventajas:
Mejoras en el rendimiento.
Capacidad para utilizar fácilmente componentes desarrollados en otros lenguajes.
Tipos extensibles que proporciona una biblioteca de clases
Características del lenguaje como herencia, interfaces y sobrecarga para la programación orientada a
objetos.
Compatibilidad con subprocesamiento libre explícito que permite la creación de aplicaciones multiproceso
escalables.
Compatibilidad con el control de excepciones estructurado.
Compatibilidad con atributos personalizados.
Recolección de elementos no utilizados.
Emplee delegados en lugar de punteros a funciones para mayor seguridad y protección de tipos. Para más
información acerca de los delegados, consulte Common Type System.

Versiones de CLR
El número de versión de .NET Framework no se corresponde necesariamente con el número de versión del CLR
que incluye. Para obtener una lista de las versiones de .NET Framework y sus versiones de CLR correspondientes,
consulte Versiones y dependencias de .NET Framework. Las versiones de .NET Core tienen una versión de producto
única, es decir, no hay ninguna versión independiente de CLR. Para obtener una lista de las versiones de .NET Core,
consulte Descarga de .NET Core.

Temas relacionados
T IT L E DESC RIP C IÓ N

Proceso de ejecución administrada Describe los pasos requeridos para aprovechar al máximo las
ventajas de Common Language Runtime.

Administración automática de la memoria Describe cómo asigna y libera memoria el recolector de


elementos no utilizados.

Información general acerca de .NET Framework Describe conceptos clave de .NET Framework como Common
Type System (CTS), interoperabilidad entre lenguajes, ejecución
administrada, dominios de aplicación y ensamblados.
T IT L E DESC RIP C IÓ N

Sistema de tipos comunes Describe cómo declarar, usar y administrar tipos en el motor
en tiempo de ejecución para permitir la integración entre
lenguajes.
proceso de ejecución administrada
16/09/2020 • 16 minutes to read • Edit Online

El proceso de ejecución administrada incluye los pasos siguientes, que se describen en detalle más adelante en
este tema:
1. Elegir un compilador.
Para obtener los beneficios que proporciona Common Language Runtime, se deben utilizar uno o más
compiladores de lenguaje orientados al tiempo de ejecución.
2. Compilar código a MSIL.
La compilación convierte el código fuente en lenguaje intermedio de Microsoft (MSIL) y genera los
metadatos necesarios.
3. Compilar MSIL a código nativo.
En tiempo de ejecución, un compilador Just-In-Time (JIT) convierte MSIL en código nativo. Durante esta
compilación, el código debe pasar un proceso de comprobación que examina el MSIL y los metadatos para
averiguar si el código garantiza la seguridad de tipos.
4. Ejecutar código.
Common Language Runtime proporciona la infraestructura que permite que la ejecución tenga lugar y los
servicios que se pueden usar durante la ejecución.

Elegir un compilador
Para aprovechar las ventajas que ofrece Common Language Runtime (CLR), deben emplearse uno o varios
compiladores de lenguaje destinados al motor en tiempo de ejecución, como Visual Basic, C#, Visual C++, F# o
uno de los muchos compiladores de otros fabricantes, como un compilador Eiffel, Perl o COBOL.
Como se ejecuta en un entorno multilenguaje, el motor en tiempo de ejecución es compatible con una gran
variedad de tipos de datos y características de lenguajes. El compilador de lenguaje utilizado determina las
características en tiempo de ejecución que están disponibles, y el código se diseña con esas características. El
compilador, y no el motor en tiempo de ejecución, es el que establece la sintaxis que se debe utilizar en el código.
Si los componentes escritos en otros lenguajes deben poder usar plenamente el componente, los tipos exportados
de ese componente solo deben exponer las características del lenguaje incluidas en Independencia del lenguaje y
componentes independientes del lenguaje (CLS). Puede utilizar el atributo CLSCompliantAttribute para garantizar
que su código es conforme a CLS. Para más información, consulte Independencia del lenguaje y componentes
independientes del lenguaje.
Volver al principio

Compilar en lenguaje intermedio de Microsoft (MSIL)


Cuando se compila a código administrado, el compilador convierte el código fuente en Lengua intermedio de
Microsoft (MSIL), que es un conjunto de instrucciones independiente de la CPU que se pueden convertir de forma
eficaz en código nativo. MSIL incluye instrucciones para cargar, almacenar, inicializar y llamar a métodos en los
objetos, así como instrucciones para operaciones lógicas y aritméticas, flujo de control, acceso directo a la
memoria, control de excepciones y otras operaciones. Antes de poder ejecutar código, se debe convertir MSIL al
código específico de la CPU, normalmente mediante un compilador Just-In-Time (JIT). Common Language
Runtime proporciona uno o varios compiladores JIT para cada arquitectura de equipo compatible, por lo que se
puede compilar y ejecutar el mismo conjunto de MSIL en cualquier arquitectura compatible.
Cuando el compilador produce MSIL, también genera metadatos. Los metadatos describen los tipos que aparecen
en el código, incluidas las definiciones de los tipos, las firmas de los miembros de tipos, los miembros a los que se
hace referencia en el código y otros datos que el motor en tiempo de ejecución utiliza en tiempo de ejecución.
MSIL y los metadatos están contenidos en un archivo ejecutable portable (PE), que se basa y extiende el PE de
Microsoft publicado y el formato de archivo de objeto común (COFF) usado tradicionalmente para contenido
ejecutable. Este formato de archivo, que contiene código MSIL o código nativo así como metadatos, permite al
sistema operativo reconocer imágenes de Common Language Runtime. La presencia de metadatos en el archivo
junto con MSIL permite crear códigos autodescriptivos, con lo cual las bibliotecas de tipos y el Lenguaje de
definición de interfaz (IDL) son innecesarios. El motor en tiempo de ejecución localiza y extrae los metadatos del
archivo cuando son necesarios durante la ejecución.
Volver al principio

Compilar MSIL a código nativo


Para poder ejecutar el lenguaje intermedio de Microsoft (MSIL), primero debe compilarse a código nativo con
Common Language Runtime para la arquitectura del equipo de destino. .NET Framework proporciona dos
mecanismos para realizar esta conversión:
Un compilador Just-In-Time (JIT) de .NET Framework.
Ngen.exe (Generador de imágenes nativas) de .NET Framework.
Compilación mediante el compilador JIT
La compilación JIT convierte MSIL en código nativo a petición en el tiempo de ejecución de la aplicación, cuando el
contenido de un ensamblado se carga y ejecuta. Common Language Runtime proporciona un compilador JIT para
cada arquitectura de CPU compatible, por lo que los programadores pueden crear un conjunto de ensamblados
MSIL que se puede compilar con un compilador JIT y se puede ejecutar en equipos distintos con diferentes
arquitecturas. No obstante, si el código administrado llama a las API nativas específicas de la plataforma o a una
biblioteca de clases específica de la plataforma, solo se ejecutará en ese sistema operativo.
La compilación JIT tiene en cuenta la posibilidad de que durante la ejecución nunca se llame a parte del código. En
vez de emplear tiempo y memoria para convertir todo el MSIL de un archivo PE a código nativo, convierte el MSIL
necesario durante la ejecución y almacena el código nativo resultante en memoria para que sea accesible en las
llamadas posteriores que se produzcan en el contexto de ese proceso. El cargador crea y asocia un código auxiliar
a cada método de un tipo cuando este tipo se carga y se inicializa. Cuando se llama a un método por primera vez,
el código auxiliar pasa el control al compilador JIT, el cual convierte el MSIL del método en código nativo y
modifica el código auxiliar para señalar directamente al código nativo generado. Por tanto, las siguientes llamadas
al método compilado mediante un compilador JIT pasan directamente al código nativo.
Generación de código en tiempo de instalación mediante NGen.exe
Puesto que el compilador JIT convierte el MSIL de un ensamblado en código nativo cuando se llama a cada uno de
los métodos definidos en ese ensamblado, esto afecta negativamente al rendimiento en tiempo de ejecución. En la
mayoría de los casos, esa disminución del rendimiento es aceptable. Y lo que es más importante, el código
generado por el compilador JIT se enlaza al proceso que activó la compilación. Este código no se puede compartir
en varios procesos. Para permitir que el código generado se comparta en varias invocaciones de una aplicación o
en varios procesos que compartan un conjunto de ensamblados, Common Language Runtime admite un modo de
compilación anticipado. Este modo de compilación Ahead Of Time usa Ngen.exe (Generador de imágenes nativas)
para convertir los ensamblados MSIL en código nativo de forma muy similar a como lo hace el compilador JIT. Sin
embargo, la operación de Ngen.exe se diferencia de la del compilador JIT en tres aspectos:
Realiza la conversión de MSIL a código nativo antes de ejecutar la aplicación en lugar de realizarla durante
su ejecución.
Compila a la vez un ensamblado completo, en lugar de compilar un método cada vez.
Conserva el código generado en la memoria caché de imágenes nativas como un archivo en disco.
Comprobación del código
Como parte de su compilación en código nativo, el código de MILS debe pasar un proceso de comprobación, a
menos que un administrador haya establecido una directiva de seguridad que permita al código omitir esta
comprobación. En esta comprobación se examina el MSIL y los metadatos para determinar si el código garantiza la
seguridad de tipos, lo que significa que el código solo tiene acceso a aquellas ubicaciones de la memoria para las
que está autorizado. La seguridad de tipos ayuda a aislar los objetos entre sí y ayuda a protegerlos frente a daños
involuntarios o malintencionados. Además, garantiza que las restricciones de seguridad sobre el código se aplican
con toda certeza.
El motor en tiempo de ejecución se basa en el hecho de que se cumplan las siguientes condiciones para el código
seguro comprobable:
La referencia a un tipo es estrictamente compatible con el tipo al que hace referencia.
En un objeto sólo se invocan las operaciones definidas adecuadamente.
Una identidad es precisamente lo que dice ser.
Durante el proceso de comprobación, se examina el código MSIL para intentar confirmar que el código tiene
acceso a las ubicaciones de memoria y puede llamar a los métodos sólo a través de los tipos definidos
correctamente. Por ejemplo, un código no permite el acceso a los campos de un objeto si esta acción sobrecarga
las ubicaciones de memoria. Además, el proceso de comprobación examina el código para determinar si el MSIL
se ha generado correctamente, ya que un MSIL incorrecto puede llevar a la infracción de las reglas en materia de
seguridad de tipos. El proceso de comprobación pasa un conjunto de código seguro bien definido, y pasa
exclusivamente código seguro. No obstante, algunos códigos con seguridad de tipos no pasan la comprobación
debido a algunas limitaciones de este proceso, y algunos lenguajes no producen código seguro comprobable
debido a su diseño. Si la directiva de seguridad requiere código seguro de tipos y el código no pasa la
comprobación, se produce una excepción al ejecutar el código.
Volver al principio

Ejecutar código
Common Language Runtime proporciona la infraestructura que permite que la ejecución administrada tenga
lugar y los servicios que se pueden usar durante la ejecución. Para poder ejecutar un método, primero se debe
compilar a código específico del procesador. Cada método para el que se ha generado código de MSIL se compila
mediante un compilador JIT la primera vez que se le llama y, después, se ejecuta. La próxima vez que se ejecuta el
método, se ejecuta el código nativo existente resultante de la compilación JIT. El proceso de compilación JIT y la
posterior ejecución de código se repite hasta completar la ejecución.
Durante la ejecución, el código administrado recibe servicios como la recolección de elementos no utilizados,
seguridad, interoperabilidad con código no administrado, compatibilidad de depuración entre lenguajes diferentes
y compatibilidad mejorada con el control de versiones y la implementación.
En Microsoft Windows Vista, el cargador del sistema operativo comprueba los módulos administrados mediante el
examen de un bit del encabezado de COFF. El bit que se establece indica un módulo administrado. Si el cargador
detecta módulos administrados, carga mscoree.dll, y _CorValidateImage y _CorImageUnloading notifican al
cargador cuándo se cargan y descargan imágenes del módulo administrado. _CorValidateImage lleva a cabo las
acciones siguientes:
1. Garantiza que el código es código administrado válido.
2. Cambia el punto de entrada en la imagen a un punto de entrada en el motor en tiempo de ejecución.
En las versiones de 64 bits de Windows, _CorValidateImage modifica la imagen que está en la memoria
transformando el formato PE32 en PE32+.
Volver al principio

Vea también
Información general
Independencia del lenguaje y componentes independientes del lenguaje
Metadatos y componentes autodescriptivos
Ilasm.exe (Ensamblador de IL)
Seguridad
Interoperating with Unmanaged Code (Interoperar con código no administrado)
Implementación
Ensamblados de .NET
Dominios de aplicación
Ensamblados de .NET
16/09/2020 • 14 minutes to read • Edit Online

Los ensamblados componen las unidades fundamentales de implementación, control de versiones, reutilización,
ámbito de activación y permisos de seguridad para las aplicaciones basadas en .NET. Un ensamblado es una
colección de tipos y recursos compilados para funcionar en conjunto y formar una unidad lógica de funcionalidad.
Los ensamblados adoptan la forma de un archivo ejecutable ( .exe) o de biblioteca de vínculos dinámicos ( .dll), y
son los bloques de creación de las aplicaciones .NET. Proporcionan a Common Language Runtime la información
necesaria para conocer las implementaciones de tipos.
En .NET Core y .NET Framework, puede crear un ensamblado a partir de uno o varios archivos de código fuente. En
.NET Framework, los ensamblados pueden contener uno o varios módulos. Esto permite que los proyectos más
grandes se planeen para que varios desarrolladores individuales puedan trabajar en archivos de código fuente o
módulos independientes, que se combinan para crear un ensamblado único. Para más información sobre los
módulos, vea el tema Procedimiento para compilar un ensamblado de varios archivos.
Los ensamblados tienen las propiedades siguientes:
Los ensamblados se implementan como archivos .exe o .dll.
Para las bibliotecas destinadas a .NET Framework, puede compartir los ensamblados entre aplicaciones si
los coloca en la caché global de ensamblados (GAC). Debe asignar nombres seguros a los ensamblados
antes de poder incluirlos en la GAC. Para obtener más información, vea Ensamblados con nombre seguro.
Los ensamblados solo se cargan en memoria si son necesarios. Si no se usan, no se cargan. Esto significa
que los ensamblados pueden ser una manera eficaz de administrar recursos en proyectos más grandes.
Mediante programación, puede obtener información sobre un ensamblado mediante reflexión. Para más
información, vea Reflexión (C#) o Reflexión (Visual Basic).
Puede cargar un ensamblado solo para inspeccionarlo mediante la clase MetadataLoadContext en .NET
Core y los métodos Assembly.ReflectionOnlyLoad o Assembly.ReflectionOnlyLoadFrom en .NET Core y .NET
Framework.

Ensamblados en Common Language Runtime


Los ensamblados proporcionan a Common Language Runtime la información necesaria para conocer las
implementaciones de tipos. Para la ejecución, un tipo no existe fuera del contexto de un ensamblado.
Un ensamblado define la información siguiente:
El código que ejecuta Common Language Runtime. Tenga en cuenta que cada ensamblado solo puede tener
un punto de entrada: DllMain , WinMain o Main .
Límite de seguridad. Un ensamblado es la unidad en la que se solicitan y conceden los permisos. Para
obtener más información sobre los límites de seguridad en los ensamblados, vea Consideraciones de
seguridad sobre ensamblados.
Límite de tipos. La identidad de cada tipo incluye el nombre del ensamblado en que reside. Por ello, un tipo
MyType cargado en el ámbito de un ensamblado no es igual que un tipo denominado MyType cargado en el
ámbito de otro ensamblado.
Límite del ámbito de referencia. El manifiesto del ensamblado contiene los metadatos que se usan para
resolver tipos y satisfacer solicitudes de recursos. En el manifiesto se especifican los tipos y recursos que se
exponen fuera del ensamblado y se enumeran otros ensamblados de los que depende. El código del
lenguaje intermedio de Microsoft (MSIL) de un archivo ejecutable portable (PE) no se ejecutará a menos
que tenga un manifiesto del ensamblado asociado.
Límite de versión. El ensamblado es la unidad versionable más pequeña de Common Language Runtime.
Todos los tipos y recursos del mismo ensamblado tienen la misma versión, como una unidad. El manifiesto
del ensamblado describe las dependencias de versión que especifique para los ensamblados dependientes.
Para obtener más información sobre el control de versiones, vea Versiones de los ensamblados.
Unidad de implementación. Cuando se inicia una aplicación, sólo deben estar presentes los ensamblados a
los que llama la aplicación inicialmente. Los demás ensamblados, como los que contengan recursos de
localización o clases de utilidad, se pueden recuperar a petición. Esto permite que las aplicaciones sean
sencillas y ligeras la primera vez que se descargan. Para obtener más información sobre cómo implementar
ensamblados, vea Implementación de aplicaciones.
Unidad de ejecución en paralelo. Para obtener más información sobre la ejecución de varias versiones de un
ensamblado, vea Ensamblados y ejecución en paralelo.

Creación de un ensamblado
Los ensamblados pueden ser estáticos o dinámicos. Los ensamblados estáticos se almacenan en el disco, en
archivos ejecutables portables PE. Los ensamblados estáticos pueden incluir interfaces, clases y recursos como
mapas de bits, archivos JPEG y otros archivos de recursos. También puede crear ensamblados dinámicos, que se
ejecutan directamente desde la memoria y no se guardan en el disco antes de su ejecución. Los ensamblados
dinámicos se pueden guardar en el disco una vez que se hayan ejecutado.
Existen varias formas de crear ensamblados. Puede usar herramientas de desarrollo, como Visual Studio, que
permite crear archivos .dll o .exe. Puede usar las herramientas de Windows SDK para crear ensamblados con
módulos de otros entornos de programación. También puede utilizar las API de Common Language Runtime,
como System.Reflection.Emit, para crear ensamblados dinámicos.
Para compilar ensamblados puede hacerlo en Visual Studio, con las herramientas de la interfaz de la línea de
comandos de .NET Core o, para los ensamblados de .NET Framework, con un compilador de línea de comandos.
Para más información sobre cómo compilar los ensamblados con la CLI de .NET Core, consulte Información
general sobre la CLI de .NET Core. Para compilar ensamblados con los compiladores de línea de comandos, vea
Compilar la línea de comandos con csc.exe para C#, o bien Compilar desde la línea de comandos (Visual Basic)
para Visual Basic.

NOTE
Para compilar un ensamblado en Visual Studio, seleccione Compilar en el menú Compilar .

Manifiesto del ensamblado


Todos los ensamblados tienen un archivo de manifiesto del ensamblado. De forma similar a una tabla de
contenido, el manifiesto del ensamblado contiene lo siguiente:
La identidad del ensamblado (su nombre y versión).
Una tabla de archivos en la que se describen todos los demás archivos que componen el ensamblado, como
otros ensamblados que haya creado y en los que se basa el archivo .exe o .dll, archivos de mapa de bits o
Léame.
Una lista de referencias de ensamblado, que es una lista de todas las dependencias externas, como archivos
.dll o de otro tipo. Las referencias de ensamblado contienen referencias a objetos globales y privados. Los
objetos globales están disponibles para todas las demás aplicaciones. En .NET Core, los objetos globales
están acoplados con un runtime de .NET Core determinado. En .NET Framework, los objetos globales
residen en la caché global de ensamblados (GAC). System.IO.dll es un ejemplo de un ensamblado en la GAC.
Los objetos privados deben estar en un directorio del mismo nivel o debajo del directorio en el que se
instala la aplicación.
Como los ensamblados contienen información sobre contenido, control de versiones y dependencias, las
aplicaciones que los usan no confían en orígenes externos, como el Registro de los sistemas Windows, para
funcionar correctamente. Los ensamblados reducen los conflictos de .dll y hacen que las aplicaciones sean más
confiables y fáciles de implementar. En muchos casos, puede instalar una aplicación basada en .NET con tan solo
copiar sus archivos en el equipo de destino. Para obtener más información, vea Manifiesto del ensamblado.

Incorporación de una referencia a un ensamblado


Para usar un ensamblado en una aplicación, debe agregar una referencia a él. Una vez que se hace referencia a un
ensamblado, todos los tipos accesibles, propiedades, métodos y otros miembros de sus espacios de nombres
están disponibles para la aplicación como si su código formara parte del archivo de origen.

NOTE
La referencia a la mayoría de los ensamblados de la biblioteca de clases de .NET se hace automáticamente. Si no se hace
referencia a un ensamblado del sistema de forma automática, para .NET Core puede agregar una referencia al paquete
NuGet que contiene el ensamblado. Use el administrador de paquetes NuGet de Visual Studio, o bien agregue un elemento
<PackageReference> para el ensamblado al proyecto .csproj o .vbproj. En .NET Framework, puede agregar una referencia al
ensamblado mediante el cuadro de diálogo Agregar referencia de Visual Studio o mediante la opción de la línea de
comandos -reference para los compiladores de C# o Visual Basic.

En C#, puede usar dos versiones del mismo ensamblado en una misma aplicación. Para obtener más información,
vea alias externo.

Contenido relacionado
T IT L E DESC RIP C IÓ N

Contenido de los ensamblados Elementos que componen un ensamblado.

Manifiesto del ensamblado Datos del manifiesto del ensamblado y cómo se almacenan en
los ensamblados.

Caché global de ensamblados Cómo almacena y usa los ensamblados la GAC.

Ensamblados con nombre seguro Características de los ensamblados con nombre seguro.

Consideraciones de seguridad sobre ensamblados Cómo funciona la seguridad de los ensamblados.

Control de versiones de los ensamblados Información general sobre la directiva de control de versiones
de .NET Framework.

Colocación de ensamblados Dónde ubicar los ensamblados.

Ensamblados y ejecución en paralelo Uso de varias versiones del runtime o de un ensamblado


simultáneamente.
T IT L E DESC RIP C IÓ N

Emitir métodos y ensamblados dinámicos Cómo crear ensamblados dinámicos.

Cómo el motor en tiempo de ejecución ubica ensamblados Cómo resuelve .NET Framework las referencias de ensamblado
en tiempo de ejecución.

Referencia
System.Reflection.Assembly

Vea también
Formato de archivo de ensamblado .NET
Ensamblados de confianza
Ensamblados de referencia
Cómo: para cargar y descargar ensamblados
Cómo: para usar y depurar la descargabilidad de ensamblados en .NET Core
Cómo: para determinar si un archivo es un ensamblado
Cómo: Inspect assembly contents using MetadataLoadContext (Procedimiento para inspeccionar el contenido
de un ensamblado con MetadataLoadContext)
Metadatos y componentes autodescriptivos
16/09/2020 • 16 minutes to read • Edit Online

Hasta ahora, un componente de software (.exe o .dll) escrito en un lenguaje no podía usar fácilmente un
componente de software escrito en otro lenguaje. COM supuso un paso adelante en la resolución de este
problema. .NET Framework hace la interoperación entre componentes todavía más fácil, permitiendo que los
compiladores emitan información de declaración adicional en todos los módulos y ensamblados. Esta información,
denominada metadatos, contribuye a que los componentes interactúen sin problemas.
Los metadatos son información binaria que describe un programa, almacenada en un archivo ejecutable portable
(PE) de Common Language Runtime o en memoria. Cuando se compila el código en un archivo PE, los metadatos
se insertan en una parte del archivo, y el código se convierte al lenguaje intermedio de Microsoft (MSIL) y se
inserta en otra parte del archivo. Cada tipo y miembro que se define y al que se hace referencia en un módulo o
ensamblado se describe en los metadatos. Cuando se ejecuta código, el motor en tiempo de ejecución carga los
metadatos en la memoria y hace referencia a ellos para detectar información acerca de las clases, miembros,
herencia, etc., del código.
Los metadatos describen todos los tipos y miembros definidos en el código mediante un lenguaje neutro. Los
metadatos almacenan la siguiente información:
Descripción del ensamblado
Identidad (nombre, versión, referencia cultural, clave pública).
Los tipos que se exportan.
Otros ensamblados de los que depende éste.
Permisos de seguridad que hay que ejecutar.
Descripción de los tipos.
Nombre, visibilidad, clase base e interfaces implementadas.
Miembros (métodos, campos, propiedades, eventos, tipos anidados).
Atributos.
Elementos descriptivos adicionales que modifiquen los tipos y los miembros.

Ventajas de los metadatos


Los metadatos son la clave para un modelo de programación más sencillo y eliminando la necesidad de tener
archivos de Lenguaje de definición de interfaz (IDL), archivos de encabezado o cualquier método externo de
referencia a componentes. Los metadatos permiten que los lenguajes de .NET Framework se describan a sí mismos
automáticamente independientemente del lenguaje, que no ven ni el programador ni el usuario. Además, los
metadatos se pueden extender mediante el uso de atributos. Los metadatos proporcionan las siguientes ventajas
principales:
Archivos autodescriptivos
Los módulos y ensamblados de Common Language Runtime son autodescriptivos. Los metadatos de un
módulo contienen todo lo necesario para interactuar con otro módulo. Los metadatos proporcionan
automáticamente la funcionalidad del IDL en COM, por lo que puede usar un archivo tanto para la definición
como para la implementación. Los módulos y ensamblados de Common Language Runtime no necesitan ni
registrarse en el sistema operativo. En consecuencia, las descripciones que usa el motor en tiempo de
ejecución reflejan siempre el código actual del archivo compilado, lo que aumenta la confiabilidad de la
aplicación.
Interoperabilidad de lenguajes y diseño más sencillo, basado en el componente.
Los metadatos proporcionan toda la información necesaria sobre el código compilado para derivar clases
de archivos PE escritos en otro lenguaje. Se puede crear una instancia de cualquier clase escrita en cualquier
lenguaje administrado (cualquier lenguaje dirigido a Common Language Runtime) sin tener que
preocuparse por la serialización explícita ni por usar código de interoperabilidad personalizado.
Atributos.
.NET Framework le permite declarar determinados tipos de metadatos, denominados atributos, en el archivo
compilado. Los atributos se encuentran en todo .NET Framework y se usan para controlar más
minuciosamente el comportamiento del programa en tiempo de ejecución. Además, se pueden emitir
metadatos personalizados propios en los archivos .NET Framework mediante atributos personalizados
definidos por el usuario. Para obtener más información, consulte Attributes (Atributos).

Metadatos y la estructura del archivo PE


Los metadatos se almacenan en una sección de un archivo ejecutable portable (PE) de .NET Framework, mientras
que el lenguaje intermedio de Microsoft (MSIL) se guarda en otra sección del mismo archivo. La parte de los
metadatos del archivo contiene una serie de estructuras de datos de tablas y montones. La parte del MSIL contiene
símbolos (token) de MSIL y de metadatos que hacen referencia a la parte de metadatos del archivo PE. Puede
encontrarse con tokens de metadatos al usar herramientas como el Desensamblador de MSIL (Ildasm.exe) para ver
el MSIL del código, por ejemplo.
Tablas y montones de metadatos
Cada tabla de metadatos tiene información sobre los elementos del programa. Por ejemplo, una tabla de
metadatos describe las clases del código, otra los campos, etc. Si hay diez clases en el código, la tabla de clases
tendrá diez filas, una por clase. Las tablas de metadatos hacen referencia a otras tablas y montones. Por ejemplo, la
tabla de metadatos de clases hace referencia a la tabla de métodos.
Los metadatos también almacenan información en cuatro estructuras de montón: cadena, objeto binario, cadena
de usuario y GUID. Todas las cadenas usadas en los nombres de los tipos y los miembros se almacenan en el
montón de cadenas. Por ejemplo, una tabla de métodos no almacena directamente el nombre de un método
concreto, sino que señala al nombre del método almacenado en el montón de cadenas.
Símbolos (token) de metadatos
Cada fila de cada tabla de metadatos se identifica de forma exclusiva en la parte de MSIL del archivo PE mediante
un token de metadatos. Los tokens de metadatos responden a un concepto similar a los punteros, que persisten en
MSIL, que hacen referencia a una tabla de metadatos concreta.
Un token de metadatos es un número de cuatro bytes. El byte superior indica la tabla de metadatos a la que se
refiere un token concreto (método, tipo, etc.). Los tres bytes restantes especifican la fila de la tabla de metadatos
que corresponde al elemento de programación que se describe. Si se define un método en C# y se compila en un
archivo PE, en la porción de MSIL del archivo PE podrían aparecer los tokens de metadatos siguientes:
0x06000004

El byte superior ( 0x06 ) indica que este es un token de MethodDef . Los tres bytes inferiores ( 000004 ) indican a
Common Language Runtime que busque en la cuarta fila de la tabla MethodDef la información que describe la
definición de este método.
Metadatos en un archivo PE
Cuando se compila un programa para Common Language Runtime, se convierte en un archivo PE formado por
tres partes. La tabla siguiente describe el contenido de cada una de estas partes.

SEC C IÓ N DEL A RC H IVO P E C O N T EN IDO DE L A SEC C IÓ N DEL A RC H IVO P E

Encabezado PE El índice de las secciones principales del archivo PE y la


dirección del punto de entrada.

El motor en tiempo de ejecución usa esta información para


identificar el archivo como archivo PE y para determinar dónde
comienza la ejecución al cargar el programa en la memoria.

Instrucciones MSIL Instrucciones del Lenguaje intermedio de Microsoft (MSIL) que


contiene el código. Muchas de las instrucciones MSIL van
acompañadas por tokens de metadatos.

Metadatos Tablas y montones de metadatos. El motor en tiempo de


ejecución usa esta sección para registrar información sobre
todos los tipos y miembros del código. Esta sección incluye
también atributos personalizados e información de seguridad.

Uso de metadatos en tiempo de ejecución


Para comprender mejor los metadatos y su uso en Common Language Runtime, puede resultar útil construir un
programa sencillo y mostrar cómo afectan los metadatos a su comportamiento durante su ejecución. El siguiente
ejemplo de código muestra dos métodos dentro de una clase llamada MyApp . El método Main es el punto de
entrada del programa, mientras que el método Add simplemente devuelve la suma de dos argumentos de
enteros.

Public Class MyApp


Public Shared Sub Main()
Dim ValueOne As Integer = 10
Dim ValueTwo As Integer = 20
Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo))
End Sub

Public Shared Function Add(One As Integer, Two As Integer) As Integer


Return (One + Two)
End Function
End Class

using System;
public class MyApp
{
public static int Main()
{
int ValueOne = 10;
int ValueTwo = 20;
Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo));
return 0;
}
public static int Add(int One, int Two)
{
return (One + Two);
}
}

Cuando se ejecuta el código, el motor en tiempo de ejecución carga el módulo en la memoria y consulta los
metadatos de esta clase. Una vez cargado, el motor en tiempo de ejecución realiza una análisis exhaustivo de la
secuencia de lenguaje intermedio de Microsoft (MSIL) del método para convertirla en rápidas instrucciones
máquina nativas. El motor en tiempo de ejecución usa un compilador Just-In-Time (JIT) para convertir las
instrucciones MSIL en código máquina nativo, método a método, según sea necesario.
En el siguiente ejemplo de código se muestra parte del MSIL producido a partir de la función Main del código
anterior. El MSIL y los metadatos se pueden ver desde cualquier aplicación de .NET Framework usando el
Desensamblador de MSIL (Ildasm.exe).

.entrypoint
.maxstack 3
.locals ([0] int32 ValueOne,
[1] int32 ValueTwo,
[2] int32 V_2,
[3] int32 V_3)
IL_0000: ldc.i4.s 10
IL_0002: stloc.0
IL_0003: ldc.i4.s 20
IL_0005: stloc.1
IL_0006: ldstr "The Value is: {0}"
IL_000b: ldloc.0
IL_000c: ldloc.1
IL_000d: call int32 ConsoleApplication.MyApp::Add(int32,int32) /* 06000003 */

El compilador JIT lee el MSIL de todo el método, lo analiza exhaustivamente y genera instrucciones nativas
efectivas para ese método. En IL_000d se encuentra un token de metadatos del método Add ( /* 06000003 */ ), y
el motor en tiempo de ejecución usa ese token para consultar la tercera fila de la tabla MethodDef .
En la siguiente tabla, se muestra parte de la tabla MethodDef a la que hace referencia el token de los metadatos
que describe el método Add . Aunque existen otras tablas de metadatos en el ensamblado y tienen sus propios
valores únicos, sólo se trata esta tabla.

N O M B RE
F IRM A ( SEÑ A L A
DIREC C IÓ N ( SEÑ A L A EL EL M O N TÓ N DE
REL AT IVA M O N TÓ N DE O B JETO S
F IL A VIRT UA L ( RVA ) IM P L F L A GS M A RC A S C A DEN A S) . B IN A RIO S)

1 0x00002050 IL Public .ctor (constructor)

Administrado ReuseSlot

SpecialName

RTSpecialName

.ctor

2 0x00002058 IL Public Método Main String

Administrado Estático

ReuseSlot

3 0x0000208c IL Public Agregar int, int, int

Administrado Estático

ReuseSlot
Cada columna de la tabla contiene información importante sobre el código. La columna RVA permite que el motor
en tiempo de ejecución calcule la dirección de memoria de inicio del MSIL que define este método. Las columnas
ImplFlags y Flags contienen máscaras de bits que describen el método (por ejemplo, si el método es público o
privado). La columna Nombre indexa el nombre del método del montón de cadenas. La columna Firma indexa la
definición de la firma del método del montón de blobs.
El motor en tiempo de ejecución calcula la dirección de desplazamiento deseada desde la columna RVA de la
tercera fila y la devuelve al compilador JIT, que, después, se dirige a la nueva dirección. El compilador JIT continúa
procesando el MSIL en la nueva dirección hasta que encuentra otro token de metadatos y se repite el proceso.
Usando metadatos, el motor en tiempo de ejecución tiene acceso a toda la información que necesita para cargar el
código y procesarlo en instrucciones máquina nativas. De este modo, los metadatos hacen posible los archivos
autodescriptivos y, junto con el sistema de tipos comunes, la herencia de un lenguaje a otro.

Temas relacionados
T IT L E DESC RIP C IÓ N

Atributos Describe cómo aplicar atributos, escribir atributos


personalizados y recuperar información almacenada en
atributos.
Carga de dependencias en .NET Core
16/09/2020 • 2 minutes to read • Edit Online

Todas las aplicaciones .NET Core tienen dependencias. Incluso la aplicación sencilla hello world tiene
dependencias en partes de las bibliotecas de clases de .NET Core.
La descripción de la lógica de carga de ensamblados predeterminados de .NET Core puede ayudar a comprender y
depurar incidencias de implementación habituales.
En algunas aplicaciones, las dependencias se determinan dinámicamente en tiempo de ejecución. En estas
situaciones, es fundamental entender cómo se cargan los ensamblados administrados y las dependencias no
administradas.

Descripción de AssemblyLoadContext
La API AssemblyLoadContext es fundamental para el diseño de carga de .NET Core. En el artículo Descripción de
AssemblyLoadContext se proporciona información general conceptual para el diseño.

Carga de detalles
Los detalles del algoritmo de carga se describen brevemente en varios artículos:
Algoritmo de carga de ensamblado administrado
Algoritmo de carga de ensamblado satélite
Algoritmo de carga de biblioteca no administrada (nativa)
Sondeo predeterminado

Creación de una aplicación de .NET Core con complementos


En el tutorial Creación de una aplicación .NET Core con complementos se describe cómo crear un elemento
AssemblyLoadContext personalizado. Usa un elemento AssemblyDependencyResolver para resolver las
dependencias del complemento. El tutorial aísla correctamente las dependencias del complemento de la aplicación
host.

Uso y depuración de la descargabilidad de ensamblado en .NET Core


El artículo Uso y depuración de la descargabilidad de ensamblado en .NET Core es un tutorial paso a paso. Muestra
cómo cargar una aplicación .NET Core, ejecutarla y luego descargarla. En el artículo también se proporcionan
sugerencias de depuración.
Descripción de
System.Runtime.Loader.AssemblyLoadContext
06/04/2020 • 10 minutes to read • Edit Online

La clase AssemblyLoadContext es única en .NET Core. En este artículo se intenta complementar la documentación
de la API AssemblyLoadContext con información conceptual.
Este artículo es relevante para los desarrolladores que implementan carga dinámica, en particular los
desarrolladores de marco de carga dinámica.

¿Qué es AssemblyLoadContext?
Cada aplicación de .NET Core usa implícitamente AssemblyLoadContext. Es el proveedor del runtime para buscar y
cargar las dependencias. Cada vez que se carga una dependencia, se invoca una instancia de AssemblyLoadContext
para buscarla.
Proporciona un servicio de búsqueda, carga y almacenamiento en caché de ensamblados administrados y
otras dependencias.
Para admitir la carga y descarga de código dinámico, crea un contexto aislado con el fin de cargar el código
y sus dependencias en su propia instancia de AssemblyLoadContext.

¿Cuándo necesita varias instancias de AssemblyLoadContext?


Una única instancia de AssemblyLoadContext se limita a cargar exactamente una versión de un elemento
Assembly por nombre de ensamblado simple, AssemblyName.Name.
Esta restricción puede convertirse en un problema cuando se cargan módulos de código de forma dinámica. Cada
módulo se compila de forma independiente y puede depender de distintas versiones de un elemento Assembly.
Este problema se produce normalmente cuando distintos módulos dependen de versiones diferentes de una
biblioteca usada habitualmente.
Para admitir la carga de código de forma dinámica, la API AssemblyLoadContext proporciona versiones en
conflicto de carga de un elemento Assembly en la misma aplicación. Cada instancia de AssemblyLoadContext
proporciona un diccionario único que asigna cada AssemblyName.Name a una instancia de Assembly concreta.
También proporciona un mecanismo práctico para agrupar las dependencias relacionadas con un módulo de
código para su descarga posterior.

¿Qué tiene de especial la instancia de AssemblyLoadContext.Default?


La instancia de AssemblyLoadContext.Default la rellena automáticamente el runtime en el inicio. Utiliza el sondeo
predeterminado para buscar y encontrar todas las dependencias estáticas.
Resuelve los escenarios de carga de dependencias más comunes.

¿Cómo admite AssemblyLoadContext las dependencias dinámicas?


AssemblyLoadContext tiene varios eventos y funciones virtuales que se pueden invalidar.
La instancia de AssemblyLoadContext.Default solo admite la invalidación de los eventos.
Los artículos Algoritmo de carga de ensamblado administrado, Algoritmo de carga de ensamblado satélite y
Algoritmo de carga de biblioteca no administrada (nativa) hacen referencia a todos los eventos y funciones
virtuales disponibles. En los artículos se muestra la posición relativa de cada evento y función en los algoritmos de
carga. En este artículo no se reproduce esa información.
En esta sección se tratan los principios generales de las funciones y eventos relevantes.
Ser reiterativo . Una consulta para una dependencia concreta siempre debe tener como resultado la misma
respuesta. Se debe devolver la misma instancia de dependencia cargada. Este requisito es fundamental para la
coherencia de la caché. En el caso concreto de los ensamblados administrados, creamos una caché de
Assembly. La clave de caché es un nombre de ensamblado sencillo, AssemblyName.Name.
Normalmente no se inician . Se espera que estas funciones devuelvan null , en lugar de iniciarse, cuando no
se pueda encontrar la dependencia solicitada. El inicio finalizará la búsqueda prematuramente y propagará una
excepción al autor de la llamada. El inicio se debe restringir a errores inesperados, como un ensamblado
dañado o una condición de memoria insuficiente.
Evitar la recursividad . Tenga en cuenta que estas funciones y controladores implementan las reglas de carga
para buscar dependencias. Su implementación no debe llamar a las API que desencadenan la recursividad.
Normalmente, el código debería llamar a las funciones de carga AssemblyLoadContext que requieran una
ruta de acceso concreta o un argumento de referencia de memoria.
Cargar en el elemento AssemblyLoadContext correcto . La elección de dónde cargar las dependencias es
específica de la aplicación. Estos eventos y funciones implementan la opción. Cuando el código llama a las
funciones de carga por ruta de acceso AssemblyLoadContext , les llama en la instancia en la que se quiere
cargar el código. En algún momento, devolver null y permitir que AssemblyLoadContext.Default controle la
carga puede ser la opción más sencilla.
Tenga en cuenta las carreras de subprocesos . La carga la pueden desencadenar varios subprocesos.
AssemblyLoadContext controla las carreras de subprocesos mediante la adición atómica de ensamblados a su
caché. La instancia del que pierde la carrera se descarta. En la lógica de implementación, no agregue lógica
adicional que no controle correctamente varios subprocesos.

¿Cómo se aíslan las dependencias dinámicas?


Cada instancia de AssemblyLoadContext representa un ámbito único para instancias de Assembly y definiciones de
Type.
No existe aislamiento binario entre estas dependencias. Solo están aisladas por no encontrarse entre sí por
nombre.
En cada AssemblyLoadContext:
AssemblyName.Name puede hacer referencia a una instancia de Assembly distinta.
Type.GetType puede devolver una instancia de tipo distinta para el mismo tipo name .

¿Cómo se comparten las dependencias?


Las dependencias se pueden compartir fácilmente entre instancias de AssemblyLoadContext. El modelo general es
que un elemento AssemblyLoadContext cargue una dependencia. El otro comparte la dependencia mediante el uso
de una referencia en el ensamblado cargado.
Este uso compartido es necesario para los ensamblados en runtime. Estos ensamblados solo se pueden cargar en
AssemblyLoadContext.Default. Lo mismo se requiere en el caso de marcos como ASP.NET , WPF o WinForms .
Se recomienda cargar las dependencias compartidas en AssemblyLoadContext.Default. Este uso compartido es el
modelo de diseño común.
El uso compartido se implementa en la codificación de la instancia de AssemblyLoadContext personalizada.
AssemblyLoadContext tiene varios eventos y funciones virtuales que se pueden invalidar. Cuando alguna de estas
funciones devuelve una referencia a una instancia de Assembly que se ha cargado en otra instancia de
AssemblyLoadContext, se comparte la instancia de Assembly. El algoritmo de carga estándar se aplaza a
AssemblyLoadContext.Default para cargar con el fin de simplificar el patrón común de uso compartido. Consulte
Algoritmo de carga de ensamblado administrado.

Complicaciones
Incidencias de conversión de tipos
Cuando dos instancias de AssemblyLoadContext contienen definiciones de tipo con el mismo name , no son del
mismo tipo. Son del mismo tipo si y solo si proceden de la misma instancia de Assembly.
Para complicar las cosas, los mensajes de excepción sobre estos tipos no coincidentes pueden ser confusos. Se
hace referencia a los tipos en los mensajes de excepción por sus nombres de tipo simple. En este caso, el mensaje
de excepción común tendría el formato siguiente:

El objeto de tipo "IsolatedType" no se puede convertir al tipo "IsolatedType".

Depuración de incidencias de conversión de tipos


Dado un par de tipos no coincidentes, es importante conocer también lo siguiente:
El elemento Type.Assembly de cada tipo.
El elemento AssemblyLoadContext de cada tipo., que se puede obtener a través de la función
AssemblyLoadContext.GetLoadContext(Assembly).
Dado dos objetos a y b , la evaluación en el depurador de lo siguiente resultará útil:

// In debugger look at each assembly's instance, Location, and FullName


a.GetType().Assembly
b.GetType().Assembly
// In debugger look at each AssemblyLoadContext's instance and name
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(a.GetType().Assembly)
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(b.GetType().Assembly)

Resolución de incidencias de conversión de tipos


Existen dos modelos de diseño para resolver estas incidencias de conversión de tipos.
1. Usar tipos comunes compartidos. Este tipo compartido puede ser un tipo en runtime primitivo o puede
implicar la creación de un nuevo tipo compartido en un ensamblado compartido. A menudo, el tipo
compartido es una interfaz definida en un ensamblado de aplicación. Vea también: ¿Cómo se comparten las
dependencias?.
2. Utilice técnicas de serialización para realizar la conversión de un tipo a otro.
Sondeo predeterminado
16/09/2020 • 5 minutes to read • Edit Online

La instancia de AssemblyLoadContext.Default se encarga de buscar las dependencias de un ensamblado. En este


artículo se describe la lógica de sondeo de la instancia de AssemblyLoadContext.Default.

Propiedades de sondeo configuradas por host


Cuando se inicia el runtime, el host de tiempo de ejecución proporciona un conjunto de propiedades de sondeo
con nombre que configuran las rutas de acceso de sondeo de AssemblyLoadContext.Default.
Cada propiedad de sondeo es opcional. Si está presente, cada propiedad es un valor de cadena que contiene una
lista delimitada de rutas de acceso absolutas. El delimitador es ";" en Windows y ":" en el resto de plataformas.

N O M B RE DE L A P RO P IEDA D DESC RIP C IÓ N

TRUSTED_PLATFORM_ASSEMBLIES Lista de rutas de acceso de archivos de ensamblado de


plataforma y aplicación.

PLATFORM_RESOURCE_ROOTS Lista de rutas de acceso de directorio para buscar


ensamblados de recursos satélite.

NATIVE_DLL_SEARCH_DIRECTORIES Lista de rutas de acceso de directorio para buscar bibliotecas


no administradas (nativas).

APP_PATHS Lista de rutas de acceso de directorio para buscar bibliotecas


administradas.

APP_NI_PATHS Lista de rutas de acceso de directorio para buscar imágenes


nativas de ensamblados administrados.

¿Cómo se rellenan las propiedades?


Existen dos escenarios principales para rellenar las propiedades, en función de si existe el archivo
<myapp>.deps.json.
Cuando el archivo *.deps.json está presente, se analiza para rellenar las propiedades de sondeo.
Cuando el archivo *.deps.json no está presente, se supone que el directorio de la aplicación contiene todas las
dependencias. El contenido del directorio se usa para rellenar las propiedades de sondeo.
Además, los archivos *.deps.json para cualquier marco al que se hace referencia se analizan de forma similar.
Por último, se puede usar la variable de entorno ADDITIONAL_DEPS para agregar dependencias adicionales.
dotnet.exe también contiene un parámetro --additional-deps opcional para establecer este valor en el inicio de
la aplicación.
Las propiedades APP_PATHS y APP_NI_PATHS no se rellenan de forma predeterminada y se omiten en la mayoría de
las aplicaciones.
Se puede tener acceso a la lista de todos los archivos *.deps.json usados por la aplicación a través de
System.AppContext.GetData("APP_CONTEXT_DEPS_FILES") .

Cómo ver las propiedades de sondeo desde un código administrado


Cada propiedad está disponible mediante una llamada a la función AppContext.GetData(String) con el nombre de
la propiedad de la tabla anterior.
Cómo depurar la construcción de las propiedades de sondeo
El host de tiempo de ejecución de .NET Core generará mensajes de seguimiento útiles cuando se habiliten
determinadas variables de entorno:

VA RIA B L E DE EN TO RN O DESC RIP C IÓ N

COREHOST_TRACE=1 Habilita el seguimiento.

COREHOST_TRACEFILE=<path> Realiza un seguimiento de una ruta de acceso de archivo en


lugar del elemento stderr predeterminado.

COREHOST_TRACE_VERBOSITY Establece el nivel de detalle de 1 (inferior) a 4 (superior).

Sondeo predeterminado de ensamblado administrado


Al realizar un sondeo para buscar un ensamblado administrado, AssemblyLoadContext.Default busca en orden en:
Archivos que coinciden con AssemblyName.Name en TRUSTED_PLATFORM_ASSEMBLIES (después de quitar
extensiones de archivo).
Archivos de ensamblado de imagen nativa en APP_NI_PATHS con extensiones de archivo comunes.
Archivos de ensamblado en APP_PATHS con extensiones de archivo comunes.

Sondeo de ensamblado satélite (recurso)


Con el fin de buscar un ensamblado satélite para una referencia cultural concreta, construya un conjunto de rutas
de acceso de archivos.
Para cada ruta de acceso de PLATFORM_RESOURCE_ROOTS y, después, APP_PATHS , anexe la cadena CultureInfo.Name,
un separador de directorios, la cadena AssemblyName.Name y la extensión ".dll".
Si existe algún archivo coincidente, intente cargarlo y devolverlo.

Sondeo de biblioteca no administrada (nativa)


Al realizar un sondeo para buscar una biblioteca no administrada, NATIVE_DLL_SEARCH_DIRECTORIES se busca
mediante una búsqueda de una biblioteca coincidente.
Algoritmo de carga de ensamblado administrado
19/03/2020 • 4 minutes to read • Edit Online

Los ensamblados administrados se ubican y se cargan con un algoritmo que implica varias fases.
Todos los ensamblados administrados, excepto los ensamblados satélite y los de WinRT , usan el mismo algoritmo.

¿Cuándo se cargan los ensamblados administrados?


El mecanismo más común para desencadenar la carga de un ensamblado administrado es una referencia estática
de ensamblado. El compilador inserta estas referencias siempre que el código usa un tipo definido en otro
ensamblado. Estos ensamblados los carga ( load-by-name ) el runtime, según sea necesario.
El uso directo de API específicas también desencadenará cargas:

API DESC RIP C IÓ N ACTIVE A SSEM B LY LO A DC O N T EXT

AssemblyLoadContext.LoadFromAssem Load-by-name La instancia de this.


blyName

AssemblyLoadContext.LoadFromAssem Cargue desde la ruta de acceso. La instancia de this.


blyPath
AssemblyLoadContext.LoadFromNa
tiveImagePath

AssemblyLoadContext.LoadFromStream Cargue desde el objeto. La instancia de this.

Assembly.LoadFile Cargue desde la ruta de acceso en una La nueva instancia de


nueva instancia de AssemblyLoadContext.
AssemblyLoadContext.

Assembly.LoadFrom Cargue desde la ruta de acceso en la Instancia de


instancia de AssemblyLoadContext.Default.
AssemblyLoadContext.Default.
Agregue un controlador Resolving
a AssemblyLoadContext.Default. El
controlador cargará las
dependencias del ensamblado
desde su directorio.

Assembly.Load(AssemblyName) Load-by-name . Se infiere del autor de la llamada.


Assembly.Load(String) Prefiere los métodos
AssemblyLoadContext.
Assembly.LoadWithPartialName

Assembly.Load(Byte[]) Cargue desde el objeto en una nueva La nueva instancia de


Assembly.Load(Byte[], Byte[]) instancia de AssemblyLoadContext. AssemblyLoadContext.
API DESC RIP C IÓ N ACTIVE A SSEM B LY LO A DC O N T EXT

Type.GetType(String) Load-by-name . Se infiere del autor de la llamada.


Type.GetType(String, Boolean) Prefiere los métodos Type.GetType
con un argumento
Type.GetType(String, Boolean, assemblyResolver .
Boolean)

Assembly.GetType Si el tipo name describe un tipo Se infiere del autor de la llamada.


genérico calificado con el ensamblado, Prefiere Type.GetType al utilizar
desencadene un elemento nombres de tipo calificados con el
Load-by-name . ensamblado.

Activator.CreateInstance(String, String) Load-by-name . Se infiere del autor de la llamada.


Activator.CreateInstance(String, Prefiere los métodos
String, Object[]) Activator.CreateInstance que toman
un argumento Type.
Activator.CreateInstance(String,
String, Boolean, BindingFlags,
Binder, Object[], CultureInfo,
Object[])

Algoritmo
El algoritmo siguiente describe cómo el runtime carga un ensamblado administrado.
1. Determine el elemento AssemblyLoadContext active .
En el caso de una referencia estática de ensamblado, el elemento AssemblyLoadContext active es la
instancia que ha cargado el ensamblado de referencia.
Las API preferidas hacen que el elemento AssemblyLoadContext active sea explícito.
Otras API infieren el elemento AssemblyLoadContext active . Para estas API, se usa la propiedad
AssemblyLoadContext.CurrentContextualReflectionContext. Si su valor es null , se usa la instancia de
AssemblyLoadContext inferida.
Consulte la tabla anterior.
2. En el caso de los métodos Load-by-name , el elemento AssemblyLoadContext activo carga el ensamblado. En
orden de prioridad por:
Comprobar su cache-by-name .
Llamar a la función de AssemblyLoadContext.Load.
Comprobar la memoria caché de las instancias de AssemblyLoadContext.Default y ejecutar la lógica
sondeo predeterminado de ensamblado administrado.
Generar el evento AssemblyLoadContext.Resolving para el AssemblyLoadContext activo.
Generar el evento AppDomain.AssemblyResolve.
3. En los demás tipos de cargas, el elemento AssemblyLoadContext active carga el ensamblado. En orden de
prioridad por:
Comprobar su cache-by-name .
Cargar desde la ruta de acceso especificada o el objeto de ensamblado sin formato.
4. En cualquier caso, si se ha cargado un ensamblado recientemente, entonces ocurre lo siguiente:
Se genera el evento AppDomain.AssemblyLoad.
Se agrega una referencia al elemento cache-by-name de la instancia de AssemblyLoadContext del
ensamblado.
5. Si se encuentra el ensamblado, se agrega una referencia según sea necesario al elemento cache-by-name
de la instancia de AssemblyLoadContext active .
Algoritmo de carga de ensamblado satélite
11/05/2020 • 5 minutes to read • Edit Online

Los ensamblados satélite se utilizan con el fin de almacenar los recursos localizados personalizados para el idioma
y la referencia cultural.
Los ensamblados satélite usan un algoritmo de carga distinto al de los ensamblados administrados generales.

¿Cuándo se cargan los ensamblados satélite?


Los ensamblados satélite se cargan al cargar un recurso localizado.
La API básica para cargar recursos localizados es la clase System.Resources.ResourceManager. En última instancia,
la clase ResourceManager llamará al método GetSatelliteAssembly para cada CultureInfo.Name.
Las API de nivel alto pueden abstraer la API de nivel bajo.

Algoritmo
El proceso de reserva de recursos de .NET Core conlleva los pasos siguientes:
1. Determine la instancia de AssemblyLoadContext active . En todos los casos, la instancia de active es el
elemento AssemblyLoadContext del ensamblado que se ejecuta.
2. La instancia de active intenta cargar un ensamblado satélite para la referencia cultural solicitada por
orden de prioridad al realizar lo siguiente:
Comprobar la memoria caché.
Comprobar en el directorio del ensamblado actualmente en ejecución un subdirectorio que coincida
con el elemento CultureInfo.Name solicitado (por ejemplo, es-MX ).

NOTE
Esta característica no se ha implementado en .NET Core antes de la versión 3.0.

NOTE
En Linux y macOS, el subdirectorio distingue entre mayúsculas y minúsculas y debe cumplir lo siguiente:
Coincidir exactamente con mayúsculas y minúsculas.
Estar en minúsculas.

Si active es la instancia de AssemblyLoadContext.Default, ejecutar la lógica sondeo del ensamblado


de satélite (recurso) predeterminado.
Llamar a la función de AssemblyLoadContext.Load.
Generar el evento AssemblyLoadContext.Resolving.
Generar el evento AppDomain.AssemblyResolve.
3. Si se carga un ensamblado satélite:
Se genera el evento AppDomain.AssemblyLoad.
Se busca en el ensamblado el recurso solicitado. Si el runtime encuentra el recurso en el ensamblado, lo
usará. Si no encuentra el recurso, seguirá con la búsqueda.

NOTE
Para buscar un recurso dentro del ensamblado satélite, el entorno de ejecución busca el archivo de recursos
solicitado por ResourceManager para el CultureInfo.Name actual. En el archivo de recursos, busca el nombre del
recurso solicitado. Si no se encuentra ninguno, se considera que no se encontró el recurso.

4. Luego, el runtime busca los ensamblados de referencias culturales primarias a través de muchos niveles
potenciales, y cada vez repite los pasos 2 y 3.
Cada referencia cultural tiene solo un elemento primario, que está definido mediante la propiedad
CultureInfo.Parent.
La búsqueda de las referencias culturales primarias se detiene cuando la propiedad Parent de una
referencia cultural es CultureInfo.InvariantCulture.
En el caso de InvariantCulture, no se vuelve a los pasos 2 y 3, sino que continúa con el paso 5.
5. Si todavía no se encuentra el recurso, se usa el recurso para la referencia cultural predeterminada (de
reserva).
Normalmente, los recursos de la referencia cultural predeterminada se incluyen en el ensamblado de la
aplicación principal. Sin embargo, se puede especificar UltimateResourceFallbackLocation.Satellite para la
propiedad NeutralResourcesLanguageAttribute.Location. Este valor indica que la ubicación de reserva final
para los recursos es un ensamblado satélite, en lugar del ensamblado principal.

NOTE
La referencia cultural predeterminada es la reserva final. Por lo tanto, se recomienda incluir siempre un conjunto de
recursos exhaustivo en el archivo de recursos predeterminado. Esto ayuda a evitar que se produzcan excepciones. Al
tener un conjunto exhaustivo, se proporciona una reserva para todos los recursos y se garantiza que al menos un
recurso esté siempre presente para el usuario, incluso si no es específico de la referencia cultural.

6. Por último,
si el tiempo de ejecución no encuentra un archivo de recursos para una referencia cultural
predeterminada (de reserva), se produce una excepción MissingManifestResourceException o
MissingSatelliteAssemblyException.
Si se encuentra el archivo de recursos pero el recurso solicitado no se encuentra, la solicitud devuelve
null .
Algoritmo de carga de biblioteca no administrada
(nativa)
19/03/2020 • 2 minutes to read • Edit Online

Las bibliotecas no administradas se ubican y se cargan con un algoritmo que implica varias fases.
El algoritmo siguiente describe cómo se cargan las bibliotecas nativas a través de PInvoke .

Algoritmo de la biblioteca de carga PInvoke


PInvoke usa el algoritmo siguiente al intentar cargar un ensamblado no administrado:
1. Determine el elemento AssemblyLoadContext active . En el caso de una biblioteca de carga no
administrada, el elemento AssemblyLoadContext de active es el que tiene el ensamblado que define
PInvoke .

2. En el caso del elemento AssemblyLoadContext active , intente buscar el ensamblado en orden de prioridad
por:
Comprobar la memoria caché.
Llamar al delegado System.Runtime.InteropServices.DllImportResolver actual que establece la
función NativeLibrary.SetDllImportResolver(Assembly, DllImportResolver).
Llamar a la función AssemblyLoadContext.LoadUnmanagedDll en el elemento AssemblyLoadContext
de active .
Comprobar la memoria caché de la instancia de AppDomain y ejecutar la lógica de sondeo de
biblioteca no administrada (nativa).
Generar el evento AssemblyLoadContext.ResolvingUnmanagedDll para el elemento
AssemblyLoadContext de active .
Creación de una aplicación de .NET Core con
complementos
20/05/2020 • 16 minutes to read • Edit Online

Este tutorial muestra cómo crear un contexto AssemblyLoadContext personalizado para cargar complementos. Se
usa un elemento AssemblyDependencyResolver para resolver las dependencias del complemento. El tutorial aísla
correctamente las dependencias del complemento de la aplicación host. Aprenderá a:
Estructurar un proyecto para admitir los complementos.
Crear un elemento AssemblyLoadContext personalizado para cargar cada complemento.
Usar el tipo System.Runtime.Loader.AssemblyDependencyResolver para permitir que los complementos tengan
dependencias.
Crear complementos que se puedan implementar fácilmente con solo copiar los artefactos de compilación.

Requisitos previos
Instale el SDK de .NET Core 3.0 o una versión más reciente.

Crear la aplicación
El primer paso es crear la aplicación:
1. Cree una carpeta nueva y, en ella, ejecute el siguiente comando:

dotnet new console -o AppWithPlugin

2. Para facilitar la compilación del proyecto, cree un archivo de solución de Visual Studio en la misma carpeta.
Ejecute el siguiente comando:

dotnet new sln

3. Ejecute el siguiente comando para agregar el proyecto de aplicación a la solución:

dotnet sln add AppWithPlugin/AppWithPlugin.csproj

Ahora ya se puede rellenar el esqueleto de la aplicación. Reemplace el código del archivo


AppWithPlugin/Program.cs con el código siguiente:
using PluginBase;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace AppWithPlugin
{
class Program
{
static void Main(string[] args)
{
try
{
if (args.Length == 1 && args[0] == "/d")
{
Console.WriteLine("Waiting for any key...");
Console.ReadLine();
}

// Load commands from plugins.

if (args.Length == 0)
{
Console.WriteLine("Commands: ");
// Output the loaded commands.
}
else
{
foreach (string commandName in args)
{
Console.WriteLine($"-- {commandName} --");

// Execute the command with the name passed as an argument.

Console.WriteLine();
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}

Creación de las interfaces de complemento


El paso siguiente en la creación de una aplicación con complementos consiste en definir la interfaz que los
complementos tienen que implementar. Se recomienda crear una biblioteca de clases que contenga los tipos que
se van a usar para la comunicación entre la aplicación y los complementos. Esta división permite publicar la
interfaz de complemento como un paquete sin tener que enviar la aplicación completa.
En la carpeta raíz del proyecto, ejecute . Además, ejecute
dotnet new classlib -o PluginBase
dotnet sln add PluginBase/PluginBase.csproj para agregar el proyecto al archivo de la solución. Elimine el archivo
PluginBase/Class1.cs y cree uno en la carpeta PluginBase con el nombre ICommand.cs con la definición de
interfaz siguiente:
namespace PluginBase
{
public interface ICommand
{
string Name { get; }
string Description { get; }

int Execute();
}
}

Esta interfaz ICommand es la que van a implementar todos los complementos.


Ahora que se ha definido la interfaz ICommand , el proyecto de aplicación se puede rellenar un poco más. Agregue
una referencia desde el proyecto AppWithPlugin al proyecto PluginBase con el comando
dotnet add AppWithPlugin\AppWithPlugin.csproj reference PluginBase\PluginBase.csproj desde la carpeta raíz.

Reemplace el comentario // Load commands from plugins con el fragmento de código siguiente para habilitarlo
para cargar complementos desde rutas de acceso de archivo determinadas:

string[] pluginPaths = new string[]


{
// Paths to plugins to load.
};

IEnumerable<ICommand> commands = pluginPaths.SelectMany(pluginPath =>


{
Assembly pluginAssembly = LoadPlugin(pluginPath);
return CreateCommands(pluginAssembly);
}).ToList();

Después, reemplace el comentario // Output the loaded commands por el fragmento de código siguiente:

foreach (ICommand command in commands)


{
Console.WriteLine($"{command.Name}\t - {command.Description}");
}

Reemplace el comentario // Execute the command with the name passed as an argument por el fragmento de código
siguiente:

ICommand command = commands.FirstOrDefault(c => c.Name == commandName);


if (command == null)
{
Console.WriteLine("No such command is known.");
return;
}

command.Execute();

Y, por último, agregue los métodos estáticos denominados LoadPlugin y CreateCommands a la clase Program ,
como se muestra aquí:
static Assembly LoadPlugin(string relativePath)
{
throw new NotImplementedException();
}

static IEnumerable<ICommand> CreateCommands(Assembly assembly)


{
int count = 0;

foreach (Type type in assembly.GetTypes())


{
if (typeof(ICommand).IsAssignableFrom(type))
{
ICommand result = Activator.CreateInstance(type) as ICommand;
if (result != null)
{
count++;
yield return result;
}
}
}

if (count == 0)
{
string availableTypes = string.Join(",", assembly.GetTypes().Select(t => t.FullName));
throw new ApplicationException(
$"Can't find any type which implements ICommand in {assembly} from {assembly.Location}.\n" +
$"Available types: {availableTypes}");
}
}

Carga de complementos
Ahora la aplicación puede cargar correctamente y crear instancias de los comandos a partir de los ensamblados de
complemento cargados, pero todavía no puede cargar los ensamblados de complemento. Cree un archivo
denominado PluginLoadContext.cs en la carpeta AppWithPlugin con el contenido siguiente:
using System;
using System.Reflection;
using System.Runtime.Loader;

namespace AppWithPlugin
{
class PluginLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;

public PluginLoadContext(string pluginPath)


{
_resolver = new AssemblyDependencyResolver(pluginPath);
}

protected override Assembly Load(AssemblyName assemblyName)


{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}

return null;
}

protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)


{
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}

return IntPtr.Zero;
}
}
}

El tipo PluginLoadContext se deriva de AssemblyLoadContext. El tipo AssemblyLoadContext es un tipo especial en el


runtime que permite a los desarrolladores aislar los ensamblados cargados en grupos diferentes para asegurarse
de que las versiones de ensamblado no entren en conflicto. Además, un elemento AssemblyLoadContext
personalizado puede elegir otras rutas de acceso desde las que cargar los ensamblados e invalidar el
comportamiento predeterminado. El elemento PluginLoadContext usa una instancia del tipo
AssemblyDependencyResolver que se introdujo en .NET Core 3.0 para resolver los nombres de ensamblado en rutas
de acceso. El objeto AssemblyDependencyResolver se construye con la ruta de acceso a una biblioteca de clases .NET.
Resuelve los ensamblados y las bibliotecas nativas en sus rutas de acceso relativas en función del archivo
deps.json para la biblioteca de clases cuya ruta de acceso se haya pasado al constructor
AssemblyDependencyResolver . El elemento AssemblyLoadContext personalizado permite que los complementos
tengan sus propias dependencias y el elemento AssemblyDependencyResolver facilita la tarea de cargar
correctamente las dependencias.
Ahora que el proyecto AppWithPlugin tiene el tipo PluginLoadContext , actualice el método Program.LoadPlugin
con el cuerpo siguiente:
static Assembly LoadPlugin(string relativePath)
{
// Navigate up to the solution root
string root = Path.GetFullPath(Path.Combine(
Path.GetDirectoryName(
Path.GetDirectoryName(
Path.GetDirectoryName(
Path.GetDirectoryName(
Path.GetDirectoryName(typeof(Program).Assembly.Location)))))));

string pluginLocation = Path.GetFullPath(Path.Combine(root, relativePath.Replace('\\',


Path.DirectorySeparatorChar)));
Console.WriteLine($"Loading commands from: {pluginLocation}");
PluginLoadContext loadContext = new PluginLoadContext(pluginLocation);
return loadContext.LoadFromAssemblyName(new
AssemblyName(Path.GetFileNameWithoutExtension(pluginLocation)));
}

Al usar una instancia de PluginLoadContext diferente para cada complemento, los complementos puede tener
dependencias diferentes o incluso en conflicto sin ningún problema.

Complemento simple sin dependencias


En la carpeta raíz, siga estos pasos:
1. Ejecute el siguiente comando para crear un nuevo proyecto de biblioteca de clases denominado
HelloPlugin :

dotnet new classlib -o HelloPlugin

2. Ejecute el siguiente comando para agregar el proyecto a la solución AppWithPlugin :

dotnet sln add HelloPlugin/HelloPlugin.csproj

3. Reemplace el archivo HelloPlugin/Class1.cs con un archivo denominado HelloCommand.cs con el contenido


siguiente:

using PluginBase;
using System;

namespace HelloPlugin
{
public class HelloCommand : ICommand
{
public string Name { get => "hello"; }
public string Description { get => "Displays hello message."; }

public int Execute()


{
Console.WriteLine("Hello !!!");
return 0;
}
}
}

Ahora, abra el archivo HelloPlugin.csproj. Debería tener un aspecto similar al siguiente:


<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>

</Project>

Entre las etiquetas <Project> , agregue los elementos siguientes:

<ItemGroup>
<ProjectReference Include="..\PluginBase\PluginBase.csproj">
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
</ItemGroup>

El elemento <Private>false</Private> es importante. Indica a MSBuild que no copie PluginBase.dll en el directorio


de salida para HelloPlugin. Si el ensamblado PluginBase.dll está presente en el directorio de salida,
PluginLoadContext encontrará el ensamblado y lo cargará cuando cargue el ensamblado HelloPlugin.dll. En este
momento, el tipo HelloPlugin.HelloCommand implementará la interfaz ICommand de PluginBase.dll en el directorio
de salida del proyecto HelloPlugin , no la interfaz ICommand que se carga en el contexto de carga predeterminado.
Como el runtime considera que estos dos tipos son tipos diferentes de ensamblados distintos, el método
AppWithPlugin.Program.CreateCommands no encontrará los comandos. Como resultado, los metadatos
<Private>false</Private> son necesarios para la referencia al ensamblado que contiene las interfaces de
complemento.
Del mismo modo, el elemento <ExcludeAssets>runtime</ExcludeAssets> también es importante si PluginBase hace
referencia a otros paquetes. Esta configuración tiene el mismo efecto que <Private>false</Private> pero funciona
en las referencias de paquete que pueden incluir el proyecto PluginBase o una de sus dependencias.
Ahora que se ha completado el proyecto HelloPlugin , se debe actualizar el proyecto AppWithPlugin para saber
dónde se puede encontrar el complemento HelloPlugin . Después del comentario // Paths to plugins to load ,
agregue @"HelloPlugin\bin\Debug\netcoreapp3.0\HelloPlugin.dll" como un elemento de la matriz pluginPaths .

Complemento con dependencias de biblioteca


Casi todos los complementos son más complejos que un simple ejemplo "Hola mundo", y muchos tienen
dependencias en otras bibliotecas. En los proyectos de complemento JsonPlugin y OldJson del ejemplo se
muestran dos ejemplos de complementos con dependencias de paquetes NuGet en Newtonsoft.Json . Los propios
archivos del proyecto no tienen información especial para las referencias del proyecto y, después de agregar las
rutas de acceso de complemento a la matriz pluginPaths , los complementos se ejecutan perfectamente, incluso
en la misma ejecución de la aplicación AppWithPlugin. Pero estos proyectos no copian los ensamblados a los que
se hace referencia en su directorio de salida, por lo que los ensamblados deben estar presentes en la máquina del
usuario para que los complementos funcionen. Hay dos maneras de solucionar este problema. La primera opción
consiste en usar el comando dotnet publish para publicar la biblioteca de clases. Como alternativa, si quiere
poder usar la salida de dotnet build para el complemento, puede agregar la propiedad
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> entre las etiquetas <PropertyGroup> del archivo
de proyecto del complemento. Vea el proyecto de complemento XcopyablePlugin para obtener un ejemplo.

Otros ejemplos en la muestra


Puede encontrar el código fuente completo para este tutorial en el repositorio dotnet/samples. El ejemplo
completo incluye algunos otros ejemplos de comportamiento AssemblyDependencyResolver . Por ejemplo, el objeto
AssemblyDependencyResolver también puede resolver las bibliotecas nativas, así como los ensamblados satélite
localizados en paquetes NuGet. UVPlugin y FrenchPlugin en el repositorio de ejemplos demuestran estos
escenarios.

Referencia a una interfaz de complemento desde un paquete NuGet


Supongamos que hay una aplicación A que tiene una interfaz de complemento definida en el paquete NuGet
denominado A.PluginBase . ¿Cómo se hace referencia correctamente al paquete en el proyecto de complemento?
Para las referencias de proyecto, el uso de los metadatos <Private>false</Private> en el elemento
ProjectReference del archivo de proyecto ha impedido que el archivo DLL se copiara en la salida.

Para hacer referencia correctamente al paquete A.PluginBase , le interesará cambiar el elemento


<PackageReference> del archivo de proyecto por lo siguiente:

<PackageReference Include="A.PluginBase" Version="1.0.0">


<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>

Esto evita que los ensamblados A.PluginBase se copien en el directorio de salida del complemento y asegura que
el complemento use la versión de A.PluginBase la aplicación A.

Recomendaciones para plataformas de destino de complemento


Como en la carga de dependencias de complemento se usa el archivo deps.json, hay un problema relacionado con
la plataforma de destino del complemento. En concreto, los complementos deben tener como destino un runtime
como .NET Core 3.0, en lugar de una versión de .NET Standard. El archivo .deps.json se genera en función de la
plataforma de destino del proyecto y, como muchos paquetes compatibles con .NET Standard incluyen
ensamblados de referencia para compilar en .NET Standard y ensamblados de implementación para runtimes
específicos, es posible que el archivo .deps.json no vea correctamente los ensamblados de implementación, o bien
que tome la versión de .NET Standard de un ensamblado en lugar de la de .NET Core que se espera.

Referencias del marco de trabajo de complementos


En la actualidad, los complementos no pueden introducir nuevos marcos en el proceso. Por ejemplo, no puede
cargar un complemento que use el marco Microsoft.AspNetCore.App en una aplicación en la que solo se use el
marco Microsoft.NETCore.App raíz. La aplicación host debe declarar referencias a todos los marcos de trabajo
necesarios para los complementos.
Uso y depuración de la descargabilidad de
ensamblado en .NET Core
16/09/2020 • 21 minutes to read • Edit Online

A partir de .NET Core 3.0, se admite la capacidad de cargar y posteriormente descargar un conjunto de
ensamblados. Para este fin, en .NET Framework se usaban dominios de aplicación personalizados, pero .NET Core
solo admite un dominio de aplicación predeterminado único.
.NET Core 3.0 y versiones posteriores admiten la descargabilidad a través de AssemblyLoadContext. Puede cargar
un conjunto de ensamblados en un AssemblyLoadContext recopilable, ejecutar métodos en ellos o simplemente
inspeccionarlos mediante reflexión y, por último, descargar el AssemblyLoadContext . Esa acción descarga los
ensamblados cargados en el AssemblyLoadContext .
Cabe destacar una diferencia entre la descarga mediante AssemblyLoadContext y mediante AppDomains. Con
AppDomains, se fuerza la descarga. En el momento de la descarga, se anulan todos los subprocesos que se
ejecutan en la instancia de AppDomain de destino, se destruyen los objetos COM administrados creados en la
AppDomain de destino, etc. Con AssemblyLoadContext , la descarga es "cooperativa". La llamada al método
AssemblyLoadContext.Unload simplemente inicia la descarga. La descarga finaliza una vez que:
Ningún subproceso tiene métodos de los ensamblados cargados en el AssemblyLoadContext en sus pilas de
llamadas.
Ninguno de los elementos siguientes hace referencia a los tipos de los ensamblados cargados en el
AssemblyLoadContext , a las instancias de esos tipos ni a los ensamblados mismos:
Referencias fuera del AssemblyLoadContext , excepto las referencias parciales (WeakReference o
WeakReference<T>).
Un recolector de elementos no utilizados (GC) potente controla (GCHandleType.Normal o
GCHandleType.Pinned) desde dentro y fuera de AssemblyLoadContext .

Uso de un AssemblyLoadContext recopilable


En esta sección se incluye un tutorial detallado paso a paso que muestra una manera sencilla de cargar una
aplicación de .NET Core en un AssemblyLoadContext recopilable, ejecutar su punto de entrada y, luego, descargarlo.
Puede encontrar un ejemplo completo en
https://github.com/dotnet/samples/tree/master/core/tutorials/Unloading.
Creación de un AssemblyLoadContext recopilable
Tendrá que derivar la clase de AssemblyLoadContext e invalidar su método AssemblyLoadContext.Load. Ese
método resuelve las referencias a todos los ensamblados que son dependencias de los ensamblados cargados en
ese AssemblyLoadContext .
El código siguiente es un ejemplo del AssemblyLoadContext personalizado más sencillo:
class TestAssemblyLoadContext : AssemblyLoadContext
{
public TestAssemblyLoadContext() : base(isCollectible: true)
{
}

protected override Assembly Load(AssemblyName name)


{
return null;
}
}

Como puede ver, el método Load devuelve null . Esto significa que todos los ensamblados de dependencias se
cargan en el contexto predeterminado y el contexto nuevo solo incluye los ensamblados que se cargaron
explícitamente en él.
Si quiere cargar algunas o todas las dependencias también en el AssemblyLoadContext , puede usar
AssemblyDependencyResolver en el método Load . AssemblyDependencyResolver resuelve los nombres de
ensamblado en rutas de acceso absoluto a archivos de ensamblado. El solucionador utiliza el archivo .deps.json y
los archivos de ensamblado en el directorio del ensamblado principal que se cargó en el contexto.

using System.Reflection;
using System.Runtime.Loader;

namespace complex
{
class TestAssemblyLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;

public TestAssemblyLoadContext(string mainAssemblyToLoadPath) : base(isCollectible: true)


{
_resolver = new AssemblyDependencyResolver(mainAssemblyToLoadPath);
}

protected override Assembly Load(AssemblyName name)


{
string assemblyPath = _resolver.ResolveAssemblyToPath(name);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}

return null;
}
}
}

Uso de un AssemblyLoadContext recopilable personalizado


En esta sección se presupone que se usa la versión más sencilla del TestAssemblyLoadContext .
Puede crear una instancia del AssemblyLoadContext personalizado y cargar en él un ensamblado como se indica a
continuación:

var alc = new TestAssemblyLoadContext();


Assembly a = alc.LoadFromAssemblyPath(assemblyPath);

Para cada uno de los ensamblados a los que hace referencia el ensamblado cargado, se llama al método
TestAssemblyLoadContext.Load para que el TestAssemblyLoadContext pueda decidir desde dónde obtener el
ensamblado. En nuestro caso, devuelve null para indicar que se debe cargar en el contexto predeterminado
desde las ubicaciones que el runtime usa para cargar los ensamblados de manera predeterminada.
Ahora que se cargó un ensamblado, puede ejecutar un método desde él. Ejecute el método Main :

var args = new object[1] {new string[] {"Hello"}};


int result = (int) a.EntryPoint.Invoke(null, args);

Una vez que el método Main devuelve algún resultado, puede iniciar la descarga ya sea llamando al método
Unload en el AssemblyLoadContext personalizado o deshaciéndose de la referencia que hace al
AssemblyLoadContext :

alc.Unload();

Esto es suficiente para descargar el ensamblado de prueba. Para que las referencias de ranura de pila (ya sean
variables locales reales o introducidas por JIT) no puedan mantener activos los objetos TestAssemblyLoadContext ,
Assembly y MethodInfo (el Assembly.EntryPoint ), pongamos todos los elementos en un método no insertable
independiente. Eso podría mantener activo al TestAssemblyLoadContext e impedir la descarga.
Además, devuelva una referencia parcial al AssemblyLoadContext para que pueda usarlo más adelante para
detectar la finalización de la descarga.

[MethodImpl(MethodImplOptions.NoInlining)]
static int ExecuteAndUnload(string assemblyPath, out WeakReference alcWeakRef)
{
var alc = new TestAssemblyLoadContext();
Assembly a = alc.LoadFromAssemblyPath(assemblyPath);

alcWeakRef = new WeakReference(alc, trackResurrection: true);

var args = new object[1] {new string[] {"Hello"}};


int result = (int) a.EntryPoint.Invoke(null, args);

alc.Unload();

return result;
}

Ahora puede ejecutar esta función para cargar, ejecutar y descargar el ensamblado.

WeakReference testAlcWeakRef;
int result = ExecuteAndUnload("absolute/path/to/your/assembly", out testAlcWeakRef);

Sin embargo, la descarga no se completa de inmediato. Como se mencionó anteriormente, se basa en el recolector
de elementos no utilizados para recopilar todos los objetos desde el ensamblado de prueba. En muchos casos, no
es necesario esperar a que se complete la descarga. Sin embargo, hay casos en los que resulta útil saber que la
descarga se completó. Por ejemplo, es posible que quiera eliminar el archivo de ensamblado que se cargó en el
AssemblyLoadContext personalizado del disco. En tal caso, se puede usar el fragmento de código siguiente.
Desencadena una recolección de elementos no utilizados y espera a los finalizadores pendientes en un bucle hasta
que la referencia parcial al AssemblyLoadContext personalizado se establece en null , lo que indica que se recopiló
el objeto de destino. En la mayoría de los casos, solo se requiere un paso a través del bucle. Sin embargo, en casos
más complejos en los que los objetos creados por el código que se ejecutan en el AssemblyLoadContext tienen
finalizadores, es posible que se necesiten más pasos.
for (int i = 0; testAlcWeakRef.IsAlive && (i < 10); i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}

El evento de descarga
En algunos casos, puede ser necesario que el código cargado en un AssemblyLoadContext personalizado realice
una limpieza cuando se inicie la descarga. Por ejemplo, es posible que tenga que detener subprocesos o limpiar los
identificadores de los recolectores de elementos no utilizados seguros, etc. En esos casos, se puede usar el evento
Unloading . Es posible enlazar a este evento un controlador que realice la limpieza necesaria.

Solución de problemas de descargabilidad


Debido a la naturaleza cooperativa de la descarga, resulta fácil olvidarse de que las referencias pueden mantener
activo el contenido de un AssemblyLoadContext recopilable, lo que impide la descarga. A continuación se muestra
un resumen de las entidades (algunas de las cuales no son obvias) que pueden contener las referencias:
Referencias habituales contenidas desde fuera del AssemblyLoadContext recopilable, que se almacenan en una
ranura de pila o en un registro de procesador (variables locales de método, ya sea creadas explícitamente por el
código de usuario o de manera implícita por el compilador Just-In-Time), una variable estática o un
identificador de GC seguro/de anclaje, y que apuntan de manera transitiva a:
Un ensamblado cargado en el AssemblyLoadContext recopilable.
Un tipo de dicho ensamblado.
Una instancia de un tipo de dicho ensamblado.
Subprocesos que ejecutan código de un ensamblado cargado en el AssemblyLoadContext recopilable.
Instancias de tipos de AssemblyLoadContext no recopilables personalizados que se crearon dentro del
AssemblyLoadContext recopilable.
Instancias de RegisteredWaitHandle pendientes con devoluciones de llamada establecidas en métodos en el
AssemblyLoadContext personalizado.

TIP
Se pueden producir referencias a objetos que se almacenan en ranuras de pilas o en los registros de procesador y que
podrían impedir la descarga de un AssemblyLoadContext en las siguientes situaciones:
Cuando los resultados de una llamada de función se pasan directamente a otra función incluso si no hay ninguna variable
local creada por el usuario.
Cuando el compilador JIT mantiene una referencia a un objeto que estaba disponible en algún momento de un método.

Depuración de problemas de descarga


Depurar los problemas con la descarga puede ser un proceso tedioso. Puede haber ocasiones en las que no sepa
qué es lo que mantiene activo un AssemblyLoadContext , pero se produce un error en la descarga. La mejor
herramienta para ayudarlo con eso es WinDbg (LLDB en Unix) con el complemento SOS. Debe buscar qué
mantiene activo a un LoaderAllocator perteneciente al AssemblyLoadContext específico. El complemento SOS
permite examinar los objetos del montón de recolección de elementos no utilizados, sus jerarquías y raíces.
Para cargar el complemento en el depurador, escriba el comando siguiente en la línea de comandos del depurador:
En WinDbg (parece que WinDbg lo hace de manera automática al irrumpir en la aplicación de .NET Core):
.loadby sos coreclr

En LLDB:

plugin load /path/to/libsosplugin.so

Vamos a depurar un programa de ejemplo que tiene problemas con la descarga. El código de origen se incluye a
continuación. Cuando lo ejecuta en WinDbg, el programa irrumpe en el depurador justo después de intentar
comprobar que la descarga se realizó correctamente. Luego, puede empezar a buscar a los culpables.

TIP
Si realiza la depuración con LLDB en Unix, los comandos de SOS de los ejemplos siguientes no tienen ! delante de ellos.

!dumpheap -type LoaderAllocator

Este comando vuelca todos los objetos con un nombre de tipo que contiene LoaderAllocator que se encuentran
en el montón de GC. A continuación se muestra un ejemplo:

Address MT Size
000002b78000ce40 00007ffadc93a288 48
000002b78000ceb0 00007ffadc93a218 24

Statistics:
MT Count TotalSize Class Name
00007ffadc93a218 1 24 System.Reflection.LoaderAllocatorScout
00007ffadc93a288 1 48 System.Reflection.LoaderAllocator
Total 2 objects

En la parte "Statistics:" (Estadísticas) que aparece abajo, compruebe el MT ( MethodTable ) que pertenece al
System.Reflection.LoaderAllocator , que es el objeto que nos interesa. Luego, en la lista que aparece al principio,
busque la entrada con el MT que coincide con ese y obtenga la dirección del objeto mismo. En nuestro caso, se
trata de "000002b78000ce40".
Ahora que sabemos la dirección del objeto LoaderAllocator , podemos usar otro comando para encontrar sus
raíces de GC:

!gcroot -all 0x000002b78000ce40

Este comando vuelca la cadena de referencias de objeto que llevan a la instancia LoaderAllocator . La lista empieza
por la raíz, que es la entidad que mantiene activo el LoaderAllocator y, por lo tanto, es la parte fundamental del
problema. La raíz puede ser una ranura de pila, un registro de procesador, un identificador de GC o una variable
estática.
Este es un ejemplo de la salida del comando gcroot :
Thread 4ac:
000000cf9499dd20 00007ffa7d0236bc example.Program.Main(System.String[])
[E:\unloadability\example\Program.cs @ 70]
rbp-20: 000000cf9499dd90
-> 000002b78000d328 System.Reflection.RuntimeMethodInfo
-> 000002b78000d1f8 System.RuntimeType+RuntimeTypeCache
-> 000002b78000d1d0 System.RuntimeType
-> 000002b78000ce40 System.Reflection.LoaderAllocator

HandleTable:
000002b7f8a81198 (strong handle)
-> 000002b78000d948 test.Test
-> 000002b78000ce40 System.Reflection.LoaderAllocator

000002b7f8a815f8 (pinned handle)


-> 000002b790001038 System.Object[]
-> 000002b78000d390 example.TestInfo
-> 000002b78000d328 System.Reflection.RuntimeMethodInfo
-> 000002b78000d1f8 System.RuntimeType+RuntimeTypeCache
-> 000002b78000d1d0 System.RuntimeType
-> 000002b78000ce40 System.Reflection.LoaderAllocator

Found 3 roots.

El paso siguiente es averiguar dónde se encuentra la raíz para poder corregirla. El caso más sencillo es cuando la
raíz es una ranura de pila o un registro de procesador. En ese caso, gcroot muestra el nombre de la función cuyo
marco contiene la raíz y el subproceso que ejecuta esa función. El caso difícil es cuando la raíz es una variable
estática o un identificador de GC.
En el ejemplo anterior, la primera raíz es una variable local de tipo System.Reflection.RuntimeMethodInfo
almacenada en el marco de la función example.Program.Main(System.String[]) en la dirección rbp-20 ( rbp es el
registro de procesador rbp y -20 es un desplazamiento hexadecimal a partir de ese registro).
La segunda raíz es un GCHandle normal (seguro) que contiene una referencia a una instancia de la clase
test.Test .

La tercera raíz es un GCHandle anclado. Esta es en realidad una variable estática, pero lamentablemente, no es
posible saberlo a ciencia cierta. Las variables estáticas para tipos de referencia se almacenan en una matriz de
objetos administrada en estructuras internas de runtime.
Otro caso que puede impedir la descarga de un AssemblyLoadContext es cuando un subproceso tiene un marco de
un método proveniente de un ensamblado cargado en el AssemblyLoadContext en su pila. Puede comprobarlo si
vuelca las pilas de llamadas administradas de todos los subprocesos:

~*e !clrstack

El comando siguiente "aplicar a todos los subprocesos el comando !clrstack ". Luego se muestra la salida de ese
comando para el ejemplo. Lamentablemente, LLDB en Unix no tiene forma de aplicar un comando a todos los
subprocesos, por lo que tendrá que cambiar de manera manual los subprocesos y repetir el comando clrstack .
Omita todos los subprocesos en los que el depurador indique "Unable to walk the managed stack" (No se puede
recorrer la pila administrada).
OS Thread Id: 0x6ba8 (0)
Child SP IP Call Site
0000001fc697d5c8 00007ffb50d9de12 [HelperMethodFrame: 0000001fc697d5c8]
System.Diagnostics.Debugger.BreakInternal()
0000001fc697d6d0 00007ffa864765fa System.Diagnostics.Debugger.Break()
0000001fc697d700 00007ffa864736bc example.Program.Main(System.String[]) [E:\unloadability\example\Program.cs @
70]
0000001fc697d998 00007ffae5fdc1e3 [GCFrame: 0000001fc697d998]
0000001fc697df28 00007ffae5fdc1e3 [GCFrame: 0000001fc697df28]
OS Thread Id: 0x2ae4 (1)
Unable to walk the managed stack. The current thread is likely not a
managed thread. You can run !threads to get a list of managed threads in
the process
Failed to start stack walk: 80070057
OS Thread Id: 0x61a4 (2)
Unable to walk the managed stack. The current thread is likely not a
managed thread. You can run !threads to get a list of managed threads in
the process
Failed to start stack walk: 80070057
OS Thread Id: 0x7fdc (3)
Unable to walk the managed stack. The current thread is likely not a
managed thread. You can run !threads to get a list of managed threads in
the process
Failed to start stack walk: 80070057
OS Thread Id: 0x5390 (4)
Unable to walk the managed stack. The current thread is likely not a
managed thread. You can run !threads to get a list of managed threads in
the process
Failed to start stack walk: 80070057
OS Thread Id: 0x5ec8 (5)
Child SP IP Call Site
0000001fc70ff6e0 00007ffb5437f6e4 [DebuggerU2MCatchHandlerFrame: 0000001fc70ff6e0]
OS Thread Id: 0x4624 (6)
Child SP IP Call Site
GetFrameContext failed: 1
0000000000000000 0000000000000000
OS Thread Id: 0x60bc (7)
Child SP IP Call Site
0000001fc727f158 00007ffb5437fce4 [HelperMethodFrame: 0000001fc727f158]
System.Threading.Thread.SleepInternal(Int32)
0000001fc727f260 00007ffb37ea7c2b System.Threading.Thread.Sleep(Int32)
0000001fc727f290 00007ffa865005b3 test.Program.ThreadProc() [E:\unloadability\test\Program.cs @ 17]
0000001fc727f2c0 00007ffb37ea6a5b System.Threading.Thread.ThreadMain_ThreadStart()
0000001fc727f2f0 00007ffadbc4cbe3
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext,
System.Threading.ContextCallback, System.Object)
0000001fc727f568 00007ffae5fdc1e3 [GCFrame: 0000001fc727f568]
0000001fc727f7f0 00007ffae5fdc1e3 [DebuggerU2MCatchHandlerFrame: 0000001fc727f7f0]

Como puede ver, el último subproceso tiene test.Program.ThreadProc() . Esta es una función del ensamblado
cargado en el AssemblyLoadContext , por lo que mantiene activo al AssemblyLoadContext .

Origen de ejemplo con problemas de descargabilidad


El código siguiente se usa en el ejemplo de depuración anterior.
Programa de prueba principal

using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;

namespace example
namespace example
{
class TestAssemblyLoadContext : AssemblyLoadContext
{
public TestAssemblyLoadContext() : base(true)
{
}
protected override Assembly Load(AssemblyName name)
{
return null;
}
}

class TestInfo
{
public TestInfo(MethodInfo mi)
{
entryPoint = mi;
}
MethodInfo entryPoint;
}

class Program
{
static TestInfo entryPoint;

[MethodImpl(MethodImplOptions.NoInlining)]
static int ExecuteAndUnload(string assemblyPath, out WeakReference testAlcWeakRef, out MethodInfo
testEntryPoint)
{
var alc = new TestAssemblyLoadContext();
testAlcWeakRef = new WeakReference(alc);

Assembly a = alc.LoadFromAssemblyPath(assemblyPath);
if (a == null)
{
testEntryPoint = null;
Console.WriteLine("Loading the test assembly failed");
return -1;
}

var args = new object[1] {new string[] {"Hello"}};

// Issue preventing unloading #1 - we keep MethodInfo of a method for an assembly loaded into the
TestAssemblyLoadContext in a static variable
entryPoint = new TestInfo(a.EntryPoint);
testEntryPoint = a.EntryPoint;

int result = (int)a.EntryPoint.Invoke(null, args);


alc.Unload();

return result;
}

static void Main(string[] args)


{
WeakReference testAlcWeakRef;
// Issue preventing unloading #2 - we keep MethodInfo of a method for an assembly loaded into the
TestAssemblyLoadContext in a local variable
MethodInfo testEntryPoint;
int result = ExecuteAndUnload(@"absolute/path/to/test.dll", out testAlcWeakRef, out
testEntryPoint);

for (int i = 0; testAlcWeakRef.IsAlive && (i < 10); i++)


{
GC.Collect();
GC.WaitForPendingFinalizers();
}

System.Diagnostics.Debugger.Break();
System.Diagnostics.Debugger.Break();

Console.WriteLine($"Test completed, result={result}, entryPoint: {testEntryPoint} unload success:


{!testAlcWeakRef.IsAlive}");
}
}
}

Programa cargado en TestAssemblyLoadContext


El código siguiente presenta el test.dll que se pasa al método ExecuteAndUnload en el programa de prueba
principal.

using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace test
{
class Test
{
string message = "Hello";
}

class Program
{
public static void ThreadProc()
{
// Issue preventing unloading #4 - a thread running method inside of the TestAssemblyLoadContext
at the unload time
Thread.Sleep(Timeout.Infinite);
}

static GCHandle handle;


static int Main(string[] args)
{
// Issue preventing unloading #3 - normal GC handle
handle = GCHandle.Alloc(new Test());
Thread t = new Thread(new ThreadStart(ThreadProc));
t.IsBackground = true;
t.Start();
Console.WriteLine($"Hello from the test: args[0] = {args[0]}");

return 1;
}
}
}
Introducción a la creación de versiones de .NET Core
16/09/2020 • 10 minutes to read • Edit Online

.NET Core se refiere al runtime y el SDK de .NET Core, que contiene las herramientas que necesita para desarrollar
aplicaciones. Los SDK de .NET Core están diseñados para funcionar con cualquier versión anteriores del runtime
de .NET Core. En este artículo se explican el runtime y la estrategia de la versión del SDK. En el artículo .NET
Standard se ofrece una explicación de los números de versión de .NET Standard.
El runtime y el SDK de .NET Core agregan características a un ritmo diferente; en general, el SDK de .NET Core
incluye herramientas actualizadas antes de que el runtime de .NET Core cambie el runtime que se usa en
producción.

Detalles de control de versiones


".NET Core 2.1" se refiere al número de versión del runtime de .NET Core. El runtime de .NET Core sigue el
esquema de control de versiones "principal/secundaria/revisón", que se basa en el control de versiones
semántico.
El SDK de .NET Core no sigue el esquema de control de versiones semántico. El SDK de .NET Core se publica con
mayor rapidez, y sus versiones deben comunicar tanto el runtime alineado como la versión secundaria y las
publicaciones de revisiones del propio SDK. Las dos primeras posiciones de la versión del SDK de .NET Core
siempre reflejan el runtime de .NET Core con el que se publicó. Cada versión del SDK puede crear aplicaciones
tanto para este runtime como para cualquier versión anterior.
La tercera posición del número de versión del SDK comunica tanto la versión secundaria como el número de
revisión. La versión secundaria se multiplica por 100. La versión secundaria 1 y la revisión 2 se representan con
102. Los dos dígitos finales representan el número de revisión. Por ejemplo, la versión de .NET Core 2.2 puede
crear versiones como en la tabla siguiente:

C A M B IO RUN T IM E DE . N ET C O RE SDK DE . N ET C O RE ( *)

Versión inicial 2.2.0 2.2.100

Revisión del SDK 2.2.0 2.2.101

Runtime y revisión del SDK 2.2.1 2.2.102

Cambio de características del SDK 2.2.1 2.2.200

(*) En este gráfico se usa el entorno de ejecución 2.2 de .NET Core como ejemplo, ya que un artefacto histórico
indica que el primer SDK de .NET Core 2.1 es el de la versión 2.1.300. Para obtener más información, consulte
.Selección de la versión de .NET Core.
NOTAS:
Si el SDK tiene 10 actualizaciones de características anteriores a la actualización de una de las características
del runtime, los números de versión progresarán en la serie de 1000 con números como 2.2.1000, tal como la
publicación de características posterior a la versión 2.2.900. No se espera que esta situación llegue a
producirse.
Nunca se publicarán 99 versiones sin publicar ninguna característica. Si una versión se acercase a este
número, se forzaría una publicación de características.
Puede ver más detalles en la propuesta inicial, que forma parte del repositorio dotnet/designs.

Control de versiones semántico


En cierto modo, el runtime de .NET Core sigue el Versionamiento Semántico (SemVer), que adopta el uso del
esquema de control de versiones MAJOR.MINOR.PATCH y emplea las distintas partes del número de versión para
describir el grado y el tipo de cambio.

MAJOR.MINOR.PATCH[-PRERELEASE-BUILDNUMBER]

Los elementos opcionales PRERELEASE y BUILDNUMBER nunca forman parte de las versiones compatibles y solo
están presentes en las compilaciones nocturnas, en las compilaciones locales a partir de destinos de origen y en
las versiones preliminares no compatibles.
Comprender los cambios en el número de versión del runtime
MAJOR se incrementa cuando:

Se producen cambios importantes en el producto, o bien este toma una nueva dirección.
Se han realizado cambios importantes. Hay un nivel de aceptación de cambios importantes elevado.
Ya no se admite una versión antigua.
Se adopta una versión MAJOR más reciente de una dependencia existente.

MINOR se incrementa cuando:


Se agrega un área expuesta de API pública.
Se agrega un nuevo comportamiento.
Se adopta una versión MINOR más reciente de una dependencia existente.
Se presenta una nueva dependencia.
PATCH se incrementa cuando:
Se realizan correcciones de errores.
Se agrega compatibilidad con una plataforma más reciente.
Se adopta una versión PATCH más reciente de una dependencia existente.
Cualquier otro cambio no se ajusta a uno de los casos anteriores.
Cuando hay varios cambios, se incrementa el elemento superior afectado por cambios individuales, mientras que
los siguientes se restablecen a cero. Por ejemplo, cuando se incrementa MAJOR , MINOR y PATCH se restablecen a
cero. Cuando se incrementa MINOR , PATCH se restablece a cero mientras que MAJOR no se modifica.

Números de versión en los nombres de archivo


Los archivos descargados para .NET Core indican la versión; por ejemplo, dotnet-sdk-2.1.300-win10-x64.exe .
Versiones preliminares
Las versiones preliminares tienen un elemento -preview[number]-([build]|"final") anexado a la versión. Por
ejemplo: 2.0.0-preview1-final .
Versiones de mantenimiento
Cuando se lanza una versión, las ramas de la versión generalmente dejan de producir compilaciones diarias y, en
su lugar, empiezan a generar compilaciones de mantenimiento. Las versiones de mantenimiento tienen un
elemento -servicing-[number] anexado a la versión. Por ejemplo: 2.0.1-servicing-006924 .
Relación con versiones de .NET Standard
.NET Standard consta de un ensamblado de referencia de .NET. Hay varias implementaciones específicas de cada
plataforma. El ensamblado de referencia contiene la definición de las API de .NET que forman parte de una
versión concreta de .NET Standard. Cada implementación cumple el contrato de .NET Standard en una plataforma
en cuestión. Puede obtener más información sobre .NET Standard en el artículo .NET Standard de la guía de .NET.
El ensamblado de referencia de .NET Standard usa el esquema de control de versiones MAJOR.MINOR . El nivel
PATCH no es útil para .NET Standard porque solo expone una especificación de API en lugar de una
implementación, de modo que , por definición, cualquier cambio en la API representaría un cambio en el conjunto
de características (es decir, una versión de MINOR nueva).
Las implementaciones de cada plataforma pueden actualizarse, normalmente como parte de la versión de la
plataforma. Por tanto, es posible que no sean evidentes para los programadores que usen .NET Standard en la
plataforma en cuestión.
Cada versión de .NET Core implementa una versión de .NET Standard. Implementar una versión de .NET Standard
implica la compatibilidad con versiones anteriores de .NET Standard. Versión de .NET standard y .NET Core por
separado. Es una coincidencia que .NET Core 2.0 implemente .NET Standard 2.0. .NET Core 2.1 también
implementa .NET Standard 2.0. .NET Core será compatible con versiones futuras de .NET Standard en cuanto
estén disponibles.

. N ET C O RE . N ET STA N DA RD

1.0 hasta la versión 1.6

2.0 hasta la versión 2.0

2.1 hasta la versión 2.0

2.2 hasta la versión 2.0

3.0 hasta la versión 2.1

3.1 hasta la versión 2.1

Para obtener una tabla interactiva de las versiones de .NET Standard y ver cómo se corresponden con las
implementaciones de .NET, vea Versiones de .NET Standard.

Vea también
Marcos de trabajo de destino
Empaquetado de distribución de .NET Core
Hoja de información sobre el ciclo de vida de compatibilidad de .NET Core
.NET Core 2+ Version Binding (Enlace de versión de .NET Core 2+)
Imágenes de Docker para .NET Core
Selección de la versión de .NET Core que se va a
usar
16/09/2020 • 12 minutes to read • Edit Online

En este artículo se explican las directivas que usan las herramientas de .NET Core, el SDK y el runtime para la
selección de versiones. Estas directivas proporcionan un equilibrio entre la ejecución de aplicaciones con las
versiones especificadas y facilitan la actualización de los equipos del desarrollador y el usuario final. Estas
directivas realizan las siguientes acciones:
La implementación sencilla y eficaz de .NET Core, incluidas las actualizaciones de seguridad y confiabilidad.
Usar las herramientas y los comandos más recientes independientemente del runtime de destino.
La selección de versiones se produce:
Al ejecutar un comando del SDK, el SDK usa la versión instalada más reciente.
Al compilar un ensamblado, los monikers de la plataforma de destino definen las API de tiempo de
compilación.
Al ejecutar una aplicación .NET Core, las aplicaciones dependientes de la plataforma de destino se ponen al
día.
Al publicar una aplicación autocontenida, las implementaciones autocontenidas incluyen el runtime
seleccionado.
En el resto de este documento se examinan los cuatro escenarios.

El SDK usa la versión instalada más reciente


Los comandos de SDK incluyen dotnet new y dotnet run . La CLI de .NET Core debe elegir una versión del SDK
para cada comando dotnet . Usa el SDK más reciente instalado en el equipo de forma predeterminada, aunque:
El proyecto tenga como destino una versión anterior del entorno de ejecución de .NET Core.
La versión mas reciente del SDK de .NET Core sea una versión preliminar.
Puede beneficiarse de las características y mejoras del SDK más reciente mientras selecciona como destino
versiones anteriores del runtime de .NET Core. Puede tener como destino varias versiones del runtime de .NET
Core en otros proyectos, con las mismas herramientas del SDK para todos los proyectos.
En raras ocasiones, es posible que tenga que usar una versión anterior del SDK. Esa versión se especifica en un
archivo global.json. La directiva "usar la versión más reciente" significa que solo se usa global.json para
especificar una versión del SDK de .NET Core anterior a la versión instalada más reciente.
global.json se puede colocar en cualquier lugar de la jerarquía de archivos. La CLI busca el primer archivo
global.json hacia arriba desde el directorio del proyecto. Puede controlar a qué proyectos se aplica un archivo
global.json determinado mediante su lugar en el sistema de archivos. La CLI de .NET busca un archivo
global.json de forma iterativa desplazándose hacia arriba en la ruta de acceso desde el directorio de trabajo
actual. El primer archivo global.json que se encuentra especifica la versión que se usa. Si esa versión del SDK
está instalada, es la que se usa. Si no se encuentra el SDK especificado en global.json, la CLI de .NET usa reglas
de coincidencia para seleccionar un SDK compatible, o bien se produce un error si no se encuentra ninguno.
En el ejemplo siguiente se muestra la sintaxis de global.json:
{
"sdk": {
"version": "3.0.0"
}
}

El proceso de selección de una versión del SDK es la siguiente:


1. dotnet busca un archivo global.json de forma iterativa desplazándose hacia arriba de forma inversa en la
ruta de acceso desde el directorio de trabajo actual.
2. dotnet usa el SDK especificado en el primer archivo global.json que encuentra.
3. dotnet usa el SDK instalado más reciente si no se encuentra ningún archivo global.json.
Puede obtener más información sobre cómo seleccionar una versión del SDK en la sección Reglas de
coincidencia del artículo sobre global.json.

Los monikers de la plataforma de destino definen las API de tiempo


de compilación
El proyecto se compila con las API definidas en un moniker de la plataforma de destino (TFM). La
plataforma de destino se especifica en el archivo del proyecto. Establezca el elemento TargetFramework del
archivo del proyecto como se muestra en el ejemplo siguiente:

<TargetFramework>netcoreapp3.0</TargetFramework>

Puede compilar el proyecto con varios TFM. Configurar varias plataformas de destino es más común para las
bibliotecas, pero también se puede hacer con las aplicaciones. Especifique una propiedad TargetFrameworks (el
plural de TargetFramework ). Las plataformas de destino se delimitan por punto y coma, como se muestra en el
ejemplo siguiente:

<TargetFrameworks>netcoreapp3.0;net47</TargetFrameworks>

Un SDK determinado admite un conjunto fijo de plataformas, limitado a la plataforma de destino del runtime
con el que se suministra. Por ejemplo, el SDK de .NET Core 3.0 incluye el runtime .NET Core 3.0, que es una
implementación de la plataforma de destino netcoreapp3.0 . El SDK de .NET Core 3.0 admite netcoreapp2.1 ,
netcoreapp2.2 y netcoreapp3.0 , pero no netcoreapp3.1 (o una versión posterior). El SDK de .NET Core 3.1 se
instala para compilar para netcoreapp3.1 .
Las plataformas de destino de .NET Standard también se limitan a la plataforma de destino del runtime con el
que se incluye el SDK. El SDK de .NET Core 3.1 está limitado a netstandard2.1 . Para más información, consulte
.NET Standard.

Puesta al día de las aplicaciones dependientes de la plataforma


Cuando una aplicación se ejecuta desde el origen con dotnet run , desde una implementación dependiente
del marco con dotnet myapp.dll o desde un ejecutable dependiente del marco con myapp.exe , el
ejecutable dotnet es el host de la aplicación.
El host elige la versión de revisión más reciente instalada en el equipo. Por ejemplo, si se especifica
netcoreapp3.0 en el archivo de proyecto, y 3.0.2 es el runtime de .NET instalado más reciente, se usa el
runtime 3.0.2 .
Si no se encuentra ninguna versión de 3.0.* aceptable, se usa una versión de 3.* nueva. Por ejemplo, si se
especificó netcoreapp3.0 y solo está instalado 3.1.0 , la aplicación se ejecuta con el runtime 3.1.0 . Este
comportamiento se conoce como "puesta al día de versión secundaria". Las versiones inferiores no se
considerarán. Cuando no hay ningún runtime aceptable instalado, la aplicación no se ejecutará.
Algunos ejemplos de uso muestran el comportamiento; si indica como destino la versión 3.0:
✔ Se especifica la versión 3.0. 3.0.3 es la versión de revisión instalada más reciente. Se usa la versión 3.0.3.

❌ Se especifica la versión 3.0. No hay ninguna versión 3.0.* instalada. 2.1.1 es la versión del runtime
instalada más alta. Se muestra un mensaje de error.
️ Se especifica la versión 3.0. No hay ninguna versión 3.0.* instalada. 3.1.0 es la versión del runtime

instalada más alta. Se usa la versión 3.1.0.
❌ Se especifica la versión 2.0. No hay ninguna versión 2.x instalada. 3.0.0 es la versión del runtime instalada
más reciente. Se muestra un mensaje de error.
La puesta al día de versión secundaria tiene un efecto secundario que puede afectar a los usuarios finales.
Considere el caso siguiente:
1. La aplicación especifica que se requiere la versión 3.0.
2. Cuando se ejecuta, la versión 3.0.* no está instalada, pero sí que lo está la 3.1.0. Se usará la versión 3.1.0.
3. Más adelante, el usuario instala la versión 3.0.3 y ejecuta de nuevo la aplicación. Ahora se usará la
versión 3.0.3.
Es posible que las versiones 3.0.3 y 3.1.0 se comporten de forma diferente, especialmente en escenarios como la
serialización de datos binarios.

Las implementaciones autocontenidas incluyen el runtime


seleccionado.
Puede publicar una aplicación como una distribución autocontenida . Este enfoque empaqueta el runtime y
las bibliotecas de .NET Core con la aplicación. Las implementaciones autocontenidas no tienen una dependencia
de los entornos de runtime. La selección de la versión del runtime se produce en el momento de la publicación,
no en el tiempo de ejecución.
El proceso de publicación selecciona la versión de revisión más reciente de la familia de runtime indicada. Por
ejemplo, dotnet publish seleccionará .NET Core 3.0.3 si es la versión de revisión más reciente de la familia de
runtime de .NET Core 3.0. La plataforma de destino (incluidas las revisiones de seguridad instaladas más
recientes) se empaqueta con la aplicación.
Es un error que no se cumpla la versión mínima especificada para una aplicación. dotnet publish se enlaza a la
versión de revisión de runtime más reciente (dentro de una familia de versión principal.secundaria
determinada). dotnet publish no es compatible con la semántica de puesta al día de dotnet run . Para obtener
más información sobre las revisiones y las implementaciones autocontenidas, vea el artículo sobre selección de
revisión de runtime en la implementación de aplicaciones .NET Core.
Las implementaciones autocontenidas pueden requerir una versión de revisión específica. Puede invalidar la
versión de revisión de runtime mínima (para versiones superiores o inferiores) en el archivo del proyecto, como
se muestra en el ejemplo siguiente:

<RuntimeFrameworkVersion>3.0.3</RuntimeFrameworkVersion>

El elemento RuntimeFrameworkVersion invalida la directiva de versión predeterminada. Para las


implementaciones autocontenidas, RuntimeFrameworkVersion especifica la versión de la plataforma de runtime
exacta. Para las aplicaciones dependientes de la plataforma, RuntimeFrameworkVersion especifica la versión de la
plataforma de runtime mínima que se requiere.

Vea también
Descarga e instalación de .NET Core
Eliminación de los componentes .NET Core Runtime y SDK
Opciones de configuración en tiempo de ejecución
de .NET Core
16/09/2020 • 6 minutes to read • Edit Online

.NET Core admite el uso de archivos de configuración y variables de entorno para configurar el comportamiento
de las aplicaciones .NET Core en tiempo de ejecución. La configuración en tiempo de ejecución es una opción
atractiva si:
No se posee ni controla el código fuente de una aplicación y, por tanto, no puede configurarlo mediante
programación.
Varias instancias de la aplicación se ejecutan al mismo tiempo en un solo sistema y se quiere configurar
cada una para un rendimiento óptimo.

NOTE
Esta documentación está en desarrollo. Si observa que la información que se presenta aquí está incompleta o es inexacta,
abra una incidencia para informarnos sobre ella o envíe una solicitud de incorporación de cambios para solucionarla. Para
obtener información sobre el envío de solicitudes de incorporación de cambios para el repositorio dotnet/docs, consulte la
guía del colaborador.

.NET Core ofrece los siguientes mecanismos para configurar el comportamiento de las aplicaciones en tiempo de
ejecución:
El archivo runtimeconfig.json
Propiedades de MSBuild
Variables de entorno

TIP
El hecho de configurar una opción relativa al tiempo de ejecución mediante el uso de una variable de entorno aplica la
configuración a todas las aplicaciones de .NET Core. Sin embargo, si se configura una opción relativa al tiempo de ejecución
en runtimeconfig.json o en el archivo del proyecto, la configuración solo afectará a la aplicación en cuestión.

Algunos valores de configuración también se pueden establecer mediante programación llamando al


método AppContext.SetSwitch.
Los artículos de esta sección de la documentación están organizados por categoría, como, por ejemplo,
depuración y recolección de elementos no utilizados. En su caso, se muestran las opciones de configuración para
archivos runtimeconfig.json, propiedades de MSBuild, variables de entorno y, para referencias cruzadas, archivos
de app.config para proyectos de .NET Framework.

runtimeconfig.json
Cuando un proyecto se compila, se genera un archivo [nombre_aplicación].runtimeconfig.json en el directorio de
salida. Si un archivo runtimeconfig.template.json existe en la misma carpeta que el archivo de proyecto, las
opciones de configuración que contiene se combinan en el archivo [nombre_aplicación].runtimeconfig.json. Si va a
compilar la aplicación, coloque las opciones de configuración en el archivo runtimeconfig.template.json. Si solo va
a ejecutar la aplicación, insértelas directamente en el archivo [nombre_aplicación].runtimeconfig.json file.

NOTE
El archivo [nombre_aplicación].runtimeconfig.json se sobrescribirá en las compilaciones posteriores.

Especifique las opciones de configuración de runtime en la sección configProper ties de los archivos
runtimeconfig.json. Esta sección tiene el formato siguiente:

"configProperties": {
"config-property-name1": "config-value1",
"config-property-name2": "config-value2"
}

Archivo de ejemplo [nombre_aplicación].runtimeconfig.json


Si va a colocar las opciones en el archivo JSON de salida, anídelas en la propiedad runtimeOptions .

{
"runtimeOptions": {
"tfm": "netcoreapp3.1",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "3.1.0"
},
"configProperties": {
"System.GC.Concurrent": false,
"System.Threading.ThreadPool.MinThreads": 4,
"System.Threading.ThreadPool.MaxThreads": 25
}
}
}

Archivo de ejemplo runtimeconfig.template.json


Si va a colocar las opciones en el archivo JSON de plantilla, omita la propiedad runtimeOptions .

{
"configProperties": {
"System.GC.Concurrent": false,
"System.Threading.ThreadPool.MinThreads": "4",
"System.Threading.ThreadPool.MaxThreads": "25"
}
}

propiedades de MSBuild
Algunas opciones de configuración de runtime se pueden establecer mediante propiedades de MSBuild en el
archivo .csproj o .vbproj de proyectos de .NET Core de estilo SDK. Las propiedades de MSBuild tienen prioridad
sobre las opciones establecidas en el archivo runtimeconfig.template.json. También sobrescriben las opciones
establecidas en el archivo [nombre_aplicación].runtimeconfig.json en tiempo de compilación.
Este es un ejemplo de archivo de proyecto de estilo SDK con propiedades de MSBuild para configurar el
comportamiento de runtime:
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<PropertyGroup>
<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
<ThreadPoolMinThreads>4</ThreadPoolMinThreads>
<ThreadPoolMaxThreads>25</ThreadPoolMaxThreads>
</PropertyGroup>

</Project>

Las propiedades de MSBuild para configurar el comportamiento de runtime se indican en los artículos
individuales de cada área, por ejemplo, la recolección de elementos no utilizados. También se enumeran en la
sección sobre la configuración del tiempo de ejecución de la referencia de las propiedades de MSBuild para los
proyectos de estilo de SDK.

Variables de entorno
Las variables de entorno se pueden usar para proporcionar información de configuración del entorno de
ejecución. El hecho de configurar una opción relativa al tiempo de ejecución mediante el uso de una variable de
entorno aplica la configuración a todas las aplicaciones de .NET Core. Los botones de configuración especificados
como variables de entorno generalmente tienen el prefijo COMPlus_ .
Puede definir variables de entorno desde el Panel de control de Windows, en la línea de comandos o mediante
programación llamando al método Environment.SetEnvironmentVariable(String, String) en sistemas basados en
Windows y Unix.
En los siguientes ejemplos se muestra cómo establecer una variable de entorno en la línea de comandos:

# Windows
set COMPlus_GCRetainVM=1

# Powershell
$env:COMPlus_GCRetainVM="1"

# Unix
export COMPlus_GCRetainVM=1
Opciones de configuración del entorno de ejecución
para compilación
16/09/2020 • 4 minutes to read • Edit Online

Compilación en niveles
Configura si el compilador Just-In-Time (JIT) usa la compilación en niveles. La compilación en niveles realiza la
transición de métodos mediante dos niveles:
El primer nivel genera código más rápidamente (JIT rápido) o carga código previamente compilado
(ReadyToRun).
El segundo nivel genera código optimizado en segundo plano ("JIT de optimización").
En la versión 3.0 de .NET Core y posteriores, la compilación en niveles está habilitada de forma predeterminada.
En las versiones 2.1 y 2.2 de .NET Core, la compilación en niveles está deshabilitada de forma predeterminada.
Para obtener más información, vea la Guía de compilación en niveles.

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json System.Runtime.TieredCompilation true : habilitado.


false : deshabilitado.

Propiedad de MSBuild TieredCompilation true : habilitado.


false : deshabilitado.

Variable del entorno COMPlus_TieredCompilation 1 : habilitado.


0 : deshabilitado.

Ejemplos
Archivo runtimeconfig.json:

{
"runtimeOptions": {
"configProperties": {
"System.Runtime.TieredCompilation": false
}
}
}

Archivo del proyecto:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TieredCompilation>false</TieredCompilation>
</PropertyGroup>

</Project>

JIT rápido
Configura si el compilador JIT usa JIT rápido. En el caso de los métodos que no contienen bucles y para los que
no hay código compilado previamente, JIT rápido los compila más rápidamente, pero sin optimizaciones.
La habilitación de JIT rápido reduce el tiempo de inicio, pero puede generar código con características de
rendimiento degradadas. Por ejemplo, el código puede usar más espacio de pila, asignar más memoria y
ejecutarse más lentamente.
Si JIT rápido está deshabilitado pero la compilación en niveles está habilitada, solo el código compilado
previamente participa en la compilación en niveles. Si un método no está compilado previamente con
ReadyToRun, el comportamiento de JIT es el mismo que si la compilación en niveles estuviera deshabilitada.
En .NET Core 3.0 y versiones posteriores, JIT rápido está habilitado de forma predeterminada.
En .NET Core 2.1 y 2.2, JIT rápido está deshabilitado de forma predeterminada.

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json System.Runtime.TieredCompilation.QuickJit
true : habilitado.
false : deshabilitado.

Propiedad de MSBuild TieredCompilationQuickJit true : habilitado.


false : deshabilitado.

Variable del entorno COMPlus_TC_QuickJit 1 : habilitado.


0 : deshabilitado.

Ejemplos
Archivo runtimeconfig.json:

{
"runtimeOptions": {
"configProperties": {
"System.Runtime.TieredCompilation.QuickJit": false
}
}
}

Archivo del proyecto:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TieredCompilationQuickJit>false</TieredCompilationQuickJit>
</PropertyGroup>

</Project>

JIT rápido para bucles


Configura si el compilador JIT usa JIT rápido en métodos que contienen bucles.
La habilitación de JIT rápido para bucles puede mejorar el rendimiento de inicio. Pero los bucles de ejecución
prolongada se pueden bloquear en código menos optimizado durante períodos largos.
Si JIT rápido está deshabilitado, este valor no tiene ningún efecto.
Si se omite esta configuración, el método JIT rápido no se utiliza para los métodos que contienen bucles. Esto es
equivalente a establecer el valor en false .
N O M B RE DE VA LO R VA LO RES

runtimeconfig.json false : deshabilitado.


System.Runtime.TieredCompilation.QuickJitForLoops
true : habilitado.

Propiedad de MSBuild TieredCompilationQuickJitForLoops false : deshabilitado.


true : habilitado.

Variable del entorno COMPlus_TC_QuickJitForLoops 0 : deshabilitado.


1 : habilitado.

Ejemplos
Archivo runtimeconfig.json:

{
"runtimeOptions": {
"configProperties": {
"System.Runtime.TieredCompilation.QuickJitForLoops": false
}
}
}

Archivo del proyecto:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TieredCompilationQuickJitForLoops>true</TieredCompilationQuickJitForLoops>
</PropertyGroup>

</Project>

ReadyToRun
Configura si el entorno de ejecución de .NET Core usa código precompilado para las imágenes con datos de
ReadyToRun disponibles. Al deshabilitar esta opción, se fuerza al entorno de ejecución a compilar código de
marco mediante JIT.
Para obtener más información, vea ReadyToRun.
Si se omite esta configuración, .NET usa datos ReadyToRun cuando están disponibles. Esto es equivalente a
establecer el valor en 1 .

N O M B RE DE VA LO R VA LO RES

Variable del entorno COMPlus_ReadyToRun 1 : habilitado.


0 : deshabilitado.
Opciones de configuración del ejecución para la
depuración y la generación de perfiles
16/09/2020 • 3 minutes to read • Edit Online

Habilitación de diagnósticos
Configura si el depurador, el generador de perfiles y los diagnósticos de EventPipe están habilitados o
deshabilitados.
Si se omite esta configuración, se habilitan los diagnósticos. Esto es equivalente a establecer el valor en 1 .

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json N/D N/D

Variable del entorno COMPlus_EnableDiagnostics 1 : habilitado.


0 : deshabilitado.

Habilitación de la generación de perfiles


Configura si la generación de perfiles está habilitada para el proceso que se ejecuta actualmente.
Si se omite esta configuración, la generación de perfiles está deshabilitada. Esto es equivalente a establecer el
valor en 0 .

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json N/D N/D

Variable del entorno CORECLR_ENABLE_PROFILING 0 : deshabilitado.


1 : habilitado.

GUID de generador de perfiles


Especifica el GUID del generador de perfiles que se va a cargar en el proceso que se ejecuta actualmente.

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json N/D N/D

Variable del entorno CORECLR_PROFILER string-guid

Ubicación del generador de perfiles


Especifica la ruta de acceso a la biblioteca de vínculos dinámicos del generador de perfiles que se va a cargar en
el proceso que se ejecuta actualmente (proceso de 32 bits o de 64 bits).
Si se establece más de una variable, las variables concretas de valor de bits tienen prioridad. Especifican el valor
de bits del generador de perfiles que se va a cargar.
Para obtener más información, vea Búsqueda de la biblioteca del generador de perfiles.
N O M B RE DE VA LO R VA LO RES

Variable del entorno CORECLR_PROFILER_PATH string-path

Variable del entorno CORECLR_PROFILER_PATH_32 string-path

Variable del entorno CORECLR_PROFILER_PATH_64 string-path

Escritura del mapa de rendimiento


Habilita o deshabilita la escritura de /tmp/perf-$pid.map en los sistemas Linux.
Si se omite esta configuración, la escritura del mapa de rendimiento está deshabilitada. Esto es equivalente a
establecer el valor en 0 .

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json N/D N/D

Variable del entorno COMPlus_PerfMapEnabled 0 : deshabilitado.


1 : habilitado.

Marcadores del registro de rendimiento


Habilita o deshabilita la señal especificada que se va a aceptar y omitir como marcador en los registros de
rendimiento.
Si se omite este valor, la señal especificada no se pasa por alto. Esto es equivalente a establecer el valor en 0 .

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json N/D N/D

Variable del entorno COMPlus_PerfMapIgnoreSignal 0 : deshabilitado.


1 : habilitado.

NOTE
Este valor se omite si se omite COMPlus_PerfMapEnabled o se establece en 0 (es decir, deshabilitado).
Opciones de configuración del entorno de ejecución
para la recolección de elementos no utilizados
16/09/2020 • 29 minutes to read • Edit Online

Esta página contiene información sobre la configuración del recolector de elementos no utilizados (GC) que se
puede cambiar en el entorno de ejecución. Si intenta lograr el máximo rendimiento de una aplicación en
ejecución, valore la posibilidad de usar esta configuración. Sin embargo, los valores predeterminados
proporcionan un rendimiento óptimo para la mayoría de aplicaciones en situaciones habituales.
En esta página, la configuración se organiza en grupos. La configuración de cada grupo se usa normalmente junto
con las otras para lograr un resultado concreto.

NOTE
La aplicación también puede cambiar dinámicamente esta configuración mientras se ejecuta, por lo que se puede
invalidar cualquier valor del entorno de ejecución que haya establecido.
Por lo general, algunos valores de configuración, como el nivel de latencia, se establecen únicamente a través de la API
en tiempo de diseño. Estos valores se omiten en esta página.
En el caso de los valores numéricos, use la notación decimal para la configuración del archivo runtimeconfig.json y la
notación hexadecimal para la configuración de las variables de entorno. Para los valores hexadecimales, puede
especificarlos con o sin el prefijo "0x".

Tipos de recolección de elementos no utilizados


Los dos tipos principales de recolección de elementos no utilizados son la GC de estación de trabajo y la de
servidor. Para más información sobre la diferencia entre estos dos tipos, consulte Recolección de elementos no
utilizados de estación de trabajo y de servidor.
Los subtipos de la recolección de elementos no utilizados son en segundo plano y no simultáneos.
Use la configuración siguiente para seleccionar los tipos de la recolección de elementos no utilizados:
Recolección de elementos no utilizados de estación de trabajo y de servidor
GC en segundo plano
Estación de trabajo frente a servidor
Configura si la aplicación usa la recolección de elementos no utilizados de estación de trabajo o la de servidor.
Predeterminado: recolección de elementos no utilizados de estación de trabajo. Esto es equivalente a
establecer el valor en false .

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.Server false : estación de trabajo. .NET Core 1.0


true : servidor.

Propiedad de MSBuild ServerGarbageCollection false : estación de trabajo. .NET Core 1.0


true : servidor.
N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

Variable del entorno COMPlus_gcServer 0 : estación de trabajo. .NET Core 1.0


1 : servidor.

app.config para .NET GCServer false : estación de trabajo.


Framework true : servidor.

Ejemplos
Archivo runtimeconfig.json:

{
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
}
}

Archivo del proyecto:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

</Project>

GC en segundo plano
Configura si está habilitada la recolección de elementos no utilizados en segundo plano (simultánea).
Predeterminado: uso de GC en segundo plano. Esto es equivalente a establecer el valor en true .
Para más información, consulte Recolección de elementos no utilizados en segundo plano.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.Concurrent true : GC en segundo .NET Core 1.0


plano.
false : GC no simultáneo.

Propiedad de MSBuild ConcurrentGarbageCollection true : GC en segundo .NET Core 1.0


plano.
false : GC no simultáneo.

Variable del entorno COMPlus_gcConcurrent 1 : GC en segundo plano. .NET Core 1.0


0 : GC no simultáneo.

app.config para .NET gcConcurrent true : GC en segundo


Framework plano.
false : GC no simultáneo.

Ejemplos
Archivo runtimeconfig.json:
{
"runtimeOptions": {
"configProperties": {
"System.GC.Concurrent": false
}
}
}

Archivo del proyecto:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
</PropertyGroup>

</Project>

Administración del uso de recursos


Use la siguiente configuración para administrar el uso del procesador y la memoria del recolector de elementos
no utilizados:
Affinitize (Afinidad)
Affinitize mask (Máscara de afinidad)
Affinitize ranges (Intervalos de afinidad)
Grupos de CPU
Heap count (Recuento de montones)
Heap limit (Límite del montón)
Heap limit percent (Límite del montón (porcentaje))
High memory percent (Uso de memoria alto (porcentaje))
Límites por cada montón de objetos
Per-object-heap limit percents (Límite por montón de objetos (porcentaje))
Retain VM (Conservar VM)
Para obtener más información sobre algunos de estos valores, vea la entrada de blog en la que se detalla el
término medio entre la GC de la estación de trabajo y del servidor.
Heap count (Recuento de montones)
Limita el número de montones creados por el recolector de elementos no utilizados.
Solo se aplica a la recolección de elementos no utilizados del servidor.
Si la afinidad del procesador de GC está habilitada (el valor predeterminado) el valor del recuento de
montones establece afinidad entre n montones o subprocesos de GC en los primeros n procesadores. (Use
los valores affinitize mask o affinitize ranges para especificar exactamente los procesadores entre los que se va
a establecer afinidad).
Si la afinidad del procesador de GC está deshabilitada, esta configuración limita el número de montones de
GC.
Para obtener más información, vea la sección Comentarios de GCHeapCount.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.HeapCount valor decimal .NET Core 3.0


N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

Variable del entorno COMPlus_GCHeapCount valor hexadecimal .NET Core 3.0

app.config para .NET GCHeapCount valor decimal .NET Framework 4.6.2


Framework

Ejemplo:

{
"runtimeOptions": {
"configProperties": {
"System.GC.HeapCount": 16
}
}
}

TIP
Si configura la opción en runtimeconfig.json, especifique un valor decimal. Si configura la opción como una variable de
entorno, especifique un valor hexadecimal. Por ejemplo, para limitar el número de montones a 16, los valores serían 16 para
el archivo JSON y 0x10 o 10 para la variable de entorno.

Affinitize mask (Máscara de afinidad)


Especifica los procesadores exactos que deben usar los subprocesos del recolector de elementos no utilizados.
Si la afinidad del procesador de GC está deshabilitada, esta configuración se ignora.
Solo se aplica a la recolección de elementos no utilizados del servidor.
El valor es una máscara de bits que define los procesadores que están disponibles para el proceso. Por
ejemplo, un valor decimal de 1023 (o un valor hexadecimal de 0x3FF o 3FF si utiliza la variable de entorno) es
0011 1111 1111 en notación binaria. Esto especifica que se usarán los 10 primeros procesadores. Para
especificar los 10 procesadores siguientes, es decir, los procesadores 10-19, especifique un valor decimal de
1047552 (o un valor hexadecimal de 0xFFC00 o FFC00), que es equivalente a un valor binario de 1111 1111
1100 0000 0000.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.HeapAffinitizeMask valor decimal .NET Core 3.0

Variable del entorno COMPlus_GCHeapAffinitizeMask valor hexadecimal .NET Core 3.0

app.config para .NET GCHeapAffinitizeMask valor decimal .NET Framework 4.6.2


Framework

Ejemplo:

{
"runtimeOptions": {
"configProperties": {
"System.GC.HeapAffinitizeMask": 1023
}
}
}
Affinitize ranges (Intervalos de afinidad)
Especifica la lista de procesadores que se van a usar para los subprocesos del recolector de elementos no
utilizados.
Este valor es similar a System.GC.HeapAffinitizeMask, salvo que permite especificar más de 64 procesadores.
En el caso de los sistemas operativos Windows, agregue el prefijo con el grupo de CPU correspondiente al
número o el rango del procesador, por ejemplo, "0:1-10,0:12,1:50-52,1:70".
Si la afinidad del procesador de GC está deshabilitada, esta configuración se ignora.
Solo se aplica a la recolección de elementos no utilizados del servidor.
Para obtener más información, vea el artículo del blog de Maoni Stephens sobre la mejora de la configuración
de la CPU para la GC en máquinas con > 64 CPU.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json Lista separada por comas de


System.GC.GCHeapAffinitizeRanges .NET Core 3.0
números de procesador o
rangos de números de
procesador.
Ejemplo de Unix: "1-
10,12,50-52,70"
Ejemplo de Windows: "0:1-
10,0:12,1:50-52,1:70"

Variable del entorno Lista separada por comas de


COMPlus_GCHeapAffinitizeRanges .NET Core 3.0
números de procesador o
rangos de números de
procesador.
Ejemplo de Unix: "1-
10,12,50-52,70"
Ejemplo de Windows: "0:1-
10,0:12,1:50-52,1:70"

Ejemplo:

{
"runtimeOptions": {
"configProperties": {
"System.GC.GCHeapAffinitizeRanges": "0:1-10,0:12,1:50-52,1:70"
}
}
}

Grupos de CPU
Configura si el recolector de elementos no utilizados usa grupos de CPU o no.
Cuando un equipo Windows de 64 bits tiene varios grupos de CPU, es decir, hay más de 64 procesadores,
la habilitación de este elemento amplía la recolección de elementos no utilizados en todos los grupos de
CPU. El recolector de elementos no utilizados usa todos los núcleos para crear y equilibrar montones.
Solo se aplica a la recolección de elementos no utilizados del servidor en sistemas operativos Windows de
64 bits.
Predeterminado: GC no se extiende por los grupos de CPU. Esto es equivalente a establecer el valor en 0 .
Para obtener más información, vea el artículo del blog de Maoni Stephens sobre la mejora de la
configuración de la CPU para la GC en máquinas con > 64 CPU.
N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.CpuGroup 0 : deshabilitado. .NET 5.0


1 : habilitado.

Variable del entorno COMPlus_GCCpuGroup 0 : deshabilitado. .NET Core 1.0


1 : habilitado.

app.config para .NET GCCpuGroup false : deshabilitado.


Framework true : habilitado.

NOTE
Para configurar Common Language Runtime (CLR) con el fin de distribuir también los subprocesos del grupo de
subprocesos entre todos los grupos de CPU, habilite la opción Elemento Thread_UseAllCpuGroups. En el caso de las
aplicaciones de .NET Core, se puede habilitar esta opción estableciendo el valor de la variable de entorno
COMPlus_Thread_UseAllCpuGroups en 1 .

Affinitize (Afinidad)
Especifica si establecer afinidad entre subprocesos de recolección de elementos no utilizados con
procesadores. El hecho de establecer afinidad entre un subproceso de GC significa que solo puede ejecutarse
en su CPU concreta. Se crea un montón para cada subproceso de GC.
Solo se aplica a la recolección de elementos no utilizados del servidor.
Predeterminado: establecer afinidad entre subprocesos de recolección de elementos no utilizados con
procesadores. Esto es equivalente a establecer el valor en false .

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.NoAffinitize false : establecer afinidad. .NET Core 3.0


true : no establecer
afinidad.

Variable del entorno COMPlus_GCNoAffinitize 0 : establecer afinidad. .NET Core 3.0


1 : no establecer afinidad.

app.config para .NET GCNoAffinitize false : establecer afinidad. .NET Framework 4.6.2
Framework true : no establecer
afinidad.

Ejemplo:

{
"runtimeOptions": {
"configProperties": {
"System.GC.NoAffinitize": true
}
}
}

Heap limit (Límite del montón)


Especifica el tamaño máximo de confirmación, en bytes, para el montón de GC y la contabilidad de GC.
Esta configuración solo se aplica a los equipos de 64 bits.
Este valor se omite si se configuran los límites por cada montón de objetos.
El valor predeterminado, que solo se aplica en ciertos casos, es el mayor de 20 MB o de 75 % del límite de
memoria del contenedor. El valor predeterminado se aplica si:
El proceso se ejecuta dentro de un contenedor que tiene un límite de memoria especificado.
No se ha establecido System.GC.HeapHardLimitPercent.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.HeapHardLimit valor decimal .NET Core 3.0

Variable del entorno COMPlus_GCHeapHardLimit valor hexadecimal .NET Core 3.0

Ejemplo:

{
"runtimeOptions": {
"configProperties": {
"System.GC.HeapHardLimit": 209715200
}
}
}

TIP
Si configura la opción en runtimeconfig.json, especifique un valor decimal. Si configura la opción como una variable de
entorno, especifique un valor hexadecimal. Por ejemplo, para especificar un límite de montón de 200 mebibytes (MiB), los
valores serían 209715200 para el archivo JSON y 0xC800000 o C800000 para la variable de entorno.

Heap limit percent (Límite del montón (porcentaje ))


Especifica el uso del montón de GC permitido como porcentaje de la memoria física total.
Si también se establece System.GC.HeapHardLimit, este valor se omite.
Esta configuración solo se aplica a los equipos de 64 bits.
Si el proceso se ejecuta dentro de un contenedor que tiene un límite de memoria especificado, el
porcentaje se calcula como un porcentaje de ese límite de memoria.
Este valor se omite si se configuran los límites por cada montón de objetos.
El valor predeterminado, que solo se aplica en ciertos casos, es el menor de 20 MB o 75 % del límite de
memoria del contenedor. El valor predeterminado se aplica si:
El proceso se ejecuta dentro de un contenedor que tiene un límite de memoria especificado.
No se ha establecido System.GC.HeapHardLimit.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json valor decimal


System.GC.HeapHardLimitPercent .NET Core 3.0

Variable del entorno valor hexadecimal


COMPlus_GCHeapHardLimitPercent .NET Core 3.0

Ejemplo:
{
"runtimeOptions": {
"configProperties": {
"System.GC.HeapHardLimitPercent": 30
}
}
}

TIP
Si configura la opción en runtimeconfig.json, especifique un valor decimal. Si configura la opción como una variable de
entorno, especifique un valor hexadecimal. Por ejemplo, para limitar el uso del montón al 30 %, los valores serían 30 para el
archivo JSON y 0x1E o 1E para la variable de entorno.

Límites por cada montón de objetos


Puede especificar el uso de montones permitidos de la recolección de elementos no utilizados por cada objeto.
Los diferentes montones son el montón de objetos grandes (LOH), el montón de objetos pequeños (SOH) y el
montón de objetos anclados (POH).
Si especifica un valor para COMPLUS_GCHeapHardLimitSOH, COMPLUS_GCHeapHardLimitLOH o
COMPLUS_GCHeapHardLimitPOH , también debe especificar un valor para COMPLUS_GCHeapHardLimitSOH y
COMPLUS_GCHeapHardLimitLOH . Si no lo hace, el runtime no se inicializará.
El valor predeterminado para COMPLUS_GCHeapHardLimitPOH es 0. COMPLUS_GCHeapHardLimitSOH y
COMPLUS_GCHeapHardLimitLOH no tienen valores predeterminados.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.HeapHardLimitSOH valor decimal .NET 5.0

Variable del entorno COMPLUS_GCHeapHardLimitSOH valor hexadecimal .NET 5.0

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.HeapHardLimitLOH valor decimal .NET 5.0

Variable del entorno COMPLUS_GCHeapHardLimitLOH valor hexadecimal .NET 5.0

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.HeapHardLimitPOH valor decimal .NET 5.0

Variable del entorno COMPLUS_GCHeapHardLimitPOH valor hexadecimal .NET 5.0

TIP
Si configura la opción en runtimeconfig.json, especifique un valor decimal. Si configura la opción como una variable de
entorno, especifique un valor hexadecimal. Por ejemplo, para especificar un límite de montón de 200 mebibytes (MiB), los
valores serían 209715200 para el archivo JSON y 0xC800000 o C800000 para la variable de entorno.

Per-object-heap limit percents (Límite por montón de objetos (porcentaje ))


Puede especificar el uso de montones permitidos de la recolección de elementos no utilizados por cada objeto.
Los diferentes montones son el montón de objetos grandes (LOH), el montón de objetos pequeños (SOH) y el
montón de objetos anclados (POH).
Si especifica un valor para COMPLUS_GCHeapHardLimitSOHPercent, COMPLUS_GCHeapHardLimitLOHPercent o
COMPLUS_GCHeapHardLimitPOHPercent , también debe especificar un valor para COMPLUS_GCHeapHardLimitSOHPercent
y COMPLUS_GCHeapHardLimitLOHPercent . Si no lo hace, el runtime no se inicializará.
Estos valores se omiten si se especifican COMPLUS_GCHeapHardLimitSOH , COMPLUS_GCHeapHardLimitLOH y
COMPLUS_GCHeapHardLimitPOH .
Un valor de 1 significa que la recolección de elementos no utilizados usa el 1 % de la memoria física total para
ese montón de objetos.
Cada valor debe ser mayor que cero y menor o igual que 100. Además, la suma de los tres valores de
porcentaje debe ser inferior a 100. En caso contrario, el runtime no se inicializará.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json valor decimal


System.GC.HeapHardLimitSOHPercent .NET 5.0

Variable del entorno valor hexadecimal


COMPLUS_GCHeapHardLimitSOHPercent .NET 5.0

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json valor decimal


System.GC.HeapHardLimitLOHPercent .NET 5.0

Variable del entorno valor hexadecimal


COMPLUS_GCHeapHardLimitLOHPercent .NET 5.0

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json valor decimal


System.GC.HeapHardLimitPOHPercent .NET 5.0

Variable del entorno valor hexadecimal


COMPLUS_GCHeapHardLimitPOHPercent .NET 5.0

TIP
Si configura la opción en runtimeconfig.json, especifique un valor decimal. Si configura la opción como una variable de
entorno, especifique un valor hexadecimal. Por ejemplo, para limitar el uso del montón al 30 %, los valores serían 30 para el
archivo JSON y 0x1E o 1E para la variable de entorno.

High memory percent (Uso de memoria alto (porcentaje ))


La carga de memoria se indica mediante el porcentaje de memoria física en uso. De forma predeterminada,
cuando la carga de memoria física alcanza el 90 % , la recolección de elementos no utilizados se vuelve más
agresiva a la hora de realizar recolecciones de elementos no utilizados completas y compactas para evitar la
paginación. Cuando la carga de memoria está por debajo del 90 %, la recolección de elementos no utilizados
favorece las recolecciones en segundo plano de recolecciones de elementos no utilizados completas, que tienen
pausas más cortas pero no reducen mucho el tamaño total del montón. En los equipos con una cantidad de
memoria considerable (80 GB o más), el umbral de carga predeterminado está entre el 90 % y el 97 %.
El umbral de carga de uso de memoria alto se puede ajustar mediante la variable de entorno
COMPlus_GCHighMemPercent o la opción de configuración JSON System.GC.HighMemoryPercent . Considere la
posibilidad de ajustar el umbral si quiere controlar el tamaño del montón. Por ejemplo, en el proceso dominante
de un equipo con 64 GB de memoria, es razonable que la recolección de elementos no utilizados empiece a
reaccionar cuando haya un 10 % de memoria disponible. Pero en el caso de procesos más pequeños, por ejemplo,
un proceso que solo usa 1 GB de memoria, la recolección de elementos no utilizados puede ejecutarse
cómodamente con menos del 10 % de memoria disponible. En estos procesos más pequeños, considere la
posibilidad de establecer un umbral más alto. Por otro lado, si quiere que los procesos más grandes tengan
tamaños de montón más pequeños (incluso cuando haya mucha memoria física disponible), la reducción de este
umbral es una manera eficaz de que la recolección de elementos no utilizados reaccione antes para reducir el
montón.

NOTE
En el caso de los procesos que se ejecutan en un contenedor, la recolección de elementos no utilizados considera la
memoria física según el límite del contenedor.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.HighMemoryPercent valor decimal .NET 5.0

Variable del entorno COMPlus_GCHighMemPercent valor hexadecimal

TIP
Si configura la opción en runtimeconfig.json, especifique un valor decimal. Si configura la opción como una variable de
entorno, especifique un valor hexadecimal. Por ejemplo, para establecer el umbral de uso de memoria alto en el 75 %, los
valores serían 75 para el archivo JSON y 0x4B o 4B para la variable de entorno.

Retain VM (Conservar VM )
Configura si los segmentos que se deben eliminar se ponen en una lista en espera para usarlos en el futuro o
se devuelven al sistema operativo (SO).
Predeterminado: devolver los segmentos al sistema operativo. Esto es equivalente a establecer el valor en
false .

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.RetainVM false : liberar al sistema .NET Core 1.0


operativo.
true : poner en espera.

Propiedad de MSBuild RetainVMGarbageCollection false : liberar al sistema .NET Core 1.0


operativo.
true : poner en espera.

Variable del entorno COMPlus_GCRetainVM 0 : liberar al sistema .NET Core 1.0


operativo.
1 : poner en espera.

Ejemplos
Archivo runtimeconfig.json:
{
"runtimeOptions": {
"configProperties": {
"System.GC.RetainVM": true
}
}
}

Archivo del proyecto:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RetainVMGarbageCollection>true</RetainVMGarbageCollection>
</PropertyGroup>

</Project>

Páginas grandes
Especifica si se deben usar páginas grandes cuando se establece un límite máximo de montones.
Predeterminado: no utilizar páginas grandes cuando se establezca un límite rígido de montones. Esto es
equivalente a establecer el valor en 0 .
Se trata de un valor de configuración experimental.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json N/D N/D N/D

Variable del entorno COMPlus_GCLargePages 0 : deshabilitado. .NET Core 3.0


1 : habilitado.

Allow large objects (Permitir objetos grandes)


Configura la compatibilidad del recolector de elementos no utilizados en plataformas de 64 bits para matrices
de más de 2 gigabytes (GB) de tamaño total.
Predeterminado: GC admite matrices de más de 2 GB. Esto es equivalente a establecer el valor en 1 .
Esta opción puede quedar obsoleta en una versión futura de .NET.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json N/D N/D N/D

Variable del entorno 1 : habilitado.


COMPlus_gcAllowVeryLargeObjects .NET Core 1.0
0 : deshabilitado.

app.config para .NET gcAllowVeryLargeObjects 1 : habilitado. .NET Framework 4.5


Framework 0 : deshabilitado.

Umbral del montón de objetos grandes


Especifica el tamaño del umbral, en bytes, que provoca que los objetos vayan al montón de objetos grandes
(LOH).
El valor predeterminado del umbral es de 85 000 bytes.
El valor que especifique debe ser mayor que el umbral predeterminado.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json System.GC.LOHThreshold valor decimal .NET Core 1.0

Variable del entorno COMPlus_GCLOHThreshold valor hexadecimal .NET Core 1.0

app.config para .NET GCLOHThreshold valor decimal .NET Framework 4.8


Framework

Ejemplo:

{
"runtimeOptions": {
"configProperties": {
"System.GC.LOHThreshold": 120000
}
}
}

TIP
Si configura la opción en runtimeconfig.json, especifique un valor decimal. Si configura la opción como una variable de
entorno, especifique un valor hexadecimal. Por ejemplo, para establecer un tamaño de umbral de 120 000 bytes, los valores
serían 120000 para el archivo JSON y 0x1D4C0 o 1D4C0 para la variable de entorno.

GC independiente
Especifica una ruta de acceso a la biblioteca que contiene el recolector de elementos no utilizados que el
entorno de ejecución pretende cargar.
Para obtener más información, vea Diseño del cargador de GC independiente.

N O M B RE DE VA LO R VA LO RES VERSIÓ N IN T RO DUC IDA

runtimeconfig.json N/D N/D N/D

Variable del entorno COMPlus_GCName string_path .NET Core 2.0


Opciones de configuración del entorno de ejecución
para globalización
16/09/2020 • 4 minutes to read • Edit Online

Modo invariable
Determina si una aplicación de .NET Core se ejecuta en modo invariable de globalización sin tener acceso a los
datos y el comportamiento concretos de la referencia cultural.
Si se omite esta configuración, la aplicación se ejecuta con acceso a los datos culturales. Esto es equivalente a
establecer el valor en false .
Para obtener más información, vea Modo invariable de globalización de .NET Core.

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json System.Globalization.Invariant false : acceder a los datos culturales.


true : ejecutar en modo invariable.

Propiedad de MSBuild InvariantGlobalization false : acceder a los datos culturales.


true : ejecutar en modo invariable.

Variable del entorno DOTNET_SYSTEM_GLOBALIZATION_INVARIANT 0 : acceder a los datos culturales.


1 : ejecutar en modo invariable.

Ejemplos
Archivo runtimeconfig.json:

{
"runtimeOptions": {
"configProperties": {
"System.Globalization.Invariant": true
}
}
}

Archivo del proyecto:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

</Project>

Rangos de años de la era


Determina si las comprobaciones de rango para los calendarios que admiten varias eras son relajadas o si las
fechas que desbordan el intervalo de fechas de una era producen un elemento ArgumentOutOfRangeException.
Si se omite este valor, las comprobaciones de intervalo son distendidas. Esto es equivalente a establecer el valor
en false .
Para obtener más información, vea Calendarios, eras e intervalos de fechas: comprobaciones de intervalos
relajadas.

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json false : comprobaciones de intervalos


Switch.System.Globalization.EnforceJapaneseEraYearRanges
relajadas.
true : los desbordamientos causan
una excepción.

Variable del entorno N/D N/D

Análisis de fechas japonesas


Determina si una cadena cuyo valor del año contiene "1" o "Gannen" se analiza correctamente o si solo se
admite "1".
Si se omite este valor, las cadenas que contienen "1" o "Gannen" como año se analizan correctamente. Esto es
equivalente a establecer el valor en false .
Para obtener más información, vea Representación de fechas en calendarios con varias eras.

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json false : se admite "Gannen" o "1".


Switch.System.Globalization.EnforceLegacyJapaneseDateParsing
true : solo se admite "1".

Variable del entorno N/D N/D

Formato de año japonés


Determina si el primer año de una era de calendario japonés tiene formato "Gannen" o un número.
Si se omite esta configuración, el primer año tiene formato "Gannen". Esto es equivalente a establecer el valor en
false .
Para obtener más información, vea Representación de fechas en calendarios con varias eras.

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json false : dar formato como "Gannen".


Switch.System.Globalization.FormatJapaneseFirstYearAsANumber
true : dar formato como número.

Variable del entorno N/D N/D

NLS
Determina si .NET usa las API de globalización de compatibilidad con el idioma nacional (NLS) o componentes
internacionales para Unicode (ICU) para aplicaciones de Windows. .NET 5.0 y versiones posteriores usan las API
de globalización de ICU de forma predeterminada en la actualización de mayo de 2019 de Windows 10 o
versiones posteriores.
Si se omite esta configuración, .NET usa las API de globalización de ICU forma predeterminada. Esto es
equivalente a establecer el valor en false .
Para obtener más información, consulte Las API de globalización usan bibliotecas ICU en Windows.
N O M B RE DE VA LO R VA LO RES IN C L USIÓ N

runtimeconfig.json System.Globalization.UseNls false -Uso de API de .NET 5.0


globalización de ICU
true -Uso de API de
globalización de NLS

Variable del entorno DOTNET_SYSTEM_GLOBALIZATION_USENLS


false -Uso de API de .NET 5.0
globalización de ICU
true -Uso de API de
globalización de NLS
Opciones de configuración del entorno de ejecución
para las redes
16/09/2020 • 2 minutes to read • Edit Online

Protocolo HTTP/2
Configura si está habilitada la compatibilidad con el protocolo HTTP/2.
Si se omite esta configuración, la compatibilidad con el protocolo HTTP/2 está deshabilitada. Esto es
equivalente a establecer el valor en false .
Introducido en .NET Core 3.0.

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json false : deshabilitado.


System.Net.Http.SocketsHttpHandler.Http2Support
true : habilitado.

Variable del entorno 0 : deshabilitado.


DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2SUPPORT
1 : habilitado.

UseSocketsHttpHandler
Configura si System.Net.Http.HttpClientHandler usa System.Net.Http.SocketsHttpHandler o pilas de
protocolo HTTP anteriores (WinHttpHandler en Windows y CurlHandler , una clase interna implementada
sobre libcurl, en Linux).

NOTE
Es posible que esté usando las API para redes de alto nivel en lugar de crear directamente instancias de la
clase HttpClientHandler. Este valor también afecta a la pila del protocolo HTTP que usan las API para redes de alto
nivel, como HttpClient y HttpClientFactory.

Si se omite este valor, HttpClientHandler utiliza SocketsHttpHandler. Esto es equivalente a establecer el valor
en true .
Se puede configurar este valor mediante programación llamando al método AppContext.SetSwitch.

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json System.Net.Http.UseSocketsHttpHandler true : habilita el uso de


SocketsHttpHandler.
false : habilita el uso
de WinHttpHandler en Windows o
libcurl en Linux

Variable del entorno 1 : habilita el uso de


DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER
SocketsHttpHandler.
0 : habilita el uso de WinHttpHandler
en Windows o libcurl en Linux
NOTE
A partir de .NET 5, la configuración System.Net.Http.UseSocketsHttpHandler ya no está disponible.
Opciones de configuración del entorno de ejecución
para subprocesos
16/09/2020 • 2 minutes to read • Edit Online

Grupos de CPU
Configura si los subprocesos se distribuyen automáticamente entre los grupos de CPU.
Si se omite esta configuración, los subprocesos no se distribuyen entre los grupos de CPU. Esto es equivalente a
establecer el valor en 0 .

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json N/D N/D

Variable del entorno COMPlus_Thread_UseAllCpuGroups 0 : deshabilitado.


1 : habilitado.

Mínimo de subprocesos
Especifica el número mínimo de subprocesos para el grupo de subprocesos de trabajo.
Corresponde al método ThreadPool.SetMinThreads.

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json System.Threading.ThreadPool.MinThreads Entero que representa el número


mínimo de subprocesos.

Propiedad de MSBuild ThreadPoolMinThreads Entero que representa el número


mínimo de subprocesos.

Variable del entorno N/D N/D

Ejemplos
Archivo runtimeconfig.json:

{
"runtimeOptions": {
"configProperties": {
"System.Threading.ThreadPool.MinThreads": 4
}
}
}

Archivo del proyecto:


<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ThreadPoolMinThreads>4</ThreadPoolMinThreads>
</PropertyGroup>

</Project>

Máximo de subprocesos
Especifica el número máximo de subprocesos para el grupo de subprocesos de trabajo.
Corresponde al método ThreadPool.SetMaxThreads.

N O M B RE DE VA LO R VA LO RES

runtimeconfig.json System.Threading.ThreadPool.MaxThreads Entero que representa el número


máximo de subprocesos.

Propiedad de MSBuild ThreadPoolMaxThreads Entero que representa el número


máximo de subprocesos.

Variable del entorno N/D N/D

Ejemplos
Archivo runtimeconfig.json:

{
"runtimeOptions": {
"configProperties": {
"System.Threading.ThreadPool.MaxThreads": 20
}
}
}

Archivo del proyecto:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ThreadPoolMaxThreads>20</ThreadPoolMaxThreads>
</PropertyGroup>

</Project>
Información general sobre la publicación de
aplicaciones de .NET Core
16/09/2020 • 16 minutes to read • Edit Online

Las aplicaciones que se crean con .NET Core se pueden publicar de dos modos diferentes, lo cual afecta a la
forma en la que un usuario ejecuta la aplicación.
La publicación de la aplicación como independiente genera una aplicación que incluye el entorno de
ejecución y las bibliotecas de .NET Core, así como la aplicación y sus dependencias. Los usuarios de la
aplicación pueden ejecutarla en un equipo que no tenga instalado el entorno de ejecución de .NET Core.
La publicación de la aplicación como dependiente de la plataforma genera una aplicación que incluye
únicamente la propia aplicación y sus dependencias. Los usuarios de la aplicación tienen que instalar el
entorno de ejecución de .NET Core por separado.
De forma predeterminada, ambos modos de publicación generan un archivo ejecutable específico de la
plataforma. Las aplicaciones dependientes de la plataforma se pueden crear sin un archivo ejecutable y son
multiplataforma.
Cuando se genera un archivo ejecutable, puede especificar la plataforma de destino con un identificador de
entorno de ejecución (RID). Para obtener más información sobre los RID, vea el Catálogo de identificadores
de entorno de ejecución (RID) de .NET Core.
En la tabla siguiente se describen los comandos que se usan para publicar una aplicación como
dependiente de la plataforma o independiente según la versión del SDK:

T IP O SDK 2. 1 SDK 3. X C O M A N DO

archivo ejecutable ️
✔ dotnet publish
dependiente de la
plataforma para la
plataforma actual.

archivo ejecutable ️
✔ dotnet publish -r
dependiente de la <RID> --self-
contained false
plataforma para una
plataforma específica.

archivo binario ️
✔ ️
✔ dotnet publish
multiplataforma
dependiente de la
plataforma.

archivo ejecutable ️
✔ ️
✔ dotnet publish -r
independiente <RID>

Para obtener más información, vea el artículo sobre el comando dotnet publish de .NET Core.

Generación de un archivo ejecutable


Los archivos ejecutables no son multiplataforma. Son específicos de un sistema operativo y de la
arquitectura de la CPU. Al publicar la aplicación y crear un archivo ejecutable, puede publicarla como
independiente o dependiente de la plataforma. La publicación de una aplicación como independiente
incluye el entorno de ejecución de .NET Core con la aplicación, y los usuarios de la aplicación no tienen que
preocuparse de instalar .NET Core antes de ejecutar la aplicación. Las aplicaciones publicadas como
dependientes de la plataforma no incluyen el entorno de ejecución ni las bibliotecas de .NET Core; solo se
incluyen la aplicación y las dependencias de terceros.
Los siguientes comandos generan un archivo ejecutable:

T IP O SDK 2. 1 SDK 3. X C O M A N DO

archivo ejecutable ️
✔ dotnet publish
dependiente de la
plataforma para la
plataforma actual.

archivo ejecutable ️
✔ dotnet publish -r
dependiente de la <RID> --self-
contained false
plataforma para una
plataforma específica.

archivo ejecutable ️
✔ ️
✔ dotnet publish -r
independiente <RID>

Generación de un archivo binario multiplataforma


Los archivos binarios multiplataforma se crean al publicar la aplicación como dependiente de la plataforma
en forma de un archivo .dll. El archivo .dll tiene el nombre del proyecto. Por ejemplo, si tiene una aplicación
denominada lector_de_palabras , se creará un archivo denominado lector_de_palabras.dll. Las
aplicaciones publicadas de este modo se ejecutan con el comando dotnet <filename.dll> y se pueden
ejecutar en cualquier plataforma.
Los archivos binarios multiplataforma se pueden ejecutar en cualquier sistema operativo siempre que ya
esté instalado el entorno de ejecución de .NET Core de destino. Si el entorno de ejecución de .NET Core de
destino no está instalado, la aplicación puede ejecutarse con un entorno de ejecución más reciente a
condición de que la aplicación esté configurada para la puesta al día. Para más información, consulte Puesta
al día de las aplicaciones dependientes de la plataforma.
El siguiente comando genera un archivo binario multiplataforma:

T IP O SDK 2. 1 SDK 3. X C O M A N DO

archivo binario ️
✔ ️
✔ dotnet publish
multiplataforma
dependiente de la
plataforma.

Publicación como dependiente de la plataforma


Las aplicaciones publicadas como dependientes de la plataforma son multiplataforma y no incluyen el
entorno de ejecución de .NET Core. El usuario de la aplicación debe instalar el entorno de ejecución de .NET
Core.
La publicación de una aplicación como dependiente de la plataforma genera un archivo binario
multiplataforma como archivo .dll y un archivo ejecutable específico de la plataforma que tiene como
destino la plataforma actual. El archivo .dll es multiplataforma, pero el ejecutable no lo es. Por ejemplo, si
publica una aplicación denominada lector_de_palabras y el destino es Windows, se creará un archivo
ejecutable lector_de_palabras.exe junto con lector_de_palabras.dll. Si el destino es Linux o macOS, se creará
un archivo ejecutable lector_de_palabras junto con lector_de_palabras.dll. Para obtener más información
sobre los RID, vea el Catálogo de identificadores de entorno de ejecución (RID) de .NET Core.

IMPORTANT
El SDK 2.1 de .NET Core no genera archivos ejecutables específicos de la plataforma al publicar una aplicación
dependiente de la plataforma.

El archivo binario multiplataforma de la aplicación se puede ejecutar con el comando


dotnet <filename.dll> en cualquier plataforma. Si la aplicación usa un paquete NuGet que tenga
implementaciones específicas de la plataforma, se copiarán todas las dependencias de la plataforma en la
carpeta de publicación junto con la aplicación.
Puede crear un archivo ejecutable para una plataforma específica pasando los parámetros
-r <RID> --self-contained false al comando dotnet publish . Si se omite el parámetro -r , se creará un
archivo ejecutable para la plataforma actual. Los paquetes NuGet que tengan dependencias específicas de la
plataforma para la plataforma de destino se copiarán en la carpeta de publicación.
Ventajas
Implementación de pequeño tamaño
En este caso solo se distribuyen la aplicación y sus dependencias. El usuario instala el entorno de
ejecución y las bibliotecas de .NET Core, y todas las aplicaciones comparten ese entorno de
ejecución.
Multiplataforma
La aplicación y cualquier biblioteca basada en .NET se ejecuta en otros sistemas operativos. No
necesita definir una plataforma de destino para la aplicación. Para obtener más información sobre el
formato de archivo de .NET, consulte Formato de archivo de ensamblado .NET.
Uso del entorno de ejecución revisado más reciente
La aplicación usa el entorno de ejecución más reciente (dentro de la familia principal y secundaria de
.NET Core de destino) instalada en el sistema de destino. De esta forma, la aplicación utilizará
automáticamente la versión revisada más reciente del entorno de ejecución de .NET Core. Este
comportamiento predeterminado se puede anular. Para más información, consulte Puesta al día de
las aplicaciones dependientes de la plataforma.
Desventajas
Preinstalación necesaria del entorno de ejecución
Su aplicación solo se puede ejecutar si la versión de .NET Core de destino de la aplicación ya está
instalada en el sistema host. Puede configurar el comportamiento de puesta al día para que la
aplicación requiera una versión específica de .NET Core o permita el uso de una versión más reciente
de .NET Core. Para más información, consulte Puesta al día de las aplicaciones dependientes de la
plataforma.
Cambios posibles de .NET Core
Es posible actualizar el entorno de ejecución y las bibliotecas de .NET Core en el equipo donde se
ejecuta la aplicación. En raras ocasiones, esto puede cambiar el comportamiento de la aplicación si
usa las bibliotecas de .NET Core, lo que sucede con la mayoría de las aplicaciones. Puede configurar
de qué modo la aplicación usa versiones más recientes de .NET Core. Para más información, consulte
Puesta al día de las aplicaciones dependientes de la plataforma.
La desventaja siguiente solo se aplica al SDK 2.1 de .NET Core.
Uso del comando dotnet para iniciar la aplicación
Los usuarios deben ejecutar el comando dotnet <filename.dll> para iniciar la aplicación. El SDK 2.1 de
.NET Core no genera archivos ejecutables específicos de la plataforma para las aplicaciones que se
publiquen como dependientes de la plataforma.
Ejemplos
Sirve para publicar una aplicación multiplataforma dependiente de la plataforma. Se creará un archivo
ejecutable que tendrá como destino la plataforma actual junto con el archivo .dll.

dotnet publish

Sirve para publicar una aplicación multiplataforma dependiente de la plataforma. Se creará un ejecutable
de 64 bits de Linux junto con el archivo .dll. Este comando no funciona con .NET Core SDK 2.1.

dotnet publish -r linux-x64 --self-contained false

Publicación como independiente


La publicación de la aplicación como independiente genera un archivo ejecutable específico de la
plataforma. La carpeta de publicación de salida contiene todos los componentes de la aplicación, incluidas
las bibliotecas y el entorno de ejecución de destino de .NET Core. La aplicación está aislada de otras
aplicaciones de .NET Core y no usa un entorno de ejecución compartido instalado localmente. No es
necesario que el usuario de la aplicación descargue e instale .NET Core.
El archivo binario ejecutable se genera para la plataforma de destino especificada. Por ejemplo, si tiene una
aplicación denominada lector_de_palabras y publica un archivo ejecutable independiente para Windows,
se creará el archivo lector_de_palabras.exe. Si se publica para Linux o macOS, se creará el archivo
lector_de_palabras. La plataforma y la arquitectura de destino se especifican con el parámetro -r <RID>
para el comando dotnet publish . Para obtener más información sobre los RID, vea el Catálogo de
identificadores de entorno de ejecución (RID) de .NET Core.
Si la aplicación tiene dependencias específicas de la plataforma, como un paquete NuGet que contenga
tales dependencias, se copiarán en la carpeta de publicación junto con la aplicación.
Ventajas
Control de la versión de .NET Core
Puede controlar qué versión de .NET Core se implementa con la aplicación.
Destinatarios específicos de la plataforma
Como tiene que publicar la aplicación para cada plataforma, sabe dónde se ejecutará. Si .NET Core
introduce una nueva plataforma, los usuarios no podrán ejecutar la aplicación en ella hasta que
publique una versión que tenga esa plataforma como destino. Puede probar la aplicación para
buscar problemas de compatibilidad antes de que los usuarios la ejecuten en la nueva plataforma.
Desventajas
Implementaciones de mayor tamaño
Dado que la aplicación incluye el entorno de ejecución de .NET Core y todas las dependencias de la
aplicación, el tamaño de descarga y el espacio de disco duro necesario es mayor que en el caso de
una versión dependiente de la plataforma.
TIP
Puede reducir el tamaño de la implementación en sistemas Linux en 28 MB aproximadamente con el modo
invariable global de .NET Core. Esto obliga a la aplicación a tratar todas las referencias culturales como la
referencia cultural invariable.

TIP
Hay una característica de recorte en versión preliminar que puede ayudar a reducir todavía más el tamaño de
su desarrollo.

Mayor dificultad para actualizar la versión de .NET Core


El entorno de ejecución de .NET Core (distribuido con la aplicación) solo se puede actualizar
mediante la publicación de una nueva versión de la aplicación. Sin embargo, .NET Core actualizará
las revisiones de seguridad críticas según sea necesario para la biblioteca de marcos en la máquina
en la que se ejecuta la aplicación. Usted es responsable de la validación de un extremo a otro del
escenario de esta revisión de seguridad.
Ejemplos
Sirve para publicar una aplicación independiente. Se creará un archivo ejecutable de macOS de 64 bits.

dotnet publish -r osx-x64

Sirve para publicar una aplicación independiente. Se creará un archivo ejecutable de Windows de 64 bits.

dotnet publish -r win-x64

Vea también
Implementación de aplicaciones de .NET Core con la CLI de .NET Core
Implementación de aplicaciones de .NET Core con Visual Studio
Catálogo de identificadores de entorno de ejecución (RID) de .NET Core.
Selección de la versión de .NET Core que se va a usar
Implementación de aplicaciones de .NET Core con
Visual Studio
16/09/2020 • 29 minutes to read • Edit Online

Puede implementar una aplicación de .NET Core como una implementación dependiente de la plataforma, que
incluye los archivos binarios de la aplicación pero depende de la presencia de .NET Core en el sistema de destino,
o como una implementación independiente, que incluye la aplicación y los archivos binarios de .NET Core. Para
obtener información general sobre la implementación de aplicaciones de NET Core, vea Implementación de
aplicaciones .NET Core.
En las secciones siguientes se muestra cómo usar Microsoft Visual Studio para crear los siguientes tipos de
implementaciones:
Implementación dependiente de marco de trabajo
Implementación dependiente de marco de trabajo con dependencias de terceros
Implementación autocontenida
Implementación autocontenida con dependencias de terceros
Para obtener información sobre el uso de Visual Studio para desarrollar aplicaciones de .NET Core, vea
Dependencias y requisitos de .NET Core .

Implementación dependiente de marco de trabajo


La implementación de una implementación dependiente del marco sin dependencias de terceros implica
simplemente la compilación, la prueba y la publicación de la aplicación. Un sencillo ejemplo escrito en C# ilustra el
proceso.
1. Crear el proyecto.
Seleccione Archivo > Nuevo > Proyecto . En el cuadro de diálogo Nuevo proyecto , expanda las
categorías de proyecto del lenguaje (C# o Visual Basic) en el panel de tipos de proyecto instalados , elija
.NET Core y luego seleccione la plantilla Aplicación de consola (.NET Core) en el panel central. Escriba
un nombre de proyecto, como "FDD", en el cuadro de texto Nombre . Seleccione el botón Aceptar .
2. Agregar el código fuente de la aplicación.
Abra el archivo Program.cs o Program.vb en el editor y reemplace el código generado automáticamente
por el siguiente. Pide al usuario que escriba texto y muestra las palabras individuales escritas por el
usuario. Se usa la expresión regular \w+ para separar las palabras en el texto de entrada.
using System;
using System.Text.RegularExpressions;

namespace Applications.ConsoleApps
{
public class ConsoleParser
{
public static void Main()
{
Console.WriteLine("Enter any text, followed by <Enter>:\n");
String s = Console.ReadLine();
ShowWords(s);
Console.Write("\nPress any key to continue... ");
Console.ReadKey();
}

private static void ShowWords(String s)


{
String pattern = @"\w+";
var matches = Regex.Matches(s, pattern);
if (matches.Count == 0)
{
Console.WriteLine("\nNo words were identified in your input.");
}
else
{
Console.WriteLine($"\nThere are {matches.Count} words in your string:");
for (int ctr = 0; ctr < matches.Count; ctr++)
{
Console.WriteLine($" #{ctr,2}: '{matches[ctr].Value}' at position
{matches[ctr].Index}");
}
}
}
}
}
Imports System.Text.RegularExpressions

Namespace Applications.ConsoleApps
Public Module ConsoleParser
Public Sub Main()
Console.WriteLine("Enter any text, followed by <Enter>:")
Console.WriteLine()
Dim s = Console.ReadLine()
ShowWords(s)
Console.Write($"{vbCrLf}Press any key to continue... ")
Console.ReadKey()
End Sub

Private Sub ShowWords(s As String)


Dim pattern = "\w+"
Dim matches = Regex.Matches(s, pattern)
Console.WriteLine()
If matches.Count = 0 Then
Console.WriteLine("No words were identified in your input.")
Else
Console.WriteLine($"There are {matches.Count} words in your string:")
For ctr = 0 To matches.Count - 1
Console.WriteLine($" #{ctr,2}: '{matches(ctr).Value}' at position
{matches(ctr).Index}")
Next
End If
Console.WriteLine()
End Sub
End Module
End Namespace

3. Crear una versión de depuración de la aplicación.


Seleccione Compilar > Compilar solución . También puede compilar y ejecutar la versión de depuración
de la aplicación seleccionando Depurar > Iniciar depuración .
4. Implementar la aplicación.
Después de depurar y probar el programa, cree los archivos que se implementarán con la aplicación. Para
publicar desde Visual Studio, siga estos pasos:
a. Cambie la configuración de la solución de Depurar a Versión en la barra de herramientas para
compilar una compilación de versión (en lugar de depuración) de la aplicación.
b. Haga clic con el botón derecho en el proyecto (no en la solución) en el Explorador de soluciones
y seleccione Publicar .
c. En la pestaña Publicar , seleccione Publicar . Visual Studio escribe los archivos que componen la
aplicación en el sistema de archivos local.
d. La pestaña Publicar muestra ahora un solo perfil, FolderProfile . Los valores de configuración del
perfil se muestran en la sección Resumen de la pestaña.
Los archivos resultantes se colocan en un directorio denominado Publish en Windows y publish en
sistemas Unix que está en un subdirectorio del subdirectorio .\bin\release\netcoreapp2.1 del proyecto.
Junto con los archivos de la aplicación, el proceso de publicación emite un archivo de base de datos de programa
(.pdb) que contiene información de depuración sobre la aplicación. El archivo es útil principalmente para depurar
excepciones. Puede decidir no empaquetarlo con los archivos de la aplicación. Pero se debe guardar en caso de
que se quiera depurar la compilación de versión de la aplicación.
Implemente el conjunto completo de archivos de la aplicación del modo que quiera. Por ejemplo, puede
empaquetarlos en un archivo ZIP, usar un simple comando copy o implementarlos con el paquete de instalación
que prefiera. Una vez instalados, los usuarios pueden ejecutar la aplicación mediante el comando dotnet y
proporcionando el nombre de archivo, como dotnet fdd.dll .
Además de los archivos binarios de la aplicación, el instalador también debe empaquetar el instalador de marco
compartido o buscarlo como requisito previo como parte de la instalación de la aplicación. La instalación del
marco compartido requiere acceso raíz o de administrador dado que implica a toda la máquina.

Implementación dependiente de marco de trabajo con dependencias


de terceros
La implementación de una implementación dependiente de la plataforma con una o más dependencias de
terceros requiere que todas las dependencias estén disponibles para el proyecto. Se requieren los pasos
adicionales siguientes antes de poder compilar la aplicación:
1. Use el Administrador de paquetes NuGet para agregar una referencia a un paquete de NuGet para el
proyecto y, si el paquete todavía no está disponible en el sistema, instálelo. Para abrir el administrador de
paquetes, seleccione Herramientas > Administrador de paquetes NuGet > Administrar paquetes
NuGet para la solución .
2. Confirme que las dependencias de terceros (por ejemplo, Newtonsoft.Json ) están instaladas en el sistema
y, si no es así, instálelas. La pestaña Instalado enumera los paquetes de NuGet instalados en el sistema. Si
Newtonsoft.Json no aparece ahí, seleccione la pestaña Examinar y escriba "Newtonsoft.Json" en el cuadro
de búsqueda. Seleccione Newtonsoft.Json y, en el panel derecho, seleccione el proyecto antes de hacer clic
en Instalar .
3. Si Newtonsoft.Json ya está instalado en el sistema, agréguelo al proyecto seleccionando el proyecto en el
panel derecho de la pestaña Administrar paquetes para la solución .
Una implementación dependiente del marco con dependencias de terceros solo es tan portátil como sus
dependencias de terceros. Por ejemplo, si una biblioteca de terceros solo admite macOS, la aplicación no se puede
portar a sistemas Windows. Esto ocurre si la dependencia de terceros propiamente dicha depende del código
nativo. Un buen ejemplo de esto es el servidor Kestrel, que requiere una dependencia nativa de libuv. Cuando se
crea una FDD para una aplicación con esta clase de dependencia de terceros, el resultado publicado contiene una
carpeta para cada identificador en tiempo de ejecución (RID) que admita la dependencia nativa (y que exista en su
paquete de NuGet).

Implementación independiente sin dependencias de terceros


La implementación de una implementación independiente sin dependencias de terceros implica crear el proyecto,
modificar el archivo csproj y compilar, probar y publicar la aplicación. Un sencillo ejemplo escrito en C# ilustra el
proceso. Empiece por crear, codificar y probar el proyecto, como haría en el caso de una implementación
dependiente del marco de trabajo:
1. Crear el proyecto.
Seleccione Archivo > Nuevo > Proyecto . En el cuadro de diálogo Nuevo proyecto , expanda las
categorías de proyecto del lenguaje (C# o Visual Basic) en el panel de tipos de proyecto instalados , elija
.NET Core y luego seleccione la plantilla Aplicación de consola (.NET Core) en el panel central. Escriba
un nombre de proyecto, como "SCD", en el cuadro de texto Nombre y pulse el botón Aceptar .
2. Agregar el código fuente de la aplicación.
Abra el archivo Program.cs o Program.vb en el editor y reemplace el código generado automáticamente
por el siguiente. Pide al usuario que escriba texto y muestra las palabras individuales escritas por el
usuario. Se usa la expresión regular \w+ para separar las palabras en el texto de entrada.

using System;
using System.Text.RegularExpressions;

namespace Applications.ConsoleApps
{
public class ConsoleParser
{
public static void Main()
{
Console.WriteLine("Enter any text, followed by <Enter>:\n");
String s = Console.ReadLine();
ShowWords(s);
Console.Write("\nPress any key to continue... ");
Console.ReadKey();
}

private static void ShowWords(String s)


{
String pattern = @"\w+";
var matches = Regex.Matches(s, pattern);
if (matches.Count == 0)
{
Console.WriteLine("\nNo words were identified in your input.");
}
else
{
Console.WriteLine($"\nThere are {matches.Count} words in your string:");
for (int ctr = 0; ctr < matches.Count; ctr++)
{
Console.WriteLine($" #{ctr,2}: '{matches[ctr].Value}' at position
{matches[ctr].Index}");
}
}
}
}
}
Imports System.Text.RegularExpressions

Namespace Applications.ConsoleApps
Public Module ConsoleParser
Public Sub Main()
Console.WriteLine("Enter any text, followed by <Enter>:")
Console.WriteLine()
Dim s = Console.ReadLine()
ShowWords(s)
Console.Write($"{vbCrLf}Press any key to continue... ")
Console.ReadKey()
End Sub

Private Sub ShowWords(s As String)


Dim pattern = "\w+"
Dim matches = Regex.Matches(s, pattern)
Console.WriteLine()
If matches.Count = 0 Then
Console.WriteLine("No words were identified in your input.")
Else
Console.WriteLine($"There are {matches.Count} words in your string:")
For ctr = 0 To matches.Count - 1
Console.WriteLine($" #{ctr,2}: '{matches(ctr).Value}' at position
{matches(ctr).Index}")
Next
End If
Console.WriteLine()
End Sub
End Module
End Namespace

3. Determine si quiere usar el modo de globalización invariable.


Especialmente si la aplicación es para Linux, puede reducir el tamaño total de la implementación si
aprovecha las ventajas del modo de globalización invariable. El modo de globalización invariable es útil
para aquellas aplicaciones que no son globales y que pueden usar las convenciones de formato, las
convenciones de mayúsculas y minúsculas y el criterio de ordenación y la comparación de cadenas de la
referencia cultural invariable.
Para habilitar el modo invariable, haga clic con el botón derecho en el proyecto (no en la solución) en el
Explorador de soluciones y seleccione Editar SCD.csproj o Editar SCD.vbproj . Luego agregue las
siguientes líneas resaltadas al archivo:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<RuntimeHostConfigurationOption Include="System.Globalization.Invariant" Value="true" />
</ItemGroup>

</Project>

4. Cree una compilación de depuración de la aplicación.


Seleccione Compilar > Compilar solución . También puede compilar y ejecutar la versión de depuración
de la aplicación seleccionando Depurar > Iniciar depuración . Este paso de depuración permite
identificar problemas con la aplicación cuando se ejecuta en la plataforma de host. Pero, aun así, tiene que
probarla en cada una de las plataformas de destino.
Si ha habilitado el modo de globalización invariable, asegúrese especialmente de probar si la ausencia de
datos dependientes de la referencia cultural es adecuada para la aplicación.
Una vez que haya terminado de depurar, puede publicar la implementación independiente:
Visual Studio 15.6 y anterior
Visual Studio 15.7 y posterior
Después de depurar y probar el programa, cree los archivos que se implementarán con la aplicación para cada
una de las plataformas de destino.
Para publicar la aplicación desde Visual Studio, siga estos pasos:
1. Definir las plataformas de destino de la aplicación.
a. Haga clic con el botón derecho en el proyecto (no en la solución) en el Explorador de soluciones
y seleccione Editar SCD.csproj .
b. Cree una etiqueta <RuntimeIdentifiers> en la sección <PropertyGroup> del archivo csproj que defina
las plataformas a las que se destina la aplicación y especifique el identificador en tiempo de
ejecución (RID) de cada una. También debe agregar un punto y coma para separar los RID. Para ver
una lista de identificadores de tiempo de ejecución, consulte el catálogo de identificadores de tiempo
de ejecución.
Por ejemplo, el ejemplo siguiente indica que la aplicación se ejecuta en sistemas operativos Windows 10 de
64 bits y OS X versión 10.11 de 64 bits.

<PropertyGroup>
<RuntimeIdentifiers>win10-x64;osx.10.11-x64</RuntimeIdentifiers>
</PropertyGroup>

El elemento <RuntimeIdentifiers> puede ir en cualquier <PropertyGroup> que tenga en el archivo csproj.


Más adelante en esta sección aparece un ejemplo completo del archivo csproj.
2. Publique la aplicación.
Después de depurar y probar el programa, cree los archivos que se implementarán con la aplicación para
cada una de las plataformas de destino.
Para publicar la aplicación desde Visual Studio, siga estos pasos:
a. Cambie la configuración de la solución de Depurar a Versión en la barra de herramientas para
compilar una compilación de versión (en lugar de depuración) de la aplicación.
b. Haga clic con el botón derecho en el proyecto (no en la solución) en el Explorador de soluciones
y seleccione Publicar .
c. En la pestaña Publicar , seleccione Publicar . Visual Studio escribe los archivos que componen la
aplicación en el sistema de archivos local.
d. La pestaña Publicar muestra ahora un solo perfil, FolderProfile . Los valores de configuración del
perfil se muestran en la sección Resumen de la pestaña. Tiempo de ejecución de destino
identifica qué tiempo de ejecución se ha publicado, y Ubicación de destino identifica dónde se
escriben los archivos de la implementación independiente.
e. De forma predeterminada, Visual Studio escribe todos los archivos publicados en un único
directorio. Para mayor comodidad, es mejor crear perfiles distintos para cada tiempo de ejecución de
destino y colocar los archivos publicados en un directorio específico de la plataforma. Esto implica la
creación de un perfil de publicación independiente para cada plataforma de destino. Por tanto,
vuelva a compilar la aplicación para cada plataforma siguiendo estos pasos:
a. Seleccione Crear nuevo perfil en el cuadro de diálogo Publicar .
b. En el cuadro de diálogo Elegir un destino de publicación , cambie la ubicación Elegir una
carpeta a bin\Release\PublishOutput\win10-x64. Seleccione Aceptar .
c. Seleccione el nuevo perfil (FolderProfile1 ) en la lista de perfiles y asegúrese de que el
Tiempo de ejecución de destino es win10-x64 . Si no lo es, seleccione Configuración . En
el cuadro de diálogo Configuración de perfil , cambie Tiempo de ejecución de destino
por win10-x64 y haga clic en Guardar . En caso contrario, haga clic en Cancelar .
d. Seleccione Publicar para publicar la aplicación para las plataformas de 64 bits de Windows
10.
e. Siga los pasos anteriores de nuevo para crear un perfil para la plataforma osx.10.11-x64 . La
Ubicación de destino es bin\Release\PublishOutput\osx.10.11-x64 y el Tiempo de
ejecución de destino es osx.10.11-x64 . El nombre que Visual Studio asigna a este perfil es
FolderProfile2 .
Cada ubicación de destino contiene el conjunto completo de archivos (los archivos de aplicación y todos los
archivos de .NET Core) necesarios para iniciar la aplicación.
Junto con los archivos de la aplicación, el proceso de publicación emite un archivo de base de datos de programa
(.pdb) que contiene información de depuración sobre la aplicación. El archivo es útil principalmente para depurar
excepciones. Puede decidir no empaquetarlo con los archivos de la aplicación. Pero se debe guardar en caso de
que se quiera depurar la compilación de versión de la aplicación.
Implemente los archivos publicados de la forma que quiera. Por ejemplo, puede empaquetarlos en un archivo ZIP,
usar un simple comando copy o implementarlos con el paquete de instalación que prefiera.
A continuación se muestra el archivo csproj completo para este proyecto.

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeIdentifiers>win10-x64;osx.10.11-x64</RuntimeIdentifiers>
</PropertyGroup>
</Project>

Implementación autocontenida con dependencias de terceros


Implementar una implementación independiente con una o varias dependencias de terceros implica agregar las
dependencias. Se requieren los pasos adicionales siguientes antes de poder compilar la aplicación:
1. Use el Administrador de paquetes NuGet para agregar una referencia a un paquete de NuGet para el
proyecto y, si el paquete todavía no está disponible en el sistema, instálelo. Para abrir el administrador de
paquetes, seleccione Herramientas > Administrador de paquetes NuGet > Administrar paquetes
NuGet para la solución .
2. Confirme que las dependencias de terceros (por ejemplo, Newtonsoft.Json ) están instaladas en el sistema
y, si no es así, instálelas. La pestaña Instalado enumera los paquetes de NuGet instalados en el sistema. Si
Newtonsoft.Json no aparece ahí, seleccione la pestaña Examinar y escriba "Newtonsoft.Json" en el cuadro
de búsqueda. Seleccione Newtonsoft.Json y, en el panel derecho, seleccione el proyecto antes de hacer clic
en Instalar .
3. Si Newtonsoft.Json ya está instalado en el sistema, agréguelo al proyecto seleccionando el proyecto en el
panel derecho de la pestaña Administrar paquetes para la solución .
A continuación se muestra el archivo csproj completo de este proyecto:
Visual Studio 15.6 y anterior
Visual Studio 15.7 y posterior

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeIdentifiers>win10-x64;osx.10.11-x64</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
</ItemGroup>
</Project>

Al implementar la aplicación, los archivos de aplicación también contienen las dependencias de terceros usadas en
la aplicación. No se requieren las bibliotecas de terceros en el sistema en el que se ejecuta la aplicación.
Solo puede implementar una implementación autocontenida con una biblioteca de terceros en plataformas
compatibles con esa biblioteca. Esto es similar a tener dependencias de terceros con dependencias nativas en la
implementación dependiente de la plataforma, donde las dependencias nativas no existirán en la plataforma de
destino a menos que se hayan instalado allí previamente.

Vea también
Implementación de aplicaciones .NET Core
Catálogo de identificadores de entorno de ejecución (RID) de .NET Core
Publicación de aplicaciones .NET Core con la CLI
de .NET Core
16/09/2020 • 16 minutes to read • Edit Online

En este artículo se muestra cómo se puede publicar la aplicación .NET Core desde la línea de comandos. .NET
core proporciona tres maneras de publicar las aplicaciones. La implementación dependiente del marco de
trabajo genera un archivo .dll multiplataforma que usa el runtime de .NET Core instalado localmente. La
implementación dependiente del marco de trabajo genera un archivo ejecutable específico de la plataforma que
usa el runtime de .NET Core instalado localmente. El archivo ejecutable independiente genera un archivo
ejecutable específico de la plataforma e incluye una copia local del runtime de .NET Core.
Para obtener información general sobre estos modos de publicación, vea Implementación de aplicaciones .NET
Core.
¿Busca ayuda rápida sobre el uso de la CLI? En la tabla siguiente se muestran algunos ejemplos de cómo
publicar la aplicación. La plataforma de destino se puede especificar con el parámetro -f <TFM> o si edita el
archivo de proyecto. Para más información, vea Conceptos básicos de publicación.

M O DO DE P UB L IC A C IÓ N VERSIÓ N DEL SDK C O M A N DO

Implementación dependiente de 2.x dotnet publish -c Release


marco de trabajo

Archivo ejecutable dependiente de la 2.2 dotnet publish -c Release -r


plataforma <RID> --self-contained false

3.0 dotnet publish -c Release -r


<RID> --self-contained false

3.0* dotnet publish -c Release

Implementación autocontenida 2.1 dotnet publish -c Release -r


<RID> --self-contained true

2.2 dotnet publish -c Release -r


<RID> --self-contained true

3.0 dotnet publish -c Release -r


<RID> --self-contained true

* Si se usa el archivo ejecutable dependiente del marco de la versión 3.0 del SDK, se trata del modo de
publicación predeterminado cuando se ejecuta el comando dotnet publish básico. Esto solo se aplica cuando
el proyecto tiene como destino .NET Core 2.1 o .NET Core 3.0 .

Conceptos básicos de publicación


El valor <TargetFramework> del archivo de proyecto especifica la plataforma de destino predeterminada al
publicar la aplicación. Se puede cambiar la plataforma de destino a cualquier moniker de la plataforma de
destino (TFM). Por ejemplo, si en el proyecto se usa <TargetFramework>netcoreapp2.2</TargetFramework> , se crea
un archivo binario que tiene como destino .NET Core 2.2. El TFM especificado en esta configuración es el
destino predeterminado que usa el comando dotnet publish .
Si quiere tener como destino más de una plataforma, puede establecer el valor <TargetFrameworks> en más de
un valor de TFM separados por punto y coma. Puede publicar una de las plataformas con el comando
dotnet publish -f <TFM> . Por ejemplo, si tiene
<TargetFrameworks>netcoreapp2.1;netcoreapp2.2</TargetFrameworks> y ejecuta dotnet publish -f netcoreapp2.1 ,
se crea un archivo binario que tiene como destino .NET Core 2.1.
A menos que se establezca otro, el directorio de salida del comando dotnet publish es
./bin/<BUILD-CONFIGURATION>/<TFM>/publish/ . El modo BUILD-CONFIGURATION predeterminado es Depurar
a menos que se cambie con el parámetro -c . Por ejemplo, dotnet publish -c Release -f netcoreapp2.1 publica
en myfolder/bin/Release/netcoreapp2.1/publish/ .
Si usa el SDK 3.0 de .NET Core o versiones posteriores, el modo de publicación predeterminado para las
aplicaciones destinadas a las versiones 2.1, 2.2, 3.0 o una versión posterior de .NET Core es el ejecutable
dependiente del marco.
Si usa el SDK 2.1 de .NET Core, el modo de publicación predeterminado para las aplicaciones destinadas a las
versiones 2.1 y 2.2 de .NET Core es la implementación dependiente del marco.
Dependencias nativas
Si la aplicación tiene dependencias nativas, es posible que no funcione en otro sistema operativo. Por ejemplo,
si en la aplicación se usa la API Windows nativa, no se ejecutará en macOS o Linux. Tendrá que proporcionar
código específico de la plataforma y compilar un archivo ejecutable para cada plataforma.
Tenga en cuenta también que, si una biblioteca a la que se hace referencia tiene una dependencia nativa, es
posible que la aplicación no se ejecute en todas las plataformas. Pero es posible que un paquete NuGet al que
se hace referencia incluya versiones específicas de la plataforma para controlar de forma automática las
dependencias nativas necesarias.
Al distribuir una aplicación con dependencias nativas, es posible que tenga que usar el modificador
dotnet publish -r <RID> para especificar la plataforma de destino para la que se quiere publicar. Para obtener
una lista de identificadores de tiempo de ejecución, vea Catálogo de identificadores de entorno de ejecución
(RID) de .NET Core.
En las secciones Archivo ejecutable dependiente de la plataforma e Implementación autocontenida se ofrece
más información sobre los archivos binarios específicos de la plataforma.

Aplicación de ejemplo
Puede usar la siguiente aplicación para explorar los comandos de publicación. La aplicación se crea mediante la
ejecución de los comandos siguientes en el terminal:

mkdir apptest1
cd apptest1
dotnet new console
dotnet add package Figgle

Es necesario cambiar el archivo Program.cs o Program.vb que genera la plantilla de consola por lo siguiente:
using System;

namespace apptest1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Figgle.FiggleFonts.Standard.Render("Hello, World!"));
}
}
}

Module Program
Sub Main(args As String())
Console.WriteLine(Figgle.FiggleFonts.Standard.Render("Hello, World!"))
End Sub
End Module

Al ejecutar la aplicación ( dotnet run ), se muestra el resultado siguiente:

_ _ _ _ __ __ _ _ _
| | | | ___| | | ___ \ \ / /__ _ __| | __| | |
| |_| |/ _ \ | |/ _ \ \ \ /\ / / _ \| '__| |/ _` | |
| _ | __/ | | (_) | \ V V / (_) | | | | (_| |_|
|_| |_|\___|_|_|\___( ) \_/\_/ \___/|_| |_|\__,_(_)
|/

Implementación dependiente de marco de trabajo


Para la CLI del SDK de .NET Core 2.x, la implementación dependiente de la plataforma (FDD) es el modo
predeterminado para el comando dotnet publish básico.
Cuando la aplicación se publica como una FDD, se crea un archivo <PROJECT-NAME>.dll en la carpeta
./bin/<BUILD-CONFIGURATION>/<TFM>/publish/ . Para ejecutar la aplicación, vaya a la carpeta de salida y use el
comando dotnet <PROJECT-NAME>.dll .
La aplicación está configurada para tener como destino una versión específica de .NET Core. Es obligatorio que
ese runtime de .NET Core de destino esté en cualquier equipo donde se ejecute la aplicación. Por ejemplo, si la
aplicación tiene como destino .NET Core 2.2, todos los equipos en los que se ejecute la aplicación deben tener
instalado el runtime de .NET Core 2.2. Como se indica en la sección Conceptos básicos de publicación, se puede
editar el archivo de proyecto para cambiar la plataforma de destino predeterminada o seleccionar más de una
como destino.
Al publicar una FDD se crea una aplicación que realiza la puesta al día automática a la revisión de seguridad de
.NET Core más reciente disponible en el sistema en el que se ejecuta la aplicación. Para más información sobre
el enlace de versión en tiempo de compilación, vea Selección de la versión de .NET Core que se va a usar.

Archivo ejecutable dependiente de la plataforma


Para la CLI del SDK de .NET Core 3.x, el archivo ejecutable dependiente de la plataforma (FDE) es el modo
predeterminado para el comando dotnet publish básico. No es necesario especificar ningún otro parámetro,
siempre que se quiera establecer como destino el sistema operativo actual.
En este modo, se crea un host ejecutable específico de la plataforma para hospedar la aplicación
multiplataforma. Este modo es similar a FDD, ya que requiere un host en forma del comando dotnet . El
nombre de archivo ejecutable de host varía según la plataforma y es similar a <PROJECT-FILE>.exe . Este archivo
ejecutable se puede ejecutar directamente en lugar de llamar a dotnet <PROJECT-FILE>.dll , que sigue siendo
una forma aceptable de ejecutar la aplicación.
La aplicación está configurada para tener como destino una versión específica de .NET Core. Es obligatorio que
ese runtime de .NET Core de destino esté en cualquier equipo donde se ejecute la aplicación. Por ejemplo, si la
aplicación tiene como destino .NET Core 2.2, todos los equipos en los que se ejecute la aplicación deben tener
instalado el runtime de .NET Core 2.2. Como se indica en la sección Conceptos básicos de publicación, se puede
editar el archivo de proyecto para cambiar la plataforma de destino predeterminada o seleccionar más de una
como destino.
Al publicar un FDE se crea una aplicación que realiza la puesta al día automática a la revisión de seguridad de
.NET Core más reciente disponible en el sistema en el que se ejecuta la aplicación. Para más información sobre
el enlace de versión en tiempo de compilación, vea Selección de la versión de .NET Core que se va a usar.
En el caso de .NET Core 2.2 y versiones anteriores, debe usar los modificadores siguientes con el comando
dotnet publish para publicar un FDE:

-r <RID> Este modificador usa un identificador (RID) para especificar la plataforma de destino. Para
obtener una lista de identificadores de tiempo de ejecución, vea Catálogo de identificadores de entorno
de ejecución (RID) de .NET Core.
--self-contained false Este modificador indica al SDK de .NET Core que cree un archivo ejecutable
como un FDE.
Siempre que se usa el modificador -r , la ruta de acceso de la carpeta de salida cambia a:
./bin/<BUILD-CONFIGURATION>/<TFM>/<RID>/publish/

Si usa la aplicación de ejemplo, ejecute dotnet publish -f netcoreapp2.2 -r win10-x64 --self-contained false .
Este comando crea el archivo ejecutable siguiente: ./bin/Debug/netcoreapp2.2/win10-x64/publish/apptest1.exe

NOTE
Puede reducir el tamaño total de la implementación si habilita el modo de globalización invariable . Este modo es útil
para las aplicaciones que no son globales y que pueden usar las convenciones de formato, las de mayúsculas y
minúsculas, y el criterio de ordenación y la comparación de cadenas de la referencia cultural invariable. Para más
información sobre el modo de globalización invariable y cómo habilitarlo, vea Modo de globalización invariable de
.NET Core.

Implementación autocontenida
Cuando se publica una implementación autocontenida (SCD), el SDK de .NET Core crea un archivo ejecutable
específico de la plataforma. La publicación de una SCD incluye todos los archivos de .NET Core necesarios para
ejecutar la aplicación, pero no incluye las dependencias nativas de .NET Core. Estas dependencias deben estar
presentes en el sistema antes de ejecutar la aplicación.
Al publicar una SCD, se crea una aplicación que no se pone al día a la revisión de seguridad de .NET Core más
reciente disponible. Para más información sobre el enlace de versión en tiempo de compilación, vea Selección
de la versión de .NET Core que se va a usar.
Debe usar los modificadores siguientes con el comando dotnet publish para publicar una SCD:
-r <RID> Este modificador usa un identificador (RID) para especificar la plataforma de destino. Para
obtener una lista de identificadores de tiempo de ejecución, vea Catálogo de identificadores de entorno
de ejecución (RID) de .NET Core.
--self-contained true Este modificador indica al SDK de .NET Core que cree un archivo ejecutable como
una SCD.

NOTE
Puede reducir el tamaño total de la implementación si habilita el modo de globalización invariable . Este modo es útil
para las aplicaciones que no son globales y que pueden usar las convenciones de formato, las de mayúsculas y
minúsculas, y el criterio de ordenación y la comparación de cadenas de la referencia cultural invariable. Para más
información sobre el modo de globalización invariable y cómo habilitarlo, vea Modo de globalización invariable de
.NET Core.

Vea también
Información general sobre la implementación de aplicaciones .NET Core
Catálogo de identificadores de entorno de ejecución (RID) de .NET Core
Creación de un paquete NuGet con la CLI de
.NET Core
18/03/2020 • 4 minutes to read • Edit Online

NOTE
A continuación se muestran ejemplos de línea de comandos mediante Unix. El comando dotnet pack , tal como se muestra
aquí, funciona del mismo modo en Windows.

Está previsto que las bibliotecas .NET Standard y .NET Core se distribuyan como paquetes NuGet. De hecho, es así
como se distribuyen y consumen todas las bibliotecas estándar .NET. Esto se hace más fácil con el comando
dotnet pack .

Imagine que acaba de escribir una nueva biblioteca sorprendente que quiere distribuir a través de NuGet. Puede
crear un paquete NuGet con herramientas multiplataforma para hacer exactamente eso. En el ejemplo siguiente,
hay una biblioteca denominada SuperAwesomeLibrar y con destino a netstandard1.0 .
Si tiene dependencias transitivas; es decir, un proyecto que depende de otro paquete, asegúrese de restaurar los
paquetes para toda la solución con el comando dotnet restore antes de crear un paquete NuGet. Si no lo hace, el
comando dotnet pack no funcionará correctamente.
No es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que necesitan que
se produzca una restauración, como dotnet new , dotnet build , dotnet run , dotnet test , dotnet publish y
dotnet pack . Para deshabilitar la restauración implícita, use la opción --no-restore .

El comando dotnet restore sigue siendo válido en algunos escenarios donde tiene sentido realizar una
restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los sistemas
de compilación que necesitan controlar explícitamente cuándo se produce la restauración.
Para obtener información sobre cómo administrar fuentes de NuGet, vea la documentación de dotnet restore .
Después de asegurarse de que se restauran los paquetes, puede ir hasta el directorio donde reside una biblioteca:

cd src/SuperAwesomeLibrary

Luego, es solo cuestión de un comando desde la línea de comandos:

dotnet pack

La carpeta /bin/Debug tendrá un aspecto similar al siguiente:

$ ls bin/Debug
netstandard1.0/
SuperAwesomeLibrary.1.0.0.nupkg
SuperAwesomeLibrary.1.0.0.symbols.nupkg

Esto genera un paquete que se puede depurar. Si quiere compilar un paquete NuGet con archivos binarios de
versión comercial, todo lo que tiene que hacer es agregar el modificador --configuration (o -c ) y usar release
como argumento.
dotnet pack --configuration release

La carpeta /bin tendrá ahora una carpeta versión que contiene el paquete NuGet con archivos binarios de versión:

$ ls bin/release
netstandard1.0/
SuperAwesomeLibrary.1.0.0.nupkg
SuperAwesomeLibrary.1.0.0.symbols.nupkg

Y ahora tiene los archivos necesarios para publicar un paquete de NuGet.

dotnet pack No confunda dotnet publish con


Es importante tener en cuenta que en ningún momento participa el comando dotnet publish . El comando
dotnet publish se destina a la implementación de aplicaciones con todas sus dependencias en el mismo conjunto,
no a la generación de un paquete NuGet que se distribuya y consuma a través de NuGet.

Vea también
Inicio rápido: Creación y publicación de un paquete
Puesta al día del runtime de implementación
autocontenida
18/03/2020 • 5 minutes to read • Edit Online

Las implementaciones de aplicaciones autocontenidas de .NET Core incluyen las bibliotecas de .NET Core y el
runtime de .NET Core. A partir del SDK de .NET Core 2.1 (versión 2.1.300), una implementación de aplicación
autocontenida publica el runtime con la revisión superior en el equipo. De forma predeterminada, el comando
dotnet publish para una implementación autocontenida selecciona la versión más reciente instalada como parte
del SDK en el equipo de publicación. Esto permite que la aplicación implementada se ejecute con las revisiones de
seguridad (y otras correcciones) disponibles en el momento de usar el comando publish . La aplicación debe
volver a publicarse para obtener una nueva revisión. Para crear una aplicación autocontenida, se especifica
-r <RID> en el comando dotnet publish o se especifica el identificador de runtime (RID) en el archivo de
proyecto (csproj o vbproj) o en la línea de comandos.

Información general sobre la puesta al día de una versión de revisión


restore , build y publish son comandos de dotnet que se pueden ejecutar por separado. La opción de runtime
forma parte de la operación restore , no de publish ni de build . Si se llama a publish , se elegirá la versión de
revisión más reciente. Si se llama a publish con el argumento --no-restore , podría no obtenerse la versión de
revisión deseada porque es posible que un restore anterior no se haya ejecutado con la directiva de publicación
de la nueva aplicación autocontenida. En este caso, se genera un error de compilación con un texto similar al
siguiente:
"El proyecto se restauró usando Microsoft.NETCore.App versión 2.0.0, pero con la configuración actual, la versión
2.0.6 se usará en su lugar. Para resolver este problema, asegúrese de que se usa la misma configuración para la
restauración y para operaciones posteriores tales como compilar o publicar. Normalmente este problema puede
producirse si la propiedad RuntimeIdentifier se establece durante la compilación o publicación, pero no durante la
restauración".

NOTE
restore y build pueden ejecutarse implícitamente como parte de otro comando, como publish . Cuando se ejecutan
implícitamente como parte de otro comando, se les proporciona contexto adicional para que se produzcan los artefactos
correctos. Cuando se usa el comando publish con un runtime (por ejemplo, dotnet publish -r linux-x64 ), la parte
implícita restore restaura los paquetes para el runtime de linux-x64. Si se llama a restore explícitamente, no se
restauran los paquetes de runtime de forma predeterminada, dado que carece del contexto.

Cómo evitar la restauración durante la publicación


Ejecutar restorecomo parte de la operación publish puede no ser adecuado para su escenario. Para evitar
restore durante publish al crear aplicaciones autocontenidas, haga lo siguiente:
Establezca la propiedad RuntimeIdentifiers en una lista separada por punto y coma de todos los RID que se
van a publicar.
Establezca la propiedad TargetLatestRuntimePatch en true .

Argumento no-restore con opciones de dotnet publish


Si quiere crear aplicaciones autocontenidas y aplicaciones dependientes del marco de trabajo con el mismo
archivo de proyecto, y además quiere utilizar el argumento --no-restore con dotnet publish , elija una de las
opciones siguientes:
1. Preferir el comportamiento dependiente del marco de trabajo. Si la aplicación depende del marco de
trabajo, este es el comportamiento predeterminado. Si la aplicación es autocontenida y puede usar un
runtime local 2.1.0 sin revisiones, establezca TargetLatestRuntimePatch en false en el archivo de proyecto.
2. Preferir el comportamiento autocontenido. Si la aplicación es autocontenida, este es el comportamiento
predeterminado. Si la aplicación depende del marco de trabajo y requiere que esté instalada la revisión más
reciente, establezca TargetLatestRuntimePatch en true en el archivo de proyecto.
3. Para tomar el control explícito del marco de trabajo del runtime, establezca RuntimeFrameworkVersion en la
versión de revisión específica en el archivo de proyecto.
Implementación de archivo único y ejecutable
16/09/2020 • 7 minutes to read • Edit Online

La agrupación de todos los archivos dependientes de la aplicación en un único binario proporciona a un


desarrollador de aplicaciones la opción atractiva de implementar y distribuir la aplicación como un único archivo.
Este modelo de implementación está disponible desde .NET Core 3.0 y se ha mejorado en .NET 5.0. Anteriormente
en .NET Core 3.0, cuando un usuario ejecuta la aplicación de archivo único, el host de .NET Core primero extrae
todos los archivos en un directorio temporal antes de ejecutar la aplicación. En .NET 5.0 se mejora esta experiencia
al ejecutar directamente el código sin necesidad de extraer los archivos de la aplicación.
La implementación de archivo único está disponible para el modelo de implementación dependiente del marco y
aplicaciones independientes. El tamaño del archivo único de una aplicación independiente será grande, ya que
incluirá el runtime y las bibliotecas de .NET Framework. La opción de implementación de archivo único se puede
combinar con las opciones de publicación ReadyToRun y Trim (una característica experimental de .NET 5.0).
Hay algunos inconvenientes que debe tener en cuenta sobre el uso de un archivo único; en primer lugar, se utiliza la
información de la ruta de acceso para localizar un archivo en relación con la ubicación de la aplicación. La API
Assembly.Location devolverá una cadena vacía, que es el comportamiento predeterminado de los ensamblados
cargados desde la memoria. El compilador generará una advertencia para esta API durante el tiempo de
compilación para avisar al desarrollador del comportamiento concreto. Si se necesita la ruta de acceso al directorio
de la aplicación, la API AppContext.BaseDirectory devolverá el directorio donde reside AppHost (el propio paquete
de archivo único). Las aplicaciones administradas de C++ no son adecuadas para la implementación de archivo
único y se recomienda escribir aplicaciones en C# para que sea compatibles con un archivo único.
De forma predeterminada, las aplicaciones de archivo único no agrupan bibliotecas nativas. En Linux, se realiza el
enlace previo del runtime a la agrupación y solo se implementan bibliotecas nativas de la aplicación en el mismo
directorio que la aplicación de archivo único. En Windows, solo se realiza el enlace previo del código de hospedaje,
y el runtime y las bibliotecas nativas de la aplicación se implementan en el mismo directorio que la aplicación de
archivo único. Esto permite garantizar una buena experiencia de depuración, que requiere que los archivos nativos
se excluyan del archivo único. Hay una opción para establecer una marca, IncludeNativeLibrariesForSelfExtract , a
fin de incluir bibliotecas nativas en el paquete de archivo único, pero estos archivos se extraerán en un directorio
temporal en el equipo cliente cuando se ejecute la aplicación de archivo único.

Exclusión de archivos de la inserción


Algunos archivos se pueden excluir de forma explícita de su inserción en el único archivo si se establecen los
metadatos siguientes:

<ExcludeFromSingleFile>true</ExcludeFromSingleFile>

Por ejemplo, para colocar algunos archivos en el directorio de publicación pero no empaquetarlos en el archivo
único:

<ItemGroup>
<Content Update="Plugin.dll">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
</ItemGroup>
Publicación de una aplicación de archivo único: CLI
Publique una aplicación de archivo único mediante el comando dotnet publish. Al publicar la aplicación, establezca
las propiedades siguientes:
Publicación para un runtime concreto: -r win-x64
Publicación como archivo único: -p:PublishSingleFile=true
En el ejemplo siguiente se publica una aplicación para Windows como aplicación independiente de archivo único.

dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true

En el ejemplo siguiente se publica una aplicación para Linux como una aplicación de archivo único dependiente del
marco.

dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained false

Para obtener más información, vea Publicación de aplicaciones .NET Core con la CLI de .NET Core.

Publicación de una aplicación de archivo único: Visual Studio


Visual Studio crea perfiles de publicación reutilizables que controlan cómo se publica la aplicación.
1. En el panel Explorador de soluciones , haga clic con el botón derecho en el proyecto que quiera publicar.
Seleccione Publicar .

Si todavía no tiene un perfil de publicación, siga las instrucciones para crear uno y elija el tipo de destino
Carpeta .
2. Elija Editar .
3. En el cuadro de diálogo Configuración de perfil , establezca las opciones siguientes:
Establezca Modo de implementación en Independiente .
Establezca Tiempo de ejecución de destino en la plataforma en la que quiera publicar.
Seleccione Producir un único archivo .
Elija Guardar para guardar la configuración y volver al cuadro de diálogo Publicar .

4. Elija Publicar para publicar la aplicación como un archivo único.


Para obtener más información, vea Publicación de aplicaciones .NET Core con Visual Studio.

Publicación de una aplicación de archivo único: Visual Studio para Mac


Visual Studio para Mac no proporciona opciones para publicar la aplicación como un archivo único. Tendrá que
publicar de forma manual mediante las instrucciones de la sección Publicación de una aplicación de archivo único:
CLI. Para obtener más información, vea Publicación de aplicaciones .NET Core con la CLI de .NET Core.

Vea también
Implementación de aplicaciones .NET Core.
Publicación de aplicaciones .NET Core con la CLI de .NET Core.
Publicación de aplicaciones .NET Core con Visual Studio.
Comando dotnet publish .
Recorte de implementaciones autocontenidas y
ejecutables
16/09/2020 • 8 minutes to read • Edit Online

El modelo de implementación dependiente del marco ha sido el modelo de implementación más eficaz desde el
inicio de .NET. En este escenario, el desarrollador de la aplicación agrupa solo la aplicación y los ensamblados de
terceros con la expectativa de que las bibliotecas del entorno de ejecución y el marco de .NET estén disponibles
en el equipo cliente. Este modelo de implementación sigue siendo el dominante en .NET Core, pero hay algunos
escenarios en los que el modelo dependiente del marco no es óptimo. La alternativa es publicar una aplicación
independiente, donde el entorno de ejecución y el marco de .NET Core están agrupados con la aplicación y
ensamblados de terceros.
El modelo de implementación trim independiente es una versión especializada del modelo de implementación
independiente que está optimizado para reducir el tamaño de la implementación. Minimizar el tamaño de la
implementación es un requisito fundamental para algunos escenarios del lado cliente, como las aplicaciones
Blazor. En función de la complejidad de la aplicación, solo se hace referencia a un subconjunto de ensamblados
de marco, y se requiere un subconjunto del código en cada ensamblado para ejecutar la aplicación. Las partes sin
usar de las bibliotecas no son necesarias y se pueden recortar de la aplicación empaquetada.
No obstante, existe el riesgo de que el análisis del tiempo de compilación de la aplicación pueda causar errores
en tiempo de ejecución, debido a que no puede analizar de forma confiable diversos patrones de código
problemáticos (centrados en gran medida en el uso de la reflexión). Dado que no se puede garantizar la
confiabilidad, este modelo de implementación se ofrece como una característica en versión preliminar.
El motor de análisis en tiempo de compilación proporciona advertencias al desarrollador de los patrones de
código problemáticos para detectar qué otro código es necesario. El código se puede anotar con atributos para
indicar al recortador qué más se debe incluir. Muchos patrones de reflexión se pueden reemplazar por la
generación de código en tiempo de compilación mediante generadores de código fuente.
El modo de recorte de las aplicaciones se configura con TrimMode . El valor predeterminado es copyused y
agrupa los ensamblados a los que se hace referencia con la aplicación. El valor link se usa con las aplicaciones
WebAssembly de Blazor y recorta el código no usado dentro de los ensamblados. Las advertencias de análisis de
recorte proporcionan información sobre patrones de código en los que no es posible realizar análisis de
dependencias completas. Estas advertencias se suprimen de forma predeterminada y se pueden activar
estableciendo la marca SuppressTrimAnalysisWarnings en false . Para obtener más información acerca de las
opciones de recorte disponibles, vea Opciones de recorte.

NOTE
El recorte es una característica experimental en .NET Core 3.1, 5.0 y solo está disponible para las aplicaciones que se
publican independientes.

Impedir que los ensamblados se recorten


Hay escenarios en los que la función de recorte no podrá detectar referencias. Por ejemplo, cuando se usa la
reflexión en un ensamblado en tiempo de ejecución, ya sea por parte de la aplicación o por una biblioteca a la
que haga referencia la aplicación, no se hace referencia directamente al ensamblado. El recorte desconoce estas
referencias indirectas y excluirá la biblioteca de la carpeta publicada.
Cuando el código hace referencia indirectamente a un ensamblado mediante la reflexión, puede evitar que el
ensamblado se recorte con la configuración <TrimmerRootAssembly> . En el ejemplo siguiente se muestra cómo
evitar que un ensamblado llamado System.Security se recorte:

<ItemGroup>
<TrimmerRootAssembly Include="System.Security" />
</ItemGroup>

Recorte de la aplicación: CLI


Recorte la aplicación mediante el comando dotnet publish. Al publicar la aplicación, establezca las propiedades
siguientes:
Publicar como una aplicación independiente para un entorno de ejecución específico: -r win-x64
Habilitar recorte: /p:PublishTrimmed=true
En este ejemplo se publica una aplicación para Windows como independiente y se recorta la salida.

<PropertyGroup>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>

En este ejemplo se publica una aplicación en el modo de recorte agresivo en el que el código no usado dentro de
los ensamblados se desactivará y se habilitarán las advertencias de recorte.

<PropertyGroup>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>

Para obtener más información, vea Publicación de aplicaciones .NET Core con la CLI de .NET Core.

Recorte de la aplicación: Visual Studio


Visual Studio crea perfiles de publicación reutilizables que controlan cómo se publica la aplicación.
1. En el panel Explorador de soluciones , haga clic con el botón derecho en el proyecto que quiera publicar.
Seleccione Publicar... .
Si todavía no tiene un perfil de publicación, siga las instrucciones para crear uno y elija el tipo de destino
Carpeta .
2. Elija Editar .

3. En el cuadro de diálogo Configuración de perfil , establezca las opciones siguientes:


Establezca Modo de implementación en Independiente .
Establezca Tiempo de ejecución de destino en la plataforma en la que quiera publicar.
Seleccione Quitar los ensamblados no usados (en versión preliminar) .
Elija Guardar para guardar la configuración y volver al cuadro de diálogo Publicar .

4. Elija Publicar para publicar la aplicación recortada.


Para obtener más información, vea Publicación de aplicaciones .NET Core con Visual Studio.
Recorte de la aplicación: Visual Studio para Mac
Visual Studio para Mac no proporciona opciones para recortar la aplicación durante la publicación. Tendrá que
publicar de forma manual mediante las instrucciones de la sección Recorte de la aplicación: CLI. Para obtener más
información, vea Publicación de aplicaciones .NET Core con la CLI de .NET Core.

Vea también
Implementación de aplicaciones .NET Core.
Publicación de aplicaciones .NET Core con la CLI de .NET Core.
Publicación de aplicaciones .NET Core con Visual Studio.
Comando dotnet publish.
Almacenamiento de paquetes en tiempo de
ejecución
16/09/2020 • 11 minutes to read • Edit Online

A partir de .NET Core 2.0, es posible empaquetar e implementar aplicaciones en un conjunto conocido de
paquetes que existen en el entorno de destino. Las ventajas son implementaciones más rápidas, menor uso de
espacio en disco y un rendimiento de inicio mejorado en algunos casos.
Esta característica se implementa como un almacenamiento de paquetes en tiempo de ejecución, que es un
directorio en disco donde se almacenan los paquetes (normalmente en /usr/local/share/dotnet/store en
macOS/Linux y C:/Program Files/dotnet/store en Windows). En este directorio, existen subdirectorios para las
arquitecturas y las plataformas de destino. El diseño del archivo es similar a la manera en que los activos de
NuGet se colocan en el disco:

\dotnet
\store
\x64
\netcoreapp2.0
\microsoft.applicationinsights
\microsoft.aspnetcore
...
\x86
\netcoreapp2.0
\microsoft.applicationinsights
\microsoft.aspnetcore
...

Un archivo de manifiesto de destino muestra los paquetes en el almacenamiento de paquetes en tiempo de


ejecución. Los desarrolladores pueden dirigirse a este manifiesto cuando publican su aplicación. Normalmente,
el propietario del entorno de producción de destino proporciona el manifiesto de destino.

Preparación de un entorno en tiempo de ejecución


El administrador de un entorno en tiempo de ejecución puede optimizar aplicaciones para obtener
implementaciones más rápidas y un menor uso de espacio en disco creando un almacenamiento de paquetes en
tiempo de ejecución y el manifiesto de destino correspondiente.
El primer paso consiste en crear un manifiesto de almacenamiento de paquetes que muestra los paquetes que
forman el almacenamiento de paquetes en tiempo de ejecución. El formato de archivo es compatible con el
formato de archivo del proyecto (csproj).

<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="NUGET_PACKAGE" Version="VERSION" />
<!-- Include additional packages here -->
</ItemGroup>
</Project>

Ejemplo
El siguiente manifiesto de almacenamiento de paquetes de ejemplo (packages.csproj) se usa para agregar
Newtonsoft.Json y Moq a un almacenamiento de paquetes en tiempo de ejecución:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="Moq" Version="4.7.63" />
</ItemGroup>
</Project>

Aprovisione el almacenamiento de paquetes en tiempo de ejecución ejecutando dotnet store con el manifiesto
de almacenamiento de paquetes, el runtime y el marco:

dotnet store --manifest <PATH_TO_MANIFEST_FILE> --runtime <RUNTIME_IDENTIFIER> --framework <FRAMEWORK>

Ejemplo

dotnet store --manifest packages.csproj --runtime win10-x64 --framework netcoreapp2.0 --framework-version


2.0.0

Puede pasar varias rutas de manifiesto de almacenamiento de paquetes de destino a un único comando
dotnet store repitiendo la opción y la ruta en el comando.

De manera predeterminada, el resultado del comando es un almacenamiento de paquetes en el subdirectorio


.dotnet/store del perfil de usuario. Puede especificar una ubicación diferente con la opción
--output <OUTPUT_DIRECTORY> . El directorio raíz del almacenamiento contiene un archivo artifact.xml de
manifiesto de destino. Este archivo puede estar disponible para su descarga y usarse por los autores de
aplicaciones que quieran dirigirse a este almacenamiento al realizar la publicación.
Ejemplo
El siguiente archivo artifact.xml se genera después de ejecutar el ejemplo anterior. Tenga en cuenta que
Castle.Core es una dependencia de Moq , de manera que se incluye automáticamente y aparece en el archivo de
manifiesto artifacts.xml.

<StoreArtifacts>
<Package Id="Newtonsoft.Json" Version="10.0.3" />
<Package Id="Castle.Core" Version="4.1.0" />
<Package Id="Moq" Version="4.7.63" />
</StoreArtifacts>

Publicación de una aplicación en un manifiesto de destino


Si tiene un archivo de manifiesto de destino en disco, especifique la ruta al archivo al publicar su aplicación con
el comando dotnet publish :

dotnet publish --manifest <PATH_TO_MANIFEST_FILE>

Ejemplo

dotnet publish --manifest manifest.xml

Implemente la aplicación publicada resultante en un entorno que tenga los paquetes descritos en el manifiesto
de destino. Realizar esto incorrectamente produce errores en el inicio de la aplicación.
Especifique varios manifiestos de destino al publicar una aplicación repitiendo la opción y la ruta (por ejemplo,
--manifest manifest1.xml --manifest manifest2.xml ). Cuando realice esto, la aplicación se reduce para la unión
de los paquetes especificados en los archivos de manifiesto de destino que se proporcionan para el comando.
Si implementa una aplicación con una dependencia de manifiesto que está presente en la implementación (el
ensamblado está presente en la carpeta bin), el almacenamiento de paquetes en tiempo de ejecución no se usa
en el host de ese ensamblado. El ensamblado de la carpeta bin se usa independientemente de su presencia en el
almacenamiento de paquetes en tiempo de ejecución en el host.
La versión de la dependencia indicada en el manifiesto debe coincidir con la versión de la dependencia en el
almacenamiento de paquetes en tiempo de ejecución. Si no coinciden las versiones entre la dependencia del
manifiesto de destino y la versión que existe en el almacenamiento de paquetes en tiempo de ejecución, y la
aplicación no incluye la versión necesaria del paquete en su implementación, se produce un error en el inicio de
la aplicación. La excepción incluye el nombre del manifiesto de destino que se ha llamado para el ensamblado
del almacenamiento de paquetes en tiempo de ejecución, que le ayuda a solucionar los errores de coincidencia.
Cuando la implementación se reduce en su publicación, solo las versiones específicas de los paquetes de
manifiesto que indique se retienen del resultado publicado. Los paquetes de las versiones indicadas deben estar
presentes en el host para que se inicie la aplicación.

Especificar los manifiestos de destino en el archivo del proyecto


Una alternativa a especificar manifiestos de destino con el comando dotnet publish es especificarlos en el
archivo de proyecto como una lista de rutas separadas por punto y coma en una etiqueta
<TargetManifestFiles> .

<PropertyGroup>
<TargetManifestFiles>manifest1.xml;manifest2.xml</TargetManifestFiles>
</PropertyGroup>

Especifique los manifiestos de destino en el archivo de proyecto solo cuando el entorno de destino de la
aplicación sea muy conocido, como para los proyectos de .NET Core. Este no es el caso de los proyectos de
código abierto. Los usuarios de un proyecto de código abierto normalmente lo implementan en diferentes
entornos de producción. Estos entornos de producción generalmente tienen diferentes conjuntos de paquetes
preinstalados. No puede realizar presuposiciones sobre el manifiesto de destino en dichos entornos, por lo que
debe usar la opción --manifest de dotnet publish .

Almacenamiento implícito de ASP.NET Core(solo .NET Core 2.0)


El almacenamiento implícito de ASP.NET Core solo se aplica a ASP.NET Core 2.0. Se recomienda encarecidamente
que las aplicaciones usen ASP.NET Core 2.1 y versiones posteriores, que no usan almacenamiento implícito.
ASP.NET Core 2.1 y versiones posteriores usan el marco de trabajo compartido.
En el caso de .NET Core 2.0, una aplicación de ASP NET Core usa implícitamente la característica de
almacenamiento de paquetes en tiempo de ejecución cuando la aplicación se implementa como aplicación de
implementación dependiente del marco. Los destinos en Microsoft.NET.Sdk.Web incluyen manifiestos que hacen
referencia al almacenamiento de paquetes implícito en el sistema de destino. Además, cualquier aplicación
dependiente del marco que dependa del paquete Microsoft.AspNetCore.All se traducirá en una aplicación
publicada que solo contiene la aplicación y sus recursos, pero no los paquetes que se muestran en el
metapaquete Microsoft.AspNetCore.All . Se presupone que esos paquetes están presentes en el sistema de
destino.
El almacenamiento de paquetes en tiempo de ejecución está instalado en el host cuando el SDK de .NET Core
está instalado. Otros instaladores pueden proporcionar el almacenamiento de paquetes en tiempo de ejecución,
incluidas las instalaciones Zip/tarball del SDK de .NET Core, apt-get , Red Hat Yum, la agrupación de hospedaje
de Windows Server para .NET Core y las instalaciones manuales de almacenamiento de paquetes en tiempo de
ejecución.
Al implementar una aplicación de implementación dependiente del marco, asegúrese de que el entorno de
destino tiene el SDK de .NET Core instalado. Si la aplicación se implementa en un entorno que no incluye
ASP.NET Core, puede rechazar el almacenamiento implícito especificando
<PublishWithAspNetCoreTargetManifest> en false en el archivo de proyecto como se muestra en este
ejemplo:

<PropertyGroup>
<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
</PropertyGroup>

NOTE
En el caso de las aplicaciones de implementación independiente, se presupone que el sistema de destino no contiene
necesariamente los paquetes de manifiesto necesarios. Por lo tanto, no se puede establecer
<PublishWithAspNetCoreTargetManifest> en true para una aplicación independiente.

Vea también
dotnet-publish
dotnet-store
Catálogo de identificadores de entorno de
ejecución (RID) de .NET Core
16/09/2020 • 9 minutes to read • Edit Online

RID es la abreviatura de identificador de entorno de ejecución. Los valores de RID se usan para
identificar las plataformas de destino donde se ejecuta la aplicación. Los paquetes de .NET los usan
para presentar los recursos específicos de la plataforma en los paquetes de NuGet. Los valores
siguientes son ejemplos de RID: linux-x64 , ubuntu.14.04-x64 , win7-x64 o osx.10.12-x64 . En el caso
de los paquetes con dependencias nativas, el RID designa las plataformas en las que se puede
restauran el paquete.
Un único RID se puede establecer en el elemento <RuntimeIdentifier> del archivo del proyecto. Se
pueden definir varios RID como una lista delimitada por punto y coma en el elemento
<RuntimeIdentifiers> del archivo del proyecto. También se usan a través de la opción --runtime con
los comandos de la CLI de .NET Core siguientes:
dotnet build
dotnet clean
dotnet pack
dotnet publish
dotnet restore
dotnet run
dotnet store
Los RID que representan sistemas operativos concretos normalmente siguen este patrón:
[os].[version]-[architecture]-[additional qualifiers] , donde:

[os] es el moniker del sistema operativo o de plataforma. Por ejemplo: ubuntu .


[version] es la versión del sistema operativo en formato de número de versión separado por
punto ( . ). Por ejemplo: 15.10 .
La versión no se debe tratar como versiones de marketing, debido a que, a menudo,
representan varias versiones discretas del sistema operativo con un área expuesta de API de
plataforma diferente.
[architecture] es la arquitectura de procesador. Por ejemplo: x86 , x64 , arm o arm64 .

[additional qualifiers] diferencia aún más las distintas plataformas. Por ejemplo: aot .

Grafo de RID
El grado de RID o el grafo de reserva de entorno de ejecución es una lista de RID compatibles entre sí.
Los RID se definen en el paquete Microsoft.NETCore.Platforms. Puede ver la lista de RID compatibles y
el grafo de RID en el archivo runtime.json, que se encuentra en el repositorio dotnet/runtime . En este
archivo puede ver que todos los RID, excepto el RID de base, contienen una instrucción "#import" .
Estas instrucciones indican los RID compatibles.
Cuando NuGet restaura los paquetes, intenta encontrar una coincidencia exacta para el entorno de
ejecución especificado. Si no se encuentra una coincidencia exacta, NuGet vuelve al grafo hasta
encontrar el sistema compatible más cercano según el grafo de RID.
El ejemplo siguiente es la entrada real del RID osx.10.12-x64 :

"osx.10.12-x64": {
"#import": [ "osx.10.12", "osx.10.11-x64" ]
}

El RID anterior especifica que osx.10.12-x64 importa osx.10.11-x64 . Por tanto, cuando NuGet
restaura los paquetes, intenta encontrar una coincidencia exacta para osx.10.12-x64 en el paquete. Si
NuGet no puede encontrar el entorno de ejecución específico, puede restaurar, por ejemplo, los
paquetes que especifican entornos de ejecución osx.10.11-x64 .
En el ejemplo siguiente se muestra un grafo de RID ligeramente más grande que también se define en
el archivo runtime.json:

win7-x64 win7-x86
| \ / |
| win7 |
| | |
win-x64 | win-x86
\ | /
win
|
any

A la larga, todos los RID se asignarán de vuelta al RID any raíz.


Hay algunas consideraciones sobre los RID que debe tener en cuenta cuando trabaja con ellos:
No intente analizar los RID para recuperar partes de componentes.
No compile RID mediante programación.
Use RID que ya estén definidos para la plataforma.
Los RID deben ser específicos, por lo que no se recomienda presuponer nada a partir del valor de
RID real.

Uso de los RID


Para poder usar los RID, debe saber cuáles son los RID que existen. Se agregan valores nuevos a la
plataforma de manera habitual. Para obtener la versión más reciente y completa, vea el archivo
runtime.json en el repositorio dotnet/runtime .
El SDK de .NET Core 2.0 presenta el concepto de RID portátiles. Son nuevos valores agregados al grafo
de RID que no están vinculados a una versión específica o distribución del sistema operativo, y son la
opción preferida cuando se usa .NET Core 2.0 y versiones posteriores. Resultan especialmente útiles al
tratar con varias distribuciones de Linux dado que la mayoría de los RID de distribución se asignan a
los RID portátiles.
En la lista siguiente se muestra un pequeño subconjunto de los RID más comunes que se usan con
cada sistema operativo.

RID de Windows
Solo se muestran los valores comunes. Para obtener la versión más reciente y completa, vea el archivo
runtime.json en el repositorio dotnet/runtime .
Portátil (.NET Core 2.0 o versiones posteriores)
win-x64
win-x86
win-arm
win-arm64
Windows 7/Windows Server 2008 R2
win7-x64
win7-x86
Windows 8.1/Windows Server 2012 R2
win81-x64
win81-x86
win81-arm
Windows 10/Windows Server 2016
win10-x64
win10-x86
win10-arm
win10-arm64

Para obtener más información, vea Dependencias y requisitos de .NET Core .

RID de Linux
Solo se muestran los valores comunes. Para obtener la versión más reciente y completa, vea el archivo
runtime.json en el repositorio dotnet/runtime . Los dispositivos que ejecutan una distribución que no
se muestran en la lista podrían funcionar con uno de los RID portátiles. Por ejemplo, el destino de los
dispositivos Raspberry Pi que ejecutan una distribución de Linux se puede establecer con linux-arm .
Portátil (.NET Core 2.0 o versiones posteriores)
linux-x64 (La mayoría de las distribuciones de escritorio como CentOS, Debian, Fedora,
Ubuntu y derivados)
linux-musl-x64 (Distribuciones ligeras con musl como Alpine Linux)
linux-arm (Distribuciones de Linux que se ejecutan en ARM, como Raspbian en Raspberry
Pi, modelo 2+)
linux-arm64 (Distribuciones de Linux que se ejecutan en ARM de 64 bits, como Ubuntu
Server de 64 bits en Raspberry Pi, modelo 3+)
Red Hat Enterprise Linux
rhel-x64 (Se reemplaza por linux-x64 para RHEL por encima de la versión 6)
rhel.6-x64 (.NET Core 2.0 o versiones posteriores)
Tizen (.NET Core 2.0 o versiones posteriores)
tizen
tizen.4.0.0
tizen.5.0.0

Para obtener más información, vea Dependencias y requisitos de .NET Core .

RID de macOS
Los RID de macOS usan la personalización de marca antigua "OSX". Solo se muestran los valores
comunes. Para obtener la versión más reciente y completa, vea el archivo runtime.json en el
repositorio dotnet/runtime .
Portátil (.NET Core 2.0 o versiones posteriores)
(La versión mínima del sistema operativo es macOS 10.12 Sierra)
osx-x64
macOS 10.10 Yosemite
osx.10.10-x64
macOS 10.11 El Capitan
osx.10.11-x64
macOS 10.12 Sierra (.NET Core 1.1 o versiones posteriores)
osx.10.12-x64
macOS 10.13 High Sierra (.NET Core 1.1 o versiones posteriores)
osx.10.13-x64
macOS 10.14 Mojave (.NET Core 1.1 o versiones posteriores)
osx.10.14-x64

Para obtener más información, vea Dependencias y requisitos de .NET Core .

Vea también
Identificadores de entorno de ejecución
Introducción a .NET y Docker
18/03/2020 • 7 minutes to read • Edit Online

.NET Core puede ejecutarse con facilidad en un contenedor de Docker. Los contenedores proporcionan una
manera ligera de aislar la aplicación del resto del sistema host, con tan solo compartir el kernel y usar los
recursos proporcionados a la aplicación. Si no está familiarizado con Docker, se recomienda encarecidamente que
lea la documentación de introducción a Docker.
Para más información sobre cómo instalar Docker, vea la página de descarga de Docker Desktop: Community
Edition.

Conceptos básicos de Docker


Hay algunos conceptos con los que debe estar familiarizado. El cliente de Docker tiene una CLI que puede usar
para administrar imágenes y contenedores. Como se mencionó anteriormente, debe dedicar tiempo a leer la
documentación de introducción a Docker.
Imágenes
Una imagen es una colección ordenada de los cambios del sistema de archivos que forman la base de un
contenedor. La imagen no tiene ningún estado y es de solo lectura. La mayor parte del tiempo, una imagen se
basa en otra imagen, pero con alguna personalización. Por ejemplo, al crear una imagen para una aplicación, esta
se basaría en una imagen existente que ya contiene .NET Core Runtime.
Dado que los contenedores se crean a partir de imágenes, estas tienen un conjunto de parámetros de ejecución
(por ejemplo, un archivo ejecutable de inicio) que se ejecutan cuando se inicia el contenedor.
Contenedores
Un contenedor es una instancia ejecutable de una imagen. Al crear la imagen, implementa la aplicación y sus
dependencias. Después, se pueden crear instancias de varios contenedores, cada una de ellas aisladas entre sí.
Cada instancia de contenedor tiene su sistema de archivos, memoria e interfaz de red propios.
Registros
Los registros de contenedor son una colección de repositorios de imágenes. Puede basar sus imágenes en una
imagen del registro. Puede crear contenedores directamente a partir de una imagen en un registro. La relación
entre los contenedores, las imágenes y los registros de Docker es un concepto importante a la hora de diseñar y
compilar aplicaciones o microservicios en contenedores. Este enfoque reduce en gran medida el tiempo que
transcurre entre el desarrollo y la implementación.
Docker tiene un registro público hospedado en Docker Hub que puede usar. Las imágenes relacionadas con .NET
Core aparecen en Docker Hub.
El registro de contenedor de Microsoft (MCR) es el origen oficial de las imágenes de contenedor proporcionadas
por Microsoft. El MCR se basa en Azure CDN para proporcionar imágenes replicadas globalmente. Sin embargo,
el MCR no tiene un sitio web público, y la manera principal de obtener información sobre las imágenes de
contenedor proporcionadas por Microsoft es en las páginas de Microsoft Docker Hub.
Dockerfile
Un archivo Dockerfile es aquel que define un conjunto de instrucciones que crea una imagen. Cada instrucción
de Dockerfile crea una capa en la imagen. Por lo general, cuando se recompila la imagen, solo se recompilan las
capas que han cambiado. El Dockerfile se puede distribuir a otros usuarios, para que puedan recrear una imagen
de la misma manera que usted. Aunque esto le permite distribuir las instrucciones sobre cómo crear la imagen, el
método principal para distribuir la imagen consiste en publicarla en un registro.

Imágenes de .NET Core


Las imágenes oficiales de Docker en .NET Core se publican en el registro de contenedor de Microsoft (MCR) y se
pueden encontrar en el repositorio de Docker Hub para Microsoft .NET Core. Cada repositorio contiene imágenes
para diferentes combinaciones de .NET (SDK o Runtime) y del sistema operativo que puede usar.
Microsoft ofrece imágenes que se adaptan a escenarios específicos. Por ejemplo, el repositorio de ASP.NET Core
proporciona imágenes que se compilan para ejecutar aplicaciones de ASP.NET Core en producción.

Servicios de Azure
Varios servicios de Azure admiten contenedores. Cree una imagen de Docker para la aplicación e impleméntela
en alguno de los siguientes servicios:
Azure Kubernetes Service (AKS)
Escale y organice contenedores de Linux mediante Kubernetes.
Azure App Service
Implemente aplicaciones web o API con contenedores de Linux en un entorno PaaS.
Azure Container Instances
Hospede el contenedor en la nube sin ningún servicio de administración de nivel superior.
Azure Batch
Ejecute trabajos de proceso repetitivos mediante contenedores.
Azure Service Fabric
Migre las aplicaciones de .NET mediante lift-and-shift y modernícelas a microservicios mediante
contenedores de Windows Server.
Azure Container Registry
Almacene y administre imágenes de contenedor en todos los tipos de implementaciones de Azure.

Pasos siguientes
Obtenga información sobre cómo incluir una aplicación de .NET Core en contenedores.
Obtenga información sobre cómo incluir una aplicación ASP.NET Core en contenedores.
Consulte el tutorial de ASP.NET Core para los microservicios.
Obtenga información sobre las herramientas de contenedor en Visual Studio
Tutorial: Incluir una aplicación de .NET Core en un
contenedor
16/09/2020 • 21 minutes to read • Edit Online

En este tutorial obtendrá información sobre cómo incluir una aplicación de .NET Core en un contenedor con
Docker. Los contenedores tienen muchas características y ventajas, como ser una infraestructura inmutable,
proporcionar una arquitectura portátil y permitir la escalabilidad. La imagen puede usarse para crear
contenedores para un entorno de desarrollo local, una nube privada o una nube pública.
En este tutorial ha:
Crear y publicar una aplicación .NET Core sencilla
Crear y configurar un archivo Dockerfile para .NET Core
Creación de una imagen de Docker
Crear y ejecutar un contenedor de Docker
Aprenderá sobre las tareas de compilación e implementación de un contenedor de Docker para una aplicación
.NET Core. La plataforma Docker usa el motor de Docker para compilar y empaquetar rápidamente aplicaciones
como imágenes de Docker. Estas imágenes se escriben en el formato Dockerfile para implementarse y ejecutarse
en un contenedor superpuesto.

NOTE
Este tutorial no es para aplicaciones ASP.NET Core. Si usa ASP.NET Core, lea el tutorial sobre cómo incluir una aplicación de
ASP.NET Core en un contenedor.

Requisitos previos
Instale estos requisitos previos:
SDK de .NET Core 3.1
Si tiene instalado .NET Core, use el comando dotnet --info para determinar el SDK que está usando.
Docker Community Edition
Una carpeta de trabajo temporal para Dockerfile y una aplicación .NET Core de ejemplo. En este tutorial, el
nombre docker-working se usa como la carpeta de trabajo.

Creación de una aplicación .NET Core


Necesita una aplicación .NET Core que el contenedor de Docker ejecutará. Abra el terminal, cree una carpeta de
trabajo si todavía no lo ha hecho y entre en ella. En la carpeta de trabajo, ejecute el comando siguiente para crear
un proyecto en un subdirectorio llamado app:

dotnet new console -o App -n NetCore.Docker

El árbol de carpetas tendrá un aspecto similar al siguiente:


docker-working
└──App
├──NetCore.Docker.csproj
├──Program.cs
└──obj
├──NetCore.Docker.csproj.nuget.dgspec.json
├──NetCore.Docker.csproj.nuget.g.props
├──NetCore.Docker.csproj.nuget.g.targets
├──project.assets.json
└──project.nuget.cache

Con el comando dotnet new se crea una carpeta denominada App y se genera una aplicación de consola "Hola
mundo". Cambie los directorios y vaya a la carpeta App, desde la sesión de Terminal. Use el comando dotnet run
para iniciar la aplicación. La aplicación se ejecutará e imprimirá Hello World! debajo del comando:

dotnet run
Hello World!

La plantilla predeterminada crea una aplicación que imprime en el terminal y termina de inmediato. En este
tutorial, se usará una aplicación que se repite en bucle de manera indefinida. Abra el archivo Program.cs en un
editor de texto.

TIP
Si está utilizando Visual Studio Code, en la sesión de Terminal anterior, escriba el siguiente comando:

code .

Se abrirá la carpeta App que contiene el proyecto en Visual Studio Code.

El archivo Program.cs debería ser similar al código de C# siguiente:

using System;

namespace NetCore.Docker
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

Reemplace el archivo por el código siguiente que cuenta números cada segundo:
using System;
using System.Threading.Tasks;

namespace NetCore.Docker
{
class Program
{
static async Task Main(string[] args)
{
var counter = 0;
var max = args.Length != 0 ? Convert.ToInt32(args[0]) : -1;
while (max == -1 || counter < max)
{
Console.WriteLine($"Counter: {++counter}");
await Task.Delay(1000);
}
}
}
}

Guarde el archivo y vuelva a probar el programa con dotnet run . Recuerde que esta aplicación se ejecuta de
manera indefinida. Use el comando Cancelar Ctrl+C para detenerla. A continuación se muestra un resultado de
ejemplo:

dotnet run
Counter: 1
Counter: 2
Counter: 3
Counter: 4
^C

Si pasa un número en la línea de comandos a la aplicación, solo se contará hasta esa cantidad y se cerrará.
Inténtelo con dotnet run -- 5 para contar hasta cinco.

IMPORTANT
Cualquier parámetro posterior a -- no se pasa al comando dotnet run y, en su lugar, se pasa a su aplicación.

Publicación de una aplicación .NET Core


Antes de agregar la aplicación .NET Core a la imagen de Docker, primero debe publicarse. Es mejor que el
contenedor ejecute la versión publicada de la aplicación. Ejecute el comando siguiente para publicar la aplicación:

dotnet publish -c Release

Con este comando se compila la aplicación en la carpeta publish. La ruta de acceso a la carpeta publish desde la
carpeta de trabajo debería ser .\App\bin\Release\netcoreapp3.1\publish\ .
Windows
Linux
En la carpeta App, obtenga un listado de los directorios de la carpeta publish para comprobar que se ha creado el
archivo NetCore.Docker.dll.
dir .\bin\Release\netcoreapp3.1\publish\

Directory: C:\Users\dapine\App\bin\Release\netcoreapp3.1\publish

Mode LastWriteTime Length Name


---- ------------- ------ ----
-a---- 4/27/2020 8:27 AM 434 NetCore.Docker.deps.json
-a---- 4/27/2020 8:27 AM 6144 NetCore.Docker.dll
-a---- 4/27/2020 8:27 AM 171520 NetCore.Docker.exe
-a---- 4/27/2020 8:27 AM 860 NetCore.Docker.pdb
-a---- 4/27/2020 8:27 AM 154 NetCore.Docker.runtimeconfig.json

Creación del archivo Dockerfile


El archivo Dockerfile lo usa el comando docker build para crear una imagen de contenedor. Este archivo es un
archivo de texto denominado Dockerfile que no tiene ninguna extensión.
Cree un archivo denominado Dockerfile en el directorio que contiene el archivo .csproj y ábralo en un editor de
texto. Este tutorial usa la imagen de runtime de ASP.NET Core (que contiene la imagen de runtime de .NET Core) y
corresponde a la aplicación de consola de .NET Core.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1

NOTE
Aquí se usa intencionadamente la imagen de runtime de ASP.NET Core, aunque se podría haber usado la imagen de
mcr.microsoft.com/dotnet/core/runtime:3.1 .

La palabra clave FROM requiere un nombre completo de imagen de contenedor de Docker. Microsoft Container
Registry (MCR, mcr.microsoft.com) es un sindicato de Docker Hub, que hospeda contenedores accesibles
públicamente. El segmento dotnet/core es el repositorio de contenedores, donde el segmento de aspnet es el
nombre de la imagen de contenedor. La imagen está etiquetada con 3.1 , que se usa para el control de versiones.
Por lo tanto, mcr.microsoft.com/dotnet/core/aspnet:3.1 es el runtime de .NET Core 3.1. Asegúrese de extraer el
runtime que coincida con el que el SDK tiene como destino. Por ejemplo, la aplicación creada en la sección
anterior usaba el SDK de .NET Core 3.1, y la imagen base a la que se hace referencia en el documento Dockerfile
se etiqueta con 3.1 .
Guarde el archivo Dockerfile. La estructura de directorios de la carpeta de trabajo debería tener el siguiente
aspecto. Algunos de los archivos y carpetas inferiores se han omitido para ahorrar espacio en este artículo:
docker-working
└──App
├──Dockerfile
├──NetCore.Docker.csproj
├──Program.cs
├──bin
│ └──Release
│ └──netcoreapp3.1
│ └──publish
│ ├──NetCore.Docker.deps.json
│ ├──NetCore.Docker.exe
│ ├──NetCore.Docker.dll
│ ├──NetCore.Docker.pdb
│ └──NetCore.Docker.runtimeconfig.json
└──obj
└──...

Desde un terminal, ejecute el comando siguiente:

docker build -t counter-image -f Dockerfile .

Docker procesará cada línea en el archivo Dockerfile. . del comando docker build indica a Docker que use la
carpeta actual para encontrar un archivo Dockerfile. Este comando crea la imagen y un repositorio local
denominado counter-image que apunta a esa imagen. Una vez que finalice este comando, ejecute
docker images para ver una lista de las imágenes instaladas:

docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest e6780479db63 4 days ago 190MB
mcr.microsoft.com/dotnet/core/aspnet 3.1 e6780479db63 4 days ago 190MB

Observe que ambas imágenes comparten el mismo valor IMAGE ID . El valor es el mismo entre ambas imágenes
porque el único comando en Dockerfile era basar la imagen nueva en una imagen existente. Agreguemos tres
comandos a Dockerfile. Cada comando crea una capa de imagen y el comando final que representa la imagen
counter-image a la que apunta el repositorio.

COPY bin/Release/netcoreapp3.1/publish/ App/


WORKDIR /App
ENTRYPOINT ["dotnet", "NetCore.Docker.dll"]

El comando COPY indica a Docker que copie la carpeta especificada en el equipo a una carpeta del contenedor. En
este ejemplo, la carpeta publish se copia en una carpeta denominada App del contenedor.
El comando WORKDIR cambia el directorio actual dentro del contenedor a App.
El comando siguiente, ENTRYPOINT , indica a Docker que configure el contenedor para que se ejecute como
ejecutable. Cuando el contenedor se inicia, se ejecuta el comando ENTRYPOINT . Cuando este comando finaliza, el
contenedor se detiene automáticamente.
Desde el terminal, ejecute docker build -t counter-image -f Dockerfile . y, cuando el comando finalice, ejecute
docker images .
docker build -t counter-image -f Dockerfile .
Sending build context to Docker daemon 1.117MB
Step 1/4 : FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
---> e6780479db63
Step 2/4 : COPY bin/Release/netcoreapp3.1/publish/ App/
---> d1732740eed2
Step 3/4 : WORKDIR /App
---> Running in b1701a42f3ff
Removing intermediate container b1701a42f3ff
---> 919aab5b95e3
Step 4/4 : ENTRYPOINT ["dotnet", "NetCore.Docker.dll"]
---> Running in c12aebd26ced
Removing intermediate container c12aebd26ced
---> cd11c3df9b19
Successfully built cd11c3df9b19
Successfully tagged counter-image:latest

docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest cd11c3df9b19 41 seconds ago 190MB
mcr.microsoft.com/dotnet/core/aspnet 3.1 e6780479db63 4 days ago 190MB

Cada comando de Dockerfile generó una capa y creó un valor IMAGE ID . El valor IMAGE ID final (el suyo será
distinto) es cd11c3df9b19 y, después, usted creará un contenedor basado en esta imagen.

Crear un contenedor
Ahora que tiene una imagen que contiene la aplicación, puede crear un contenedor. Hay dos formas de crear un
contenedor. En primer lugar, cree un contenedor que esté detenido.

docker create --name core-counter counter-image


0f281cb3af994fba5d962cc7d482828484ea14ead6bfe386a35e5088c0058851

El comando docker create anterior creará un contenedor basado en la imagen counter-image . La salida de ese
comando muestra el valor CONTAINER ID (el suyo será distinto) del contenedor creado. Para ver una lista de
todos los contenedores, use el comando docker ps -a :

docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0f281cb3af99 counter-image "dotnet NetCore.Dock…" 40 seconds ago Created core-counter

Administración del contenedor


El contenedor se creó con un nombre específico, core-counter , usado para administrar el contenedor. En el
ejemplo siguiente se usa el comando docker start para iniciar el contenedor y luego se usa el comando
docker ps para mostrar solo los contenedores que están en ejecución:

docker start core-counter


core-counter

docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2f6424a7ddce counter-image "dotnet NetCore.Dock…" 2 minutes ago Up 11 seconds core-
counter

De manera similar, el comando docker stop detendrá el contenedor. En el ejemplo siguiente se usa el comando
docker stop para detener el contenedor y luego se usa el comando docker ps para mostrar que no hay ningún
contenedor en ejecución:

docker stop core-counter


core-counter

docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Conectarse a un contenedor
Una vez que se ejecuta un contenedor, puede conectarse a él para ver la salida. Use los comandos docker start y
docker attach para iniciar el contenedor y echar un vistazo al flujo de salida. En este ejemplo, la pulsación de
teclas Ctrl+C se usa para desasociarse del contenedor en ejecución. A menos que se especifique lo contrario,
esta pulsación de teclas finalizará el proceso en el contenedor, con lo que se detendrá el contenedor. El parámetro
--sig-proxy=false garantiza que Ctrl+C no detendrá el proceso en el contenedor.

Después de desasociarse del contenedor, reasócielo para comprobar que sigue en ejecución.

docker start core-counter


core-counter

docker attach --sig-proxy=false core-counter


Counter: 7
Counter: 8
Counter: 9
^C

docker attach --sig-proxy=false core-counter


Counter: 17
Counter: 18
Counter: 19
^C

Eliminación de un contenedor
Para el propósito de este artículo, no queremos contenedores que solo existan y no hagan nada. Elimine el
contenedor que creó anteriormente. Si el contenedor está en ejecución, deténgalo.

docker stop core-counter

En el ejemplo siguiente se muestran todos los contenedores. Luego, usa el comando docker rm para eliminar el
contenedor y después vuelve a comprobar si hay algún contenedor en ejecución.

docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS
PORTS NAMES
2f6424a7ddce counter-image "dotnet NetCore.Dock…" 7 minutes ago Exited (143) 20 seconds ago
core-counter

docker rm core-counter
core-counter

docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Ejecución única
Docker proporciona el comando docker run para crear y ejecutar el contenedor como comando único. Este
comando elimina la necesidad de ejecutar docker create y luego docker start . También puede establecer este
comando en que elimine automáticamente el contenedor cuando este se detenga. Por ejemplo, use
docker run -it --rm para hacer dos cosas: primero, use automáticamente el terminal actual para conectarse al
contenedor y cuando el contenedor finalice, quítelo:

docker run -it --rm counter-image


Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
^C

El contenedor también pasa parámetros a la ejecución de la aplicación .NET Core. Para indicar a la aplicación .NET
Core que cuente solo hasta 3, pase 3.

docker run -it --rm counter-image 3


Counter: 1
Counter: 2
Counter: 3

Con docker run -it , el comando Ctrl+C detendrá el proceso que está ejecutándose en el contenedor, lo que, a
su vez, detendrá el contenedor. Como se proporcionó el parámetro --rm , el contenedor se elimina
automáticamente cuando se detiene el proceso. Compruebe que no existe:

docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Cambio de ENTRYPOINT
El comando docker run también permite modificar el comando ENTRYPOINT desde el archivo Dockerfile y ejecute
algún otro elemento, pero solo para ese contenedor. Por ejemplo, use el comando siguiente para ejecutar bash o
cmd.exe . Edite el comando según sea necesario.

Windows
Linux
En este ejemplo, ENTRYPOINT cambia a cmd.exe . Se presiona Ctrl+C para finalizar el proceso y detener el
contenedor.
docker run -it --rm --entrypoint "cmd.exe" counter-image

Microsoft Windows [Version 10.0.17763.379]


(c) 2018 Microsoft Corporation. All rights reserved.

C:\>dir
Volume in drive C has no label.
Volume Serial Number is 3005-1E84

Directory of C:\

04/09/2019 08:46 AM <DIR> app


03/07/2019 10:25 AM 5,510 License.txt
04/02/2019 01:35 PM <DIR> Program Files
04/09/2019 01:06 PM <DIR> Users
04/02/2019 01:35 PM <DIR> Windows
1 File(s) 5,510 bytes
4 Dir(s) 21,246,517,248 bytes free

C:\>^C

Comandos esenciales
Docker tiene muchos comandos diferentes que crean, administran e interactúan con contenedores e imágenes.
Estos comandos de Docker son esenciales para la administración de los contenedores:
docker build
docker run
docker ps
docker stop
docker rm
docker rmi
docker image

Limpiar los recursos


Durante este tutorial, creó contenedores e imágenes. Elimine estos recursos si quiere hacerlo. Use los comandos
siguientes para
1. Mostrar todos los contenedores

docker ps -a

2. Detener los contenedores que están en ejecución por nombre

docker stop counter-image

3. Eliminar el contenedor

docker rm counter-image

A continuación, elimine las imágenes que ya no quiere tener en la máquina. Elimine la imagen que creó el archivo
Dockerfile y luego elimine la imagen de .NET Core en que se basó el archivo Dockerfile. Puede usar el valor
IMAGE ID o la cadena con formato REPOSITORY:TAG .
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/core/aspnet:3.1

Use el comando docker images para ver una lista de las imágenes instaladas.

TIP
Los archivos de imagen pueden ser grandes. Por lo general, quitaría los contenedores temporales que creó al probar y
desarrollar la aplicación. Habitualmente, estas imágenes base se conservan con el runtime instalado si se planea crear otras
imágenes basadas en ese runtime.

Pasos siguientes
Obtenga información sobre cómo incluir una aplicación ASP.NET Core en contenedores.
Consulte el tutorial de ASP.NET Core para los microservicios.
Revise los servicios de Azure que admiten contenedores.
Lea sobre los comandos de Dockerfile.
Explorar las herramientas de contenedor para Visual Studio
Common Type System y Common Language
Specification
19/03/2020 • 6 minutes to read • Edit Online

De nuevo, dos términos que se usan libremente en el mundo de .NET, en realidad, son cruciales para entender
cómo permite una implementación de .NET el desarrollo de varios lenguajes y para entender cómo funciona.

Sistema de tipos comunes


Para comenzar desde el principio, recuerde que una implementación de .NET es independiente del lenguaje. Esto
no solo significa que un programador pueda escribir su código en cualquier lenguaje que se pueda compilar en IL,
sino también que tiene que poder interactuar con código escrito en otros lenguajes que se pueden usar en una
implementación de .NET.
Para hacer esto de forma transparente, debe haber una forma común de describir todos los tipos compatibles. De
esto se encarga Common Type System (CTS). Se ha creado para realizar varias acciones:
Establecer un marco para la ejecución de varios lenguajes.
Proporcionar un modelo orientado a objetos para admitir la implementación de varios lenguajes en una
implementación de .NET.
Definir un conjunto de reglas que deben seguir todos los lenguajes al trabajar con tipos.
Proporcionar una biblioteca que contenga los tipos de datos primitivos que se emplean en el desarrollo de
aplicaciones (por ejemplo, Boolean , Byte , Char , etc.).
CTS define dos tipos principales que deben ser compatibles: tipos de referencia y valor. Sus nombres indican sus
definiciones.
Los objetos de los tipos de referencia se representan mediante una referencia al valor real del objeto; aquí, una
referencia es similar a un puntero en C/C++. Simplemente, hace referencia a una ubicación de memoria donde
están los valores de los objetos. Esto tiene un impacto profundo sobre cómo se usan estos tipos. Si asigna un tipo
de referencia a una variable y después pasa esa variable en un método, por ejemplo, se reflejarán los cambios del
objeto en el objeto principal; no hay ninguna copia.
Los tipos de valor son lo opuesto, donde los objetos se representan mediante sus valores. Si asigna un tipo de
valor a una variable, está copiando un valor del objeto.
CTS define varias categorías de tipos, cada una con su semántica y uso específicos:
Clases
Estructuras
Enumeraciones
Interfaces
Delegados
CTS también define todas las demás propiedades de los tipos, como modificadores de acceso, cuáles son los
miembros de tipo válidos, cómo funcionan la herencia y la sobrecarga, etc. Desafortunadamente, profundizar sobre
cualquiera de ellas queda fuera del ámbito de un artículo de introducción como este, pero puede consultar la
sección Más recursos al final para obtener vínculos a contenido más detallado que trata estos temas.

Common Language Specification


Para habilitar escenarios de interoperabilidad completa, todos los objetos que se creen en el código deben basarse
en algunas similitudes en los lenguajes que los usan (son sus llamadores). Puesto que hay numerosos lenguajes
diferentes, .NET ha especificado las similitudes en lo que se denomina Common Language Specification (CLS).
CLS define un conjunto de características que son necesarias para muchas aplicaciones comunes. También
proporciona una especie de receta para cualquier lenguaje que se implementa sobre .NET sobre qué necesita para
ser compatible.
CLS es un subconjunto de CTS. Esto significa que todas las reglas de CTS se aplican también a CLS, a menos que las
reglas de CLS sean más estrictas. Si un componente se compila solo con las reglas de CLS, es decir, expone solo las
características de CLS en la API, se dice que es conforme a CLS . Por ejemplo, las <framework-librares> son
conformes a CLS precisamente porque tienen que funcionar en todos los lenguajes que se admiten en .NET.
Puede consultar los documentos de la sección Más recursos a continuación para obtener una información general
de todas las características de CLS.

Más recursos
Sistema de tipos comunes
Common Language Specification
Sistema de tipos comunes
16/09/2020 • 46 minutes to read • Edit Online

Common Type System define cómo se declaran, usan y administran los tipos en Common Language Runtime. Es
también una parte importante de la compatibilidad en tiempo de ejecución con la integración entre lenguajes. El
sistema de tipos común realiza las funciones siguientes:
Establece un marco de trabajo que ayuda a permitir la integración entre lenguajes, la seguridad de tipos y
la ejecución de código de alto rendimiento.
Proporciona un modelo orientado a objetos que admite la implementación completa de muchos lenguajes
de programación.
Define reglas que deben seguir los lenguajes, lo que ayuda a garantizar que los objetos escritos en
distintos lenguajes puedan interactuar unos con otros.
Proporciona una biblioteca que contiene los tipos de datos primitivos (como Boolean, Byte, Char, Int32 y
UInt64) que se emplean en el desarrollo de aplicaciones.

Tipos de .NET
Todos los tipos de .NET son tipos de valor o tipos de referencia.
Los tipos de valor son tipos de datos cuyos objetos se representan mediante el valor real del objeto. Si se asigna
una instancia de un tipo de valor a una variable, esa variable obtiene una copia reciente del valor.
Los tipos de referencia son tipos de datos cuyos objetos se representan mediante una referencia (similar a un
puntero) al valor real del objeto. Si se asigna un tipo de referencia a una variable, esa variable hace referencia (o
apunta) al valor original. No se realiza ninguna copia.
Common Type System en .NET admite las cinco categorías de tipos siguientes:
Clases
Estructuras
Enumeraciones
Interfaces
Delegados
Clases
Una clase es un tipo de referencia que se puede derivar directamente de otra clase y que se deriva
implícitamente de System.Object. La clase define las operaciones que un objeto (que es una instancia de la clase)
puede realizar (métodos, eventos o propiedades) y los datos que el objeto contiene (campos). Aunque una clase
suele incluir una definición y una implementación (a diferencia, por ejemplo, de las interfaces, que solo contienen
una definición sin implementación), puede tener uno o varios miembros sin implementación.
En la tabla siguiente se describen algunas de las características que una clase puede tener. Cada lenguaje
compatible con el motor en tiempo de ejecución proporciona una forma de indicar que una clase o un miembro
de clase tiene una o varias de estas características. En cambio, puede que no estén disponibles todas estas
características en los lenguajes de programación orientados a .NET.
C A RA C T ERÍST IC A DESC RIP C IÓ N

sealed Especifica que no se puede derivar otra clase de este tipo.

implementa Indica que la clase utiliza una o varias interfaces


proporcionando implementaciones de miembros de la
interfaz.

abstract Indica que no se pueden crear instancias de la clase. Para


utilizarla se debe derivar de ella otra clase.

hereda Indica que las instancias de la clase se pueden utilizar en


cualquier lugar en que se especifique la clase base. Una clase
derivada que hereda de una clase base puede usar la
implementación de cualquier miembro público
proporcionado por la clase base o la clase derivada puede
invalidar la implementación de los miembros públicos con su
propia implementación.

exported o not exported Indica si una clase está visible fuera del ensamblado en el que
se define. Esta característica se aplica únicamente a las clases
de nivel superior y no a las clases anidadas.

NOTE
Una clase también puede estar anidada en una estructura o clase primaria. Las clases anidadas tienen también
características de miembro. Para obtener más información, consulte Tipos anidados.

Los miembros de clase que no tienen implementación son miembros abstractos. Una clase que tiene uno o
varios miembros abstractos es abstracta y no se pueden crear nuevas instancias de ella. Algunos lenguajes
destinados al motor en tiempo de ejecución permiten marcar una clase como abstracta incluso aunque no tenga
ningún miembro abstracto. Se puede usar una clase abstracta cuando se desea encapsular un conjunto básico de
funcionalidad que las clases derivadas pueden heredar o invalidar según corresponda. Las clases que no son
abstractas se conocen como clases concretas.
Una clase puede implementar cualquier número de interfaces pero puede heredar solo de una clase base
además de la clase System.Object, de la que todas las clases heredan implícitamente. Todas las clases deben tener
al menos un constructor, que inicializa nuevas instancias de la clase. Si no se define explícitamente un constructor,
la mayoría de los compiladores proporcionarán automáticamente un constructor sin parámetros.
Estructuras
Una estructura es un tipo de valor que se deriva implícitamente de System.ValueType, que a su vez se deriva de
System.Object. Una estructura es útil para representar valores cuyos requisitos de memoria sean reducidos y
para pasar valores como parámetros por valor a los métodos que tengan parámetros fuertemente tipados. En
.NET, todos los tipos de datos primitivos (Boolean, Byte, Char, DateTime, Decimal, Double, Int16, Int32, Int64,
SByte, Single, UInt16, UInt32 y UInt64) se definen como estructuras.
Al igual que las clases, las estructuras definen datos (los campos de la estructura) y las operaciones que se
pueden realizar con esos datos (los métodos de la estructura). Esto significa que se puede llamar a los métodos
en las estructuras, incluso a los métodos virtuales definidos en las clases System.Object y System.ValueType, y a
cualquier método definido en el propio tipo de valor. Es decir, las estructuras pueden tener campos, propiedades
y eventos, así como métodos estáticos y no estáticos. Se pueden crear instancias de las estructuras, pasarlas
como parámetros, almacenarlas como variables locales o almacenarlas en un campo de otro tipo de valor o tipo
de referencia. Las estructuras también pueden implementar interfaces.
Los tipos de valor también difieren de las clases en varios aspectos. En primer lugar, aunque heredan
implícitamente de System.ValueType, no pueden heredar directamente de ningún tipo. De manera similar, todos
los tipos de valor están sellados, lo que quiere decir que de ellos no se puede derivar ningún otro tipo. Tampoco
necesitan constructores.
Para cada tipo de valor, Common Language Runtime proporciona un tipo correspondiente al que se ha aplicado
la conversión boxing, que es una clase que tiene el mismo estado y comportamiento que el tipo de valor. A una
instancia de un tipo de valor se le aplica la conversión boxing cuando se pasa a un método que acepta un
parámetro de tipo System.Object. Se le aplica la conversión unboxing (es decir, se vuelve a convertir la instancia
de una clase en una instancia de un tipo de valor) cuando se devuelve el control de una llamada a un método que
acepta un tipo de valor como parámetro por referencia. En el caso de algunos lenguajes, se debe usar una
sintaxis especial cuando se necesita el tipo al que se haya aplicado la conversión boxing, mientras que otros
emplean el tipo automáticamente cuando es necesario. Cuando se define un tipo de valor, se definen los dos
tipos: al que se ha aplicado la conversión boxing y al que se ha aplicado la conversión unboxing.
Enumeraciones
Una enumeración es un tipo de valor que hereda directamente de System.Enum y proporciona nombres
alternativos para los valores de un tipo primitivo subyacente. Un tipo de enumeración tiene un nombre, un tipo
subyacente que debe ser uno de los tipos de enteros con o sin signo integrados (como Byte, Int32 o UInt64) y un
conjunto de campos. Los campos son campos literales estáticos, cada uno de los cuales representa una
constante. El mismo valor se puede asignar a varios campos. Cuando esto sucede, se debe marcar uno de los
valores como valor de enumeración primario para la reflexión y la conversión de cadenas.
Se puede asignar un valor del tipo subyacente a una enumeración y viceversa, y no es necesario que el motor en
tiempo de ejecución realice una conversión. Se puede crear una instancia de una enumeración y llamar a los
métodos de System.Enum, además de llamar a cualquier método definido en el tipo subyacente de la
enumeración. Sin embargo, algunos lenguajes no permiten pasar una enumeración como parámetro cuando se
necesita una instancia del tipo subyacente (o viceversa).
A las enumeraciones se les aplican las restricciones siguientes:
No pueden definir sus propios métodos.
No pueden implementar interfaces.
No pueden definir propiedades ni eventos.
No pueden ser genéricas, a menos que sean genéricas solo porque están anidadas dentro de un tipo
genérico. Es decir, una enumeración no puede tener parámetros de tipo propios.

NOTE
Los tipos anidados (incluidas las enumeraciones) creados con Visual Basic, C# y C++ incluyen los parámetros de
tipo de todos los tipos genéricos envolventes, por lo que son genéricos aunque no tengan parámetros de tipo
propios. Para obtener más información, vea la sección "Tipos anidados" en el tema de referencia del método
Type.MakeGenericType.

El atributo FlagsAttribute indica una clase especial de enumeración denominada campo de bits. El motor en
tiempo de ejecución no distingue entre enumeraciones tradicionales y campos de bits, pero el lenguaje podría
hacerlo. Cuando se hace esta distinción, se pueden utilizar operadores bit a bit en estos campos de bits, para
generar valores sin nombre, pero no en las enumeraciones. Normalmente, las enumeraciones se utilizan para
listas de elementos únicos, como los días de la semana, los nombres de países o regiones, etc. Los campos de
bits se utilizan, en general, para listas de calidades o cantidades que pueden producirse en combinaciones, como
Red And Big And Fast .

En el ejemplo siguiente se muestra cómo utilizar los campos de bits y las enumeraciones tradicionales.
using System;
using System.Collections.Generic;

// A traditional enumeration of some root vegetables.


public enum SomeRootVegetables
{
HorseRadish,
Radish,
Turnip
}

// A bit field or flag enumeration of harvesting seasons.


[Flags]
public enum Seasons
{
None = 0,
Summer = 1,
Autumn = 2,
Winter = 4,
Spring = 8,
All = Summer | Autumn | Winter | Spring
}

public class Example


{
public static void Main()
{
// Hash table of when vegetables are available.
Dictionary<SomeRootVegetables, Seasons> AvailableIn = new Dictionary<SomeRootVegetables, Seasons>();

AvailableIn[SomeRootVegetables.HorseRadish] = Seasons.All;
AvailableIn[SomeRootVegetables.Radish] = Seasons.Spring;
AvailableIn[SomeRootVegetables.Turnip] = Seasons.Spring |
Seasons.Autumn;

// Array of the seasons, using the enumeration.


Seasons[] theSeasons = new Seasons[] { Seasons.Summer, Seasons.Autumn,
Seasons.Winter, Seasons.Spring };

// Print information of what vegetables are available each season.


foreach (Seasons season in theSeasons)
{
Console.Write(String.Format(
"The following root vegetables are harvested in {0}:\n",
season.ToString("G")));
foreach (KeyValuePair<SomeRootVegetables, Seasons> item in AvailableIn)
{
// A bitwise comparison.
if (((Seasons)item.Value & season) > 0)
Console.Write(String.Format(" {0:G}\n",
(SomeRootVegetables)item.Key));
}
}
}
}
// The example displays the following output:
// The following root vegetables are harvested in Summer:
// HorseRadish
// The following root vegetables are harvested in Autumn:
// Turnip
// HorseRadish
// The following root vegetables are harvested in Winter:
// HorseRadish
// The following root vegetables are harvested in Spring:
// Turnip
// Radish
// HorseRadish
Imports System.Collections.Generic

' A traditional enumeration of some root vegetables.


Public Enum SomeRootVegetables
HorseRadish
Radish
Turnip
End Enum

' A bit field or flag enumeration of harvesting seasons.


<Flags()> Public Enum Seasons
None = 0
Summer = 1
Autumn = 2
Winter = 4
Spring = 8
All = Summer Or Autumn Or Winter Or Spring
End Enum

' Entry point.


Public Class Example
Public Shared Sub Main()
' Hash table of when vegetables are available.
Dim AvailableIn As New Dictionary(Of SomeRootVegetables, Seasons)()

AvailableIn(SomeRootVegetables.HorseRadish) = Seasons.All
AvailableIn(SomeRootVegetables.Radish) = Seasons.Spring
AvailableIn(SomeRootVegetables.Turnip) = Seasons.Spring Or _
Seasons.Autumn

' Array of the seasons, using the enumeration.


Dim theSeasons() As Seasons = {Seasons.Summer, Seasons.Autumn, _
Seasons.Winter, Seasons.Spring}

' Print information of what vegetables are available each season.


For Each season As Seasons In theSeasons
Console.WriteLine(String.Format( _
"The following root vegetables are harvested in {0}:", _
season.ToString("G")))
For Each item As KeyValuePair(Of SomeRootVegetables, Seasons) In AvailableIn
' A bitwise comparison.
If (CType(item.Value, Seasons) And season) > 0 Then
Console.WriteLine(" " + _
CType(item.Key, SomeRootVegetables).ToString("G"))
End If
Next
Next
End Sub
End Class
' The example displays the following output:
' The following root vegetables are harvested in Summer:
' HorseRadish
' The following root vegetables are harvested in Autumn:
' Turnip
' HorseRadish
' The following root vegetables are harvested in Winter:
' HorseRadish
' The following root vegetables are harvested in Spring:
' Turnip
' Radish
' HorseRadish

Interfaces
Una interfaz define un contrato que especifica una relación de lo que se puede hacer o una relación de lo que se
tiene. Las interfaces se utilizan a menudo para implementar una funcionalidad, como comparar y ordenar
(interfaces IComparable e IComparable<T>), comprobar la igualdad (interfaz IEquatable<T>) o enumerar los
elementos de una colección (interfaces IEnumerable e IEnumerable<T>). Las interfaces pueden tener
propiedades, métodos y eventos, que son todos miembros abstractos; es decir, aunque la interfaz define los
miembros y sus firmas, deja que el tipo encargado de implementar la interfaz defina la funcionalidad de cada
miembro de la interfaz. Esto significa que cualquier clase o estructura que implemente una interfaz debe
proporcionar definiciones de los miembros abstractos declarados en la interfaz. Una interfaz puede necesitar que
cualquier clase o estructura que implemente una interfaz implemente también otras interfaces.
A las interfaces se les aplican las restricciones siguientes:
Una interfaz se puede declarar con cualquier tipo de accesibilidad, pero los miembros de la interfaz deben
tener todos accesibilidad pública.
Las interfaces no pueden definir constructores
Las interfaces no pueden definir campos.
Las interfaces solo pueden definir miembros de instancia. No pueden definir miembros estáticos.
Cada lenguaje debe proporcionar reglas para asignar una implementación a la interfaz que necesita el miembro,
ya que varias interfaces pueden declarar un miembro con la misma firma y esos miembros pueden tener
implementaciones independientes.
Delegados
Los delegados son tipos de referencia con una finalidad similar a la de los punteros a función de C++. Se usan
para los controladores de eventos y las funciones de devolución de llamada en .NET. A diferencia de los punteros
a función, los delegados son seguros, se pueden comprobar y proporcionan seguridad de tipos. Un tipo de
delegado puede representar cualquier método de instancia o método estático que tenga una firma compatible.
Un parámetro de un delegado es compatible con el parámetro correspondiente de un método si el tipo del
parámetro del delegado es más restrictivo que el del método, porque así se garantiza que el argumento que se
pase al delegado también se podrá pasar de forma segura al método.
De forma similar, el tipo de valor devuelto de un delegado es compatible con el tipo de valor devuelto de un
método si el del método es más restrictivo que el del delegado, porque así se garantiza que el tipo de valor
devuelto por el método se puede convertir con seguridad al tipo de valor devuelto del delegado.
Por ejemplo, un delegado que tiene un parámetro de tipo IEnumerable y un tipo de valor devuelto Object puede
representar un método que tiene un parámetro de tipo Object y un valor devuelto de tipo IEnumerable. Para
obtener más información y un código de ejemplo, vea Delegate.CreateDelegate(Type, Object, MethodInfo).
Se dice que un delegado está enlazado al método que representa. Además de estar enlazado al método, un
delegado puede estar enlazado a un objeto. El objeto representa el primer parámetro del método y se pasa al
método cada vez que se invoca el delegado. Si el método es un método de instancia, el objeto enlazado se pasa
como el parámetro this implícito ( Me en Visual Basic); si el método es estático, el objeto se pasa como primer
parámetro formal del método y la firma del delegado debe coincidir con los parámetros restantes. Para obtener
más información y un código de ejemplo, vea System.Delegate.
Todos los delegados heredan de System.MulticastDelegate, que hereda de System.Delegate. Los lenguajes C#,
Visual Basic y C++ no permiten que se herede de estos tipos. En su lugar, proporcionan palabras clave para
declarar los delegados.
Dado que los delegados heredan de MulticastDelegate, un delegado tiene una lista de invocación, que es una
lista de métodos que representa el delegado y que se ejecutan cuando se llama al delegado. Todos los métodos
de la lista reciben los argumentos proporcionados cuando se invoca al delegado.
NOTE
El valor devuelto no se define para los delegados que tienen más de un método en su lista de invocación, aunque el
delegado tenga un tipo de valor devuelto.

En muchos casos, como en el de los métodos de devolución de llamada, un delegado solo representa un método
y las únicas acciones que se deben llevar a cabo son la creación y la invocación del delegado.
Por lo que se refiere a los delegados que representan varios métodos, .NET proporciona métodos de las clases de
delegado Delegate y MulticastDelegate para operaciones tales como agregar un método a una lista de
invocación del delegado (el método Delegate.Combine), quitar un método (el método Delegate.Remove) y
obtener la lista de invocación (el método Delegate.GetInvocationList).

NOTE
No es preciso usar estos métodos para los delegados de controladores de eventos en C#, C++ ni Visual Basic, ya que estos
lenguajes proporcionan sintaxis para agregar y quitar controladores de eventos.

Definiciones de tipo
Una definición de tipo incluye lo siguiente:
Los atributos definidos en el tipo.
La accesibilidad del tipo (visibilidad).
El nombre del tipo.
El tipo base del tipo.
Las interfaces que implementa el tipo.
Las definiciones de todos los miembros del tipo
Atributos
Los atributos proporcionan metadatos adicionales definidos por el usuario . Normalmente, se emplean para
almacenar información adicional sobre un tipo en su ensamblado o para modificar el comportamiento de un
miembro de tipo en tiempo de diseño o en tiempo de ejecución.
Los atributos son clases que heredan de System.Attribute. Los lenguajes que admiten el uso de atributos tienen
su propia sintaxis para aplicar atributos a un elemento del lenguaje. Los atributos se pueden aplicar a casi
cualquier elemento del lenguaje; los elementos específicos a los que se puede aplicar un atributo los define la
clase AttributeUsageAttribute aplicada a esa clase de atributos.
Accesibilidad a tipos
Todos los tipos tienen un modificador que rige su accesibilidad desde otros tipos. En la tabla siguiente se
describen las accesibilidades a tipos que admite el motor en tiempo de ejecución.

A C C ESIB IL IDA D DESC RIP C IÓ N

public Todos los ensamblados pueden tener acceso al tipo.

ensamblado El tipo sólo es accesible desde su ensamblado.

La accesibilidad de un tipo anidado depende de su dominio de accesibilidad, que viene determinado por la
accesibilidad declarada del miembro y el dominio de accesibilidad del tipo contenedor inmediato. Sin embargo,
el dominio de accesibilidad de un tipo anidado no puede superar al del tipo contenedor.
El dominio de accesibilidad de un miembro anidado M declarado en un tipo T dentro de un programa P se
define de la manera siguiente (teniendo en cuenta que el propio miembro M puede ser un tipo):
Si la accesibilidad declarada de M es public , el dominio de accesibilidad de M es el dominio de
accesibilidad de T .
Si la accesibilidad declarada de M es protected internal , el dominio de accesibilidad de M es la
intersección del dominio de accesibilidad de T con el texto de programa de P y el texto de programa de
cualquier tipo derivado de T declarado fuera de P .
Si la accesibilidad declarada de M es protected , el dominio de accesibilidad de M es la intersección del
dominio de accesibilidad de T con el texto de programa de T y cualquier tipo derivado de T .
Si la accesibilidad declarada de M es internal , el dominio de accesibilidad de M es la intersección del
dominio de accesibilidad de T con el texto de programa de P .
Si la accesibilidad declarada de M es private , el dominio de accesibilidad de M es el texto de programa
de T .
Nombres de tipo
El sistema de tipos común sólo impone dos restricciones en los nombres:
Todos los nombres se codifican como cadenas de caracteres Unicode (de 16 bits).
Los nombres no pueden tener un valor incrustado (de 16 bits) de 0x0000.
Sin embargo, la mayoría de los lenguajes imponen restricciones adicionales sobre los nombres de tipo. Todas las
comparaciones se realizan byte a byte, por lo que distinguen entre mayúsculas y minúsculas y son
independientes de la configuración regional.
Aunque un tipo puede hacer referencia a tipos de otros módulos y ensamblados, es preciso que se defina
íntegramente en un solo módulo de .NET. (Sin embargo, según la compatibilidad del compilador, se puede dividir
en varios archivos de código fuente.) Los nombres de tipo solo tienen que ser únicos dentro de un espacio de
nombres. Para identificar íntegramente un tipo, su nombre debe calificarse con el espacio de nombres que
contiene la implementación del tipo.
Tipos base e interfaces
Un tipo puede heredar valores y comportamientos de otro. El sistema de tipos común no permite que los tipos
hereden de más de un tipo base.
Un tipo puede implementar cualquier número de interfaces. Para implementar una interfaz, un tipo debe
implementar todos los miembros virtuales de la interfaz. Un tipo derivado puede implementar un método
virtual, que se puede invocar estática o dinámicamente.

Miembros de tipos
El motor en tiempo de ejecución permite definir miembros de tipos, que especifican el comportamiento y el
estado de los tipos. Los miembros de tipos incluyen lo siguiente:
Campos
Propiedades
Métodos
Constructores
Eventos
Tipos anidados
Campos
Un campo describe y contiene parte del estado del tipo. Los campos pueden ser de cualquier tipo que admita el
motor en tiempo de ejecución. Normalmente, los campos son de tipo private o protected , por lo que son
accesibles únicamente desde la clase o desde una clase derivada. Si el valor de un campo se puede modificar
desde fuera de su tipo, se suele emplear un descriptor de acceso set de una propiedad. Los campos expuestos
públicamente suelen ser de solo lectura y pueden ser de dos tipos:
Constantes, cuyo valor se asigna en tiempo de diseño. Se trata de miembros estáticos de una clase,
aunque no se definen mediante la palabra clave static ( Shared en Visual Basic).
Variables de solo lectura, cuyos valores se pueden asignar en el constructor de clase.
En el ejemplo siguiente se muestran estos dos usos de los campos de solo lectura.

using System;

public class Constants


{
public const double Pi = 3.1416;
public readonly DateTime BirthDate;

public Constants(DateTime birthDate)


{
this.BirthDate = birthDate;
}
}

public class Example


{
public static void Main()
{
Constants con = new Constants(new DateTime(1974, 8, 18));
Console.Write(Constants.Pi + "\n");
Console.Write(con.BirthDate.ToString("d") + "\n");
}
}
// The example displays the following output if run on a system whose current
// culture is en-US:
// 3.1416
// 8/18/1974
Public Class Constants
Public Const Pi As Double = 3.1416
Public ReadOnly BirthDate As Date

Public Sub New(birthDate As Date)


Me.BirthDate = birthDate
End Sub
End Class

Public Module Example


Public Sub Main()
Dim con As New Constants(#8/18/1974#)
Console.WriteLine(Constants.Pi.ToString())
Console.WriteLine(con.BirthDate.ToString("d"))
End Sub
End Module
' The example displays the following output if run on a system whose current
' culture is en-US:
' 3.1416
' 8/18/1974

Propiedades
Una propiedad identifica un valor o un estado del tipo y define los métodos para obtener o establecer el valor de
la propiedad. Las propiedades pueden ser tipos primitivos, colecciones de tipos primitivos, tipos definidos por el
usuario o colecciones de tipos definidos por el usuario. Las propiedades se usan a menudo para que la interfaz
pública de un tipo se mantenga independiente de la representación real del tipo. De este modo, las propiedades
pueden reflejar valores que no están almacenados directamente en la clase (por ejemplo, cuando una propiedad
devuelve un valor calculado) o realizar la validación antes de que se asignen valores a campos privados. En el
ejemplo siguiente se muestra el último modelo.

using System;

public class Person


{
private int m_Age;

public int Age


{
get { return m_Age; }
set {
if (value < 0 || value > 125)
{
throw new ArgumentOutOfRangeException("The value of the Age property must be between 0 and
125.");
}
else
{
m_Age = value;
}
}
}
}
Public Class Person
Private m_Age As Integer

Public Property Age As Integer


Get
Return m_Age
End Get
Set
If value < 0 Or value > 125 Then
Throw New ArgumentOutOfRangeException("The value of the Age property must be between 0 and
125.")
Else
m_Age = value
End If
End Set
End Property
End Class

Además de incluir la propiedad propiamente dicha, el Lenguaje Intermedio de Microsoft (MSIL) de un tipo que
contiene una propiedad de lectura incluye un método get_ propertyname y el lenguaje MSIL de un tipo que
contiene una propiedad de escritura incluye un método set_ propertyname.
Métodos
Un método describe las operaciones que están disponibles en el tipo. La firma de un método especifica los tipos
permitidos de todos sus parámetros y de su valor devuelto.
Aunque la mayoría de los métodos definen el número exacto de los parámetros necesarios para las llamadas a
métodos, algunos admiten un número de parámetros que es variable. El último parámetro declarado de estos
métodos se marca con el atributo ParamArrayAttribute. Normalmente, los compiladores de lenguaje
proporcionan una palabra clave, como params en C# y ParamArray en Visual Basic, que hace que sea innecesario
el uso explícito de ParamArrayAttribute.
Constructores
Un constructor es un tipo de método especial que crea nuevas instancias de una clase o una estructura. Al igual
que cualquier otro método, un constructor puede incluir parámetros; sin embargo, los constructores no tienen
ningún valor devuelto (es decir, devuelven void ).
Si el código fuente de una clase no define explícitamente un constructor, el compilador incluye un constructor sin
parámetros. Sin embargo, si el código fuente de una clase define solo constructores parametrizados, los
compiladores de Visual Basic y C# no generan un constructor sin parámetros.
Si el código fuente de una estructura define constructores, estos deben tener parámetros; una estructura no
puede definir un constructor sin parámetros y los compiladores no generan constructores sin parámetros para
las estructuras u otros tipos de valor. Todos los tipos de valor tienen un constructor sin parámetros implícito.
Common Language Runtime implementa este constructor, que inicializa todos los campos de la estructura en sus
valores predeterminados.
Events
Un evento define un incidente al que se puede responder, así como los métodos para suscribirse a un evento,
anular la suscripción y generar el evento. Los eventos se usan con frecuencia para informar a otros tipos de
cambios de estado. Para más información, vea Eventos.
Tipos anidados
Un tipo anidado es un tipo que es un miembro de algún otro tipo. Los tipos anidados deben estar estrechamente
acoplados a su tipo contenedor y no deben ser útiles como tipos de uso general. Los tipos anidados son útiles
cuando el tipo declarativo utiliza y crea instancias del tipo anidado y el uso de dicho tipo anidado no se expone
en miembros públicos.
Los tipos anidados resultan confusos para algunos desarrolladores y no deben estar públicamente visibles a
menos que haya una razón de peso. En una biblioteca bien diseñada, los desarrolladores rara vez deberían tener
que utilizar tipos anidados para crear instancias de objetos o declarar variables.

Características de los miembros de tipos


Common Type System permite que los miembros de tipos tengan diversas características; sin embargo, no es
necesario que los lenguajes admitan todas estas características. En la siguiente tabla se describen las
características de los miembros.

C A RA C T ERÍST IC A SE P UEDE A P L IC A R A DESC RIP C IÓ N

abstract Métodos, propiedades y eventos El tipo no proporciona la


implementación del método. Los tipos
que heredan o implementan métodos
abstractos deben proporcionar una
implementación para el método. La
única excepción es que el tipo derivado
sea un tipo abstracto. Todos lo
métodos abstractos son virtuales.

private, family, assembly, family y Todas Define la accesibilidad del miembro:


assembly, family o assembly, o public
private
Solo es accesible desde el mismo tipo
que el miembro o desde un tipo
anidado.

family
Accesible desde el mismo tipo que el
miembro y desde tipos derivados que
heredan de él.

ensamblado
Accesible sólo en el ensamblado en que
está definido el tipo.

family y assembly
Accesible sólo desde los tipos que
estén calificados para el acceso de
familia y ensamblado.

family o assembly
Accesible sólo desde los tipos que
califican el acceso de familia o
ensamblado.

public
Accesible desde cualquier tipo.

final Métodos, propiedades y eventos El método virtual no puede ser


reemplazado en un tipo derivado.

initialize-only Campos El valor sólo se puede inicializar y no se


puede escribir en él después de la
inicialización.
C A RA C T ERÍST IC A SE P UEDE A P L IC A R A DESC RIP C IÓ N

instancia Campos, métodos, propiedades y Si un miembro no está marcado como


eventos static (C# y C++), Shared (Visual
Basic), virtual (C# y C++) u
Overridable (Visual Basic), es un
miembro de instancia (no hay palabra
clave de la instancia). En la memoria
habrá tantas copias de estos miembros
como objetos que los utilicen.

Literal Campos El valor asignado al campo es un valor


fijo, conocido en tiempo de
compilación, de un tipo de valor
integrado. Los campos literales, a
veces, se conocen como constantes.

newslot u override Todas Define cómo interactúa el miembro con


los miembros heredados que tienen la
misma firma:

newslot
Oculta los miembros heredados que
tienen la misma firma.

override
Reemplaza la definición de un método
virtual heredado.

El valor predeterminado es newslot.

estático Campos, métodos, propiedades y El miembro pertenece al tipo en que


eventos está definido, no a una instancia
particular del tipo. El miembro existe
incluso si no se ha creado ninguna
instancia del tipo y lo comparten todas
las instancias del tipo.

virtual Métodos, propiedades y eventos Un tipo derivado puede implementar el


método, que se puede invocar estática
o dinámicamente. Si se usa la
invocación dinámica, el tipo de la
instancia que hace la llamada en
tiempo de ejecución (en lugar del tipo
conocido en tiempo de compilación)
determina a qué implementación del
método se llama. Para invocar un
método virtual de manera estática, es
posible que haya que convertir la
variable en un tipo que use la versión
deseada del método.

Sobrecarga
Cada miembro de tipo tiene una firma única. Las firmas de método están formadas por el nombre del método y
una lista de parámetros (el orden y los tipos de los argumentos del método). Se pueden definir varios métodos
con el mismo nombre dentro un tipo, siempre y cuando sus firmas sean distintas. Cuando se definen dos o más
métodos con el mismo nombre se dice que el método está sobrecargado. Por ejemplo, en System.Char, se
reemplaza el método IsDigit. Un método toma un argumento de tipo Char. El otro método toma un argumento
de tipo String y un argumento de tipo Int32.
NOTE
El tipo de valor devuelto no se considera parte de la firma de un método. Es decir, no se pueden sobrecargar los métodos
si solo difieren en el tipo de valor devuelto.

Herencia, invalidación y ocultación de miembros


Un tipo derivado hereda todos los miembros de su tipo base, es decir, estos miembros se definen en el tipo
derivado y están disponibles para él. El comportamiento o cualidades de los miembros heredados se puede
modificar de dos maneras:
Un tipo derivado puede ocultar un miembro heredado definiendo un nuevo miembro con la misma firma.
Esto puede hacerse para convertir un miembro público en privado o para definir un nuevo
comportamiento para un método heredado que está marcado como final .
Un tipo derivado puede reemplazar a un método virtual heredado. El método de reemplazo proporciona
una nueva definición del método que se invocará según el tipo del valor en tiempo de ejecución y no el
tipo de la variable conocido en tiempo de compilación. Un método puede invalidar un método virtual
únicamente si el método virtual no está marcado como final y el nuevo método es, al menos, tan
accesible como el método virtual.

Vea también
Explorador de API de .NET
Common Language Runtime
Conversión de tipos en .NET
Independencia del lenguaje y componentes
independientes del lenguaje
16/09/2020 • 108 minutes to read • Edit Online

.NET es independiente del lenguaje. Esto significa que, como desarrollador, puede desarrollar en uno de los muchos
lenguajes que tienen como destino las implementaciones de .NET, como C#, F# y Visual Basic. Puede tener acceso a
los tipos y miembros de bibliotecas de clases desarrollados para implementaciones de .NET sin tener que conocer
el lenguaje en el que se escribieron originalmente y sin tener que seguir las convenciones del lenguaje original. Si
es un desarrollador de componentes, puede tener acceso al componente desde cualquier aplicación .NET con
independencia de su lenguaje.

NOTE
En la primera parte de este artículo se describe la creación de componentes independientes del lenguaje; es decir,
componentes que pueden usarse en aplicaciones escritas en cualquier lenguaje. También puede crear una aplicación o
componente únicos de código fuente escrito en varios lenguajes; consulte Interoperabilidad entre lenguajes en la segunda
parte de este artículo.

Para que los objetos puedan tener una interacción total con otros objetos escritos en cualquier lenguaje, estos
objetos solo deben exponer a los llamadores las características que son comunes a todos los lenguajes. Este
conjunto común de características se define mediante Common Language Specification (CLS), que es un conjunto
de reglas que se aplican a los ensamblados generados. Common Language Specification se define en el apartado I,
cláusulas 7 a 11 del estándar ECMA-335: Common Language Infrastructure.
Si el componente se ajusta a Common Language Specification, existe la garantía de que será conforme a CLS y que
será accesible desde el código de un ensamblado escrito en cualquier lenguaje de programación que admita CLS.
Para determinar si el componente se ajusta o no a Common Language Specification en tiempo de compilación,
puede aplicar el atributo CLSCompliantAttribute en el código fuente. Para más información, consulte
CLSCompliantAttribute (Atributo).

Reglas de conformidad con CLS


En esta sección se explican las reglas para crear un componente conforme a CLS. Para obtener una lista completa
de las normas, vea el apartado I, cláusula 11 del estándar ECMA-335: Common Language Infrastructure.

NOTE
Common Language Specification describe en cada regla la conformidad con CLS en referencia a los consumidores
(desarrolladores que acceden mediante programación a un componente que es conforme a CLS), los marcos (desarrolladores
que usan un compilador de lenguaje para crear bibliotecas conformes a CLS) y los extensores (desarrolladores que crean una
herramienta, como un compilador de lenguaje o un analizador de código, que crea componentes conformes a CLS). Este
artículo se centra en las reglas que se aplican a los marcos. Pero observe que algunas de las reglas que se aplican a los
extensores también se pueden aplicar a los ensamblados que se crean mediante Reflection.Emit.

Para diseñar un componente que sea independiente del lenguaje, solo tiene que aplicar las reglas de conformidad
con CLS a la interfaz pública del componente. La implementación privada no tiene que ajustarse a la especificación.
IMPORTANT
Las reglas de conformidad con CLS solo se aplican a la interfaz pública de un componente, y no a su implementación privada.

Por ejemplo, los números enteros sin signo distintos de Byte no son conformes a CLS. Dado que la clase Person
del siguiente ejemplo expone una propiedad Age del tipo UInt16, el código siguiente desencadena una advertencia
del compilador.

using System;

[assembly: CLSCompliant(true)]

public class Person


{
private UInt16 personAge = 0;

public UInt16 Age


{ get { return personAge; } }
}
// The attempt to compile the example displays the following compiler warning:
// Public1.cs(10,18): warning CS3003: Type of 'Person.Age' is not CLS-compliant

<Assembly: CLSCompliant(True)>

Public Class Person


Private personAge As UInt16

Public ReadOnly Property Age As UInt16


Get
Return personAge
End Get
End Property
End Class
' The attempt to compile the example displays the following compiler warning:
' Public1.vb(9) : warning BC40027: Return type of function 'Age' is not CLS-compliant.
'
' Public ReadOnly Property Age As UInt16
' ~~~

Para hacer que la clase Person sea conforme a CLS, puede cambiar el tipo de la propiedad Age de UInt16 a Int16,
que es un entero de 16 bits con signo conforme a CLS. No es necesario que cambie el tipo del campo privado
personAge .

using System;

[assembly: CLSCompliant(true)]

public class Person


{
private Int16 personAge = 0;

public Int16 Age


{ get { return personAge; } }
}
<Assembly: CLSCompliant(True)>

Public Class Person


Private personAge As UInt16

Public ReadOnly Property Age As Int16


Get
Return CType(personAge, Int16)
End Get
End Property
End Class

Las interfaces públicas de una biblioteca se componen de los elementos siguientes:


Definiciones de clases públicas.
Definiciones de los miembros públicos de las clases públicas y de los miembros accesibles por las clases
derivadas (es decir, miembros protegidos).
Parámetros y tipos devueltos de los métodos públicos de las clases públicas y parámetros y tipos devueltos
de los métodos accesibles por las clases derivadas.
Las reglas de conformidad con CLS se muestran en la tabla siguiente. El texto de las normas se toma literalmente
del estándar ECMA-335: Common Language Infrastructure, Copyright de 2012 de Ecma International. En las
secciones siguientes encontrará información más detallada sobre estas reglas.

C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Accesibilidad Accesibilidad de miembros Cuando se reemplacen 10


métodos heredados, no
debe modificarse la
accesibilidad, excepto cuando
se reemplace un método
heredado de un ensamblado
diferente cuya accesibilidad
sea family-or-assembly .
En este caso, el reemplazo
debe tener accesibilidad
family .
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Accesibilidad Accesibilidad de miembros La visibilidad y accesibilidad 12


de los tipos y miembros se
establecerá de modo que los
tipos de la signatura de
cualquier miembro sean
visibles y accesibles siempre
que el propio miembro sea
visible y accesible. Por
ejemplo, un método público
que sea visible fuera del
ensamblado no debe tener
ningún argumento cuyo tipo
solamente sea visible en el
interior del ensamblado. La
visibilidad y la accesibilidad
de los tipos que conforman
un tipo genérico con
instancias que se utilice en la
signatura de cualquier
miembro deben establecerse
de forma que serán visibles y
accesibles siempre que el
propio miembro sea visible y
accesible. Por ejemplo, un
tipo genérico con instancias
que esté presente en la
signatura de un miembro
que sea visible fuera del
ensamblado no debe tener
ningún argumento genérico
cuyo tipo solamente sea
visible en el interior del
ensamblado.

Matrices Matrices Las matrices deben tener 16


elementos con un tipo
conforme a CLS y los límites
inferiores de todas las
dimensiones de la matriz
deben ser iguales a cero.
Para distinguir entre
sobrecargas, solo se tendrá
en cuenta el hecho de que el
elemento es una matriz y el
tipo de elementos de la
matriz. Cuando la sobrecarga
se basa en dos o varios tipos
de matrices, los tipos de
elementos deben ser tipos
con nombre.

Atributos Atributos Los atributos deben ser del 41


tipo System.Attribute o
heredarse de este.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Atributos Atributos CLS solo permite un 34


subconjunto de
codificaciones de atributos
personalizados. Los únicos
tipos que deben aparecer en
estas codificaciones son (vea
el apartado IV): System.Type,
System.String, System.Char,
System.Boolean,
System.Byte, System.Int16,
System.Int32, System.Int64,
System.Single,
System.Double y cualquier
tipo de enumeración basada
en un tipo entero base
compatible con CLS.

Atributos Atributos CLS no admite modificadores 35


obligatorios que sean visibles
públicamente ( modreq , vea
el Apartado II), pero sí
admite modificadores
opcionales ( modopt , vea el
apartado II) que no
comprenda.

Constructores Constructores Un constructor de objetos 21


debe llamar a un constructor
de instancias de su clase
base antes de que tenga
lugar cualquier acceso a los
datos de instancia
heredados. (Esto no se aplica
a los tipos de valor, que no
necesitan constructores.)

Constructores Constructores No debe llamarse a los 22


constructores de objetos
excepto durante la creación
de un objeto y no podrá
iniciarse dos veces un objeto.

Enumeraciones Enumeraciones El tipo subyacente de una 7


enumeración debe ser un
tipo de entero integrado en
CLS, el nombre del campo
debe ser “value__” y dicho
campo debe marcarse como
RTSpecialName .
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Enumeraciones Enumeraciones Hay dos tipos distintos de 8


enumeraciones, que se
indican mediante la
presencia o ausencia del
atributo personalizado
System.FlagsAttribute
(consulte la biblioteca del
apartado IV). Uno
representa valores enteros
con nombre; el otro
representa los marcadores
de bit con nombre que se
pueden combinar para
generar un valor sin nombre.
El valor de enum no se
limita a los valores
especificados.

Enumeraciones Enumeraciones Los campos estáticos 9


literales de una enumeración
deben contener el tipo de la
propia enumeración.

Events Eventos Los métodos que 29


implementen un evento se
marcarán como
SpecialName en los
metadatos.

Events Eventos La accesibilidad de un evento 30


y sus descriptores de acceso
será idéntica.

Events Eventos Los métodos add y 31


remove de un evento
deben estar presentes o
ausentes a la vez.

Events Eventos Los métodos add y 32


remove de un evento
deben tomar un parámetro
cuyo tipo defina el tipo del
evento, y ese tipo debe
derivarse de
System.Delegate.

Events Eventos Los eventos deben adherirse 33


a un patrón de asignación de
nombres concreto. En las
comparaciones de nombres
correspondientes se omitirá
el atributo SpecialName
mencionado en la regla 29
de CLS y se seguirán las
reglas del identificador.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Excepciones Excepciones Los objetos que se inicien 40


deberán ser de tipo
System.Exception o de un
tipo que herede de él. No
obstante, los métodos
conformes a CLS no
necesitan bloquear la
propagación de otros tipos
de excepciones.

General Reglas de conformidad con Las reglas de CLS solo se 1


CLS aplican a las partes de los
tipos que son accesibles o
visibles desde fuera del
ensamblado de definición.

General Reglas de conformidad con Los miembros de tipos no 2


CLS conformes con CLS no
deben marcarse como
conformes con CLS.

Genéricos Miembros y tipos genéricos Los tipos anidados deben 42


tener, como mínimo, el
mismo número de
parámetros genéricos que el
tipo envolvente. Los
parámetros genéricos de un
tipo anidado se
corresponden por posición
con los parámetros
genéricos del tipo
contenedor.

Genéricos Miembros y tipos genéricos El nombre de un tipo 43


genérico debe codificar el
número de parámetros de
tipo declarados en el tipo no
anidado o que se acaban de
introducir en el tipo, si este
está anidado, según las
reglas definidas
anteriormente.

Genéricos Miembros y tipos genéricos Todo tipo genérico deberá 44


volver a declarar
restricciones suficientes
como para garantizar que
cualquier restricción de las
interfaces o del tipo base se
vea satisfecha por las
restricciones del tipo
genérico.

Genéricos Miembros y tipos genéricos Los tipos que se utilicen 45


como restricciones en
parámetros genéricos deben
ser conformes a CLS.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Genéricos Miembros y tipos genéricos Se entiende que la visibilidad 46


y accesibilidad de los
miembros (incluidos los tipos
anidados) de un tipo
genérico con instancias
deben quedar restringidas al
ámbito específico de la
creación de instancias, y no a
la declaración de tipos
genéricos en general.
Suponiendo que esto sea
cierto, se seguirán aplicando
las especificaciones de
accesibilidad y visibilidad de
la regla 12 de CLS.

Genéricos Miembros y tipos genéricos Cada método genérico 47


abstracto o virtual deberá
tener su propia
implementación concreta (no
abstracta) predeterminada

Interfaces Interfaces Las interfaces conformes a 18


CLS no deben requerir la
definición de métodos no
conformes a CLS para su
implementación.

Interfaces Interfaces Las interfaces conformes a 19


CLS no pueden definir
métodos estáticos ni pueden
definir campos.

Miembros Miembros de tipos en Los campos y métodos static 36


general globales no son conformes a
CLS.

Miembros -- El valor de un estático literal 13


se especifica mediante el uso
de metadatos de
inicialización de campos. Un
literal conforme a CLS debe
tener un valor especificado
en los metadatos de
inicialización de campos que
sea exactamente del mismo
tipo que el literal (o el tipo
subyacente, si el literal es
enum ).

Miembros Miembros de tipos en La restricción vararg no 15


general forma parte de CLS y la
única convención de llamada
admitida por CLS es la
convención de llamada
administrada estándar.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Convenciones de Convenciones de Los ensamblados seguirán 4


nomenclatura nomenclatura las directrices del anexo 7 del
informe técnico 15 del
estándar Unicode 3.0, que
rige el conjunto de
caracteres permitidos que
pueden usarse como iniciales
e incluirse en los
identificadores. Estas
directrices están disponibles
en línea en Formularios de
normalización Unicode. Los
identificadores deben
aparecer en el formato
canónico definido por el
Formulario C de
normalización Unicode. En
aras de la conformidad con
CLS, dos identificadores se
considerarán iguales si sus
asignaciones de minúsculas
(tal y como se especificó en
las asignaciones unívocas de
minúsculas de Unicode en
las que no se tiene en cuenta
la configuración regional)
son iguales. Es decir, para
que dos identificadores se
consideren diferentes según
CLS, tendrán que
diferenciarse en algo más
que en el uso de mayúsculas
y minúsculas. Pero para
invalidar una definición
heredada, CLI requiere que
se use la codificación exacta
de la declaración original.

Sobrecarga Convenciones de Todos los nombres 5


nomenclatura especificados en un ámbito
conforme a CLS deben ser
distintos
independientemente del
tipo, salvo en los casos en
los que los nombres sean
idénticos y se resuelvan
mediante sobrecarga. Es
decir, mientras CTS permite
que un tipo único use el
mismo nombre para un
método y un campo, CLS no.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Sobrecarga Convenciones de Los campos y los tipos 6


nomenclatura anidados deben distinguirse
únicamente por la
comparación de
identificadores, aunque CTS
permita que se distingan
signaturas diferentes. Los
métodos, las propiedades y
los eventos que tengan el
mismo nombre (por
comparación de
identificadores) deben
distinguirse por algo más
que el tipo de valor devuelto,
excepto según lo
especificado en la regla 39
de CLS.

Sobrecarga Sobrecargas Solo las propiedades y los 37


métodos se pueden
sobrecargar.

Sobrecarga Sobrecargas Las propiedades y los 38


métodos se pueden
sobrecargar únicamente en
función del número y los
tipos de sus parámetros,
excepto los operadores de
conversión denominados
op_Implicit y
op_Explicit , que también
se pueden sobrecargar en
función del tipo de valor
devuelto.

Sobrecarga -- Si dos o más de los métodos 48


conformes a CLS declarados
en un tipo tienen el mismo
nombre y, en un conjunto
específico de instancias de
tipos, tienen los mismos
tipos de valor devuelto y
parámetros, todos estos
métodos serán
semánticamente
equivalentes en esas
instancias de tipos.

Propiedades Propiedades Los métodos que 24


implementan los métodos de
captador y establecedor de
una propiedad deben estar
marcados con SpecialName
en los metadatos.

Propiedades Propiedades Todos los descriptores de 26


acceso de una propiedad
deben ser estáticos, virtuales
o de instancia.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Propiedades Propiedades El tipo de una propiedad es 27


el tipo de valor devuelto del
método captador y el tipo
del último argumento del
método establecedor. Los
tipos de los parámetros de la
propiedad deben ser los
tipos de los parámetros del
método captador y los tipos
de todos los parámetros del
método establecedor,
excepto el último. Todos
estos tipos deben ser
conformes a CLS y no
pueden ser punteros
administrados (es decir, no
deben pasarse por
referencia).

Propiedades Propiedades Las propiedades deben 28


ajustarse a un patrón de
asignación de nombres
concreto. En las
comparaciones de nombres
correspondientes se omitirá
el atributo SpecialName
mencionado en la regla 24
de CLS y se seguirán las
reglas del identificador. Una
propiedad tendrá un método
de captador, un método de
establecedor o ambos.

Conversión de tipos Conversión de tipos Si se proporciona op_Implicit 39


u op_Explicit, deben darse
medios alternativos para
realizar la conversión.

Tipos Signaturas de tipos y Los tipos de valor a los que 3


miembros de tipo se les ha aplicado la
conversión boxing no son
conformes a CLS.

Tipos Signaturas de tipos y Todos los tipos que aparecen 11


miembros de tipo en una signatura deben ser
conformes a CLS. Todos los
tipos que forman un tipo
genérico con instancias
deben ser conformes a CLS.

Tipos Signaturas de tipos y Las referencias a tipos no 14


miembros de tipo son conformes a CLS.

Tipos Signaturas de tipos y Los tipos de puntero no 17


miembros de tipo administrados no son
conformes a CLS.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Tipos Signaturas de tipos y Las interfaces, los tipos de 20


miembros de tipo valor y las clases conformes
a CLS no deben requerir la
implementación de
miembros no conformes a
CLS

Tipos Signaturas de tipos y System.Object es conforme a 23


miembros de tipo CLS. Cualquier otra clase
conforme a CLS se heredará
de una clase conforme a CLS.

Índice de las subsecciones:


Signaturas de tipos y miembros de tipo
Convenciones de nomenclatura
Conversión de tipos
Matrices
Interfaces
Enumeraciones
Miembros de tipos en general
Accesibilidad de miembros
Miembros y tipos genéricos
Constructores
Propiedades
Eventos
Sobrecargas
Excepciones
Atributos
Signaturas de tipos y miembros de tipo
El tipo System.Object es conforme a CLS y es el tipo base de todos los tipos de objetos del sistema de tipos de .NET
Framework. La herencia de .NET Framework es implícita (por ejemplo, la clase String hereda implícitamente de la
clase Object ) o explícita (por ejemplo, la clase CultureNotFoundException hereda explícitamente de la clase
ArgumentException, que a su vez hereda también explícitamente de la clase Exception). Para que un tipo derivado
sea conforme a CLS, su tipo base también debe ser conforme a CLS.
En el ejemplo siguiente se muestra un tipo derivado cuyo tipo base no es conforme a CLS. En el ejemplo, se define
una clase base Counter que usa un entero de 32 bits sin signo como contador. Dado que la clase proporciona la
funcionalidad de contador ajustando un entero sin signo, la clase se marca como no conforme a CLS. Como
resultado, la clase derivada, NonZeroCounter , tampoco es conforme a CLS.
using System;

[assembly: CLSCompliant(true)]

[CLSCompliant(false)]
public class Counter
{
UInt32 ctr;

public Counter()
{
ctr = 0;
}

protected Counter(UInt32 ctr)


{
this.ctr = ctr;
}

public override string ToString()


{
return $"{ctr}). ";
}

public UInt32 Value


{
get { return ctr; }
}

public void Increment()


{
ctr += (uint) 1;
}
}

public class NonZeroCounter : Counter


{
public NonZeroCounter(int startIndex) : this((uint) startIndex)
{
}

private NonZeroCounter(UInt32 startIndex) : base(startIndex)


{
}
}
// Compilation produces a compiler warning like the following:
// Type3.cs(37,14): warning CS3009: 'NonZeroCounter': base type 'Counter' is not
// CLS-compliant
// Type3.cs(7,14): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>

<CLSCompliant(False)> _
Public Class Counter
Dim ctr As UInt32

Public Sub New


ctr = 0
End Sub

Protected Sub New(ctr As UInt32)


ctr = ctr
End Sub

Public Overrides Function ToString() As String


Return $"{ctr}). "
End Function

Public ReadOnly Property Value As UInt32


Get
Return ctr
End Get
End Property

Public Sub Increment()


ctr += CType(1, UInt32)
End Sub
End Class

Public Class NonZeroCounter : Inherits Counter


Public Sub New(startIndex As Integer)
MyClass.New(CType(startIndex, UInt32))
End Sub

Private Sub New(startIndex As UInt32)


MyBase.New(CType(startIndex, UInt32))
End Sub
End Class
' Compilation produces a compiler warning like the following:
' Type3.vb(34) : warning BC40026: 'NonZeroCounter' is not CLS-compliant
' because it derives from 'Counter', which is not CLS-compliant.
'
' Public Class NonZeroCounter : Inherits Counter
' ~~~~~~~~~~~~~~

Todos los tipos que aparecen en las signaturas de miembros, incluidos los tipos de propiedades y los tipos de
valores devueltos de un método, deben ser conformes a CLS. Además, en el caso de los tipos genéricos:
Todos los tipos que forman un tipo genérico con instancias deben ser conformes a CLS.
Todos los tipos que se utilizan como restricciones en parámetros genéricos deben ser conformes a CLS.
El sistema de tipos común de .NET incluye varios tipos integrados que se admiten directamente en Common
Language Runtime y que se codifican de forma especial en los metadatos de un ensamblado. De estos tipos
intrínsecos, los tipos enumerados en la tabla siguiente son conformes a CLS.

T IP O C O N F O RM E A C L S DESC RIP C IÓ N

Byte Entero de 8 bits sin signo

Int16 Entero de 16 bits con signo


T IP O C O N F O RM E A C L S DESC RIP C IÓ N

Int32 Entero de 32 bits con signo

Int64 Entero de 64 bits con signo

Single Valor de punto flotante de precisión sencilla

Double Valor de punto flotante de precisión doble

Boolean tipo de valor true o false

Char Unidad de código con la codificación UTF-16

Decimal Número decimal de punto no flotante

IntPtr Puntero o identificador de un tamaño definido por la


plataforma

String Colección de cero, uno o varios objetos Char

Los tipos intrínsecos enumerados en la tabla siguiente no son conformes a CLS.

T IP O N O C O N F O RM E DESC RIP C IÓ N A LT ERN AT IVA C O N F O RM E A C L S

SByte Tipo de datos enteros de 8 bits con Int16


signo

UInt16 Entero de 16 bits sin signo Int32

UInt32 Entero de 32 bits sin signo Int64

UInt64 Entero de 64 bits sin signo Int64 (se puede desbordar), BigInteger
o Double

UIntPtr Puntero o identificador sin signo IntPtr

La biblioteca de clases de .NET Framework o cualquier otra biblioteca de clases puede incluir otros tipos que no
sean conformes a CLS; por ejemplo:
Tipos de valor a los que se les ha aplicado la conversión boxing. En el siguiente ejemplo de C# se crea una clase
con una propiedad pública de tipo int* denominada Value . Dado que int* es un tipo de valor al que se le ha
aplicado la conversión boxing, el compilador lo marca como no conforme a CLS.
using System;

[assembly:CLSCompliant(true)]

public unsafe class TestClass


{
private int* val;

public TestClass(int number)


{
val = (int*) number;
}

public int* Value {


get { return val; }
}
}
// The compiler generates the following output when compiling this example:
// warning CS3003: Type of 'TestClass.Value' is not CLS-compliant

Referencias con establecimiento de tipos, que son construcciones especiales que contienen una referencia a un
objeto y una referencia a un tipo.
Si un tipo no es conforme a CLS, debe aplicar el atributo CLSCompliantAttribute con un parámetro isCompliant con
un valor de false en él. Para obtener más información, consulte la sección CLSCompliantAttribute (Atributo).
En el ejemplo siguiente se muestra el problema de la conformidad con CLS en la creación de instancias de tipos
genéricos y signaturas de métodos. En este ejemplo, se define una clase InvoiceItem con una propiedad de tipo
UInt32, una propiedad de tipo Nullable(Of UInt32) y un constructor con parámetros de tipo UInt32 y
Nullable(Of UInt32) . Cuando intente compilar este ejemplo, aparecerán cuatro advertencias del compilador.
using System;

[assembly: CLSCompliant(true)]

public class InvoiceItem


{
private uint invId = 0;
private uint itemId = 0;
private Nullable<uint> qty;

public InvoiceItem(uint sku, Nullable<uint> quantity)


{
itemId = sku;
qty = quantity;
}

public Nullable<uint> Quantity


{
get { return qty; }
set { qty = value; }
}

public uint InvoiceId


{
get { return invId; }
set { invId = value; }
}
}
// The attempt to compile the example displays the following output:
// Type1.cs(13,23): warning CS3001: Argument type 'uint' is not CLS-compliant
// Type1.cs(13,33): warning CS3001: Argument type 'uint?' is not CLS-compliant
// Type1.cs(19,26): warning CS3003: Type of 'InvoiceItem.Quantity' is not CLS-compliant
// Type1.cs(25,16): warning CS3003: Type of 'InvoiceItem.InvoiceId' is not CLS-compliant
<Assembly: CLSCompliant(True)>

Public Class InvoiceItem

Private invId As UInteger = 0


Private itemId As UInteger = 0
Private qty AS Nullable(Of UInteger)

Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))


itemId = sku
qty = quantity
End Sub

Public Property Quantity As Nullable(Of UInteger)


Get
Return qty
End Get
Set
qty = value
End Set
End Property

Public Property InvoiceId As UInteger


Get
Return invId
End Get
Set
invId = value
End Set
End Property
End Class
' The attempt to compile the example displays output similar to the following:
' Type1.vb(13) : warning BC40028: Type of parameter 'sku' is not CLS-compliant.
'
' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
' ~~~
' Type1.vb(13) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'
' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
' ~~~~~~~~
' Type1.vb(18) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'
' Public Property Quantity As Nullable(Of UInteger)
' ~~~~~~~~
' Type1.vb(27) : warning BC40027: Return type of function 'InvoiceId' is not CLS-compliant.
'
' Public Property InvoiceId As UInteger

Para eliminar las advertencias del compilador, reemplace los tipos no conformes a CLS de la interfaz pública de
InvoiceItem por tipos conformes:
using System;

[assembly: CLSCompliant(true)]

public class InvoiceItem


{
private uint invId = 0;
private uint itemId = 0;
private Nullable<int> qty;

public InvoiceItem(int sku, Nullable<int> quantity)


{
if (sku <= 0)
throw new ArgumentOutOfRangeException("The item number is zero or negative.");
itemId = (uint) sku;

qty = quantity;
}

public Nullable<int> Quantity


{
get { return qty; }
set { qty = value; }
}

public int InvoiceId


{
get { return (int) invId; }
set {
if (value <= 0)
throw new ArgumentOutOfRangeException("The invoice number is zero or negative.");
invId = (uint) value; }
}
}
Assembly: CLSCompliant(True)>

Public Class InvoiceItem

Private invId As UInteger = 0


Private itemId As UInteger = 0
Private qty AS Nullable(Of Integer)

Public Sub New(sku As Integer, quantity As Nullable(Of Integer))


If sku <= 0 Then
Throw New ArgumentOutOfRangeException("The item number is zero or negative.")
End If
itemId = CUInt(sku)
qty = quantity
End Sub

Public Property Quantity As Nullable(Of Integer)


Get
Return qty
End Get
Set
qty = value
End Set
End Property

Public Property InvoiceId As Integer


Get
Return CInt(invId)
End Get
Set
invId = CUInt(value)
End Set
End Property
End Class

Además de los tipos específicos indicados, algunas categorías de tipos no son conformes a CLS. Entre estas
categorías se incluyen tipos de punteros no administrados y tipos de punteros de función. En el ejemplo siguiente
se genera una advertencia del compilador, ya que se utiliza un puntero a un entero para crear una matriz de
enteros.

using System;

[assembly: CLSCompliant(true)]

public class ArrayHelper


{
unsafe public static Array CreateInstance(Type type, int* ptr, int items)
{
Array arr = Array.CreateInstance(type, items);
int* addr = ptr;
for (int ctr = 0; ctr < items; ctr++) {
int value = *addr;
arr.SetValue(value, ctr);
addr++;
}
return arr;
}
}
// The attempt to compile this example displays the following output:
// UnmanagedPtr1.cs(8,57): warning CS3001: Argument type 'int*' is not CLS-compliant
using System;

[assembly: CLSCompliant(true)]

public class ArrayHelper


{
unsafe public static Array CreateInstance(Type type, int* ptr, int items)
{
Array arr = Array.CreateInstance(type, items);
int* addr = ptr;
for (int ctr = 0; ctr < items; ctr++) {
int value = *addr;
arr.SetValue(value, ctr);
addr++;
}
return arr;
}
}
// The attempt to compile this example displays the following output:
// UnmanagedPtr1.cs(8,57): warning CS3001: Argument type 'int*' is not CLS-compliant

En las clases abstractas conformes a CLS (es decir, clases marcadas como abstract en C#), todos los miembros de
dichas clases deben ser también conformes a CLS.
Convenciones de nomenclatura
Dado que algunos lenguajes de programación distinguen entre mayúsculas y minúsculas, los identificadores (como
los nombres de espacios de nombres, los tipos y los miembros) deben tener otro elemento distintivo aparte del
uso de mayúsculas. Dos identificadores se consideran equivalentes si sus asignaciones de minúsculas son iguales.
En el ejemplo de C# siguiente, se definen dos clases públicas: Person y person . Como solo se distinguen por el
uso de mayúsculas, el compilador de C# las marca como no conformes a CLS.

using System;

[assembly: CLSCompliant(true)]

public class Person : person


{

public class person


{

}
// Compilation produces a compiler warning like the following:
// Naming1.cs(11,14): warning CS3005: Identifier 'person' differing
// only in case is not CLS-compliant
// Naming1.cs(6,14): (Location of symbol related to previous warning)

Los identificadores del lenguaje de programación, como los nombres de espacios de nombres, tipos y miembros,
deben ajustarse al Estándar Unicode. Esto significa que:
El primer carácter de un identificador puede ser cualquier letra en mayúscula, letra en minúscula, letra de
inicial en mayúscula, letra modificadora, otra letra o número de letra. Para obtener información acerca de las
categorías de caracteres Unicode, consulte la enumeración System.Globalization.UnicodeCategory.
Los caracteres siguientes pueden proceder de cualquier categoría cuando funcionan como primer carácter y
también pueden incluir marcas no espaciadas, marcas de combinación de espaciado, números decimales,
puntuaciones de conexión y códigos de formato.
Antes de comparar los identificadores, debe filtrar los códigos de formato y convertir los identificadores a la forma
de normalización Unicode C, ya que un mismo carácter se puede representar mediante diferentes unidades de
código UTF-16. Las secuencias de caracteres que producen las mismas unidades de código en la forma de
normalización Unicode C no son conformes a CLS. En el ejemplo siguiente se define una propiedad llamada Å ,
que se compone del carácter SIGNO DE ANGSTROM (U+212B) y una segunda propiedad llamada Å , que se
compone de la LETRA MAYÚSCULA A LATINA CON UN ANILLO ENCIMA (U+00C5). El compilador de C# marca el
código fuente como no conforme a CLS.

public class Size


{
private double a1;
private double a2;

public double Å
{
get { return a1; }
set { a1 = value; }
}

public double Å
{
get { return a2; }
set { a2 = value; }
}
}
// Compilation produces a compiler warning like the following:
// Naming2a.cs(16,18): warning CS3005: Identifier 'Size.Å' differing only in case is not
// CLS-compliant
// Naming2a.cs(10,18): (Location of symbol related to previous warning)
// Naming2a.cs(18,8): warning CS3005: Identifier 'Size.Å.get' differing only in case is not
// CLS-compliant
// Naming2a.cs(12,8): (Location of symbol related to previous warning)
// Naming2a.cs(19,8): warning CS3005: Identifier 'Size.Å.set' differing only in case is not
// CLS-compliant
// Naming2a.cs(13,8): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>
Public Class Size
Private a1 As Double
Private a2 As Double

Public Property Å As Double


Get
Return a1
End Get
Set
a1 = value
End Set
End Property

Public Property Å As Double


Get
Return a2
End Get
Set
a2 = value
End Set
End Property
End Class
' Compilation produces a compiler warning like the following:
' Naming1.vb(9) : error BC30269: 'Public Property Å As Double' has multiple definitions
' with identical signatures.
'
' Public Property Å As Double
' ~

Los nombres de miembros con un ámbito determinado (como los espacios de nombres de un ensamblado, los
tipos de un espacio de nombres o los miembros de un tipo) deben ser únicos, excepto los nombres que se
resuelven a través de la sobrecarga. Este requisito es más estricto que el del sistema de tipos comunes, que permite
a varios miembros de un ámbito tener nombres idénticos siempre que sean diferentes tipos de miembros (por
ejemplo, que uno sea un método y otro, un campo). En particular, en el caso de los miembros de tipo:
Los campos y los tipos anidados solo se distinguen por el nombre.
Los métodos, las propiedades y los eventos que tienen el mismo nombre deben distinguirse por algo más
que el tipo de valor devuelto.
En el ejemplo siguiente se muestra el requisito que establece que los nombres de miembro deben ser únicos
dentro de su ámbito. En este ejemplo se define una clase denominada Converter , que incluye cuatro miembros
denominados Conversion . Tres son métodos y uno es una propiedad. El método que incluye un parámetro Int64
recibe un nombre único, pero no ocurre lo mismo con los dos métodos que tienen un parámetro Int32 , ya que el
valor devuelto no se considera parte de la signatura del miembro. La propiedad Conversion también infringe este
requisito, ya que las propiedades no pueden tener el mismo nombre que los métodos sobrecargados.
using System;

[assembly: CLSCompliant(true)]

public class Converter


{
public double Conversion(int number)
{
return (double) number;
}

public float Conversion(int number)


{
return (float) number;
}

public double Conversion(long number)


{
return (double) number;
}

public bool Conversion


{
get { return true; }
}
}
// Compilation produces a compiler error like the following:
// Naming3.cs(13,17): error CS0111: Type 'Converter' already defines a member called
// 'Conversion' with the same parameter types
// Naming3.cs(8,18): (Location of symbol related to previous error)
// Naming3.cs(23,16): error CS0102: The type 'Converter' already contains a definition for
// 'Conversion'
// Naming3.cs(8,18): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>

Public Class Converter


Public Function Conversion(number As Integer) As Double
Return CDbl(number)
End Function

Public Function Conversion(number As Integer) As Single


Return CSng(number)
End Function

Public Function Conversion(number As Long) As Double


Return CDbl(number)
End Function

Public ReadOnly Property Conversion As Boolean


Get
Return True
End Get
End Property
End Class
' Compilation produces a compiler error like the following:
' Naming3.vb(8) : error BC30301: 'Public Function Conversion(number As Integer) As Double'
' and 'Public Function Conversion(number As Integer) As Single' cannot
' overload each other because they differ only by return types.
'
' Public Function Conversion(number As Integer) As Double
' ~~~~~~~~~~
' Naming3.vb(20) : error BC30260: 'Conversion' is already declared as 'Public Function
' Conversion(number As Integer) As Single' in this class.
'
' Public ReadOnly Property Conversion As Boolean
' ~~~~~~~~~~

Todos los lenguajes contienen palabras claves únicas, de modo que los lenguajes dirigidos a Common Language
Runtime también deben proporcionar un mecanismo para hacer referencia a identificadores (como nombres de
tipo) que coincidan con las palabras clave. Por ejemplo, case es una palabra clave en C# y Visual Basic. Sin
embargo, en el siguiente ejemplo de Visual Basic se elimina la ambigüedad entre una clase denominada case y la
palabra clave case mediante llaves de apertura y cierre. De lo contrario, el ejemplo produciría el mensaje de error
"Una palabra clave no es válida como identificador" y no se compilaría.

Public Class [case]


Private _id As Guid
Private name As String

Public Sub New(name As String)


_id = Guid.NewGuid()
Me.name = name
End Sub

Public ReadOnly Property ClientName As String


Get
Return name
End Get
End Property
End Class

En el siguiente ejemplo de C# se crean instancias de la clase case usando el símbolo @ para eliminar la
ambigüedad entre el identificador y la palabra clave del lenguaje. Sin él, el compilador de C# mostraría dos
mensajes de error similares a los siguientes: "Se esperaba un tipo" y "'Término 'case' de expresión no válido".
using System;

public class Example


{
public static void Main()
{
@case c = new @case("John");
Console.WriteLine(c.ClientName);
}
}

Conversión de tipos
Common Language Specification define dos operadores de conversión:
op_Implicit , que se utiliza en las conversiones de ampliación que no dan lugar a la pérdida de datos o de
precisión. Por ejemplo, la estructura Decimal contiene un operador sobrecargado op_Implicit para
convertir valores de tipos enteros y valores Char en valores Decimal .
op_Explicit , que se utiliza en las conversiones de restricción que pueden producir una pérdida de magnitud
(un valor se convierte en un valor que tiene un intervalo menor) o de precisión. Por ejemplo, la estructura
Decimal contiene un operador sobrecargado op_Explicit para convertir los valores Double y Single en
Decimal , y convertir los valores Decimal en los valores integrales Double , Single y Char .

Sin embargo, no todos los lenguajes admiten la sobrecarga de operadores o la definición de operadores
personalizados. Si decide implementar estos operadores de conversión, debe proporcionar un mecanismo
alternativo para realizar la conversión. Se recomienda proporcionar los métodos From Xxx y To Xxx.
En el ejemplo siguiente se definen conversiones implícitas y explícitas conformes a CLS. Se crea una clase UDouble
que representa un número de punto flotante con signo de precisión doble. En las conversiones implícitas, pasa de
UDouble a Double y, en las conversiones explícitas, de UDouble a Single , de Double a UDouble y de Single a
UDouble . También define un método ToDouble como alternativa al operador de conversión implícita y los métodos
ToSingle , FromDouble y FromSingle como alternativas a los operadores de conversión explícitos.

using System;

public struct UDouble


{
private double number;

public UDouble(double value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

number = value;
}

public UDouble(float value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

number = value;
}

public static readonly UDouble MinValue = (UDouble) 0.0;


public static readonly UDouble MaxValue = (UDouble) Double.MaxValue;

public static explicit operator Double(UDouble value)


{
return value.number;
}

public static implicit operator Single(UDouble value)


{
if (value.number > (double) Single.MaxValue)
throw new InvalidCastException("A UDouble value is out of range of the Single type.");

return (float) value.number;


}

public static explicit operator UDouble(double value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

return new UDouble(value);


}

public static implicit operator UDouble(float value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

return new UDouble(value);


}

public static Double ToDouble(UDouble value)


{
return (Double) value;
}

public static float ToSingle(UDouble value)


{
return (float) value;
}

public static UDouble FromDouble(double value)


{
return new UDouble(value);
}

public static UDouble FromSingle(float value)


{
return new UDouble(value);
}
}
Public Structure UDouble
Private number As Double

Public Sub New(value As Double)


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
number = value
End Sub

Public Sub New(value As Single)


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
number = value
End Sub

Public Shared ReadOnly MinValue As UDouble = CType(0.0, UDouble)


Public Shared ReadOnly MaxValue As UDouble = Double.MaxValue

Public Shared Widening Operator CType(value As UDouble) As Double


Return value.number
End Operator

Public Shared Narrowing Operator CType(value As UDouble) As Single


If value.number > CDbl(Single.MaxValue) Then
Throw New InvalidCastException("A UDouble value is out of range of the Single type.")
End If
Return CSng(value.number)
End Operator

Public Shared Narrowing Operator CType(value As Double) As UDouble


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
Return New UDouble(value)
End Operator

Public Shared Narrowing Operator CType(value As Single) As UDouble


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
Return New UDouble(value)
End Operator

Public Shared Function ToDouble(value As UDouble) As Double


Return CType(value, Double)
End Function

Public Shared Function ToSingle(value As UDouble) As Single


Return CType(value, Single)
End Function

Public Shared Function FromDouble(value As Double) As UDouble


Return New UDouble(value)
End Function

Public Shared Function FromSingle(value As Single) As UDouble


Return New UDouble(value)
End Function
End Structure

Matrices
Las matrices conformes a CLS cumplen las reglas siguientes:
Todas las dimensiones de una matriz deben tener un límite inferior igual a cero. En el ejemplo siguiente se
crea una matriz no conforme a CLS cuyo límite inferior es uno. Pese a la presencia del atributo
CLSCompliantAttribute, el compilador no detecta que la matriz devuelta por el método
Numbers.GetTenPrimes no es conforme a CLS.

[assembly: CLSCompliant(true)]

public class Numbers


{
public static Array GetTenPrimes()
{
Array arr = Array.CreateInstance(typeof(Int32), new int[] {10}, new int[] {1});
arr.SetValue(1, 1);
arr.SetValue(2, 2);
arr.SetValue(3, 3);
arr.SetValue(5, 4);
arr.SetValue(7, 5);
arr.SetValue(11, 6);
arr.SetValue(13, 7);
arr.SetValue(17, 8);
arr.SetValue(19, 9);
arr.SetValue(23, 10);

return arr;
}
}

<Assembly: CLSCompliant(True)>

Public Class Numbers


Public Shared Function GetTenPrimes() As Array
Dim arr As Array = Array.CreateInstance(GetType(Int32), {10}, {1})
arr.SetValue(1, 1)
arr.SetValue(2, 2)
arr.SetValue(3, 3)
arr.SetValue(5, 4)
arr.SetValue(7, 5)
arr.SetValue(11, 6)
arr.SetValue(13, 7)
arr.SetValue(17, 8)
arr.SetValue(19, 9)
arr.SetValue(23, 10)
Return arr
End Function
End Class

Todos los elementos de la matriz deben componerse de tipos conformes a CLS. En el ejemplo siguiente se
definen dos métodos que devuelven matrices no conformes a CLS. El primero devuelve una matriz de
valores UInt32. El segundo devuelve una matriz Object que contiene los valores Int32 y UInt32 . Aunque el
compilador identifica la primera matriz como no conforme debido a su tipo UInt32 , no reconoce que la
segunda matriz incluye elementos no conformes a CLS.
using System;

[assembly: CLSCompliant(true)]

public class Numbers


{
public static UInt32[] GetTenPrimes()
{
uint[] arr = { 1u, 2u, 3u, 5u, 7u, 11u, 13u, 17u, 19u };
return arr;
}

public static Object[] GetFivePrimes()


{
Object[] arr = { 1, 2, 3, 5u, 7u };
return arr;
}
}
// Compilation produces a compiler warning like the following:
// Array2.cs(8,27): warning CS3002: Return type of 'Numbers.GetTenPrimes()' is not
// CLS-compliant

<Assembly: CLSCompliant(True)>

Public Class Numbers


Public Shared Function GetTenPrimes() As UInt32()
Return { 1ui, 2ui, 3ui, 5ui, 7ui, 11ui, 13ui, 17ui, 19ui }
End Function
Public Shared Function GetFivePrimes() As Object()
Dim arr() As Object = { 1, 2, 3, 5ui, 7ui }
Return arr
End Function
End Class
' Compilation produces a compiler warning like the following:
' warning BC40027: Return type of function 'GetTenPrimes' is not CLS-compliant.

La resolución de desbordamiento de los métodos que tienen parámetros de matriz se basa en el hecho de
que son matrices y en su tipo de elemento. Por esta razón, la siguiente definición de un método GetSquares
sobrecargado es conforme a CLS.
using System;
using System.Numerics;

[assembly: CLSCompliant(true)]

public class Numbers


{
public static byte[] GetSquares(byte[] numbers)
{
byte[] numbersOut = new byte[numbers.Length];
for (int ctr = 0; ctr < numbers.Length; ctr++) {
int square = ((int) numbers[ctr]) * ((int) numbers[ctr]);
if (square <= Byte.MaxValue)
numbersOut[ctr] = (byte) square;
// If there's an overflow, assign MaxValue to the corresponding
// element.
else
numbersOut[ctr] = Byte.MaxValue;

}
return numbersOut;
}

public static BigInteger[] GetSquares(BigInteger[] numbers)


{
BigInteger[] numbersOut = new BigInteger[numbers.Length];
for (int ctr = 0; ctr < numbers.Length; ctr++)
numbersOut[ctr] = numbers[ctr] * numbers[ctr];

return numbersOut;
}
}

Imports System.Numerics

<Assembly: CLSCompliant(True)>

Public Module Numbers


Public Function GetSquares(numbers As Byte()) As Byte()
Dim numbersOut(numbers.Length - 1) As Byte
For ctr As Integer = 0 To numbers.Length - 1
Dim square As Integer = (CInt(numbers(ctr)) * CInt(numbers(ctr)))
If square <= Byte.MaxValue Then
numbersOut(ctr) = CByte(square)
' If there's an overflow, assign MaxValue to the corresponding
' element.
Else
numbersOut(ctr) = Byte.MaxValue
End If
Next
Return numbersOut
End Function

Public Function GetSquares(numbers As BigInteger()) As BigInteger()


Dim numbersOut(numbers.Length - 1) As BigInteger
For ctr As Integer = 0 To numbers.Length - 1
numbersOut(ctr) = numbers(ctr) * numbers(ctr)
Next
Return numbersOut
End Function
End Module

Interfaces
Las interfaces conformes a CLS pueden definir propiedades, eventos y métodos virtuales (métodos sin
implementación). Una interfaz conforme a CLS no puede tener ninguno de los elementos siguientes:
Métodos estáticos o campos estáticos. El compilador de C# genera errores de compilador si se define un
miembro estático en una interfaz.
Campos. El compilador de C# genera errores de compilador si se define un campo en una interfaz.
Métodos que no son conformes a CLS. Por ejemplo, la siguiente definición de interfaz incluye un método,
INumber.GetUnsigned , que está marcado como no conforme a CLS. Este ejemplo genera una advertencia del
compilador.

using System;

[assembly:CLSCompliant(true)]

public interface INumber


{
int Length();
[CLSCompliant(false)] ulong GetUnsigned();
}
// Attempting to compile the example displays output like the following:
// Interface2.cs(8,32): warning CS3010: 'INumber.GetUnsigned()': CLS-compliant interfaces
// must have only CLS-compliant members

<Assembly: CLSCompliant(True)>

Public Interface INumber


Function Length As Integer
<CLSCompliant(False)> Function GetUnsigned As ULong
End Interface
' Attempting to compile the example displays output like the following:
' Interface2.vb(9) : warning BC40033: Non CLS-compliant 'function' is not allowed in a
' CLS-compliant interface.
'
' <CLSCompliant(False)> Function GetUnsigned As ULong
' ~~~~~~~~~~~

Debido a esta regla, no se necesitan tipos conformes a CLS para implementar miembros no conformes a
CLS. Si un marco conforme a CLS expone una clase que implementa una interfaz no conforme a CLS,
también debe proporcionar implementaciones concretas de todos los miembros no conformes a CLS.
Los compiladores de lenguaje conformes a CLS también deben permitir que una clase proporcione
implementaciones independientes de miembros con el mismo nombre y la misma signatura en varias interfaces.
C# admite implementaciones de interfaz explícitas para proporcionar diferentes implementaciones de métodos con
el mismo nombre. En el ejemplo siguiente se muestra este escenario con la definición de una clase Temperature
que implementa las interfaces ICelsius y IFahrenheit como implementaciones de interfaces explícitas.
using System;

[assembly: CLSCompliant(true)]

public interface IFahrenheit


{
decimal GetTemperature();
}

public interface ICelsius


{
decimal GetTemperature();
}

public class Temperature : ICelsius, IFahrenheit


{
private decimal _value;

public Temperature(decimal value)


{
// We assume that this is the Celsius value.
_value = value;
}

decimal IFahrenheit.GetTemperature()
{
return _value * 9 / 5 + 32;
}

decimal ICelsius.GetTemperature()
{
return _value;
}
}
public class Example
{
public static void Main()
{
Temperature temp = new Temperature(100.0m);
ICelsius cTemp = temp;
IFahrenheit fTemp = temp;
Console.WriteLine("Temperature in Celsius: {0} degrees",
cTemp.GetTemperature());
Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
fTemp.GetTemperature());
}
}
// The example displays the following output:
// Temperature in Celsius: 100.0 degrees
// Temperature in Fahrenheit: 212.0 degrees
Assembly: CLSCompliant(True)>

Public Interface IFahrenheit


Function GetTemperature() As Decimal
End Interface

Public Interface ICelsius


Function GetTemperature() As Decimal
End Interface

Public Class Temperature : Implements ICelsius, IFahrenheit


Private _value As Decimal

Public Sub New(value As Decimal)


' We assume that this is the Celsius value.
_value = value
End Sub

Public Function GetFahrenheit() As Decimal _


Implements IFahrenheit.GetTemperature
Return _value * 9 / 5 + 32
End Function

Public Function GetCelsius() As Decimal _


Implements ICelsius.GetTemperature
Return _value
End Function
End Class

Module Example
Public Sub Main()
Dim temp As New Temperature(100.0d)
Console.WriteLine("Temperature in Celsius: {0} degrees",
temp.GetCelsius())
Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
temp.GetFahrenheit())
End Sub
End Module
' The example displays the following output:
' Temperature in Celsius: 100.0 degrees
' Temperature in Fahrenheit: 212.0 degrees

Enumeraciones
Las enumeraciones conformes a CLS deben seguir estas reglas:
El tipo subyacente de una enumeración debe ser un entero intrínseco conforme a CLS (Byte, Int16, Int32 o
Int64). Por ejemplo, el código siguiente intenta definir una enumeración cuyo tipo subyacente es UInt32 y
genera una advertencia del compilador.
using System;

[assembly: CLSCompliant(true)]

public enum Size : uint {


Unspecified = 0,
XSmall = 1,
Small = 2,
Medium = 3,
Large = 4,
XLarge = 5
};

public class Clothing


{
public string Name;
public string Type;
public string Size;
}
// The attempt to compile the example displays a compiler warning like the following:
// Enum3.cs(6,13): warning CS3009: 'Size': base type 'uint' is not CLS-compliant

<Assembly: CLSCompliant(True)>

Public Enum Size As UInt32


Unspecified = 0
XSmall = 1
Small = 2
Medium = 3
Large = 4
XLarge = 5
End Enum

Public Class Clothing


Public Name As String
Public Type As String
Public Size As Size
End Class
' The attempt to compile the example displays a compiler warning like the following:
' Enum3.vb(6) : warning BC40032: Underlying type 'UInt32' of Enum is not CLS-compliant.
'
' Public Enum Size As UInt32
' ~~~~

Un tipo de enumeración debe tener un campo de instancia único denominado Value__ marcado con el
atributo FieldAttributes.RTSpecialName . Esto permite hacer referencia al valor del campo de forma implícita.
Las enumeraciones incluyen campos estáticos literales del mismo tipo que el de la enumeración. Por
ejemplo, si define una enumeración State con los valores State.On y State.Off , State.On y State.Off
son campos estáticos literales cuyo tipo será State .
Hay dos tipos de enumeraciones:
Las enumeraciones que representan un conjunto de valores enteros con nombre mutuamente
excluyentes. Este tipo de enumeración se indica por la ausencia del atributo personalizado
System.FlagsAttribute.
Las enumeraciones que representan un conjunto de marcadores de bits que se pueden combinar
para generar un valor sin nombre. Este tipo de enumeración se indica por la presencia del atributo
personalizado System.FlagsAttribute.
Para obtener más información, consulte la documentación de la estructura Enum.
El valor de una enumeración no se limita al intervalo de sus valores especificados. Es decir, el intervalo de
valores de una enumeración es el intervalo de su valor subyacente. Puede utilizar el método Enum.IsDefined
para determinar si un valor especificado es miembro de una enumeración.
Miembros de tipos en general
Common Language Specification necesita todos los campos y métodos accesibles como miembros de una clase
determinada. Por tanto, los métodos y los campos estáticos globales (es decir, los métodos o los campos que se
definen con independencia de un tipo) no son conformes a CLS. Si intenta incluir un campo o un método global en
el código fuente, el compilador de C# generará un error de compilación.
Common Language Specification solo admite la convención de llamada administrada estándar. No admite
convenciones de llamada ni métodos no administrados con listas de argumentos variables marcados con la palabra
clave varargs . En el caso de las listas de argumentos variables que son compatibles con la convención de llamada
administrada estándar, use el atributo ParamArrayAttribute o la implementación del lenguaje específico, como la
palabra clave params en C# y la palabra clave ParamArray en Visual Basic.
Accesibilidad de miembros
Al reemplazar un miembro heredado no se puede cambiar la accesibilidad de dicho miembro. Por ejemplo, un
método público de una clase base no se puede reemplazar por un método privado de una clase derivada. Existe
una excepción: un miembro protected internal (en C#) o Protected Friend (en Visual Basic) de un ensamblado
que se haya reemplazado por un tipo de un ensamblado diferente. En ese caso, la accesibilidad del reemplazo es
Protected .

En el ejemplo siguiente se muestra el error que se genera cuando el atributo CLSCompliantAttribute se establece
en true y Person , que es una clase derivada de Animal , intenta cambiar la accesibilidad de la propiedad Species
de pública a privada. El ejemplo se compila correctamente si su accesibilidad se cambia a pública.
using System;

[assembly: CLSCompliant(true)]

public class Animal


{
private string _species;

public Animal(string species)


{
_species = species;
}

public virtual string Species


{
get { return _species; }
}

public override string ToString()


{
return _species;
}
}

public class Human : Animal


{
private string _name;

public Human(string name) : base("Homo Sapiens")


{
_name = name;
}

public string Name


{
get { return _name; }
}

private override string Species


{
get { return base.Species; }
}

public override string ToString()


{
return _name;
}
}

public class Example


{
public static void Main()
{
Human p = new Human("John");
Console.WriteLine(p.Species);
Console.WriteLine(p.ToString());
}
}
// The example displays the following output:
// error CS0621: 'Human.Species': virtual or abstract members cannot be private
<Assembly: CLSCompliant(True)>

Public Class Animal


Private _species As String

Public Sub New(species As String)


_species = species
End Sub

Public Overridable ReadOnly Property Species As String


Get
Return _species
End Get
End Property

Public Overrides Function ToString() As String


Return _species
End Function
End Class

Public Class Human : Inherits Animal


Private _name As String

Public Sub New(name As String)


MyBase.New("Homo Sapiens")
_name = name
End Sub

Public ReadOnly Property Name As String


Get
Return _name
End Get
End Property

Private Overrides ReadOnly Property Species As String


Get
Return MyBase.Species
End Get
End Property

Public Overrides Function ToString() As String


Return _name
End Function
End Class

Public Module Example


Public Sub Main()
Dim p As New Human("John")
Console.WriteLine(p.Species)
Console.WriteLine(p.ToString())
End Sub
End Module
' The example displays the following output:
' 'Private Overrides ReadOnly Property Species As String' cannot override
' 'Public Overridable ReadOnly Property Species As String' because
' they have different access levels.
'
' Private Overrides ReadOnly Property Species As String

Los tipos de la signatura de un miembro deben estar accesibles siempre que dicho miembro esté accesible. Esto
significa, por ejemplo, que un miembro público no puede incluir un parámetro cuyo tipo sea privado, protegido o
interno. En el ejemplo siguiente se muestra el error del compilador que se produce cuando un constructor de clase
StringWrapper expone un valor de enumeración StringOperationType interno que determina cómo debe ajustarse
un valor de cadena.
using System;
using System.Text;

public class StringWrapper


{
string internalString;
StringBuilder internalSB = null;
bool useSB = false;

public StringWrapper(StringOperationType type)


{
if (type == StringOperationType.Normal) {
useSB = false;
}
else {
useSB = true;
internalSB = new StringBuilder();
}
}

// The remaining source code...


}

internal enum StringOperationType { Normal, Dynamic }


// The attempt to compile the example displays the following output:
// error CS0051: Inconsistent accessibility: parameter type
// 'StringOperationType' is less accessible than method
// 'StringWrapper.StringWrapper(StringOperationType)'

Imports System.Text

<Assembly:CLSCompliant(True)>

Public Class StringWrapper

Dim internalString As String


Dim internalSB As StringBuilder = Nothing
Dim useSB As Boolean = False

Public Sub New(type As StringOperationType)


If type = StringOperationType.Normal Then
useSB = False
Else
internalSB = New StringBuilder()
useSB = True
End If
End Sub

' The remaining source code...


End Class

Friend Enum StringOperationType As Integer


Normal = 0
Dynamic = 1
End Enum
' The attempt to compile the example displays the following output:
' error BC30909: 'type' cannot expose type 'StringOperationType'
' outside the project through class 'StringWrapper'.
'
' Public Sub New(type As StringOperationType)
' ~~~~~~~~~~~~~~~~~~~

Miembros y tipos genéricos


Los tipos anidados siempre tienen, como mínimo, el mismo número de parámetros genéricos que el tipo
envolvente. Estos se corresponden por posición con los parámetros genéricos del tipo envolvente. El tipo genérico
también puede incluir nuevos parámetros genéricos.
Las relaciones entre los parámetros de tipo genérico de un tipo envolvente y sus tipos anidados se pueden ocultar
en la sintaxis de cada lenguaje. En el ejemplo siguiente, un tipo genérico Outer<T> contiene dos clases anidadas:
Inner1A e Inner1B<U> . Las llamadas al método ToString , donde cada clase hereda de Object.ToString , muestran
que cada clase anidada contiene los parámetros de tipo de la clase contenedora.

using System;

[assembly:CLSCompliant(true)]

public class Outer<T>


{
T value;

public Outer(T value)


{
this.value = value;
}

public class Inner1A : Outer<T>


{
public Inner1A(T value) : base(value)
{ }
}

public class Inner1B<U> : Outer<T>


{
U value2;

public Inner1B(T value1, U value2) : base(value1)


{
this.value2 = value2;
}
}
}

public class Example


{
public static void Main()
{
var inst1 = new Outer<String>("This");
Console.WriteLine(inst1);

var inst2 = new Outer<String>.Inner1A("Another");


Console.WriteLine(inst2);

var inst3 = new Outer<String>.Inner1B<int>("That", 2);


Console.WriteLine(inst3);
}
}
// The example displays the following output:
// Outer`1[System.String]
// Outer`1+Inner1A[System.String]
// Outer`1+Inner1B`1[System.String,System.Int32]
<Assembly:CLSCompliant(True)>

Public Class Outer(Of T)


Dim value As T

Public Sub New(value As T)


Me.value = value
End Sub

Public Class Inner1A : Inherits Outer(Of T)


Public Sub New(value As T)
MyBase.New(value)
End Sub
End Class

Public Class Inner1B(Of U) : Inherits Outer(Of T)


Dim value2 As U

Public Sub New(value1 As T, value2 As U)


MyBase.New(value1)
Me.value2 = value2
End Sub
End Class
End Class

Public Module Example


Public Sub Main()
Dim inst1 As New Outer(Of String)("This")
Console.WriteLine(inst1)

Dim inst2 As New Outer(Of String).Inner1A("Another")


Console.WriteLine(inst2)

Dim inst3 As New Outer(Of String).Inner1B(Of Integer)("That", 2)


Console.WriteLine(inst3)
End Sub
End Module
' The example displays the following output:
' Outer`1[System.String]
' Outer`1+Inner1A[System.String]
' Outer`1+Inner1B`1[System.String,System.Int32]

Los nombres de tipos genéricos se codifican con el formato name n, donde name es el nombre del tipo, ` es un
carácter literal y n es el número de parámetros declarados en el tipo o, en el caso de tipos genéricos anidados, el
número de parámetros de tipo recién incorporados. Esta codificación de nombres de tipo genérico tiene interés
fundamentalmente para los desarrolladores que utilizan la reflexión a fin de acceder a los tipos genéricos
conformes a CLS de una biblioteca.
Si las restricciones se aplican a un tipo genérico, los tipos utilizados como restricciones también deben ser
conformes a CLS. En el ejemplo siguiente se define una clase denominada BaseClass que no es conforme a CLS y
una clase genérica denominada BaseCollection cuyo parámetro de tipo debe derivarse de BaseClass . Sin
embargo, puesto que BaseClass no es conforme a CLS, el compilador emite una advertencia.
using System;

[assembly:CLSCompliant(true)]

[CLSCompliant(false)] public class BaseClass


{}

public class BaseCollection<T> where T : BaseClass


{}
// Attempting to compile the example displays the following output:
// warning CS3024: Constraint type 'BaseClass' is not CLS-compliant

Assembly: CLSCompliant(True)>

<CLSCompliant(False)> Public Class BaseClass


End Class

Public Class BaseCollection(Of T As BaseClass)


End Class
' Attempting to compile the example displays the following output:
' warning BC40040: Generic parameter constraint type 'BaseClass' is not
' CLS-compliant.
'
' Public Class BaseCollection(Of T As BaseClass)
' ~~~~~~~~~

Si un tipo genérico se deriva de un tipo base genérico, es necesario volver a declarar las restricciones para que se
pueda garantizar que las restricciones del tipo base también se cumplen. En el ejemplo siguiente se define un
objeto Number<T> que puede representar cualquier tipo numérico. También se define una clase FloatingPoint<T>
que representa un valor de punto flotante. Sin embargo, el código fuente no puede compilarse, ya que no aplica la
restricción de Number<T> (T debe ser un tipo de valor) en FloatingPoint<T> .
using System;

[assembly:CLSCompliant(true)]

public class Number<T> where T : struct


{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;

public Number(T value)


{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}

public T Add(T value)


{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}

public T Subtract(T value)


{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}

public class FloatingPoint<T> : Number<T>


{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
// The attempt to compile the example displays the following output:
// error CS0453: The type 'T' must be a non-nullable value type in
// order to use it as parameter 'T' in the generic type or method 'Number<T>'
<Assembly:CLSCompliant(True)>

Public Class Number(Of T As Structure)


' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double

Public Sub New(value As T)


Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub

Public Function Add(value As T) As T


Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function

Public Function Subtract(value As T) As T


Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class

Public Class FloatingPoint(Of T) : Inherits Number(Of T)


Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class
' The attempt to compile the example displays the following output:
' error BC32105: Type argument 'T' does not satisfy the 'Structure'
' constraint for type parameter 'T'.
'
' Public Class FloatingPoint(Of T) : Inherits Number(Of T)
' ~

El ejemplo se compila correctamente si se agrega la restricción a la clase FloatingPoint<T> .


using System;

[assembly:CLSCompliant(true)]

public class Number<T> where T : struct


{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;

public Number(T value)


{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}

public T Add(T value)


{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}

public T Subtract(T value)


{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}

public class FloatingPoint<T> : Number<T> where T : struct


{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
<Assembly:CLSCompliant(True)>

Public Class Number(Of T As Structure)


' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double

Public Sub New(value As T)


Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub

Public Function Add(value As T) As T


Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function

Public Function Subtract(value As T) As T


Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class

Public Class FloatingPoint(Of T As Structure) : Inherits Number(Of T)


Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class

Common Language Specification impone un modelo conservador adaptado a cada instancia en los tipos anidados
y los miembros protegidos. Los tipos genéricos abiertos no pueden exponer campos ni miembros con signaturas
que contengan una instancia específica de un tipo genérico anidado y protegido. Los tipos no genéricos que
amplíen una instancia específica de una interfaz o clase base genérica no pueden exponer campos ni miembros con
signaturas que contengan otra instancia de un tipo genérico anidado y protegido.
En el ejemplo siguiente se define un tipo genérico, C1<T> , y una clase protegida, C1<T>.N . C1<T> tiene dos
métodos: M1 y M2 . Pero M1 no es conforme a CLS porque intenta devolver un objeto C1<int>.N a partir de
C1<T> . Una segunda clase, C2 , se deriva de C1<long> . Esta clase tiene dos métodos, M3 y M4 . M3 o es conforme
a CLS porque intenta devolver un objeto C1<int>.N a partir de una subclase de C1<long> . Los compiladores del
lenguaje pueden ser aún más restrictivos. En este ejemplo, Visual Basic muestra un error cuando intenta compilar
M4 .
using System;

[assembly:CLSCompliant(true)]

public class C1<T>


{
protected class N { }

protected void M1(C1<int>.N n) { } // Not CLS-compliant - C1<int>.N not


// accessible from within C1<T> in all
// languages
protected void M2(C1<T>.N n) { } // CLS-compliant – C1<T>.N accessible
// inside C1<T>
}

public class C2 : C1<long>


{
protected void M3(C1<int>.N n) { } // Not CLS-compliant – C1<int>.N is not
// accessible in C2 (extends C1<long>)

protected void M4(C1<long>.N n) { } // CLS-compliant, C1<long>.N is


// accessible in C2 (extends C1<long>)
}
// Attempting to compile the example displays output like the following:
// Generics4.cs(9,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
// Generics4.cs(18,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant

<Assembly:CLSCompliant(True)>

Public Class C1(Of T)


Protected Class N
End Class

Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
' accessible from within C1(Of T) in all
End Sub ' languages

Protected Sub M2(n As C1(Of T).N) ' CLS-compliant – C1(Of T).N accessible
End Sub ' inside C1(Of T)
End Class

Public Class C2 : Inherits C1(Of Long)


Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant – C1(Of Integer).N is not
End Sub ' accessible in C2 (extends C1(Of Long))

Protected Sub M4(n As C1(Of Long).N)


End Sub
End Class
' Attempting to compile the example displays output like the following:
' error BC30508: 'n' cannot expose type 'C1(Of Integer).N' in namespace
' '<Default>' through class 'C1'.
'
' Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
' ~~~~~~~~~~~~~~~~
' error BC30389: 'C1(Of T).N' is not accessible in this context because
' it is 'Protected'.
'
' Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant - C1(Of Integer).N is not
'
' ~~~~~~~~~~~~~~~~
'
' error BC30389: 'C1(Of T).N' is not accessible in this context because it is 'Protected'.
'
' Protected Sub M4(n As C1(Of Long).N)
' ~~~~~~~~~~~~~
Constructores
Los constructores de clases y estructuras conformes a CLS deben seguir estas reglas:
Un constructor de una clase derivada debe llamar al constructor de instancia de su clase base antes de tener
acceso a datos de instancia heredados. Este requisito se debe al hecho de que los constructores de clase base
no se heredan por sus clases derivadas. Esta regla no se aplica a las estructuras, que no admiten la herencia
directa.
Normalmente, los compiladores aplican esta regla independientemente de la conformidad con CLS, como se
muestra en el ejemplo siguiente. En este ejemplo, se crea una clase Doctor que se deriva de una clase
Person , pero la clase Doctor no consigue llamar al constructor de la clase Person para inicializar los
campos de instancia heredados.
using System;

[assembly: CLSCompliant(true)]

public class Person


{
private string fName, lName, _id;

public Person(string firstName, string lastName, string id)


{
if (String.IsNullOrEmpty(firstName + lastName))
throw new ArgumentNullException("Either a first name or a last name must be provided.");

fName = firstName;
lName = lastName;
_id = id;
}

public string FirstName


{
get { return fName; }
}

public string LastName


{
get { return lName; }
}

public string Id
{
get { return _id; }
}

public override string ToString()


{
return String.Format("{0}{1}{2}", fName,
String.IsNullOrEmpty(fName) ? "" : " ",
lName);
}
}

public class Doctor : Person


{
public Doctor(string firstName, string lastName, string id)
{
}

public override string ToString()


{
return "Dr. " + base.ToString();
}
}
// Attempting to compile the example displays output like the following:
// ctor1.cs(45,11): error CS1729: 'Person' does not contain a constructor that takes 0
// arguments
// ctor1.cs(10,11): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>

Public Class Person


Private fName, lName, _id As String

Public Sub New(firstName As String, lastName As String, id As String)


If String.IsNullOrEmpty(firstName + lastName) Then
Throw New ArgumentNullException("Either a first name or a last name must be provided.")
End If

fName = firstName
lName = lastName
_id = id
End Sub

Public ReadOnly Property FirstName As String


Get
Return fName
End Get
End Property

Public ReadOnly Property LastName As String


Get
Return lName
End Get
End Property

Public ReadOnly Property Id As String


Get
Return _id
End Get
End Property

Public Overrides Function ToString() As String


Return String.Format("{0}{1}{2}", fName,
If(String.IsNullOrEmpty(fName), "", " "),
lName)
End Function
End Class

Public Class Doctor : Inherits Person


Public Sub New(firstName As String, lastName As String, id As String)
End Sub

Public Overrides Function ToString() As String


Return "Dr. " + MyBase.ToString()
End Function
End Class
' Attempting to compile the example displays output like the following:
' Ctor1.vb(46) : error BC30148: First statement of this 'Sub New' must be a call
' to 'MyBase.New' or 'MyClass.New' because base class 'Person' of 'Doctor' does
' not have an accessible 'Sub New' that can be called with no arguments.
'
' Public Sub New()
' ~~~

No se puede llamar a un constructor de objetos excepto para crear un objeto. Además, un objeto no se
puede inicializar dos veces. Por ejemplo, esto significa que Object.MemberwiseClone no debe llamar a los
constructores.
Propiedades
Las propiedades de los tipos conformes a CLS deben seguir estas reglas:
Una propiedad debe tener un establecedor, un captador o ambos. En un ensamblado, estos elementos se
implementan como métodos especiales, lo que significa que aparecerán como métodos independientes (el
captador se llama get _propertyname y el establecedor es set _propertyname) marcados como
SpecialName en los metadatos del ensamblado. El compilador de C# aplica esta regla automáticamente sin
necesidad de aplicar el atributo CLSCompliantAttribute.
El tipo de una propiedad es el tipo de valor devuelto del captador de la propiedad y el último argumento del
establecedor. Estos tipos deben ser conformes a CLS y los argumentos no se pueden asignar a la propiedad
por referencia (es decir, no pueden ser punteros administrados).
Si una propiedad tiene un captador y un establecedor, estos deben ser virtuales, estáticos o de instancia. El
compilador de C# aplica automáticamente esta regla mediante la sintaxis de la definición de la propiedad.
Events
Un evento se define por su nombre y su tipo. El tipo de evento es un delegado que se utiliza para indicar el evento.
Por ejemplo, el evento DbConnection.StateChange es del tipo StateChangeEventHandler . Además del propio evento,
hay tres métodos con nombres basados en el nombre de evento que proporcionan la implementación del evento y
que se marcan como SpecialName en los metadatos de ensamblado:
Un método para agregar un controlador de eventos, llamado add EventName. Por ejemplo, el método de
suscripción de eventos del evento DbConnection.StateChange se denomina add_StateChange .
Un método para quitar un controlador de eventos, llamado remove EventName. Por ejemplo, el método de
eliminación del evento DbConnection.StateChange se denomina remove_StateChange .
Un método para indicar que el evento se ha producido, llamado raise _EventName.

NOTE
La mayoría de las reglas de Common Language Specification relacionadas con los eventos se implementan mediante
compiladores del lenguaje y son transparentes para los desarrolladores de componentes.

Los métodos para agregar, quitar y generar el evento deben tener la misma accesibilidad. Además, todos deben ser
estáticos, virtuales o de instancia. Los métodos para agregar y quitar un evento tienen un parámetro cuyo tipo es el
mismo que el del delegado de eventos. Los métodos para agregar y quitar deben estar presentes o ausentes al
mismo tiempo.
En el ejemplo siguiente se define una clase conforme a CLS denominada Temperature que genera un evento
TemperatureChanged si el cambio de temperatura entre dos lecturas es igual o mayor que el valor de umbral. La
clase Temperature define de manera explícita un método raise_TemperatureChanged para que pueda ejecutar
controladores de eventos de forma selectiva.

using System;
using System.Collections;
using System.Collections.Generic;

[assembly: CLSCompliant(true)]

public class TemperatureChangedEventArgs : EventArgs


{
private Decimal originalTemp;
private Decimal newTemp;
private DateTimeOffset when;

public TemperatureChangedEventArgs(Decimal original, Decimal @new, DateTimeOffset time)


{
originalTemp = original;
newTemp = @new;
when = time;
}
public Decimal OldTemperature
{
get { return originalTemp; }
}

public Decimal CurrentTemperature


{
get { return newTemp; }
}

public DateTimeOffset Time


{
get { return when; }
}
}

public delegate void TemperatureChanged(Object sender, TemperatureChangedEventArgs e);

public class Temperature


{
private struct TemperatureInfo
{
public Decimal Temperature;
public DateTimeOffset Recorded;
}

public event TemperatureChanged TemperatureChanged;

private Decimal previous;


private Decimal current;
private Decimal tolerance;
private List<TemperatureInfo> tis = new List<TemperatureInfo>();

public Temperature(Decimal temperature, Decimal tolerance)


{
current = temperature;
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = temperature;
tis.Add(ti);
ti.Recorded = DateTimeOffset.UtcNow;
this.tolerance = tolerance;
}

public Decimal CurrentTemperature


{
get { return current; }
set {
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = value;
ti.Recorded = DateTimeOffset.UtcNow;
previous = current;
current = value;
if (Math.Abs(current - previous) >= tolerance)
raise_TemperatureChanged(new TemperatureChangedEventArgs(previous, current, ti.Recorded));
}
}

public void raise_TemperatureChanged(TemperatureChangedEventArgs eventArgs)


{
if (TemperatureChanged == null)
return;

foreach (TemperatureChanged d in TemperatureChanged.GetInvocationList()) {


if (d.Method.Name.Contains("Duplicate"))
Console.WriteLine("Duplicate event handler; event handler not executed.");
else
d.Invoke(this, eventArgs);
}
}
}
}

public class Example


{
public Temperature temp;

public static void Main()


{
Example ex = new Example();
}

public Example()
{
temp = new Temperature(65, 3);
temp.TemperatureChanged += this.TemperatureNotification;
RecordTemperatures();
Example ex = new Example(temp);
ex.RecordTemperatures();
}

public Example(Temperature t)
{
temp = t;
RecordTemperatures();
}

public void RecordTemperatures()


{
temp.TemperatureChanged += this.DuplicateTemperatureNotification;
temp.CurrentTemperature = 66;
temp.CurrentTemperature = 63;
}

internal void TemperatureNotification(Object sender, TemperatureChangedEventArgs e)


{
Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature);
}

public void DuplicateTemperatureNotification(Object sender, TemperatureChangedEventArgs e)


{
Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature);
}
}

Imports System.Collections
Imports System.Collections.Generic

<Assembly: CLSCompliant(True)>

Public Class TemperatureChangedEventArgs : Inherits EventArgs


Private originalTemp As Decimal
Private newTemp As Decimal
Private [when] As DateTimeOffset

Public Sub New(original As Decimal, [new] As Decimal, [time] As DateTimeOffset)


originalTemp = original
newTemp = [new]
[when] = [time]
End Sub

Public ReadOnly Property OldTemperature As Decimal


Get
Return originalTemp
End Get
End Property
End Property

Public ReadOnly Property CurrentTemperature As Decimal


Get
Return newTemp
End Get
End Property

Public ReadOnly Property [Time] As DateTimeOffset


Get
Return [when]
End Get
End Property
End Class

Public Delegate Sub TemperatureChanged(sender As Object, e As TemperatureChangedEventArgs)

Public Class Temperature


Private Structure TemperatureInfo
Dim Temperature As Decimal
Dim Recorded As DateTimeOffset
End Structure

Public Event TemperatureChanged As TemperatureChanged

Private previous As Decimal


Private current As Decimal
Private tolerance As Decimal
Private tis As New List(Of TemperatureInfo)

Public Sub New(temperature As Decimal, tolerance As Decimal)


current = temperature
Dim ti As New TemperatureInfo()
ti.Temperature = temperature
ti.Recorded = DateTimeOffset.UtcNow
tis.Add(ti)
Me.tolerance = tolerance
End Sub

Public Property CurrentTemperature As Decimal


Get
Return current
End Get
Set
Dim ti As New TemperatureInfo
ti.Temperature = value
ti.Recorded = DateTimeOffset.UtcNow
previous = current
current = value
If Math.Abs(current - previous) >= tolerance Then
raise_TemperatureChanged(New TemperatureChangedEventArgs(previous, current, ti.Recorded))
End If
End Set
End Property

Public Sub raise_TemperatureChanged(eventArgs As TemperatureChangedEventArgs)


If TemperatureChangedEvent Is Nothing Then Exit Sub

Dim ListenerList() As System.Delegate = TemperatureChangedEvent.GetInvocationList()


For Each d As TemperatureChanged In TemperatureChangedEvent.GetInvocationList()
If d.Method.Name.Contains("Duplicate") Then
Console.WriteLine("Duplicate event handler; event handler not executed.")
Else
d.Invoke(Me, eventArgs)
End If
Next
End Sub
End Class

Public Class Example


Public Class Example
Public WithEvents temp As Temperature

Public Shared Sub Main()


Dim ex As New Example()
End Sub

Public Sub New()


temp = New Temperature(65, 3)
RecordTemperatures()
Dim ex As New Example(temp)
ex.RecordTemperatures()
End Sub

Public Sub New(t As Temperature)


temp = t
RecordTemperatures()
End Sub

Public Sub RecordTemperatures()


temp.CurrentTemperature = 66
temp.CurrentTemperature = 63

End Sub

Friend Shared Sub TemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _


Handles temp.TemperatureChanged
Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature)
End Sub

Friend Shared Sub DuplicateTemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _


Handles temp.TemperatureChanged
Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature)
End Sub
End Class

Overloads
Common Language Specification impone los siguientes requisitos a los miembros sobrecargados:
Los miembros se pueden sobrecargar según el número de parámetros y el tipo de cualquiera de los
parámetros. A la hora de distinguir entre sobrecargas, no se tienen en cuenta los factores de convención de
llamada, el tipo de valor devuelto, los modificadores personalizados aplicados al método o a su parámetro,
ni si los parámetros se pasan por valor o por referencia. Para consultar un ejemplo, vea el código del
requisito que establece que los nombres deben ser únicos en cada ámbito que se incluye en la sección
Convenciones de nomenclatura.
Solo las propiedades y los métodos se pueden sobrecargar. Los campos y eventos no se pueden sobrecargar.
Los métodos genéricos pueden sobrecargarse en función del número de parámetros genéricos.

NOTE
Los operadores op_Explicit y op_Implicit son una excepción de la regla que establece que el valor devuelto no se
considera parte de la signatura de un método en la resolución de la sobrecarga. Estos dos operadores se pueden sobrecargar
según sus parámetros y su valor devuelto.

Excepciones
Los objetos de excepción deben derivar de System.Exception o de otro tipo derivado de System.Exception . En el
ejemplo siguiente se muestra el error de compilador que se produce cuando una clase personalizada denominada
ErrorClass se utiliza para el control de excepciones.
using System;

[assembly: CLSCompliant(true)]

public class ErrorClass


{
string msg;

public ErrorClass(string errorMessage)


{
msg = errorMessage;
}

public string Message


{
get { return msg; }
}
}

public static class StringUtilities


{
public static string[] SplitString(this string value, int index)
{
if (index < 0 | index > value.Length) {
ErrorClass badIndex = new ErrorClass("The index is not within the string.");
throw badIndex;
}
string[] retVal = { value.Substring(0, index - 1),
value.Substring(index) };
return retVal;
}
}
// Compilation produces a compiler error like the following:
// Exceptions1.cs(26,16): error CS0155: The type caught or thrown must be derived from
// System.Exception
Imports System.Runtime.CompilerServices

<Assembly: CLSCompliant(True)>

Public Class ErrorClass


Dim msg As String

Public Sub New(errorMessage As String)


msg = errorMessage
End Sub

Public ReadOnly Property Message As String


Get
Return msg
End Get
End Property
End Class

Public Module StringUtilities


<Extension()> Public Function SplitString(value As String, index As Integer) As String()
If index < 0 Or index > value.Length Then
Dim BadIndex As New ErrorClass("The index is not within the string.")
Throw BadIndex
End If
Dim retVal() As String = { value.Substring(0, index - 1),
value.Substring(index) }
Return retVal
End Function
End Module
' Compilation produces a compiler error like the following:
' Exceptions1.vb(27) : error BC30665: 'Throw' operand must derive from 'System.Exception'.
'
' Throw BadIndex
' ~~~~~~~~~~~~~~

Para corregir este error, la clase ErrorClass debe heredar de System.Exception . Además, la propiedad Message
debe invalidarse. En el ejemplo siguiente se corrigen estos errores para definir una clase ErrorClass que sea
conforme a CLS.
using System;

[assembly: CLSCompliant(true)]

public class ErrorClass : Exception


{
string msg;

public ErrorClass(string errorMessage)


{
msg = errorMessage;
}

public override string Message


{
get { return msg; }
}
}

public static class StringUtilities


{
public static string[] SplitString(this string value, int index)
{
if (index < 0 | index > value.Length) {
ErrorClass badIndex = new ErrorClass("The index is not within the string.");
throw badIndex;
}
string[] retVal = { value.Substring(0, index - 1),
value.Substring(index) };
return retVal;
}
}

Imports System.Runtime.CompilerServices

<Assembly: CLSCompliant(True)>

Public Class ErrorClass : Inherits Exception


Dim msg As String

Public Sub New(errorMessage As String)


msg = errorMessage
End Sub

Public Overrides ReadOnly Property Message As String


Get
Return msg
End Get
End Property
End Class

Public Module StringUtilities


<Extension()> Public Function SplitString(value As String, index As Integer) As String()
If index < 0 Or index > value.Length Then
Dim BadIndex As New ErrorClass("The index is not within the string.")
Throw BadIndex
End If
Dim retVal() As String = { value.Substring(0, index - 1),
value.Substring(index) }
Return retVal
End Function
End Module

Atributos
En los ensamblados de .NET Framework, los atributos personalizados proporcionan un mecanismo extensible para
almacenar atributos personalizados y recuperar metadatos sobre objetos de programación, tales como
ensamblados, tipos, miembros y parámetros de método. Los atributos personalizados deben derivar de
System.Attribute o de un tipo derivado de System.Attribute .
En el ejemplo siguiente se infringe esta regla. Define una clase NumericAttribute que no se deriva de
System.Attribute . Un error del compilador solo se produce cuando se aplica el atributo no conforme a CLS, y no
cuando se define la clase.

using System;

[assembly: CLSCompliant(true)]

[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Struct)]
public class NumericAttribute
{
private bool _isNumeric;

public NumericAttribute(bool isNumeric)


{
_isNumeric = isNumeric;
}

public bool IsNumeric


{
get { return _isNumeric; }
}
}

[Numeric(true)] public struct UDouble


{
double Value;
}
// Compilation produces a compiler error like the following:
// Attribute1.cs(22,2): error CS0616: 'NumericAttribute' is not an attribute class
// Attribute1.cs(7,14): (Location of symbol related to previous error)

<Assembly: CLSCompliant(True)>

<AttributeUsageAttribute(AttributeTargets.Class Or AttributeTargets.Struct)> _
Public Class NumericAttribute
Private _isNumeric As Boolean

Public Sub New(isNumeric As Boolean)


_isNumeric = isNumeric
End Sub

Public ReadOnly Property IsNumeric As Boolean


Get
Return _isNumeric
End Get
End Property
End Class

<Numeric(True)> Public Structure UDouble


Dim Value As Double
End Structure
' Compilation produces a compiler error like the following:
' error BC31504: 'NumericAttribute' cannot be used as an attribute because it
' does not inherit from 'System.Attribute'.
'
' <Numeric(True)> Public Structure UDouble
' ~~~~~~~~~~~~~
El constructor o las propiedades de un atributo conforme a CLS pueden exponer solo los tipos siguientes:
Boolean
Byte
Char
Double
Int16
Int32
Int64
Single
String
Type
Cualquier tipo de enumeración cuyo tipo subyacente sea Byte , Int16 , Int32 o Int64 .

En el ejemplo siguiente se define una clase DescriptionAttribute que se deriva de Attribute. El constructor de clase
tiene un parámetro de tipo Descriptor , de modo que la clase no es conforme a CLS. El compilador de C# emite
una advertencia, pero realiza la compilación correctamente.

using System;

[assembly:CLSCompliantAttribute(true)]

public enum DescriptorType { type, member };

public class Descriptor


{
public DescriptorType Type;
public String Description;
}

[AttributeUsage(AttributeTargets.All)]
public class DescriptionAttribute : Attribute
{
private Descriptor desc;

public DescriptionAttribute(Descriptor d)
{
desc = d;
}

public Descriptor Descriptor


{ get { return desc; } }
}
// Attempting to compile the example displays output like the following:
// warning CS3015: 'DescriptionAttribute' has no accessible
// constructors which use only CLS-compliant types
<Assembly:CLSCompliantAttribute(True)>

Public Enum DescriptorType As Integer


Type = 0
Member = 1
End Enum

Public Class Descriptor


Public Type As DescriptorType
Public Description As String
End Class

<AttributeUsage(AttributeTargets.All)> _
Public Class DescriptionAttribute : Inherits Attribute
Private desc As Descriptor

Public Sub New(d As Descriptor)


desc = d
End Sub

Public ReadOnly Property Descriptor As Descriptor


Get
Return desc
End Get
End Property
End Class

CLSCompliantAttribute (Atributo)
El atributo CLSCompliantAttribute se usa para indicar si un elemento del programa es conforme a Common
Language Specification. El constructor CLSCompliantAttribute.CLSCompliantAttribute(Boolean) contiene un único
parámetro obligatorio, isCompliant, que indica si el elemento del programa es conforme a CLS.
En tiempo de compilación, el compilador detecta los elementos que supuestamente deberían ser conformes a CLS
y emite una advertencia. El compilador no emite advertencias relacionadas con los tipos o miembros cuya no
conformidad se declara explícitamente.
Los desarrolladores de componentes pueden usar el atributo CLSCompliantAttribute de dos maneras:
Para definir las partes de la interfaz pública expuestas por un componente que son conformes a CLS y las
que no lo son. Cuando el atributo se utiliza para marcar determinados elementos del programa como
conformes a CLS, este uso garantiza que dichos elementos son accesibles desde todos los lenguajes y
herramientas que tienen como destino .NET Framework.
Para garantizar que la interfaz pública de la biblioteca de componentes expone solo elementos del programa
que son conformes a CLS. Si los elementos no son conformes a CLS, los compiladores normalmente
emitirán una advertencia.

WARNING
En algunos casos, los compiladores de lenguaje aplican reglas conformes a CLS independientemente de si se usa el atributo
CLSCompliantAttribute o no. Por ejemplo, la definición de un miembro *static en una interfaz infringe una regla de CLS.
Pero si define un miembro *static en una interfaz, el compilador de C# muestra un mensaje de error y se produce un error
al compilar la aplicación.

El atributo CLSCompliantAttribute está marcado con un atributo AttributeUsageAttribute que tiene el valor
AttributeTargets.All . Este valor le permite aplicar el atributo CLSCompliantAttribute a cualquier elemento de
programa, incluidos los ensamblados, módulos, tipos (clases, estructuras, enumeraciones, interfaces, delegados),
miembros de tipo (constructores, métodos, propiedades, campos y eventos), parámetros, parámetros genéricos y
valores devueltos. Sin embargo, en la práctica, solo debe aplicar el atributo a los ensamblados, tipos y miembros
del tipo. De lo contrario, los compiladores omitirán el atributo y seguirán generando advertencias de compilación
siempre que encuentren un parámetro no conforme, un parámetro genérico o un valor devuelto en la interfaz
pública de la biblioteca.
El valor del atributo CLSCompliantAttribute lo heredan los elementos del programa contenidos. Por ejemplo, si se
marca un ensamblado como conforme a CLS, sus tipos también son conformes a CLS. Si un tipo se marca como
conforme a CLS, sus tipos anidados y miembros también serán conformes a CLS.
Puede invalidar explícitamente la conformidad heredada aplicando el atributo CLSCompliantAttribute a un
elemento del programa contenido. Por ejemplo, puede usar el atributo CLSCompliantAttribute con el valor
isCompliant establecido en false para definir un tipo no conforme en un ensamblado conforme, y puede usar el
atributo con el valor isCompliant establecido en true para definir un tipo conforme en un ensamblado no
conforme. También puede definir miembros no conformes en un tipo conforme. Pero un tipo no conforme no
puede tener miembros conformes, por lo que no puede usar el atributo con el valor isCompliant establecido en
true para invalidar la herencia de un tipo no conforme.

Cuando desarrolle componentes, debe utilizar siempre el atributo CLSCompliantAttribute para indicar si el
ensamblado, sus tipos y sus miembros son conformes a CLS.
Para crear componentes conformes a CLS:
1. Utilice CLSCompliantAttribute para marcar el ensamblado como conforme a CLS.
2. Marque como no conforme los tipos expuestos públicamente en el ensamblado que no sean conformes a
CLS.
3. Marque como no conforme los miembros expuestos públicamente en tipos conformes a CLS.
4. Proporcione una alternativa conforme a CLS para los miembros que no sean conformes a CLS.
Si ha marcado correctamente todos los tipos y miembros no conformes, el compilador no debe emitir ninguna
advertencia de no conformidad. Sin embargo, debe indicar qué miembros no son conformes a CLS y mostrar las
alternativas conformes a CLS en la documentación del producto.
En el ejemplo siguiente se usa el atributo CLSCompliantAttribute para definir un ensamblado conforme a CLS y un
tipo, CharacterUtilities , que tiene dos miembros no conformes a CLS. Dado que ambos miembros están
etiquetados con el atributo CLSCompliant(false) , el compilador no genera ninguna advertencia. La clase también
proporciona una alternativa conforme a CLS para ambos métodos. Por lo general, simplemente se agregarían dos
sobrecargas al método ToUTF16 para proporcionar alternativas conformes a CLS. Sin embargo, puesto que no se
pueden sobrecargar los métodos en función de un valor devuelto, los nombres de los métodos conformes a CLS
son distintos de los nombres de los métodos no conformes.
using System;
using System.Text;

[assembly:CLSCompliant(true)]

public class CharacterUtilities


{
[CLSCompliant(false)] public static ushort ToUTF16(String s)
{
s = s.Normalize(NormalizationForm.FormC);
return Convert.ToUInt16(s[0]);
}

[CLSCompliant(false)] public static ushort ToUTF16(Char ch)


{
return Convert.ToUInt16(ch);
}

// CLS-compliant alternative for ToUTF16(String).


public static int ToUTF16CodeUnit(String s)
{
s = s.Normalize(NormalizationForm.FormC);
return (int) Convert.ToUInt16(s[0]);
}

// CLS-compliant alternative for ToUTF16(Char).


public static int ToUTF16CodeUnit(Char ch)
{
return Convert.ToInt32(ch);
}

public bool HasMultipleRepresentations(String s)


{
String s1 = s.Normalize(NormalizationForm.FormC);
return s.Equals(s1);
}

public int GetUnicodeCodePoint(Char ch)


{
if (Char.IsSurrogate(ch))
throw new ArgumentException("ch cannot be a high or low surrogate.");

return Char.ConvertToUtf32(ch.ToString(), 0);


}

public int GetUnicodeCodePoint(Char[] chars)


{
if (chars.Length > 2)
throw new ArgumentException("The array has too many characters.");

if (chars.Length == 2) {
if (! Char.IsSurrogatePair(chars[0], chars[1]))
throw new ArgumentException("The array must contain a low and a high surrogate.");
else
return Char.ConvertToUtf32(chars[0], chars[1]);
}
else {
return Char.ConvertToUtf32(chars.ToString(), 0);
}
}
}
Imports System.Text

<Assembly:CLSCompliant(True)>

Public Class CharacterUtilities


<CLSCompliant(False)> Public Shared Function ToUTF16(s As String) As UShort
s = s.Normalize(NormalizationForm.FormC)
Return Convert.ToUInt16(s(0))
End Function

<CLSCompliant(False)> Public Shared Function ToUTF16(ch As Char) As UShort


Return Convert.ToUInt16(ch)
End Function

' CLS-compliant alternative for ToUTF16(String).


Public Shared Function ToUTF16CodeUnit(s As String) As Integer
s = s.Normalize(NormalizationForm.FormC)
Return CInt(Convert.ToInt16(s(0)))
End Function

' CLS-compliant alternative for ToUTF16(Char).


Public Shared Function ToUTF16CodeUnit(ch As Char) As Integer
Return Convert.ToInt32(ch)
End Function

Public Function HasMultipleRepresentations(s As String) As Boolean


Dim s1 As String = s.Normalize(NormalizationForm.FormC)
Return s.Equals(s1)
End Function

Public Function GetUnicodeCodePoint(ch As Char) As Integer


If Char.IsSurrogate(ch) Then
Throw New ArgumentException("ch cannot be a high or low surrogate.")
End If
Return Char.ConvertToUtf32(ch.ToString(), 0)
End Function

Public Function GetUnicodeCodePoint(chars() As Char) As Integer


If chars.Length > 2 Then
Throw New ArgumentException("The array has too many characters.")
End If
If chars.Length = 2 Then
If Not Char.IsSurrogatePair(chars(0), chars(1)) Then
Throw New ArgumentException("The array must contain a low and a high surrogate.")
Else
Return Char.ConvertToUtf32(chars(0), chars(1))
End If
Else
Return Char.ConvertToUtf32(chars.ToString(), 0)
End If
End Function
End Class

Si está desarrollando una aplicación en lugar de una biblioteca (es decir, si no expone los tipos o miembros que
pueden consumir otros desarrolladores de aplicaciones), la conformidad con CLS de los elementos del programa
usados por la aplicación solo tendrá interés si su lenguaje admite estos elementos. En ese caso, el compilador del
lenguaje generará un error cuando intente utilizar un elemento no conforme a CLS.

Interoperabilidad entre lenguajes


La independencia de lenguaje tiene varios significados posibles. Un significado implica perfectamente tipos escritos
en un lenguaje desde una aplicación escrita en otro lenguaje. Un segundo significado, que es el descrito en este
artículo, implica combinar código escrito en varios lenguajes en un único ensamblado de .NET Framework.
El ejemplo siguiente muestra la interoperabilidad entre lenguajes creando una biblioteca de clases de denominada
Utilities.dll que incluye dos clases, NumericLib y StringLib . La clase NumericLib está escrita en C#, y la clase
StringLib está escrita en Visual Basic. A continuación se muestra el código fuente para StringUtil.vb , que incluye
un solo miembro, ToTitleCase , en su clase StringLib .

Imports System.Collections.Generic
Imports System.Runtime.CompilerServices

Public Module StringLib


Private exclusions As List(Of String)

Sub New()
Dim words() As String = { "a", "an", "and", "of", "the" }
exclusions = New List(Of String)
exclusions.AddRange(words)
End Sub

<Extension()> _
Public Function ToTitleCase(title As String) As String
Dim words() As String = title.Split()
Dim result As String = String.Empty

For ctr As Integer = 0 To words.Length - 1


Dim word As String = words(ctr)
If ctr = 0 OrElse Not exclusions.Contains(word.ToLower()) Then
result += word.Substring(0, 1).ToUpper() + _
word.Substring(1).ToLower()
Else
result += word.ToLower()
End If
If ctr <= words.Length - 1 Then
result += " "
End If
Next
Return result
End Function
End Module

A continuación se muestra el código fuente para NumberUtil.cs, que define una clase NumericLib con dos
miembros, IsEven y NearZero .
using System;

public static class NumericLib


{
public static bool IsEven(this IConvertible number)
{
if (number is Byte ||
number is SByte ||
number is Int16 ||
number is UInt16 ||
number is Int32 ||
number is UInt32 ||
number is Int64)
return ((long) number) % 2 == 0;
else if (number is UInt64)
return ((ulong) number) %2 == 0;
else
throw new NotSupportedException("IsEven called for a non-integer value.");
}

public static bool NearZero(double number)


{
return number < .00001;
}
}

Para empaquetar las dos clases en un solo ensamblado, debe compilarlas en módulos. Para compilar el archivo de
código fuente de Visual Basic en un módulo, use este comando:

vbc /t:module StringUtil.vb

Para compilar el archivo de código fuente de C# en un módulo, use este comando:

csc /t:module NumberUtil.cs

A continuación, use la herramienta de vinculación (Link.exe) para compilar los dos módulos en un ensamblado:

link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll

El ejemplo siguiente llama a los métodos NumericLib.NearZero y StringLib.ToTitleCase . El código de Visual Basic y
el código de C# pueden tener acceso a los métodos de ambas clases.

using System;

public class Example


{
public static void Main()
{
Double dbl = 0.0 - Double.Epsilon;
Console.WriteLine(NumericLib.NearZero(dbl));

string s = "war and peace";


Console.WriteLine(s.ToTitleCase());
}
}
// The example displays the following output:
// True
// War and Peace
Module Example
Public Sub Main()
Dim dbl As Double = 0.0 - Double.Epsilon
Console.WriteLine(NumericLib.NearZero(dbl))

Dim s As String = "war and peace"


Console.WriteLine(s.ToTitleCase())
End Sub
End Module
' The example displays the following output:
' True
' War and Peace

Para compilar el código de Visual Basic, use este comando:

vbc example.vb /r:UtilityLib.dll

Para compilar con C#, cambie el nombre del compilador de vbc a csc y cambie la extensión de archivo .vb a .cs:

csc example.cs /r:UtilityLib.dll


Independencia del lenguaje y componentes
independientes del lenguaje
16/09/2020 • 110 minutes to read • Edit Online

.NET Framework. es independiente del lenguaje. Esto significa que, como desarrollador, puede utilizar uno de los
muchos lenguajes que tienen como destino .NET Framework; por ejemplo, C#, C++/CLI, Eiffel, F#, IronPython,
IronRuby, PowerBuilder, Visual Basic, Visual COBOL y Windows PowerShell. Puede acceder a los tipos y miembros
de las bibliotecas de clases desarrolladas para .NET Framework sin necesidad de conocer el lenguaje en el que se
escribieron originalmente y sin necesidad de seguir ninguna de las convenciones del lenguaje original. Si es un
desarrollador de componentes, podrá acceder a su componente desde cualquier aplicación de .NET Framework,
con independencia del lenguaje.

NOTE
En la primera parte de este artículo, se explica cómo se crean componentes independientes del lenguaje, es decir,
componentes que pueden utilizarse en aplicaciones escritas en cualquier lenguaje. También puede crear una aplicación o
componente únicos de código fuente escrito en varios lenguajes; consulte Interoperabilidad entre lenguajes en la segunda
parte de este artículo.

Para que los objetos puedan tener una interacción total con otros objetos escritos en cualquier lenguaje, estos
objetos solo deben exponer a los llamadores las características que son comunes a todos los lenguajes. Este
conjunto común de características se define mediante Common Language Specification (CLS), que es un
conjunto de reglas que se aplican a los ensamblados generados. Common Language Specification se define en el
apartado I, cláusulas 7 a 11 del estándar ECMA-335: Common Language Infrastructure.
Si el componente se ajusta a Common Language Specification, existe la garantía de que será conforme a CLS y
que será accesible desde el código de un ensamblado escrito en cualquier lenguaje de programación que admita
CLS. Para determinar si el componente se ajusta o no a Common Language Specification en tiempo de
compilación, puede aplicar el atributo CLSCompliantAttribute en el código fuente. Para más información, consulte
CLSCompliantAttribute (Atributo).
En este artículo:
Reglas de conformidad con CLS
Signaturas de tipos y miembros de tipo
Convenciones de nomenclatura
Conversión de tipos
Matrices
Interfaces
Enumeraciones
Miembros de tipos en general
Accesibilidad de miembros
Miembros y tipos genéricos
Constructores
Propiedades
Eventos
Sobrecargas
Excepciones
Atributos
CLSCompliantAttribute (Atributo)
Interoperabilidad entre lenguajes

Reglas de conformidad con CLS


En esta sección se explican las reglas para crear un componente conforme a CLS. Para obtener una lista completa
de las normas, vea el apartado I, cláusula 11 del estándar ECMA-335: Common Language Infrastructure.

NOTE
Common Language Specification describe en cada regla la conformidad con CLS en referencia a los consumidores
(desarrolladores que acceden mediante programación a un componente que es conforme a CLS), los marcos
(desarrolladores que usan un compilador de lenguaje para crear bibliotecas conformes a CLS) y los extensores
(desarrolladores que crean una herramienta, como un compilador de lenguaje o un analizador de código, que crea
componentes conformes a CLS). Este artículo se centra en las reglas que se aplican a los marcos. Sin embargo, observe que
algunas de las reglas que se aplican a los extensores también se pueden aplicar a los ensamblados que se crean mediante
Reflection.Emit.

Para diseñar un componente que sea independiente del lenguaje, solo tiene que aplicar las reglas de
conformidad con CLS a la interfaz pública del componente. La implementación privada no tiene que ajustarse a la
especificación.

IMPORTANT
Las reglas de conformidad con CLS solo se aplican a la interfaz pública de un componente, y no a su implementación
privada.

Por ejemplo, los números enteros sin signo distintos de Byte no son conformes a CLS. Dado que la clase Person
del siguiente ejemplo expone una propiedad Age del tipo UInt16, el código siguiente desencadena una
advertencia del compilador.

using System;

[assembly: CLSCompliant(true)]

public class Person


{
private UInt16 personAge = 0;

public UInt16 Age


{ get { return personAge; } }
}
// The attempt to compile the example displays the following compiler warning:
// Public1.cs(10,18): warning CS3003: Type of 'Person.Age' is not CLS-compliant
<Assembly: CLSCompliant(True)>

Public Class Person


Private personAge As UInt16

Public ReadOnly Property Age As UInt16


Get
Return personAge
End Get
End Property
End Class
' The attempt to compile the example displays the following compiler warning:
' Public1.vb(9) : warning BC40027: Return type of function 'Age' is not CLS-compliant.
'
' Public ReadOnly Property Age As UInt16
' ~~~

Para hacer que la clase Person sea conforme a CLS, puede cambiar el tipo de la propiedad Age de UInt16 a
Int16, que es un entero de 16 bits con signo conforme a CLS. No es necesario que cambie el tipo del campo
privado personAge .

using System;

[assembly: CLSCompliant(true)]

public class Person


{
private Int16 personAge = 0;

public Int16 Age


{ get { return personAge; } }
}

<Assembly: CLSCompliant(True)>

Public Class Person


Private personAge As UInt16

Public ReadOnly Property Age As Int16


Get
Return CType(personAge, Int16)
End Get
End Property
End Class

Las interfaces públicas de una biblioteca se componen de los elementos siguientes:


Definiciones de clases públicas.
Definiciones de los miembros públicos de las clases públicas y de los miembros accesibles por las clases
derivadas (es decir, miembros protegidos).
Parámetros y tipos devueltos de los métodos públicos de las clases públicas y parámetros y tipos
devueltos de los métodos accesibles por las clases derivadas.
Las reglas de conformidad con CLS se muestran en la tabla siguiente. El texto de las normas se toma literalmente
del estándar ECMA-335: Common Language Infrastructure, Copyright de 2012 de Ecma International. En las
secciones siguientes encontrará información más detallada sobre estas reglas.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Accesibilidad Accesibilidad de miembros Cuando se reemplacen 10


métodos heredados, no
debe modificarse la
accesibilidad, excepto
cuando se reemplace un
método heredado de un
ensamblado diferente cuya
accesibilidad sea
family-or-assembly . En
este caso, el reemplazo debe
tener accesibilidad family .

Accesibilidad Accesibilidad de miembros La visibilidad y accesibilidad 12


de los tipos y miembros se
establecerá de modo que
los tipos de la signatura de
cualquier miembro sean
visibles y accesibles siempre
que el propio miembro sea
visible y accesible. Por
ejemplo, un método público
que sea visible fuera del
ensamblado no debe tener
ningún argumento cuyo
tipo solamente sea visible
en el interior del
ensamblado. La visibilidad y
la accesibilidad de los tipos
que conforman un tipo
genérico con instancias que
se utilice en la signatura de
cualquier miembro deben
establecerse de forma que
serán visibles y accesibles
siempre que el propio
miembro sea visible y
accesible. Por ejemplo, un
tipo genérico con instancias
que esté presente en la
signatura de un miembro
que sea visible fuera del
ensamblado no debe tener
ningún argumento genérico
cuyo tipo solamente sea
visible en el interior del
ensamblado.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Matrices Matrices Las matrices deben tener 16


elementos con un tipo
conforme a CLS y los límites
inferiores de todas las
dimensiones de la matriz
deben ser iguales a cero.
Para distinguir entre
sobrecargas, solo se tendrá
en cuenta el hecho de que
el elemento es una matriz y
el tipo de elementos de la
matriz. Cuando la
sobrecarga se basa en dos o
varios tipos de matrices, los
tipos de elementos deben
ser tipos con nombre.

Atributos Atributos Los atributos deben ser del 41


tipo System.Attribute o de
un tipo que se herede de
este.

Atributos Atributos CLS solo permite un 34


subconjunto de
codificaciones de atributos
personalizados. Los únicos
tipos que deben aparecer en
estas codificaciones son
(consulte el apartado IV):
System.Type, System.String,
System.Char,
System.Boolean,
System.Byte, System.Int16,
System.Int32, System.Int64,
System.Single,
System.Double y cualquier
tipo de enumeración basada
en un tipo de entero base
conforme a CLS.

Atributos Atributos CLS no admite 35


modificadores obligatorios
que sean visibles
públicamente ( modreq , vea
el Apartado II), pero sí
admite modificadores
opcionales ( modopt , vea el
apartado II) que no
comprenda.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Constructores Constructores Un constructor de objetos 21


debe llamar a un
constructor de instancias de
su clase base antes de que
tenga lugar cualquier acceso
a los datos de instancia
heredados. (Esto no se
aplica a los tipos de valor,
que no necesitan
constructores.)

Constructores Constructores No debe llamarse a los 22


constructores de objetos
excepto durante la creación
de un objeto y no podrá
iniciarse dos veces un
objeto.

Enumeraciones Enumeraciones El tipo subyacente de una 7


enumeración debe ser un
tipo de entero integrado en
CLS, el nombre del campo
debe ser “value__” y dicho
campo debe marcarse como
RTSpecialName .

Enumeraciones Enumeraciones Hay dos tipos distintos de 8


enumeraciones, que se
indican mediante la
presencia o ausencia del
atributo personalizado
System.FlagsAttribute (vea
la biblioteca del apartado
IV). Uno representa valores
enteros con nombre; el otro
representa los marcadores
de bit con nombre que se
pueden combinar para
generar un valor sin
nombre. El valor de enum
no se limita a los valores
especificados.

Enumeraciones Enumeraciones Los campos estáticos 9


literales de una
enumeración deben
contener el tipo de la propia
enumeración.

Events Eventos Los métodos que 29


implementen un evento se
marcarán como
SpecialName en los
metadatos.

Events Eventos La accesibilidad de un 30


evento y sus descriptores de
acceso será idéntica.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Events Eventos Los métodos add y 31


remove de un evento
deben estar presentes o
ausentes a la vez.

Events Eventos Los métodos add y 32


remove de un evento
deben tomar un parámetro
cuyo tipo defina el tipo del
evento, y ese tipo debe
derivarse de
System.Delegate.

Events Eventos Los eventos deben 33


adherirse a un patrón de
asignación de nombres
concreto. En las
comparaciones de nombres
correspondientes, se omitirá
el atributo SpecialName
mencionado en la regla 29
de CLS y se seguirán las
reglas del identificador.

Excepciones Excepciones Los objetos que se inicien 40


deberán ser de tipo
System.Exception o de un
tipo que herede de él. No
obstante, los métodos
conformes a CLS no
necesitan bloquear la
propagación de otros tipos
de excepciones.

General Conformidad con CLS: Las reglas de CLS solo se 1


reglas aplican a las partes de los
tipos que son accesibles o
visibles desde fuera del
ensamblado de definición.

General Conformidad con CLS: Los miembros de tipos no 2


reglas conformes con CLS no
deben marcarse como
conformes con CLS.

Genéricos Miembros y tipos genéricos Los tipos anidados deben 42


tener, como mínimo, el
mismo número de
parámetros genéricos que el
tipo envolvente. Los
parámetros genéricos de un
tipo anidado se
corresponden por posición
con los parámetros
genéricos del tipo
contenedor.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Genéricos Miembros y tipos genéricos El nombre de un tipo 43


genérico debe codificar el
número de parámetros de
tipo declarados en el tipo no
anidado o que se acaban de
introducir en el tipo, si este
está anidado, según las
reglas definidas
anteriormente.

Genéricos Miembros y tipos genéricos Todo tipo genérico deberá 4444


volver a declarar
restricciones suficientes
como para garantizar que
cualquier restricción de las
interfaces o del tipo base se
vea satisfecha por las
restricciones del tipo
genérico.

Genéricos Miembros y tipos genéricos Los tipos que se utilicen 45


como restricciones en
parámetros genéricos deben
ser conformes a CLS.

Genéricos Miembros y tipos genéricos Se entiende que la 46


visibilidad y accesibilidad de
los miembros (incluidos los
tipos anidados) de un tipo
genérico con instancias
deben quedar restringidas al
ámbito específico de la
creación de instancias, y no
a la declaración de tipos
genéricos en general.
Suponiendo que esto sea
cierto, se seguirán aplicando
las especificaciones de
accesibilidad y visibilidad de
la regla 12 de CLS.

Genéricos Miembros y tipos genéricos Cada método genérico 47


abstracto o virtual deberá
tener su propia
implementación concreta
(no abstracta)
predeterminada.

Interfaces Interfaces Las interfaces conformes a 18


CLS no deben requerir la
definición de métodos no
conformes a CLS para su
implementación.

Interfaces Interfaces Las interfaces conformes a 19


CLS no pueden definir
métodos estáticos ni
pueden definir campos.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Miembros Miembros de tipos en Los campos y métodos 36


general static globales no son
conformes a CLS.

Miembros -- El valor de un estático literal 13


se especifica mediante el
uso de metadatos de
inicialización de campos. Un
literal conforme a CLS debe
tener un valor especificado
en los metadatos de
inicialización de campos que
sea exactamente del mismo
tipo que el literal (o el tipo
subyacente, si el literal es
enum ).

Miembros Miembros de tipos en La restricción vararg no 15


general forma parte de CLS y la
única convención de
llamada admitida por CLS es
la convención de llamada
administrada estándar.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Convenciones de Convenciones de Los ensamblados seguirán 4


nomenclatura nomenclatura las directrices del anexo 7
del informe técnico 15 del
estándar Unicode 3.0, que
rige el conjunto de
caracteres permitidos que
pueden usarse como
iniciales e incluirse en los
identificadores. Estas
directrices están disponibles
en línea en
https://www.unicode.org/uni
code/reports/tr15/tr15-
18.html. Los identificadores
deben aparecer en el
formato canónico definido
por el Formulario C de
normalización Unicode. En
aras de la conformidad con
CLS, dos identificadores se
considerarán iguales si sus
asignaciones de minúsculas
(tal y como se especificó en
las asignaciones unívocas de
minúsculas de Unicode en
las que no se tiene en
cuenta la configuración
regional) son iguales. Es
decir, para que dos
identificadores se
consideren diferentes según
CLS, tendrán que
diferenciarse en algo más
que en el uso de
mayúsculas y minúsculas.
Pero para invalidar una
definición heredada, CLI
requiere que se use la
codificación exacta de la
declaración original.

Sobrecarga Convenciones de Todos los nombres 5


nomenclatura especificados en un ámbito
conforme a CLS deben ser
distintos
independientemente del
tipo, salvo en los casos en
los que los nombres sean
idénticos y se resuelvan
mediante sobrecarga. Es
decir, mientras CTS permite
que un tipo único use el
mismo nombre para un
método y un campo, CLS
no.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Sobrecarga Convenciones de Los campos y los tipos 6


nomenclatura anidados deben distinguirse
únicamente por la
comparación de
identificadores, aunque CTS
permita que se distingan
signaturas diferentes. Los
métodos, las propiedades y
los eventos que tengan el
mismo nombre (por
comparación de
identificadores) deben
distinguirse por algo más
que el tipo de valor
devuelto, excepto según lo
especificado en la regla 39
de CLS.

Sobrecarga Sobrecargas Solo las propiedades y los 37


métodos se pueden
sobrecargar.

Sobrecarga Sobrecargas Las propiedades y los 38


métodos se pueden
sobrecargar únicamente en
función del número y los
tipos de sus parámetros,
excepto los operadores de
conversión denominados
op_Implicit y
op_Explicit , que también
se pueden sobrecargar en
función del tipo de valor
devuelto.

Sobrecarga -- Si dos o más de los 48


métodos conformes a CLS
declarados en un tipo tienen
el mismo nombre y, en un
conjunto específico de
instancias de tipos, tienen
los mismos tipos de valor
devuelto y parámetros,
todos estos métodos serán
semánticamente
equivalentes en esas
instancias de tipos.

Tipos Signaturas de tipos y System.Object es conforme 23


miembros de tipo a CLS. Cualquier otra clase
conforme a CLS se heredará
de una clase conforme a
CLS.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Propiedades Propiedades Los métodos que 24


implementan los métodos
de captador y establecedor
de una propiedad deben
estar marcados con
SpecialName en los
metadatos.

Propiedades Propiedades Todos los descriptores de 26


acceso de una propiedad
deben ser estáticos,
virtuales o de instancia.

Propiedades Propiedades El tipo de una propiedad es 27


el tipo de valor devuelto del
método captador y el tipo
del último argumento del
método establecedor. Los
tipos de los parámetros de
la propiedad deben ser los
tipos de los parámetros del
método captador y los tipos
de todos los parámetros del
método establecedor,
excepto el último. Todos
estos tipos deben ser
conformes a CLS y no
pueden ser punteros
administrados (es decir, no
deben pasarse por
referencia).

Propiedades Propiedades Las propiedades deben 28


ajustarse a un patrón de
asignación de nombres
concreto. En las
comparaciones de nombres
correspondientes se omitirá
el atributo SpecialName
mencionado en la regla 24
de CLS y se seguirán las
reglas del identificador. Una
propiedad tendrá un
método de captador, un
método de establecedor o
ambos.

Conversión de tipos Conversión de tipos Si se proporciona 39


op_Implicit u
op_Explicit , deben darse
medios alternativos para
realizar la conversión.

Tipos Signaturas de tipos y Los tipos de valor a los que 3


miembros de tipo se les ha aplicado la
conversión boxing no son
conformes a CLS.
C AT EGO RÍA VEA REGL A N ÚM ERO DE REGL A

Tipos Signaturas de tipos y Todos los tipos que 11


miembros de tipo aparecen en una signatura
deben ser conformes a CLS.
Todos los tipos que forman
un tipo genérico con
instancias deben ser
conformes a CLS.

Tipos Signaturas de tipos y Las referencias a tipos no 14


miembros de tipo son conformes a CLS.

Tipos Signaturas de tipos y Los tipos de puntero no 17


miembros de tipo administrados no son
conformes a CLS.

Tipos Signaturas de tipos y Las interfaces, los tipos de 20


miembros de tipo valor y las clases conformes
a CLS no deben requerir la
implementación de
miembros no conformes a
CLS.

Signaturas de tipos y miembros de tipo


El tipo System.Object es conforme a CLS y es el tipo base de todos los tipos de objetos del sistema de tipos de
.NET Framework. La herencia de .NET Framework es implícita (por ejemplo, la clase String hereda implícitamente
de la clase Object) o explícita (por ejemplo, la clase CultureNotFoundException hereda explícitamente de la clase
ArgumentException, que a su vez hereda explícitamente de la clase SystemException, que a su vez hereda
también explícitamente de la clase Exception). Para que un tipo derivado sea conforme a CLS, su tipo base
también debe ser conforme a CLS.
En el ejemplo siguiente se muestra un tipo derivado cuyo tipo base no es conforme a CLS. En el ejemplo, se
define una clase base Counter que usa un entero de 32 bits sin signo como contador. Dado que la clase
proporciona la funcionalidad de contador ajustando un entero sin signo, la clase se marca como no conforme a
CLS. Como resultado, la clase derivada, NonZeroCounter , tampoco es conforme a CLS.
using System;

[assembly: CLSCompliant(true)]

[CLSCompliant(false)]
public class Counter
{
UInt32 ctr;

public Counter()
{
ctr = 0;
}

protected Counter(UInt32 ctr)


{
this.ctr = ctr;
}

public override string ToString()


{
return String.Format("{0}). ", ctr);
}

public UInt32 Value


{
get { return ctr; }
}

public void Increment()


{
ctr += (uint) 1;
}
}

public class NonZeroCounter : Counter


{
public NonZeroCounter(int startIndex) : this((uint) startIndex)
{
}

private NonZeroCounter(UInt32 startIndex) : base(startIndex)


{
}
}
// Compilation produces a compiler warning like the following:
// Type3.cs(37,14): warning CS3009: 'NonZeroCounter': base type 'Counter' is not
// CLS-compliant
// Type3.cs(7,14): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>

<CLSCompliant(False)> _
Public Class Counter
Dim ctr As UInt32

Public Sub New


ctr = 0
End Sub

Protected Sub New(ctr As UInt32)


ctr = ctr
End Sub

Public Overrides Function ToString() As String


Return String.Format("{0}). ", ctr)
End Function

Public ReadOnly Property Value As UInt32


Get
Return ctr
End Get
End Property

Public Sub Increment()


ctr += CType(1, UInt32)
End Sub
End Class

Public Class NonZeroCounter : Inherits Counter


Public Sub New(startIndex As Integer)
MyClass.New(CType(startIndex, UInt32))
End Sub

Private Sub New(startIndex As UInt32)


MyBase.New(CType(startIndex, UInt32))
End Sub
End Class
' Compilation produces a compiler warning like the following:
' Type3.vb(34) : warning BC40026: 'NonZeroCounter' is not CLS-compliant
' because it derives from 'Counter', which is not CLS-compliant.
'
' Public Class NonZeroCounter : Inherits Counter
' ~~~~~~~~~~~~~~

Todos los tipos que aparecen en las signaturas de miembros, incluidos los tipos de propiedades y los tipos de
valores devueltos de un método, deben ser conformes a CLS. Además, en el caso de los tipos genéricos:
Todos los tipos que forman un tipo genérico con instancias deben ser conformes a CLS.
Todos los tipos que se utilizan como restricciones en parámetros genéricos deben ser conformes a CLS.
El sistema de tipos común de .NET Framework incluye varios tipos integrados que se admiten directamente en
Common Language Runtime y que se codifican de forma especial en los metadatos de un ensamblado. De estos
tipos intrínsecos, los tipos enumerados en la tabla siguiente son conformes a CLS.

T IP O C O N F O RM E A C L S DESC RIP C IÓ N

Byte Entero de 8 bits sin signo

Int16 Entero de 16 bits con signo


T IP O C O N F O RM E A C L S DESC RIP C IÓ N

Int32 Entero de 32 bits con signo

Int64 Entero de 64 bits con signo

Single Valor de punto flotante de precisión sencilla

Double Valor de punto flotante de precisión doble

Boolean Tipo de valor true o false

Char Unidad de código con la codificación UTF-16

Decimal Número decimal de punto no flotante

IntPtr Puntero o identificador de un tamaño definido por la


plataforma

String Colección de cero, uno o varios objetos Char

Los tipos intrínsecos enumerados en la tabla siguiente no son conformes a CLS.

T IP O N O C O N F O RM E DESC RIP C IÓ N A LT ERN AT IVA C O N F O RM E A C L S

SByte Tipo de datos enteros de 8 bits con Int16


signo

TypedReference Puntero a un objeto y su tipo en None


tiempo de ejecución

UInt16 Entero de 16 bits sin signo Int32

UInt32 Entero de 32 bits sin signo Int64

UInt64 Entero de 64 bits sin signo Int64 (se puede desbordar), BigInteger
o Double

UIntPtr Puntero o identificador sin signo IntPtr

La biblioteca de clases de .NET Framework o cualquier otra biblioteca de clases puede incluir otros tipos que no
sean conformes a CLS; por ejemplo:
Tipos de valor a los que se les ha aplicado la conversión boxing. En el siguiente ejemplo de C# se crea una
clase con una propiedad pública de tipo int* denominada Value . Dado que int* es un tipo de valor al
que se le ha aplicado la conversión boxing, el compilador lo marca como no conforme a CLS.
using System;

[assembly:CLSCompliant(true)]

public unsafe class TestClass


{
private int* val;

public TestClass(int number)


{
val = (int*) number;
}

public int* Value {


get { return val; }
}
}
// The compiler generates the following output when compiling this example:
// warning CS3003: Type of 'TestClass.Value' is not CLS-compliant

Referencias con establecimiento de tipos, que son construcciones especiales que contienen una referencia
a un objeto y una referencia a un tipo. Las referencias con establecimiento de tipos se representan en .NET
Framework mediante la clase TypedReference.
Si un tipo no es conforme a CLS, deberá aplicarle el atributo CLSCompliantAttribute con el valor de isCompliant
establecido en false . Para obtener más información, vea la sección CLSCompliantAttribute (Atributo).
En el ejemplo siguiente se muestra el problema de la conformidad con CLS en la creación de instancias de tipos
genéricos y signaturas de métodos. En este ejemplo, se define una clase InvoiceItem con una propiedad de tipo
UInt32, una propiedad de tipo Nullable(Of UInt32) y un constructor con parámetros de tipo UInt32 y
Nullable(Of UInt32) . Cuando intente compilar este ejemplo, aparecerán cuatro advertencias del compilador.
using System;

[assembly: CLSCompliant(true)]

public class InvoiceItem


{
private uint invId = 0;
private uint itemId = 0;
private Nullable<uint> qty;

public InvoiceItem(uint sku, Nullable<uint> quantity)


{
itemId = sku;
qty = quantity;
}

public Nullable<uint> Quantity


{
get { return qty; }
set { qty = value; }
}

public uint InvoiceId


{
get { return invId; }
set { invId = value; }
}
}
// The attempt to compile the example displays the following output:
// Type1.cs(13,23): warning CS3001: Argument type 'uint' is not CLS-compliant
// Type1.cs(13,33): warning CS3001: Argument type 'uint?' is not CLS-compliant
// Type1.cs(19,26): warning CS3003: Type of 'InvoiceItem.Quantity' is not CLS-compliant
// Type1.cs(25,16): warning CS3003: Type of 'InvoiceItem.InvoiceId' is not CLS-compliant
<Assembly: CLSCompliant(True)>

Public Class InvoiceItem

Private invId As UInteger = 0


Private itemId As UInteger = 0
Private qty AS Nullable(Of UInteger)

Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))


itemId = sku
qty = quantity
End Sub

Public Property Quantity As Nullable(Of UInteger)


Get
Return qty
End Get
Set
qty = value
End Set
End Property

Public Property InvoiceId As UInteger


Get
Return invId
End Get
Set
invId = value
End Set
End Property
End Class
' The attempt to compile the example displays output similar to the following:
' Type1.vb(13) : warning BC40028: Type of parameter 'sku' is not CLS-compliant.
'
' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
' ~~~
' Type1.vb(13) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'
' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
' ~~~~~~~~
' Type1.vb(18) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'
' Public Property Quantity As Nullable(Of UInteger)
' ~~~~~~~~
' Type1.vb(27) : warning BC40027: Return type of function 'InvoiceId' is not CLS-compliant.
'
' Public Property InvoiceId As UInteger
' ~~~~~~~~~

Para eliminar las advertencias del compilador, reemplace los tipos no conformes a CLS de la interfaz pública de
InvoiceItem por tipos conformes:
using System;

[assembly: CLSCompliant(true)]

public class InvoiceItem


{
private uint invId = 0;
private uint itemId = 0;
private Nullable<int> qty;

public InvoiceItem(int sku, Nullable<int> quantity)


{
if (sku <= 0)
throw new ArgumentOutOfRangeException("The item number is zero or negative.");
itemId = (uint) sku;

qty = quantity;
}

public Nullable<int> Quantity


{
get { return qty; }
set { qty = value; }
}

public int InvoiceId


{
get { return (int) invId; }
set {
if (value <= 0)
throw new ArgumentOutOfRangeException("The invoice number is zero or negative.");
invId = (uint) value; }
}
}
<Assembly: CLSCompliant(True)>

Public Class InvoiceItem

Private invId As UInteger = 0


Private itemId As UInteger = 0
Private qty AS Nullable(Of Integer)

Public Sub New(sku As Integer, quantity As Nullable(Of Integer))


If sku <= 0 Then
Throw New ArgumentOutOfRangeException("The item number is zero or negative.")
End If
itemId = CUInt(sku)
qty = quantity
End Sub

Public Property Quantity As Nullable(Of Integer)


Get
Return qty
End Get
Set
qty = value
End Set
End Property

Public Property InvoiceId As Integer


Get
Return CInt(invId)
End Get
Set
invId = CUInt(value)
End Set
End Property
End Class

Además de los tipos específicos indicados, algunas categorías de tipos no son conformes a CLS. Entre estas
categorías se incluyen tipos de punteros no administrados y tipos de punteros de función. En el ejemplo
siguiente se genera una advertencia del compilador, ya que se utiliza un puntero a un entero para crear una
matriz de enteros.

using System;

[assembly: CLSCompliant(true)]

public class ArrayHelper


{
unsafe public static Array CreateInstance(Type type, int* ptr, int items)
{
Array arr = Array.CreateInstance(type, items);
int* addr = ptr;
for (int ctr = 0; ctr < items; ctr++) {
int value = *addr;
arr.SetValue(value, ctr);
addr++;
}
return arr;
}
}
// The attempt to compile this example displays the following output:
// UnmanagedPtr1.cs(8,57): warning CS3001: Argument type 'int*' is not CLS-compliant

En las clases abstractas conformes a CLS (es decir, clases marcadas como abstract en C# o como MustInherit
en Visual Basic), todos los miembros de dichas clases deben ser también conformes a CLS.
Convenciones de nomenclatura
Dado que algunos lenguajes de programación distinguen entre mayúsculas y minúsculas, los identificadores
(como los nombres de espacios de nombres, los tipos y los miembros) deben tener otro elemento distintivo
aparte del uso de mayúsculas. Dos identificadores se consideran equivalentes si sus asignaciones de minúsculas
son iguales. En el ejemplo de C# siguiente, se definen dos clases públicas: Person y person . Como solo se
distinguen por el uso de mayúsculas, el compilador de C# las marca como no conformes a CLS.

using System;

[assembly: CLSCompliant(true)]

public class Person : person


{
}

public class person


{
}
// Compilation produces a compiler warning like the following:
// Naming1.cs(11,14): warning CS3005: Identifier 'person' differing
// only in case is not CLS-compliant
// Naming1.cs(6,14): (Location of symbol related to previous warning)

Los identificadores de los lenguajes de programación, como los nombres de los espacios de nombres, tipos y
miembros, deben ajustarse al Estándar Unicode 3.0, Informe técnico 15, Anexo 7. Esto significa que:
El primer carácter de un identificador puede ser cualquier letra en mayúscula, letra en minúscula, letra de
inicial en mayúscula, letra modificadora, otra letra o número de letra. Para obtener información acerca de
las categorías de caracteres Unicode, vea la enumeración System.Globalization.UnicodeCategory.
Los caracteres siguientes pueden proceder de cualquier categoría cuando funcionan como primer carácter
y también pueden incluir marcas no espaciadas, marcas de combinación de espaciado, números
decimales, puntuaciones de conexión y códigos de formato.
Antes de comparar los identificadores, debe filtrar los códigos de formato y convertir los identificadores a la
forma de normalización Unicode C, ya que un mismo carácter se puede representar mediante diferentes
unidades de código UTF-16. Las secuencias de caracteres que producen las mismas unidades de código en la
forma de normalización Unicode C no son conformes a CLS. En el ejemplo siguiente se define una propiedad
llamada Å , que se compone del carácter SIGNO DE ANGSTROM (U+212B) y una segunda propiedad llamada
Å , que se compone de la LETRA MAYÚSCULA A LATINA CON UN ANILLO ENCIMA (U+00C5). Los compiladores
de C# y Visual Basic identifican el código fuente como no conforme a CLS.
public class Size
{
private double a1;
private double a2;

public double Å
{
get { return a1; }
set { a1 = value; }
}

public double Å
{
get { return a2; }
set { a2 = value; }
}
}
// Compilation produces a compiler warning like the following:
// Naming2a.cs(16,18): warning CS3005: Identifier 'Size.Å' differing only in case is not
// CLS-compliant
// Naming2a.cs(10,18): (Location of symbol related to previous warning)
// Naming2a.cs(18,8): warning CS3005: Identifier 'Size.Å.get' differing only in case is not
// CLS-compliant
// Naming2a.cs(12,8): (Location of symbol related to previous warning)
// Naming2a.cs(19,8): warning CS3005: Identifier 'Size.Å.set' differing only in case is not
// CLS-compliant
// Naming2a.cs(13,8): (Location of symbol related to previous warning)

<Assembly: CLSCompliant(True)>
Public Class Size
Private a1 As Double
Private a2 As Double

Public Property Å As Double


Get
Return a1
End Get
Set
a1 = value
End Set
End Property

Public Property Å As Double


Get
Return a2
End Get
Set
a2 = value
End Set
End Property
End Class
' Compilation produces a compiler warning like the following:
' Naming1.vb(9) : error BC30269: 'Public Property Å As Double' has multiple definitions
' with identical signatures.
'
' Public Property Å As Double
' ~

Los nombres de miembros con un ámbito determinado (como los espacios de nombres de un ensamblado, los
tipos de un espacio de nombres o los miembros de un tipo) deben ser únicos, excepto los nombres que se
resuelven a través de la sobrecarga. Este requisito es más estricto que el del sistema de tipos comunes, que
permite a varios miembros de un ámbito tener nombres idénticos siempre que sean diferentes tipos de
miembros (por ejemplo, que uno sea un método y otro, un campo). En particular, en el caso de los miembros de
tipo:
Los campos y los tipos anidados solo se distinguen por el nombre.
Los métodos, las propiedades y los eventos que tienen el mismo nombre deben distinguirse por algo más
que el tipo de valor devuelto.
En el ejemplo siguiente se muestra el requisito que establece que los nombres de miembro deben ser únicos
dentro de su ámbito. En este ejemplo se define una clase denominada Converter , que incluye cuatro miembros
denominados Conversion . Tres son métodos y uno es una propiedad. El método que incluye un parámetro Int64
recibe un nombre único, pero no ocurre lo mismo con los dos métodos que tienen un parámetro Int32, ya que el
valor devuelto no se considera parte de la signatura del miembro. La propiedad Conversion también infringe
este requisito, ya que las propiedades no pueden tener el mismo nombre que los métodos sobrecargados.

using System;

[assembly: CLSCompliant(true)]

public class Converter


{
public double Conversion(int number)
{
return (double) number;
}

public float Conversion(int number)


{
return (float) number;
}

public double Conversion(long number)


{
return (double) number;
}

public bool Conversion


{
get { return true; }
}
}
// Compilation produces a compiler error like the following:
// Naming3.cs(13,17): error CS0111: Type 'Converter' already defines a member called
// 'Conversion' with the same parameter types
// Naming3.cs(8,18): (Location of symbol related to previous error)
// Naming3.cs(23,16): error CS0102: The type 'Converter' already contains a definition for
// 'Conversion'
// Naming3.cs(8,18): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>

Public Class Converter


Public Function Conversion(number As Integer) As Double
Return CDbl(number)
End Function

Public Function Conversion(number As Integer) As Single


Return CSng(number)
End Function

Public Function Conversion(number As Long) As Double


Return CDbl(number)
End Function

Public ReadOnly Property Conversion As Boolean


Get
Return True
End Get
End Property
End Class
' Compilation produces a compiler error like the following:
' Naming3.vb(8) : error BC30301: 'Public Function Conversion(number As Integer) As Double'
' and 'Public Function Conversion(number As Integer) As Single' cannot
' overload each other because they differ only by return types.
'
' Public Function Conversion(number As Integer) As Double
' ~~~~~~~~~~
' Naming3.vb(20) : error BC30260: 'Conversion' is already declared as 'Public Function
' Conversion(number As Integer) As Single' in this class.
'
' Public ReadOnly Property Conversion As Boolean
' ~~~~~~~~~~

Todos los lenguajes contienen palabras claves únicas, de modo que los lenguajes dirigidos a Common Language
Runtime también deben proporcionar un mecanismo para hacer referencia a identificadores (como nombres de
tipo) que coincidan con las palabras clave. Por ejemplo, case es una palabra clave en C# y Visual Basic. Sin
embargo, en el siguiente ejemplo de Visual Basic se elimina la ambigüedad entre una clase denominada case y
la palabra clave case mediante llaves de apertura y cierre. De lo contrario, el ejemplo produciría el mensaje de
error "Una palabra clave no es válida como identificador" y no se compilaría.

Public Class [case]


Private _id As Guid
Private name As String

Public Sub New(name As String)


_id = Guid.NewGuid()
Me.name = name
End Sub

Public ReadOnly Property ClientName As String


Get
Return name
End Get
End Property
End Class

En el siguiente ejemplo de C# se crean instancias de la clase case utilizando el símbolo @ para eliminar la
ambigüedad entre el identificador y la palabra clave del lenguaje. Sin él, el compilador de C# mostraría dos
mensajes de error similares a los siguientes: "Se esperaba un tipo" y "'Término 'case' de expresión no válido".
using System;

public class Example


{
public static void Main()
{
@case c = new @case("John");
Console.WriteLine(c.ClientName);
}
}

Conversión de tipos
Common Language Specification define dos operadores de conversión:
op_Implicit , que se utiliza en las conversiones de ampliación que no dan lugar a la pérdida de datos o de
precisión. Por ejemplo, la estructura Decimal contiene un operador sobrecargado op_Implicit para
convertir valores de tipos enteros y valores Char en valores Decimal.
op_Explicit , que se utiliza en las conversiones de restricción que pueden producir una pérdida de
magnitud (un valor se convierte en un valor que tiene un intervalo menor) o de precisión. Por ejemplo, la
estructura Decimal contiene un operador sobrecargado op_Explicit para convertir los valores Double y
Single en Decimal y convertir Decimal en los valores integrales Double, Single y Char.
Sin embargo, no todos los lenguajes admiten la sobrecarga de operadores o la definición de operadores
personalizados. Si decide implementar estos operadores de conversión, debe proporcionar un mecanismo
alternativo para realizar la conversión. Se recomienda proporcionar los métodos From Xxx y To Xxx.
En el ejemplo siguiente se definen conversiones implícitas y explícitas conformes a CLS. En este ejemplo, se crea
una clase UDouble que representa un número de punto flotante con signo de precisión doble. En las
conversiones implícitas, pasa de UDouble a Double y, en las conversiones explícitas, de UDouble a Single, de
Double a UDouble y de Single a UDouble . También define un método ToDouble como alternativa al operador de
conversión implícita y los métodos ToSingle , FromDouble y FromSingle como alternativas a los operadores de
conversión explícitos.

using System;

public struct UDouble


{
private double number;

public UDouble(double value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

number = value;
}

public UDouble(float value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

number = value;
}

public static readonly UDouble MinValue = (UDouble) 0.0;


public static readonly UDouble MaxValue = (UDouble) Double.MaxValue;

public static explicit operator Double(UDouble value)


{
{
return value.number;
}

public static implicit operator Single(UDouble value)


{
if (value.number > (double) Single.MaxValue)
throw new InvalidCastException("A UDouble value is out of range of the Single type.");

return (float) value.number;


}

public static explicit operator UDouble(double value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

return new UDouble(value);


}

public static implicit operator UDouble(float value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

return new UDouble(value);


}

public static Double ToDouble(UDouble value)


{
return (Double) value;
}

public static float ToSingle(UDouble value)


{
return (float) value;
}

public static UDouble FromDouble(double value)


{
return new UDouble(value);
}

public static UDouble FromSingle(float value)


{
return new UDouble(value);
}
}
Public Structure UDouble
Private number As Double

Public Sub New(value As Double)


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
number = value
End Sub

Public Sub New(value As Single)


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
number = value
End Sub

Public Shared ReadOnly MinValue As UDouble = CType(0.0, UDouble)


Public Shared ReadOnly MaxValue As UDouble = Double.MaxValue

Public Shared Widening Operator CType(value As UDouble) As Double


Return value.number
End Operator

Public Shared Narrowing Operator CType(value As UDouble) As Single


If value.number > CDbl(Single.MaxValue) Then
Throw New InvalidCastException("A UDouble value is out of range of the Single type.")
End If
Return CSng(value.number)
End Operator

Public Shared Narrowing Operator CType(value As Double) As UDouble


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
Return New UDouble(value)
End Operator

Public Shared Narrowing Operator CType(value As Single) As UDouble


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
Return New UDouble(value)
End Operator

Public Shared Function ToDouble(value As UDouble) As Double


Return CType(value, Double)
End Function

Public Shared Function ToSingle(value As UDouble) As Single


Return CType(value, Single)
End Function

Public Shared Function FromDouble(value As Double) As UDouble


Return New UDouble(value)
End Function

Public Shared Function FromSingle(value As Single) As UDouble


Return New UDouble(value)
End Function
End Structure

Matrices
Las matrices conformes a CLS cumplen las reglas siguientes:
Todas las dimensiones de una matriz deben tener un límite inferior igual a cero. En el ejemplo siguiente se
crea una matriz no conforme a CLS cuyo límite inferior es uno. Observe que, a pesar de la presencia del
atributo CLSCompliantAttribute, el compilador no detecta que la matriz devuelta por el método
Numbers.GetTenPrimes no es conforme a CLS.

[assembly: CLSCompliant(true)]

public class Numbers


{
public static Array GetTenPrimes()
{
Array arr = Array.CreateInstance(typeof(Int32), new int[] {10}, new int[] {1});
arr.SetValue(1, 1);
arr.SetValue(2, 2);
arr.SetValue(3, 3);
arr.SetValue(5, 4);
arr.SetValue(7, 5);
arr.SetValue(11, 6);
arr.SetValue(13, 7);
arr.SetValue(17, 8);
arr.SetValue(19, 9);
arr.SetValue(23, 10);

return arr;
}
}

<Assembly: CLSCompliant(True)>

Public Class Numbers


Public Shared Function GetTenPrimes() As Array
Dim arr As Array = Array.CreateInstance(GetType(Int32), {10}, {1})
arr.SetValue(1, 1)
arr.SetValue(2, 2)
arr.SetValue(3, 3)
arr.SetValue(5, 4)
arr.SetValue(7, 5)
arr.SetValue(11, 6)
arr.SetValue(13, 7)
arr.SetValue(17, 8)
arr.SetValue(19, 9)
arr.SetValue(23, 10)

Return arr
End Function
End Class

Todos los elementos de la matriz deben componerse de tipos conformes a CLS. En el ejemplo siguiente se
definen dos métodos que devuelven matrices no conformes a CLS. El primero devuelve una matriz de
valores UInt32. El segundo devuelve una matriz Object que contiene los valores Int32 y UInt32. Aunque el
compilador identifica la primera matriz como no conforme debido a su tipo UInt32, no reconoce que la
segunda matriz incluye elementos no conformes a CLS.
using System;

[assembly: CLSCompliant(true)]

public class Numbers


{
public static UInt32[] GetTenPrimes()
{
uint[] arr = { 1u, 2u, 3u, 5u, 7u, 11u, 13u, 17u, 19u };
return arr;
}

public static Object[] GetFivePrimes()


{
Object[] arr = { 1, 2, 3, 5u, 7u };
return arr;
}
}
// Compilation produces a compiler warning like the following:
// Array2.cs(8,27): warning CS3002: Return type of 'Numbers.GetTenPrimes()' is not
// CLS-compliant

<Assembly: CLSCompliant(True)>

Public Class Numbers


Public Shared Function GetTenPrimes() As UInt32()
Return {1ui, 2ui, 3ui, 5ui, 7ui, 11ui, 13ui, 17ui, 19ui}
End Function

Public Shared Function GetFivePrimes() As Object()


Dim arr() As Object = {1, 2, 3, 5ui, 7ui}
Return arr
End Function
End Class
' Compilation produces a compiler warning like the following:
' warning BC40027: Return type of function 'GetTenPrimes' is not CLS-compliant.
'
' Public Shared Function GetTenPrimes() As UInt32()
' ~~~~~~~~~~~~

La resolución de desbordamiento de los métodos que tienen parámetros de matriz se basa en el hecho de
que son matrices y en su tipo de elemento. Por esta razón, la siguiente definición de un método
GetSquares sobrecargado es conforme a CLS.
using System;
using System.Numerics;

[assembly: CLSCompliant(true)]

public class Numbers


{
public static byte[] GetSquares(byte[] numbers)
{
byte[] numbersOut = new byte[numbers.Length];
for (int ctr = 0; ctr < numbers.Length; ctr++) {
int square = ((int) numbers[ctr]) * ((int) numbers[ctr]);
if (square <= Byte.MaxValue)
numbersOut[ctr] = (byte) square;
// If there's an overflow, assign MaxValue to the corresponding
// element.
else
numbersOut[ctr] = Byte.MaxValue;
}
return numbersOut;
}

public static BigInteger[] GetSquares(BigInteger[] numbers)


{
BigInteger[] numbersOut = new BigInteger[numbers.Length];
for (int ctr = 0; ctr < numbers.Length; ctr++)
numbersOut[ctr] = numbers[ctr] * numbers[ctr];

return numbersOut;
}
}

Imports System.Numerics

<Assembly: CLSCompliant(True)>

Public Module Numbers


Public Function GetSquares(numbers As Byte()) As Byte()
Dim numbersOut(numbers.Length - 1) As Byte
For ctr As Integer = 0 To numbers.Length - 1
Dim square As Integer = (CInt(numbers(ctr)) * CInt(numbers(ctr)))
If square <= Byte.MaxValue Then
numbersOut(ctr) = CByte(square)
' If there's an overflow, assign MaxValue to the corresponding
' element.
Else
numbersOut(ctr) = Byte.MaxValue
End If
Next
Return numbersOut
End Function

Public Function GetSquares(numbers As BigInteger()) As BigInteger()


Dim numbersOut(numbers.Length - 1) As BigInteger
For ctr As Integer = 0 To numbers.Length - 1
numbersOut(ctr) = numbers(ctr) * numbers(ctr)
Next
Return numbersOut
End Function
End Module

Interfaces
Las interfaces conformes a CLS pueden definir propiedades, eventos y métodos virtuales (métodos sin
implementación). Una interfaz conforme a CLS no puede tener ninguno de los elementos siguientes:
Métodos estáticos o campos estáticos. Los compiladores de C# y Visual Basic generan errores de
compilación si se define un miembro estático en una interfaz.
Campos. Los compiladores de C# y Visual Basic generan errores de compilación si se define un campo en
una interfaz.
Métodos que no son conformes a CLS. Por ejemplo, la siguiente definición de interfaz incluye un método,
INumber.GetUnsigned , que está marcado como no conforme a CLS. Este ejemplo genera una advertencia
del compilador.

using System;

[assembly:CLSCompliant(true)]

public interface INumber


{
int Length();
[CLSCompliant(false)] ulong GetUnsigned();
}
// Attempting to compile the example displays output like the following:
// Interface2.cs(8,32): warning CS3010: 'INumber.GetUnsigned()': CLS-compliant interfaces
// must have only CLS-compliant members

<Assembly: CLSCompliant(True)>

Public Interface INumber


Function Length As Integer

<CLSCompliant(False)> Function GetUnsigned As ULong


End Interface
' Attempting to compile the example displays output like the following:
' Interface2.vb(9) : warning BC40033: Non CLS-compliant 'function' is not allowed in a
' CLS-compliant interface.
'
' <CLSCompliant(False)> Function GetUnsigned As ULong
' ~~~~~~~~~~~

Debido a esta regla, no se necesitan tipos conformes a CLS para implementar miembros no conformes a
CLS. Si un marco conforme a CLS expone una clase que implementa una interfaz no conforme a CLS,
también debe proporcionar implementaciones concretas de todos los miembros no conformes a CLS.
Los compiladores de lenguaje conformes a CLS también deben permitir que una clase proporcione
implementaciones independientes de miembros con el mismo nombre y la misma signatura en varias interfaces.
C# y Visual Basic admiten implementaciones de interfaz explícitas para proporcionar diferentes
implementaciones de métodos con el mismo nombre. Visual Basic también admite la palabra clave Implements ,
que permite designar explícitamente qué interfaz y miembro implementa un determinado miembro. En el
ejemplo siguiente se muestra este escenario con la definición de una clase Temperature que implementa las
interfaces ICelsius y IFahrenheit como implementaciones de interfaces explícitas.
using System;

[assembly: CLSCompliant(true)]

public interface IFahrenheit


{
decimal GetTemperature();
}

public interface ICelsius


{
decimal GetTemperature();
}

public class Temperature : ICelsius, IFahrenheit


{
private decimal _value;

public Temperature(decimal value)


{
// We assume that this is the Celsius value.
_value = value;
}

decimal IFahrenheit.GetTemperature()
{
return _value * 9 / 5 + 32;
}

decimal ICelsius.GetTemperature()
{
return _value;
}
}
public class Example
{
public static void Main()
{
Temperature temp = new Temperature(100.0m);
ICelsius cTemp = temp;
IFahrenheit fTemp = temp;
Console.WriteLine("Temperature in Celsius: {0} degrees",
cTemp.GetTemperature());
Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
fTemp.GetTemperature());
}
}
// The example displays the following output:
// Temperature in Celsius: 100.0 degrees
// Temperature in Fahrenheit: 212.0 degrees
<Assembly: CLSCompliant(True)>

Public Interface IFahrenheit


Function GetTemperature() As Decimal
End Interface

Public Interface ICelsius


Function GetTemperature() As Decimal
End Interface

Public Class Temperature : Implements ICelsius, IFahrenheit


Private _value As Decimal

Public Sub New(value As Decimal)


' We assume that this is the Celsius value.
_value = value
End Sub

Public Function GetFahrenheit() As Decimal _


Implements IFahrenheit.GetTemperature
Return _value * 9 / 5 + 32
End Function

Public Function GetCelsius() As Decimal _


Implements ICelsius.GetTemperature
Return _value
End Function
End Class

Module Example
Public Sub Main()
Dim temp As New Temperature(100.0d)
Console.WriteLine("Temperature in Celsius: {0} degrees",
temp.GetCelsius())
Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
temp.GetFahrenheit())
End Sub
End Module
' The example displays the following output:
' Temperature in Celsius: 100.0 degrees
' Temperature in Fahrenheit: 212.0 degrees

Enumeraciones
Las enumeraciones conformes a CLS deben seguir estas reglas:
El tipo subyacente de una enumeración debe ser un entero intrínseco conforme a CLS (Byte, Int16, Int32 o
Int64). Por ejemplo, el código siguiente intenta definir una enumeración cuyo tipo subyacente es UInt32 y
genera una advertencia del compilador.
using System;

[assembly: CLSCompliant(true)]

public enum Size : uint {


Unspecified = 0,
XSmall = 1,
Small = 2,
Medium = 3,
Large = 4,
XLarge = 5
};

public class Clothing


{
public string Name;
public string Type;
public string Size;
}
// The attempt to compile the example displays a compiler warning like the following:
// Enum3.cs(6,13): warning CS3009: 'Size': base type 'uint' is not CLS-compliant

<Assembly: CLSCompliant(True)>

Public Enum Size As UInt32


Unspecified = 0
XSmall = 1
Small = 2
Medium = 3
Large = 4
XLarge = 5
End Enum

Public Class Clothing


Public Name As String
Public Type As String
Public Size As Size
End Class
' The attempt to compile the example displays a compiler warning like the following:
' Enum3.vb(6) : warning BC40032: Underlying type 'UInt32' of Enum is not CLS-compliant.
'
' Public Enum Size As UInt32
' ~~~~

Un tipo de enumeración debe tener un campo de instancia único denominado Value__ marcado con el
atributo FieldAttributes.RTSpecialName. Esto permite hacer referencia al valor del campo de forma
implícita.
Las enumeraciones incluyen campos estáticos literales del mismo tipo que el de la enumeración. Por
ejemplo, si define una enumeración State con los valores State.On y State.Off , State.On y State.Off
son campos estáticos literales cuyo tipo será State .
Hay dos tipos de enumeraciones:
Las enumeraciones que representan un conjunto de valores enteros con nombre mutuamente
excluyentes. Este tipo de enumeración se indica por la ausencia del atributo personalizado
System.FlagsAttribute.
Las enumeraciones que representan un conjunto de marcadores de bits que se pueden combinar
para generar un valor sin nombre. Este tipo de enumeración se indica por la presencia del atributo
personalizado System.FlagsAttribute.
Para obtener más información, consulte la documentación de la estructura Enum.
El valor de una enumeración no se limita al intervalo de sus valores especificados. Es decir, el intervalo de
valores de una enumeración es el intervalo de su valor subyacente. Puede utilizar el método
Enum.IsDefined para determinar si un valor especificado es miembro de una enumeración.
Miembros de tipos en general
Common Language Specification necesita todos los campos y métodos accesibles como miembros de una clase
determinada. Por tanto, los métodos y los campos estáticos globales (es decir, los métodos o los campos que se
definen con independencia de un tipo) no son conformes a CLS. Si intenta incluir un campo o un método global
en el código fuente, tanto el compilador de C# como el de Visual Basic generarán un error de compilación.
Common Language Specification solo admite la convención de llamada administrada estándar. No admite
convenciones de llamada ni métodos no administrados con listas de argumentos variables marcados con la
palabra clave varargs . En el caso de las listas de argumentos variables que son compatibles con la convención
de llamada administrada estándar, utilice el atributo ParamArrayAttribute o la implementación del lenguaje
específico, como la palabra clave params en C# y la palabra clave ParamArray en Visual Basic.
Accesibilidad de miembros
Al reemplazar un miembro heredado no se puede cambiar la accesibilidad de dicho miembro. Por ejemplo, un
método público de una clase base no se puede reemplazar por un método privado de una clase derivada. Existe
una excepción: un miembro protected internal (en C#) o Protected Friend (en Visual Basic) de un ensamblado
que se haya reemplazado por un tipo de un ensamblado diferente. En ese caso, la accesibilidad del reemplazo es
Protected .

En el ejemplo siguiente se muestra el error que se genera cuando el atributo CLSCompliantAttribute se establece
en true y Human , que es una clase derivada de Animal , intenta cambiar la accesibilidad de la propiedad
Species de pública a privada. El ejemplo se compila correctamente si su accesibilidad se cambia a pública.
using System;

[assembly: CLSCompliant(true)]

public class Animal


{
private string _species;

public Animal(string species)


{
_species = species;
}

public virtual string Species


{
get { return _species; }
}

public override string ToString()


{
return _species;
}
}

public class Human : Animal


{
private string _name;

public Human(string name) : base("Homo Sapiens")


{
_name = name;
}

public string Name


{
get { return _name; }
}

private override string Species


{
get { return base.Species; }
}

public override string ToString()


{
return _name;
}
}

public class Example


{
public static void Main()
{
Human p = new Human("John");
Console.WriteLine(p.Species);
Console.WriteLine(p.ToString());
}
}
// The example displays the following output:
// error CS0621: 'Human.Species': virtual or abstract members cannot be private
<Assembly: CLSCompliant(True)>

Public Class Animal


Private _species As String

Public Sub New(species As String)


_species = species
End Sub

Public Overridable ReadOnly Property Species As String


Get
Return _species
End Get
End Property

Public Overrides Function ToString() As String


Return _species
End Function
End Class

Public Class Human : Inherits Animal


Private _name As String

Public Sub New(name As String)


MyBase.New("Homo Sapiens")
_name = name
End Sub

Public ReadOnly Property Name As String


Get
Return _name
End Get
End Property

Private Overrides ReadOnly Property Species As String


Get
Return MyBase.Species
End Get
End Property

Public Overrides Function ToString() As String


Return _name
End Function
End Class

Public Module Example


Public Sub Main()
Dim p As New Human("John")
Console.WriteLine(p.Species)
Console.WriteLine(p.ToString())
End Sub
End Module
' The example displays the following output:
' 'Private Overrides ReadOnly Property Species As String' cannot override
' 'Public Overridable ReadOnly Property Species As String' because
' they have different access levels.
'
' Private Overrides ReadOnly Property Species As String

Los tipos de la signatura de un miembro deben estar accesibles siempre que dicho miembro esté accesible. Esto
significa, por ejemplo, que un miembro público no puede incluir un parámetro cuyo tipo sea privado, protegido o
interno. En el ejemplo siguiente se muestra el error del compilador que se produce cuando un constructor de
clase StringWrapper expone un valor de enumeración StringOperationType interno que determina cómo debe
ajustarse un valor de cadena.
using System;
using System.Text;

public class StringWrapper


{
string internalString;
StringBuilder internalSB = null;
bool useSB = false;

public StringWrapper(StringOperationType type)


{
if (type == StringOperationType.Normal) {
useSB = false;
}
else {
useSB = true;
internalSB = new StringBuilder();
}
}

// The remaining source code...


}

internal enum StringOperationType { Normal, Dynamic }


// The attempt to compile the example displays the following output:
// error CS0051: Inconsistent accessibility: parameter type
// 'StringOperationType' is less accessible than method
// 'StringWrapper.StringWrapper(StringOperationType)'

Imports System.Text

<Assembly: CLSCompliant(True)>

Public Class StringWrapper

Dim internalString As String


Dim internalSB As StringBuilder = Nothing
Dim useSB As Boolean = False

Public Sub New(type As StringOperationType)


If type = StringOperationType.Normal Then
useSB = False
Else
internalSB = New StringBuilder()
useSB = True
End If
End Sub

' The remaining source code...


End Class

Friend Enum StringOperationType As Integer


Normal = 0
Dynamic = 1
End Enum
' The attempt to compile the example displays the following output:
' error BC30909: 'type' cannot expose type 'StringOperationType'
' outside the project through class 'StringWrapper'.
'
' Public Sub New(type As StringOperationType)
' ~~~~~~~~~~~~~~~~~~~

Miembros y tipos genéricos


Los tipos anidados siempre tienen, como mínimo, el mismo número de parámetros genéricos que el tipo
envolvente. Estos se corresponden por posición con los parámetros genéricos del tipo envolvente. El tipo
genérico también puede incluir nuevos parámetros genéricos.
Las relaciones entre los parámetros de tipo genérico de un tipo envolvente y sus tipos anidados se pueden
ocultar en la sintaxis de cada lenguaje. En el ejemplo siguiente, un tipo genérico Outer<T> contiene dos clases
anidadas: Inner1A e Inner1B<U> . Las llamadas al método ToString , donde cada clase hereda de Object.ToString,
muestran que cada clase anidada contiene los parámetros de tipo de la clase contenedora.

using System;

[assembly:CLSCompliant(true)]

public class Outer<T>


{
T value;

public Outer(T value)


{
this.value = value;
}

public class Inner1A : Outer<T>


{
public Inner1A(T value) : base(value)
{ }
}

public class Inner1B<U> : Outer<T>


{
U value2;

public Inner1B(T value1, U value2) : base(value1)


{
this.value2 = value2;
}
}
}

public class Example


{
public static void Main()
{
var inst1 = new Outer<String>("This");
Console.WriteLine(inst1);

var inst2 = new Outer<String>.Inner1A("Another");


Console.WriteLine(inst2);

var inst3 = new Outer<String>.Inner1B<int>("That", 2);


Console.WriteLine(inst3);
}
}
// The example displays the following output:
// Outer`1[System.String]
// Outer`1+Inner1A[System.String]
// Outer`1+Inner1B`1[System.String,System.Int32]
<Assembly: CLSCompliant(True)>

Public Class Outer(Of T)


Dim value As T

Public Sub New(value As T)


Me.value = value
End Sub

Public Class Inner1A : Inherits Outer(Of T)


Public Sub New(value As T)
MyBase.New(value)
End Sub
End Class

Public Class Inner1B(Of U) : Inherits Outer(Of T)


Dim value2 As U

Public Sub New(value1 As T, value2 As U)


MyBase.New(value1)
Me.value2 = value2
End Sub
End Class
End Class

Public Module Example


Public Sub Main()
Dim inst1 As New Outer(Of String)("This")
Console.WriteLine(inst1)

Dim inst2 As New Outer(Of String).Inner1A("Another")


Console.WriteLine(inst2)

Dim inst3 As New Outer(Of String).Inner1B(Of Integer)("That", 2)


Console.WriteLine(inst3)
End Sub
End Module
' The example displays the following output:
' Outer`1[System.String]
' Outer`1+Inner1A[System.String]
' Outer`1+Inner1B`1[System.String,System.Int32]

Los nombres de tipos genéricos se codifican con el formato nombre`n, donde nombre es el nombre del tipo, ` es
un carácter literal y n es el número de parámetros declarados en el tipo o, en el caso de tipos genéricos anidados,
el número de parámetros de tipo recién incorporados. Esta codificación de nombres de tipo genérico tiene
interés fundamentalmente para los desarrolladores que utilizan la reflexión a fin de acceder a los tipos genéricos
conformes a CLS de una biblioteca.
Si las restricciones se aplican a un tipo genérico, los tipos utilizados como restricciones también deben ser
conformes a CLS. En el ejemplo siguiente se define una clase denominada BaseClass que no es conforme a CLS
y una clase genérica denominada BaseCollection cuyo parámetro de tipo debe derivarse de BaseClass . Sin
embargo, puesto que BaseClass no es conforme a CLS, el compilador emite una advertencia.
using System;

[assembly:CLSCompliant(true)]

[CLSCompliant(false)] public class BaseClass


{}

public class BaseCollection<T> where T : BaseClass


{}
// Attempting to compile the example displays the following output:
// warning CS3024: Constraint type 'BaseClass' is not CLS-compliant

<Assembly: CLSCompliant(True)>

<CLSCompliant(False)> Public Class BaseClass


End Class

Public Class BaseCollection(Of T As BaseClass)


End Class
' Attempting to compile the example displays the following output:
' warning BC40040: Generic parameter constraint type 'BaseClass' is not
' CLS-compliant.
'
' Public Class BaseCollection(Of T As BaseClass)
' ~~~~~~~~~

Si un tipo genérico se deriva de un tipo base genérico, es necesario volver a declarar las restricciones para que se
pueda garantizar que las restricciones del tipo base también se cumplen. En el ejemplo siguiente se define un
objeto Number<T> que puede representar cualquier tipo numérico. También se define una clase FloatingPoint<T>
que representa un valor de punto flotante. Sin embargo, el código fuente no puede compilarse, ya que no aplica
la restricción de Number<T> (T debe ser un tipo de valor) en FloatingPoint<T> .
using System;

[assembly:CLSCompliant(true)]

public class Number<T> where T : struct


{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;

public Number(T value)


{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}

public T Add(T value)


{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}

public T Subtract(T value)


{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}

public class FloatingPoint<T> : Number<T>


{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
// The attempt to comple the example displays the following output:
// error CS0453: The type 'T' must be a non-nullable value type in
// order to use it as parameter 'T' in the generic type or method 'Number<T>'
<Assembly: CLSCompliant(True)>

Public Class Number(Of T As Structure)


' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double

Public Sub New(value As T)


Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub

Public Function Add(value As T) As T


Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function

Public Function Subtract(value As T) As T


Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class

Public Class FloatingPoint(Of T) : Inherits Number(Of T)


Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class
' The attempt to comple the example displays the following output:
' error BC32105: Type argument 'T' does not satisfy the 'Structure'
' constraint for type parameter 'T'.
'
' Public Class FloatingPoint(Of T) : Inherits Number(Of T)
' ~

El ejemplo se compila correctamente si se agrega la restricción a la clase FloatingPoint<T> .


using System;

[assembly:CLSCompliant(true)]

public class Number<T> where T : struct


{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;

public Number(T value)


{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}

public T Add(T value)


{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}

public T Subtract(T value)


{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}

public class FloatingPoint<T> : Number<T> where T : struct


{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
<Assembly: CLSCompliant(True)>

Public Class Number(Of T As Structure)


' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double

Public Sub New(value As T)


Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub

Public Function Add(value As T) As T


Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function

Public Function Subtract(value As T) As T


Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class

Public Class FloatingPoint(Of T As Structure) : Inherits Number(Of T)


Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class

Common Language Specification impone un modelo conservador adaptado a cada instancia en los tipos
anidados y los miembros protegidos. Los tipos genéricos abiertos no pueden exponer campos ni miembros con
signaturas que contengan una instancia específica de un tipo genérico anidado y protegido. Los tipos no
genéricos que amplíen una instancia específica de una interfaz o clase base genérica no pueden exponer campos
ni miembros con signaturas que contengan otra instancia de un tipo genérico anidado y protegido.
En el ejemplo siguiente se define un tipo genérico, C1<T> (o C1(Of T) en Visual Basic) y una clase protegida,
C1<T>.N (o C1(Of T).N en Visual Basic). C1<T> tiene dos métodos: M1 y M2 . Sin embargo, M1 no es conforme
a CLS porque intenta devolver un objeto C1<int>.N (o C1(Of Integer).N ) a partir de C1<T> (o C1(Of T) ). Una
segunda clase, C2 , se deriva de C1<long> (o C1(Of Long) ). Esta clase tiene dos métodos, M3 y M4 . El objeto
M3 no es conforme a CLS porque intenta devolver un objeto C1<int>.N (o C1(Of Integer).N ) a partir de una
subclase de C1<long> . Observe que los compiladores del lenguaje pueden ser aun más restrictivos. En este
ejemplo, Visual Basic muestra un error cuando intenta compilar M4 .
using System;

[assembly:CLSCompliant(true)]

public class C1<T>


{
protected class N { }

protected void M1(C1<int>.N n) { } // Not CLS-compliant - C1<int>.N not


// accessible from within C1<T> in all
// languages
protected void M2(C1<T>.N n) { } // CLS-compliant – C1<T>.N accessible
// inside C1<T>
}

public class C2 : C1<long>


{
protected void M3(C1<int>.N n) { } // Not CLS-compliant – C1<int>.N is not
// accessible in C2 (extends C1<long>)

protected void M4(C1<long>.N n) { } // CLS-compliant, C1<long>.N is


// accessible in C2 (extends C1<long>)
}
// Attempting to compile the example displays output like the following:
// Generics4.cs(9,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
// Generics4.cs(18,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
<Assembly: CLSCompliant(True)>

Public Class C1(Of T)


Protected Class N
End Class

Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
' accessible from within C1(Of T) in all
End Sub ' languages

Protected Sub M2(n As C1(Of T).N) ' CLS-compliant – C1(Of T).N accessible
End Sub ' inside C1(Of T)
End Class

Public Class C2 : Inherits C1(Of Long)


Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant – C1(Of Integer).N is not
End Sub ' accessible in C2 (extends C1(Of Long))

Protected Sub M4(n As C1(Of Long).N)


End Sub
End Class
' Attempting to compile the example displays output like the following:
' error BC30508: 'n' cannot expose type 'C1(Of Integer).N' in namespace
' '<Default>' through class 'C1'.
'
' Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
' ~~~~~~~~~~~~~~~~
' error BC30389: 'C1(Of T).N' is not accessible in this context because
' it is 'Protected'.
'
' Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant - C1(Of Integer).N is not
'
' ~~~~~~~~~~~~~~~~
'
' error BC30389: 'C1(Of T).N' is not accessible in this context because it is 'Protected'.
'
' Protected Sub M4(n As C1(Of Long).N)
' ~~~~~~~~~~~~~

Constructores
Los constructores de clases y estructuras conformes a CLS deben seguir estas reglas:
Un constructor de una clase derivada debe llamar al constructor de instancia de su clase base antes de
tener acceso a datos de instancia heredados. Este requisito se debe al hecho de que los constructores de
clase base no se heredan por sus clases derivadas. Esta regla no se aplica a las estructuras, que no admiten
la herencia directa.
Normalmente, los compiladores aplican esta regla independientemente de la conformidad con CLS, como
se muestra en el ejemplo siguiente. En este ejemplo, se crea una clase Doctor que se deriva de una clase
Person , pero la clase Doctor no consigue llamar al constructor de la clase Person para inicializar los
campos de instancia heredados.
using System;

[assembly: CLSCompliant(true)]

public class Person


{
private string fName, lName, _id;

public Person(string firstName, string lastName, string id)


{
if (String.IsNullOrEmpty(firstName + lastName))
throw new ArgumentNullException("Either a first name or a last name must be provided.");

fName = firstName;
lName = lastName;
_id = id;
}

public string FirstName


{
get { return fName; }
}

public string LastName


{
get { return lName; }
}

public string Id
{
get { return _id; }
}

public override string ToString()


{
return String.Format("{0}{1}{2}", fName,
String.IsNullOrEmpty(fName) ? "" : " ",
lName);
}
}

public class Doctor : Person


{
public Doctor(string firstName, string lastName, string id)
{
}

public override string ToString()


{
return "Dr. " + base.ToString();
}
}
// Attempting to compile the example displays output like the following:
// ctor1.cs(45,11): error CS1729: 'Person' does not contain a constructor that takes 0
// arguments
// ctor1.cs(10,11): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>

Public Class Person


Private fName, lName, _id As String

Public Sub New(firstName As String, lastName As String, id As String)


If String.IsNullOrEmpty(firstName + lastName) Then
Throw New ArgumentNullException("Either a first name or a last name must be provided.")
End If

fName = firstName
lName = lastName
_id = id
End Sub

Public ReadOnly Property FirstName As String


Get
Return fName
End Get
End Property

Public ReadOnly Property LastName As String


Get
Return lName
End Get
End Property

Public ReadOnly Property Id As String


Get
Return _id
End Get
End Property

Public Overrides Function ToString() As String


Return String.Format("{0}{1}{2}", fName,
If(String.IsNullOrEmpty(fName), "", " "),
lName)
End Function
End Class

Public Class Doctor : Inherits Person


Public Sub New(firstName As String, lastName As String, id As String)
End Sub

Public Overrides Function ToString() As String


Return "Dr. " + MyBase.ToString()
End Function
End Class
' Attempting to compile the example displays output like the following:
' Ctor1.vb(46) : error BC30148: First statement of this 'Sub New' must be a call
' to 'MyBase.New' or 'MyClass.New' because base class 'Person' of 'Doctor' does
' not have an accessible 'Sub New' that can be called with no arguments.
'
' Public Sub New()
' ~~~

No se puede llamar a un constructor de objetos excepto para crear un objeto. Además, un objeto no se
puede inicializar dos veces. Esto significa, por ejemplo, que el método Object.MemberwiseClone y los
métodos de deserialización, como BinaryFormatter.Deserialize, no deben llamar a constructores.
Propiedades
Las propiedades de los tipos conformes a CLS deben seguir estas reglas:
Una propiedad debe tener un establecedor, un captador o ambos. En un ensamblado, estos elementos se
implementan como métodos especiales, lo que significa que aparecerán como métodos independientes (el
captador se llama get_ propertyname y el establecedor es set_ propertyname) marcados como
SpecialName en los metadatos del ensamblado. Los compiladores de C# y Visual Basic aplican esta regla
automáticamente sin necesidad de aplicar el atributo CLSCompliantAttribute.
El tipo de una propiedad es el tipo de valor devuelto del captador de la propiedad y el último argumento
del establecedor. Estos tipos deben ser conformes a CLS y los argumentos no se pueden asignar a la
propiedad por referencia (es decir, no pueden ser punteros administrados).
Si una propiedad tiene un captador y un establecedor, estos deben ser virtuales, estáticos o de instancia.
Los compiladores de C# y Visual Basic aplican automáticamente esta regla a través de la sintaxis de
definición de la propiedad.
Events
Un evento se define por su nombre y su tipo. El tipo de evento es un delegado que se utiliza para indicar el
evento. Por ejemplo, el evento AppDomain.AssemblyResolve es del tipo ResolveEventHandler. Además del propio
evento, hay tres métodos con nombres basados en el nombre de evento que proporcionan la implementación
del evento y que se marcan como SpecialName en los metadatos de ensamblado:
Un método para agregar un controlador de eventos, llamado add_ EventName. Por ejemplo, el método de
suscripción de eventos del evento AppDomain.AssemblyResolve se denomina add_AssemblyResolve .
Un método para quitar un controlador de eventos, llamado remove_ EventName. Por ejemplo, el método
de eliminación del evento AppDomain.AssemblyResolve se denomina remove_AssemblyResolve .
Un método para indicar que el evento se produjo, llamado raise_ EventName.

NOTE
La mayoría de las reglas de Common Language Specification relacionadas con los eventos se implementan mediante
compiladores del lenguaje y son transparentes para los desarrolladores de componentes.

Los métodos para agregar, quitar y generar el evento deben tener la misma accesibilidad. Además, todos deben
ser estáticos, virtuales o de instancia. Los métodos para agregar y quitar un evento tienen un parámetro cuyo
tipo es el mismo que el del delegado de eventos. Los métodos para agregar y quitar deben estar presentes o
ausentes al mismo tiempo.
En el ejemplo siguiente se define una clase conforme a CLS denominada Temperature que genera un evento
TemperatureChanged si el cambio de temperatura entre dos lecturas es igual o mayor que el valor de umbral. La
clase Temperature define de manera explícita un método raise_TemperatureChanged para que pueda ejecutar
controladores de eventos de forma selectiva.

using System;
using System.Collections;
using System.Collections.Generic;

[assembly: CLSCompliant(true)]

public class TemperatureChangedEventArgs : EventArgs


{
private Decimal originalTemp;
private Decimal newTemp;
private DateTimeOffset when;

public TemperatureChangedEventArgs(Decimal original, Decimal @new, DateTimeOffset time)


{
originalTemp = original;
newTemp = @new;
when = time;
}
}

public Decimal OldTemperature


{
get { return originalTemp; }
}

public Decimal CurrentTemperature


{
get { return newTemp; }
}

public DateTimeOffset Time


{
get { return when; }
}
}

public delegate void TemperatureChanged(Object sender, TemperatureChangedEventArgs e);

public class Temperature


{
private struct TemperatureInfo
{
public Decimal Temperature;
public DateTimeOffset Recorded;
}

public event TemperatureChanged TemperatureChanged;

private Decimal previous;


private Decimal current;
private Decimal tolerance;
private List<TemperatureInfo> tis = new List<TemperatureInfo>();

public Temperature(Decimal temperature, Decimal tolerance)


{
current = temperature;
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = temperature;
tis.Add(ti);
ti.Recorded = DateTimeOffset.UtcNow;
this.tolerance = tolerance;
}

public Decimal CurrentTemperature


{
get { return current; }
set {
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = value;
ti.Recorded = DateTimeOffset.UtcNow;
previous = current;
current = value;
if (Math.Abs(current - previous) >= tolerance)
raise_TemperatureChanged(new TemperatureChangedEventArgs(previous, current, ti.Recorded));
}
}

public void raise_TemperatureChanged(TemperatureChangedEventArgs eventArgs)


{
if (TemperatureChanged == null)
return;

foreach (TemperatureChanged d in TemperatureChanged.GetInvocationList()) {


if (d.Method.Name.Contains("Duplicate"))
Console.WriteLine("Duplicate event handler; event handler not executed.");
else
d.Invoke(this, eventArgs);
}
}
}
}

public class Example


{
public Temperature temp;

public static void Main()


{
Example ex = new Example();
}

public Example()
{
temp = new Temperature(65, 3);
temp.TemperatureChanged += this.TemperatureNotification;
RecordTemperatures();
Example ex = new Example(temp);
ex.RecordTemperatures();
}

public Example(Temperature t)
{
temp = t;
RecordTemperatures();
}

public void RecordTemperatures()


{
temp.TemperatureChanged += this.DuplicateTemperatureNotification;
temp.CurrentTemperature = 66;
temp.CurrentTemperature = 63;
}

internal void TemperatureNotification(Object sender, TemperatureChangedEventArgs e)


{
Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature);
}

public void DuplicateTemperatureNotification(Object sender, TemperatureChangedEventArgs e)


{
Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature);
}
}

Imports System.Collections
Imports System.Collections.Generic

<Assembly: CLSCompliant(True)>

Public Class TemperatureChangedEventArgs : Inherits EventArgs


Private originalTemp As Decimal
Private newTemp As Decimal
Private [when] As DateTimeOffset

Public Sub New(original As Decimal, [new] As Decimal, [time] As DateTimeOffset)


originalTemp = original
newTemp = [new]
[when] = [time]
End Sub

Public ReadOnly Property OldTemperature As Decimal


Get
Return originalTemp
End Get
End Get
End Property

Public ReadOnly Property CurrentTemperature As Decimal


Get
Return newTemp
End Get
End Property

Public ReadOnly Property [Time] As DateTimeOffset


Get
Return [when]
End Get
End Property
End Class

Public Delegate Sub TemperatureChanged(sender As Object, e As TemperatureChangedEventArgs)

Public Class Temperature


Private Structure TemperatureInfo
Dim Temperature As Decimal
Dim Recorded As DateTimeOffset
End Structure

Public Event TemperatureChanged As TemperatureChanged

Private previous As Decimal


Private current As Decimal
Private tolerance As Decimal
Private tis As New List(Of TemperatureInfo)

Public Sub New(temperature As Decimal, tolerance As Decimal)


current = temperature
Dim ti As New TemperatureInfo()
ti.Temperature = temperature
ti.Recorded = DateTimeOffset.UtcNow
tis.Add(ti)
Me.tolerance = tolerance
End Sub

Public Property CurrentTemperature As Decimal


Get
Return current
End Get
Set
Dim ti As New TemperatureInfo
ti.Temperature = value
ti.Recorded = DateTimeOffset.UtcNow
previous = current
current = value
If Math.Abs(current - previous) >= tolerance Then
raise_TemperatureChanged(New TemperatureChangedEventArgs(previous, current, ti.Recorded))
End If
End Set
End Property

Public Sub raise_TemperatureChanged(eventArgs As TemperatureChangedEventArgs)


If TemperatureChangedEvent Is Nothing Then Exit Sub

Dim ListenerList() As System.Delegate = TemperatureChangedEvent.GetInvocationList()


For Each d As TemperatureChanged In TemperatureChangedEvent.GetInvocationList()
If d.Method.Name.Contains("Duplicate") Then
Console.WriteLine("Duplicate event handler; event handler not executed.")
Else
d.Invoke(Me, eventArgs)
End If
Next
End Sub
End Class
Public Class Example
Public WithEvents temp As Temperature

Public Shared Sub Main()


Dim ex As New Example()
End Sub

Public Sub New()


temp = New Temperature(65, 3)
RecordTemperatures()
Dim ex As New Example(temp)
ex.RecordTemperatures()
End Sub

Public Sub New(t As Temperature)


temp = t
RecordTemperatures()
End Sub

Public Sub RecordTemperatures()


temp.CurrentTemperature = 66
temp.CurrentTemperature = 63

End Sub

Friend Shared Sub TemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _


Handles temp.TemperatureChanged
Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature)
End Sub

Friend Shared Sub DuplicateTemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _


Handles temp.TemperatureChanged
Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature)
End Sub
End Class

Overloads
Common Language Specification impone los siguientes requisitos a los miembros sobrecargados:
Los miembros se pueden sobrecargar según el número de parámetros y el tipo de cualquiera de los
parámetros. A la hora de distinguir entre sobrecargas, no se tienen en cuenta los factores de convención
de llamada, el tipo de valor devuelto, los modificadores personalizados aplicados al método o a su
parámetro, ni si los parámetros se pasan por valor o por referencia. Para consultar un ejemplo, vea el
código del requisito que establece que los nombres deben ser únicos en cada ámbito que se incluye en la
sección Convenciones de nomenclatura.
Solo las propiedades y los métodos se pueden sobrecargar. Los campos y eventos no se pueden
sobrecargar.
Los métodos genéricos pueden sobrecargarse en función del número de parámetros genéricos.

NOTE
Los operadores op_Explicit y op_Implicit son una excepción de la regla que establece que el valor devuelto no se
considera parte de la signatura de un método en la resolución de la sobrecarga. Estos dos operadores se pueden
sobrecargar según sus parámetros y su valor devuelto.

Excepciones
Los objetos de excepción deben derivar de System.Exception o de otro tipo derivado de System.Exception. En el
ejemplo siguiente se muestra el error de compilador que se produce cuando una clase personalizada
denominada ErrorClass se utiliza para el control de excepciones.

using System;

[assembly: CLSCompliant(true)]

public class ErrorClass


{
string msg;

public ErrorClass(string errorMessage)


{
msg = errorMessage;
}

public string Message


{
get { return msg; }
}
}

public static class StringUtilities


{
public static string[] SplitString(this string value, int index)
{
if (index < 0 | index > value.Length) {
ErrorClass badIndex = new ErrorClass("The index is not within the string.");
throw badIndex;
}
string[] retVal = { value.Substring(0, index - 1),
value.Substring(index) };
return retVal;
}
}
// Compilation produces a compiler error like the following:
// Exceptions1.cs(26,16): error CS0155: The type caught or thrown must be derived from
// System.Exception
Imports System.Runtime.CompilerServices

<Assembly: CLSCompliant(True)>

Public Class ErrorClass


Dim msg As String

Public Sub New(errorMessage As String)


msg = errorMessage
End Sub

Public ReadOnly Property Message As String


Get
Return msg
End Get
End Property
End Class

Public Module StringUtilities


<Extension()> Public Function SplitString(value As String, index As Integer) As String()
If index < 0 Or index > value.Length Then
Dim BadIndex As New ErrorClass("The index is not within the string.")
Throw BadIndex
End If
Dim retVal() As String = {value.Substring(0, index - 1),
value.Substring(index)}
Return retVal
End Function
End Module
' Compilation produces a compiler error like the following:
' Exceptions1.vb(27) : error BC30665: 'Throw' operand must derive from 'System.Exception'.
'
' Throw BadIndex
' ~~~~~~~~~~~~~~

Para corregir este error, la clase ErrorClass debe heredar de System.Exception. Además, la propiedad Message
debe invalidarse. En el ejemplo siguiente se corrigen estos errores para definir una clase ErrorClass que sea
conforme a CLS.
using System;

[assembly: CLSCompliant(true)]

public class ErrorClass : Exception


{
string msg;

public ErrorClass(string errorMessage)


{
msg = errorMessage;
}

public override string Message


{
get { return msg; }
}
}

public static class StringUtilities


{
public static string[] SplitString(this string value, int index)
{
if (index < 0 | index > value.Length) {
ErrorClass badIndex = new ErrorClass("The index is not within the string.");
throw badIndex;
}
string[] retVal = { value.Substring(0, index - 1),
value.Substring(index) };
return retVal;
}
}

Imports System.Runtime.CompilerServices

<Assembly: CLSCompliant(True)>

Public Class ErrorClass : Inherits Exception


Dim msg As String

Public Sub New(errorMessage As String)


msg = errorMessage
End Sub

Public Overrides ReadOnly Property Message As String


Get
Return msg
End Get
End Property
End Class

Public Module StringUtilities


<Extension()> Public Function SplitString(value As String, index As Integer) As String()
If index < 0 Or index > value.Length Then
Dim BadIndex As New ErrorClass("The index is not within the string.")
Throw BadIndex
End If
Dim retVal() As String = {value.Substring(0, index - 1),
value.Substring(index)}
Return retVal
End Function
End Module

Atributos
En los ensamblados de .NET Framework, los atributos personalizados proporcionan un mecanismo extensible
para almacenar atributos personalizados y recuperar metadatos sobre objetos de programación, tales como
ensamblados, tipos, miembros y parámetros de método. Los atributos personalizados deben derivar de
System.Attribute o de un tipo derivado de System.Attribute.
En el ejemplo siguiente se infringe esta regla. Define una clase NumericAttribute que no se deriva de
System.Attribute. Tenga en cuenta que un error del compilador solo se produce cuando se aplica el atributo no
conforme a CLS, y no cuando se define la clase.

using System;

[assembly: CLSCompliant(true)]

[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Struct)]
public class NumericAttribute
{
private bool _isNumeric;

public NumericAttribute(bool isNumeric)


{
_isNumeric = isNumeric;
}

public bool IsNumeric


{
get { return _isNumeric; }
}
}

[Numeric(true)] public struct UDouble


{
double Value;
}
// Compilation produces a compiler error like the following:
// Attribute1.cs(22,2): error CS0616: 'NumericAttribute' is not an attribute class
// Attribute1.cs(7,14): (Location of symbol related to previous error)

<Assembly: CLSCompliant(True)>

<AttributeUsageAttribute(AttributeTargets.Class Or AttributeTargets.Struct)> _
Public Class NumericAttribute
Private _isNumeric As Boolean

Public Sub New(isNumeric As Boolean)


_isNumeric = isNumeric
End Sub

Public ReadOnly Property IsNumeric As Boolean


Get
Return _isNumeric
End Get
End Property
End Class

<Numeric(True)> Public Structure UDouble


Dim Value As Double
End Structure
' Compilation produces a compiler error like the following:
' error BC31504: 'NumericAttribute' cannot be used as an attribute because it
' does not inherit from 'System.Attribute'.
'
' <Numeric(True)> Public Structure UDouble
' ~~~~~~~~~~~~~
El constructor o las propiedades de un atributo conforme a CLS pueden exponer solo los tipos siguientes:
Boolean
Byte
Char
Double
Int16
Int32
Int64
Single
String
Type
Cualquier tipo de enumeración cuyo tipo subyacente sea Byte, Int16, Int32 o Int64.
En el ejemplo siguiente se define una clase DescriptionAttribute que se deriva de Attribute. El constructor de
clase tiene un parámetro de tipo Descriptor , de modo que la clase no es conforme a CLS. Observe que el
compilador de C# emite una advertencia, aunque realiza la compilación correctamente, mientras que el
compilador de Visual Basic no emite ninguna una advertencia ni ningún error.

using System;

[assembly:CLSCompliantAttribute(true)]

public enum DescriptorType { type, member };

public class Descriptor


{
public DescriptorType Type;
public String Description;
}

[AttributeUsage(AttributeTargets.All)]
public class DescriptionAttribute : Attribute
{
private Descriptor desc;

public DescriptionAttribute(Descriptor d)
{
desc = d;
}

public Descriptor Descriptor


{ get { return desc; } }
}
// Attempting to compile the example displays output like the following:
// warning CS3015: 'DescriptionAttribute' has no accessible
// constructors which use only CLS-compliant types
<Assembly: CLSCompliantAttribute(True)>

Public Enum DescriptorType As Integer


Type = 0
Member = 1
End Enum

Public Class Descriptor


Public Type As DescriptorType
Public Description As String
End Class

<AttributeUsage(AttributeTargets.All)> _
Public Class DescriptionAttribute : Inherits Attribute
Private desc As Descriptor

Public Sub New(d As Descriptor)


desc = d
End Sub

Public ReadOnly Property Descriptor As Descriptor


Get
Return desc
End Get
End Property
End Class

CLSCompliantAttribute (Atributo)
El atributo CLSCompliantAttribute se usa para indicar si un elemento del programa es conforme a Common
Language Specification. El constructor CLSCompliantAttribute(Boolean) contiene un único parámetro obligatorio,
isCompliant , que indica si el elemento del programa es conforme a CLS.

En tiempo de compilación, el compilador detecta los elementos que supuestamente deberían ser conformes a
CLS y emite una advertencia. El compilador no emite advertencias relacionadas con los tipos o miembros cuya
no conformidad se declara explícitamente.
Los desarrolladores de componentes pueden usar el atributo CLSCompliantAttribute de dos maneras:
Para definir las partes de la interfaz pública expuestas por un componente que son conformes a CLS y las
que no lo son. Cuando el atributo se utiliza para marcar determinados elementos del programa como
conformes a CLS, este uso garantiza que dichos elementos son accesibles desde todos los lenguajes y
herramientas que tienen como destino .NET Framework.
Para garantizar que la interfaz pública de la biblioteca de componentes expone solo elementos del
programa que son conformes a CLS. Si los elementos no son conformes a CLS, los compiladores
normalmente emitirán una advertencia.

WARNING
En algunos casos, los compiladores de lenguaje aplican reglas conformes a CLS independientemente de si se usa el atributo
CLSCompliantAttribute o no. Por ejemplo, la definición de un miembro estático en una interfaz infringe una regla de CLS.
En este sentido, si se define un miembro static (en C#) o Shared (en Visual Basic) en una interfaz, los compiladores de
C# y Visual Basic mostrarán un mensaje de error y no podrán compilar la aplicación.

El atributo CLSCompliantAttribute está marcado con un atributo AttributeUsageAttribute que tiene el valor
AttributeTargets.All. Este valor le permite aplicar el atributo CLSCompliantAttribute a cualquier elemento de
programa, incluidos los ensamblados, módulos, tipos (clases, estructuras, enumeraciones, interfaces, delegados),
miembros de tipo (constructores, métodos, propiedades, campos y eventos), parámetros, parámetros genéricos y
valores devueltos. Sin embargo, en la práctica, solo debe aplicar el atributo a los ensamblados, tipos y miembros
del tipo. De lo contrario, los compiladores omitirán el atributo y seguirán generando advertencias de compilación
siempre que encuentren un parámetro no conforme, un parámetro genérico o un valor devuelto en la interfaz
pública de la biblioteca.
El valor del atributo CLSCompliantAttribute lo heredan los elementos del programa contenidos. Por ejemplo, si se
marca un ensamblado como conforme a CLS, sus tipos también son conformes a CLS. Si un tipo se marca como
conforme a CLS, sus tipos anidados y miembros también serán conformes a CLS.
Puede invalidar explícitamente la conformidad heredada aplicando el atributo CLSCompliantAttribute a un
elemento del programa contenido. Por ejemplo, puede utilizar el atributo CLSCompliantAttribute con el valor
isCompliant establecido en false para definir un tipo no conforme en un ensamblado conforme, y puede
utilizar el atributo con el valor isCompliant establecido en true para definir un tipo conforme en un
ensamblado no conforme. También puede definir miembros no conformes en un tipo conforme. Sin embargo, un
tipo no conforme no puede tener miembros conformes, por lo que no puede utilizar el atributo con el valor
isCompliant establecido en true para invalidar la herencia de un tipo no conforme.

Cuando desarrolle componentes, debe utilizar siempre el atributo CLSCompliantAttribute para indicar si el
ensamblado, sus tipos y sus miembros son conformes a CLS.
Para crear componentes conformes a CLS:
1. Utilice CLSCompliantAttribute para marcar el ensamblado como conforme a CLS.
2. Marque como no conforme los tipos expuestos públicamente en el ensamblado que no sean conformes a
CLS.
3. Marque como no conforme los miembros expuestos públicamente en tipos conformes a CLS.
4. Proporcione una alternativa conforme a CLS para los miembros que no sean conformes a CLS.
Si ha marcado correctamente todos los tipos y miembros no conformes, el compilador no debe emitir ninguna
advertencia de no conformidad. Sin embargo, debe indicar qué miembros no son conformes a CLS y mostrar las
alternativas conformes a CLS en la documentación del producto.
En el ejemplo siguiente se usa el atributo CLSCompliantAttribute para definir un ensamblado conforme a CLS y
un tipo, CharacterUtilities , que tiene dos miembros no conformes a CLS. Dado que ambos miembros están
etiquetados con el atributo CLSCompliant(false) , el compilador no genera ninguna advertencia. La clase también
proporciona una alternativa conforme a CLS para ambos métodos. Por lo general, simplemente se agregarían
dos sobrecargas al método ToUTF16 para proporcionar alternativas conformes a CLS. Sin embargo, puesto que
no se pueden sobrecargar los métodos en función de un valor devuelto, los nombres de los métodos conformes
a CLS son distintos de los nombres de los métodos no conformes.
using System;
using System.Text;

[assembly:CLSCompliant(true)]

public class CharacterUtilities


{
[CLSCompliant(false)] public static ushort ToUTF16(String s)
{
s = s.Normalize(NormalizationForm.FormC);
return Convert.ToUInt16(s[0]);
}

[CLSCompliant(false)] public static ushort ToUTF16(Char ch)


{
return Convert.ToUInt16(ch);
}

// CLS-compliant alternative for ToUTF16(String).


public static int ToUTF16CodeUnit(String s)
{
s = s.Normalize(NormalizationForm.FormC);
return (int) Convert.ToUInt16(s[0]);
}

// CLS-compliant alternative for ToUTF16(Char).


public static int ToUTF16CodeUnit(Char ch)
{
return Convert.ToInt32(ch);
}

public bool HasMultipleRepresentations(String s)


{
String s1 = s.Normalize(NormalizationForm.FormC);
return s.Equals(s1);
}

public int GetUnicodeCodePoint(Char ch)


{
if (Char.IsSurrogate(ch))
throw new ArgumentException("ch cannot be a high or low surrogate.");

return Char.ConvertToUtf32(ch.ToString(), 0);


}

public int GetUnicodeCodePoint(Char[] chars)


{
if (chars.Length > 2)
throw new ArgumentException("The array has too many characters.");

if (chars.Length == 2) {
if (! Char.IsSurrogatePair(chars[0], chars[1]))
throw new ArgumentException("The array must contain a low and a high surrogate.");
else
return Char.ConvertToUtf32(chars[0], chars[1]);
}
else {
return Char.ConvertToUtf32(chars.ToString(), 0);
}
}
}
Imports System.Text

<Assembly: CLSCompliant(True)>

Public Class CharacterUtilities


<CLSCompliant(False)> Public Shared Function ToUTF16(s As String) As UShort
s = s.Normalize(NormalizationForm.FormC)
Return Convert.ToUInt16(s(0))
End Function

<CLSCompliant(False)> Public Shared Function ToUTF16(ch As Char) As UShort


Return Convert.ToUInt16(ch)
End Function

' CLS-compliant alternative for ToUTF16(String).


Public Shared Function ToUTF16CodeUnit(s As String) As Integer
s = s.Normalize(NormalizationForm.FormC)
Return CInt(Convert.ToInt16(s(0)))
End Function

' CLS-compliant alternative for ToUTF16(Char).


Public Shared Function ToUTF16CodeUnit(ch As Char) As Integer
Return Convert.ToInt32(ch)
End Function

Public Function HasMultipleRepresentations(s As String) As Boolean


Dim s1 As String = s.Normalize(NormalizationForm.FormC)
Return s.Equals(s1)
End Function

Public Function GetUnicodeCodePoint(ch As Char) As Integer


If Char.IsSurrogate(ch) Then
Throw New ArgumentException("ch cannot be a high or low surrogate.")
End If
Return Char.ConvertToUtf32(ch.ToString(), 0)
End Function

Public Function GetUnicodeCodePoint(chars() As Char) As Integer


If chars.Length > 2 Then
Throw New ArgumentException("The array has too many characters.")
End If
If chars.Length = 2 Then
If Not Char.IsSurrogatePair(chars(0), chars(1)) Then
Throw New ArgumentException("The array must contain a low and a high surrogate.")
Else
Return Char.ConvertToUtf32(chars(0), chars(1))
End If
Else
Return Char.ConvertToUtf32(chars.ToString(), 0)
End If
End Function
End Class

Si está desarrollando una aplicación en lugar de una biblioteca (es decir, si no expone los tipos o miembros que
pueden consumir otros desarrolladores de aplicaciones), la conformidad con CLS de los elementos del programa
usados por la aplicación solo tendrá interés si su lenguaje admite estos elementos. En ese caso, el compilador del
lenguaje generará un error cuando intente utilizar un elemento no conforme a CLS.

Interoperabilidad entre lenguajes


La independencia de lenguaje tiene varios significados posibles. Un significado, descrito en el artículo
Independencia del lenguaje y componentes independientes del lenguaje, implica el uso sin problemas de tipos
escritos en un lenguaje desde una aplicación escrita en otro lenguaje. Un segundo significado, que es el descrito
en este artículo, implica combinar código escrito en varios lenguajes en un único ensamblado de .NET
Framework.
El ejemplo siguiente muestra la interoperabilidad entre lenguajes creando una biblioteca de clases de
denominada Utilities.dll que incluye dos clases, NumericLib y StringLib . La clase NumericLib está escrita en C#,
y la clase StringLib está escrita en Visual Basic. A continuación se muestra el código fuente para StringUtil.vb,
que incluye un solo miembro, ToTitleCase , en su clase StringLib .

Imports System.Collections.Generic
Imports System.Runtime.CompilerServices

Public Module StringLib


Private exclusions As List(Of String)

Sub New()
Dim words() As String = {"a", "an", "and", "of", "the"}
exclusions = New List(Of String)
exclusions.AddRange(words)
End Sub

<Extension()> _
Public Function ToTitleCase(title As String) As String
Dim words() As String = title.Split()
Dim result As String = String.Empty

For ctr As Integer = 0 To words.Length - 1


Dim word As String = words(ctr)
If ctr = 0 OrElse Not exclusions.Contains(word.ToLower()) Then
result += word.Substring(0, 1).ToUpper() + _
word.Substring(1).ToLower()
Else
result += word.ToLower()
End If
If ctr <= words.Length - 1 Then
result += " "
End If
Next
Return result
End Function
End Module

A continuación se muestra el código fuente para NumberUtil.cs, que define una clase NumericLib con dos
miembros, IsEven y NearZero .
using System;

public static class NumericLib


{
public static bool IsEven(this IConvertible number)
{
if (number is Byte ||
number is SByte ||
number is Int16 ||
number is UInt16 ||
number is Int32 ||
number is UInt32 ||
number is Int64)
return Convert.ToInt64(number) % 2 == 0;
else if (number is UInt64)
return ((ulong) number) % 2 == 0;
else
throw new NotSupportedException("IsEven called for a non-integer value.");
}

public static bool NearZero(double number)


{
return Math.Abs(number) < .00001;
}
}

Para empaquetar las dos clases en un solo ensamblado, debe compilarlas en módulos. Para compilar el archivo
de código fuente de Visual Basic en un módulo, use este comando:

vbc /t:module StringUtil.vb

Para más información sobre la sintaxis de la línea de comandos del compilador de Visual Basic, consulte
Compilación desde la línea de comandos.
Para compilar el archivo de código fuente de C# en un módulo, use este comando:

csc /t:module NumberUtil.cs

Para más información sobre la sintaxis de la línea de comandos del compilador de C#, consulte Compilación de la
línea de comandos con csc.exe.
Utilice luego las opciones del enlazador para compilar los dos módulos en un ensamblado:

link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll

El ejemplo siguiente llama a los métodos NumericLib.NearZero y StringLib.ToTitleCase . Observe que el código
de Visual Basic y el código de C# pueden tener acceso a los métodos en ambas clases.
using System;

public class Example


{
public static void Main()
{
Double dbl = 0.0 - Double.Epsilon;
Console.WriteLine(NumericLib.NearZero(dbl));

string s = "war and peace";


Console.WriteLine(s.ToTitleCase());
}
}
// The example displays the following output:
// True
// War and Peace

Module Example
Public Sub Main()
Dim dbl As Double = 0.0 - Double.Epsilon
Console.WriteLine(NumericLib.NearZero(dbl))

Dim s As String = "war and peace"


Console.WriteLine(s.ToTitleCase())
End Sub
End Module
' The example displays the following output:
' True
' War and Peace

Para compilar el código de Visual Basic, use este comando:

vbc example.vb /r:UtilityLib.dll

Para compilar con C#, cambie el nombre del compilador de vbc a csc y cambie la extensión de archivo .vb a .cs:

csc example.cs /r:UtilityLib.dll

Vea también
CLSCompliantAttribute
Conversión de tipos en .NET Framework
16/09/2020 • 43 minutes to read • Edit Online

Cada valor tiene un tipo asociado, que define atributos como la cantidad de espacio asignado al valor, el intervalo
de valores posibles que puede tener y los miembros que ofrece. Muchos valores se pueden expresar como más de
un tipo. Por ejemplo, el valor 4 se puede expresar como un entero o como un valor de punto flotante. La
conversión de tipo crea un valor en un nuevo tipo que es equivalente al valor de un tipo antiguo, pero no conserva
necesariamente la identidad (o valor exacto) del objeto original.
.NET Framework admite automáticamente las conversiones siguientes:
Conversión de una clase derivada a una clase base. Esto significa, por ejemplo, que una instancia de
cualquier clase o estructura se puede convertir en una instancia de tipo Object. Esta conversión no requiere
un operador de conversión.
Conversión de una clase base a la clase derivada original. En C#, esta conversión requiere un operador de
conversión. En Visual Basic, requiere el operador CType si Option Strict está activado.
Conversión de un tipo que implementa una interfaz a un objeto de interfaz que representa esa interfaz. Esta
conversión no requiere un operador de conversión.
Conversión de un objeto de interfaz al tipo original que implementa esa interfaz. En C#, esta conversión
requiere un operador de conversión. En Visual Basic, requiere el operador CType si Option Strict está
activado.
Además de estas conversiones automáticas, .NET Framework proporciona varias características que admiten la
conversión de tipos personalizada. Entre ellas se incluyen las siguientes:
El operador Implicit , que define las conversiones de ampliación disponibles entre los tipos. Para obtener
más información, consulte la sección Conversión implícita con el operador Implicit.
El operador Explicit , que define las conversiones de restricción disponibles entre los tipos. Para obtener
más información, consulte la sección Conversión explícita con el operador Explicit.
La interfaz IConvertible, que define las conversiones en cada uno de los tipos de datos base de .NET
Framework. Para obtener más información, vea Interfaz IConvertible.
La clase Convert, que proporciona un conjunto de métodos que implementan los métodos de la interfaz
IConvertible. Para obtener más información, vea la sección Clase Convert.
La clase TypeConverter, que es una clase base que se puede extender para admitir la conversión de un tipo
concreto en cualquier otro tipo. Para obtener más información, vea Clase TypeConverter.

Conversión implícita con el operador Implicit


Las conversiones de ampliación implican la creación de un nuevo valor a partir del valor de un tipo existente que
tiene un intervalo más restrictivo o una lista de miembros más restringida que el tipo de destino. Las conversión
de ampliación no pueden producir ninguna pérdida de datos (aunque pueden producir una pérdida de precisión).
Puesto que no se pueden perder datos, los compiladores pueden administrar la conversión de manera implícita o
transparente, sin que sea necesario el uso de un método de conversión explícito o de un operador de conversión.
NOTE
Aunque el código que realiza una conversión implícita puede llamar a un método de conversión o usar un operador de
conversión, los compiladores que admiten las conversiones implícitas no necesitan usarlos.

Por ejemplo, el tipo Decimal admite conversiones implícitas a partir de valores Byte, Char, Int16, Int32, Int64,
SByte, UInt16, UInt32 y UInt64. En el ejemplo siguiente se muestran algunas de estas conversiones implícitas al
asignar valores a una variable Decimal.

byte byteValue = 16;


short shortValue = -1024;
int intValue = -1034000;
long longValue = 1152921504606846976;
ulong ulongValue = UInt64.MaxValue;

decimal decimalValue;

decimalValue = byteValue;
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
byteValue.GetType().Name, decimalValue);

decimalValue = shortValue;
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
shortValue.GetType().Name, decimalValue);

decimalValue = intValue;
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
intValue.GetType().Name, decimalValue);

decimalValue = longValue;
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
longValue.GetType().Name, decimalValue);

decimalValue = ulongValue;
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
longValue.GetType().Name, decimalValue);
// The example displays the following output:
// After assigning a Byte value, the Decimal value is 16.
// After assigning a Int16 value, the Decimal value is -1024.
// After assigning a Int32 value, the Decimal value is -1034000.
// After assigning a Int64 value, the Decimal value is 1152921504606846976.
// After assigning a Int64 value, the Decimal value is 18446744073709551615.
Dim byteValue As Byte = 16
Dim shortValue As Short = -1024
Dim intValue As Integer = -1034000
Dim longValue As Long = CLng(1024 ^ 6)
Dim ulongValue As ULong = ULong.MaxValue

Dim decimalValue As Decimal

decimalValue = byteValue
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
byteValue.GetType().Name, decimalValue)

decimalValue = shortValue
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
shortValue.GetType().Name, decimalValue)

decimalValue = intValue
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
intValue.GetType().Name, decimalValue)

decimalValue = longValue
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
longValue.GetType().Name, decimalValue)

decimalValue = ulongValue
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
longValue.GetType().Name, decimalValue)
' The example displays the following output:
' After assigning a Byte value, the Decimal value is 16.
' After assigning a Int16 value, the Decimal value is -1024.
' After assigning a Int32 value, the Decimal value is -1034000.
' After assigning a Int64 value, the Decimal value is 1152921504606846976.
' After assigning a Int64 value, the Decimal value is 18446744073709551615.

Si un compilador de un lenguaje determinado admite operadores personalizados, también puede definir


conversiones implícitas en sus propios tipos personalizados. En el ejemplo siguiente se proporciona una
implementación parcial de un tipo de datos de byte con signo denominado ByteWithSign que usa la
representación de signo y magnitud. Admite la conversión implícita de valores Byte y SByte a valores
ByteWithSign .
public struct ByteWithSign
{
private SByte signValue;
private Byte value;

public static implicit operator ByteWithSign(SByte value)


{
ByteWithSign newValue;
newValue.signValue = (SByte) Math.Sign(value);
newValue.value = (byte) Math.Abs(value);
return newValue;
}

public static implicit operator ByteWithSign(Byte value)


{
ByteWithSign newValue;
newValue.signValue = 1;
newValue.value = value;
return newValue;
}

public override string ToString()


{
return (signValue * value).ToString();
}
}

Public Structure ByteWithSign


Private signValue As SByte
Private value As Byte

Public Overloads Shared Widening Operator CType(value As SByte) As ByteWithSign


Dim newValue As ByteWithSign
newValue.signValue = CSByte(Math.Sign(value))
newValue.value = CByte(Math.Abs(value))
Return newValue
End Operator

Public Overloads Shared Widening Operator CType(value As Byte) As ByteWithSign


Dim NewValue As ByteWithSign
newValue.signValue = 1
newValue.value = value
Return newValue
End Operator

Public Overrides Function ToString() As String


Return (signValue * value).ToString()
End Function
End Structure

A continuación, el código de cliente puede declarar una variable ByteWithSign y asignarle valores Byte y SByte sin
realizar ninguna conversión explícita ni emplear ningún operador de conversión, como se muestra en el ejemplo
siguiente.

SByte sbyteValue = -120;


ByteWithSign value = sbyteValue;
Console.WriteLine(value);
value = Byte.MaxValue;
Console.WriteLine(value);
// The example displays the following output:
// -120
// 255
Dim sbyteValue As SByte = -120
Dim value As ByteWithSign = sbyteValue
Console.WriteLine(value.ToString())
value = Byte.MaxValue
Console.WriteLine(value.ToString())
' The example displays the following output:
' -120
' 255

Conversión explícita con el operador Explicit


Las conversiones de restricción implican la creación de un nuevo valor a partir del valor de un tipo existente que
tiene un intervalo mayor o una lista de miembros mayor que el tipo de destino. Puesto que una conversión de
restricción puede producir una pérdida de datos, los compiladores suelen necesitar que la conversión se haga
explícita a través de una llamada a un método de conversión o a un operador de conversión. Es decir, la conversión
se debe administrar explícitamente en el código de desarrollo.

NOTE
La finalidad principal de solicitar un método de conversión o un operador de conversión para las conversiones de restricción
es que el desarrollador sea consciente de la posibilidad de que se pierdan datos o de que se produzca una excepción
OverflowException, para que se pueda administrar en el código. Sin embargo, algunos compiladores pueden ser menos
exigentes con este requisito. Por ejemplo, en Visual Basic, si Option Strict está desactivado (la configuración
predeterminada), el compilador de Visual Basic intenta realizar las conversiones de restricción implícitamente.

Por ejemplo, los tipos de datos UInt32, Int64 y UInt64 tienen intervalos que superan el del tipo de datos Int32,
como se muestra en la tabla siguiente.

T IP O C O M PA RA C IÓ N C O N EL IN T ERVA LO DE IN T 32

Int64 Int64.MaxValue es mayor que Int32.MaxValue, y


Int64.MinValue es menor que (tiene un intervalo negativo
mayor que) Int32.MinValue.

UInt32 UInt32.MaxValue es mayor que Int32.MaxValue.

UInt64 UInt64.MaxValue es mayor que Int32.MaxValue.

Para administrar estas conversiones de restricción, .NET Framework permite que los tipos definan un operador
Explicit . A continuación, los compiladores de lenguaje individuales pueden implementar este operador usando
su propia sintaxis o se puede llamar a un miembro de la clase Convert para realizar la conversión. (Para obtener
más información sobre la clase Convert, vea Clase Convert más adelante en este tema). En el ejemplo siguiente se
muestra el uso de las características de lenguaje para administrar la conversión explícita de estos valores enteros,
que potencialmente están fuera del intervalo, a valores Int32.
long number1 = int.MaxValue + 20L;
uint number2 = int.MaxValue - 1000;
ulong number3 = int.MaxValue;

int intNumber;

try {
intNumber = checked((int) number1);
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number1.GetType().Name, intNumber);
}
catch (OverflowException) {
if (number1 > int.MaxValue)
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number1, int.MaxValue);
else
Console.WriteLine("Conversion failed: {0} is less than {1}.",
number1, int.MinValue);
}

try {
intNumber = checked((int) number2);
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number2.GetType().Name, intNumber);
}
catch (OverflowException) {
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number2, int.MaxValue);
}

try {
intNumber = checked((int) number3);
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number3.GetType().Name, intNumber);
}
catch (OverflowException) {
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number1, int.MaxValue);
}

// The example displays the following output:


// Conversion failed: 2147483667 exceeds 2147483647.
// After assigning a UInt32 value, the Integer value is 2147482647.
// After assigning a UInt64 value, the Integer value is 2147483647.
Dim number1 As Long = Integer.MaxValue + 20L
Dim number2 As UInteger = Integer.MaxValue - 1000
Dim number3 As ULong = Integer.MaxValue

Dim intNumber As Integer

Try
intNumber = CInt(number1)
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number1.GetType().Name, intNumber)
Catch e As OverflowException
If number1 > Integer.MaxValue Then
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number1, Integer.MaxValue)
Else
Console.WriteLine("Conversion failed: {0} is less than {1}.\n",
number1, Integer.MinValue)
End If
End Try

Try
intNumber = CInt(number2)
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number2.GetType().Name, intNumber)
Catch e As OverflowException
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number2, Integer.MaxValue)
End Try

Try
intNumber = CInt(number3)
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number3.GetType().Name, intNumber)
Catch e As OverflowException
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number1, Integer.MaxValue)
End Try
' The example displays the following output:
' Conversion failed: 2147483667 exceeds 2147483647.
' After assigning a UInt32 value, the Integer value is 2147482647.
' After assigning a UInt64 value, the Integer value is 2147483647.

Las conversiones explícitas pueden producir resultados diferentes en los distintos lenguajes y estos resultados
pueden diferir del valor devuelto por el método Convert correspondiente. Por ejemplo, si el valor Double
12.63251 se convierte en Int32, el método CInt de Visual Basic y el método Convert.ToInt32(Double) de .NET
Framework redondean el valor Double para devolver un valor de 13, pero el operador (int) de C# trunca Double
para devolver un valor de 12. De manera similar, el operador (int) de C# no admite la conversión de booleano a
entero, pero el método CInt de Visual Basic convierte un valor de true en -1. Por otra parte, el método
Convert.ToInt32(Boolean) convierte un valor de true en 1.
La mayoría de los compiladores permiten realizar conversiones explícitas de manera comprobada o no
comprobada. Cuando se realiza una conversión comprobada, se produce una excepción OverflowException si el
valor del tipo que se va a convertir está fuera del intervalo del tipo de destino. Si se realiza una conversión no
comprobada en las mismas condiciones, es posible que la conversión no produzca una excepción, pero no se
podrá determinar con exactitud cuál será el comportamiento y se puede obtener como resultado un valor
incorrecto.
NOTE
En C#, las conversiones comprobadas se pueden realizar usando la palabra clave checked junto con un operador de
conversión o especificando la opción del compilador /checked+ . Por el contrario, las conversiones no comprobadas se
pueden realizar utilizando la palabra clave unchecked junto con el operador de conversión de tipo o especificando la opción
del compilador /checked- . De forma predeterminada, las conversiones explícitas no se comprueban. En Visual Basic, para
realizar conversiones comprobadas, se puede desactivar la casilla Quitar comprobaciones de desbordamiento con
enteros en el cuadro de diálogo Configuración de compilador avanzada del proyecto o especificando la opción de
compilador /removeintchecks- . Por el contrario, para realizar conversiones no comprobadas, se puede activar la casilla
Quitar comprobaciones de desbordamiento con enteros en el cuadro de diálogo Configuración de compilador
avanzada del proyecto o especificando la opción de compilador /removeintchecks+ . De forma predeterminada, las
conversiones explícitas se comprueban.

En el siguiente ejemplo de C# se usan las palabras clave checked y unchecked para mostrar la diferencia de
comportamiento cuando un valor que está fuera del intervalo de Byte se convierte en Byte. La conversión
comprobada produce una excepción, pero la conversión no comprobada asigna Byte.MaxValue a la variable Byte.

int largeValue = Int32.MaxValue;


byte newValue;

try {
newValue = unchecked((byte) largeValue);
Console.WriteLine("Converted the {0} value {1} to the {2} value {3}.",
largeValue.GetType().Name, largeValue,
newValue.GetType().Name, newValue);
}
catch (OverflowException) {
Console.WriteLine("{0} is outside the range of the Byte data type.",
largeValue);
}

try {
newValue = checked((byte) largeValue);
Console.WriteLine("Converted the {0} value {1} to the {2} value {3}.",
largeValue.GetType().Name, largeValue,
newValue.GetType().Name, newValue);
}
catch (OverflowException) {
Console.WriteLine("{0} is outside the range of the Byte data type.",
largeValue);
}
// The example displays the following output:
// Converted the Int32 value 2147483647 to the Byte value 255.
// 2147483647 is outside the range of the Byte data type.

Si un compilador de un lenguaje determinado admite operadores sobrecargados personalizados, también puede


definir conversiones explícitas en sus propios tipos personalizados. En el ejemplo siguiente se proporciona una
implementación parcial de un tipo de datos de byte con signo denominado ByteWithSign que usa la
representación de signo y magnitud. Admite la conversión explícita de valores Int32 y UInt32 a valores
ByteWithSign .
public struct ByteWithSign
{
private SByte signValue;
private Byte value;

private const byte MaxValue = byte.MaxValue;


private const int MinValue = -1 * byte.MaxValue;

public static explicit operator ByteWithSign(int value)


{
// Check for overflow.
if (value > ByteWithSign.MaxValue || value < ByteWithSign.MinValue)
throw new OverflowException(String.Format("'{0}' is out of range of the ByteWithSign data type.",
value));

ByteWithSign newValue;
newValue.signValue = (SByte) Math.Sign(value);
newValue.value = (byte) Math.Abs(value);
return newValue;
}

public static explicit operator ByteWithSign(uint value)


{
if (value > ByteWithSign.MaxValue)
throw new OverflowException(String.Format("'{0}' is out of range of the ByteWithSign data type.",
value));

ByteWithSign newValue;
newValue.signValue = 1;
newValue.value = (byte) value;
return newValue;
}

public override string ToString()


{
return (signValue * value).ToString();
}
}
Public Structure ByteWithSign
Private signValue As SByte
Private value As Byte

Private Const MaxValue As Byte = Byte.MaxValue


Private Const MinValue As Integer = -1 * Byte.MaxValue

Public Overloads Shared Narrowing Operator CType(value As Integer) As ByteWithSign


' Check for overflow.
If value > ByteWithSign.MaxValue Or value < ByteWithSign.MinValue Then
Throw New OverflowException(String.Format("'{0}' is out of range of the ByteWithSign data type.",
value))
End If

Dim newValue As ByteWithSign

newValue.signValue = CSByte(Math.Sign(value))
newValue.value = CByte(Math.Abs(value))
Return newValue
End Operator

Public Overloads Shared Narrowing Operator CType(value As UInteger) As ByteWithSign


If value > ByteWithSign.MaxValue Then
Throw New OverflowException(String.Format("'{0}' is out of range of the ByteWithSign data type.",
value))
End If

Dim NewValue As ByteWithSign

newValue.signValue = 1
newValue.value = CByte(value)
Return newValue
End Operator

Public Overrides Function ToString() As String


Return (signValue * value).ToString()
End Function
End Structure

A continuación, el código de cliente puede declarar una variable ByteWithSign y asignarle valores de tipo Int32 y
UInt32 si las asignaciones incluyen un operador de conversión o un método de conversión, como se muestra en el
ejemplo siguiente.
ByteWithSign value;

try {
int intValue = -120;
value = (ByteWithSign) intValue;
Console.WriteLine(value);
}
catch (OverflowException e) {
Console.WriteLine(e.Message);
}

try {
uint uintValue = 1024;
value = (ByteWithSign) uintValue;
Console.WriteLine(value);
}
catch (OverflowException e) {
Console.WriteLine(e.Message);
}
// The example displays the following output:
// -120
// '1024' is out of range of the ByteWithSign data type.

Dim value As ByteWithSign

Try
Dim intValue As Integer = -120
value = CType(intValue, ByteWithSign)
Console.WriteLine(value)
Catch e As OverflowException
Console.WriteLine(e.Message)
End Try

Try
Dim uintValue As UInteger = 1024
value = CType(uintValue, ByteWithSign)
Console.WriteLine(value)
Catch e As OverflowException
Console.WriteLine(e.Message)
End Try
' The example displays the following output:
' -120
' '1024' is out of range of the ByteWithSign data type.

La interfaz IConvertible
Para admitir la conversión de cualquier tipo en un tipo base de Common Language Runtime, .NET Framework
proporciona la interfaz IConvertible. El tipo que se está implementando debe proporcionar lo siguiente:
Un método que devuelva el objeto TypeCode del tipo que se está implementando.
Métodos para convertir el tipo que se está implementando en cada uno de los tipos base de Common
Language Runtime (Boolean, Byte, DateTime, Decimal, Double, etc.).
Un método de conversión generalizado para convertir una instancia del tipo que se está implementando en
otro tipo concreto. Las conversiones que no se admiten deben iniciar una excepción InvalidCastException.
Cada uno de los tipos base de Common Language Runtime (es decir, Boolean, Byte, Char, DateTime, Decimal,
Double, Int16, Int32, Int64, SByte, Single, String, UInt16, UInt32y UInt64), así como los tipos DBNull y Enum,
implementan la interfaz IConvertible. Sin embargo, se trata de implementaciones de interfaz explícitas; solo se
puede llamar al método de conversión mediante una variable de la interfaz IConvertible, como se muestra en el
ejemplo siguiente. Este ejemplo convierte un valor Int32 en su valor Char equivalente.

int codePoint = 1067;


IConvertible iConv = codePoint;
char ch = iConv.ToChar(null);
Console.WriteLine("Converted {0} to {1}.", codePoint, ch);

Dim codePoint As Integer = 1067


Dim iConv As IConvertible = codePoint
Dim ch As Char = iConv.ToChar(Nothing)
Console.WriteLine("Converted {0} to {1}.", codePoint, ch)

El requisito de llamar al método de conversión en su interfaz, en lugar de en el tipo que se está implementando,
hace que las implementaciones de interfaz explícitas resulten relativamente costosas. En su lugar, se recomienda
llamar al miembro adecuado de la clase Convert para convertir entre los tipos base de Common Language
Runtime. Para obtener más información, consulte la próxima sección, Clase Convert.

NOTE
Además de la interfaz IConvertible y la clase Convert proporcionadas por .NET Framework, cada lenguaje puede
proporcionar también maneras de realizar conversiones. Por ejemplo, C# utiliza operadores de conversión, mientras que
Visual Basic utiliza funciones de conversión implementadas por el compilador como CType , CInt y DirectCast .

En su mayor parte, la interfaz IConvertible está diseñada para admitir la conversión entre los tipos base de .NET
Framework. Sin embargo, la interfaz también puede implementarse por un tipo personalizado con el fin de
admitir la conversión de ese tipo a otros tipos personalizados. Para obtener más información, consulte la sección
Conversiones personalizadas con el método ChangeType más adelante en este tema.

Clase Convert
Aunque se puede llamar a la implementación de la interfaz IConvertible de cada tipo base para realizar una
conversión de tipo, llamar a los métodos de la clase System.Convert es la manera recomendada de convertir de
un tipo base en otro de una manera independiente del lenguaje. Además, se puede usar el método
Convert.ChangeType(Object, Type, IFormatProvider) para convertir de un tipo personalizado concreto a otro tipo.
Conversiones entre los tipos base
La clase Convert proporciona una manera independiente del lenguaje de realizar conversiones entre los tipos base
y está disponible para todos los lenguajes cuyo destino es Common Language Runtime. Ofrece un conjunto
completo de métodos tanto para conversiones de ampliación como de restricción e inicia una excepción
InvalidCastException para las conversiones que no se admiten (como la conversión de un valor DateTime a un
valor entero). Las conversiones de restricción se realizan en un contexto comprobado y se inicia una excepción
OverflowException si se produce un error en la conversión.

IMPORTANT
Puesto que la clase Convert incluye métodos para efectuar la conversión directa e inversa de cada tipo base, elimina la
necesidad de llamar a la implementación de interfaz explícita IConvertible de cada tipo base.

En el ejemplo siguiente se muestra el uso de la clase System.Convert para realizar varias conversiones de
restricción y ampliación entre los tipos base de .NET Framework.
// Convert an Int32 value to a Decimal (a widening conversion).
int integralValue = 12534;
decimal decimalValue = Convert.ToDecimal(integralValue);
Console.WriteLine("Converted the {0} value {1} to " +
"the {2} value {3:N2}.",
integralValue.GetType().Name,
integralValue,
decimalValue.GetType().Name,
decimalValue);
// Convert a Byte value to an Int32 value (a widening conversion).
byte byteValue = Byte.MaxValue;
int integralValue2 = Convert.ToInt32(byteValue);
Console.WriteLine("Converted the {0} value {1} to " +
"the {2} value {3:G}.",
byteValue.GetType().Name,
byteValue,
integralValue2.GetType().Name,
integralValue2);

// Convert a Double value to an Int32 value (a narrowing conversion).


double doubleValue = 16.32513e12;
try {
long longValue = Convert.ToInt64(doubleValue);
Console.WriteLine("Converted the {0} value {1:E} to " +
"the {2} value {3:N0}.",
doubleValue.GetType().Name,
doubleValue,
longValue.GetType().Name,
longValue);
}
catch (OverflowException) {
Console.WriteLine("Unable to convert the {0:E} value {1}.",
doubleValue.GetType().Name, doubleValue);
}

// Convert a signed byte to a byte (a narrowing conversion).


sbyte sbyteValue = -16;
try {
byte byteValue2 = Convert.ToByte(sbyteValue);
Console.WriteLine("Converted the {0} value {1} to " +
"the {2} value {3:G}.",
sbyteValue.GetType().Name,
sbyteValue,
byteValue2.GetType().Name,
byteValue2);
}
catch (OverflowException) {
Console.WriteLine("Unable to convert the {0} value {1}.",
sbyteValue.GetType().Name, sbyteValue);
}
// The example displays the following output:
// Converted the Int32 value 12534 to the Decimal value 12,534.00.
// Converted the Byte value 255 to the Int32 value 255.
// Converted the Double value 1.632513E+013 to the Int64 value 16,325,130,000,000.
// Unable to convert the SByte value -16.
' Convert an Int32 value to a Decimal (a widening conversion).
Dim integralValue As Integer = 12534
Dim decimalValue As Decimal = Convert.ToDecimal(integralValue)
Console.WriteLine("Converted the {0} value {1} to the {2} value {3:N2}.",
integralValue.GetType().Name,
integralValue,
decimalValue.GetType().Name,
decimalValue)

' Convert a Byte value to an Int32 value (a widening conversion).


Dim byteValue As Byte = Byte.MaxValue
Dim integralValue2 As Integer = Convert.ToInt32(byteValue)
Console.WriteLine("Converted the {0} value {1} to " +
"the {2} value {3:G}.",
byteValue.GetType().Name,
byteValue,
integralValue2.GetType().Name,
integralValue2)

' Convert a Double value to an Int32 value (a narrowing conversion).


Dim doubleValue As Double = 16.32513e12
Try
Dim longValue As Long = Convert.ToInt64(doubleValue)
Console.WriteLine("Converted the {0} value {1:E} to " +
"the {2} value {3:N0}.",
doubleValue.GetType().Name,
doubleValue,
longValue.GetType().Name,
longValue)
Catch e As OverflowException
Console.WriteLine("Unable to convert the {0:E} value {1}.",
doubleValue.GetType().Name, doubleValue)
End Try

' Convert a signed byte to a byte (a narrowing conversion).


Dim sbyteValue As SByte = -16
Try
Dim byteValue2 As Byte = Convert.ToByte(sbyteValue)
Console.WriteLine("Converted the {0} value {1} to " +
"the {2} value {3:G}.",
sbyteValue.GetType().Name,
sbyteValue,
byteValue2.GetType().Name,
byteValue2)
Catch e As OverflowException
Console.WriteLine("Unable to convert the {0} value {1}.",
sbyteValue.GetType().Name, sbyteValue)
End Try
' The example displays the following output:
' Converted the Int32 value 12534 to the Decimal value 12,534.00.
' Converted the Byte value 255 to the Int32 value 255.
' Converted the Double value 1.632513E+013 to the Int64 value 16,325,130,000,000.
' Unable to convert the SByte value -16.

En algunos casos, en especial al efectuar conversiones directas e inversas de valores de punto flotante, una
conversión puede conllevar una pérdida de precisión, aunque no se inicie una excepción OverflowException. En el
ejemplo siguiente se muestra esta pérdida de precisión. En el primer caso, un valor Decimal tiene menos precisión
(menos dígitos significativos) cuando se convierte en el tipo Double. En el segundo caso, un valor Double se
redondea de 42,72 a 43 para completar la conversión.
double doubleValue;

// Convert a Double to a Decimal.


decimal decimalValue = 13956810.96702888123451471211m;
doubleValue = Convert.ToDouble(decimalValue);
Console.WriteLine("{0} converted to {1}.", decimalValue, doubleValue);

doubleValue = 42.72;
try {
int integerValue = Convert.ToInt32(doubleValue);
Console.WriteLine("{0} converted to {1}.",
doubleValue, integerValue);
}
catch (OverflowException) {
Console.WriteLine("Unable to convert {0} to an integer.",
doubleValue);
}
// The example displays the following output:
// 13956810.96702888123451471211 converted to 13956810.9670289.
// 42.72 converted to 43.

Dim doubleValue As Double

' Convert a Double to a Decimal.


Dim decimalValue As Decimal = 13956810.96702888123451471211d
doubleValue = Convert.ToDouble(decimalValue)
Console.WriteLine("{0} converted to {1}.", decimalValue, doubleValue)

doubleValue = 42.72
Try
Dim integerValue As Integer = Convert.ToInt32(doubleValue)
Console.WriteLine("{0} converted to {1}.",
doubleValue, integerValue)
Catch e As OverflowException
Console.WriteLine("Unable to convert {0} to an integer.",
doubleValue)
End Try
' The example displays the following output:
' 13956810.96702888123451471211 converted to 13956810.9670289.
' 42.72 converted to 43.

Para obtener una tabla en la que se muestra una lista de conversiones de restricción y ampliación admitidas por la
clase Convert, vea Tablas de conversiones de tipos.
Conversiones personalizadas con el método ChangeType
Además de admitir las conversiones en cada uno de los tipos base, la clase Convert se puede usar para convertir
un tipo personalizado en uno o varios tipos predefinidos. El método Convert.ChangeType(Object, Type,
IFormatProvider), que a su vez contiene una llamada al método IConvertible.ToType del parámetro value , realiza
esta conversión. Esto significa que el objeto representado por el parámetro value debe proporcionar una
implementación de la interfaz IConvertible.

NOTE
Dado que los métodos Convert.ChangeType(Object, Type) y Convert.ChangeType(Object, Type, IFormatProvider) utilizan un
objeto Type para especificar el tipo de destino al que se convierte value , se pueden utilizar para realizar una conversión
dinámica a un objeto cuyo tipo se desconoce en tiempo de compilación. Sin embargo, observe que la implementación de
IConvertible de value aún debe admitir esta conversión.

En el ejemplo siguiente se muestra una posible implementación de la interfaz IConvertible que permite convertir
un objeto TemperatureCelsius en un objeto TemperatureFahrenheit y viceversa. En el ejemplo se define una clase
base, Temperature , que implementa la interfaz IConvertible e invalida el método Object.ToString. Cada una de las
clases derivadas TemperatureCelsius y TemperatureFahrenheit invalida los métodos ToType y ToString de la
clase base.

using System;

public abstract class Temperature : IConvertible


{
protected decimal temp;

public Temperature(decimal temperature)


{
this.temp = temperature;
}

public decimal Value


{
get { return this.temp; }
set { this.temp = value; }
}

public override string ToString()


{
return temp.ToString(null as IFormatProvider) + "º";
}

// IConvertible implementations.
public TypeCode GetTypeCode() {
return TypeCode.Object;
}

public bool ToBoolean(IFormatProvider provider) {


throw new InvalidCastException(String.Format("Temperature-to-Boolean conversion is not supported."));
}

public byte ToByte(IFormatProvider provider) {


if (temp < Byte.MinValue || temp > Byte.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the Byte data type.", temp));
else
return (byte) temp;
}

public char ToChar(IFormatProvider provider) {


throw new InvalidCastException("Temperature-to-Char conversion is not supported.");
}

public DateTime ToDateTime(IFormatProvider provider) {


throw new InvalidCastException("Temperature-to-DateTime conversion is not supported.");
}

public decimal ToDecimal(IFormatProvider provider) {


return temp;
}

public double ToDouble(IFormatProvider provider) {


return (double) temp;
}

public short ToInt16(IFormatProvider provider) {


if (temp < Int16.MinValue || temp > Int16.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the Int16 data type.", temp));
else
return (short) Math.Round(temp);
}

public int ToInt32(IFormatProvider provider) {


public int ToInt32(IFormatProvider provider) {
if (temp < Int32.MinValue || temp > Int32.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the Int32 data type.", temp));
else
return (int) Math.Round(temp);
}

public long ToInt64(IFormatProvider provider) {


if (temp < Int64.MinValue || temp > Int64.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the Int64 data type.", temp));
else
return (long) Math.Round(temp);
}

public sbyte ToSByte(IFormatProvider provider) {


if (temp < SByte.MinValue || temp > SByte.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the SByte data type.", temp));
else
return (sbyte) temp;
}

public float ToSingle(IFormatProvider provider) {


return (float) temp;
}

public virtual string ToString(IFormatProvider provider) {


return temp.ToString(provider) + "°";
}

// If conversionType is implemented by another IConvertible method, call it.


public virtual object ToType(Type conversionType, IFormatProvider provider) {
switch (Type.GetTypeCode(conversionType))
{
case TypeCode.Boolean:
return this.ToBoolean(provider);
case TypeCode.Byte:
return this.ToByte(provider);
case TypeCode.Char:
return this.ToChar(provider);
case TypeCode.DateTime:
return this.ToDateTime(provider);
case TypeCode.Decimal:
return this.ToDecimal(provider);
case TypeCode.Double:
return this.ToDouble(provider);
case TypeCode.Empty:
throw new NullReferenceException("The target type is null.");
case TypeCode.Int16:
return this.ToInt16(provider);
case TypeCode.Int32:
return this.ToInt32(provider);
case TypeCode.Int64:
return this.ToInt64(provider);
case TypeCode.Object:
// Leave conversion of non-base types to derived classes.
throw new InvalidCastException(String.Format("Cannot convert from Temperature to {0}.",
conversionType.Name));
case TypeCode.SByte:
return this.ToSByte(provider);
case TypeCode.Single:
return this.ToSingle(provider);
case TypeCode.String:
IConvertible iconv = this;
return iconv.ToString(provider);
case TypeCode.UInt16:
return this.ToUInt16(provider);
case TypeCode.UInt32:
return this.ToUInt32(provider);
case TypeCode.UInt64:
return this.ToUInt64(provider);
return this.ToUInt64(provider);
default:
throw new InvalidCastException("Conversion not supported.");
}
}

public ushort ToUInt16(IFormatProvider provider) {


if (temp < UInt16.MinValue || temp > UInt16.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the UInt16 data type.", temp));
else
return (ushort) Math.Round(temp);
}

public uint ToUInt32(IFormatProvider provider) {


if (temp < UInt32.MinValue || temp > UInt32.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the UInt32 data type.", temp));
else
return (uint) Math.Round(temp);
}

public ulong ToUInt64(IFormatProvider provider) {


if (temp < UInt64.MinValue || temp > UInt64.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the UInt64 data type.", temp));
else
return (ulong) Math.Round(temp);
}
}

public class TemperatureCelsius : Temperature, IConvertible


{
public TemperatureCelsius(decimal value) : base(value)
{
}

// Override ToString methods.


public override string ToString()
{
return this.ToString(null);
}

public override string ToString(IFormatProvider provider)


{
return temp.ToString(provider) + "°C";
}

// If conversionType is a implemented by another IConvertible method, call it.


public override object ToType(Type conversionType, IFormatProvider provider) {
// For non-objects, call base method.
if (Type.GetTypeCode(conversionType) != TypeCode.Object) {
return base.ToType(conversionType, provider);
}
else
{
if (conversionType.Equals(typeof(TemperatureCelsius)))
return this;
else if (conversionType.Equals(typeof(TemperatureFahrenheit)))
return new TemperatureFahrenheit((decimal) this.temp * 9 / 5 + 32);
else
throw new InvalidCastException(String.Format("Cannot convert from Temperature to {0}.",
conversionType.Name));
}
}
}

public class TemperatureFahrenheit : Temperature, IConvertible


{
public TemperatureFahrenheit(decimal value) : base(value)
{
}
// Override ToString methods.
public override string ToString()
{
return this.ToString(null);
}

public override string ToString(IFormatProvider provider)


{
return temp.ToString(provider) + "°F";
}

public override object ToType(Type conversionType, IFormatProvider provider)


{
// For non-objects, call base methood.
if (Type.GetTypeCode(conversionType) != TypeCode.Object) {
return base.ToType(conversionType, provider);
}
else
{
// Handle conversion between derived classes.
if (conversionType.Equals(typeof(TemperatureFahrenheit)))
return this;
else if (conversionType.Equals(typeof(TemperatureCelsius)))
return new TemperatureCelsius((decimal) (this.temp - 32) * 5 / 9);
// Unspecified object type: throw an InvalidCastException.
else
throw new InvalidCastException(String.Format("Cannot convert from Temperature to {0}.",
conversionType.Name));
}
}
}

Public MustInherit Class Temperature


Implements IConvertible

Protected temp As Decimal

Public Sub New(temperature As Decimal)


Me.temp = temperature
End Sub

Public Property Value As Decimal


Get
Return Me.temp
End Get
Set
Me.temp = Value
End Set
End Property

Public Overrides Function ToString() As String


Return temp.ToString() & "º"
End Function

' IConvertible implementations.


Public Function GetTypeCode() As TypeCode Implements IConvertible.GetTypeCode
Return TypeCode.Object
End Function

Public Function ToBoolean(provider As IFormatProvider) As Boolean Implements IConvertible.ToBoolean


Throw New InvalidCastException(String.Format("Temperature-to-Boolean conversion is not supported."))
End Function

Public Function ToByte(provider As IFormatProvider) As Byte Implements IConvertible.ToByte


If temp < Byte.MinValue Or temp > Byte.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the Byte data type.", temp))
Else
Else
Return CByte(temp)
End If
End Function

Public Function ToChar(provider As IFormatProvider) As Char Implements IConvertible.ToChar


Throw New InvalidCastException("Temperature-to-Char conversion is not supported.")
End Function

Public Function ToDateTime(provider As IFormatProvider) As DateTime Implements IConvertible.ToDateTime


Throw New InvalidCastException("Temperature-to-DateTime conversion is not supported.")
End Function

Public Function ToDecimal(provider As IFormatProvider) As Decimal Implements IConvertible.ToDecimal


Return temp
End Function

Public Function ToDouble(provider As IFormatProvider) As Double Implements IConvertible.ToDouble


Return CDbl(temp)
End Function

Public Function ToInt16(provider As IFormatProvider) As Int16 Implements IConvertible.ToInt16


If temp < Int16.MinValue Or temp > Int16.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the Int16 data type.", temp))
End If
Return CShort(Math.Round(temp))
End Function

Public Function ToInt32(provider As IFormatProvider) As Int32 Implements IConvertible.ToInt32


If temp < Int32.MinValue Or temp > Int32.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the Int32 data type.", temp))
End If
Return CInt(Math.Round(temp))
End Function

Public Function ToInt64(provider As IFormatProvider) As Int64 Implements IConvertible.ToInt64


If temp < Int64.MinValue Or temp > Int64.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the Int64 data type.", temp))
End If
Return CLng(Math.Round(temp))
End Function

Public Function ToSByte(provider As IFormatProvider) As SByte Implements IConvertible.ToSByte


If temp < SByte.MinValue Or temp > SByte.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the SByte data type.", temp))
Else
Return CSByte(temp)
End If
End Function

Public Function ToSingle(provider As IFormatProvider) As Single Implements IConvertible.ToSingle


Return CSng(temp)
End Function

Public Overridable Overloads Function ToString(provider As IFormatProvider) As String Implements


IConvertible.ToString
Return temp.ToString(provider) & " °C"
End Function

' If conversionType is a implemented by another IConvertible method, call it.


Public Overridable Function ToType(conversionType As Type, provider As IFormatProvider) As Object
Implements IConvertible.ToType
Select Case Type.GetTypeCode(conversionType)
Case TypeCode.Boolean
Return Me.ToBoolean(provider)
Case TypeCode.Byte
Return Me.ToByte(provider)
Case TypeCode.Char
Return Me.ToChar(provider)
Case TypeCode.DateTime
Case TypeCode.DateTime
Return Me.ToDateTime(provider)
Case TypeCode.Decimal
Return Me.ToDecimal(provider)
Case TypeCode.Double
Return Me.ToDouble(provider)
Case TypeCode.Empty
Throw New NullReferenceException("The target type is null.")
Case TypeCode.Int16
Return Me.ToInt16(provider)
Case TypeCode.Int32
Return Me.ToInt32(provider)
Case TypeCode.Int64
Return Me.ToInt64(provider)
Case TypeCode.Object
' Leave conversion of non-base types to derived classes.
Throw New InvalidCastException(String.Format("Cannot convert from Temperature to {0}.", _
conversionType.Name))
Case TypeCode.SByte
Return Me.ToSByte(provider)
Case TypeCode.Single
Return Me.ToSingle(provider)
Case TypeCode.String
Return Me.ToString(provider)
Case TypeCode.UInt16
Return Me.ToUInt16(provider)
Case TypeCode.UInt32
Return Me.ToUInt32(provider)
Case TypeCode.UInt64
Return Me.ToUInt64(provider)
Case Else
Throw New InvalidCastException("Conversion not supported.")
End Select
End Function

Public Function ToUInt16(provider As IFormatProvider) As UInt16 Implements IConvertible.ToUInt16


If temp < UInt16.MinValue Or temp > UInt16.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the UInt16 data type.", temp))
End If
Return CUShort(Math.Round(temp))
End Function

Public Function ToUInt32(provider As IFormatProvider) As UInt32 Implements IConvertible.ToUInt32


If temp < UInt32.MinValue Or temp > UInt32.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the UInt32 data type.", temp))
End If
Return CUInt(Math.Round(temp))
End Function

Public Function ToUInt64(provider As IFormatProvider) As UInt64 Implements IConvertible.ToUInt64


If temp < UInt64.MinValue Or temp > UInt64.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the UInt64 data type.", temp))
End If
Return CULng(Math.Round(temp))
End Function
End Class

Public Class TemperatureCelsius : Inherits Temperature : Implements IConvertible


Public Sub New(value As Decimal)
MyBase.New(value)
End Sub

' Override ToString methods.


Public Overrides Function ToString() As String
Return Me.ToString(Nothing)
End Function

Public Overrides Function ToString(provider As IFormatProvider) As String


Return temp.ToString(provider) + "°C"
End Function
End Function

' If conversionType is a implemented by another IConvertible method, call it.


Public Overrides Function ToType(conversionType As Type, provider As IFormatProvider) As Object
' For non-objects, call base method.
If Type.GetTypeCode(conversionType) <> TypeCode.Object Then
Return MyBase.ToType(conversionType, provider)
Else
If conversionType.Equals(GetType(TemperatureCelsius)) Then
Return Me
ElseIf conversionType.Equals(GetType(TemperatureFahrenheit))
Return New TemperatureFahrenheit(CDec(Me.temp * 9 / 5 + 32))
' Unspecified object type: throw an InvalidCastException.
Else
Throw New InvalidCastException(String.Format("Cannot convert from Temperature to {0}.", _
conversionType.Name))
End If
End If
End Function
End Class

Public Class TemperatureFahrenheit : Inherits Temperature : Implements IConvertible


Public Sub New(value As Decimal)
MyBase.New(value)
End Sub

' Override ToString methods.


Public Overrides Function ToString() As String
Return Me.ToString(Nothing)
End Function

Public Overrides Function ToString(provider As IFormatProvider) As String


Return temp.ToString(provider) + "°F"
End Function

Public Overrides Function ToType(conversionType As Type, provider As IFormatProvider) As Object


' For non-objects, call base methood.
If Type.GetTypeCode(conversionType) <> TypeCode.Object Then
Return MyBase.ToType(conversionType, provider)
Else
' Handle conversion between derived classes.
If conversionType.Equals(GetType(TemperatureFahrenheit)) Then
Return Me
ElseIf conversionType.Equals(GetType(TemperatureCelsius))
Return New TemperatureCelsius(CDec((MyBase.temp - 32) * 5 / 9))
' Unspecified object type: throw an InvalidCastException.
Else
Throw New InvalidCastException(String.Format("Cannot convert from Temperature to {0}.", _
conversionType.Name))
End If
End If
End Function
End Class

En el ejemplo siguiente se muestran varias llamadas a estas implementaciones de IConvertible para convertir los
objetos TemperatureCelsius en objetos TemperatureFahrenheit y viceversa.
TemperatureCelsius tempC1 = new TemperatureCelsius(0);
TemperatureFahrenheit tempF1 = (TemperatureFahrenheit) Convert.ChangeType(tempC1,
typeof(TemperatureFahrenheit), null);
Console.WriteLine("{0} equals {1}.", tempC1, tempF1);
TemperatureCelsius tempC2 = (TemperatureCelsius) Convert.ChangeType(tempC1, typeof(TemperatureCelsius), null);
Console.WriteLine("{0} equals {1}.", tempC1, tempC2);
TemperatureFahrenheit tempF2 = new TemperatureFahrenheit(212);
TemperatureCelsius tempC3 = (TemperatureCelsius) Convert.ChangeType(tempF2, typeof(TemperatureCelsius), null);
Console.WriteLine("{0} equals {1}.", tempF2, tempC3);
TemperatureFahrenheit tempF3 = (TemperatureFahrenheit) Convert.ChangeType(tempF2,
typeof(TemperatureFahrenheit), null);
Console.WriteLine("{0} equals {1}.", tempF2, tempF3);
// The example displays the following output:
// 0°C equals 32°F.
// 0°C equals 0°C.
// 212°F equals 100°C.
// 212°F equals 212°F.

Dim tempC1 As New TemperatureCelsius(0)


Dim tempF1 As TemperatureFahrenheit = CType(Convert.ChangeType(tempC1, GetType(TemperatureFahrenheit),
Nothing), TemperatureFahrenheit)
Console.WriteLine("{0} equals {1}.", tempC1, tempF1)
Dim tempC2 As TemperatureCelsius = CType(Convert.ChangeType(tempC1, GetType(TemperatureCelsius), Nothing),
TemperatureCelsius)
Console.WriteLine("{0} equals {1}.", tempC1, tempC2)
Dim tempF2 As New TemperatureFahrenheit(212)
Dim tempC3 As TEmperatureCelsius = CType(Convert.ChangeType(tempF2, GEtType(TemperatureCelsius), Nothing),
TemperatureCelsius)
Console.WriteLine("{0} equals {1}.", tempF2, tempC3)
Dim tempF3 As TemperatureFahrenheit = CType(Convert.ChangeType(tempF2, GetType(TemperatureFahrenheit),
Nothing), TemperatureFahrenheit)
Console.WriteLine("{0} equals {1}.", tempF2, tempF3)
' The example displays the following output:
' 0°C equals 32°F.
' 0°C equals 0°C.
' 212°F equals 100°C.
' 212°F equals 212°F.

Clase TypeConverter
.NET Framework también permite definir un convertidor de tipos para un tipo personalizado extendiendo la clase
System.ComponentModel.TypeConverter y asociando el convertidor de tipos al tipo mediante un atributo
System.ComponentModel.TypeConverterAttribute. En la tabla siguiente se resaltan las diferencias entre este
enfoque y la implementación de la interfaz IConvertible para un tipo personalizado.

NOTE
Se puede proporcionar compatibilidad en tiempo de diseño para un tipo personalizado sólo si se ha definido un convertidor
de tipos para éste.

C O N VERSIÓ N M EDIA N T E T Y P EC O N VERT ER C O N VERSIÓ N M EDIA N T E IC O N VERT IB L E

Se implementa para un tipo personalizado derivando una Se implementa mediante un tipo personalizado para realizar la
clase independiente de TypeConverter. Esta clase derivada se conversión. Un usuario del tipo invoca un método de
asocia al tipo personalizado aplicando un atributo conversión de IConvertible para el tipo.
TypeConverterAttribute.
C O N VERSIÓ N M EDIA N T E T Y P EC O N VERT ER C O N VERSIÓ N M EDIA N T E IC O N VERT IB L E

Se puede utilizar en tiempo de diseño y en tiempo de Sólo se puede utilizar en tiempo de ejecución.
ejecución.

Usa la reflexión; por tanto, es más lenta que la conversión No utiliza la reflexión.
mediante IConvertible.

Permite realizar conversiones bidireccionales; es decir, Permite convertir un tipo personalizado en otros tipos de
convertir el tipo personalizado en otros tipos de datos y datos, pero no otros tipos de datos en el tipo personalizado.
convertir otros tipos de datos en el tipo personalizado. Por
ejemplo, un objeto TypeConverter definido para MyType
permite conversiones de MyType a String y de String a
MyType .

Para obtener más información sobre cómo usar convertidores de tipos para realizar conversiones, vea
System.ComponentModel.TypeConverter.

Vea también
System.Convert
IConvertible
Tablas de conversión de tipos
Tablas de conversión de tipos en .NET
16/09/2020 • 4 minutes to read • Edit Online

Una conversión de ampliación se produce cuando se convierte un valor de un tipo a otro tipo que es de igual o
mayor tamaño. Una conversión de restricción se produce cuando se convierte un valor de un tipo a otro tipo que
es de un tamaño menor. En las tablas de este tema se muestran los comportamientos de ambos tipos de
conversiones.

Conversiones de ampliación
En la tabla siguiente se describen las conversiones de ampliación que pueden realizarse sin pérdida de
información.

T IP O SE P UEDE C O N VERT IR SIN P ÉRDIDA DE DATO S A

Byte UInt16, Int16, UInt32, Int32, UInt64, Int64, Single, Double,


Decimal

SByte Int16, Int32, Int64, Single, Double, Decimal

Int16 Int32, Int64, Single, Double, Decimal

UInt16 UInt32, Int32, UInt64, Int64, Single, Double, Decimal

Char UInt16, UInt32, Int32, UInt64, Int64, Single, Double, Decimal

Int32 Int64, Double, Decimal

UInt32 Int64, UInt64, Double, Decimal

Int64 Decimal

UInt64 Decimal

Single Double

Algunas conversiones de ampliación a Single o Double pueden provocar una pérdida de precisión. En la tabla
siguiente se describen las conversiones de ampliación que a veces se traducen en una pérdida de información.

T IP O SE P UEDE C O N VERT IR A

Int32 Single

UInt32 Single

Int64 Single, Double

UInt64 Single, Double


T IP O SE P UEDE C O N VERT IR A

Decimal Single, Double

Conversiones de restricción
Una conversión de restricción a Single o Double puede provocar una pérdida de información. Si el tipo de destino
no puede expresar correctamente la magnitud del origen, el tipo resultante se establece en la constante
PositiveInfinity o NegativeInfinity . PositiveInfinity resulta al dividir un número positivo por cero y también
se devuelve cuando el valor de Single o Double supera el valor del campo MaxValue . NegativeInfinity resulta al
dividir un número negativo por cero y también se devuelve cuando el valor de Single o Double cae por debajo del
valor del campo MinValue . Una conversión de Double a Single podría dar lugar a PositiveInfinity o
NegativeInfinity .

Una conversión de restricción también puede traducirse en una pérdida de información para otros tipos de datos.
Pero se inicia OverflowException si el valor de un tipo que se va a convertir está fuera del intervalo especificado
por los campos MaxValue y MinValue del tipo de destino y el tiempo de ejecución comprueba la conversión para
asegurarse de que el valor del tipo de destino no supera su MaxValue o MinValue . Las conversiones que se
realizan con la clase System.Convert siempre se comprueban de esta manera.
En la tabla siguiente se enumeran las conversiones que inician OverflowException con System.Convert o cualquier
conversión comprobada si el valor del tipo que se va a convertir está fuera del intervalo definido del tipo
resultante.

T IP O SE P UEDE C O N VERT IR A

Byte SByte

SByte Byte, UInt16, UInt32, UInt64

Int16 Byte, SByte, UInt16

UInt16 Byte, SByte, Int16

Int32 Byte, SByte, Int16, UInt16,UInt32

UInt32 Byte, SByte, Int16, UInt16, Int32

Int64 Byte, SByte, Int16, UInt16, Int32,UInt32,UInt64

UInt64 Byte, SByte, Int16, UInt16, Int32, UInt32, Int64

Decimal Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64

Single Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64

Double Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64

Vea también
System.Convert
Conversión de tipos en .NET
Bibliotecas de Framework
18/03/2020 • 6 minutes to read • Edit Online

.NET tiene un amplio conjunto estándar de bibliotecas, a las que se hace referencia como bibliotecas de clases base
(conjunto básico) o bibliotecas de clases de marco de trabajo (conjunto completo). Estas bibliotecas proporcionan
implementaciones para muchos tipos generales y específicos de las aplicaciones, algoritmos y funcionalidad de la
utilidad. Las bibliotecas comerciales y las comunitarias se basan en las bibliotecas de clases de marco de trabajo, ya
que ofrecen bibliotecas estándar fáciles de usar para un amplio conjunto de tareas informáticas.
Se proporcionan un subconjunto de estas bibliotecas con cada implementación .NET. Se esperan API de bibliotecas
de clases base (BCL) con cualquier implementación de .NET porque los programadores las querrán y porque las
bibliotecas populares necesitarán su ejecución. No estarán disponibles bibliotecas específicas de la aplicación por
encima de BCL, como ASP.NET, en todas las implementaciones .NET.

Bibliotecas de clases base


La BCL proporciona los tipos más fundamentales y la funcionalidad de la utilidad, además de ser la base de otras
bibliotecas de clases .NET. Pretende ofrecer implementaciones muy generales sin compensaciones por ninguna
carga de trabajo. El rendimiento es siempre una consideración importante, puesto que las aplicaciones podrían
preferir una directiva concreta, como baja latencia para alto rendimiento o baja memoria para uso bajo de la CPU.
Estas bibliotecas están diseñadas para ser generalmente de alto rendimiento y adoptar un enfoque de término
medio atendiendo a estos diferentes aspectos de rendimiento. En la mayoría de las aplicaciones, este enfoque ha
tenido bastante éxito.

Tipos primitivos
.NET incluye un conjunto de tipos primitivos, que se usan (en distintos grados) en todos los programas. Estos tipos
contienen datos, como números, cadenas, bytes y objetos arbitrarios. El lenguaje C# incluye palabras clave para
estos tipos. A continuación se muestra un conjunto de ejemplo de estos tipos, con las palabras clave de C#
correspondientes.
System.Object (object): la clase base fundamental en el sistema de tipos de CLR. Es la raíz de la jerarquía de
tipos.
System.Int16 (short): tipo entero con signo de 16 bits. También existe el valor UInt16 sin signo.
System.Int32 (int): tipo entero con signo de 32 bits. También existe el valor UInt32 sin firmar.
System.Single (float): tipo de punto flotante de 32 bits.
System.Decimal (decimal): tipo decimal de 128 bits.
System.Byte (byte): entero sin signo de 8 bits que representa un byte de memoria.
System.Boolean (bool): tipo booleano que representa true o false .
System.Char (char): tipo numérico de 16 bits que representa un carácter Unicode.
System.String (string): representa una serie de caracteres. Diferente de char[] , pero permite la indexación en
cada char individual en string .

Estructuras de datos
.NET incluye un conjunto de estructuras de datos que son la piedra angular de casi cualquier aplicación .NET.
Principalmente se trata de colecciones, pero también incluyen otros tipos.
Array: representa una matriz de objetos fuertemente tipados a la que se puede obtener acceso por índice. Tiene
un tamaño fijo debido a su construcción.
List<T>: representa una lista de objetos fuertemente tipados a la que se puede obtener acceso por índice.
Cambia automáticamente de tamaño según sea necesario.
Dictionary<TKey,TValue>: representa una colección de valores que se indexan mediante una clave. Se puede
obtener acceso a los valores a través de la clave. Cambia automáticamente de tamaño según sea necesario.
Uri: proporciona una representación de objeto de un identificador uniforme de recursos (URI) y un acceso
sencillo a las partes del identificador URI.
DateTime: representa un instante de tiempo, normalmente expresado en forma de fecha y hora del día.

API de utilidades
.NET incluye un conjunto de API de utilidades que proporcionan funcionalidad para muchas tareas importantes.
HttpClient: una API para enviar solicitudes HTTP y recibir respuestas HTTP de un recurso identificado por un URI.
XDocument: una API para cargar y consultar documentos XML con LINQ.
StreamReader: una API para leer archivos.
StreamWriter: una API para escribir archivos.

API del modelo de aplicaciones


Hay muchos modelos de aplicaciones que pueden usarse con .NET, proporcionados por varias empresas.
ASP.NET: proporciona un marco de trabajo web para la creación de sitios Web y servicios. Es compatible con
Windows, Linux y macOS (depende de la versión de ASP.NET).
Información general de la biblioteca de clases de
.NET
16/09/2020 • 9 minutes to read • Edit Online

Las implementaciones de .NET incluyen clases, interfaces, delegados y tipos de valor que agilizan y optimizan el
proceso de desarrollo y proporcionan acceso a las funciones del sistema. Para facilitar la interoperabilidad entre
lenguajes, la mayoría de los tipos de .NET son conformes a CLS y, por tanto, se pueden utilizar en todos los
lenguajes de programación cuyo compilador satisfaga los requisitos de CLS.
Los tipos de .NET son la base sobre la que se compilan aplicaciones, componentes y controles de .NET. Las
implementaciones de .NET incluyen tipos que efectúan las siguientes funciones:
Representar tipos de datos base y excepciones.
Encapsular estructuras de datos.
Realizar E/S.
Obtener acceso a información sobre tipos cargados.
Invocar las comprobaciones de seguridad de .NET Framework.
Proporcionar: acceso a datos, interfaz gráfica para el usuario (GUI) independiente de cliente e interfaz GUI de
cliente controlada por el servidor.
.NET proporciona un conjunto completo de interfaces, así como clases abstractas y concretas (no abstractas). Se
pueden utilizar las clases concretas tal como están o, en muchos casos, derivar las clases propias de ellas. Para usar
la funcionalidad de una interfaz se puede crear una clase que implemente la interfaz o derivar una clase de una de
las clases de .NET que implementa la interfaz.

Convenciones de nomenclatura
Los tipos de .NET usan un esquema de nomenclatura con sintaxis de punto lo que indica la existencia de una
jerarquía. Esta técnica agrupa tipos relacionados en espacios de nombres para que se pueda buscar y hacer
referencia a ellos más fácilmente. La primera parte del nombre completo, hasta el punto situado más a la derecha,
es el nombre del espacio de nombres. La última parte es el nombre de tipo. Por ejemplo,
System.Collections.Generic.List<T> representa el tipo List<T> , que pertenece al espacio de nombres
System.Collections.Generic . Los tipos de System.Collections.Generic se pueden usar para trabajar con colecciones
genéricas.
Este esquema de nomenclatura facilita a los desarrolladores de bibliotecas la tarea de extender .NET Framework
para crear grupos jerárquicos de tipos y asignarles nombre de forma coherente e informativa. También permite
identificar de forma inequívoca los tipos mediante su nombre completo (es decir, por su espacio de nombres y
nombre de tipo), lo que evita que se produzcan conflictos entre los nombres de tipo. Se espera que los
programadores de bibliotecas usen la siguiente convención cuando creen nombres para sus propios espacios de
nombres:
NombreCompañía.NombreTecnología
Por ejemplo, el espacio de nombres Microsoft.Word cumple esta directriz.
El uso de modelos de nomenclatura para agrupar tipos relacionados en espacios de nombres es una forma muy
útil de compilar y documentar bibliotecas de clases. Sin embargo, este esquema de nomenclatura no influye en la
visibilidad, el acceso a miembros, la herencia, la seguridad o el enlace. Se puede hacer la partición de un espacio de
nombres en varios ensamblados y un ensamblado individual puede contener tipos de varios espacios de nombres.
El ensamblado proporciona la estructura formal para el control de versiones, la implementación, la seguridad, la
carga y la visibilidad en Common Language Runtime.
Para obtener más información sobre espacios de nombres y nombres de tipos, vea Common Type System.

System (espacio de nombres)


El espacio de nombres System es el espacio de nombres de la raíz de los tipos fundamentales de .NET. Este espacio
de nombres contiene clases que representan los tipos de datos base que se utilizan en todas las aplicaciones:
Object (raíz de la jerarquía de herencia), Byte, Char, Array, Int32, String, etc. Muchos de estos tipos se corresponden
con los tipos de datos primitivos que utiliza el lenguaje de programación. Cuando se escribe código utilizando tipos
de .NET Framework se puede utilizar la palabra clave correspondiente del lenguaje cuando se espera un tipo de
datos base de .NET Framework.
En la tabla siguiente se muestra una lista de los tipos base que proporciona .NET, se describe brevemente cada tipo
y se indica el tipo correspondiente de Visual Basic, C#, C++ y F#.

T IP O DE T IP O DE
N O M B RE DE DATO S EN T IP O DE DATO S EN T IP O DE
C AT EGO RÍA L A C L A SE DESC RIP C IÓ N VISUA L B A SIC DATO S EN C # C ++/ C L I DATO S EN F #

Integer Byte Entero de 8 Byte byte unsigned byte


bits sin signo. char

SByte Entero de 8 SByte sbyte char sbyte


bits con signo. o bien
signed char
No es
conforme a
CLS.

Int16 Entero de 16 Shor t shor t shor t int16


bits con signo.

Int32 Entero de 32 Integer int int int


bits con signo.
o bien

long

Int64 Entero de 64 Long long __int64 int64


bits con signo.

UInt16 Entero de 16 UShor t ushor t unsigned uint16


bits sin signo. shor t

No es
conforme a
CLS.

UInt32 Entero de 32 UInteger uint unsigned int uint32


bits sin signo. o bien
unsigned
No es long
conforme a
CLS.
T IP O DE T IP O DE
N O M B RE DE DATO S EN T IP O DE DATO S EN T IP O DE
C AT EGO RÍA L A C L A SE DESC RIP C IÓ N VISUA L B A SIC DATO S EN C # C ++/ C L I DATO S EN F #

UInt64 Entero de 64 ULong ulong unsigned uint64


bits sin signo. __int64

No es
conforme a
CLS.

Punto flotante Single Número de Single float float float32


punto flotante o
(32 bits) de single
precisión
sencilla.

Double Número de Double double double float


punto flotante o
(64 bits) de double
doble
precisión.

Lógico Boolean Valor Boolean bool bool bool


booleano
(verdadero o
falso).

Otros Char Carácter Char char wchar_t char


Unicode (16
bits).

Decimal Valor decimal Decimal decimal Decimal decimal


(128 bits).

IntPtr Entero con IntPtr IntPtr IntPtr unativeint


signo cuyo
tamaño No dispone de No dispone de No dispone de
depende de la un tipo un tipo un tipo
plataforma integrado. integrado. integrado.
subyacente
(valor de 32
bits en una
plataforma de
32 bits y valor
de 64 bits en
una
plataforma de
64 bits).
T IP O DE T IP O DE
N O M B RE DE DATO S EN T IP O DE DATO S EN T IP O DE
C AT EGO RÍA L A C L A SE DESC RIP C IÓ N VISUA L B A SIC DATO S EN C # C ++/ C L I DATO S EN F #

UIntPtr Entero sin UIntPtr UIntPtr UIntPtr unativeint


signo cuyo
tamaño No dispone de No dispone de No dispone de
depende de la un tipo un tipo un tipo
plataforma integrado. integrado. integrado.
subyacente
(valor de 32
bits en una
plataforma de
32 bits y valor
de 64 bits en
una
plataforma de
64 bits).

No es
conforme a
CLS.

Object Base de la Objeto object Object^ obj


jerarquía de
objetos.

String Cadena String string String^ string


inmutable de
longitud fija
de caracteres
Unicode.

Además de los tipos de datos base, el espacio de nombres System contiene más de 100 clases, que comprenden
desde las clases que controlan excepciones hasta las clases que tratan conceptos básicos en tiempo de ejecución,
como los dominios de aplicación y el recolector de elementos no utilizados. El espacio de nombres System también
contiene muchos espacios de nombres de segundo nivel.
Para más información sobre los espacios de nombres, use el explorador de API de .NET para examinar la biblioteca
de clases .NET. La documentación de referencia de API proporciona información sobre cada espacio de nombres,
sus tipos y cada uno de sus miembros.

Vea también
Sistema de tipos comunes
Explorador de API de .NET
Información general
Elementos genéricos en .NET
16/09/2020 • 17 minutes to read • Edit Online

Los genéricos permiten personalizar un método, clase, estructura o interfaz con respecto a los datos precisos sobre
los que se actúa. Por ejemplo, en lugar de utilizar la clase Hashtable , que permite cualquier tipo de clave y valor,
puede utilizar la clase genérica Dictionary<TKey,TValue> y especificar el tipo permitido para la clave y el tipo
permitido para el valor. Entre las ventajas de los genéricos están una mayor reutilización del código y la seguridad
de tipos.

Definición y uso de genéricos


Los genéricos son clases, estructuras, interfaces y métodos que tienen marcadores de posición (parámetros de
tipo) para uno o varios de los tipos que almacenan o utilizan. Una clase de colección genérica puede usar un
parámetro de tipo como marcador de posición para el tipo de objetos que almacena; los parámetros de tipo
aparecen como los tipos de sus campos y los tipos de parámetros de sus métodos. Un método genérico puede
usar su parámetro de tipo como el tipo de su valor devuelto o como el tipo de uno de sus parámetros formales. El
código siguiente ilustra una definición de clase genérica simple.

generic<typename T>
public ref class Generics
{
public:
T Field;
};

public class Generic<T>


{
public T Field;
}

Public Class Generic(Of T)


Public Field As T

End Class

Cuando cree una instancia de una clase genérica, especifique los tipos reales que hay que sustituir para los
parámetros de tipo. Esto establece una nueva clase genérica, a la que se hace referencia como clase genérica
construida, con los tipos que elija sustituidos en todas partes en la que aparecen los parámetros de tipo. El
resultado es una clase con seguridad de tipos que se adapta a su elección de tipos, como se muestra en el código
siguiente.

static void Main()


{
Generics<String^>^ g = gcnew Generics<String^>();
g->Field = "A string";
//...
Console::WriteLine("Generics.Field = \"{0}\"", g->Field);
Console::WriteLine("Generics.Field.GetType() = {0}", g->Field->GetType()->FullName);
}
public static void Main()
{
Generic<string> g = new Generic<string>();
g.Field = "A string";
//...
Console.WriteLine("Generic.Field = \"{0}\"", g.Field);
Console.WriteLine("Generic.Field.GetType() = {0}", g.Field.GetType().FullName);
}

Public Shared Sub Main()


Dim g As New Generic(Of String)
g.Field = "A string"
'...
Console.WriteLine("Generic.Field = ""{0}""", g.Field)
Console.WriteLine("Generic.Field.GetType() = {0}", g.Field.GetType().FullName)
End Sub

Terminología de los genéricos


Los siguientes términos se utilizan para explicar los genéricos en .NET:
Una definición de tipo genérico es una clase, estructura o declaración de interfaz que funciona como una
plantilla, con marcadores de posición para los tipos que puede contener o utilizar. Por ejemplo, la clase
System.Collections.Generic.Dictionary<TKey,TValue> puede contener dos tipos: claves y valores. Dado que
una definición de tipo genérico es solo una plantilla, no se pueden crear instancias de una clase, estructura o
interfaz que sea una definición de tipo genérico.
Losparámetros de tipo genérico, o parámetros de tipo, son los marcadores de posición en una definición de
método o tipo genérico. El tipo genérico System.Collections.Generic.Dictionary<TKey,TValue> tiene dos
parámetros de tipo, TKey y TValue , que representan los tipos de sus claves y valores.
Un tipo genérico construido, o tipo construido, es el resultado de especificar los tipos para los parámetros
de tipo genérico de una definición de tipo genérico.
Un argumento de tipo genérico es cualquier tipo que se sustituye por un parámetro de tipo genérico.
El término general tipo genérico incluye tanto tipos construidos como definiciones de tipo genérico.
La covarianza y contravarianza de parámetros de tipo genérico permite usar tipos genéricos construidos,
cuyos argumentos de tipo están más derivados (covarianza) o menos derivados (contravarianza) que un
tipo construido de destino. La covarianza y la contravarianza se denominan colectivamente varianza. Para
obtener más información, vea Covarianza y contravarianza.
Lasrestricciones son límites colocados en parámetros de tipo genérico. Por ejemplo, puede limitar un
parámetro de tipo a tipos que implementan la interfaz genérica System.Collections.Generic.IComparer<T>
para asegurarse de que se pueden ordenar las instancias del tipo. También puede restringir los parámetros
de tipo a tipos que tienen una clase base concreta, un constructor sin parámetros o que son tipos de
referencia o tipos de valor. Los usuarios del tipo genérico no pueden sustituir los argumentos de tipo que
no cumplen con las restricciones.
Una definición de método genérico es un método con dos listas de parámetros: una lista de parámetros de
tipo genérico y una lista de parámetros formales. Los parámetros de tipo pueden aparecer como el tipo de
valor devuelto o como los tipos de los parámetros formales, como se muestra en el siguiente código.
generic<typename T>
T Generic(T arg)
{
T temp = arg;
//...
return temp;
}

T Generic<T>(T arg)
{
T temp = arg;
//...
return temp;
}

Function Generic(Of T)(ByVal arg As T) As T


Dim temp As T = arg
'...
Return temp
End Function

Los métodos genéricos pueden aparecer en tipos genéricos o no genéricos. Es importante tener en cuenta que un
método no es genérico solo porque pertenezca a un tipo genérico, ni siquiera porque tenga parámetros formales
cuyos tipos sean los parámetros genéricos del tipo envolvente. Un método solo es genérico si tiene su propia lista
de parámetros de tipo. En el siguiente código, solo es genérico el método G .

ref class A
{
generic<typename T>
T G(T arg)
{
T temp = arg;
//...
return temp;
}
};
generic<typename T>
ref class Generic
{
T M(T arg)
{
T temp = arg;
//...
return temp;
}
};
class A
{
T G<T>(T arg)
{
T temp = arg;
//...
return temp;
}
}
class Generic<T>
{
T M(T arg)
{
T temp = arg;
//...
return temp;
}
}

Class A
Function G(Of T)(ByVal arg As T) As T
Dim temp As T = arg
'...
Return temp
End Function
End Class
Class Generic(Of T)
Function M(ByVal arg As T) As T
Dim temp As T = arg
'...
Return temp
End Function
End Class

Ventajas y desventajas de los genéricos


Usar delegados y colecciones genéricas ofrece muchas ventajas:
Seguridad de tipos. Los genéricos trasladan al compilador la carga de la seguridad de tipos. No es necesario
escribir código para comprobar el tipo de datos correcto porque se hace en tiempo de compilación. Se
reduce la necesidad de conversión de tipos y la posibilidad de errores en tiempo de ejecución.
Menos código que, además, se reutiliza más fácilmente. No es necesario heredar de un tipo base y
reemplazar a los miembros. Por ejemplo, LinkedList<T> está listo para su uso inmediato. Por ejemplo,
puede crear una lista vinculada de cadenas con la siguiente declaración de variable:

LinkedList<String^>^ llist = gcnew LinkedList<String^>();

LinkedList<string> llist = new LinkedList<string>();

Dim llist As New LinkedList(Of String)()

Mejor rendimiento. Los tipos de colección genéricos suelen comportarse mejor en el almacenamiento y
manipulación de tipos de valor porque no es necesario realizar una conversión boxing de los tipos de valor.
Los delegados genéricos permiten devoluciones de llamada con seguridad de tipos sin necesidad de crear
varias clases de delegado. Por ejemplo, el delegado genérico Predicate<T> le permite crear un método que
implementa sus propios criterios de búsqueda para un tipo concreto y utilizar su método con métodos del
tipo Array , como Find, FindLasty FindAll.
Los genéricos optimizan el código generado dinámicamente. Cuando se utilizan genéricos con código
generado dinámicamente, no es necesario generar el tipo. Esto aumenta el número de escenarios en los que
puede utilizar métodos dinámicos ligeros en lugar de generar ensamblados completos. Para obtener más
información, vea Cómo: Definir y ejecutar métodos dinámicos y DynamicMethod.
Las siguientes son algunas limitaciones de los genéricos:
Los tipos genéricos pueden derivarse de la mayoría de las clases base, como MarshalByRefObject (y pueden
utilizarse restricciones para exigir que los parámetros de tipo genérico se deriven de clases base como
MarshalByRefObject). Sin embargo, .NET Framework no admite los tipos genéricos enlazados a un contexto.
Un tipo genérico puede derivarse de ContextBoundObject, pero al intentar crear una instancia de dicho tipo,
se genera una TypeLoadException.
Las enumeraciones no pueden tener parámetros de tipo genérico. Una enumeración solo puede ser
genérica a propósito (por ejemplo, porque está anidada en un tipo genérico definido mediante Visual Basic,
C# o C++). Para más información, vea "Enumeraciones" en Common Type System.
Los métodos dinámicos ligeros no pueden ser genéricos.
En Visual Basic, C# y C++, no se pueden crear instancias de un tipo anidado incluido en un tipo genérico, a
menos que los tipos se hayan asignado a los parámetros de tipo de todos los tipos envolventes. Dicho de
otro modo: en la reflexión, un tipo anidado definido mediante estos lenguajes incluye los parámetros de
tipo de todos sus tipos envolventes. Esto permite que los parámetros de tipo de tipos envolventes se usen
en las definiciones de miembro de un tipo anidado. Para obtener más información, vea “Tipos anidados” en
MakeGenericType.

NOTE
Un tipo anidado definido mediante la emisión de código en un ensamblado dinámico o mediante el uso de Ilasm.exe
(Ensamblado de IL) no necesita incluir los parámetros de tipo de sus tipos envolventes. Sin embargo, si no los incluye,
los parámetros de tipo no estarán en el ámbito de la clase anidada.

Para obtener más información, vea “Tipos anidados” en MakeGenericType.

Biblioteca de clases y lenguajes compatibles


.NET ofrece una serie de clases de colección genérica en los espacios de nombres siguientes:
El espacio de nombres System.Collections.Generic contiene la mayoría de los tipos de colección genéricos
proporcionados por .NET, como las clases genéricas List<T> y Dictionary<TKey,TValue>.
El espacio de nombres System.Collections.ObjectModel contiene tipos de colección genéricos adicionales,
como la clase genérica ReadOnlyCollection<T>, que son útiles para exponer modelos de objetos a los
usuarios de las clases.
Las interfaces genéricas para implementar comparaciones de orden e igualdad se proporcionan en el espacio de
nombres System , junto con los tipos de delegado genérico para controladores de eventos, conversiones y
predicados de búsqueda.
Se ha agregado compatibilidad con genéricos a los siguientes espacios de nombres: a System.Reflection para
examinar tipos y métodos genéricos; a System.Reflection.Emit para emitir ensamblados dinámicos que contengan
tipos y métodos genéricos; y a System.CodeDom para generar gráficos de origen que incluyan genéricos.
Common language runtime proporciona nuevos opcode y prefijos para admitir tipos genéricos en el lenguaje
intermedio de Microsoft (MSIL), incluidos Stelem, Ldelem, Unbox_Any, Constrainedy Readonly.
Visual C++, C# y Visual Basic proporcionan compatibilidad completa para definir y utilizar genéricos. Para más
información sobre la compatibilidad de lenguaje, consulte Tipos genéricos en Visual Basic, Introducción a los
genéricos e Información general sobre genéricos en Visual C++.

Genéricos y tipos anidados


Un tipo que está anidado en un tipo genérico puede depender de los parámetros de tipo del tipo genérico
envolvente. Common language runtime considera que los tipos anidados son genéricos, aunque no tengan sus
propios parámetros de tipo genérico. Cuando cree una instancia de un tipo anidado, especifique los argumentos
de tipo para todos los tipos genéricos envolventes.

Temas relacionados
T IT L E DESC RIP C IÓ N

Colecciones genéricas en .NET Describe las clases de colección genérica y otros tipos
genéricos en .NET.

Delegados genéricos para manipular matrices y listas Describe los delegados genéricos para conversiones,
predicados de búsqueda y acciones realizadas en los
elementos de una matriz o colección.

Interfaces genéricas Describe las interfaces genéricas que proporcionan


funcionalidad común entre las familias de tipos genéricos.

Covarianza y contravarianza Describe la covarianza y la contravarianza en los parámetros


de tipo genérico.

Tipos de colección utilizados normalmente Proporciona información de resumen sobre las características
y escenarios de uso de los tipos de colección en .NET, incluidos
los tipos genéricos.

Cuándo utilizar colecciones genéricas Describe las reglas generales para determinar cuándo utilizar
los tipos de colección genéricos.

Cómo: Definir un tipo genérico con emisión de reflexión Explica cómo generar ensamblados dinámicos que incluyan
métodos y tipos genéricos.

Tipos genéricos en Visual Basic Describe los genéricos a los usuarios de Visual Basic e incluye
temas sobre el uso y la definición de tipos genéricos.

Introducción a los genéricos Proporciona información general sobre la definición y uso de


tipos genéricos para usuarios de C#.

Información general sobre genéricos en Visual C++ Describe los genéricos a los usuarios de C++ e incluye las
diferencias entre genéricos y plantillas.

Referencia
System.Collections.Generic
System.Collections.ObjectModel
System.Reflection.Emit.OpCodes
Información general de tipos genéricos
16/09/2020 • 5 minutes to read • Edit Online

Los desarrolladores utilizan genéricos en todo momento en .NET, ya sea implícita o explícitamente. Al usar LINQ en
.NET, ¿alguna vez observó que estaba trabajando con IEnumerable<T>? O, si alguna vez ha visto un ejemplo en
línea de un "repositorio genérico" para comunicarse con bases de datos mediante Entity Framework, ha observado
que la mayoría de los métodos devuelven IQueryable<T> ? Probablemente se pregunte qué significa la T en estos
ejemplos y por qué aparece.
Introducidos por primera vez en .NET Framework 2.0, los genéricos son esencialmente una "plantilla de código"
que permite a los desarrolladores definir estructuras de datos con seguridad de tipos sin confirmar un tipo de
datos real. Por ejemplo, List<T> es una colección genérica que se puede declarar y usar con cualquier tipo, como
List<int> , List<string> o List<Person> .

Para entender por qué los genéricos son útiles, vamos a echar un vistazo a una clase específica antes y después de
agregar genéricos: ArrayList. En .NET Framework 1.0, los elementos ArrayList eran de tipo Object. Cualquier
elemento agregado a la colección se convertía desapercibidamente en un elemento Object . Lo mismo podría
suceder al leer elementos de la lista. Este proceso se conoce como conversión boxing y unboxing, y afecta al
rendimiento. Sin embargo, además del rendimiento, no hay ninguna manera de determinar el tipo de datos de la
lista en tiempo de compilación, lo que hace que algunos códigos sean frágiles. Los genéricos solucionan este
problema definiendo el tipo de datos que va a contener cada instancia de la lista. Por ejemplo, solo puede agregar
enteros a List<int> y solo puede agregar personas a List<Person> .
Los genéricos también están disponibles en tiempo de ejecución. El tiempo de ejecución conoce qué tipo de
estructura de datos está usando y puede almacenarla en memoria de forma más eficaz.
El ejemplo siguiente es un pequeño programa que muestra la eficacia que supone conocer el tipo de estructura de
datos en tiempo de ejecución:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace GenericsExample {
class Program {
static void Main(string[] args) {
//generic list
List<int> ListGeneric = new List<int> { 5, 9, 1, 4 };
//non-generic list
ArrayList ListNonGeneric = new ArrayList { 5, 9, 1, 4 };
// timer for generic list sort
Stopwatch s = Stopwatch.StartNew();
ListGeneric.Sort();
s.Stop();
Console.WriteLine($"Generic Sort: {ListGeneric} \n Time taken: {s.Elapsed.TotalMilliseconds}ms");

//timer for non-generic list sort


Stopwatch s2 = Stopwatch.StartNew();
ListNonGeneric.Sort();
s2.Stop();
Console.WriteLine($"Non-Generic Sort: {ListNonGeneric} \n Time taken:
{s2.Elapsed.TotalMilliseconds}ms");
Console.ReadLine();
}
}
}

Este programa genera un resultado similar al siguiente:

Generic Sort: System.Collections.Generic.List`1[System.Int32]


Time taken: 0.0034ms
Non-Generic Sort: System.Collections.ArrayList
Time taken: 0.2592ms

Lo primero que verá aquí es que la ordenación de la lista genérica es significativamente más rápida que la de la
lista no genérica. También puede observar que el tipo de la lista genérica es distinto ([System.Int32]), mientras que
el tipo de la lista no genérica es generalizado. Dado que el tiempo de ejecución sabe que el genérico List<int> es
de tipo Int32, puede almacenar los elementos de la lista en una matriz de enteros subyacente en memoria,
mientras el no genérico ArrayList tiene que convertir cada elemento de la lista en un objeto. Como se muestra en
este ejemplo, las conversiones adicionales consumen tiempo y ralentizan la ordenación de la lista.
Otra ventaja adicional de que el tiempo de ejecución conozca el tipo de la clase genérica es una mejor experiencia
de depuración. Cuando se depura un genérico en C#, sabe qué tipo de cada elemento se encuentra en la estructura
de datos. Sin genéricos, no sabría qué tipo de cada elemento estaba presente.

Vea también
Guía de programación de C#: genéricos
Colecciones genéricas en .NET
16/09/2020 • 3 minutes to read • Edit Online

La biblioteca de clases de .NET ofrece varias clases de colección genéricas en los espacios de nombres
System.Collections.Generic y System.Collections.ObjectModel. Para obtener información más detallada sobre estas
clases, vea Tipos de colección utilizados normalmente.

System.Collections.Generic
Muchos de los tipos de colección genéricos son análogos directos de tipos no genéricos. Dictionary<TKey,TValue>
es una versión genérica de Hashtable; usa la estructura genérica KeyValuePair<TKey,TValue> para la enumeración
en lugar de DictionaryEntry.
List<T> es una versión genérica de ArrayList. Hay clases Queue<T> y Stack<T> genéricas que se corresponden
con las versiones no genéricas.
Hay versiones genéricas y no genéricas de SortedList<TKey,TValue>. Ambas versiones son híbridos de un
diccionario y una lista. La clase genérica SortedDictionary<TKey,TValue> es un diccionario puro y no tiene ninguna
homóloga no genérica.
La clase genérica LinkedList<T> es una lista vinculada genuina. No tiene ninguna homóloga no genérica.

System.Collections.ObjectModel
La clase genérica Collection<T> proporciona una clase base para derivar sus propios tipos de colección genéricos.
La clase ReadOnlyCollection<T> proporciona una manera sencilla de generar una colección de solo lectura de
cualquier tipo que implementa la interfaz genérica IList<T>. La clase genérica KeyedCollection<TKey,TItem>
proporciona una manera de almacenar objetos que contienen sus propias claves.

Otros tipos genéricos


La estructura genérica Nullable<T> permite usar tipos de valor como si se les pudiera asignar el valor null . Esto
puede ser útil para trabajar con consultas de base de datos en las que pueden faltar campos que contienen tipos
de valor. El parámetro de tipo genérico puede ser cualquier tipo de valor.

NOTE
En C# y Visual Basic no hay que usar Nullable<T> explícitamente porque el lenguaje tiene sintaxis para tipos que aceptan
valores NULL. Consulte Tipos de valor que admiten un valor NULL (referencia de C#) y Tipos de valor que admiten un valor
NULL (Visual Basic).

La estructura genérica ArraySegment<T> proporciona una manera de delimitar un intervalo de elementos en una
matriz unidimensional basada en cero de cualquier tipo. El parámetro de tipo genérico es el tipo de los elementos
de la matriz.
El delegado genérico EventHandler<TEventArgs> elimina la necesidad de declarar un tipo de delegado para
controlar los eventos, siempre que el evento siga el patrón de control de eventos que usa .NET Framework. Por
ejemplo, supongamos que ha creado una clase MyEventArgs , derivada de EventArgs, para contener los datos del
evento. Puede declarar el evento de la siguiente manera:
public:
event EventHandler<MyEventArgs^>^ MyEvent;

public event EventHandler<MyEventArgs> MyEvent;

Public Event MyEvent As EventHandler(Of MyEventArgs)

Vea también
System.Collections.Generic
System.Collections.ObjectModel
Genéricos
Delegados genéricos para manipular matrices y listas
Interfaces genéricas
Delegados genéricos para manipular matrices y listas
16/09/2020 • 5 minutes to read • Edit Online

En este tema se ofrece una introducción a los delegados genéricos para conversiones, predicados de búsqueda y
acciones realizadas en los elementos de una matriz o colección.

Delegados genéricos para manipular matrices y listas


El delegado genérico Action<T> representa un método que realiza alguna acción en un elemento del tipo
especificado. Puede crear un método que realiza la acción deseada en el elemento, crear una instancia del
delegado Action<T> para que represente ese método y, después, pasar la matriz y el delegado al método genérico
estático Array.ForEach. Se llama al método para cada elemento de la matriz.
La clase genérica List<T> también proporciona un método ForEach que usa el delegado Action<T>. Este método
no es genérico.

NOTE
Este es un aspecto interesante de los tipos y métodos genéricos. El método Array.ForEach debe ser estático ( Shared en
Visual Basic) y genérico porque Array no es un tipo genérico; la única razón por la que se puede especificar un tipo para que
Array.ForEach opere sobre él es que el método tiene su propia lista de parámetros de tipo. Por el contrario, el método no
genérico List<T>.ForEach pertenece a la clase genérica List<T>, por lo que simplemente utiliza el parámetro de tipo de su
clase. La clase está fuertemente tipada, por lo que el método puede ser un método de instancia.

El delegado genérico Predicate<T> representa un método que determina si un determinado elemento cumple los
criterios definidos. Puede usar los siguientes métodos genéricos estáticos de Array para buscar un elemento o un
conjunto de elementos: Exists, Find, FindAll, FindIndex, FindLast, FindLastIndex y TrueForAll.
Predicate<T> también trabaja con los correspondientes métodos de instancia no genéricos de la clase genérica
List<T>.
El delegado genérico Comparison<T> permite proporcionar un criterio de ordenación para los elementos de la
matriz o la lista que no tienen un criterio de ordenación nativo, o para reemplazar el criterio de ordenación nativo.
Cree un método que realice la comparación, cree una instancia del delegado Comparison<T> para representar el
método y, después, pase la matriz y el delegado al método genérico estático Array.Sort<T>(T[], Comparison<T>).
La clase genérica List<T> proporciona una sobrecarga del método de instancia correspondiente,
List<T>.Sort(Comparison<T>).
El delegado genérico Converter<TInput,TOutput> permite definir una conversión entre dos tipos y convertir una
matriz de un tipo en una matriz del otro, o convertir una lista de un tipo a una lista del otro. Cree un método que
convierta los elementos de la lista existente a un nuevo tipo, cree una instancia de delegado para representar el
método y use el método estático genérico Array.ConvertAll para producir una matriz del nuevo tipo a partir de la
matriz original, o el método de instancia genérico List<T>.ConvertAll<TOutput>(Converter<T,TOutput>) para
producir una lista del nuevo tipo a partir de la lista original.
Encadenar delegados
Muchos de los métodos que usan estos delegados devuelven una matriz o una lista, que se puede pasar a otro
método. Por ejemplo, si quiere seleccionar determinados elementos de una matriz, convertirlos a un nuevo tipo y
guardarlos en una nueva matriz, puede pasar la matriz devuelta por el método genérico FindAll al método
genérico ConvertAll. Si el nuevo tipo de elemento carece de un criterio de ordenación natural, puede pasar la
matriz devuelta por el método genérico ConvertAll al método genérico Sort<T>(T[], Comparison<T>).
Vea también
System.Collections.Generic
System.Collections.ObjectModel
Genéricos
Colecciones genéricas en .NET Framework
Interfaces genéricas
Covarianza y contravarianza
Interfaces genéricas
16/09/2020 • 4 minutes to read • Edit Online

Este tema ofrece una introducción a las interfaces genéricas que proporcionan funcionalidad común a distintas
familias de tipos genéricos.

Interfaces genéricas
Las interfaces genéricas proporcionan homólogas con seguridad de tipos a las interfaces no genéricas para
realizar comparaciones de ordenación y de igualdad, y para obtener funcionalidad compartida por los tipos de
colección genéricos.

NOTE
A partir de .NET Framework 4, los parámetros de tipo de varias interfaces genéricas están marcados como covariantes o
contravariantes, y ofrecen una mayor flexibilidad para asignar y usar tipos que implementan estas interfaces. Vea Covarianza
y contravarianza.

Comparaciones de igualdad y ordenación


En el espacio de nombres System, las interfaces genéricas System.IComparable<T> y System.IEquatable<T>, igual
que sus homólogas no genéricas, definen métodos para realizar comparaciones de ordenación y comparaciones
de igualdad, respectivamente. Los tipos implementan estas interfaces para proporcionar la capacidad de
desempeñar dichas comparaciones.
En el espacio de nombres System.Collections.Generic, las interfaces genéricas IComparer<T> y
IEqualityComparer<T> ofrecen una manera de definir una comparación de ordenación o de igualdad para los
tipos que no implementan las interfaces genéricas System.IComparable<T> o System.IEquatable<T> y
proporcionan una manera de redefinir esas relaciones para los tipos que sí lo hacen. Estas interfaces son usadas
por métodos y constructores de muchas de las clases de colección genéricas. Por ejemplo, puede pasar un objeto
genérico IComparer<T> al constructor de la clase SortedDictionary<TKey,TValue> para especificar un criterio de
ordenación para un tipo que no implementa una interfaz System.IComparable<T> genérica. Hay sobrecargas del
método estático genérico Array.Sort y el método de instancia List<T>.Sort para ordenar matrices y listas usando
implementaciones genéricas IComparer<T>.
Las clases genéricas Comparer<T> y EqualityComparer<T> proporcionan clases base para las implementaciones
de las interfaces genéricas IComparer<T> y IEqualityComparer<T>, y también proporcionan comparaciones de
ordenación y de igualdad predeterminadas mediante sus respectivas propiedades Comparer<T>.Default y
EqualityComparer<T>.Default.
Funcionalidad de colección
La interfaz genérica ICollection<T> es la interfaz básica para los tipos de colección genéricos. Proporciona la
funcionalidad básica para agregar, quitar, copiar y enumerar elementos. ICollection<T> hereda tanto de la interfaz
genérica IEnumerable<T> como de la interfaz no genérica IEnumerable.
La interfaz genérica IList<T> extiende la interfaz genérica ICollection<T> con métodos para la recuperación
indizada.
La interfaz genérica IDictionary<TKey,TValue> extiende la interfaz genérica ICollection<T> con métodos para la
recuperación con clave. Los tipos de diccionario genéricos de la biblioteca de clases base de .NET Framework
también implementan la interfaz no genérica IDictionary.
La interfaz genérica IEnumerable<T> proporciona una estructura de enumerador genérica. La interfaz genérica
IEnumerator<T> implementada por enumeradores genéricos hereda la interfaz no genérica IEnumerator; los
miembros MoveNext y Reset, que no dependen del parámetro de tipo T , solo aparecen en la interfaz no genérica.
Esto significa que cualquier consumidor de la interfaz no genérica también puede usar la interfaz genérica.

Vea también
System.Collections.Generic
System.Collections.ObjectModel
Genéricos
Colecciones genéricas en .NET Framework
Delegados genéricos para manipular matrices y listas
Covarianza y contravarianza
Covarianza y contravarianza en genéricos
16/09/2020 • 26 minutes to read • Edit Online

Covarianza y contravarianza son términos que hacen referencia a la capacidad de usar un tipo más derivado (más
específico) o menos derivado (menos específico) que el indicado originalmente. Los parámetros de tipo genérico
admiten la covarianza y contravarianza para proporcionar mayor flexibilidad a la hora de asignar y usar tipos
genéricos. Cuando se hace referencia a un sistema de tipos, la covarianza, contravarianza e invarianza tienen las
siguientes definiciones. En el ejemplo se presupone una clase base denominada Base y una clase derivada
denominada Derived .
Covariance

Permite usar un tipo más derivado que el especificado originalmente.


Puede asignar una instancia de IEnumerable<Derived> ( IEnumerable(Of Derived) en Visual Basic) a una
variable de tipo IEnumerable<Base> .
Contravariance

Permite usar un tipo más genérico (menos derivado) que el especificado originalmente.
Puede asignar una instancia de Action<Base> ( Action(Of Base) en Visual Basic) a una variable de tipo
Action<Derived> .

Invariance

Significa que solo se puede usar el tipo especificado originalmente. Así, un parámetro de tipo genérico
invariable no es covariante ni contravariante.
No se puede asignar una instancia de List<Base> ( List(Of Base) en Visual Basic) a una variable de tipo
List<Derived> o viceversa.

Los parámetros de tipo covariante permiten realizar asignaciones muy similares al polimorfismo, como se
muestra en el código siguiente.

IEnumerable<Derived> d = new List<Derived>();


IEnumerable<Base> b = d;

Dim d As IEnumerable(Of Derived) = New List(Of Derived)


Dim b As IEnumerable(Of Base) = d

La clase List<T> implementa la interfaz IEnumerable<T> , por lo que List<Derived> ( List(Of Derived) en Visual
Basic) implementa IEnumerable<Derived> . El parámetro de tipo covariante se encarga del resto.
La contravarianza, sin embargo, parece poco intuitiva. En el siguiente ejemplo, se crea un delegado de tipo
Action<Base> ( Action(Of Base) en Visual Basic) y, a continuación, se asigna ese delegado a una variable de tipo
Action<Derived> .

Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };


Action<Derived> d = b;
d(new Derived());
Dim b As Action(Of Base) = Sub(target As Base)
Console.WriteLine(target.GetType().Name)
End Sub
Dim d As Action(Of Derived) = b
d(New Derived())

Parece un paso hacia atrás, pero lo que se compila y se ejecuta es código con seguridad de tipos. La expresión
lambda se corresponde con el delegado que tiene asignado, por lo que define un método que toma un parámetro
de tipo Base y no tiene ningún valor devuelto. El delegado resultante puede asignarse a una variable de tipo
Action<Derived> porque el parámetro de tipo T del delegado Action<T> es contravariante. El código tiene
seguridad de tipos porque T especifica un tipo de parámetro. Cuando se invoca el delegado de tipo
Action<Base> como si fuera un delegado de tipo Action<Derived> , su argumento debe ser de tipo Derived . Este
argumento siempre se puede pasar de manera segura al método subyacente porque el parámetro del método es
de tipo Base .
En general, los parámetros de tipo covariante se pueden utilizar como tipos de valor devuelto de un delegado, y
los parámetros de tipo contravariante se pueden usar como tipos de parámetro. En el caso de una interfaz, los
parámetros de tipo covariante se pueden utilizar como tipos de valor devuelto de los métodos de la interfaz, y los
parámetros de tipo contravariante se pueden usar como tipos de parámetro de los métodos de la interfaz.
La covarianza y la contravarianza se denominan colectivamente varianza. Un parámetro de tipo genérico que no
está marcado como covariante ni contravariante se denomina invariable. Un breve resumen de hechos
relacionados con la varianza en Common Language Runtime:
En .NET Framework 4, los parámetros de tipo variante están restringidos a los tipos de interfaz genérica y
delegado genérico.
Un tipo de interfaz genérica o de delegado genérico puede tener parámetros de tipo covariante y
contravariante.
La varianza se aplica únicamente a los tipos de referencia; si se especifica un tipo de valor para un
parámetro de tipo variante, ese parámetro de tipo es invariable para el tipo construido resultante.
La varianza no se aplica a la combinación de delegados. Es decir, si hay dos delegados de tipo
Action<Derived> y de tipo Action<Base> ( Action(Of Derived) y Action(Of Base) en Visual Basic), no se
puede combinar el segundo delegado con el primero aunque el resultado tuviese seguridad de tipos. La
varianza permite la asignación del segundo delegado a una variable de tipo Action<Derived> , pero los
delegados solo se pueden combinar si tienen exactamente el mismo tipo.

Interfaces genéricas con parámetros de tipo covariante


A partir de .NET Framework 4, varias interfaces genéricas tienen parámetros de tipo covariante; por ejemplo,
IEnumerable<T>, IEnumerator<T>, IQueryable<T> y IGrouping<TKey,TElement>. Todos los parámetros de tipo
de estas interfaces son covariantes, por lo que los parámetros de tipo se usan únicamente para los tipos de valor
devuelto de los miembros.
En el ejemplo siguiente, se muestran los parámetros de tipo covariante. Se definen dos tipos: Base tiene un
método estático denominado PrintBases que toma una interfaz IEnumerable<Base> ( IEnumerable(Of Base) en
Visual Basic) e imprime los elementos. Derived hereda de Base . En el ejemplo, se crea un tipo List<Derived> (
List(Of Derived) en Visual Basic) vacío y se muestra que este tipo se puede pasar a PrintBases y asignar a una
variable de tipo IEnumerable<Base> sin conversión alguna. List<T> implementa IEnumerable<T>, que tiene un
solo parámetro de tipo covariante. El parámetro de tipo covariante es el motivo por el cual se puede usar una
instancia de IEnumerable<Derived> en lugar de IEnumerable<Base> .
using System;
using System.Collections.Generic;

class Base
{
public static void PrintBases(IEnumerable<Base> bases)
{
foreach(Base b in bases)
{
Console.WriteLine(b);
}
}
}

class Derived : Base


{
public static void Main()
{
List<Derived> dlist = new List<Derived>();

Derived.PrintBases(dlist);
IEnumerable<Base> bIEnum = dlist;
}
}

Imports System.Collections.Generic

Class Base
Public Shared Sub PrintBases(ByVal bases As IEnumerable(Of Base))
For Each b As Base In bases
Console.WriteLine(b)
Next
End Sub
End Class

Class Derived
Inherits Base

Shared Sub Main()


Dim dlist As New List(Of Derived)()

Derived.PrintBases(dlist)
Dim bIEnum As IEnumerable(Of Base) = dlist
End Sub
End Class

Interfaces genéricas con parámetros de tipo genérico contravariante


A partir de .NET Framework 4, varias interfaces genéricas tienen parámetros de tipo contravariante; por ejemplo,
IComparer<T>, IComparable<T> y IEqualityComparer<T>. Estas interfaces tienen únicamente parámetros de
tipo contravariante, por lo que los parámetros de tipo se utilizan solamente como tipos de parámetro en los
miembros de las interfaces.
En el ejemplo siguiente se muestran los parámetros de tipo contravariante. En el ejemplo se define clase abstracta
MustInherit ( Shape en Visual Basic) con una propiedad Area . En el ejemplo también se define una clase
ShapeAreaComparer que implementa IComparer<Shape> ( IComparer(Of Shape) en Visual Basic). La implementación
del método IComparer<T>.Compare se basa en el valor de la propiedad Area , por lo que ShapeAreaComparer se
puede usar para ordenar los objetos Shape por área.
La clase Circle hereda Shape e invalida Area . En el ejemplo se crea una colección SortedSet<T> de objetos
Circle , usando un constructor que toma IComparer<Circle> ( IComparer(Of Circle) en Visual Basic). Sin
embargo, en lugar de pasar IComparer<Circle> , en el ejemplo se pasa un objeto ShapeAreaComparer , que
implementa IComparer<Shape> . En el ejemplo se puede pasar un comparador de un tipo menos derivado ( Shape )
cuando el código llama a un comparador de un tipo más derivado ( Circle ), ya que el parámetro de tipo de la
interfaz genérica IComparer<T> es contravariante.
Cuando se agrega un nuevo objeto Circle a SortedSet<Circle> , se llama al método IComparer<Shape>.Compare (
IComparer(Of Shape).Compare en Visual Basic) del objeto ShapeAreaComparer cada vez que el nuevo elemento se
compara con un elemento existente. El tipo de parámetro del método ( Shape ) es menos derivado que el tipo que
se pasa ( Circle ), por lo que la llamada tiene seguridad de tipos. La contravarianza permite a ShapeAreaComparer
ordenar una colección de cualquier tipo único, así como a una colección mixta de tipos, que derivan de Shape .

using System;
using System.Collections.Generic;

abstract class Shape


{
public virtual double Area { get { return 0; }}
}

class Circle : Shape


{
private double r;
public Circle(double radius) { r = radius; }
public double Radius { get { return r; }}
public override double Area { get { return Math.PI * r * r; }}
}

class ShapeAreaComparer : System.Collections.Generic.IComparer<Shape>


{
int IComparer<Shape>.Compare(Shape a, Shape b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : a.Area.CompareTo(b.Area);
}
}

class Program
{
static void Main()
{
// You can pass ShapeAreaComparer, which implements IComparer<Shape>,
// even though the constructor for SortedSet<Circle> expects
// IComparer<Circle>, because type parameter T of IComparer<T> is
// contravariant.
SortedSet<Circle> circlesByArea =
new SortedSet<Circle>(new ShapeAreaComparer())
{ new Circle(7.2), new Circle(100), null, new Circle(.01) };

foreach (Circle c in circlesByArea)


{
Console.WriteLine(c == null ? "null" : "Circle with area " + c.Area);
}
}
}

/* This code example produces the following output:

null
Circle with area 0.000314159265358979
Circle with area 162.860163162095
Circle with area 31415.9265358979
*/
Imports System.Collections.Generic

MustInherit Class Shape


Public MustOverride ReadOnly Property Area As Double
End Class

Class Circle
Inherits Shape

Private r As Double
Public Sub New(ByVal radius As Double)
r = radius
End Sub
Public ReadOnly Property Radius As Double
Get
Return r
End Get
End Property
Public Overrides ReadOnly Property Area As Double
Get
Return Math.Pi * r * r
End Get
End Property
End Class

Class ShapeAreaComparer
Implements System.Collections.Generic.IComparer(Of Shape)

Private Function AreaComparer(ByVal a As Shape, ByVal b As Shape) As Integer _


Implements System.Collections.Generic.IComparer(Of Shape).Compare
If a Is Nothing Then Return If(b Is Nothing, 0, -1)
Return If(b Is Nothing, 1, a.Area.CompareTo(b.Area))
End Function
End Class

Class Program
Shared Sub Main()
' You can pass ShapeAreaComparer, which implements IComparer(Of Shape),
' even though the constructor for SortedSet(Of Circle) expects
' IComparer(Of Circle), because type parameter T of IComparer(Of T)
' is contravariant.
Dim circlesByArea As New SortedSet(Of Circle)(New ShapeAreaComparer()) _
From {New Circle(7.2), New Circle(100), Nothing, New Circle(.01)}

For Each c As Circle In circlesByArea


Console.WriteLine(If(c Is Nothing, "Nothing", "Circle with area " & c.Area))
Next
End Sub
End Class

' This code example produces the following output:


'
'Nothing
'Circle with area 0.000314159265358979
'Circle with area 162.860163162095
'Circle with area 31415.9265358979

Delegados genéricos con parámetros de tipo variante


En .NET Framework 4, los delegados genéricos Func , como Func<T,TResult>, tienen tipos de valor devuelto
covariante y tipos de parámetro contravariante. Los delegados genéricos Action , como Action<T1,T2>, tienen
tipos de parámetro contravariante. Esto significa que los delegados se pueden asignar a variables que tengan
tipos de parámetro más derivados y (en el caso de los delegados genéricos Func ) tipos de valor devuelto menos
derivados.
NOTE
El último parámetro de tipo genérico de los delegados genéricos Func especifica el tipo del valor devuelto en la firma de
delegado. Es covariante (palabra clave out ), mientras que los otros parámetros de tipo genérico son contravariante
(palabra clave in ).

Esto se ilustra en el código siguiente: En el primer fragmento de código, se definen una clase denominada Base ,
una clase denominada Derived que hereda de Base y otra clase con un método static ( Shared en Visual
Basic) denominado MyMethod . El método toma una instancia de Base y devuelve una instancia de Derived . (Si el
argumento es una instancia de Derived , MyMethod la devuelve; si el argumento es una instancia de Base ,
MyMethod devuelve una nueva instancia de Derived .) En Main() , se crea en el ejemplo una instancia de
Func<Base, Derived> ( Func(Of Base, Derived) en Visual Basic) que representa MyMethod , y la almacena en la
variable f1 .

public class Base {}


public class Derived : Base {}

public class Program


{
public static Derived MyMethod(Base b)
{
return b as Derived ?? new Derived();
}

static void Main()


{
Func<Base, Derived> f1 = MyMethod;

Public Class Base


End Class
Public Class Derived
Inherits Base
End Class

Public Class Program


Public Shared Function MyMethod(ByVal b As Base) As Derived
Return If(TypeOf b Is Derived, b, New Derived())
End Function

Shared Sub Main()


Dim f1 As Func(Of Base, Derived) = AddressOf MyMethod

En el segundo fragmento de código, se muestra que el delegado puede asignarse a una variable de tipo
Func<Base, Base> ( Func(Of Base, Base) en Visual Basic) ya que el tipo de valor devuelto es covariante.

// Covariant return type.


Func<Base, Base> f2 = f1;
Base b2 = f2(new Base());

' Covariant return type.


Dim f2 As Func(Of Base, Base) = f1
Dim b2 As Base = f2(New Base())

En el tercer fragmento de código, se muestra que el delegado puede asignarse a una variable de tipo
Func<Derived, Derived> ( Func(Of Derived, Derived) en Visual Basic) ya que el tipo de parámetro es
contravariante.

// Contravariant parameter type.


Func<Derived, Derived> f3 = f1;
Derived d3 = f3(new Derived());

' Contravariant parameter type.


Dim f3 As Func(Of Derived, Derived) = f1
Dim d3 As Derived = f3(New Derived())

En el último fragmento de código, se muestra que el delegado puede asignarse a una variable de tipo
Func<Derived, Base> ( Func(Of Derived, Base) en Visual Basic), combinando los efectos del tipo de parámetro
contravariante y el tipo de valor devuelto covariante.

// Covariant return type and contravariant parameter type.


Func<Derived, Base> f4 = f1;
Base b4 = f4(new Derived());

' Covariant return type and contravariant parameter type.


Dim f4 As Func(Of Derived, Base) = f1
Dim b4 As Base = f4(New Derived())

La varianza en delegados genéricos y no genéricos


En el código anterior, la signatura de MyMethod coincide exactamente con la signatura del delegado genérico
construido: Func<Base, Derived> ( Func(Of Base, Derived) en Visual Basic). En el ejemplo, se muestra que este
delegado genérico se puede almacenar en variables o en parámetros de método que tengan tipos de parámetro
más derivados y tipos de valor devuelto menos derivados, siempre y cuando todos los tipos de delegado se
construyan a partir del tipo de delegado genérico Func<T,TResult>.
Este es un aspecto importante. Los efectos de la covarianza y la contravarianza en los parámetros de tipo de los
delegados genéricos son similares a los efectos de la covarianza y la contravarianza en el enlace a delegados
normal; vea Varianza en delegados (C#) y Varianza en delegados (Visual Basic). Sin embargo, la varianza en el
enlace a delegados funciona con todos los tipos de delegado, no solo con tipos de delegado genérico que tienen
parámetros de tipo variante. Además, la varianza en el enlace a delegados permite enlazar un método a cualquier
delegado que tenga tipos de parámetro más restrictivos y un tipo de valor devuelto menos restrictivo, mientras
que la asignación de delegados genéricos solo funciona si ambos tipos de delegado se construyen a partir de la
misma definición de tipo genérico.
En el ejemplo siguiente se muestran los efectos combinados de la varianza en el enlace a delegados y la varianza
en los parámetros de tipo genérico. En el ejemplo se define una jerarquía de tipos que incluye tres tipos, de
menos derivado ( Type1 ) a más derivado ( Type3 ). La varianza en el enlace a delegados normal se usa para
enlazar un método con un tipo de parámetro de Type1 y un tipo de valor devuelto de Type3 a un delegado
genérico con un tipo de parámetro de Type2 y un tipo de valor devuelto de Type2 . A continuación, el delegado
genérico resultante se asigna a otra variable cuyo tipo de delegado genérico tiene un parámetro de tipo Type3 y
un tipo de valor devuelto de Type1 , usando la covarianza y contravarianza de parámetros de tipo genérico. La
segunda asignación requiere que tanto el tipo de variable como el tipo de delegado se construyan a partir de la
misma definición de tipo genérico, en este caso Func<T,TResult>.
using System;

public class Type1 {}


public class Type2 : Type1 {}
public class Type3 : Type2 {}

public class Program


{
public static Type3 MyMethod(Type1 t)
{
return t as Type3 ?? new Type3();
}

static void Main()


{
Func<Type2, Type2> f1 = MyMethod;

// Covariant return type and contravariant parameter type.


Func<Type3, Type1> f2 = f1;
Type1 t1 = f2(new Type3());
}
}

Public Class Type1


End Class
Public Class Type2
Inherits Type1
End Class
Public Class Type3
Inherits Type2
End Class

Public Class Program


Public Shared Function MyMethod(ByVal t As Type1) As Type3
Return If(TypeOf t Is Type3, t, New Type3())
End Function

Shared Sub Main()


Dim f1 As Func(Of Type2, Type2) = AddressOf MyMethod

' Covariant return type and contravariant parameter type.


Dim f2 As Func(Of Type3, Type1) = f1
Dim t1 As Type1 = f2(New Type3())
End Sub
End Class

Definir interfaces y delegados genéricos variantes


A partir de .NET Framework 4, Visual Basic y C# tienen palabras clave que permiten marcar como covariante o
contravariante los parámetros de tipo genérico de las interfaces y los delegados.

NOTE
A partir de .NET Framework versión 2.0, Common Language Runtime admite anotaciones de varianza en parámetros de
tipo genérico. Antes de .NET Framework 4, la única manera de definir una clase genérica que tuviera estas anotaciones era
usar el lenguaje intermedio de Microsoft (MSIL), ya fuera mediante la compilación de la clase con Ilasm.exe (Ensamblador de
IL) o con la emisión en un ensamblado dinámico.

Un parámetro de tipo covariante se marca con la palabra clave out (palabra clave Out en Visual Basic, + para el
Ensamblador de MSIL). Puede usar un parámetro de tipo covariante como el valor devuelto de un método que
pertenece a una interfaz o como el tipo de valor devuelto de un delegado. No puede usar un parámetro de tipo
covariante como una restricción de tipo genérico para los métodos de interfaz.

NOTE
Si un método de una interfaz tiene un parámetro que es un tipo de delegado genérico, se puede usar un parámetro de tipo
covariante del tipo de interfaz para especificar un parámetro de tipo contravariante del tipo de delegado.

Un parámetro de tipo contravariante se marca con la palabra clave in (palabra clave In en Visual Basic, - para
el Ensamblador de MSIL). Puede usar un parámetro de tipo contravariante como el tipo de un parámetro de un
método que pertenece a una interfaz o como el tipo de un parámetro de un delegado. Puede usar un parámetro
de tipo contravariante como una restricción de tipo genérico para un método de interfaz.
Solo los tipos de interfaz y los tipos de delegado pueden tener parámetros de tipo variante. Un tipo de interfaz o
un tipo de delegado puede tener parámetros de tipo covariante y contravariante.
Visual Basic y C# no le permiten infringir las reglas de uso de parámetros de tipo covariante y contravariante ni
agregar anotaciones de covarianza y contravarianza a los parámetros de tipo de tipos distintos de interfaces y
delegados. El Ensamblador de MSIL no realiza esas comprobaciones, pero se produce TypeLoadException si
intenta cargar un tipo que infringe las reglas.
Para obtener información y código de ejemplo, vea Varianza en interfaces genéricas (C#) y Varianza en interfaces
genéricas (Visual Basic).

Lista de tipos de interfaces y delegados genéricos variantes


En .NET Framework 4, los siguientes tipos de interfaz y delegado tienen parámetros de tipo covariante y/o
contravariante.

PA RÁ M ET RO S DE T IP O
T IP O PA RÁ M ET RO S DE T IP O C O VA RIA N T E C O N T RAVA RIA N T E

Action<T> a Sí
Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,
T11,T12,T13,T14,T15,T16>

Comparison<T> Sí

Converter<TInput,TOutput> Sí Sí

Func<TResult> Sí

Func<T,TResult> a Sí Sí
Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T
11,T12,T13,T14,T15,T16,TResult>

IComparable<T> Sí

Predicate<T> Sí

IComparer<T> Sí

IEnumerable<T> Sí

IEnumerator<T> Sí
PA RÁ M ET RO S DE T IP O
T IP O PA RÁ M ET RO S DE T IP O C O VA RIA N T E C O N T RAVA RIA N T E

IEqualityComparer<T> Sí

IGrouping<TKey,TElement> Sí

IOrderedEnumerable<TElement> Sí

IOrderedQueryable<T> Sí

IQueryable<T> Sí

Vea también
Covarianza y contravarianza (C#)
Covarianza y contravarianza (Visual Basic)
Varianza en delegados (C#)
Varianza en delegados (Visual Basic)
Colecciones y estructuras de datos
16/09/2020 • 13 minutes to read • Edit Online

A menudo, los datos similares pueden controlarse de forma más eficaz si se almacenan y manipulan como si
fuesen una colección. Puede utilizar la clase System.Array o las clases de los espacios de nombres
System.Collections, System.Collections.Generic,System.Collections.Concurrent y System.Collections.Immutable
para agregar, quitar y modificar elementos individuales o un intervalo de elementos de una colección.
Hay dos tipos principales de colecciones: las colecciones genéricas y las colecciones no genéricas. Las colecciones
genéricas se agregaron en la versión 2.0 de.NET Framework y son colecciones con seguridad de tipos en tiempo de
compilación. Debido a esto, las colecciones genéricas normalmente ofrecen un mejor rendimiento. Las colecciones
genéricas aceptan un parámetro de tipo cuando se construyen y no requieren conversiones con el tipo Object al
agregar o quitar elementos de la colección. Además, la mayoría de colecciones genéricas son compatibles con las
aplicaciones de la Tienda Windows. Las colecciones no genéricas almacenan elementos como Object, requieren
una conversión y la mayoría de ellas no son compatibles con el desarrollo de aplicaciones de la Tienda Windows.
Sin embargo, puede que vea colecciones no genéricas en código antiguo.
A partir de .NET Framework 4, las colecciones del espacio de nombres System.Collections.Concurrent proporcionan
operaciones eficaces y seguras para subprocesos con el fin de obtener acceso a los elementos de la colección
desde varios subprocesos. Las clases de colección inmutables en el espacio de nombres
System.Collections.Immutable (paquete de NuGet) son intrínsecamente seguras para los subprocesos, ya que las
operaciones se realizan en una copia de la colección original, mientras que la colección original no se puede
modificar.

Características comunes de las colecciones


Todas las colecciones ofrecen métodos para agregar, quitar o buscar elementos en la colección. Además, todas las
colecciones que implementan directa o indirectamente las interfaces ICollection o ICollection<T> comparten estas
características:
Capacidad para enumerar la colección
Las colecciones de .NET Framework implementan System.Collections.IEnumerable o
System.Collections.Generic.IEnumerable<T> para permitir procesar una iteración en la colección. Un
enumerador puede considerarse como un puntero móvil para cualquier elemento de la colección. Las
instrucciones foreach, in y For Each...Next usan el enumerador expuesto por el método GetEnumerator y
ocultan la complejidad que supone manipular el enumerador. Además, cualquier colección que implementa
System.Collections.Generic.IEnumerable<T> se considera un tipo consultable y se puede consultar con
LINQ. Las consultas LINQ proporcionan un modelo común para acceder a los datos. Por lo general, son más
concisas y legibles que los bucles foreach estándar y ofrecen capacidad de filtrado, ordenación y
agrupación. Las consultas LINQ también pueden mejorar el rendimiento. Para obtener más información, vea
LINQ to Objects (C#), LINQ to Objects (Visual Basic), Parallel LINQ (PLINQ), Introducción a las consultas
LINQ (C#) y Operaciones básicas de consulta (Visual Basic).
Capacidad de copiar el contenido de la colección en una matriz
Todas las colecciones se pueden copiar en una matriz mediante el método CopyTo ; sin embargo, el orden
de los elementos de la nueva matriz se basa en la secuencia en la que los devuelve el enumerador. La matriz
resultante siempre es unidimensional con un límite inferior de cero.
Además, muchas clases de colecciones contienen las siguientes características:
Propiedades de capacidad y recuento
La capacidad de una colección es el número de elementos que puede contener. El recuento de una colección
es el número de elementos que realmente contiene. Algunas colecciones ocultan la capacidad, el recuento, o
ambos.
La mayoría de las colecciones expanden automáticamente su capacidad cuando se alcanza la capacidad
actual. La memoria se reasigna y los elementos de la antigua colección se copian en la nueva. Esto reduce el
código necesario para utilizar la colección; sin embargo, el rendimiento de la colección podría verse afectado
negativamente. Por ejemplo, en List<T>, si Count es menor que Capacity, el agregar un elemento supone
una operación O(1). Si es necesario aumentar la capacidad para alojar el nuevo elemento, agregar un
elemento se convierte en una operación O( n ), donde n es Count. La mejor manera de evitar el
rendimiento deficiente provocado por múltiples reasignaciones es establecer la capacidad inicial el tamaño
estimado de la colección.
BitArray es un caso especial; su capacidad es igual que su longitud, que es la misma que su recuento.
Límite inferior coherente
El límite inferior de una colección es el índice de su primer elemento. Todas las colecciones indizadas en el
espacio de nombres System.Collections tienen un límite inferior de cero, lo que significa que están indizadas
en 0. De forma predeterminada, Array tiene un límite inferior de cero, pero se puede definir un límite
inferior diferente mediante la creación de una instancia de la clase Array con Array.CreateInstance.
Sincronización para el acceso de varios subprocesos (solo clases System.Collections).
Los tipos de colecciones no genéricas del espacio de nombres System.Collections proporcionan una
seguridad de subprocesos con sincronización; normalmente se exponen a través de los miembros SyncRoot
y IsSynchronized. Estas colecciones no son seguras para subprocesos de forma predeterminada. Si necesita
un acceso multiproceso escalable y eficaz a una colección, utilice una de las clases del espacio de nombres
System.Collections.Concurrent o considere el uso de una colección inmutable. Para obtener más
información, consulte Colecciones seguras para subprocesos.

Elija una colección.


En general, debería utilizar colecciones genéricas. En la tabla siguiente se describen algunos escenarios habituales
de las colecciones y las clases de colección que puede utilizar en esos escenarios. Si es la primera vez que usa
colecciones genéricas, con esta tabla le será más fácil elegir la colección genérica que funciona mejor para su tarea.

O P C IO N ES DE C O L EC C IÓ N
O P C IO N ES DE C O L EC C IÓ N O P C IO N ES DE C O L EC C IÓ N DE SUB P RO C ESO S O
DESEO. . . GEN ÉRIC A N O GEN ÉRIC A IN M UTA B L E

Almacenar elementos como Dictionary<TKey,TValue> Hashtable ConcurrentDictionary<TKey,


pares clave/valor para una TValue>
consulta rápida por clave (Colección de pares
clave/valor que se organizan ReadOnlyDictionary<TKey,T
en función del código hash Value>
de la clave).
ImmutableDictionary<TKey,T
Value>

Acceso a elementos por List<T> Array ImmutableList<T>


índice
ArrayList ImmutableArray
O P C IO N ES DE C O L EC C IÓ N
O P C IO N ES DE C O L EC C IÓ N O P C IO N ES DE C O L EC C IÓ N DE SUB P RO C ESO S O
DESEO. . . GEN ÉRIC A N O GEN ÉRIC A IN M UTA B L E

Utilizar elementos FIFO (el Queue<T> Queue ConcurrentQueue<T>


primero en entrar es el
primero en salir) ImmutableQueue<T>

Utilizar datos LIFO (el último Stack<T> Stack ConcurrentStack<T>


en entrar es el primero en
salir) ImmutableStack<T>

Acceso a elementos de LinkedList<T> Sin recomendación Sin recomendación


forma secuencial

Recibir notificaciones cuando ObservableCollection<T> Sin recomendación Sin recomendación


se quitan o se agregan
elementos a la colección.
(implementa
INotifyPropertyChanged y
INotifyCollectionChanged)

Una colección ordenada SortedList<TKey,TValue> SortedList ImmutableSortedDictionary


<TKey,TValue>

ImmutableSortedSet<T>

Un conjunto de funciones HashSet<T> Sin recomendación ImmutableHashSet<T>


matemáticas
SortedSet<T> ImmutableSortedSet<T>

Complejidad algorítmica de colecciones


Al elegir una clase de colección, vale la pena tener en cuenta las posibles compensaciones en cuanto al
rendimiento. Use la siguiente tabla para hacer referencia a la comparación de varios tipos de colecciones mutables
por lo que respecta a la complejidad algorítmica con sus equivalentes inmutables correspondientes. A menudo, los
tipos de colecciones inmutables son menos efectivos, pero proporcionan inmutabilidad, lo que a menudo es un
beneficio comparativo válido.

M UTA B L E A M O RT IZ A DO EL P EO R C A SO IN M UTA B L E C O M P L E JIDA D

Stack<T>.Push O(1) O( n ) ImmutableStack<T>.Push O(1)

Queue<T>.Enqueue O(1) O( n ) O(1)


ImmutableQueue<T>.Enqueue

List<T>.Add O(1) O( n ) ImmutableList<T>.Add O(log n )

List<T>.Item[Int32] O(1) O(1) O(log n


ImmutableList<T>.Item[Int32] )

List<T>.Enumerator O( n ) O( n ) O( n )
ImmutableList<T>.Enumerator

HashSet<T>.Add , O(1) O( n ) ImmutableHashSet<T>.AddO(log n )


búsqueda

SortedSet<T>.Add O(log n ) O( n ) O(log n


ImmutableSortedSet<T>.Add )
M UTA B L E A M O RT IZ A DO EL P EO R C A SO IN M UTA B L E C O M P L E JIDA D

Dictionary<T>.Add O(1) O( n ) O(log n


ImmutableDictionary<T>.Add )

Búsqueda de O(1) O(1), o bien O( n ) de ImmutableDictionary<T> O(log n )


Dictionary<T> manera estricta lookup

SortedDictionary<T>.AddO(log n ) O( n log n ) O(log n )


ImmutableSortedDictionary<T>.Add

Un objeto List<T> se puede enumerar eficientemente utilizando un bucle for o un bucle foreach . Sin embargo,
un objeto ImmutableList<T> realiza un trabajo insuficiente dentro de un bucle for , debido al tiempo de O(log n )
de su indizador. Enumerar un objeto ImmutableList<T> usando un bucle foreach es eficiente porque
ImmutableList<T> usa un árbol binario para almacenar sus datos en lugar de una matriz simple como la que usa
List<T> . Una matriz se puede indexar muy rápidamente, mientras que un árbol binario debe desplazarse hasta
que se encuentre el nodo con el índice deseado.
Además, SortedSet<T> tiene la misma complejidad que ImmutableSortedSet<T> . Esto es porque ambos usan
árboles binarios. La diferencia significativa, por supuesto, es que ImmutableSortedSet<T> usa un árbol binario
inmutable. Dado que ImmutableSortedSet<T> también ofrece una clase
System.Collections.Immutable.ImmutableSortedSet<T>.Builder que permite la mutación, puede obtener tanto
inmutabilidad como rendimiento.

Temas relacionados
T IT L E DESC RIP C IÓ N

Seleccionar una clase de colección Describe las diferentes colecciones y le ayuda a seleccionar una
para su escenario.

Tipos de colección utilizados normalmente Describe los tipos de colección genéricos y no genéricos más
utilizados, como System.Array,
System.Collections.Generic.List<T> y
System.Collections.Generic.Dictionary<TKey,TValue>.

Cuándo utilizar colecciones genéricas Describe el uso de los tipos de colección genéricos.

Comparaciones y ordenaciones en colecciones Describe el uso de las comparaciones de igualdad y


ordenación en las colecciones.

Tipos de colecciones ordenadas Describe las características y el funcionamiento de colecciones


ordenadas.

Tipos de las colecciones Hashtable y Dictionary Describe las características de los tipos de diccionarios basados
en hash genéricos y no genéricos.

Colecciones seguras para subprocesos Describe los tipos de colección, como


System.Collections.Concurrent.BlockingCollection<T> y
System.Collections.Concurrent.ConcurrentBag<T>, que
admiten un acceso simultáneo seguro y eficaz desde varios
subprocesos.

System.Collections.Immutable Presenta las colecciones inalterables y proporciona vínculos a


los tipos de colección.
Referencia
System.Array System.Collections System.Collections.Concurrent System.Collections.Generic
System.Collections.Specialized System.Linq System.Collections.Immutable
Seleccionar una clase de colección
16/09/2020 • 8 minutes to read • Edit Online

Asegúrese de elegir con cuidado la clase de colección. Usar un tipo incorrecto puede restringir el uso de la
colección.

IMPORTANT
Evite usar los tipos del espacio de nombres System.Collections. Se recomiendan las versiones genéricas y simultáneas de las
colecciones por la mayor seguridad de los tipos y otras mejoras.

Pregúntese lo siguiente:
¿Necesita una lista secuencial en la que normalmente se descarta el elemento después de recuperar su
valor?
En caso afirmativo, considere usar la clase Queue o la clase genérica Queue<T> si necesita un
comportamiento de tipo primero en entrar, primero en salir (FIFO). Considere usar la clase Stack o la
clase genérica Stack<T> si necesita un comportamiento de tipo último en entrar, primero en salir
(LIFO). Para obtener acceso seguro desde varios subprocesos, use las versiones simultáneas
ConcurrentQueue<T> y ConcurrentStack<T>. En el caso de la inmutabilidad, tenga en cuenta las
versiones inmutables, ImmutableQueue<T> y ImmutableStack<T>.
En caso contrario, considere usar las demás colecciones.
¿Necesita acceder a los elementos en cierto orden, como FIFO, LIFO o aleatorio?
La clase Queue, así como las clases genéricas Queue<T>, ConcurrentQueue<T> y
ImmutableQueue<T>, ofrecen acceso FIFO. Para obtener más información, consulte Cuándo usar
una colección segura para subprocesos.
La clase Stack, así como las clases genéricas Stack<T>, ConcurrentStack<T> y ImmutableStack<T>,
ofrecen acceso LIFO. Para obtener más información, consulte Cuándo usar una colección segura para
subprocesos.
La clase genérica LinkedList<T> permite el acceso secuencial desde el encabezado hasta el final o
desde el final hasta el encabezado.
¿Necesita acceder a cada elemento por índice?
Las clases ArrayList y StringCollection y la clase genérica List<T> ofrecen acceso a sus elementos por
el índice de base cero del elemento. En el caso de la inmutabilidad, tenga en cuenta las versiones
genéricas inmutables, ImmutableArray<T> y ImmutableList<T>.
Las clases Hashtable, SortedList, ListDictionary y StringDictionary, y las clases genéricas
Dictionary<TKey,TValue> y SortedDictionary<TKey,TValue> ofrecen acceso a sus elementos por la
clave del elemento. Además, hay versiones inmutables de varios tipos correspondientes:
ImmutableHashSet<T>, ImmutableDictionary<TKey,TValue>, ImmutableSortedSet<T> y
ImmutableSortedDictionary<TKey,TValue>.
Las clases NameObjectCollectionBase y NameValueCollection, y las clases genéricas
KeyedCollection<TKey,TItem> y SortedList<TKey,TValue> ofrecen acceso a sus elementos por el
índice de base cero o por la clave del elemento.
¿Cada elemento contendrá un valor, una combinación de una clave y un valor, o una combinación de una
clave y varios valores?
Un valor: use cualquiera de las colecciones basadas en la interfaz IList o en la interfaz genérica
IList<T>. En el caso de una opción inmutable, tenga en cuenta la interfaz genérica de
IImmutableList<T>.
Una clave y un valor: use cualquiera de las colecciones basadas en la interfaz IDictionary o en la
interfaz genérica IDictionary<TKey,TValue>. En el caso de una opción inmutable, tenga en cuenta las
interfaces genérica de IImmutableSet<T> o IImmutableDictionary<TKey,TValue>.
Un valor con clave insertada: use la clase genérica KeyedCollection<TKey,TItem>.
Una clave y varios valores: use la clase NameValueCollection.
¿Necesita ordenar los elementos de manera diferente a como se especificaron?
La clase Hashtable ordena los elementos por sus códigos hash.
La clase SortedList y las clases genéricas SortedList<TKey,TValue> y SortedDictionary<TKey,TValue>
ordenan sus elementos por la clave. El criterio de ordenación se basa en la implementación de la
interfaz IComparer para la clase SortedList y en la implementación de la interfaz genérica
IComparer<T> para las clases genéricas SortedList<TKey,TValue> y SortedDictionary<TKey,TValue>.
De los dos tipos genéricos, SortedDictionary<TKey,TValue> ofrece mejor rendimiento que
SortedList<TKey,TValue>, mientras que SortedList<TKey,TValue> consume menos memoria.
ArrayList proporciona un método Sort que toma una implementación de IComparer como
parámetro. Su homóloga genérica, la clase genérica List<T>, proporciona un método Sort que toma
una implementación de la interfaz genérica IComparer<T> como parámetro.
¿Necesita realizar búsquedas y recuperaciones rápidas de información?
ListDictionary es más rápida que Hashtable para colecciones pequeñas (de 10 elementos o menos). La
clase genérica Dictionary<TKey,TValue> proporciona búsquedas más rápidas que la clase genérica
SortedDictionary<TKey,TValue>. La implementación multiproceso es
ConcurrentDictionary<TKey,TValue>. ConcurrentBag<T> proporciona una inserción multiproceso
rápida para datos no ordenados. Para más información sobre ambos tipos multiproceso, consulte
Cuándo usar una colección segura para subprocesos.
¿Necesita colecciones que acepten solo cadenas?
StringCollection (basada en IList) y StringDictionary (basada en IDictionary) están en el espacio de
nombres System.Collections.Specialized.
Además, puede usar cualquiera de las clases de colección genéricas del espacio de nombres
System.Collections.Generic como colecciones de cadenas fuertemente tipadas especificando la clase
String para sus argumentos de tipo genéricos. Por ejemplo, puede declarar una variable para que sea
de tipo List<String> o Dictionary<String,String>.

LINQ to Objects y PLINQ


LINQ to Objects permite usar consultas LINQ para acceder a los objetos en memoria siempre que el tipo de objeto
implemente las interfaces IEnumerable o IEnumerable<T>. Las consultas LINQ proporcionan un modelo común
para acceder a los datos; suelen ser más concisas y legibles que los bucles foreach estándar y proporcionan
funciones de filtrado, ordenación y agrupación. Para obtener más información, vea LINQ to Objects (C#) y LINQ to
Objects (Visual Basic).
PLINQ proporciona una implementación paralela de LINQ to Objects que puede ofrecer una ejecución de
consultas más rápida en muchos escenarios gracias a un uso más eficaz de los equipos de varios núcleos. Para
más información, consulte Parallel LINQ (PLINQ).

Vea también
System.Collections
System.Collections.Specialized
System.Collections.Generic
Colecciones seguras para subprocesos
Tipos de colección utilizados normalmente
16/09/2020 • 5 minutes to read • Edit Online

Los tipos de colecciones son las variaciones comunes de las colecciones de datos, como tablas hash, colas, pilas,
bolsas, diccionarios y listas.
Las colecciones se basan en las interfaces ICollection, IList, IDictionary o en sus equivalentes genéricos. La interfaz
IList y la interfaz IDictionary se derivan ambas de la interfaz ICollection; por lo tanto, todas las colecciones se
basan en la interfaz ICollection directa o indirectamente. En colecciones basadas en la interfaz IList (como Array,
ArrayList o List<T>) o directamente en la interfaz ICollection (como Queue, ConcurrentQueue<T>, Stack,
ConcurrentStack<T> o LinkedList<T>), cada elemento contiene un solo valor. En colecciones basadas en la
interfaz IDictionary (como las clases Hashtable y SortedList, y las clases genéricas Dictionary<TKey,TValue> y
SortedList<TKey,TValue>), o en las clases ConcurrentDictionary<TKey,TValue>, cada elemento contiene una clave
y un valor. La clase KeyedCollection<TKey,TItem> es única porque es una lista de valores con claves insertadas en
los valores y, por lo tanto, se comporta como una lista y como un diccionario.
Las colecciones genéricas son la mejor solución para elementos fuertemente tipados. Sin embargo, si su lenguaje
no admite genéricos, el espacio de nombres System.Collections incluye colecciones base, como CollectionBase,
ReadOnlyCollectionBase y DictionaryBase, que son clases base abstractas que se pueden extender para crear
clases de colección fuertemente tipadas. Cuando se requiere un acceso eficaz a una colección multiproceso, utilice
las colecciones genéricas del espacio de nombres System.Collections.Concurrent.
Las colecciones pueden variar en función de cómo se almacenan los elementos, cómo se ordenan, cómo se
realizan las búsquedas y cómo se realizan las comparaciones. La clase Queue y la clase genérica Queue<T>
proporcionan listas de tipo “el primero en entrar es el primero en salir”, mientras que la clase Stack y la clase
genérica Stack<T> proporcionan listas de tipo “el último en entrar es el primero en salir”. La clase SortedList y la
clase genérica SortedList<TKey,TValue> proporcionan versiones ordenadas de la clase Hashtable y de la clase
genérica Dictionary<TKey,TValue>. El acceso a los elementos de Hashtable o de Dictionary<TKey,TValue> solo es
posible mediante la clave del elemento, pero el acceso a los elementos de SortedList o de
KeyedCollection<TKey,TItem> es posible mediante la clave o mediante el índice del elemento. Los índices de todas
las colecciones son de base cero, excepto Array, que permite matrices que no son de base cero.
La característica LINQ to Objects permite usar consultas LINQ para obtener acceso a los objetos en memoria
mientras el tipo de objeto implemente la interfaz IEnumerable o IEnumerable<T>. Las consultas LINQ
proporcionan un patrón común para acceder a los datos; suelen ser más concisas y legibles que los bucles
foreach estándar y proporcionan capacidades de filtrado, ordenación y agrupación. Las consultas LINQ también
pueden mejorar el rendimiento. Para más información, vea LINQ to Objects (C#), LINQ to Objects (Visual Basic) y
Parallel LINQ (PLINQ).

Temas relacionados
T IT L E DESC RIP C IÓ N

Colecciones y estructuras de datos Describe los diversos tipos de colecciones disponibles en .NET
Framework, incluidas las pilas, colas, listas, matrices y
diccionarios.

Tipos de las colecciones Hashtable y Dictionary Describe las características de los tipos de diccionario basado
en hash genéricos y no genéricos.
T IT L E DESC RIP C IÓ N

Tipos de colecciones ordenadas Describe las clases que proporcionan funcionalidad de


ordenación para las listas y los conjuntos.

Genéricos Describe la característica de genéricos, incluidas las


colecciones, los delegados y las interfaces genéricos
proporcionados por .NET Framework. Proporciona vínculos a
la documentación de características para C#, Visual Basic y
Visual C++, así como a tecnologías de apoyo como la
reflexión.

Referencia
System.Collections
System.Collections.Generic
System.Collections.ICollection
System.Collections.Generic.ICollection<T>
System.Collections.IList
System.Collections.Generic.IList<T>
System.Collections.IDictionary
System.Collections.Generic.IDictionary<TKey,TValue>
Cuándo utilizar colecciones genéricas
16/09/2020 • 7 minutes to read • Edit Online

El uso de colecciones genéricas ofrece la ventaja inmediata de la seguridad de tipos sin necesidad de derivar de un
tipo de colección base ni de implementar miembros específicos de tipo. Los tipos de colección genéricos también
suelen funcionan mejor que los correspondientes tipos de colección no genéricos (y mejor que los tipos que se
derivan de los tipos de colección base no genéricos) cuando los elementos de la colección son tipos de valor. Esto
se debe a que con los genéricos no es necesario realizar una conversión boxing de los elementos.
En programas destinados a .NET Standard 1.0 o una versión posterior, utilice las clases de colección genérica en el
espacio de nombres System.Collections.Concurrent cuando varios subprocesos puedan agregar o quitar
elementos de la colección simultáneamente. Además, cuando busque inmutabilidad, tenga en cuenta las clases de
colección genéricas en el espacio de nombres System.Collections.Immutable.
Los siguientes tipos genéricos corresponden a los tipos de colección existentes:
List<T> es la clase genérica que se corresponde con ArrayList.
Dictionary<TKey,TValue> y ConcurrentDictionary<TKey,TValue> son las clases genéricas que se
corresponden con Hashtable.
Collection<T> es la clase genérica que se corresponde con CollectionBase. Collection<T> puede utilizarse
como una clase base pero, a diferencia de CollectionBase, no es abstracta, lo que hace que sea mucho más
fácil de usar.
ReadOnlyCollection<T> es la clase genérica que se corresponde con ReadOnlyCollectionBase. La colección
ReadOnlyCollection<T> no es abstracta y tiene un constructor que hace más fácil exponer una colección
List<T> existente como de solo lectura.
Las clases genéricas Queue<T>, ConcurrentQueue<T>, ImmutableQueue<T>, ImmutableArray<T>,
SortedList<TKey,TValue> y ImmutableSortedSet<T> se corresponden con las respectivas clases no
genéricas de igual nombre.

Tipos adicionales
Hay varios tipos de colección genéricos que no tienen un tipo homólogo no genérico. Entre esos tipos se incluyen
los siguientes:
LinkedList<T> es una lista vinculada de uso general que proporciona operaciones de eliminación e
inserción O(1).
SortedDictionary<TKey,TValue> es un diccionario ordenado con operaciones de eliminación e inserción
O(log n ), lo que lo convierte en una alternativa útil a SortedList<TKey,TValue>.
KeyedCollection<TKey,TItem> es un híbrido entre una lista y un diccionario, lo que proporciona una manera
de almacenar objetos que contienen sus propias claves.
BlockingCollection<T> implementa una clase de colección con funcionalidad de límite y bloqueo.
ConcurrentBag<T> proporciona una rápida inserción y eliminación de elementos no ordenados.
Generadores inmutables
Si quiere disponer de la funcionalidad de inmutabilidad en la aplicación, el espacio de nombres
System.Collections.Immutable ofrece tipos de colección genéricos que se pueden usar. Todos los tipos de colección
inmutables ofrecen clases Builder que pueden optimizar el rendimiento cuando se realicen varias mutaciones. La
clase Builder agrupa las operaciones en un estado mutable. Cuando se hayan completado todas las mutaciones,
llame al método ToImmutable para "inmovilizar" todos los nodos y crear una colección genérica inmutable, por
ejemplo, una colección ImmutableList<T>.
Se puede crear el objeto Builder llamando al método CreateBuilder() no genérico. En una instancia de Builder ,
se puede llamar a ToImmutable() . Del mismo modo, en la colección Immutable* , se puede llamar a ToBuilder()
para crear una instancia de generador a partir de la colección inmutable genérica. Estos son los distintos tipos de
Builder .

ImmutableArray<T>.Builder
ImmutableDictionary<TKey,TValue>.Builder
ImmutableHashSet<T>.Builder
ImmutableList<T>.Builder
ImmutableSortedDictionary<TKey,TValue>.Builder
ImmutableSortedSet<T>.Builder

LINQ to Objects
La característica LINQ to Objects permite usar consultas LINQ para obtener acceso a los objetos en memoria
mientras el tipo de objeto implemente la interfaz System.Collections.IEnumerable o
System.Collections.Generic.IEnumerable<T> . Las consultas LINQ proporcionan un patrón común para acceder a
los datos; suelen ser más concisas y legibles que los bucles foreach estándar y proporcionan capacidades de
filtrado, ordenación y agrupación. Las consultas LINQ también pueden mejorar el rendimiento. Para más
información, vea LINQ to Objects (C#), LINQ to Objects (Visual Basic) y Parallel LINQ (PLINQ).

Funcionalidad adicional
Algunos de los tipos genéricos tienen funcionalidades que no se encuentran en los tipos de colección no genéricos.
Por ejemplo, la clase List<T> , que se corresponde con la clase ArrayList no genérica, tiene una serie de métodos
que aceptan delegados genéricos, como el delegado Predicate<T> que permite especificar los métodos de
búsqueda en la lista, el delegado Action<T> que representa los métodos que actúan en cada elemento de la lista y
el delegado Converter<TInput,TOutput> que permite definir conversiones entre tipos.
La clase List<T> permite especificar sus propias implementaciones de interfaz genérica IComparer<T> para la
ordenación y búsqueda en la lista. Las clases SortedDictionary<TKey,TValue> y SortedList<TKey,TValue> también
tienen esta capacidad. Además, estas clases le permiten especificar los comparadores cuando se crea la colección.
De forma similar, las clases Dictionary<TKey,TValue> y KeyedCollection<TKey,TItem> le permiten especificar sus
propios comparadores de igualdad.

Vea también
Colecciones y estructuras de datos
Tipos de colección utilizados normalmente
Genéricos
Comparaciones y ordenaciones en colecciones
16/09/2020 • 10 minutes to read • Edit Online

Las clases System.Collections realizan comparaciones en casi todos los procesos implicados en la administración
de colecciones, bien al buscar el elemento que se va a quitar, bien al devolver el valor de un par de clave y valor.
Normalmente, las colecciones usan un comparador de igualdad o un comparador de orden. En las comparaciones
se usan dos constructores.

Comprobación de la igualdad
Los métodos como Contains , IndexOf, LastIndexOfy Remove utilizan un comparador de igualdad para los
elementos de la colección. Si la colección es genérica, se compara la igualdad de los elementos según las siguientes
directrices:
Si el tipo T implementa la interfaz genérica IEquatable<T> , el comparador de igualdad es el método Equals
de dicha interfaz.
Si el tipo T no implementa IEquatable<T>, se utiliza Object.Equals .
Además, algunas sobrecargas de constructores para colecciones de diccionario aceptan una implementación de
IEqualityComparer<T>, que se utiliza para comparar la igualdad de claves. Para ver un ejemplo, consulte el
constructor Dictionary<TKey,TValue> .

Determinación del criterio de ordenación


Los métodos como BinarySearch y Sort utilizan un comparador de orden para los elementos de la colección. Las
comparaciones pueden ser entre elementos de la colección o entre un elemento y un valor especificado. Para
comparar objetos, existe el concepto de un default comparer y un explicit comparer .
El comparador predeterminado se basa en al menos uno de los objetos que se comparan para implementar la
interfaz IComparable . Una práctica recomendada es implementar IComparable en todas las clases que se
utilizan como valores en una colección de lista o como claves en una colección de diccionarios. Para una colección
genérica, la comparación de igualdad se determina según lo siguiente:
Si el tipo T implementa la interfaz genérica System.IComparable<T> , el comparador predeterminado es el
método IComparable<T>.CompareTo(T) de dicha interfaz.
Si el tipo T implementa la interfaz no genérica System.IComparable , el comparador predeterminado es el
método IComparable.CompareTo(Object) de dicha interfaz.
Si el tipo T no implementa ninguna de estas interfaces, no hay ningún comparador predeterminado y debe
proporcionarse explícitamente un delegado de comparación o comparador.
Para proporcionar comparaciones explícitas, algunos métodos aceptan una implementación de IComparer como
parámetro. Por ejemplo, el método List<T>.Sort acepta una implementación de
System.Collections.Generic.IComparer<T> .
La configuración de la referencia cultural actual del sistema puede afectar a las comparaciones y ordenaciones de
una colección. De forma predeterminada, las comparaciones y ordenaciones de las clases colecciones tienen en
cuenta la referencia cultural. Para omitir la configuración de referencia cultural y así obtener resultados de
comparación y ordenación coherentes, utilice InvariantCulture con sobrecargas de miembros que acepten un
CultureInfo. Para obtener más información, consulte Realizar operaciones de cadenas que no tienen en cuenta las
referencias culturales en colecciones y Realizar operaciones de cadenas que no tienen en cuenta las referencias
culturales en matrices.

Ejemplo de igualdad y ordenación


El código siguiente muestra una implementación de IEquatable<T> y IComparable<T> en un objeto comercial
simple. Además, cuando el objeto se almacena en una lista y se ordena, la llamada al método Sort() implica el uso
del comparador predeterminado para el tipo Part y el método Sort(Comparison<T>) implementado mediante el
uso de un método anónimo.

using System;
using System.Collections.Generic;

// Simple business object. A PartId is used to identify the


// type of part but the part name can change.
public class Part : IEquatable<Part>, IComparable<Part>
{
public string PartName { get; set; }

public int PartId { get; set; }

public override string ToString() =>


$"ID: {PartId} Name: {PartName}";

public override bool Equals(object obj) =>


(obj is Part part)
? Equals(part)
: false;

public int SortByNameAscending(string name1, string name2) =>


name1?.CompareTo(name2) ?? 1;

// Default comparer for Part type.


// A null value means that this object is greater.
public int CompareTo(Part comparePart) =>
comparePart == null ? 1 : PartId.CompareTo(comparePart.PartId);

public override int GetHashCode() => PartId;

public bool Equals(Part other) =>


other is null ? false : PartId.Equals(other.PartId);

// Should also override == and != operators.


}

public class Example


{
public static void Main()
{
// Create a list of parts.
var parts = new List<Part>
{
// Add parts to the list.
new Part { PartName = "regular seat", PartId = 1434 },
new Part { PartName = "crank arm", PartId = 1234 },
new Part { PartName = "shift lever", PartId = 1634 },
// Name intentionally left null.
new Part { PartId = 1334 },
new Part { PartName = "banana seat", PartId = 1444 },
new Part { PartName = "cassette", PartId = 1534 }
};

// Write out the parts in the list. This will call the overridden
// ToString method in the Part class.
Console.WriteLine("\nBefore sort:");
parts.ForEach(Console.WriteLine);
parts.ForEach(Console.WriteLine);

// Call Sort on the list. This will use the


// default comparer, which is the Compare method
// implemented on Part.
parts.Sort();

Console.WriteLine("\nAfter sort by part number:");


parts.ForEach(Console.WriteLine);

// This shows calling the Sort(Comparison<T> comparison) overload using


// a lambda expression as the Comparison<T> delegate.
// This method treats null as the lesser of two values.
parts.Sort((Part x, Part y) =>
x.PartName == null && y.PartName == null
? 0
: x.PartName == null
? -1
: y.PartName == null
? 1
: x.PartName.CompareTo(y.PartName));

Console.WriteLine("\nAfter sort by name:");


parts.ForEach(Console.WriteLine);

/*

Before sort:
ID: 1434 Name: regular seat
ID: 1234 Name: crank arm
ID: 1634 Name: shift lever
ID: 1334 Name:
ID: 1444 Name: banana seat
ID: 1534 Name: cassette

After sort by part number:


ID: 1234 Name: crank arm
ID: 1334 Name:
ID: 1434 Name: regular seat
ID: 1444 Name: banana seat
ID: 1534 Name: cassette
ID: 1634 Name: shift lever

After sort by name:


ID: 1334 Name:
ID: 1444 Name: banana seat
ID: 1534 Name: cassette
ID: 1234 Name: crank arm
ID: 1434 Name: regular seat
ID: 1634 Name: shift lever

*/
}
}

Imports System.Collections.Generic

' Simple business object. A PartId is used to identify the type of part
' but the part name can change.
Public Class Part
Implements IEquatable(Of Part)
Implements IComparable(Of Part)
Public Property PartName() As String
Get
Return m_PartName
End Get
Set(value As String)
m_PartName = Value
m_PartName = Value
End Set
End Property
Private m_PartName As String

Public Property PartId() As Integer


Get
Return m_PartId
End Get
Set(value As Integer)
m_PartId = Value
End Set
End Property
Private m_PartId As Integer

Public Overrides Function ToString() As String


Return "ID: " & PartId & " Name: " & PartName
End Function

Public Overrides Function Equals(obj As Object) As Boolean


If obj Is Nothing Then
Return False
End If
Dim objAsPart As Part = TryCast(obj, Part)
If objAsPart Is Nothing Then
Return False
Else
Return Equals(objAsPart)
End If
End Function

Public Function SortByNameAscending(name1 As String, name2 As String) As Integer

Return name1.CompareTo(name2)
End Function

' Default comparer for Part.


Public Function CompareTo(comparePart As Part) As Integer _
Implements IComparable(Of ListSortVB.Part).CompareTo
' A null value means that this object is greater.
If comparePart Is Nothing Then
Return 1
Else

Return Me.PartId.CompareTo(comparePart.PartId)
End If
End Function
Public Overrides Function GetHashCode() As Integer
Return PartId
End Function
Public Overloads Function Equals(other As Part) As Boolean Implements IEquatable(Of
ListSortVB.Part).Equals
If other Is Nothing Then
Return False
End If
Return (Me.PartId.Equals(other.PartId))
End Function
' Should also override == and != operators.

End Class
Public Class Example
Public Shared Sub Main()
' Create a list of parts.
Dim parts As New List(Of Part)()

' Add parts to the list.


parts.Add(New Part() With { _
.PartName = "regular seat", _
.PartId = 1434 _
})
})
parts.Add(New Part() With { _
.PartName = "crank arm", _
.PartId = 1234 _
})
parts.Add(New Part() With { _
.PartName = "shift lever", _
.PartId = 1634 _
})

' Name intentionally left null.


parts.Add(New Part() With { _
.PartId = 1334 _
})
parts.Add(New Part() With { _
.PartName = "banana seat", _
.PartId = 1444 _
})
parts.Add(New Part() With { _
.PartName = "cassette", _
.PartId = 1534 _
})

' Write out the parts in the list. This will call the overridden
' ToString method in the Part class.
Console.WriteLine(vbLf & "Before sort:")
For Each aPart As Part In parts
Console.WriteLine(aPart)
Next

' Call Sort on the list. This will use the


' default comparer, which is the Compare method
' implemented on Part.
parts.Sort()

Console.WriteLine(vbLf & "After sort by part number:")


For Each aPart As Part In parts
Console.WriteLine(aPart)
Next

' This shows calling the Sort(Comparison(T) overload using


' an anonymous delegate method.
' This method treats null as the lesser of two values.
parts.Sort(Function(x As Part, y As Part)
If x.PartName Is Nothing AndAlso y.PartName Is Nothing Then
Return 0
ElseIf x.PartName Is Nothing Then
Return -1
ElseIf y.PartName Is Nothing Then
Return 1
Else
Return x.PartName.CompareTo(y.PartName)
End If
End Function)

Console.WriteLine(vbLf & "After sort by name:")


For Each aPart As Part In parts
Console.WriteLine(aPart)
Next

'
'
' Before sort:
' ID: 1434 Name: regular seat
' ID: 1234 Name: crank arm
' ID: 1234 Name: crank arm
' ID: 1634 Name: shift lever
' ID: 1334 Name:
' ID: 1444 Name: banana seat
' ID: 1534 Name: cassette
'
' After sort by part number:
' ID: 1234 Name: crank arm
' ID: 1334 Name:
' ID: 1434 Name: regular seat
' ID: 1444 Name: banana seat
' ID: 1534 Name: cassette
' ID: 1634 Name: shift lever
'
' After sort by name:
' ID: 1334 Name:
' ID: 1444 Name: banana seat
' ID: 1534 Name: cassette
' ID: 1234 Name: crank arm
' ID: 1434 Name: regular seat
' ID: 1634 Name: shift lever

End Sub
End Class

Vea también
IComparer
IEquatable<T>
IComparer<T>
IComparable
IComparable<T>
Tipos de colecciones ordenadas
16/09/2020 • 3 minutes to read • Edit Online

La clase System.Collections.SortedList, la clase genérica System.Collections.Generic.SortedList<TKey,TValue> y la


clase genérica System.Collections.Generic.SortedDictionary<TKey,TValue> son similares a la clase Hashtable y a la
clase genérica Dictionary<TKey,TValue> en que implementan la interfaz IDictionary, pero mantienen sus
elementos en el criterio de ordenación mediante la clave, y no tienen la inserción O(1) y la característica de
recuperación de las tablas hash. Las tres clases tienen varias características en común:
Las tres clases implementan la interfaz System.Collections.IDictionary. Las dos clases genéricas también
implementan la interfaz genérica System.Collections.Generic.IDictionary<TKey,TValue>.
Cada elemento es un par de clave y valor para propósitos de enumeración.

NOTE
La clase SortedList no genérica devuelve objetos DictionaryEntry cuando se enumera, aunque los dos tipos genéricos
devuelven objetos KeyValuePair<TKey,TValue>.

Los elementos se ordenan según una implementación System.Collections.IComparer (para SortedList no


genérico) o una implementación System.Collections.Generic.IComparer<T> (para las dos clases genéricas).
Cada clase proporciona propiedades que devuelven colecciones que contienen solo las claves o solo los
valores.
En la tabla siguiente se enumeran algunas de las diferencias entre las dos clases de lista ordenada y la clase
SortedDictionary<TKey,TValue>.

C L A SE N O GEN ÉRIC A SO RT EDL IST Y C L A SE GEN ÉRIC A


SO RT EDL IST <T K EY, T VA L UE> C L A SE GEN ÉRIC A SO RT EDDIC T IO N A RY <T K EY, T VA L UE>

Las propiedades que devuelven claves y valores se indizan, lo Sin recuperación indizada.
que permite una recuperación indizada eficaz.

La recuperación es O(log n ). La recuperación es O(log n ).

La inserción y eliminación son generalmente O( n ); pero la La inserción y eliminación son O(log n ).


inserción es O(log n ) para los datos que ya están en el
criterio de ordenación, de manera que cada elemento se
agrega al final de la lista. (Se supone que no es necesario
cambiar de tamaño).

Usa menos memoria que SortedDictionary<TKey,TValue>. Usa más memoria que la clase no genérica SortedList y la
clase genérica SortedList<TKey,TValue>.

Para listas ordenadas o diccionarios que deben ser accesibles simultáneamente desde varios subprocesos, se
puede agregar una lógica de ordenación a una clase que deriva de ConcurrentDictionary<TKey,TValue>. Al
considerar la inmutabilidad, los siguientes tipos inmutables correspondientes siguen una semántica de ordenación
similar: ImmutableSortedSet<T> y ImmutableSortedDictionary<TKey,TValue>.
NOTE
Para los valores que contienen sus propias claves (por ejemplo, registros de empleados que contienen un número de id. de
empleado), puede derivar de la clase genérica KeyedCollection<TKey,TItem> para crear una colección con clave que tenga
algunas características de lista y algunas características de diccionario.

A partir de .NET Framework 4, la clase SortedSet<T> proporciona un árbol que mantiene los datos ordenados
después de las inserciones, eliminaciones y búsquedas. Esta clase y la clase HashSet<T> implementan la interfaz
ISet<T>.

Vea también
System.Collections.IDictionary
System.Collections.Generic.IDictionary<TKey,TValue>
ConcurrentDictionary<TKey,TValue>
Tipos de colección utilizados normalmente
Tipos de las colecciones Hashtable y Dictionary
16/09/2020 • 4 minutes to read • Edit Online

La clase System.Collections.Hashtable y las clases genéricas System.Collections.Generic.Dictionary<TKey,TValue> y


System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue> implementan la interfaz
System.Collections.IDictionary. La clase genérica Dictionary<TKey,TValue> también implementa la interfaz
genérica IDictionary<TKey,TValue>. Por lo tanto, cada elemento de esta colección es un par de clave y valor.
Un objeto Hashtable consta de depósitos que contienen los elementos de la colección. Un depósito es un subgrupo
virtual de elementos dentro de la colección Hashtable, lo que permite buscar y recuperar más fácil y rápidamente
que en la mayoría de las colecciones. Cada depósito está asociado con un código hash, que se genera usando una
función hash y se basa en la clave del elemento.
La clase genérica HashSet<T> es una colección no ordenada de elementos únicos.
Una función hash es un algoritmo que devuelve un código hash numérico basado en una clave. La clave es el valor
de alguna propiedad del objeto que se almacena. Una función hash siempre debe devolver el mismo código hash
para la misma clave. Una función hash puede generar el mismo código hash para dos claves diferentes, pero las
funciones hash que generan un código hash único para cada clave única tienen un rendimiento mejor al recuperar
los elementos de la tabla hash.
Cada objeto que se usa como un elemento en una colección Hashtable debe ser capaz de generar un código hash
para sí mismo usando una implementación del método GetHashCode. Sin embargo, también puede especificar
una función hash para todos los elementos de una colección Hashtable usando un constructor Hashtable que
acepta una implementación de IHashCodeProvider como uno de sus parámetros.
Cuando se agrega un objeto a una colección Hashtable, se almacena en el depósito que está asociado con el
código hash que coincide con el código hash del objeto. Cuando se busca un valor en la colección Hashtable, se
genera el código hash para ese valor y se busca el depósito asociado con ese código hash.
Por ejemplo, una función hash para una cadena podría tomar los códigos ASCII de cada carácter de la cadena y
sumarlos todos para generar un código hash. La cadena "picnic" tendría un código hash que es diferente del
código hash de la cadena "cesta"; por lo tanto, las cadenas "picnic" y "cesta" estarían en depósitos distintos. En
cambio, "bolsa" y "lobas" tendrían el mismo código hash y estarían en el mismo cubo.
Las clases Dictionary<TKey,TValue> y ConcurrentDictionary<TKey,TValue> tienen la misma funcionalidad que la
clase Hashtable. Una clase Dictionary<TKey,TValue> de un tipo específico (distinto de Object) proporciona un
rendimiento mejor que una clase Hashtable para tipos de valor. Esto se debe a que los elementos de Hashtable son
del tipo Object y, por lo tanto, las conversiones boxing y unboxing se suelen producir al almacenar o recuperar un
tipo de valor. La clase ConcurrentDictionary<TKey,TValue> debe usarse cuando varios subprocesos puedan tener
acceso a la colección simultáneamente.

Vea también
Hashtable
IDictionary
IHashCodeProvider
Dictionary<TKey,TValue>
System.Collections.Generic.IDictionary<TKey,TValue>
System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue>
Tipos de colección utilizados normalmente
Colecciones seguras para subprocesos
16/09/2020 • 7 minutes to read • Edit Online

.NET Framework 4 introduce el espacio de nombres System.Collections.Concurrent, que incluye varias clases de
colección que son a la vez seguras para subprocesos y escalables. Varios subprocesos pueden agregar o quitar
elementos de estas colecciones sin ningún riesgo y de un modo eficaz, sin requerir una sincronización adicional
en código de usuario. Al escribir un código nuevo, utilice las clases de colección simultáneas siempre que varios
subprocesos se vayan a escribir en la colección de forma simultánea. Si solo está leyendo en una colección
compartida, puede utilizar las clases en el espacio de nombres System.Collections.Generic. Recomendamos no
utilizar clases de colección 1.0 a menos que estén destinadas a .NET Framework 1.1. o un runtime de una versión
anterior.

Sincronización de subprocesos en las colecciones de .NET Framework


1.0 y 2.0
Las colecciones introducidas en .NET Framework 1.0 se encuentran en el espacio de nombres System.Collections.
Estas colecciones, que incluyen ArrayList y Hashtable utilizados habitualmente, proporcionan cierta seguridad
para subprocesos mediante la propiedad Synchronized , que devuelve un contenedor seguro para subprocesos en
torno a la colección. El contenedor funciona bloqueando toda la colección en cada operación de agregar o quitar.
Por consiguiente, cada subproceso que intenta tener acceso a la colección debe esperar su turno para tomar el
único bloqueo. Esto no es escalable y puede producir una degradación significativa del rendimiento en las
colecciones grandes. Asimismo, el diseño no está totalmente protegido de las condiciones de carrera. Para obtener
más información, vea Synchronization in Generic Collections (Sincronización de colecciones genéricas).
Las clases de colección introducidas en .NET Framework 2.0 se encuentran en el espacio de nombres
System.Collections.Generic. Éstas incluyen List<T>, Dictionary<TKey,TValue>, etc. Estas clases proporcionan una
seguridad de tipos y un rendimiento mejorados comparados con las clases de .NET Framework 1.0. Sin embargo,
las clases de colección de .NET Framework 2.0 no proporcionan ninguna sincronización de subprocesos; el código
de usuario debe proporcionar toda la sincronización cuando se agregan o quitan elementos en varios
subprocesos simultáneamente.
Se recomiendan las clases de colección simultáneas en .NET Framework 4 porque proporcionan no solo la
seguridad de tipos de las clases de colección de .NET Framework 2.0, sino también una seguridad de subprocesos
más eficaz y completa que las colecciones de .NET Framework 1.0.

Mecanismos de bloqueo específico y sin bloqueos


Algunos de los tipos de colección simultáneos utilizan mecanismos de sincronización ligeros como SpinLock,
SpinWait, SemaphoreSlim y CountdownEvent, que son nuevos en .NET Framework 4. Estos tipos de sincronización
utilizan normalmente giro de ocupado durante breves períodos antes de colocar el subproceso en un verdadero
estado de espera. Cuando se prevé que los tiempos de espera sean muy cortos, el giro es técnicamente menos
costoso que la espera, que implica una costosa transición del kernel. Para las clases de colección que utilizan el
giro, esta eficacia significa que se pueden agregar y quitar varios subprocesos con una tasa muy alta. Para más
información sobre la comparación del giro y el bloque, consulte SpinLock y SpinWait.
Las clases ConcurrentQueue<T> y ConcurrentStack<T> no utilizan bloqueos en absoluto. En su lugar, dependen
de las operaciones Interlocked para lograr la seguridad para subprocesos.
NOTE
Como las clases de colección simultáneas son compatibles con ICollection, proporcionan implementaciones para las
propiedades IsSynchronized y SyncRoot, aunque estas propiedades sean irrelevantes. IsSynchronized devuelve siempre
false y SyncRoot es siempre null ( Nothing en Visual Basic).

La siguiente tabla enumera los tipos de colección en el espacio de nombres System.Collections.Concurrent.

T IP O DESC RIP C IÓ N

BlockingCollection<T> Proporciona funcionalidad de límite y bloqueo para cualquier


tipo que implemente IProducerConsumerCollection<T>. Para
obtener más información, consulte Información general sobre
BlockingCollection.

ConcurrentDictionary<TKey,TValue> Implementación segura para subprocesos de un diccionario


de pares clave-valor.

ConcurrentQueue<T> Implementación segura para subprocesos de una cola FIFO


(primero en entrar, primero en salir).

ConcurrentStack<T> Implementación segura para subprocesos de una pila LIFO


(último en entrar, primero en salir).

ConcurrentBag<T> Implementación segura para subprocesos de una colección no


ordenada de elementos.

IProducerConsumerCollection<T> Interfaz que debe implementar un tipo para su uso en


BlockingCollection .

Temas relacionados
T IT L E DESC RIP C IÓ N

Información general sobre BlockingCollection Describe la funcionalidad proporcionada por el tipo


BlockingCollection<T>.

Cómo: agregar y quitar elementos de ConcurrentDictionary Describe cómo agregar y quitar los elementos de
ConcurrentDictionary<TKey,TValue>

Cómo: agregar y tomar elementos de forma individual en una Describe cómo agregar y recuperar elementos de una
clase BlockingCollection colección de bloqueo sin utilizar el enumerador de solo
lectura.

Cómo: agregar la funcionalidad de límite y bloqueo a una Describe cómo utilizar cualquier clase de colección como
colección mecanismo de almacenamiento subyacente para una
colección IProducerConsumerCollection<T>.

Cómo: utilizar ForEach para quitar elementos de Describe cómo utilizar foreach , ( For Each en Visual Basic)
BlockingCollection para quitar todos los elementos en una colección de bloqueo.

Cómo: usar matrices de colecciones de bloqueo en una Describe cómo utilizar varias colecciones de bloqueo para
canalización implementar una canalización al mismo tiempo.
T IT L E DESC RIP C IÓ N

Cómo: crear un grupo de objetos usando ConcurrentBag Muestra cómo usar un controlador simultáneo para mejorar
el rendimiento en escenarios donde puede reutilizar objetos
en lugar de crear continuamente otros nuevos.

Referencia
System.Collections.Concurrent
Delegados y expresiones lambda
16/09/2020 • 7 minutes to read • Edit Online

Los delegados definen un tipo que especifica una firma de método concreta. Se puede asignar un método (estático
o de instancia) que satisfaga esta firma a una variable de ese tipo y luego llamarlo directamente (con los
argumentos adecuados) o pasarlo como argumento a otro método y después llamarlo. El siguiente ejemplo
muestra el uso de delegados.

using System;
using System.Linq;

public class Program


{
public delegate string Reverse(string s);

static string ReverseString(string s)


{
return new string(s.Reverse().ToArray());
}

static void Main(string[] args)


{
Reverse rev = ReverseString;

Console.WriteLine(rev("a string"));
}
}

En la línea public delegate string Reverse(string s); se crea un tipo delegado de una firma determinada, en
este caso un método que toma un parámetro de cadena y luego devuelve un parámetro de cadena.
El método static string ReverseString(string s) , que tiene exactamente la misma firma que el tipo delegado
definido, implementa el delegado.
En la línea Reverse rev = ReverseString; se muestra que se puede asignar un método a una variable del tipo de
delegado correspondiente.
En la línea Console.WriteLine(rev("a string")); se muestra cómo se usa una variable de un tipo de delegado
para invocar al delegado.
Para simplificar el proceso de desarrollo, .NET incluye un conjunto de tipos delegados que los programadores
pueden volver a usar para no tener que crear nuevos tipos. Estos tipos son Func<> , Action<> y Predicate<> , y se
pueden usar sin necesidad de definir nuevos tipos de delegado. Hay algunas diferencias entre los tres tipos que
tienen que ver con la forma en que se van a usar:
Action<> se usa cuando es necesario realizar una acción mediante los argumentos del delegado. El método
que encapsula no devuelve ningún valor.
Func<> se usa normalmente cuando se tiene una transformación a mano; es decir, cuando se necesita
transformar los argumentos del delegado en un resultado diferente. Las proyecciones son un buen ejemplo. El
método que encapsula devuelve un valor especificado.
Predicate<> se usa cuando es necesario determinar si el argumento cumple la condición del delegado.
También se puede escribir como Func<T, bool> , lo que significa que el método devuelve un valor booleano.
Ahora podemos tomar el ejemplo anterior y volver a escribirlo mediante el delegado Func<> en lugar de un tipo
personalizado. El programa seguirá ejecutándose de la misma forma.
using System;
using System.Linq;

public class Program


{
static string ReverseString(string s)
{
return new string(s.Reverse().ToArray());
}

static void Main(string[] args)


{
Func<string, string> rev = ReverseString;

Console.WriteLine(rev("a string"));
}
}

En este ejemplo sencillo, tener un método definido fuera del método Main parece un poco superfluo. .NET
Framework 2.0 ha introducido el concepto de delegados anónimos, que permiten crear delegados "insertados" sin
necesidad de especificar ningún otro tipo o método.
En el ejemplo siguiente, un delegado anónimo filtra una lista solo por los números pares y, después, los imprime
en la consola.

using System;
using System.Collections.Generic;

public class Program


{
public static void Main(string[] args)
{
List<int> list = new List<int>();

for (int i = 1; i <= 100; i++)


{
list.Add(i);
}

List<int> result = list.FindAll(


delegate (int no)
{
return (no % 2 == 0);
}
);

foreach (var item in result)


{
Console.WriteLine(item);
}
}
}

Como puede ver, el cuerpo del delegado es simplemente un conjunto de expresiones, como cualquier otro
delegado. Pero en lugar de ser una definición independiente, se ha introducido ad hoc en la llamada al método
List<T>.FindAll.
Pero incluso con este enfoque, sigue habiendo mucho código del que es posible deshacerse. Aquí es donde entran
en juego las expresiones lambda. Las expresiones lambda, o las "lambda", para abreviar, se presentaron en C# 3.0
como uno de los bloques de compilación fundamentales de Language Integrated Query (LINQ). Constituyen una
sintaxis más cómoda para el uso de delegados. Declaran una firma y un cuerpo de método, pero no tienen una
identidad formal propia, a menos que se asignen a un delegado. A diferencia de los delegados, se pueden asignar
directamente como lado izquierdo del registro de eventos, o bien en distintas cláusulas y métodos de LINQ.
Puesto que una expresión lambda es solo otra forma de especificar un delegado, debería ser posible volver a
escribir el ejemplo anterior para usar una expresión lambda en lugar de un delegado anónimo.

using System;
using System.Collections.Generic;

public class Program


{
public static void Main(string[] args)
{
List<int> list = new List<int>();

for (int i = 1; i <= 100; i++)


{
list.Add(i);
}

List<int> result = list.FindAll(i => i % 2 == 0);

foreach (var item in result)


{
Console.WriteLine(item);
}
}
}

En el ejemplo anterior, la expresión lambda que se usa es i => i % 2 == 0 . De nuevo, constituyen una sintaxis más
cómoda para el uso de delegados. Lo que sucede en segundo plano es similar a lo que ocurre con el delegado
anónimo.
De nuevo, las expresiones lambda son solo delegados, lo que significa que se pueden usar como controlador de
eventos sin problemas, como se muestra en el siguiente fragmento de código.

public MainWindow()
{
InitializeComponent();

Loaded += (o, e) =>


{
this.Title = "Loaded";
};
}

En este contexto, el operador += se usa para suscribirse a un evento. Para obtener más información, vea
Procedimiento para suscribir y cancelar la suscripción a eventos.

Más información y recursos


Delegados
Funciones anónimas
Expresiones lambda
Controlar y provocar eventos
16/09/2020 • 15 minutes to read • Edit Online

Los eventos de .NET se basan en el modelo de delegado. El modelo de delegado sigue el patrón de diseño del
observador, que permite que un suscriptor se registre con un proveedor y reciba notificaciones de él. El emisor de
un evento inserta una notificación de que se ha producido un evento, y un receptor de eventos recibe la
notificación y define una respuesta a la misma. En este artículo se describen los componentes principales del
modelo de delegado, cómo consumir eventos en las aplicaciones y cómo implementar eventos en el código.
Para obtener información sobre el control de eventos en las aplicaciones de la Tienda de Windows 8.x, vea
Introducción a eventos y eventos enrutados.

Events
Un evento es un mensaje que envía un objeto cuando ocurre una acción. La acción podría deberse a la interacción
del usuario, como hacer clic en un botón, o podría derivarse de cualquier otra lógica del programa, como el
cambio del valor de una propiedad. El objeto que provoca el evento se conoce como emisor del evento. El emisor
del evento no sabe qué objeto o método recibirá (controlará) los eventos que genera. El evento normalmente es
un miembro del emisor del evento; por ejemplo, el evento Click es un miembro de la clase Button, y el evento
PropertyChanged es un miembro de la clase que implementa la interfaz INotifyPropertyChanged.
Para definir un evento, se utiliza la palabra clave event de C# o Event de Visual Basic en la signatura de la clase
de eventos y se especifica el tipo de delegado para el evento. Los delegados se describen en la sección siguiente.
Normalmente, para generar un evento, se agrega un método marcado como protected y virtual (en C#) o
Protected y Overridable (en Visual Basic). Asigne a este método el nombre On EventName; por ejemplo,
OnDataReceived . El método debe tomar un parámetro que especifica un objeto de datos de evento, que es un
objeto de tipo EventArgs o un tipo derivado. Este método se proporciona para permitir que las clases derivadas
reemplacen la lógica para generar el evento. Una clase derivada siempre debería llamar al método On EventName
de la clase base para asegurarse de que los delegados registrados reciben el evento.
En el ejemplo siguiente se muestra cómo declarar un evento denominado ThresholdReached . El evento está
asociado al delegado EventHandler y se genera en un método denominado OnThresholdReached .

class Counter
{
public event EventHandler ThresholdReached;

protected virtual void OnThresholdReached(EventArgs e)


{
EventHandler handler = ThresholdReached;
handler?.Invoke(this, e);
}

// provide remaining implementation for the class


}
Public Class Counter
Public Event ThresholdReached As EventHandler

Protected Overridable Sub OnThresholdReached(e As EventArgs)


RaiseEvent ThresholdReached(Me, e)
End Sub

' provide remaining implementation for the class


End Class

Delegados
Un delegado es un tipo que tiene una referencia a un método. Un delegado se declara con una signatura que
muestra el tipo de valor devuelto y los parámetros para los métodos a los que hace referencia, y únicamente
puede contener referencias a los métodos que coinciden con su signatura. Por lo tanto, un delegado equivale a un
puntero a función con seguridad o a una devolución de llamada. Una declaración de delegado es suficiente para
definir una clase de delegado.
Los delegados tienen muchos usos en .NET. En el contexto de los eventos, un delegado es un intermediario (o un
mecanismo de puntero) entre el origen del evento y el código que lo controla. Para asociar un delegado a un
evento se incluye el tipo de delegado en la declaración del evento, como se muestra en el ejemplo de la sección
anterior. Para obtener más información sobre los delegados, vea la clase Delegate.
.NET proporciona los delegados EventHandler y EventHandler<TEventArgs> que admiten la mayoría de los
escenarios de eventos. Use el delegado EventHandler para todos los eventos que no incluyen datos de evento. Use
el delegado EventHandler<TEventArgs> para los eventos que incluyen datos sobre el evento. Estos delegados no
tienen ningún valor de tipo devuelto y toman dos parámetros (un objeto para el origen del evento y un objeto
para los datos del evento).
Los delegados son de multidifusión, lo que significa que pueden guardar referencias a más de un método de
control de eventos. Para obtener información detallada, vea la página de referencia de Delegate. Los delegados
permiten realizar un control de eventos más flexible y detallado. Un delegado actúa como remitente de eventos de
la clase que genera el evento y mantiene una lista de los controladores registrados para el evento.
Para los escenarios en que no funcionan los delegados EventHandler y EventHandler<TEventArgs>, puede definir
un delegado. Los escenarios para los es necesario definir un delegado son poco habituales, como cuando se debe
ejecutar código que no reconoce genéricos. Los delegados se marcan con la palabra clave delegate de C# y
Delegate de Visual Basic en la declaración. En el ejemplo siguiente se muestra cómo declarar un delegado
denominado ThresholdReachedEventHandler .

public delegate void ThresholdReachedEventHandler(object sender, ThresholdReachedEventArgs e);

Public Delegate Sub ThresholdReachedEventHandler(sender As Object, e As ThresholdReachedEventArgs)

Datos de evento
Los datos asociados a un evento se pueden proporcionar a través de una clase de datos de evento. .NET
proporciona muchas clases de datos de evento que puede utilizar en las aplicaciones. Por ejemplo, la clase
SerialDataReceivedEventArgs es la clase de datos de evento del evento SerialPort.DataReceived. En .NET se sigue
un patrón de nombres que consiste en finalizar todas las clases de datos de evento con EventArgs . Para
determinar qué clase de datos de evento está asociada a un evento, basta con examinar el delegado del evento.
Por ejemplo, el delegado SerialDataReceivedEventHandler incluye entre sus parámetros la clase
SerialDataReceivedEventArgs.
La clase EventArgs es el tipo base para todas las clases de datos de evento. EventArgs también es la clase que se
usa cuando un evento no tiene datos asociados. Cuando cree un evento que solo sirva para notificar a otras clases
que algo ha sucedido y que no necesite pasar ningún dato, incluya la clase EventArgs como segundo parámetro
del delegado. Puede pasar el valor EventArgs.Empty cuando no se proporciona ningún dato. El delegado
EventHandler incluye la clase EventArgs como parámetro.
Si desea crear una clase de datos de evento personalizada, cree una clase que se derive de EventArgs y, a
continuación, especifique los miembros que sean necesarios para pasar los datos relacionados con el evento.
Normalmente, debe usar el mismo patrón de asignación de nombres que se usa en .NET y finalizar el nombre de
la clase de los datos de evento con EventArgs .
En el ejemplo siguiente se muestra una clase de datos de evento denominada ThresholdReachedEventArgs .
Contiene propiedades específicas del evento que se genera.

public class ThresholdReachedEventArgs : EventArgs


{
public int Threshold { get; set; }
public DateTime TimeReached { get; set; }
}

Public Class ThresholdReachedEventArgs


Inherits EventArgs

Public Property Threshold As Integer


Public Property TimeReached As DateTime
End Class

Controladores de eventos
Para responder a un evento, se define un método controlador de eventos en el receptor de eventos. Este método
debe coincidir con la signatura del delegado del evento que se está controlando. En el controlador de eventos, se
realizan las acciones que es necesario llevar a cabo cuando se genera el evento, como recopilar los datos
proporcionados por el usuario cuando este hace clic en un botón. Para recibir notificaciones cuando se genera el
evento, el método controlador de eventos debe suscribirse al evento.
En el ejemplo siguiente se muestra un método de control de eventos denominado c_ThresholdReached que
coincide con la signatura del delegado EventHandler. El método se suscribe al evento ThresholdReached .

class Program
{
static void Main()
{
var c = new Counter();
c.ThresholdReached += c_ThresholdReached;

// provide remaining implementation for the class


}

static void c_ThresholdReached(object sender, EventArgs e)


{
Console.WriteLine("The threshold was reached.");
}
}
Module Module1

Sub Main()
Dim c As New Counter()
AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

' provide remaining implementation for the class


End Sub

Sub c_ThresholdReached(sender As Object, e As EventArgs)


Console.WriteLine("The threshold was reached.")
End Sub
End Module

Controladores de eventos estáticos y dinámicos


.NET permite a los suscriptores registrarse para las notificaciones de eventos estática o dinámicamente. Los
controladores de eventos estáticos son efectivos durante toda la vida de la clase cuyos eventos controlan. Los
controladores de eventos dinámicos se activan y desactivan explícitamente durante la ejecución de un programa,
normalmente en respuesta a alguna lógica condicional del programa. Por ejemplo, pueden utilizarse si las
notificaciones de eventos solo son necesarias en condiciones específicas o si una aplicación proporciona varios
controladores de eventos y las condiciones en tiempo de ejecución determinan cuál es el que debe utilizarse. En el
ejemplo de la sección anterior se muestra cómo agregar dinámicamente un controlador de eventos. Para obtener
más información, vea Eventos (en Visual Basic) y Eventos (en C#).

Generar múltiples eventos


Si la clase genera varios eventos, el compilador genera un campo por cada instancia de delegado de eventos. Si el
número de eventos es alto, es posible que el costo de almacenamiento de un campo por delegado no sea
aceptable. Para estos casos, .NET dispone de propiedades de evento que se pueden usar con otra estructura de
datos (de elección propia) para almacenar los delegados de eventos.
Las propiedades de evento están compuestas de declaraciones de evento acompañadas de descriptores de acceso
de evento. Los descriptores de acceso de eventos son métodos que se definen para agregar o quitar instancias de
delegados de eventos de la estructura de datos de almacenamiento. Hay que tener en cuenta que las propiedades
de evento son más lentas que los campos de evento, ya que se debe recuperar cada delegado de evento antes de
poder invocarlo. La memoria y la velocidad se ven afectadas. Si la clase define muchos eventos que no se
provocan con frecuencia, es posible que desee implementar propiedades de evento. Para obtener más
información, vea Cómo: Controlar varios eventos mediante las propiedades de evento.

Temas relacionados
T IT L E DESC RIP C IÓ N

Cómo: Provocar y utilizar eventos Contiene ejemplos de cómo generar y consumir eventos.

Cómo: Controlar varios eventos mediante las propiedades de Muestra cómo utilizar propiedades de evento para controlar
evento varios eventos.

Modelo de diseño de observador Describe el patrón de diseño que permite que un suscriptor se
registre con un proveedor y reciba notificaciones de dicho
proveedor.

Cómo: Consumir eventos en una aplicación de formularios Muestra cómo controlar un evento generado por un control
Web Forms de formularios Web Forms.
Vea también
EventHandler
EventHandler<TEventArgs>
EventArgs
Delegate
Eventos (Visual Basic)
Eventos (Guía de programación de C#)
Introducción a eventos y eventos enrutados (aplicaciones de UWP)
Procedimiento para provocar y consumir eventos
16/09/2020 • 6 minutes to read • Edit Online

En los ejemplos de este tema se muestra cómo trabajar con eventos. Se incluyen ejemplos del delegado
EventHandler, el delegado EventHandler<TEventArgs> y un delegado personalizado, para ilustrar eventos con y sin
datos.
En los ejemplos se usan los conceptos descritos en el artículo Eventos.

Ejemplo
En el primer ejemplo se muestra cómo generar y consumir un evento que no tiene datos. Contiene una clase
denominada Counter que tiene un evento denominado ThresholdReached . Este evento se genera cuando un valor
de contador iguala o supera el valor de umbral. El delegado EventHandler está asociado al evento, porque no se
proporcionan datos de evento.
using System;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Counter c = new Counter(new Random().Next(10));
c.ThresholdReached += c_ThresholdReached;

Console.WriteLine("press 'a' key to increase total");


while (Console.ReadKey(true).KeyChar == 'a')
{
Console.WriteLine("adding one");
c.Add(1);
}
}

static void c_ThresholdReached(object sender, EventArgs e)


{
Console.WriteLine("The threshold was reached.");
Environment.Exit(0);
}
}

class Counter
{
private int threshold;
private int total;

public Counter(int passedThreshold)


{
threshold = passedThreshold;
}

public void Add(int x)


{
total += x;
if (total >= threshold)
{
OnThresholdReached(EventArgs.Empty);
}
}

protected virtual void OnThresholdReached(EventArgs e)


{
EventHandler handler = ThresholdReached;
if (handler != null)
{
handler(this, e);
}
}

public event EventHandler ThresholdReached;


}
}
Module Module1

Sub Main()
Dim c As Counter = New Counter(New Random().Next(10))
AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

Console.WriteLine("press 'a' key to increase total")


While Console.ReadKey(True).KeyChar = "a"
Console.WriteLine("adding one")
c.Add(1)
End While
End Sub

Sub c_ThresholdReached(sender As Object, e As EventArgs)


Console.WriteLine("The threshold was reached.")
Environment.Exit(0)
End Sub
End Module

Class Counter
Private threshold As Integer
Private total As Integer

Public Sub New(passedThreshold As Integer)


threshold = passedThreshold
End Sub

Public Sub Add(x As Integer)


total = total + x
If (total >= threshold) Then
OnThresholdReached(EventArgs.Empty)
End If
End Sub

Protected Overridable Sub OnThresholdReached(e As EventArgs)


RaiseEvent ThresholdReached(Me, e)
End Sub

Public Event ThresholdReached As EventHandler


End Class

Ejemplo
En el ejemplo siguiente se muestra cómo generar y consumir un evento que proporciona datos. El delegado
EventHandler<TEventArgs> está asociado al evento, y se proporciona una instancia de un objeto de datos de
eventos personalizados.
using namespace System;

public ref class ThresholdReachedEventArgs : public EventArgs


{
public:
property int Threshold;
property DateTime TimeReached;
};

public ref class Counter


{
private:
int threshold;
int total;

public:
Counter() {};

Counter(int passedThreshold)
{
threshold = passedThreshold;
}

void Add(int x)
{
total += x;
if (total >= threshold) {
ThresholdReachedEventArgs^ args = gcnew ThresholdReachedEventArgs();
args->Threshold = threshold;
args->TimeReached = DateTime::Now;
OnThresholdReached(args);
}
}

event EventHandler<ThresholdReachedEventArgs^>^ ThresholdReached;

protected:
virtual void OnThresholdReached(ThresholdReachedEventArgs^ e)
{
ThresholdReached(this, e);
}
};

public ref class SampleHandler


{
public:
static void c_ThresholdReached(Object^ sender, ThresholdReachedEventArgs^ e)
{
Console::WriteLine("The threshold of {0} was reached at {1}.",
e->Threshold, e->TimeReached);
Environment::Exit(0);
}
};

void main()
{
Counter^ c = gcnew Counter((gcnew Random())->Next(10));
c->ThresholdReached += gcnew EventHandler<ThresholdReachedEventArgs^>(SampleHandler::c_ThresholdReached);

Console::WriteLine("press 'a' key to increase total");


while (Console::ReadKey(true).KeyChar == 'a') {
Console::WriteLine("adding one");
c->Add(1);
}
}
using System;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Counter c = new Counter(new Random().Next(10));
c.ThresholdReached += c_ThresholdReached;

Console.WriteLine("press 'a' key to increase total");


while (Console.ReadKey(true).KeyChar == 'a')
{
Console.WriteLine("adding one");
c.Add(1);
}
}

static void c_ThresholdReached(object sender, ThresholdReachedEventArgs e)


{
Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached);
Environment.Exit(0);
}
}

class Counter
{
private int threshold;
private int total;

public Counter(int passedThreshold)


{
threshold = passedThreshold;
}

public void Add(int x)


{
total += x;
if (total >= threshold)
{
ThresholdReachedEventArgs args = new ThresholdReachedEventArgs();
args.Threshold = threshold;
args.TimeReached = DateTime.Now;
OnThresholdReached(args);
}
}

protected virtual void OnThresholdReached(ThresholdReachedEventArgs e)


{
EventHandler<ThresholdReachedEventArgs> handler = ThresholdReached;
if (handler != null)
{
handler(this, e);
}
}

public event EventHandler<ThresholdReachedEventArgs> ThresholdReached;


}

public class ThresholdReachedEventArgs : EventArgs


{
public int Threshold { get; set; }
public DateTime TimeReached { get; set; }
}
}
Module Module1

Sub Main()
Dim c As Counter = New Counter(New Random().Next(10))
AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

Console.WriteLine("press 'a' key to increase total")


While Console.ReadKey(True).KeyChar = "a"
Console.WriteLine("adding one")
c.Add(1)
End While
End Sub

Sub c_ThresholdReached(sender As Object, e As ThresholdReachedEventArgs)


Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached)
Environment.Exit(0)
End Sub
End Module

Class Counter
Private threshold As Integer
Private total As Integer

Public Sub New(passedThreshold As Integer)


threshold = passedThreshold
End Sub

Public Sub Add(x As Integer)


total = total + x
If (total >= threshold) Then
Dim args As ThresholdReachedEventArgs = New ThresholdReachedEventArgs()
args.Threshold = threshold
args.TimeReached = DateTime.Now
OnThresholdReached(args)
End If
End Sub

Protected Overridable Sub OnThresholdReached(e As ThresholdReachedEventArgs)


RaiseEvent ThresholdReached(Me, e)
End Sub

Public Event ThresholdReached As EventHandler(Of ThresholdReachedEventArgs)


End Class

Class ThresholdReachedEventArgs
Inherits EventArgs

Public Property Threshold As Integer


Public Property TimeReached As DateTime
End Class

Ejemplo
En el ejemplo siguiente se muestra cómo declarar un delegado para un evento. El delegado se denomina
ThresholdReachedEventHandler . Esto es simplemente una ilustración. Normalmente no es necesario declarar un
delegado para un evento, porque se puede usar el delegado EventHandler o EventHandler<TEventArgs>. Solo
debe declararse un delegado en escenarios poco habituales, como cuando se facilita el uso de una clase a código
heredado que no puede usar elementos genéricos.

using System;

namespace ConsoleApplication1
{
class Program
class Program
{
static void Main(string[] args)
{
Counter c = new Counter(new Random().Next(10));
c.ThresholdReached += c_ThresholdReached;

Console.WriteLine("press 'a' key to increase total");


while (Console.ReadKey(true).KeyChar == 'a')
{
Console.WriteLine("adding one");
c.Add(1);
}
}

static void c_ThresholdReached(Object sender, ThresholdReachedEventArgs e)


{
Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached);
Environment.Exit(0);
}
}

class Counter
{
private int threshold;
private int total;

public Counter(int passedThreshold)


{
threshold = passedThreshold;
}

public void Add(int x)


{
total += x;
if (total >= threshold)
{
ThresholdReachedEventArgs args = new ThresholdReachedEventArgs();
args.Threshold = threshold;
args.TimeReached = DateTime.Now;
OnThresholdReached(args);
}
}

protected virtual void OnThresholdReached(ThresholdReachedEventArgs e)


{
ThresholdReachedEventHandler handler = ThresholdReached;
if (handler != null)
{
handler(this, e);
}
}

public event ThresholdReachedEventHandler ThresholdReached;


}

public class ThresholdReachedEventArgs : EventArgs


{
public int Threshold { get; set; }
public DateTime TimeReached { get; set; }
}

public delegate void ThresholdReachedEventHandler(Object sender, ThresholdReachedEventArgs e);


}
Module Module1

Sub Main()
Dim c As Counter = New Counter(New Random().Next(10))
AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

Console.WriteLine("press 'a' key to increase total")


While Console.ReadKey(True).KeyChar = "a"
Console.WriteLine("adding one")
c.Add(1)
End While
End Sub

Sub c_ThresholdReached(sender As Object, e As ThresholdReachedEventArgs)


Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached)
Environment.Exit(0)
End Sub
End Module

Class Counter
Private threshold As Integer
Private total As Integer

Public Sub New(passedThreshold As Integer)


threshold = passedThreshold
End Sub

Public Sub Add(x As Integer)


total = total + x
If (total >= threshold) Then
Dim args As ThresholdReachedEventArgs = New ThresholdReachedEventArgs()
args.Threshold = threshold
args.TimeReached = DateTime.Now
OnThresholdReached(args)
End If
End Sub

Protected Overridable Sub OnThresholdReached(e As ThresholdReachedEventArgs)


RaiseEvent ThresholdReached(Me, e)
End Sub

Public Event ThresholdReached As ThresholdReachedEventHandler


End Class

Public Class ThresholdReachedEventArgs


Inherits EventArgs

Public Property Threshold As Integer


Public Property TimeReached As DateTime
End Class

Public Delegate Sub ThresholdReachedEventHandler(sender As Object, e As ThresholdReachedEventArgs)

Vea también
Eventos
Procedimiento para controlar varios eventos
mediante las propiedades de evento
16/09/2020 • 6 minutes to read • Edit Online

Para poder utilizar propiedades de evento, hay que definirlas en la clase que provoca los eventos y, a continuación,
establecer los delegados de las propiedades de evento en las clases que controlan los eventos. Para implementar
varias propiedades de evento en una clase, esta clase deberá almacenar internamente y mantener el delegado
definido para cada evento. Para hacerlo, uno de los enfoques típicos consiste en implementar una colección de
delegados que se indice por medio de una clave de evento.
Para almacenar los delegados de cada evento, puede utilizar la clase EventHandlerList o implementar su propia
colección. La clase de colección debe proporcionar métodos para establecer, obtener acceso y recuperar el
delegado del controlador de eventos basándose en la clave del evento. Por ejemplo, se puede utilizar una clase
Hashtable o derivar una clase personalizada a partir de la clase DictionaryBase. No es necesario exponer los
detalles de implementación de la colección de delegados fuera de la clase.
Cada una de las propiedades de evento de la clase define un método de descriptor de acceso add y un método de
descriptor de acceso remove. El descriptor de acceso add de una propiedad de evento agrega la instancia de
delegado de entrada a la colección de delegados. El descriptor de acceso remove de una propiedad de evento
quita la instancia de delegado de entrada de la colección de delegados. Los descriptores de acceso de las
propiedades de eventos utilizan la clave predefinida de la propiedad de evento para agregar y quitar instancias en
la colección de delegados.
Para controlar varios eventos mediante las propiedades de evento
1. Defina una colección de delegados dentro de la clase que provoca los eventos.
2. Defina una clave para cada evento.
3. Defina las propiedades de evento de la clase que provoca los eventos.
4. Utilice la colección de delegados para implementar los métodos de descriptor de acceso add y remove de
las propiedades de evento.
5. Utilice las propiedades de evento públicas para agregar y quitar delegados de controlador de eventos en las
clases que controlan los eventos.

Ejemplo
En el siguiente ejemplo de C#, se implementan las propiedades de evento MouseDown y MouseUp mediante el uso
de EventHandlerList para almacenar el delegado de cada evento. Las palabras clave de las construcciones de
propiedades de evento están en negrita.
// The class SampleControl defines two event properties, MouseUp and MouseDown.
ref class SampleControl : Component
{
// :
// Define other control methods and properties.
// :

// Define the delegate collection.


protected:
EventHandlerList^ listEventDelegates;

private:
// Define a unique key for each event.
static Object^ mouseDownEventKey = gcnew Object();
static Object^ mouseUpEventKey = gcnew Object();

// Define the MouseDown event property.


public:
SampleControl()
{
listEventDelegates = gcnew EventHandlerList();
}

event MouseEventHandler^ MouseDown


{
// Add the input delegate to the collection.
void add(MouseEventHandler^ value)
{
listEventDelegates->AddHandler(mouseDownEventKey, value);
}
// Remove the input delegate from the collection.
void remove(MouseEventHandler^ value)
{
listEventDelegates->RemoveHandler(mouseDownEventKey, value);
}
// Raise the event with the delegate specified by mouseDownEventKey
void raise(Object^ sender, MouseEventArgs^ e)
{
MouseEventHandler^ mouseEventDelegate =
(MouseEventHandler^)listEventDelegates[mouseDownEventKey];
mouseEventDelegate(sender, e);
}
}

// Define the MouseUp event property.


event MouseEventHandler^ MouseUp
{
// Add the input delegate to the collection.
void add(MouseEventHandler^ value)
{
listEventDelegates->AddHandler(mouseUpEventKey, value);
}
// Remove the input delegate from the collection.
void remove(MouseEventHandler^ value)
{
listEventDelegates->RemoveHandler(mouseUpEventKey, value);
}
// Raise the event with the delegate specified by mouseUpEventKey
void raise(Object^ sender, MouseEventArgs^ e)
{
MouseEventHandler^ mouseEventDelegate =
(MouseEventHandler^)listEventDelegates[mouseUpEventKey];
mouseEventDelegate(sender, e);
}
}
};
// The class SampleControl defines two event properties, MouseUp and MouseDown.
class SampleControl : Component
{
// :
// Define other control methods and properties.
// :

// Define the delegate collection.


protected EventHandlerList listEventDelegates = new EventHandlerList();

// Define a unique key for each event.


static readonly object mouseDownEventKey = new object();
static readonly object mouseUpEventKey = new object();

// Define the MouseDown event property.


public event MouseEventHandler MouseDown
{
// Add the input delegate to the collection.
add
{
listEventDelegates.AddHandler(mouseDownEventKey, value);
}
// Remove the input delegate from the collection.
remove
{
listEventDelegates.RemoveHandler(mouseDownEventKey, value);
}
}

// Raise the event with the delegate specified by mouseDownEventKey


private void OnMouseDown(MouseEventArgs e)
{
MouseEventHandler mouseEventDelegate =
(MouseEventHandler)listEventDelegates[mouseDownEventKey];
mouseEventDelegate(this, e);
}

// Define the MouseUp event property.


public event MouseEventHandler MouseUp
{
// Add the input delegate to the collection.
add
{
listEventDelegates.AddHandler(mouseUpEventKey, value);
}
// Remove the input delegate from the collection.
remove
{
listEventDelegates.RemoveHandler(mouseUpEventKey, value);
}
}

// Raise the event with the delegate specified by mouseUpEventKey


private void OnMouseUp(MouseEventArgs e)
{
MouseEventHandler mouseEventDelegate =
(MouseEventHandler)listEventDelegates[mouseUpEventKey];
mouseEventDelegate(this, e);
}
}
' The class SampleControl defines two event properties, MouseUp and MouseDown.
Class SampleControl
Inherits Component
' :
' Define other control methods and properties.
' :

' Define the delegate collection.


Protected listEventDelegates As New EventHandlerList()

' Define a unique key for each event.


Shared ReadOnly mouseDownEventKey As New Object()
Shared ReadOnly mouseUpEventKey As New Object()

' Define the MouseDown event property.


Public Custom Event MouseDown As MouseEventHandler
' Add the input delegate to the collection.
AddHandler(Value As MouseEventHandler)
listEventDelegates.AddHandler(mouseDownEventKey, Value)
End AddHandler
' Remove the input delegate from the collection.
RemoveHandler(Value As MouseEventHandler)
listEventDelegates.RemoveHandler(mouseDownEventKey, Value)
End RemoveHandler
' Raise the event with the delegate specified by mouseDownEventKey
RaiseEvent(sender As Object, e As MouseEventArgs)
Dim mouseEventDelegate As MouseEventHandler = _
listEventDelegates(mouseDownEventKey)
mouseEventDelegate(sender, e)
End RaiseEvent
End Event

' Define the MouseUp event property.


Public Custom Event MouseUp As MouseEventHandler
' Add the input delegate to the collection.
AddHandler(Value As MouseEventHandler)
listEventDelegates.AddHandler(mouseUpEventKey, Value)
End AddHandler
' Remove the input delegate from the collection.
RemoveHandler(Value As MouseEventHandler)
listEventDelegates.RemoveHandler(mouseUpEventKey, Value)
End RemoveHandler
' Raise the event with the delegate specified by mouseDownUpKey
RaiseEvent(sender As Object, e As MouseEventArgs)
Dim mouseEventDelegate As MouseEventHandler = _
listEventDelegates(mouseUpEventKey)
mouseEventDelegate(sender, e)
End RaiseEvent
End Event
End Class

Vea también
System.ComponentModel.EventHandlerList
Eventos
Control.Events
Cómo: Declarar eventos personalizados para conservar memoria
Modelo de diseño de observador
16/09/2020 • 21 minutes to read • Edit Online

El modelo de diseño de observador permite que un suscriptor se registre con un proveedor y reciba notificaciones
de dicho proveedor. Este modelo es adecuado para cualquier escenario que requiera notificaciones push. El
modelo define un proveedor (también conocido como un tema o una observable) y cero, uno o más
observadores. Los observadores se registran con el proveedor y siempre que se produce una condición
predefinida, un evento o un cambio de estado, el proveedor notifica automáticamente a todos los observadores
mediante la llamada a uno de sus métodos. En esta llamada al método, el proveedor puede proporcionar también
información sobre el estado actual a los observadores. En .NET Framework, el modelo de diseño de observador se
aplica con la implementación de las interfaces genéricas System.IObservable<T> y System.IObserver<T>. El
parámetro de tipo genérico representa el tipo que proporciona información de notificación.

Aplicación del modelo


El modelo de diseño de observador es adecuado para las notificaciones distribuidas mediante push, ya que admite
una separación clara entre dos componentes diferentes o capas de aplicación, como una capa de origen de datos
(lógica de negocios) y una capa de interfaz de usuario (pantalla). El modelo puede implementarse cada vez que un
proveedor usa devoluciones de llamada para proporcionar información actual a sus clientes.
Para implementar el modelo, es necesario proporcionar lo siguiente:
Un proveedor o un tema, que es el objeto que envía notificaciones a los observadores. Un proveedor es
una clase o estructura que implementa la interfaz IObservable<T>. El proveedor debe implementar un
único método, IObservable<T>.Subscribe, al que llaman los observadores que desean recibir notificaciones
del proveedor.
Un observador, que es un objeto que recibe notificaciones de un proveedor. Un observador es una clase o
estructura que implementa la interfaz IObserver<T>. El observador debe implementar tres métodos, a los
que llama el proveedor:
IObserver<T>.OnNext, que proporciona al observador información nueva o actual.
IObserver<T>.OnError, que informa al observador de que se ha producido un error.
IObserver<T>.OnCompleted, que indica que el proveedor ha terminado de enviar notificaciones.
Un mecanismo que permite al proveedor realizar el seguimiento de observadores. Normalmente, el
proveedor usa un objeto contenedor, como System.Collections.Generic.List<T>, para mantener las
referencias a las implementaciones de IObserver<T> que se han suscrito a las notificaciones. Gracias a un
contenedor de almacenamiento específico para este fin, el proveedor puede controlar desde cero a un
número ilimitado de observadores. El orden en que los observadores reciben las notificaciones no está
definido y el proveedor es libre de usar cualquier método para determinar el orden.
Una implementación de IDisposable que permite al proveedor quitar los observadores cuando se completa
la notificación. Los observadores reciben una referencia a la implementación de IDisposable desde el
método Subscribe, por lo que también pueden llamar al método IDisposable.Dispose para cancelar la
suscripción antes de que el proveedor termine de enviar las notificaciones.
Un objeto que contiene los datos que el proveedor envía a sus observadores. El tipo de este objeto se
corresponde con el parámetro de tipo genérico de las interfaces IObservable<T> y IObserver<T>. Aunque
este objeto puede ser el mismo que la implementación de IObservable<T>, normalmente es un tipo
diferente.
NOTE
Además de implementar el modelo de diseño de observador, es posible que le interese explorar las bibliotecas que se
compilan con las interfaces IObservable<T> y IObserver<T>. Por ejemplo, las extensiones reactivas para .NET (Rx) constan
de un conjunto de métodos de extensión y los operadores de secuencia estándar de LINQ para admitir la programación
asincrónica.

Implementación del modelo


En el ejemplo siguiente se usa el modelo de diseño de observador para implementar un sistema de información
de recogida de equipaje en un aeropuerto. Una clase BaggageInfo proporciona información sobre la llegada de
los vuelos y la cinta de recogida de equipaje correspondiente a cada vuelo. Esta implementación se muestra en el
ejemplo siguiente.

using System;
using System.Collections.Generic;

public class BaggageInfo


{
private int flightNo;
private string origin;
private int location;

internal BaggageInfo(int flight, string from, int carousel)


{
this.flightNo = flight;
this.origin = from;
this.location = carousel;
}

public int FlightNumber {


get { return this.flightNo; }
}

public string From {


get { return this.origin; }
}

public int Carousel {


get { return this.location; }
}
}
Public Class BaggageInfo
Private flightNo As Integer
Private origin As String
Private location As Integer

Friend Sub New(ByVal flight As Integer, ByVal from As String, ByVal carousel As Integer)
Me.flightNo = flight
Me.origin = from
Me.location = carousel
End Sub

Public ReadOnly Property FlightNumber As Integer


Get
Return Me.flightNo
End Get
End Property

Public ReadOnly Property From As String


Get
Return Me.origin
End Get
End Property

Public ReadOnly Property Carousel As Integer


Get
Return Me.location
End Get
End Property
End Class

Una clase BaggageHandler es la responsable de recibir la información sobre la llegada de los vuelos y las cintas de
recogida de equipaje. Internamente, mantiene dos colecciones:
observers : colección de clientes que recibirán información actualizada.
flights : colección de los vuelos y sus cintas asignadas.

Ambas colecciones están representadas por objetos List<T> genéricos de los que se crean instancias en el
constructor de clase BaggageHandler . El código fuente de la clase BaggageHandler se muestra en el ejemplo
siguiente.
public class BaggageHandler : IObservable<BaggageInfo>
{
private List<IObserver<BaggageInfo>> observers;
private List<BaggageInfo> flights;

public BaggageHandler()
{
observers = new List<IObserver<BaggageInfo>>();
flights = new List<BaggageInfo>();
}

public IDisposable Subscribe(IObserver<BaggageInfo> observer)


{
// Check whether observer is already registered. If not, add it
if (! observers.Contains(observer)) {
observers.Add(observer);
// Provide observer with existing data.
foreach (var item in flights)
observer.OnNext(item);
}
return new Unsubscriber<BaggageInfo>(observers, observer);
}

// Called to indicate all baggage is now unloaded.


public void BaggageStatus(int flightNo)
{
BaggageStatus(flightNo, String.Empty, 0);
}

public void BaggageStatus(int flightNo, string from, int carousel)


{
var info = new BaggageInfo(flightNo, from, carousel);

// Carousel is assigned, so add new info object to list.


if (carousel > 0 && ! flights.Contains(info)) {
flights.Add(info);
foreach (var observer in observers)
observer.OnNext(info);
}
else if (carousel == 0) {
// Baggage claim for flight is done
var flightsToRemove = new List<BaggageInfo>();
foreach (var flight in flights) {
if (info.FlightNumber == flight.FlightNumber) {
flightsToRemove.Add(flight);
foreach (var observer in observers)
observer.OnNext(info);
}
}
foreach (var flightToRemove in flightsToRemove)
flights.Remove(flightToRemove);

flightsToRemove.Clear();
}
}

public void LastBaggageClaimed()


{
foreach (var observer in observers)
observer.OnCompleted();

observers.Clear();
}
}
Public Class BaggageHandler : Implements IObservable(Of BaggageInfo)

Private observers As List(Of IObserver(Of BaggageInfo))


Private flights As List(Of BaggageInfo)

Public Sub New()


observers = New List(Of IObserver(Of BaggageInfo))
flights = New List(Of BaggageInfo)
End Sub

Public Function Subscribe(ByVal observer As IObserver(Of BaggageInfo)) As IDisposable _


Implements IObservable(Of BaggageInfo).Subscribe
' Check whether observer is already registered. If not, add it
If Not observers.Contains(observer) Then
observers.Add(observer)
' Provide observer with existing data.
For Each item In flights
observer.OnNext(item)
Next
End If
Return New Unsubscriber(Of BaggageInfo)(observers, observer)
End Function

' Called to indicate all baggage is now unloaded.


Public Sub BaggageStatus(ByVal flightNo As Integer)
BaggageStatus(flightNo, String.Empty, 0)
End Sub

Public Sub BaggageStatus(ByVal flightNo As Integer, ByVal from As String, ByVal carousel As Integer)
Dim info As New BaggageInfo(flightNo, from, carousel)

' Carousel is assigned, so add new info object to list.


If carousel > 0 And Not flights.Contains(info) Then
flights.Add(info)
For Each observer In observers
observer.OnNext(info)
Next
ElseIf carousel = 0 Then
' Baggage claim for flight is done
Dim flightsToRemove As New List(Of BaggageInfo)
For Each flight In flights
If info.FlightNumber = flight.FlightNumber Then
flightsToRemove.Add(flight)
For Each observer In observers
observer.OnNext(info)
Next
End If
Next
For Each flightToRemove In flightsToRemove
flights.Remove(flightToRemove)
Next
flightsToRemove.Clear()
End If
End Sub

Public Sub LastBaggageClaimed()


For Each observer In observers
observer.OnCompleted()
Next
observers.Clear()
End Sub
End Class

Los clientes que deseen recibir información actualizada llaman al método BaggageHandler.Subscribe . Si el cliente
no se ha suscrito previamente a las notificaciones, en la colección de observers se agrega una referencia a la
implementación de IObserver<T> del cliente.
Se puede llamar al método BaggageHandler.BaggageStatus sobrecargado para indicar que el equipaje de un vuelo
se está descargando o ya no se está descargando. En el primer caso, se pasa al método un número de vuelo, el
aeropuerto de origen del vuelo y la cinta en la que se está descargando el equipaje. En el segundo caso, solo se
pasa al método un número de vuelo. Para el equipaje que se está descargado, el método comprueba si la
información BaggageInfo que se pasa al método existe en la colección flights . Si no es así, el método agrega la
información y llama al método OnNext de cada observador. Para los vuelos cuyo equipaje ya no se está
descargando, el método comprueba si la información del vuelo se almacena en la colección flights . Si es así, el
método llama al método OnNext de cada observador y quita el objeto BaggageInfo de la colección flights .
Una vez que aterrice el último vuelo del día y se procese su equipaje, se llama al método
BaggageHandler.LastBaggageClaimed . Este método llama al método OnCompleted de cada observador para indicar
que se han completado todas las notificaciones y, a continuación, borra la colección observers .
El método Subscribe del proveedor devuelve una implementación IDisposable que permite a los observadores
dejar de recibir notificaciones antes de que se llame al método OnCompleted. El código fuente para esta clase
Unsubscriber(Of BaggageInfo) se muestra en el ejemplo siguiente. Cuando se crea una instancia de la clase en el
método BaggageHandler.Subscribe , se pasa una referencia a la colección observers y una referencia al observador
que se agrega a la colección. Estas referencias se asignan a variables locales. Cuando se llama al método Dispose
del objeto, comprueba si el observador todavía existe en la colección observers y, si es así, se quita el observador.

internal class Unsubscriber<BaggageInfo> : IDisposable


{
private List<IObserver<BaggageInfo>> _observers;
private IObserver<BaggageInfo> _observer;

internal Unsubscriber(List<IObserver<BaggageInfo>> observers, IObserver<BaggageInfo> observer)


{
this._observers = observers;
this._observer = observer;
}

public void Dispose()


{
if (_observers.Contains(_observer))
_observers.Remove(_observer);
}
}

Friend Class Unsubscriber(Of BaggageInfo) : Implements IDisposable


Private _observers As List(Of IObserver(Of BaggageInfo))
Private _observer As IObserver(Of BaggageInfo)

Friend Sub New(ByVal observers As List(Of IObserver(Of BaggageInfo)), ByVal observer As IObserver(Of
BaggageInfo))
Me._observers = observers
Me._observer = observer
End Sub

Public Sub Dispose() Implements IDisposable.Dispose


If _observers.Contains(_observer) Then
_observers.Remove(_observer)
End If
End Sub
End Class

En el ejemplo siguiente se proporciona una implementación de IObserver<T> denominada ArrivalsMonitor , que


es una clase base que muestra información sobre la recogida de equipaje. La información se muestra por orden
alfabético, según el nombre de la ciudad de origen. Los métodos de ArrivalsMonitor se marcan como
overridable (en Visual Basic) o virtual (en C#), por lo que se pueden invalidar mediante una clase derivada.

using System;
using System.Collections.Generic;

public class ArrivalsMonitor : IObserver<BaggageInfo>


{
private string name;
private List<string> flightInfos = new List<string>();
private IDisposable cancellation;
private string fmt = "{0,-20} {1,5} {2, 3}";

public ArrivalsMonitor(string name)


{
if (String.IsNullOrEmpty(name))
throw new ArgumentNullException("The observer must be assigned a name.");

this.name = name;
}

public virtual void Subscribe(BaggageHandler provider)


{
cancellation = provider.Subscribe(this);
}

public virtual void Unsubscribe()


{
cancellation.Dispose();
flightInfos.Clear();
}

public virtual void OnCompleted()


{
flightInfos.Clear();
}

// No implementation needed: Method is not called by the BaggageHandler class.


public virtual void OnError(Exception e)
{
// No implementation.
}

// Update information.
public virtual void OnNext(BaggageInfo info)
{
bool updated = false;

// Flight has unloaded its baggage; remove from the monitor.


if (info.Carousel == 0) {
var flightsToRemove = new List<string>();
string flightNo = String.Format("{0,5}", info.FlightNumber);

foreach (var flightInfo in flightInfos) {


if (flightInfo.Substring(21, 5).Equals(flightNo)) {
flightsToRemove.Add(flightInfo);
updated = true;
}
}
foreach (var flightToRemove in flightsToRemove)
flightInfos.Remove(flightToRemove);

flightsToRemove.Clear();
}
else {
// Add flight if it does not exist in the collection.
string flightInfo = String.Format(fmt, info.From, info.FlightNumber, info.Carousel);
if (! flightInfos.Contains(flightInfo)) {
flightInfos.Add(flightInfo);
flightInfos.Add(flightInfo);
updated = true;
}
}
if (updated) {
flightInfos.Sort();
Console.WriteLine("Arrivals information from {0}", this.name);
foreach (var flightInfo in flightInfos)
Console.WriteLine(flightInfo);

Console.WriteLine();
}
}
}

Public Class ArrivalsMonitor : Implements IObserver(Of BaggageInfo)


Private name As String
Private flightInfos As New List(Of String)
Private cancellation As IDisposable
Private fmt As String = "{0,-20} {1,5} {2, 3}"

Public Sub New(ByVal name As String)


If String.IsNullOrEmpty(name) Then Throw New ArgumentNullException("The observer must be assigned a
name.")

Me.name = name
End Sub

Public Overridable Sub Subscribe(ByVal provider As BaggageHandler)


cancellation = provider.Subscribe(Me)
End Sub

Public Overridable Sub Unsubscribe()


cancellation.Dispose()
flightInfos.Clear()
End Sub

Public Overridable Sub OnCompleted() Implements System.IObserver(Of BaggageInfo).OnCompleted


flightInfos.Clear()
End Sub

' No implementation needed: Method is not called by the BaggageHandler class.


Public Overridable Sub OnError(ByVal e As System.Exception) Implements System.IObserver(Of
BaggageInfo).OnError
' No implementation.
End Sub

' Update information.


Public Overridable Sub OnNext(ByVal info As BaggageInfo) Implements System.IObserver(Of
BaggageInfo).OnNext
Dim updated As Boolean = False

' Flight has unloaded its baggage; remove from the monitor.
If info.Carousel = 0 Then
Dim flightsToRemove As New List(Of String)
Dim flightNo As String = String.Format("{0,5}", info.FlightNumber)
For Each flightInfo In flightInfos
If flightInfo.Substring(21, 5).Equals(flightNo) Then
flightsToRemove.Add(flightInfo)
updated = True
End If
Next
For Each flightToRemove In flightsToRemove
flightInfos.Remove(flightToRemove)
Next
flightsToRemove.Clear()
Else
' Add flight if it does not exist in the collection.
Dim flightInfo As String = String.Format(fmt, info.From, info.FlightNumber, info.Carousel)
If Not flightInfos.Contains(flightInfo) Then
flightInfos.Add(flightInfo)
updated = True
End If
End If
If updated Then
flightInfos.Sort()
Console.WriteLine("Arrivals information from {0}", Me.name)
For Each flightInfo In flightInfos
Console.WriteLine(flightInfo)
Next
Console.WriteLine()
End If
End Sub
End Class

La clase ArrivalsMonitor contiene los métodos Subscribe y Unsubscribe . El método Subscribe permite que la
clase guarde la implementación de IDisposable devuelta por la llamada a Subscribe a una variable privada. El
método Unsubscribe permite que la clase cancele la suscripción a notificaciones mediante una llamada a la
implementación de Dispose del proveedor. ArrivalsMonitor también proporciona implementaciones de los
métodos OnNext, OnError y OnCompleted. Tan solo la implementación de OnNext contiene una cantidad
significativa de código. El método funciona con un objeto List<T> privado, ordenado y genérico que posee
información sobre los aeropuertos de origen de los vuelos de llegada y las cintas en las que está disponible el
correspondiente equipaje. Si la clase BaggageHandler notifica la llegada de un nuevo vuelo, la implementación del
método OnNext agrega información sobre ese vuelo a la lista. Si la clase BaggageHandler informa de que el
equipaje del vuelo se ha descargado, el método OnNext quita este vuelo de la lista. Cada vez que se produce un
cambio, la lista se ordena y se muestra en la consola.
El ejemplo siguiente contiene el punto de entrada de la aplicación que crea una instancia de la clase
BaggageHandler , así como dos instancias de la clase ArrivalsMonitor , y usa el método
BaggageHandler.BaggageStatus para agregar y quitar información sobre los vuelos de llegada. En cada caso, los
observadores reciben actualizaciones y muestran correctamente la información de recogida de equipaje.

using System;
using System.Collections.Generic;

public class Example


{
public static void Main()
{
BaggageHandler provider = new BaggageHandler();
ArrivalsMonitor observer1 = new ArrivalsMonitor("BaggageClaimMonitor1");
ArrivalsMonitor observer2 = new ArrivalsMonitor("SecurityExit");

provider.BaggageStatus(712, "Detroit", 3);


observer1.Subscribe(provider);
provider.BaggageStatus(712, "Kalamazoo", 3);
provider.BaggageStatus(400, "New York-Kennedy", 1);
provider.BaggageStatus(712, "Detroit", 3);
observer2.Subscribe(provider);
provider.BaggageStatus(511, "San Francisco", 2);
provider.BaggageStatus(712);
observer2.Unsubscribe();
provider.BaggageStatus(400);
provider.LastBaggageClaimed();
}
}
// The example displays the following output:
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
//
// Arrivals information from BaggageClaimMonitor1
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
//
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
//
// Arrivals information from SecurityExit
// Detroit 712 3
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
//
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from BaggageClaimMonitor1
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from SecurityExit
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from BaggageClaimMonitor1
// San Francisco 511 2
Module Example
Public Sub Main()
Dim provider As New BaggageHandler()
Dim observer1 As New ArrivalsMonitor("BaggageClaimMonitor1")
Dim observer2 As New ArrivalsMonitor("SecurityExit")

provider.BaggageStatus(712, "Detroit", 3)
observer1.Subscribe(provider)
provider.BaggageStatus(712, "Kalamazoo", 3)
provider.BaggageStatus(400, "New York-Kennedy", 1)
provider.BaggageStatus(712, "Detroit", 3)
observer2.Subscribe(provider)
provider.BaggageStatus(511, "San Francisco", 2)
provider.BaggageStatus(712)
observer2.Unsubscribe()
provider.BaggageStatus(400)
provider.LastBaggageClaimed()
End Sub
End Module
' The example displays the following output:
' Arrivals information from BaggageClaimMonitor1
' Detroit 712 3
'
' Arrivals information from BaggageClaimMonitor1
' Detroit 712 3
' Kalamazoo 712 3
'
' Arrivals information from BaggageClaimMonitor1
' Detroit 712 3
' Kalamazoo 712 3
' New York-Kennedy 400 1
'
' Arrivals information from SecurityExit
' Detroit 712 3
'
' Arrivals information from SecurityExit
' Detroit 712 3
' Kalamazoo 712 3
'
' Arrivals information from SecurityExit
' Detroit 712 3
' Kalamazoo 712 3
' New York-Kennedy 400 1
'
' Arrivals information from BaggageClaimMonitor1
' Detroit 712 3
' Kalamazoo 712 3
' New York-Kennedy 400 1
' San Francisco 511 2
'
' Arrivals information from SecurityExit
' Detroit 712 3
' Kalamazoo 712 3
' New York-Kennedy 400 1
' San Francisco 511 2
'
' Arrivals information from BaggageClaimMonitor1
' New York-Kennedy 400 1
' San Francisco 511 2
'
' Arrivals information from SecurityExit
' New York-Kennedy 400 1
' San Francisco 511 2
'
' Arrivals information from BaggageClaimMonitor1
' San Francisco 511 2
Temas relacionados
T IT L E DESC RIP C IÓ N

Procedimientos recomendados para modelos de diseño de Describe los procedimientos recomendados que deben
observador adoptarse en el desarrollo de aplicaciones que implementan el
modelo de diseño de observador.

Cómo: Implementar un proveedor Proporciona una implementación detallada de un proveedor


para una aplicación de supervisión de temperatura.

Cómo: Implementar un observador Proporciona una implementación detallada de un observador


para una aplicación de supervisión de temperatura.
Procedimientos recomendados para modelos de
diseño de observador
16/09/2020 • 7 minutes to read • Edit Online

En .NET Framework, el patrón de diseño de observador se implementa como un conjunto de interfaces. La interfaz
System.IObservable<T> representa al proveedor de datos, que también es responsable de proporcionar una
implementación IDisposable que permite a los observadores cancelar la suscripción a las notificaciones. La interfaz
System.IObserver<T> representa al observador. En este tema se describen los procedimientos recomendados que
los desarrolladores deben seguir al implementar el patrón de diseño de observador con estas interfaces.

Subprocesos
Normalmente, para implementar el método IObservable<T>.Subscribe, un proveedor agrega un observador
determinado a una lista de suscriptores que se representa mediante un objeto de colección; por su parte, para
implementar el método IDisposable.Dispose, el proveedor quita un observador determinado de la lista de
suscriptores. Un observador puede llamar a estos métodos en cualquier momento. Además, dado que el contrato
de proveedor/observador no especifica quién es el responsable de cancelar la suscripción después del método de
devolución de llamada IObserver<T>.OnCompleted, el proveedor y el observador pueden intentar quitar el mismo
miembro de la lista. Debido a esta posibilidad, ambos métodos Subscribe y Dispose deben ser seguros para
subprocesos. Normalmente, esto implica el uso de una colección simultánea o un bloqueo. Las implementaciones
que no son seguras para subprocesos deben documentar explícitamente que no lo son.
Las posibles garantías adicionales deben especificarse en un nivel por encima del contrato de
proveedor/observador. Los implementadores deben dejar claro que imponen requisitos adicionales para evitar
confusiones al usuario sobre el contrato de observador.

Controlar las excepciones


Debido al acoplamiento flexible entre un proveedor de datos y un observador, las excepciones en el patrón de
diseño de observador tienen un carácter informativo. Esto afecta a cómo los proveedores y observadores
controlan las excepciones en el patrón de diseño de observador.
El proveedor: llamada al método OnError
El propósito del método OnError es servir de mensaje informativo para el observador, de modo parecido al
método IObserver<T>.OnNext. Sin embargo, el método OnNext está diseñado para proporcionarle a un
observador información actual o actualizada, mientras que el método OnError está diseñado para indicar que el
proveedor no puede proporcionar datos válidos.
El proveedor debe seguir estas recomendaciones a la hora de controlar las excepciones y llamar al método
OnError:
El proveedor debe controlar sus propias excepciones si tiene requisitos específicos.
El proveedor no debe esperar ni exigir que los observadores controlen las excepciones en ningún modo en
particular.
El proveedor debe llamar al método OnError cuando controla una excepción que pone en peligro su
capacidad para proporcionar actualizaciones. La información sobre este tipo de excepciones se puede pasar
al observador. En otros casos, no es necesario informar a los observadores de una excepción.
Una vez que el proveedor llama al método OnError o IObserver<T>.OnCompleted, no debe haber más
notificaciones y el proveedor puede cancelar la suscripción de sus observadores. No obstante, también los
observadores pueden cancelar ellos mismos su suscripción en cualquier momento, incluso antes y después de
recibir una notificación OnError o IObserver<T>.OnCompleted. El patrón de diseño de observador no indica si el
proveedor o el observador son responsables de cancelar la suscripción; por lo tanto, es posible que ambos traten
de cancelar la suscripción. Normalmente, cuando los observadores cancelan su suscripción, se quitan de una
colección de los suscriptores. En una aplicación de un único subproceso, la implementación de IDisposable.Dispose
debe asegurarse de la validez de una referencia de objeto y de que el objeto es miembro de la colección de
suscriptores antes de intentar quitarlo. En una aplicación multiproceso, debe utilizarse un objeto de colección
seguro para subprocesos, por ejemplo, un objeto System.Collections.Concurrent.BlockingCollection<T>.
El observador: implementación del método OnError
Cuando un observador recibe una notificación de error de un proveedor, debe considerar solo el valor informativo
de la excepción sin que sea necesario realizar ninguna acción especial.
El observador debe seguir estas recomendaciones al responder a una llamada de método OnError de un
proveedor:
El observador no debe generar excepciones desde sus implementaciones de interfaz, como OnNext o
OnError. No obstante, si el observador genera excepciones, no debe esperar que se controlen.
Para conservar la pila de llamadas, un observador que desee producir un objeto Exception pasado a su
método OnError, debe encapsular la excepción antes generarla. Para este propósito, debe usarse un objeto
de excepción estándar.

Recomendaciones adicionales
Intentar anular el registro en el método IObservable<T>.Subscribe puede producir una referencia nula. Por lo
tanto, se recomienda evitar esta práctica.
Aunque es posible adjuntar un observador a varios proveedores, el patrón recomendado consiste en adjuntar una
instancia de IObserver<T> a una única instancia de IObservable<T>.

Vea también
Modelo de diseño de observador
Implementar un observador
Implementar un proveedor
Cómo: Implementar un proveedor
16/09/2020 • 10 minutes to read • Edit Online

El modelo de diseño de observador requiere una división entre un proveedor, que controla los datos y envía
notificaciones, y uno o varios observadores, que reciben notificaciones (devoluciones de llamada) del proveedor.
En este tema se describe cómo crear un proveedor. En un tema relacionado, Cómo: Implementar un observador, se
describe cómo crear un observador.
Para crear un proveedor
1. Defina los datos de los que el proveedor es responsable de enviar a los observadores. Aunque el proveedor
y los datos que envía a los observadores pueden ser de un solo tipo, suelen representarse con tipos
distintos. Por ejemplo, en una aplicación de supervisión de temperatura, la estructura Temperature define
los datos que el proveedor (representado por la clase TemperatureMonitor definida en el paso siguiente)
controla y al que se suscriben los observadores.

using System;

public struct Temperature


{
private decimal temp;
private DateTime tempDate;

public Temperature(decimal temperature, DateTime dateAndTime)


{
this.temp = temperature;
this.tempDate = dateAndTime;
}

public decimal Degrees


{ get { return this.temp; } }

public DateTime Date


{ get { return this.tempDate; } }
}

Public Structure Temperature


Private temp As Decimal
Private tempDate As DateTime

Public Sub New(ByVal temperature As Decimal, ByVal dateAndTime As DateTime)


Me.temp = temperature
Me.tempDate = dateAndTime
End Sub

Public ReadOnly Property Degrees As Decimal


Get
Return Me.temp
End Get
End Property

Public ReadOnly Property [Date] As DateTime


Get
Return tempDate
End Get
End Property
End Structure
2. Defina el proveedor de datos, que es un tipo que implementa la interfaz System.IObservable<T>. El
argumento de tipo genérico es el tipo que el proveedor envía a los observadores. En el ejemplo siguiente se
define una clase TemperatureMonitor , que es una implementación System.IObservable<T> construida con
un argumento de tipo genérico de Temperature .

using System;
using System.Collections.Generic;

public class TemperatureMonitor : IObservable<Temperature>


{

Imports System.Collections.Generic

Public Class TemperatureMonitor : Implements IObservable(Of Temperature)

3. Determine cómo el proveedor almacenará las referencias a los observadores, para que cada observador
reciba una notificación cuando sea oportuno. Lo más común es que, para este propósito, se use un objeto
de colección como un objeto List<T> genérico. En el ejemplo siguiente se define un objeto List<T> privado
del que se crea una instancia en el constructor de clases TemperatureMonitor .

using System;
using System.Collections.Generic;

public class TemperatureMonitor : IObservable<Temperature>


{
List<IObserver<Temperature>> observers;

public TemperatureMonitor()
{
observers = new List<IObserver<Temperature>>();
}

Imports System.Collections.Generic

Public Class TemperatureMonitor : Implements IObservable(Of Temperature)


Dim observers As List(Of IObserver(Of Temperature))

Public Sub New()


observers = New List(Of IObserver(Of Temperature))
End Sub

4. Defina una implementación IDisposable que el proveedor pueda devolver a los suscriptores, para que
puedan dejar de recibir notificaciones en cualquier momento. En el ejemplo siguiente se define una clase
Unsubscriber anidada de la que se pasa una referencia a la colección de suscriptores y al suscriptor cuando
se crea la instancia de la clase. Este código permite que el suscriptor llame a la implementación
IDisposable.Dispose del objeto para quitarse de la colección de suscriptores.
private class Unsubscriber : IDisposable
{
private List<IObserver<Temperature>> _observers;
private IObserver<Temperature> _observer;

public Unsubscriber(List<IObserver<Temperature>> observers, IObserver<Temperature> observer)


{
this._observers = observers;
this._observer = observer;
}

public void Dispose()


{
if (! (_observer == null)) _observers.Remove(_observer);
}
}

Private Class Unsubscriber : Implements IDisposable


Private _observers As List(Of IObserver(Of Temperature))
Private _observer As IObserver(Of Temperature)

Public Sub New(ByVal observers As List(Of IObserver(Of Temperature)), ByVal observer As


IObserver(Of Temperature))
Me._observers = observers
Me._observer = observer
End Sub

Public Sub Dispose() Implements IDisposable.Dispose


If _observer IsNot Nothing Then _observers.Remove(_observer)
End Sub
End Class

5. Implemente el método IObservable<T>.Subscribe. Al método se le pasa una referencia a la interfaz


System.IObserver<T> y se debe almacenar en el objeto designado para dicho propósito en el paso 3. El
método, a continuación, debe devolver la implementación IDisposable desarrollada en el paso 4. El
siguiente ejemplo muestra la implementación del método Subscribe de la clase TemperatureMonitor .

public IDisposable Subscribe(IObserver<Temperature> observer)


{
if (! observers.Contains(observer))
observers.Add(observer);

return new Unsubscriber(observers, observer);


}

Public Function Subscribe(ByVal observer As System.IObserver(Of Temperature)) As System.IDisposable


Implements System.IObservable(Of Temperature).Subscribe
If Not observers.Contains(observer) Then
observers.Add(observer)
End If
Return New Unsubscriber(observers, observer)
End Function

6. Notifique a los observadores según proceda con una llamada a sus implementaciones
IObserver<T>.OnNext, IObserver<T>.OnError y IObserver<T>.OnCompleted. En algunos casos, un
proveedor no puede llamar al método OnError cuando se produce un error. Por ejemplo, el método
GetTemperature siguiente simula un monitor que lee los datos de temperatura cada cinco segundos y
notifica a los observadores si la temperatura ha cambiado por al menos 1 grado desde la lectura anterior. Si
el dispositivo no informa de una temperatura (es decir, si su valor es null), el proveedor notifica a los
observadores que la transmisión está completa. Tenga en cuenta que, además de llamar al método
OnCompleted de cada observador, el método GetTemperature borra la colección List<T>. En este caso, el
proveedor no realiza ninguna llamada al método OnError de sus observadores.

public void GetTemperature()


{
// Create an array of sample data to mimic a temperature device.
Nullable<Decimal>[] temps = {14.6m, 14.65m, 14.7m, 14.9m, 14.9m, 15.2m, 15.25m, 15.2m,
15.4m, 15.45m, null };
// Store the previous temperature, so notification is only sent after at least .1 change.
Nullable<Decimal> previous = null;
bool start = true;

foreach (var temp in temps) {


System.Threading.Thread.Sleep(2500);
if (temp.HasValue) {
if (start || (Math.Abs(temp.Value - previous.Value) >= 0.1m )) {
Temperature tempData = new Temperature(temp.Value, DateTime.Now);
foreach (var observer in observers)
observer.OnNext(tempData);
previous = temp;
if (start) start = false;
}
}
else {
foreach (var observer in observers.ToArray())
if (observer != null) observer.OnCompleted();

observers.Clear();
break;
}
}
}

Public Sub GetTemperature()


' Create an array of sample data to mimic a temperature device.
Dim temps() As Nullable(Of Decimal) = {14.6D, 14.65D, 14.7D, 14.9D, 14.9D, 15.2D, 15.25D, 15.2D,
15.4D, 15.45D, Nothing}
' Store the previous temperature, so notification is only sent after at least .1 change.
Dim previous As Nullable(Of Decimal)
Dim start As Boolean = True

For Each temp In temps


System.Threading.Thread.Sleep(2500)

If temp.HasValue Then
If start OrElse Math.Abs(temp.Value - previous.Value) >= 0.1 Then
Dim tempData As New Temperature(temp.Value, Date.Now)
For Each observer In observers
observer.OnNext(tempData)
Next
previous = temp
If start Then start = False
End If
Else
For Each observer In observers.ToArray()
If observer IsNot Nothing Then observer.OnCompleted()
Next
observers.Clear()
Exit For
End If
Next
End Sub
Ejemplo
El siguiente ejemplo contiene el código fuente completo para definir una implementación IObservable<T> para
una aplicación de supervisión de temperatura. Incluye la estructura Temperature , que son los datos enviados a los
observadores, y la clase TemperatureMonitor , que es la implementación IObservable<T>.

using System.Threading;
using System;
using System.Collections.Generic;

public class TemperatureMonitor : IObservable<Temperature>


{
List<IObserver<Temperature>> observers;

public TemperatureMonitor()
{
observers = new List<IObserver<Temperature>>();
}

private class Unsubscriber : IDisposable


{
private List<IObserver<Temperature>> _observers;
private IObserver<Temperature> _observer;

public Unsubscriber(List<IObserver<Temperature>> observers, IObserver<Temperature> observer)


{
this._observers = observers;
this._observer = observer;
}

public void Dispose()


{
if (! (_observer == null)) _observers.Remove(_observer);
}
}

public IDisposable Subscribe(IObserver<Temperature> observer)


{
if (! observers.Contains(observer))
observers.Add(observer);

return new Unsubscriber(observers, observer);


}

public void GetTemperature()


{
// Create an array of sample data to mimic a temperature device.
Nullable<Decimal>[] temps = {14.6m, 14.65m, 14.7m, 14.9m, 14.9m, 15.2m, 15.25m, 15.2m,
15.4m, 15.45m, null };
// Store the previous temperature, so notification is only sent after at least .1 change.
Nullable<Decimal> previous = null;
bool start = true;

foreach (var temp in temps) {


System.Threading.Thread.Sleep(2500);
if (temp.HasValue) {
if (start || (Math.Abs(temp.Value - previous.Value) >= 0.1m )) {
Temperature tempData = new Temperature(temp.Value, DateTime.Now);
foreach (var observer in observers)
observer.OnNext(tempData);
previous = temp;
if (start) start = false;
}
}
else {
foreach (var observer in observers.ToArray())
if (observer != null) observer.OnCompleted();
if (observer != null) observer.OnCompleted();

observers.Clear();
break;
}
}
}
}
Imports System.Threading
Imports System.Collections.Generic

Public Class TemperatureMonitor : Implements IObservable(Of Temperature)


Dim observers As List(Of IObserver(Of Temperature))

Public Sub New()


observers = New List(Of IObserver(Of Temperature))
End Sub

Private Class Unsubscriber : Implements IDisposable


Private _observers As List(Of IObserver(Of Temperature))
Private _observer As IObserver(Of Temperature)

Public Sub New(ByVal observers As List(Of IObserver(Of Temperature)), ByVal observer As IObserver(Of
Temperature))
Me._observers = observers
Me._observer = observer
End Sub

Public Sub Dispose() Implements IDisposable.Dispose


If _observer IsNot Nothing Then _observers.Remove(_observer)
End Sub
End Class

Public Function Subscribe(ByVal observer As System.IObserver(Of Temperature)) As System.IDisposable


Implements System.IObservable(Of Temperature).Subscribe
If Not observers.Contains(observer) Then
observers.Add(observer)
End If
Return New Unsubscriber(observers, observer)
End Function

Public Sub GetTemperature()


' Create an array of sample data to mimic a temperature device.
Dim temps() As Nullable(Of Decimal) = {14.6D, 14.65D, 14.7D, 14.9D, 14.9D, 15.2D, 15.25D, 15.2D,
15.4D, 15.45D, Nothing}
' Store the previous temperature, so notification is only sent after at least .1 change.
Dim previous As Nullable(Of Decimal)
Dim start As Boolean = True

For Each temp In temps


System.Threading.Thread.Sleep(2500)

If temp.HasValue Then
If start OrElse Math.Abs(temp.Value - previous.Value) >= 0.1 Then
Dim tempData As New Temperature(temp.Value, Date.Now)
For Each observer In observers
observer.OnNext(tempData)
Next
previous = temp
If start Then start = False
End If
Else
For Each observer In observers.ToArray()
If observer IsNot Nothing Then observer.OnCompleted()
Next
observers.Clear()
Exit For
End If
Next
End Sub
End Class
Vea también
IObservable<T>
Modelo de diseño de observador
Implementar un observador
Procedimientos recomendados para modelos de diseño de observador
Procedimiento para implementar un observador
16/09/2020 • 5 minutes to read • Edit Online

El modelo de diseño de observador requiere una división entre un observador, que registra notificaciones, y un
proveedor, que controla los datos y envía notificaciones a uno o varios observadores. En este tema se describe
cómo crear un observador. En un tema relacionado, Cómo: Implementar un proveedor, se describe cómo crear un
proveedor.
Para crear un observador
1. Defina el observador, que es un tipo que implementa la interfaz System.IObserver<T>. Por ejemplo, el
código siguiente define un tipo llamado TemperatureReporter , que es una implementación
System.IObserver<T> construida con un argumento de tipo genérico de Temperature .

public class TemperatureReporter : IObserver<Temperature>

Public Class TemperatureReporter : Implements IObserver(Of Temperature)

2. Si el observador puede dejar de recibir notificaciones antes de que el proveedor llame a su implementación
IObserver<T>.OnCompleted, defina una variable privada que contenga la implementación IDisposable
devuelta por el método IObservable<T>.Subscribe del proveedor. También debe definir un método de
suscripción que llame al método Subscribe del proveedor y almacene el objeto IDisposable devuelto. Por
ejemplo, el código siguiente define una variable privada denominada unsubscriber y define un método
Subscribe que llama al método Subscribe del proveedor y asigna el objeto devuelto a la variable
unsubscriber .

public class TemperatureReporter : IObserver<Temperature>


{
private IDisposable unsubscriber;
private bool first = true;
private Temperature last;

public virtual void Subscribe(IObservable<Temperature> provider)


{
unsubscriber = provider.Subscribe(this);
}

Public Class TemperatureReporter : Implements IObserver(Of Temperature)

Private unsubscriber As IDisposable


Private first As Boolean = True
Private last As Temperature

Public Overridable Sub Subscribe(ByVal provider As IObservable(Of Temperature))


unsubscriber = provider.Subscribe(Me)
End Sub

3. Defina un método que permita al observador dejar de recibir notificaciones antes de que el proveedor
llame a su implementación IObserver<T>.OnCompleted, en caso de que esta característica sea necesaria.
En el ejemplo siguiente, se define un método Unsubscribe .
public virtual void Unsubscribe()
{
unsubscriber.Dispose();
}

Public Overridable Sub Unsubscribe()


unsubscriber.Dispose()
End Sub

4. Proporcione implementaciones de los tres métodos definidos en la interfaz IObserver<T>:


IObserver<T>.OnNext, IObserver<T>.OnError y IObserver<T>.OnCompleted. En función del proveedor y
de las necesidades de la aplicación, los métodos OnError y OnCompleted pueden ser implementaciones de
código auxiliar. Tenga en cuenta que el método OnError no debe controlar el objeto Exception pasado como
una excepción y que el método OnCompleted es libre de llamar a la implementación IDisposable.Dispose
del proveedor. El siguiente ejemplo muestra la implementación IObserver<T> de la clase
TemperatureReporter .

public virtual void OnCompleted()


{
Console.WriteLine("Additional temperature data will not be transmitted.");
}

public virtual void OnError(Exception error)


{
// Do nothing.
}

public virtual void OnNext(Temperature value)


{
Console.WriteLine("The temperature is {0}°C at {1:g}", value.Degrees, value.Date);
if (first)
{
last = value;
first = false;
}
else
{
Console.WriteLine(" Change: {0}° in {1:g}", value.Degrees - last.Degrees,
value.Date.ToUniversalTime() -
last.Date.ToUniversalTime());
}
}
Public Overridable Sub OnCompleted() Implements System.IObserver(Of Temperature).OnCompleted
Console.WriteLine("Additional temperature data will not be transmitted.")
End Sub

Public Overridable Sub OnError(ByVal [error] As System.Exception) Implements System.IObserver(Of


Temperature).OnError
' Do nothing.
End Sub

Public Overridable Sub OnNext(ByVal value As Temperature) Implements System.IObserver(Of


Temperature).OnNext
Console.WriteLine("The temperature is {0}°C at {1:g}", value.Degrees, value.Date)
If first Then
last = value
first = False
Else
Console.WriteLine(" Change: {0}° in {1:g}", value.Degrees - last.Degrees,
value.Date.ToUniversalTime -
last.Date.ToUniversalTime)
End If
End Sub

Ejemplo
El siguiente ejemplo contiene el código fuente completo para la clase TemperatureReporter , que ofrece la
implementación IObserver<T> de una aplicación de supervisión de temperatura.
public class TemperatureReporter : IObserver<Temperature>
{
private IDisposable unsubscriber;
private bool first = true;
private Temperature last;

public virtual void Subscribe(IObservable<Temperature> provider)


{
unsubscriber = provider.Subscribe(this);
}

public virtual void Unsubscribe()


{
unsubscriber.Dispose();
}

public virtual void OnCompleted()


{
Console.WriteLine("Additional temperature data will not be transmitted.");
}

public virtual void OnError(Exception error)


{
// Do nothing.
}

public virtual void OnNext(Temperature value)


{
Console.WriteLine("The temperature is {0}°C at {1:g}", value.Degrees, value.Date);
if (first)
{
last = value;
first = false;
}
else
{
Console.WriteLine(" Change: {0}° in {1:g}", value.Degrees - last.Degrees,
value.Date.ToUniversalTime() -
last.Date.ToUniversalTime());
}
}
}
Public Class TemperatureReporter : Implements IObserver(Of Temperature)

Private unsubscriber As IDisposable


Private first As Boolean = True
Private last As Temperature

Public Overridable Sub Subscribe(ByVal provider As IObservable(Of Temperature))


unsubscriber = provider.Subscribe(Me)
End Sub

Public Overridable Sub Unsubscribe()


unsubscriber.Dispose()
End Sub

Public Overridable Sub OnCompleted() Implements System.IObserver(Of Temperature).OnCompleted


Console.WriteLine("Additional temperature data will not be transmitted.")
End Sub

Public Overridable Sub OnError(ByVal [error] As System.Exception) Implements System.IObserver(Of


Temperature).OnError
' Do nothing.
End Sub

Public Overridable Sub OnNext(ByVal value As Temperature) Implements System.IObserver(Of


Temperature).OnNext
Console.WriteLine("The temperature is {0}°C at {1:g}", value.Degrees, value.Date)
If first Then
last = value
first = False
Else
Console.WriteLine(" Change: {0}° in {1:g}", value.Degrees - last.Degrees,
value.Date.ToUniversalTime -
last.Date.ToUniversalTime)
End If
End Sub
End Class

Vea también
IObserver<T>
Modelo de diseño de observador
Cómo: Implementar un proveedor
Procedimientos recomendados para modelos de diseño de observador
Controlar y generar excepciones en .NET
16/09/2020 • 5 minutes to read • Edit Online

Las aplicaciones tienen que ser capaces de controlar de forma coherente los errores que se producen durante la
ejecución. .NET proporciona un modelo para notificar errores a las aplicaciones de manera uniforme: las
operaciones .NET indican errores iniciando excepciones.

Excepciones
Una excepción es cualquier condición de error o comportamiento inesperado que encuentra un programa en
ejecución. Las excepciones pueden iniciarse debido a un error en el código propio o en el código al que se llama
(por ejemplo, una biblioteca compartida), a recursos del sistema operativo no disponibles, a condiciones
inesperadas que encuentra el runtime (por ejemplo, imposibilidad de comprobar el código), etc. La aplicación
puede recuperarse de algunas de estas condiciones, pero no de todas. Aunque es posible recuperarse de la
mayoría de las excepciones que se producen en la aplicación, no ocurre lo mismo con las excepciones de runtime.
En .NET, una excepción es un objeto que hereda de la clase System.Exception. Una excepción se inicia desde un
área del código en la que ha producido un problema. La excepción se pasa hacia arriba en la pila hasta que la
aplicación la controla o el programa finaliza.

Excepciones vs. métodos tradicionales de control de errores


Tradicionalmente, el modelo de control de errores de un lenguaje se basaba en el modo exclusivo del lenguaje de
detectar los errores y buscarles controladores, o bien en el mecanismo de control de errores ofrecido por el
sistema operativo. La forma en que .NET implementa el control de excepciones proporciona las siguientes
ventajas:
La administración e iniciación de excepciones funciona de igual modo para los lenguajes de programación.
NET.
No requiere ninguna sintaxis de lenguaje específica para controlar las excepciones, pero permite que cada
lenguaje defina su propia sintaxis.
Las excepciones pueden iniciarse en varios procesos en incluso límites de máquina.
Se puede agregar el código de control de excepciones a una aplicación para aumentar la confiabilidad del
programa.
Las excepciones ofrecen ventajas sobre otros métodos de notificación de errores, por ejemplo, los códigos de
retorno. Lo errores no pasan desapercibidos porque si se inicia una excepción y no la controla, el runtime finaliza
la aplicación. Los valores no válidos no continúan propagándose por el sistema como resultado de que el código
no pueda encontrar un código de retorno de error.

Excepciones comunes
En la tabla siguiente se muestra algunas excepciones comunes con ejemplos de las causas que las originan.

T IP O DE EXC EP C IÓ N DESC RIP C IÓ N E JEM P LO

Exception Clase base de todas las excepciones. Ninguno (utilice una clase derivada de
esta excepción).
T IP O DE EXC EP C IÓ N DESC RIP C IÓ N E JEM P LO

IndexOutOfRangeException El tiempo de ejecución la genera solo La indexación de una matriz fuera de su


cuando una matriz no está intervalo válido:
correctamente indexada. arr[arr.Length+1]

NullReferenceException El tiempo de ejecución la genera solo object o = null;


cuando se hace referencia a un objeto o.ToString();
null.

InvalidOperationException Los métodos la generan si se produce Llamada a Enumerator.MoveNext()


un estado no válido. después de quitar un elemento de la
colección subyacente.

ArgumentException Clase base de todas las excepciones de Ninguno (utilice una clase derivada de
argumento. esta excepción).

ArgumentNullException Los métodos que no permiten que un String s = null;


argumento sea null la generan. "Calculate".IndexOf(s);

ArgumentOutOfRangeException Los métodos que comprueban que los String s = "string";


argumentos se encuentran en un s.Substring(s.Length+1);
intervalo determinado la generan.

Vea también
Clase Exception y propiedades
Cómo: Utilizar el bloque Try/Catch para detectar excepciones
Cómo: Usar excepciones específicas en un bloque Catch
Cómo: Iniciar excepciones explícitamente
Cómo: Crear excepciones definidas por el usuario
Utilizar controladores de excepciones filtradas por el usuario
Cómo: Usar bloques Finally
Controlar excepciones de interoperabilidad COM
Procedimientos recomendados para excepciones
¿Qué necesitan saber todos los desarrolladores acerca de las excepciones producidas en el runtime?
Clase Exception y propiedades
18/03/2020 • 4 minutes to read • Edit Online

La clase Exception es la clase base de la que heredan las excepciones. Por ejemplo, la jerarquía de clases de
InvalidCastException es como sigue:
Object
Exception
SystemException
InvalidCastException
La clase Exception tiene las siguientes propiedades que facilitan la comprensión de una excepción.

N O M B RE DE L A P RO P IEDA D DESC RIP T IO N

Data Un IDictionary que contiene datos arbitrarios en pares de


clave y valor.

HelpLink Puede contener una dirección URL (o URN) a un archivo de


ayuda que proporciona amplia información sobre la causa de
una excepción.

InnerException Esta propiedad puede usarse para crear y mantener una serie
de excepciones durante el control de excepciones. Puede
usarla para crear una nueva excepción que contiene
excepciones detectadas con anterioridad. La segunda
excepción en la propiedad InnerException puede capturar la
excepción original, lo que permite que el código que controla
la segunda excepción examine la información adicional. Por
ejemplo, supongamos que tiene un método que recibe un
argumento que contiene un formato incorrecto. El código
intenta leer el argumento, pero se inicia una excepción. El
método detecta la excepción e inicia FormatException. Para
mejorar la capacidad del autor de llamada a la hora de
determinar por qué se inicia una excepción, a veces, para un
método es recomendable detectar una excepción iniciada por
una rutina del asistente y, después, iniciar una excepción más
indicativa del error que se ha producido. Puede crear una
excepción nueva y más significativa, donde se puede
establecer la referencia a la excepción interna en la excepción
original. Después, se puede iniciar esta excepción más
significativa para el autor de llamada. Tenga en cuenta que con
esta funcionalidad, puede crear una serie de excepciones
vinculadas que termina con la excepción que se inicia en
primer lugar.

Message Proporciona detalles sobre la causa de una excepción.

Source Devuelve o establece el nombre de la aplicación o del objeto


que generó el error.
N O M B RE DE L A P RO P IEDA D DESC RIP T IO N

StackTrace Contiene un seguimiento de la pila que puede usarse para


determinar dónde se produjo un error. El seguimiento de la
pila contiene el nombre del archivo de código fuente y el
número de línea del programa si está disponible la
información de depuración.

La mayoría de las clases que heredan de Exception no implementan miembros adicionales ni proporcionan
funciones adicionales; simplemente heredan de Exception. Por tanto, se puede encontrar la información más
importante para una excepción en la jerarquía de clases de excepción, el nombre de la excepción y la información
contenida en la excepción.
Se recomienda iniciar y detectar solo los objetos que se derivan de Exception, pero puede iniciar cualquier objeto
que se deriva de la clase Object como una excepción. Tenga en cuenta que no todos los lenguajes admiten iniciar y
detectar objetos que no se derivan de Exception.

Vea también
Excepciones
Cómo usar el bloque Try/Catch para detectar
excepciones
16/09/2020 • 3 minutes to read • Edit Online

Coloque las instrucciones de código que podrían elevar o producir una excepción en un bloque try , y las que se
usan para controlar la excepción o excepciones en uno o varios bloques catch debajo del bloque try . Cada
bloque catch incluye el tipo de excepción y puede contener instrucciones adicionales necesarias para controlar
ese tipo de excepción.
En el ejemplo siguiente, un elemento StreamReader abre un archivo denominado data.txt y recupera una línea del
archivo. Como es posible que el código genere cualquiera de las tres excepciones, se coloca en un bloque try . Tres
bloques catch detectan las excepciones y las controlan mostrando los resultados en la consola.

using System;
using System.IO;

public class ProcessFile


{
public static void Main()
{
try
{
using (StreamReader sr = File.OpenText("data.txt"))
{
Console.WriteLine($"The first line of this file is {sr.ReadLine()}");
}
}
catch (FileNotFoundException e)
{
Console.WriteLine($"The file was not found: '{e}'");
}
catch (DirectoryNotFoundException e)
{
Console.WriteLine($"The directory was not found: '{e}'");
}
catch (IOException e)
{
Console.WriteLine($"The file could not be opened: '{e}'");
}
}
}
Imports System.IO

Public Class ProcessFile


Public Shared Sub Main()
Try
Using sr As StreamReader = File.OpenText("data.txt")
Console.WriteLine($"The first line of this file is {sr.ReadLine()}")
End Using
Catch e As FileNotFoundException
Console.WriteLine($"The file was not found: '{e}'")
Catch e As DirectoryNotFoundException
Console.WriteLine($"The directory was not found: '{e}'")
Catch e As IOException
Console.WriteLine($"The file could not be opened: '{e}'")
End Try
End Sub
End Class

Common Language Runtime (CLR) detecta las excepciones no controladas por los bloques catch . Si CLR detecta
una excepción, puede producirse uno de los resultados siguientes, en función de la configuración de CLR:
Aparece un cuadro de diálogo Depurar .
El programa detiene la ejecución y aparece un cuadro de diálogo con información de la excepción.
Se imprime un error en el flujo de salida de error estándar.

NOTE
La mayoría del código puede producir una excepción y algunas excepciones, como OutOfMemoryException, las puede
generar el propio CLR en cualquier momento. Aunque no es obligatorio que las aplicaciones controlen estas excepciones,
tenga en cuenta esta posibilidad al escribir bibliotecas que puedan usar otros usuarios. Para obtener sugerencias sobre
cuándo establecer el código en un bloque try , vea Procedimientos recomendados para excepciones.

Vea también
Excepciones
Control de errores de E/S en .NET
Cómo: Utilizar excepciones específicas en un bloque
Catch
18/03/2020 • 3 minutes to read • Edit Online

En general, en programación es recomendable detectar un tipo de excepción específico en lugar de usar una
instrucción catch básica.
Cuando se produce una excepción, se pasa a la pila y cada bloque Catch tiene la oportunidad de controlarla. El
orden de las instrucciones Catch es importante. Ponga los bloques Catch dirigidos a excepciones específicas antes
de un bloque Catch de excepción general o el compilador podría emitir un error. El bloque Catch adecuado se
determina haciendo coincidir el tipo de la excepción con el nombre de la excepción especificada en el bloque Catch.
Si no hay ningún bloque Catch específico, la excepción se detecta mediante un bloque Catch general, si existe
alguno.
En el siguiente ejemplo de código se usa un bloque try / catch para detectar una InvalidCastException. El ejemplo
crea una clase denominada Employee con una propiedad única, el nivel de empleado ( Emlevel ). Un método,
PromoteEmployee , toma un objeto e incrementa el nivel del empleado. Una InvalidCastException se produce cuando
una instancia de DateTime se pasa al método PromoteEmployee .
using namespace System;

public ref class Employee


{
public:
Employee()
{
emlevel = 0;
}

//Create employee level property.


property int Emlevel
{
int get()
{
return emlevel;
}
void set(int value)
{
emlevel = value;
}
}

private:
int emlevel;
};

public ref class Ex13


{
public:
static void PromoteEmployee(Object^ emp)
{
//Cast object to Employee.
Employee^ e = (Employee^) emp;
// Increment employee level.
e->Emlevel++;
}

static void Main()


{
try
{
Object^ o = gcnew Employee();
DateTime^ newyears = gcnew DateTime(2001, 1, 1);
//Promote the new employee.
PromoteEmployee(o);
//Promote DateTime; results in InvalidCastException as newyears is not an employee instance.
PromoteEmployee(newyears);
}
catch (InvalidCastException^ e)
{
Console::WriteLine("Error passing data to PromoteEmployee method. " + e->Message);
}
}
};

int main()
{
Ex13::Main();
}
using System;

public class Employee


{
//Create employee level property.
public int Emlevel
{
get
{
return(emlevel);
}
set
{
emlevel = value;
}
}

private int emlevel = 0;


}

public class Ex13


{
public static void PromoteEmployee(Object emp)
{
// Cast object to Employee.
var e = (Employee) emp;
// Increment employee level.
e.Emlevel = e.Emlevel + 1;
}

public static void Main()


{
try
{
Object o = new Employee();
DateTime newYears = new DateTime(2001, 1, 1);
// Promote the new employee.
PromoteEmployee(o);
// Promote DateTime; results in InvalidCastException as newYears is not an employee instance.
PromoteEmployee(newYears);
}
catch (InvalidCastException e)
{
Console.WriteLine("Error passing data to PromoteEmployee method. " + e.Message);
}
}
}
Public Class Employee
'Create employee level property.
Public Property Emlevel As Integer
Get
Return emlevelValue
End Get
Set
emlevelValue = Value
End Set
End Property

Private emlevelValue As Integer = 0


End Class

Public Class Ex13


Public Shared Sub PromoteEmployee(emp As Object)
' Cast object to Employee.
Dim e As Employee = CType(emp, Employee)
' Increment employee level.
e.Emlevel = e.Emlevel + 1
End Sub

Public Shared Sub Main()


Try
Dim o As Object = New Employee()
Dim newYears As New DateTime(2001, 1, 1)
' Promote the new employee.
PromoteEmployee(o)
' Promote DateTime; results in InvalidCastException as newYears is not an employee instance.
PromoteEmployee(newYears)
Catch e As InvalidCastException
Console.WriteLine("Error passing data to PromoteEmployee method. " + e.Message)
End Try
End Sub
End Class

Vea también
Excepciones
Cómo iniciar excepciones explícitamente
16/09/2020 • 2 minutes to read • Edit Online

Puede iniciar explícitamente una excepción mediante la instrucción throw de C# o la instrucción Throw de Visual
Basic. También se puede iniciar una excepción detectada usando de nuevo la instrucción throw . En diseño de
código, es recomendable agregar información a una excepción que se vuelve a iniciar para proporcionar más
información durante la depuración.
En el siguiente ejemplo de código se usa un bloque try / catch para detectar una posible FileNotFoundException.
Seguido del bloque try va un bloque catch que detecta FileNotFoundException y escribe un mensaje a la
consola si no se encuentra el archivo de datos. La siguiente instrucción es throw que inicia un parámetro
FileNotFoundException nuevo y agrega información de texto a la excepción.

using System;
using System.IO;

public class ProcessFile


{
public static void Main()
{
FileStream fs;
try
{
// Opens a text tile.
fs = new FileStream(@"C:\temp\data.txt", FileMode.Open);
var sr = new StreamReader(fs);

// A value is read from the file and output to the console.


string line = sr.ReadLine();
Console.WriteLine(line);
}
catch(FileNotFoundException e)
{
Console.WriteLine($"[Data File Missing] {e}");
throw new FileNotFoundException(@"[data.txt not in c:\temp directory]", e);
}
finally
{
if (fs != null)
fs.Close();
}
}
}
Option Strict On

Imports System.IO

Public Class ProcessFile

Public Shared Sub Main()


Dim fs As FileStream
Try
' Opens a text file.
fs = New FileStream("c:\temp\data.txt", FileMode.Open)
Dim sr As New StreamReader(fs)

' A value is read from the file and output to the console.
Dim line As String = sr.ReadLine()
Console.WriteLine(line)
Catch e As FileNotFoundException
Console.WriteLine($"[Data File Missing] {e}")
Throw New FileNotFoundException("[data.txt not in c:\temp directory]", e)
Finally
If fs IsNot Nothing Then fs.Close()
End Try
End Sub
End Class

Vea también
Excepciones
Cómo crear excepciones definidas por el usuario
16/09/2020 • 2 minutes to read • Edit Online

.NET proporciona una jerarquía de clases de excepciones derivadas en última instancia de la clase base Exception.
Pero si ninguna de las excepciones predefinidas satisface sus necesidades, puede crear sus propias clases de
excepciones derivadas de la clase Exception.
Al crear sus propias excepciones, termine el nombre de clase de la excepción definida por el usuario con la palabra
"Exception" e implemente los tres constructores comunes, como se muestra en el ejemplo siguiente. En el ejemplo
se define una nueva clase de excepción denominada EmployeeListNotFoundException . La clase se deriva de
Exception e incluye tres constructores.

using namespace System;

public ref class EmployeeListNotFoundException : Exception


{
public:
EmployeeListNotFoundException()
{
}

EmployeeListNotFoundException(String^ message)
: Exception(message)
{
}

EmployeeListNotFoundException(String^ message, Exception^ inner)


: Exception(message, inner)
{
}
};

using System;

public class EmployeeListNotFoundException : Exception


{
public EmployeeListNotFoundException()
{
}

public EmployeeListNotFoundException(string message)


: base(message)
{
}

public EmployeeListNotFoundException(string message, Exception inner)


: base(message, inner)
{
}
}
Public Class EmployeeListNotFoundException
Inherits Exception

Public Sub New()


End Sub

Public Sub New(message As String)


MyBase.New(message)
End Sub

Public Sub New(message As String, inner As Exception)


MyBase.New(message, inner)
End Sub
End Class

NOTE
En situaciones en que use la comunicación remota, debe asegurarse de que los metadatos de todas las excepciones definidas
por el usuario estén disponibles en el servidor (destinatario) y en el cliente (el objeto proxy o autor de la llamada). Para
obtener más información, consulte Procedimientos recomendados para excepciones.

Vea también
Excepciones
Creación de excepciones definidas por el usuario con
mensajes de excepción localizados
16/09/2020 • 5 minutes to read • Edit Online

En este artículo, obtendrá información sobre cómo crear excepciones definidas por el usuario que se hereden de la
clase base Exception con mensajes de excepción localizados mediante ensamblados satélite.

Creación de excepciones personalizadas


.NET contiene muchas excepciones distintas que puede usar. Sin embargo, en algunos casos, cuando ninguna de
ellas satisface sus necesidades, puede crear sus propias excepciones personalizadas.
Supongamos que desea crear un StudentNotFoundException que contiene una propiedad StudentName . Para crear
una excepción personalizada, siga estos pasos:
1. Cree una clase serializable que herede de Exception. El nombre de clase debe terminar en "Exception":

[Serializable]
public class StudentNotFoundException : Exception { }

<Serializable>
Public Class StudentNotFoundException
Inherits Exception
End Class

2. Agregue los constructores predeterminados:

[Serializable]
public class StudentNotFoundException : Exception
{
public StudentNotFoundException() { }

public StudentNotFoundException(string message)


: base(message) { }

public StudentNotFoundException(string message, Exception inner)


: base(message, inner) { }
}
<Serializable>
Public Class StudentNotFoundException
Inherits Exception

Public Sub New()


End Sub

Public Sub New(message As String)


MyBase.New(message)
End Sub

Public Sub New(message As String, inner As Exception)


MyBase.New(message, inner)
End Sub
End Class

3. Defina cualquier propiedad y constructores adicionales:

[Serializable]
public class StudentNotFoundException : Exception
{
public string StudentName { get; }

public StudentNotFoundException() { }

public StudentNotFoundException(string message)


: base(message) { }

public StudentNotFoundException(string message, Exception inner)


: base(message, inner) { }

public StudentNotFoundException(string message, string studentName)


: this(message)
{
StudentName = studentName;
}
}

<Serializable>
Public Class StudentNotFoundException
Inherits Exception

Public ReadOnly Property StudentName As String

Public Sub New()


End Sub

Public Sub New(message As String)


MyBase.New(message)
End Sub

Public Sub New(message As String, inner As Exception)


MyBase.New(message, inner)
End Sub

Public Sub New(message As String, studentName As String)


Me.New(message)
StudentName = studentName
End Sub
End Class

Creación de mensajes de excepción localizados


Creó una excepción personalizada y puede iniciarla en cualquier parte con código como el siguiente:

throw new StudentNotFoundException("The student cannot be found.", "John");

Throw New StudentNotFoundException("The student cannot be found.", "John")

El problema con la línea anterior es que "The student cannot be found." es simplemente una cadena constante. En
una aplicación localizada, quiere tener mensajes diferentes en función de la referencia cultural del usuario. Los
ensamblados satélite son una buena manera de hacerlo. Un ensamblado satélite es un archivo .dll que contiene
recursos para un idioma específico. Cuando se solicitan recursos específicos en tiempo de ejecución, el CLR
encuentra ese recurso en función de la referencia cultural del usuario. Si no se encuentra ningún ensamblado
satélite para esa referencia cultural, se usan los recursos de la referencia cultural predeterminada.
Para crear los mensajes de excepción localizados:
1. Cree una carpeta llamada Recursos para contener los archivos de recursos.
2. Agréguele un archivo de recursos nuevo. Para ello, en Visual Studio, haga clic con el botón derecho en la
carpeta en el Explorador de soluciones y seleccione Agregar > Nuevo elemento > Archivo de
recursos . Asígnele al archivo el nombre ExceptionMessages.resx. Este es el archivo de recursos
predeterminado.
3. Agregue un par nombre-valor para el mensaje de excepción, como se muestra en la imagen siguiente:

4. Agregue un archivo de recursos nuevo para francés. Asígnele el nombre ExceptionMessages.fr-FR.resx.


5. Vuelva a agregar un par nombre-valor para el mensaje de excepción, pero con un valor en francés:

6. Después de compilar el proyecto, la carpeta de la salida de compilación debe contener la carpeta fr-FR con
un archivo .dll, que es el ensamblado satélite.
7. Inicia la excepción con código como el siguiente:

var resourceManager = new ResourceManager("FULLY_QUALIFIED_NAME_OF_RESOURCE_FILE",


Assembly.GetExecutingAssembly());
throw new StudentNotFoundException(resourceManager.GetString("StudentNotFound"), "John");

Dim resourceManager As New ResourceManager("FULLY_QUALIFIED_NAME_OF_RESOURCE_FILE",


Assembly.GetExecutingAssembly())
Throw New StudentNotFoundException(resourceManager.GetString("StudentNotFound"), "John")
NOTE
Si el nombre del proyecto es TestProject y el archivo de recursos ExceptionMessages.resx reside en la carpeta
Recursos del proyecto, el nombre completo del archivo de recursos es TestProject.Resources.ExceptionMessages .

Vea también
Cómo crear excepciones definidas por el usuario
Crear ensamblados satélite para aplicaciones de escritorio
base (Referencia de C#)
this (Referencia de C#)
Cómo usar bloques Finally
18/03/2020 • 2 minutes to read • Edit Online

Cuando se produce una excepción, se detiene la ejecución y se proporciona el control al controlador de


excepciones adecuado. A menudo, esto significa que se omiten líneas de código que esperaba que se ejecuten. Se
debe realizar alguna limpieza de recursos, como cerrar un archivo, aunque se inicie una excepción. Para ello, puede
usar un bloque finally . Un bloque finally siempre se ejecuta, independientemente de si se inicia una excepción.
En el siguiente ejemplo de código se usa un bloque try / catch para detectar una
ArgumentOutOfRangeException. El método Main crea dos matrices e intenta copiar una en la otra. La acción
genera un ArgumentOutOfRangeException y el error se escribe en la consola. Este bloque finally se ejecuta
independientemente del resultado de la acción de copia.

using namespace System;

ref class ArgumentOutOfRangeExample


{
public:
static void Main()
{
array<int>^ array1 = {0, 0};
array<int>^ array2 = {0, 0};

try
{
Array::Copy(array1, array2, -1);
}
catch (ArgumentOutOfRangeException^ e)
{
Console::WriteLine("Error: {0}", e);
throw;
}
finally
{
Console::WriteLine("This statement is always executed.");
}
}
};

int main()
{
ArgumentOutOfRangeExample::Main();
}
using System;

class ArgumentOutOfRangeExample
{
public static void Main()
{
int[] array1 = {0, 0};
int[] array2 = {0, 0};

try
{
Array.Copy(array1, array2, -1);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine("Error: {0}", e);
throw;
}
finally
{
Console.WriteLine("This statement is always executed.");
}
}
}

Class ArgumentOutOfRangeExample
Public Shared Sub Main()
Dim array1() As Integer = {0, 0}
Dim array2() As Integer = {0, 0}

Try
Array.Copy(array1, array2, -1)
Catch e As ArgumentOutOfRangeException
Console.WriteLine("Error: {0}", e)
Throw
Finally
Console.WriteLine("This statement is always executed.")
End Try
End Sub
End Class

Vea también
Excepciones
Utilizar controladores de excepciones filtradas por el
usuario
18/03/2020 • 2 minutes to read • Edit Online

Actualmente, Visual Basic admite excepciones filtradas por el usuario. Los controladores de excepciones filtradas
por usuario detectan y controlan las excepciones en función de los requisitos que se definen para la excepción.
Estos controladores utilizan la instrucción Catch con la palabra clave When .
Esta técnica es útil cuando un objeto de excepción concreto corresponde a varios errores. En este caso, el objeto
normalmente tiene una propiedad que contiene el código de error específico asociado con el error. Puede utilizar la
propiedad de código de error en la expresión para seleccionar solo el error concreto que desea administrar en esa
cláusula Catch .
El siguiente ejemplo de Visual Basic ilustra la instrucción Catch/When .

Try
'Try statements.
Catch When Err = VBErr_ClassLoadException
'Catch statements.
End Try

La expresión de la cláusula filtrada por el usuario no está restringida en modo alguno. Si se produce una excepción
durante la ejecución de la expresión filtrada por el usuario, esa excepción se descarta y la expresión de filtro se
considera evaluada como false. En este caso, Common Language Runtime continúa la búsqueda de un controlador
para la excepción actual.

Combinar la excepción específica y las cláusulas filtradas por el usuario


Una instrucción Catch puede contener tanto la excepción específica como las cláusulas filtradas por el usuario. El
motor de tiempo de ejecución comprueba primero la excepción específica. Si la comprobación de la excepción
específica es correcta, el motor de tiempo de ejecución ejecuta el filtro de usuario. El filtro genérico puede contener
una referencia a la variable declarada en el filtro de clase. Tenga en cuenta que no se puede revertir el orden de las
dos cláusulas de filtro.
El siguiente ejemplo de Visual Basic muestra la excepción específica ClassLoadException en la instrucción Catch ,
así como la cláusula filtrada por el usuario mediante la palabra clave When .

Try
'Try statements.
Catch cle As ClassLoadException When cle.IsRecoverable()
'Catch statements.
End Try

Vea también
Excepciones
Controlar excepciones de interoperabilidad COM
16/09/2020 • 2 minutes to read • Edit Online

El código administrado y el código no administrado pueden trabajar juntos para controlar excepciones. Si un
método produce una excepción en código administrado, Common Language Runtime puede pasar un HRESULT a
un objeto COM. Si un método produce un error en código no administrado y devuelve un HRESULT de error, el
tiempo de ejecución produce una excepción que el código administrado puede capturar.
El tiempo de ejecución asigna automáticamente el HRESULT de la interoperabilidad COM a excepciones más
específicas. Por ejemplo, E_ACCESSDENIED se convierte en UnauthorizedAccessException, E_OUTOFMEMORY se
convierte en OutOfMemoryException, y así sucesivamente.
Si el HRESULT es un resultado personalizado o es desconocido para el tiempo de ejecución, este pasa una
COMException genérica al cliente. La propiedad ErrorCode de COMException contiene el valor HRESULT.

Trabajar con IErrorInfo


Cuando se pasa un error desde COM al código administrado, el tiempo de ejecución rellena el objeto de excepción
con la información del error. Los objetos COM que admiten IErrorInfo y devuelven HRESULTS proporcionan esta
información a las excepciones de código administrado. Por ejemplo, el tiempo de ejecución asigna la descripción
del error COM a la propiedad Message de la excepción. Si el valor HRESULT no proporciona ninguna información
de error adicional, el tiempo de ejecución rellena muchas de las propiedades de la excepción con los valores
predeterminados.
Si un método produce un error en código no administrado, se puede pasar una excepción a un segmento de
código administrado. El tema HRESULT y excepciones contiene una tabla que muestra cómo se asignan los
HRESULTS a los objetos de excepción en tiempo de ejecución.

Vea también
Excepciones
Procedimientos recomendados para excepciones
16/09/2020 • 17 minutes to read • Edit Online

Una aplicación diseñada correctamente controla las excepciones y los errores para evitar que se bloquee. En este
artículo se describen los procedimientos recomendados para controlar y crear excepciones.

Uso de bloques try/catch/finally para recuperarse de errores o liberar


recursos
Use bloques try / catch alrededor del código que podría generar una excepción y su código podrá recuperarse
de una excepción. Ordene siempre las excepciones de los bloques catch de la más derivada a la menos. Todas las
excepciones se derivan de Exception. Las excepciones más derivadas no las controla una cláusula catch que está
precedida por una cláusula catch para una clase de excepción base. Cuando el código no puede recuperarse de
una excepción, no capture esa excepción. Habilite los métodos más arriba en la pila de llamadas para recuperarse
si es posible.
Limpie los recursos asignados con instrucciones using o bloques finally . Dé prioridad a las instrucciones
using para limpiar automáticamente los recursos cuando se produzcan excepciones. Use bloques finally para
limpiar los recursos que no implementan IDisposable. El código de una cláusula finally casi siempre se ejecuta
incluso cuando se producen excepciones.

Administrar condiciones comunes sin iniciar excepciones


Para las condiciones con probabilidad de producirse, pero que podrían desencadenar una excepción, considere la
posibilidad de controlarlas de forma que se evite la excepción. Por ejemplo, si intenta se cerrar una conexión que
ya está cerrada, obtendrá InvalidOperationException . Se puede evitar mediante una instrucción if para
comprobar el estado de conexión antes de intentar cerrarla.

if (conn->State != ConnectionState::Closed)
{
conn->Close();
}

if (conn.State != ConnectionState.Closed)
{
conn.Close();
}

If conn.State <> ConnectionState.Closed Then


conn.Close()
End IF

Si no comprueba el estado de la conexión antes de cerrar, se puede detectar la excepción


InvalidOperationException .
try
{
conn->Close();
}
catch (InvalidOperationException^ ex)
{
Console::WriteLine(ex->GetType()->FullName);
Console::WriteLine(ex->Message);
}

try
{
conn.Close();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.GetType().FullName);
Console.WriteLine(ex.Message);
}

Try
conn.Close()
Catch ex As InvalidOperationException
Console.WriteLine(ex.GetType().FullName)
Console.WriteLine(ex.Message)
End Try

El método que se elija depende de la frecuencia con la que espera que se produzca el evento.
Utilice el control de excepciones si el evento no se produce con frecuencia, es decir, si el evento es muy
excepcional e indica un error (como un final de archivo inesperado). Cuando se usa el control de
excepciones, se ejecuta menos código en condiciones normales.
Compruebe condiciones de error en el código cuando el evento se produce con frecuencia y se puede
considerar como parte de la ejecución normal. Cuando se buscan condiciones de error comunes, se ejecuta
menos código porque se evitan excepciones.

Diseñar clases para que se puedan evitar excepciones


Una clase puede proporcionar métodos o propiedades que permiten evitar realizar una llamada que
desencadenaría una excepción. Por ejemplo, una clase FileStream proporciona métodos que ayudan a determinar
si se ha alcanzado el final del archivo. Esto se puede usar para evitar la excepción que se inicia si se lee más allá
del final del archivo. En el ejemplo siguiente se muestra cómo leer hasta el final de un archivo sin desencadenar
una excepción.
class FileRead
{
public:
void ReadAll(FileStream^ fileToRead)
{
// This if statement is optional
// as it is very unlikely that
// the stream would ever be null.
if (fileToRead == nullptr)
{
throw gcnew System::ArgumentNullException();
}

int b;

// Set the stream position to the beginning of the file.


fileToRead->Seek(0, SeekOrigin::Begin);

// Read each byte to the end of the file.


for (int i = 0; i < fileToRead->Length; i++)
{
b = fileToRead->ReadByte();
Console::Write(b.ToString());
// Or do something else with the byte.
}
}
};

class FileRead
{
public void ReadAll(FileStream fileToRead)
{
// This if statement is optional
// as it is very unlikely that
// the stream would ever be null.
if (fileToRead == null)
{
throw new ArgumentNullException();
}

int b;

// Set the stream position to the beginning of the file.


fileToRead.Seek(0, SeekOrigin.Begin);

// Read each byte to the end of the file.


for (int i = 0; i < fileToRead.Length; i++)
{
b = fileToRead.ReadByte();
Console.Write(b.ToString());
// Or do something else with the byte.
}
}
}
Class FileRead
Public Sub ReadAll(fileToRead As FileStream)
' This if statement is optional
' as it is very unlikely that
' the stream would ever be null.
If fileToRead Is Nothing Then
Throw New System.ArgumentNullException()
End If

Dim b As Integer

' Set the stream position to the beginning of the file.


fileToRead.Seek(0, SeekOrigin.Begin)

' Read each byte to the end of the file.


For i As Integer = 0 To fileToRead.Length - 1
b = fileToRead.ReadByte()
Console.Write(b.ToString())
' Or do something else with the byte.
Next i
End Sub
End Class

Otro modo de evitar excepciones es devolver un valor NULL (o el valor predeterminado) para los casos de errores
muy frecuentes en lugar de iniciar una excepción. Un caso de error muy común se puede considerar como un
flujo de control normal. Al devolver un valor NULL en estos casos, se minimiza el impacto en el rendimiento de
una aplicación.
Para los tipos de valores, el uso de Nullable<T> o de valores predeterminados como indicador de error es algo
que se debe tener en cuenta para la aplicación en particular. Al utilizar Nullable<Guid> , default se convierte en
null en lugar de Guid.Empty . Algunas veces, agregar Nullable<T> puede aclarar cuando un valor está presente
o ausente. Otras veces, agregar Nullable<T> puede crear casos adicionales para comprobar que no son
necesarios, y solo sirven para crear posibles orígenes de errores.

Iniciar excepciones en lugar de devolver un código de error


Las excepciones garantizan que los errores no pasen desapercibidos porque la llamada al código no compruebe
un código de retorno.

Uso de los tipos de excepción predefinidos de .NET


Solo se deben introducir nuevas clases de excepción si no se puede aplicar ningún tipo predefinido. Por ejemplo:
Inicie una excepción InvalidOperationException si un conjunto de propiedades o una llamada a un método
no resultan apropiados de acuerdo con el estado actual del objeto.
Inicie una excepción ArgumentException o una de las clases predefinidas que derivan de
ArgumentException si se pasan parámetros no válidos.

Termine los nombres de clases de excepción con la palabra Exception


Cuando se necesite una excepción personalizada, debe ponerse el nombre apropiado y derivarla de la clase
Exception. Por ejemplo:

public ref class MyFileNotFoundException : public Exception


{
};
public class MyFileNotFoundException : Exception
{
}

Public Class MyFileNotFoundException


Inherits Exception
End Class

Incluir tres constructores en las clases de excepciones personalizadas


Use al menos tres constructores comunes al crear sus propias clases de excepción: el constructor sin parámetros,
un constructor que tome un mensaje de cadena y un constructor que tome un mensaje de cadena y una
excepción interna.
Exception(), que utiliza valores predeterminados.
Exception(String), que acepta un mensaje de cadena.
Exception(String, Exception), que acepta un mensaje de cadena y una excepción interna.
Como ejemplo, vea Cómo: Crear excepciones definidas por el usuario.

Asegúrese de que los datos de excepción estén disponibles cuando el


código se ejecute de forma remota
Cuando cree excepciones definidas por el usuario, debe garantizar que los metadatos de las excepciones están
disponibles para el código que se ejecute de forma remota.
Por ejemplo, en las implementaciones de .NET que admiten los dominios de aplicación, pueden producirse
excepciones entre dominios de aplicación. Por ejemplo, supongamos que el dominio de aplicación A crea el
dominio de aplicación B, que ejecuta código que inicia una excepción. Para que el dominio de aplicación A detecte
y controle la excepción correctamente, debe poder encontrar el ensamblado que contiene la excepción iniciada
por el dominio de aplicación B. Si el dominio de aplicación B inicia una excepción contenida en un ensamblado en
su base de aplicación pero no en la base de aplicación del dominio de aplicación A, el dominio de aplicación A no
podrá encontrar la excepción y common language runtime iniciará una excepción de FileNotFoundException. Para
evitar esta situación, puede implementar el ensamblado que contiene la información de la excepción de dos
maneras:
Ponga el ensamblado en una base de aplicación compartida por los dos dominios de aplicación.
-o-
Si los dominios no comparten una base de aplicación común, firme el ensamblado que contiene la
información de la excepción con un nombre seguro e impleméntelo en la caché global de ensamblados.

Usar mensajes de error gramaticalmente correctos


Escriba frases claras e incluya puntuación final. Todas las oraciones de la cadena asignada a la propiedad
Exception.Message deben terminar en punto. Por ejemplo, "la tabla del registro se ha desbordado". podría ser una
cadena de mensaje adecuada.

Incluir un mensaje de cadena localizada en todas las excepciones


El mensaje de error que ve el usuario deriva de la propiedad Exception.Message de la excepción que se ha
generado, y no del nombre de la clase de excepción. Normalmente, se asigna un valor a la propiedad
Exception.Message pasando la cadena de mensaje al argumento message de un constructor de excepciones.
Para las aplicaciones localizadas, debe proporcionar una cadena de mensaje localizada para todas las excepciones
que la aplicación pueda desencadenar. Use archivos de recursos para proporcionar mensajes de error localizados.
Para información sobre la localización de aplicaciones y la recuperación de cadenas localizadas, consulte los
siguientes artículos:
Procedimientos: Creación de excepciones definidas por el usuario con mensajes de excepción localizados
Recursos de aplicaciones de escritorio
System.Resources.ResourceManager

En excepciones personalizadas, proporcione propiedades adicionales


según sea necesario
Únicamente proporcione información adicional para una excepción, además de la cadena del mensaje
personalizado, si hay un escenario de programación en el que dicha información sea útil. Por ejemplo,
FileNotFoundException proporciona la propiedad FileName.

Colocar instrucciones de iniciación para que el seguimiento de la pila


sea útil
El seguimiento de pila comienza en la instrucción en que se produce la excepción y termina en la instrucción
catch que detecta la excepción.

Usar métodos de generador de excepciones


Es habitual que una clase produzca la misma excepción desde distintos lugares de su implementación. Para evitar
el exceso de código, use métodos del asistente que creen la excepción y la devuelvan. Por ejemplo:

ref class FileReader


{
private:
String^ fileName;

public:
FileReader(String^ path)
{
fileName = path;
}

array<Byte>^ Read(int bytes)


{
array<Byte>^ results = FileUtils::ReadFromFile(fileName, bytes);
if (results == nullptr)
{
throw NewFileIOException();
}
return results;
}

FileReaderException^ NewFileIOException()
{
String^ description = "My NewFileIOException Description";

return gcnew FileReaderException(description);


}
};
class FileReader
{
private string fileName;

public FileReader(string path)


{
fileName = path;
}

public byte[] Read(int bytes)


{
byte[] results = FileUtils.ReadFromFile(fileName, bytes);
if (results == null)
{
throw NewFileIOException();
}
return results;
}

FileReaderException NewFileIOException()
{
string description = "My NewFileIOException Description";

return new FileReaderException(description);


}
}

Class FileReader
Private fileName As String

Public Sub New(path As String)


fileName = path
End Sub

Public Function Read(bytes As Integer) As Byte()


Dim results() As Byte = FileUtils.ReadFromFile(fileName, bytes)
If results Is Nothing
Throw NewFileIOException()
End If
Return results
End Function

Function NewFileIOException() As FileReaderException


Dim description As String = "My NewFileIOException Description"

Return New FileReaderException(description)


End Function
End Class

En algunos casos, es más apropiado usar el constructor de excepciones para generar la excepción. Un ejemplo es
una clase de excepción global, como ArgumentException.

Restauración del estado cuando los métodos no se completan debido


a excepciones
Los autores de llamadas deben poder asumir que no se producen efectos no deseados cuando se produce una
excepción desde un método. Por ejemplo, si tiene código que transfiere dinero mediante la retirada de una cuenta
y el depósito en otra, y se inicia una excepción mientras se ejecuta el depósito, no quiere que la retirada siga
siendo efectiva.
public void TransferFunds(Account from, Account to, decimal amount)
{
from.Withdrawal(amount);
// If the deposit fails, the withdrawal shouldn't remain in effect.
to.Deposit(amount);
}

Public Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)


from.Withdrawal(amount)
' If the deposit fails, the withdrawal shouldn't remain in effect.
[to].Deposit(amount)
End Sub

El método anterior no produce ninguna excepción directamente, pero debe escribirse de forma defensiva para
que, si se produce un error en la operación de depósito, se invierta la retirada.
Para controlar esta situación se puede detectar cualquier excepción iniciada por la transacción del depósito y
revertir la retirada.

private static void TransferFunds(Account from, Account to, decimal amount)


{
string withdrawalTrxID = from.Withdrawal(amount);
try
{
to.Deposit(amount);
}
catch
{
from.RollbackTransaction(withdrawalTrxID);
throw;
}
}

Private Shared Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)


Dim withdrawalTrxID As String = from.Withdrawal(amount)
Try
[to].Deposit(amount)
Catch
from.RollbackTransaction(withdrawalTrxID)
Throw
End Try
End Sub

Este ejemplo muestra el uso de throw para volver a iniciar la excepción original, que puede facilitar a los autores
de llamada ver la causa del problema real sin tener que examinar la propiedad InnerException. Como alternativa
se puede iniciar una nueva excepción e incluir la excepción original como excepción interna:

catch (Exception ex)


{
from.RollbackTransaction(withdrawalTrxID);
throw new TransferFundsException("Withdrawal failed.", innerException: ex)
{
From = from,
To = to,
Amount = amount
};
}
Catch ex As Exception
from.RollbackTransaction(withdrawalTrxID)
Throw New TransferFundsException("Withdrawal failed.", innerException:=ex) With
{
.From = from,
.[To] = [to],
.Amount = amount
}
End Try

Vea también
Excepciones
Valores numéricos en .NET
06/05/2020 • 8 minutes to read • Edit Online

.NET proporciona una serie de tipos primitivos de entero numérico y de punto flotante, así como
System.Numerics.BigInteger, que es un tipo entero sin límite inferior ni superior teórico, System.Numerics.Complex,
que representa números complejos y un conjunto de tipos habilitados para SIMD en el espacio de nombres
System.Numerics.

Tipos enteros
.NET admite tipos enteros de 8, 16, 32 y 64 bits con signo y sin signo, que se enumeran en la tabla siguiente:

C O N SIGN O / SIN
T IP O SIGN O TA M A Ñ O ( EN B Y T ES) VA LO R M ÍN IM O VA LO R M Á XIM O

System.Byte Sin signo 1 0 255

System.Int16 Firmado 2 -32.768 32.767

System.Int32 Firmado 4 -2.147.483.648 2.147.483.647

System.Int64 Firmado 8 - 9.223.372.036.854.77


9.223.372.036.854.77 5.807
5.808

System.SByte Firmado 1 -128 127

System.UInt16 Sin signo 2 0 65.535

System.UInt32 Sin signo 4 0 4.294.967.295

System.UInt64 Sin signo 8 0 18.446.744.073.709.5


51.615

Cada tipo de entero admite un conjunto de operadores aritméticos estándar. La clase System.Math proporciona
métodos para un conjunto más amplio de funciones matemáticas.
También puede trabajar con los bits individuales de un valor entero usando la clase System.BitConverter.

NOTE
Los tipos enteros sin signo no son conformes a CLS. Para obtener más información, consulte Independencia del lenguaje y
componentes independientes del lenguaje.

BigInteger
La estructura System.Numerics.BigInteger es un tipo inmutable que representa un entero arbitrariamente grande
cuyo valor, en teoría, no tiene ningún límite superior o inferior. Los métodos del tipo BigInteger son análogos a los
de otros tipos integrales.
Tipos de punto flotante
.NET incluye tres tipos primitivos de punto flotante, que se enumeran en la tabla siguiente:

T IP O TA M A Ñ O ( EN B Y T ES) IN T ERVA LO A P RO XIM A DO P REC ISIÓ N

System.Single 4 De ±1,5 x 10-45 a ±3,4 x De 6 a 9 dígitos


1038 aproximadamente

System.Double 8 De ±5,0 × 10− 324 a ±1,7 × De 15 a 17 dígitos


10308 aproximadamente

System.Decimal 16 De ±1,0 x 10-28 to ±7,9228 28-29 dígitos


x 1028

Los tipos Single y Double admiten valores especiales que representan un valor no numérico e infinito. Por ejemplo,
el tipo Double proporciona los siguientes valores: Double.NaN, Double.NegativeInfinity y Double.PositiveInfinity.
Los métodos Double.IsNaN, Double.IsInfinity, Double.IsPositiveInfinity y Double.IsNegativeInfinity se usan para
comprobar estos valores especiales.
Cada tipo de punto flotante admite un conjunto de operadores aritméticos estándar. La clase System.Math
proporciona métodos para un conjunto más amplio de funciones matemáticas. .NET Core 2.0 y versiones
posteriores incluyen la clase System.MathF que proporciona métodos que aceptan argumentos del tipo Single.
También puede trabajar con bits individuales de valores Double y Single usando la clase System.BitConverter. La
estructura System.Decimal tiene sus propios métodos, Decimal.GetBits y Decimal(Int32[]), para trabajar con los bits
individuales de un valor decimal, así como su propio conjunto de métodos para realizar algunas operaciones
matemáticas adicionales.
Los tipos Double y Single están diseñados para usarse con valores que, por su naturaleza, no son precisos (por
ejemplo, la distancia entre dos estrellas) y para aplicaciones en las que no se necesita un alto grado de precisión ni
un mínimo error de redondeo. Use el tipo System.Decimal para los casos en los que se necesite una mayor
precisión y se deban minimizar los errores de redondeo.

NOTE
El tipo Decimal no elimina la necesidad de redondeo. En su lugar, minimiza los errores debido al redondeo.

Complex
La estructura System.Numerics.Complex representa un número complejo, es decir, un número con una parte de
número real y una parte de número imaginario. Admite un conjunto estándar de operadores de aritmética,
comparación, igualdad, conversión explícita e implícita, así como métodos matemáticos, algebraicos y
trigonométricos.

Tipos habilitados para SIMD


El espacio de nombres System.Numerics incluye un conjunto de tipos habilitados para SIMD para .NET. Las
operaciones SIMD (Single Instruction Multiple Data) se pueden paralelizar en el nivel de hardware. Eso aumenta el
rendimiento de los cálculos vectorizados, que son comunes en aplicaciones matemáticas, científicas y gráficas.
Los tipos habilitados para SIMD para .NET incluyen los siguientes:
Los tipos Vector2, Vector3 y Vector4, que representan vectores con los valores Single 2, 3 y 4.
Dos tipos de matriz: Matrix3x2, que representa una matriz de 3x2, y Matrix4x4, que representa una matriz de
4x4.
El tipo Plane, que representa un plano en un espacio tridimensional.
El tipo Quaternion, que representa un vector que se usa para codificar rotaciones físicas tridimensionales.
El tipo Vector<T>, que representa un vector de un tipo numérico especificado y proporciona un amplio
conjunto de operadores que aprovechan la compatibilidad con SIMD. El recuento de una instancia
Vector<T> es fijo, pero su valor Vector<T>.Count depende de la CPU de la máquina, en la que se ejecuta el
código.

NOTE
El Vector<T> tipo no está incluido en .NET Framework. Debe instalar el paquete NuGet System.Numerics.Vectors para
acceder a este tipo.

Los tipos habilitados para SIMD se implementan de tal forma que se pueden utilizar con hardware no habilitado
para SIMD o compiladores JIT. Para aprovechar las instrucciones de SIMD, las aplicaciones de 64 bits las debe
ejecutar el entorno en tiempo de ejecución que usa el compilador RyuJIT, que se incluye en .NET Core y en .NET
Framework 4.6 y versiones posteriores. Agrega compatibilidad con SIMD cuando se usan procesadores de 64 bits
como destino.
Para obtener más información, vea Uso de tipos numéricos acelerados por SIMD.

Vea también
Cadenas con formato numérico estándar
Fechas, horas y zonas horarias
16/09/2020 • 7 minutes to read • Edit Online

Además de la estructura básica DateTime, .NET proporciona las siguientes clases que permiten trabajar con zonas
horarias:
TimeZone
Use esta clase para trabajar con la zona horaria local del sistema y la zona de la hora universal coordinada
(UTC). La TimeZone clase reemplaza en gran medida la funcionalidad de la clase TimeZoneInfo .
TimeZoneInfo
Use esta clase para trabajar con cualquier zona horaria predefinida en un sistema, para crear zonas horarias
nuevas y para convertir fácilmente fechas y horas desde una zona horaria a otra. Para desarrollo nuevo,
debe usar la clase TimeZoneInfo, en lugar de la clase TimeZone.
DateTimeOffset
Use esta estructura para trabajar con fechas y horas cuyo desplazamiento (o diferencia) con respecto a la
hora UTC es conocido. La estructura DateTimeOffset combina un valor de fecha y hora con ese
desplazamiento de hora de UTC. Debido a su relación con la hora UTC, un valor individual de fecha y hora
identifica de forma inequívoca un punto temporal único. Esto hace que un valor de DateTimeOffset sea más
portátil de un equipo a otro que un valor de DateTime.
Esta sección de la documentación proporciona la información que necesita para trabajar con zonas horarias y para
crear aplicaciones basadas en la zona horaria que puedan convertir fechas y horas de una zona horaria a otra.

En esta sección
Información general sobre zonas horarias describe la terminología, los conceptos y los problemas relacionados con
la creación de aplicaciones basadas en la zona horaria.
Elegir entre DateTime, DateTimeOffset, TimeSpan y TimeZoneInfo Describe cuándo usar los DateTime
DateTimeOffset tipos, y TimeZoneInfo al trabajar con datos de fecha y hora.
En Finding the time zones defined on a local system (Buscar las zonas horarias definidas en un sistema local) se
describe cómo enumerar las zonas horarias que se encuentran en un sistema local.
En How to: Enumerate time zones present on a computer (Cómo: enumerar zonas horarias presentes en un equipo)
se proporcionan ejemplos que enumeran las zonas horarias definidas en el Registro de un equipo y que permiten a
los usuarios seleccionar una zona horaria predefinida de una lista.
En How to: Access the predefined UTC and local time zone objects (Cómo: obtener acceso a los objetos de zona
horaria local y UTC predefinidos) se describe cómo tener acceso a la hora universal coordinada y a la zona horaria
local.
En How to: Instantiate a TimeZoneInfo object (Cómo: crear instancias de un objeto TimeZoneInfo) se describe cómo
crear una instancia de un objeto TimeZoneInfo desde el Registro del sistema local.
En Instantiating a DateTimeOffset object (Crear instancias de un objeto DateTimeOffset) se describen las formas en
que pueden crearse instancias de un objeto DateTimeOffset y las formas en que un valor DateTime se puede
convertir en un valor DateTimeOffset.
En How to: Create time zones without adjustment rules (Cómo: crear zonas horarias sin reglas de ajuste) se
describe cómo crear una zona horaria personalizada que no admite la transición al horario de verano o desde este.
En How to: Create time zones with adjustment rules (Cómo: crear zonas horarias con reglas de ajuste) se describe
cómo crear una zona horaria personalizada que admite una o más transiciones al horario de verano o desde este.
En Saving and restoring time zones (Guardar y restaurar zonas horarias) se describe la compatibilidad de
TimeZoneInfo con la serialización y la deserialización de datos de zona horaria y muestra algunos de los escenarios
en que se pueden usar estas características.
En How to: Save time zones to an embedded resource (Cómo: guardar zonas horarias en un recurso insertado) se
describe cómo crear una zona horaria personalizada y guardar su información en un archivo de recursos.
En How to: Restore time zones from an embedded resource (Cómo: restaurar zonas horarias de un recurso
insertado) se describe cómo crear instancias de zonas horarias personalizadas que se han guardado en un archivo
de recursos insertado.
En Performing arithmetic operations with dates and times (Efectuar operaciones aritméticas con fechas y horas) se
describen los aspectos necesarios para agregar, sustraer y comparar los valores DateTime y DateTimeOffset.
En How to: Use time zones in date and time arithmetic (Cómo: usar zonas horarias en aritmética de fecha y hora) se
describe cómo realizar la aritmética de fecha y hora que refleja las reglas de ajuste de una zona horaria.
En Converting between DateTime and DateTimeOffset (Convertir entre DateTime y DateTimeOffset) se describe
cómo convertir entre valores DateTime y DateTimeOffset.
En Converting times between time zones (Convertir horas entre zonas horarias) se describe cómo convertir las
horas de una zona horaria a otra.
En How to: Resolve ambiguous times (Cómo: resolver horas ambiguas) se describe cómo resolver una hora
ambigua mediante su asignación a la hora estándar de la zona horaria.
Cómo: Permitir que los usuarios resuelvan horas ambiguas describe cómo permitir que los usuarios determinen la
asignación entre una hora local ambigua y la hora universal coordinada.

Referencia
System.TimeZoneInfo
Tipos de formato en .NET
16/09/2020 • 64 minutes to read • Edit Online

Aplicar formato es el proceso de convertir una instancia de una clase, una estructura o un valor de enumeración
en su representación de cadena, a menudo para que la cadena resultante se pueda mostrar a los usuarios o
deserializar para restaurar el tipo de datos original. Esta conversión puede plantear varios desafíos:
La forma en que se almacenan internamente los valores no refleja necesariamente la manera en que los
usuarios desean verlos. Por ejemplo, un número de teléfono podría almacenarse con el formato
8009999999, que no es fácil de usar. Se debería mostrar en su lugar como 800-999-9999. Consulte la
sección Cadenas de formato personalizado para obtener un ejemplo en el que da formato a un número
de esta forma.
A veces, la conversión de un objeto en su representación de cadena no es intuitiva. Por ejemplo, no está
claro cómo debe aparecer la representación de cadena de un objeto Temperature o un objeto Person. Para
obtener un ejemplo en el que se da formato a un objeto Temperature de varias formas, consulte la
sección Cadenas de formato estándar .
A menudo, los valores requieren un formato dependiente de la referencia cultural. Por ejemplo, en una
aplicación en la que se usan números para reflejar los valores monetarios, las cadenas numéricas deben
incluir el símbolo de divisa, el separador de grupo (que en la mayoría de las referencias culturales es el
separador de miles) y el símbolo decimal correspondientes a la referencia cultural actual. Para ver un
ejemplo, consulte la sección Formato según la referencia cultural con proveedores de formato.
Puede que una aplicación muestre el mismo valor de diferentes maneras. Por ejemplo, es posible que una
aplicación represente un miembro de enumeración mostrando una representación de cadena de su
nombre o mostrando su valor subyacente. Para obtener un ejemplo en el que se da formato a un
miembro de la enumeración DayOfWeek de maneras diferentes, consulte la sección Cadenas de formato
estándar .

NOTE
La aplicación de formato convierte el valor de un tipo en una representación de cadena. El análisis es lo contrario que la
aplicación de formato. Una operación de análisis crea una instancia de un tipo de datos a partir de su representación de
cadena. Para información sobre cómo convertir cadenas en otros tipos de datos, vea Analizar cadenas en .NET.

.NET proporciona compatibilidad de formato enriquecida que permite a los desarrolladores hacer frente a estos
requisitos.

Formato en .NET
El mecanismo básico para aplicar formato es la implementación predeterminada del método Object.ToString,
que se explica en la sección Formato predeterminado mediante el método ToString más adelante en este tema.
Pero .NET proporciona varias formas de modificar y extender su compatibilidad de formato predeterminado.
Entre ellas se incluyen las siguientes:
Invalidar el método Object.ToString para definir una representación de cadena personalizada del valor de
un objeto. Para obtener más información, consulte la sección Invalidación del método ToString más
adelante en este tema.
Definir especificadores de formato que permitan que la representación de cadena del valor de un objeto
adopte varios formatos. Por ejemplo, el especificador de formato "X" en la siguiente instrucción convierte
un entero en la representación de cadena de un valor hexadecimal.

int integerValue = 60312;


Console.WriteLine(integerValue.ToString("X")); // Displays EB98.

Dim integerValue As Integer = 60312


Console.WriteLine(integerValue.ToString("X")) ' Displays EB98.

Para obtener más información sobre los especificadores de formato, vea la sección Método ToString y
cadenas de formato .
Usar proveedores de formato para aprovechar las convenciones de formato de una referencia cultural
concreta. Por ejemplo, la instrucción siguiente muestra un valor de divisa usando las convenciones de
formato de la referencia cultural en-US.

double cost = 1632.54;


Console.WriteLine(cost.ToString("C",
new System.Globalization.CultureInfo("en-US")));
// The example displays the following output:
// $1,632.54

Dim cost As Double = 1632.54


Console.WriteLine(cost.ToString("C", New System.Globalization.CultureInfo("en-US")))
' The example displays the following output:
' $1,632.54

Para obtener más información sobre cómo aplicar formato con proveedores de formato, consulte la
sección Proveedores de formato.
Implementar la interfaz IFormattable para admitir tanto la conversión de cadenas con la clase Convert
como formatos compuestos. Para obtener más información, vea la sección Interfaz IFormattable .
Usar formatos compuestos para incrustar la representación de cadena de un valor en una cadena más
larga. Para obtener más información, vea la sección Formatos compuestos .
Implementar ICustomFormatter y IFormatProvider para proporcionar una solución de formato
personalizado completa. Para obtener más información, vea la sección Formato personalizado con
ICustomFormatter .
En las secciones siguientes se examinan estos métodos para convertir un objeto en su representación de cadena.

Formato predeterminado mediante el método ToString


Cada tipo derivado de System.Object hereda automáticamente un método ToString sin parámetros, que
devuelve el nombre del tipo de forma predeterminada. En el ejemplo siguiente se ilustra el método ToString
predeterminado. Se define una clase denominada Automobile que no tiene ninguna implementación. Cuando se
crea una instancia de la clase y se llama a su método ToString , se muestra el nombre de su tipo. Observe que
en el ejemplo no se llama explícitamente al método ToString . El método Console.WriteLine(Object) llama
implícitamente al método ToString del objeto pasado como argumento.
using System;

public class Automobile


{
// No implementation. All members are inherited from Object.
}

public class Example


{
public static void Main()
{
Automobile firstAuto = new Automobile();
Console.WriteLine(firstAuto);
}
}
// The example displays the following output:
// Automobile

Public Class Automobile


' No implementation. All members are inherited from Object.
End Class

Module Example
Public Sub Main()
Dim firstAuto As New Automobile()
Console.WriteLine(firstAuto)
End Sub
End Module
' The example displays the following output:
' Automobile

WARNING
A partir de Windows 8.1, Windows Runtime incluye una interfaz IStringable con un solo método, IStringable.ToString, que
ofrece compatibilidad con el formato predeterminado. Sin embargo, es recomendable que los tipos administrados no
implementen la interfaz IStringable . Para obtener más información, vea la sección "Windows Runtime y la interfaz
IStringable " de la página de referencia Object.ToString.

Puesto que todos los tipos distintos de las interfaces se derivan de Object, esta funcionalidad se proporciona
automáticamente a sus clases o estructuras personalizadas. Si bien, la funcionalidad que ofrece el método
ToString predeterminado es limitada: Aunque identifica el tipo, no proporciona ninguna información sobre una
instancia del tipo. Para proporcionar una representación de cadena de un objeto que proporciona información
sobre ese objeto, debe invalidar el método ToString .

NOTE
Las estructuras heredan de ValueType, que a su vez se deriva de Object. Aunque ValueType invalida Object.ToString, su
implementación es idéntica.

Invalidación del método ToString


La presentación del nombre de un tipo suele tener un uso limitado y no permite a los consumidores de sus tipos
diferenciar una instancia de otra. Sin embargo, puede invalidar el método ToString para proporcionar una
representación más útil del valor de un objeto. En el ejemplo siguiente se define un objeto Temperature y se
invalida su método ToString para mostrar la temperatura en grados centígrados.
using System;

public class Temperature


{
private decimal temp;

public Temperature(decimal temperature)


{
this.temp = temperature;
}

public override string ToString()


{
return this.temp.ToString("N1") + "°C";
}
}

public class Example


{
public static void Main()
{
Temperature currentTemperature = new Temperature(23.6m);
Console.WriteLine("The current temperature is " +
currentTemperature.ToString());
}
}
// The example displays the following output:
// The current temperature is 23.6°C.

Public Class Temperature


Private temp As Decimal

Public Sub New(temperature As Decimal)


Me.temp = temperature
End Sub

Public Overrides Function ToString() As String


Return Me.temp.ToString("N1") + "°C"
End Function
End Class

Module Example
Public Sub Main()
Dim currentTemperature As New Temperature(23.6d)
Console.WriteLine("The current temperature is " +
currentTemperature.ToString())
End Sub
End Module
' The example displays the following output:
' The current temperature is 23.6°C.

En .NET, el método ToString de cada tipo de valor primitivo se ha invalidado para que se muestre el valor del
objeto en lugar de su nombre. En la tabla siguiente se muestra la invalidación para cada tipo primitivo. Observe
que la mayoría de los métodos invalidados llaman a otra sobrecarga del método ToString y le pasan el
especificador de formato "G", que define el formato general de su tipo, y un objeto IFormatProvider que
representa la referencia cultural actual.

T IP O IN VA L IDA C IÓ N DE TO ST RIN G

Boolean Devuelve Boolean.TrueString o Boolean.FalseString.


T IP O IN VA L IDA C IÓ N DE TO ST RIN G

Byte Llama a
Byte.ToString("G", NumberFormatInfo.CurrentInfo)
para dar formato al valor de tipo Byte correspondiente a la
referencia cultural actual.

Char Devuelve el carácter como una cadena.

DateTime Llama a
DateTime.ToString("G",
DatetimeFormatInfo.CurrentInfo)
para dar formato al valor de fecha y hora correspondiente a
la referencia cultural actual.

Decimal Llama a
Decimal.ToString("G", NumberFormatInfo.CurrentInfo)
para dar formato al valor de tipo Decimal correspondiente a
la referencia cultural actual.

Double Llama a
Double.ToString("G", NumberFormatInfo.CurrentInfo)
para dar formato al valor de tipo Double correspondiente a
la referencia cultural actual.

Int16 Llama a
Int16.ToString("G", NumberFormatInfo.CurrentInfo)
para dar formato al valor de tipo Int16 correspondiente a la
referencia cultural actual.

Int32 Llama a
Int32.ToString("G", NumberFormatInfo.CurrentInfo)
para dar formato al valor de tipo Int32 correspondiente a la
referencia cultural actual.

Int64 Llama a
Int64.ToString("G", NumberFormatInfo.CurrentInfo)
para dar formato al valor de tipo Int64 correspondiente a la
referencia cultural actual.

SByte Llama a
SByte.ToString("G", NumberFormatInfo.CurrentInfo)
para dar formato al valor de tipo SByte correspondiente a la
referencia cultural actual.

Single Llama a
Single.ToString("G", NumberFormatInfo.CurrentInfo)
para dar formato al valor de tipo Single correspondiente a la
referencia cultural actual.

UInt16 Llama a
UInt16.ToString("G", NumberFormatInfo.CurrentInfo)
para dar formato al valor de tipo UInt16 correspondiente a
la referencia cultural actual.
T IP O IN VA L IDA C IÓ N DE TO ST RIN G

UInt32 Llama a
UInt32.ToString("G", NumberFormatInfo.CurrentInfo)
para dar formato al valor de tipo UInt32 correspondiente a
la referencia cultural actual.

UInt64 Llama a
UInt64.ToString("G", NumberFormatInfo.CurrentInfo)
para dar formato al valor de tipo UInt64 correspondiente a
la referencia cultural actual.

Método ToString y cadenas de formato


Recurrir al método ToString predeterminado o invalidar ToString resulta apropiado cuando un objeto tiene
una única representación de cadena. Sin embargo, el valor de un objeto tiene a menudo varias representaciones.
Por ejemplo, una temperatura puede expresarse en grados Fahrenheit, grados centígrados o grados Kelvin. De
manera similar, el valor entero 10 puede representarse de numerosas maneras; por ejemplo, 10, 10,0, 1,0e01 o
$10,00.
Para que un valor pueda tener varias representaciones de cadena, .NET usa cadenas de formato. Una cadena de
formato es una cadena que contiene uno o varios especificadores de formato predefinidos, que son caracteres
individuales o grupos de caracteres que definen cómo el método ToString debe dar formato a su salida. A
continuación, se pasa la cadena de formato como un parámetro al método ToString del objeto, por lo que
determina cómo debe mostrarse la representación de cadena del valor de ese objeto.
Todos los tipos numéricos, de fecha y hora, y de enumeración de .NET admiten un conjunto predefinido de
especificadores de formato. Las cadenas de formato también se pueden emplear para definir varias
representaciones de cadena de los tipos de datos definidos por una aplicación.
Cadenas de formato estándar
Una cadena de formato estándar contiene un único especificador de formato, que es un carácter alfabético que
define la representación de cadena del objeto al que se aplica, junto con un especificador de precisión opcional
que afecta a cuántos dígitos se muestran en la cadena de resultado. Si el especificador de precisión se omite o
no se admite, un especificador de formato estándar es equivalente a una cadena de formato estándar.
.NET define un conjunto de especificadores de formato estándar para todos los tipos numéricos, de fecha y hora,
y de enumeración. Por ejemplo, cada una de estas categorías admite un especificador de formato estándar "G",
que define una representación de cadena general de un valor de ese tipo.
Las cadenas de formato estándar para los tipos de enumeración controlan directamente la representación de
cadena de un valor. Las cadenas de formato que se pasan al método ToString de un valor de enumeración
determinan si el valor se muestra con su nombre de cadena (especificadores de formato "G" y "F"), su valor
integral subyacente (especificador de formato "D") o su valor hexadecimal (especificador de formato "X"). En el
ejemplo siguiente se muestra el uso de cadenas de formato estándar para dar formato a un valor de
enumeración DayOfWeek .
DayOfWeek thisDay = DayOfWeek.Monday;
string[] formatStrings = {"G", "F", "D", "X"};

foreach (string formatString in formatStrings)


Console.WriteLine(thisDay.ToString(formatString));
// The example displays the following output:
// Monday
// Monday
// 1
// 00000001

Dim thisDay As DayOfWeek = DayOfWeek.Monday


Dim formatStrings() As String = {"G", "F", "D", "X"}

For Each formatString As String In formatStrings


Console.WriteLine(thisDay.ToString(formatString))
Next
' The example displays the following output:
' Monday
' Monday
' 1
' 00000001

Para información sobre las cadenas de formato de enumeración, vea Enumeration Format Strings.
Las cadenas de formato estándar para tipos numéricos normalmente definen una cadena de resultado cuya
apariencia exacta está controlada por uno o más valores de propiedad. Por ejemplo, el especificador de formato
"C" da formato a un número como un valor de divisa. Al llamar al método ToString con el especificador de
formato "C" como único parámetro, se usan los siguientes valores de propiedad del objeto NumberFormatInfo
de la referencia cultural actual para definir la representación de cadena del valor numérico:
La propiedad CurrencySymbol, que especifica el símbolo de divisa de la referencia cultural actual.
La propiedad CurrencyNegativePattern o CurrencyPositivePattern , que devuelve un entero que
determina lo siguiente:
La posición del símbolo de divisa.
Si los valores negativos se indican mediante un signo negativo inicial, un signo negativo final o
paréntesis.
Si aparece un espacio entre el valor numérico y el símbolo de divisa.
La propiedad CurrencyDecimalDigits , que define el número de dígitos fraccionarios en la cadena de
resultado.
La propiedad CurrencyDecimalSeparator , que define el símbolo del separador decimal en la cadena de
resultado.
La propiedad CurrencyGroupSeparator , que define el símbolo del separador de grupo.
La propiedad CurrencyGroupSizes , que define el número de dígitos de cada grupo que hay a la izquierda
del decimal.
La propiedad NegativeSign , que determina el signo negativo usado en la cadena de resultado si no se
emplean paréntesis para indicar valores negativos.
Además, las cadenas de formato numérico pueden incluir un especificador de precisión. El significado de este
especificador depende de la cadena de formato con la que se usa, pero suele indicar el número total de dígitos o
el número de dígitos fraccionarios que deben aparecer en la cadena de resultado. Por ejemplo, en el ejemplo
siguiente se usa la cadena numérica estándar "X4" y un especificador de precisión para crear un valor de cadena
que tiene cuatro dígitos hexadecimales.

byte[] byteValues = { 12, 163, 255 };


foreach (byte byteValue in byteValues)
Console.WriteLine(byteValue.ToString("X4"));
// The example displays the following output:
// 000C
// 00A3
// 00FF

Dim byteValues() As Byte = {12, 163, 255}


For Each byteValue As Byte In byteValues
Console.WriteLine(byteValue.ToString("X4"))
Next
' The example displays the following output:
' 000C
' 00A3
' 00FF

Para más información sobre las cadenas de formato numérico estándar, vea Standard Numeric Format Strings.
Las cadenas de formato estándar para valores de fecha y hora son alias de las cadenas de formato
personalizado almacenadas por una propiedad DateTimeFormatInfo determinada. Por ejemplo, al llamar al
método ToString de un valor de fecha y hora con el especificador de formato "D" se muestran la fecha y la hora
usando la cadena de formato personalizado que está almacenada en la propiedad
DateTimeFormatInfo.LongDatePattern de la referencia cultural actual. (Para obtener más información sobre las
cadenas de formato personalizado, vea la próxima sección). En el ejemplo siguiente se ilustra esta relación.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
DateTime date1 = new DateTime(2009, 6, 30);
Console.WriteLine("D Format Specifier: {0:D}", date1);
string longPattern = CultureInfo.CurrentCulture.DateTimeFormat.LongDatePattern;
Console.WriteLine("'{0}' custom format string: {1}",
longPattern, date1.ToString(longPattern));
}
}
// The example displays the following output when run on a system whose
// current culture is en-US:
// D Format Specifier: Tuesday, June 30, 2009
// 'dddd, MMMM dd, yyyy' custom format string: Tuesday, June 30, 2009
Imports System.Globalization

Module Example
Public Sub Main()
Dim date1 As Date = #6/30/2009#
Console.WriteLine("D Format Specifier: {0:D}", date1)
Dim longPattern As String = CultureInfo.CurrentCulture.DateTimeFormat.LongDatePattern
Console.WriteLine("'{0}' custom format string: {1}", _
longPattern, date1.ToString(longPattern))
End Sub
End Module
' The example displays the following output when run on a system whose
' current culture is en-US:
' D Format Specifier: Tuesday, June 30, 2009
' 'dddd, MMMM dd, yyyy' custom format string: Tuesday, June 30, 2009

Para más información sobre las cadenas de formato de fecha y hora estándar, vea Standard Date and Time
Format Strings.
También se pueden emplear las cadenas de formato estándar para definir la representación de cadena de un
objeto definido por la aplicación que genera el método ToString(String) del objeto. Puede definir los
especificadores de formato estándar concretos que su objeto admite y determinar si distinguen entre
mayúsculas y minúsculas o no. Su implementación del método ToString(String) debe aceptar lo siguiente:
Un especificador de formato "G" que representa un formato personalizado o común del objeto. La
sobrecarga sin parámetros del método ToString del objeto debe llamar a su sobrecarga de
ToString(String) y pasarle la cadena de formato estándar "G".

Compatibilidad con un especificador de formato que sea igual a una referencia nula ( Nothing en Visual
Basic). Un especificador de formato que es igual a una referencia nula debe considerarse equivalente al
especificador de formato "G".
Por ejemplo, una clase Temperature puede almacenar internamente la temperatura en grados centígrados y
usar especificadores de formato para representar el valor del objeto Temperature en grados centígrados, grados
Fahrenheit y grados Kelvin. Esto se muestra en el ejemplo siguiente.

using System;

public class Temperature


{
private decimal m_Temp;

public Temperature(decimal temperature)


{
this.m_Temp = temperature;
}

public decimal Celsius


{
get { return this.m_Temp; }
}

public decimal Kelvin


{
get { return this.m_Temp + 273.15m; }
}

public decimal Fahrenheit


{
get { return Math.Round(((decimal) (this.m_Temp * 9 / 5 + 32)), 2); }
}

public override string ToString()


public override string ToString()
{
return this.ToString("C");
}

public string ToString(string format)


{
// Handle null or empty string.
if (String.IsNullOrEmpty(format)) format = "C";
// Remove spaces and convert to uppercase.
format = format.Trim().ToUpperInvariant();

// Convert temperature to Fahrenheit and return string.


switch (format)
{
// Convert temperature to Fahrenheit and return string.
case "F":
return this.Fahrenheit.ToString("N2") + " °F";
// Convert temperature to Kelvin and return string.
case "K":
return this.Kelvin.ToString("N2") + " K";
// return temperature in Celsius.
case "G":
case "C":
return this.Celsius.ToString("N2") + " °C";
default:
throw new FormatException(String.Format("The '{0}' format string is not supported.", format));
}
}
}

public class Example


{
public static void Main()
{
Temperature temp1 = new Temperature(0m);
Console.WriteLine(temp1.ToString());
Console.WriteLine(temp1.ToString("G"));
Console.WriteLine(temp1.ToString("C"));
Console.WriteLine(temp1.ToString("F"));
Console.WriteLine(temp1.ToString("K"));

Temperature temp2 = new Temperature(-40m);


Console.WriteLine(temp2.ToString());
Console.WriteLine(temp2.ToString("G"));
Console.WriteLine(temp2.ToString("C"));
Console.WriteLine(temp2.ToString("F"));
Console.WriteLine(temp2.ToString("K"));

Temperature temp3 = new Temperature(16m);


Console.WriteLine(temp3.ToString());
Console.WriteLine(temp3.ToString("G"));
Console.WriteLine(temp3.ToString("C"));
Console.WriteLine(temp3.ToString("F"));
Console.WriteLine(temp3.ToString("K"));

Console.WriteLine(String.Format("The temperature is now {0:F}.", temp3));


}
}
// The example displays the following output:
// 0.00 °C
// 0.00 °C
// 0.00 °C
// 32.00 °F
// 273.15 K
// -40.00 °C
// -40.00 °C
// -40.00 °C
// -40.00 °F
// 233.15 K
// 16.00 °C
// 16.00 °C
// 16.00 °C
// 60.80 °F
// 289.15 K
// The temperature is now 16.00 °C.

Public Class Temperature


Private m_Temp As Decimal

Public Sub New(temperature As Decimal)


Me.m_Temp = temperature
End Sub

Public ReadOnly Property Celsius() As Decimal


Get
Return Me.m_Temp
End Get
End Property

Public ReadOnly Property Kelvin() As Decimal


Get
Return Me.m_Temp + 273.15d
End Get
End Property

Public ReadOnly Property Fahrenheit() As Decimal


Get
Return Math.Round(CDec(Me.m_Temp * 9 / 5 + 32), 2)
End Get
End Property

Public Overrides Function ToString() As String


Return Me.ToString("C")
End Function

Public Overloads Function ToString(format As String) As String


' Handle null or empty string.
If String.IsNullOrEmpty(format) Then format = "C"
' Remove spaces and convert to uppercase.
format = format.Trim().ToUpperInvariant()

Select Case format


Case "F"
' Convert temperature to Fahrenheit and return string.
Return Me.Fahrenheit.ToString("N2") & " °F"
Case "K"
' Convert temperature to Kelvin and return string.
Return Me.Kelvin.ToString("N2") & " K"
Case "C", "G"
' Return temperature in Celsius.
Return Me.Celsius.ToString("N2") & " °C"
Case Else
Throw New FormatException(String.Format("The '{0}' format string is not supported.",
format))
End Select
End Function
End Class

Public Module Example


Public Sub Main()
Dim temp1 As New Temperature(0d)
Console.WriteLine(temp1.ToString())
Console.WriteLine(temp1.ToString("G"))
Console.WriteLine(temp1.ToString("C"))
Console.WriteLine(temp1.ToString("F"))
Console.WriteLine(temp1.ToString("K"))

Dim temp2 As New Temperature(-40d)


Console.WriteLine(temp2.ToString())
Console.WriteLine(temp2.ToString("G"))
Console.WriteLine(temp2.ToString("C"))
Console.WriteLine(temp2.ToString("F"))
Console.WriteLine(temp2.ToString("K"))

Dim temp3 As New Temperature(16d)


Console.WriteLine(temp3.ToString())
Console.WriteLine(temp3.ToString("G"))
Console.WriteLine(temp3.ToString("C"))
Console.WriteLine(temp3.ToString("F"))
Console.WriteLine(temp3.ToString("K"))

Console.WriteLine(String.Format("The temperature is now {0:F}.", temp3))


End Sub
End Module
' The example displays the following output:
' 0.00 °C
' 0.00 °C
' 0.00 °C
' 32.00 °F
' 273.15 K
' -40.00 °C
' -40.00 °C
' -40.00 °C
' -40.00 °F
' 233.15 K
' 16.00 °C
' 16.00 °C
' 16.00 °C
' 60.80 °F
' 289.15 K
' The temperature is now 16.00 °C.

Cadenas de formato personalizado


Además de las cadenas de formato estándar, .NET define cadenas de formato personalizado tanto para los
valores numéricos como para los valores de fecha y hora. Una cadena de formato personalizado se compone de
uno o varios especificadores de formato personalizado que definen la representación de cadena de un valor. Por
ejemplo, la cadena de formato personalizado de fecha y hora “yyyy/mm/dd hh:mm:ss.ffff t zzz” convierte una
fecha en su representación de cadena con el formato "2008/11/15 07:45:00.0000 P -08:00" para la referencia
cultural en-US. Del mismo modo, la cadena de formato personalizado “0000” convierte el valor entero 12 en
“0012”. Para una lista completa de las cadenas de formato personalizado, vea Custom Date and Time Format
Strings y Custom Numeric Format Strings.
Si una cadena de formato consta de un único especificador de formato personalizado, el especificador de
formato debe ir precedido del símbolo de porcentaje (%) para evitar la confusión con un especificador de
formato estándar. En el ejemplo siguiente, se usa el especificador de formato personalizado "M" para mostrar un
número de un dígito o de dos dígitos del mes de una fecha determinada.

DateTime date1 = new DateTime(2009, 9, 8);


Console.WriteLine(date1.ToString("%M")); // Displays 9

Dim date1 As Date = #09/08/2009#


Console.WriteLine(date1.ToString("%M")) ' Displays 9

Muchas cadenas de formato estándar para valores de fecha y hora son alias para cadenas de formato
personalizado definidas por propiedades del objeto DateTimeFormatInfo . Las cadenas de formato
personalizado también ofrecen una gran flexibilidad a la hora de proporcionar formatos definidos por la
aplicación para los valores numéricos o de fecha y hora. Puede definir sus propias cadenas de resultado
personalizadas para los valores numéricos y de fecha y hora si combina varios especificadores de formato
personalizados en una única cadena de formato personalizado. En el ejemplo siguiente se define una cadena de
formato personalizado que muestra el día de la semana entre paréntesis después del nombre de mes, el día y el
año.

string customFormat = "MMMM dd, yyyy (dddd)";


DateTime date1 = new DateTime(2009, 8, 28);
Console.WriteLine(date1.ToString(customFormat));
// The example displays the following output if run on a system
// whose language is English:
// August 28, 2009 (Friday)

Dim customFormat As String = "MMMM dd, yyyy (dddd)"


Dim date1 As Date = #8/28/2009#
Console.WriteLine(date1.ToString(customFormat))
' The example displays the following output if run on a system
' whose language is English:
' August 28, 2009 (Friday)

En el ejemplo siguiente se define una cadena de formato personalizado que muestra un valor de Int64 como un
número de teléfono de Estados Unidos estándar de siete dígitos con el código de área.

using System;

public class Example


{
public static void Main()
{
long number = 8009999999;
string fmt = "000-000-0000";
Console.WriteLine(number.ToString(fmt));
}
}
// The example displays the following output:
// 800-999-9999

Module Example
Public Sub Main()
Dim number As Long = 8009999999
Dim fmt As String = "000-000-0000"
Console.WriteLine(number.ToString(fmt))
End Sub
End Module
' The example displays the following output:

' The example displays the following output:


' 800-999-9999

Aunque las cadenas de formato estándar pueden satisfacer generalmente la mayoría de las necesidades de
formato para los tipos definidos por la aplicación, también puede definir especificadores de formato
personalizados para dar formato a sus tipos.
Cadenas de formato y tipos de .NET
Todos los tipos numéricos (es decir, los tipos Byte, Decimal, Double, Int16, Int32, Int64, SByte, Single, UInt16,
UInt32, UInt64 y BigInteger), así como DateTime, DateTimeOffset, TimeSpan, Guidy todos los tipos de
enumeración, son compatibles con el formato con cadenas de formato. Para obtener información sobre las
cadenas de formato específicas que admite cada tipo, consulte los temas siguientes:

T IT L E DEF IN IC IÓ N

Standard Numeric Format Strings Describe cadenas de formato estándar que crean
representaciones de cadena usadas con frecuencia de valores
numéricos.

Custom Numeric Format Strings Describe cadenas de formato personalizado que crean
formatos específicos de la aplicación para valores numéricos.

Cadenas con formato de fecha y hora estándar Describe cadenas de formato estándar que crean
representaciones de cadena usadas con frecuencia de valores
DateTime y DateTimeOffset.

Cadenas con formato de fecha y hora personalizado Describe cadenas de formato personalizado que crean
formatos específicos de la aplicación para valores DateTime y
DateTimeOffset.

Cadenas de formato TimeSpan estándar Describe cadenas de formato estándar que crean
representaciones de cadena usadas con frecuencia de
intervalos de tiempo.

Cadenas de formato TimeSpan personalizado Describe cadenas de formato personalizado que crean
formatos específicos de la aplicación para intervalos de
tiempo.

Enumeration Format Strings Describe cadenas de formato estándar que se usan para
crear representaciones de cadena de valores de
enumeración.

Guid.ToString(String) Describe cadenas de formato estándar para los valores de


Guid .

Formato según la referencia cultural con proveedores de formato


Si bien los especificadores de formato permiten personalizar el formato de los objetos, la generación de una
representación de cadena significativa de los objetos requiere a menudo información de formato adicional. Por
ejemplo, cuando se da formato a un número como un valor de divisa mediante la cadena de formato estándar
"C" o la cadena de formato personalizado “$ #,#.00”, se necesita como mínimo información sobre el símbolo de
divisa, el separador de grupos y el separador decimal correctos para incluirla en la cadena con formato. En .NET,
esta información de formato adicional está disponible mediante la interfaz IFormatProvider, que se proporciona
como un parámetro a una o más sobrecargas del método ToString de los tipos numéricos y de fecha y hora.
Las implementaciones de IFormatProvider se usan en .NET para admitir el formato específico de una referencia
cultural. En el siguiente ejemplo se muestra cómo cambia la representación en forma de cadena de un objeto
cuando se le da formato con tres objetos IFormatProvider que representan referencias culturales diferentes.
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
decimal value = 1603.42m;
Console.WriteLine(value.ToString("C3", new CultureInfo("en-US")));
Console.WriteLine(value.ToString("C3", new CultureInfo("fr-FR")));
Console.WriteLine(value.ToString("C3", new CultureInfo("de-DE")));
}
}
// The example displays the following output:
// $1,603.420
// 1 603,420 €
// 1.603,420 €

Imports System.Globalization

Public Module Example


Public Sub Main()
Dim value As Decimal = 1603.42d
Console.WriteLine(value.ToString("C3", New CultureInfo("en-US")))
Console.WriteLine(value.ToString("C3", New CultureInfo("fr-FR")))
Console.WriteLine(value.ToString("C3", New CultureInfo("de-DE")))
End Sub
End Module
' The example displays the following output:
' $1,603.420
' 1 603,420 €
' 1.603,420 €

La interfaz IFormatProvider incluye un método, GetFormat(Type), que tiene un único parámetro que especifica el
tipo de objeto que proporciona información de formato. Si el método puede proporcionar un objeto de ese tipo,
lo devuelve. De lo contrario, devuelve una referencia nula ( Nothing en Visual Basic).
IFormatProvider.GetFormat es un método de devolución de llamada. Al llamar a una sobrecarga del método
ToString que incluye un parámetro de IFormatProvider , se llama al método GetFormat de ese objeto
IFormatProvider . El método GetFormat devuelve un objeto que proporciona al método formatType la
información de formato necesaria especificada por su parámetro ToString .
Varios métodos de formato o de conversión de cadenas incluyen un parámetro de tipo IFormatProviderpero, en
muchos casos, se omite el valor del parámetro cuando se llama al método. En la tabla siguiente se muestran
algunos métodos de formato que usan el parámetro y el tipo del objeto Type que pasan al método
IFormatProvider.GetFormat .

M ÉTO DO T IP O DE PA RÁ M ET RO FORMATTYPE

Método ToString de tipos numéricos System.Globalization.NumberFormatInfo

Método ToString de tipos de fecha y hora System.Globalization.DateTimeFormatInfo

String.Format System.ICustomFormatter

StringBuilder.AppendFormat System.ICustomFormatter
NOTE
Los métodos ToString de los tipos numéricos y los tipos de fecha y hora se sobrecargan y solo algunas de las
sobrecargas incluyen un parámetro IFormatProvider . Si un método no tiene un parámetro de tipo IFormatProvider, se
pasa en su lugar el objeto devuelto por la propiedad CultureInfo.CurrentCulture . Por ejemplo, una llamada al método
Int32.ToString() predeterminado resultará finalmente en una llamada similar a esta:
Int32.ToString("G", System.Globalization.CultureInfo.CurrentCulture) .

.NET proporciona tres clases que implementan IFormatProvider:


DateTimeFormatInfo, una clase que proporciona información de formato para los valores de fecha y hora
para una referencia cultural concreta. Su implementación de IFormatProvider.GetFormat devuelve una
instancia de sí misma.
NumberFormatInfo, una clase que proporciona información de formato numérico para una referencia
cultural concreta. Su implementación de IFormatProvider.GetFormat devuelve una instancia de sí misma.
CultureInfo. Su implementación de IFormatProvider.GetFormat puede devolver un objeto
NumberFormatInfo para proporcionar información de formato numérico o un objeto
DateTimeFormatInfo para proporcionar información de formato para los valores de fecha y hora.
También se puede implementar un proveedor de formato propio para reemplazar cualquiera de estas clases. Sin
embargo, el método GetFormat de la implementación debe devolver un objeto del tipo mostrado en la tabla
anterior si debe proporcionar información de formato al método ToString .
Formato que tiene en cuenta las referencias culturales de valores numéricos
De forma predeterminada, el formato de los valores numéricos depende de la referencia cultural. Si no
especifica una referencia cultural cuando llama a un método de formato, se utilizan las convenciones de formato
de la referencia cultural del subproceso actual. Esto se muestra en el ejemplo siguiente, que cambia la referencia
cultural del subproceso actual cuatro veces y después llama al método Decimal.ToString(String) . En cada caso, la
cadena resultante refleja las convenciones de formato de la referencia cultural actual. Esto se debe a que los
métodos ToString y ToString(String) incluyen llamadas a cada tipo numérico del método
ToString(String, IFormatProvider) .
using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
string[] cultureNames = { "en-US", "fr-FR", "es-MX", "de-DE" };
Decimal value = 1043.17m;

foreach (var cultureName in cultureNames) {


// Change the current thread culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
Console.WriteLine("The current culture is {0}",
Thread.CurrentThread.CurrentCulture.Name);
Console.WriteLine(value.ToString("C2"));
Console.WriteLine();
}
}
}
// The example displays the following output:
// The current culture is en-US
// $1,043.17
//
// The current culture is fr-FR
// 1 043,17 €
//
// The current culture is es-MX
// $1,043.17
//
// The current culture is de-DE
// 1.043,17 €

Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim cultureNames() As String = {"en-US", "fr-FR", "es-MX", "de-DE"}
Dim value As Decimal = 1043.17d

For Each cultureName In cultureNames


' Change the current thread culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName)
Console.WriteLine("The current culture is {0}",
Thread.CurrentThread.CurrentCulture.Name)
Console.WriteLine(value.ToString("C2"))
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' The current culture is en-US
' $1,043.17
'
' The current culture is fr-FR
' 1 043,17 €
'
' The current culture is es-MX
' $1,043.17
'
' The current culture is de-DE
' 1.043,17 €
También se puede dar formato a un valor numérico para una referencia cultural concreta llamando a una
sobrecarga de ToString que tenga un parámetro provider y pasándole uno de los elementos siguientes:
Un objeto CultureInfo que representa la referencia cultural cuyas convenciones de formato se van a usar.
El método CultureInfo.GetFormat devuelve el valor de la propiedad CultureInfo.NumberFormat , que es el
objeto NumberFormatInfo que proporciona información sobre el formato de la referencia cultural para
los valores numéricos.
Un objeto NumberFormatInfo que define las convenciones de formato de la referencia cultural que se
van a usar. Su método GetFormat devuelve una instancia de sí mismo.
En el siguiente ejemplo se utilizan objetos NumberFormatInfo que representan las referencias culturales de
inglés (Estados Unidos) e inglés (Reino Unido), y las referencias culturales neutras de francés y ruso para dar
formato a un número de punto flotante.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
Double value = 1043.62957;
string[] cultureNames = { "en-US", "en-GB", "ru", "fr" };

foreach (var name in cultureNames) {


NumberFormatInfo nfi = CultureInfo.CreateSpecificCulture(name).NumberFormat;
Console.WriteLine("{0,-6} {1}", name + ":", value.ToString("N3", nfi));
}
}
}
// The example displays the following output:
// en-US: 1,043.630
// en-GB: 1,043.630
// ru: 1 043,630
// fr: 1 043,630

Imports System.Globalization

Module Example
Public Sub Main()
Dim value As Double = 1043.62957
Dim cultureNames() As String = {"en-US", "en-GB", "ru", "fr"}

For Each name In cultureNames


Dim nfi As NumberFormatInfo = CultureInfo.CreateSpecificCulture(name).NumberFormat
Console.WriteLine("{0,-6} {1}", name + ":", value.ToString("N3", nfi))
Next
End Sub
End Module
' The example displays the following output:
' en-US: 1,043.630
' en-GB: 1,043.630
' ru: 1 043,630
' fr: 1 043,630

Formato que tiene en cuenta las referencias culturales de valores de fecha y hora
De forma predeterminada, el formato de los valores de fecha y hora tiene en cuenta las referencias culturales. Si
no especifica una referencia cultural cuando llama a un método de formato, se utilizan las convenciones de
formato de la referencia cultural del subproceso actual. Esto se muestra en el ejemplo siguiente, que cambia la
referencia cultural del subproceso actual cuatro veces y después llama al método DateTime.ToString(String) . En
cada caso, la cadena resultante refleja las convenciones de formato de la referencia cultural actual. Esto se debe
a que los métodos DateTime.ToString(), DateTime.ToString(String), DateTimeOffset.ToString()y
DateTimeOffset.ToString(String) encapsulan llamadas a los métodos DateTime.ToString(String, IFormatProvider)
y DateTimeOffset.ToString(String, IFormatProvider) .

using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
string[] cultureNames = { "en-US", "fr-FR", "es-MX", "de-DE" };
DateTime dateToFormat = new DateTime(2012, 5, 28, 11, 30, 0);

foreach (var cultureName in cultureNames) {


// Change the current thread culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
Console.WriteLine("The current culture is {0}",
Thread.CurrentThread.CurrentCulture.Name);
Console.WriteLine(dateToFormat.ToString("F"));
Console.WriteLine();
}
}
}
// The example displays the following output:
// The current culture is en-US
// Monday, May 28, 2012 11:30:00 AM
//
// The current culture is fr-FR
// lundi 28 mai 2012 11:30:00
//
// The current culture is es-MX
// lunes, 28 de mayo de 2012 11:30:00 a.m.
//
// The current culture is de-DE
// Montag, 28. Mai 2012 11:30:00
Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim cultureNames() As String = {"en-US", "fr-FR", "es-MX", "de-DE"}
Dim dateToFormat As Date = #5/28/2012 11:30AM#

For Each cultureName In cultureNames


' Change the current thread culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName)
Console.WriteLine("The current culture is {0}",
Thread.CurrentThread.CurrentCulture.Name)
Console.WriteLine(dateToFormat.ToString("F"))
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' The current culture is en-US
' Monday, May 28, 2012 11:30:00 AM
'
' The current culture is fr-FR
' lundi 28 mai 2012 11:30:00
'
' The current culture is es-MX
' lunes, 28 de mayo de 2012 11:30:00 a.m.
'
' The current culture is de-DE
' Montag, 28. Mai 2012 11:30:00

También se puede dar formato a un valor de fecha y hora para una referencia cultural concreta llamando a una
sobrecarga de DateTime.ToString o DateTimeOffset.ToString que tenga un parámetro provider y pasándole uno
de los elementos siguientes:
Un objeto CultureInfo que representa la referencia cultural cuyas convenciones de formato se van a usar.
Su método CultureInfo.GetFormat devuelve el valor de la propiedad CultureInfo.DateTimeFormat , que es
el objeto DateTimeFormatInfo que proporciona información sobre el formato de una referencia cultural
específica para valores de fecha y hora.
Un objeto DateTimeFormatInfo que define las convenciones de formato de la referencia cultural que se
van a usar. Su método GetFormat devuelve una instancia de sí mismo.
En el siguiente ejemplo se utilizan objetos DateTimeFormatInfo que representan las referencias culturales de
inglés (Estados Unidos) e inglés (Reino Unido), y las referencias culturales neutras de francés y ruso para dar
formato a una fecha.
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
DateTime dat1 = new DateTime(2012, 5, 28, 11, 30, 0);
string[] cultureNames = { "en-US", "en-GB", "ru", "fr" };

foreach (var name in cultureNames) {


DateTimeFormatInfo dtfi = CultureInfo.CreateSpecificCulture(name).DateTimeFormat;
Console.WriteLine("{0}: {1}", name, dat1.ToString(dtfi));
}
}
}
// The example displays the following output:
// en-US: 5/28/2012 11:30:00 AM
// en-GB: 28/05/2012 11:30:00
// ru: 28.05.2012 11:30:00
// fr: 28/05/2012 11:30:00

Imports System.Globalization

Module Example
Public Sub Main()
Dim dat1 As Date = #5/28/2012 11:30AM#
Dim cultureNames() As String = {"en-US", "en-GB", "ru", "fr"}

For Each name In cultureNames


Dim dtfi As DateTimeFormatInfo = CultureInfo.CreateSpecificCulture(name).DateTimeFormat
Console.WriteLine("{0}: {1}", name, dat1.ToString(dtfi))
Next
End Sub
End Module
' The example displays the following output:
' en-US: 5/28/2012 11:30:00 AM
' en-GB: 28/05/2012 11:30:00
' ru: 28.05.2012 11:30:00
' fr: 28/05/2012 11:30:00

Interfaz IFormattable
Normalmente, los tipos que sobrecargan el método ToString con una cadena de formato y un parámetro
IFormatProvider también implementan la interfaz IFormattable . Esta interfaz tiene un solo miembro,
IFormattable.ToString(String, IFormatProvider), que incluye una cadena de formato y un proveedor de formato
como parámetros.
La implementación de la interfaz IFormattable para su clase definida por la aplicación ofrece dos ventajas:
Compatibilidad con la conversión de cadenas por la clase Convert . Las llamadas a los métodos
Convert.ToString(Object) y Convert.ToString(Object, IFormatProvider) llaman automáticamente a su
implementación de IFormattable .
Compatibilidad con formatos compuestos. Si se usa un elemento de formato que incluye una cadena de
formato para dar formato a su tipo personalizado, Common Language Runtime llama automáticamente a
su implementación de IFormattable y le pasa la cadena de formato. Para obtener más información sobre
los formatos compuestos con métodos como String.Format o Console.WriteLine, vea la sección Formatos
compuestos .
En el ejemplo siguiente se define una clase Temperature que implementa la interfaz IFormattable . Admite los
especificadores de formato "C" o "G" para mostrar la temperatura en grados centígrados, el especificador de
formato "F" para mostrar la temperatura en grados Fahrenheit y el especificador de formato "K" para mostrar la
temperatura en grados Kelvin.
using System;
using System.Globalization;

public class Temperature : IFormattable


{
private decimal m_Temp;

public Temperature(decimal temperature)


{
this.m_Temp = temperature;
}

public decimal Celsius


{
get { return this.m_Temp; }
}

public decimal Kelvin


{
get { return this.m_Temp + 273.15m; }
}

public decimal Fahrenheit


{
get { return Math.Round((decimal) this.m_Temp * 9 / 5 + 32, 2); }
}

public override string ToString()


{
return this.ToString("G", null);
}

public string ToString(string format)


{
return this.ToString(format, null);
}

public string ToString(string format, IFormatProvider provider)


{
// Handle null or empty arguments.
if (String.IsNullOrEmpty(format))
format = "G";
// Remove any white space and covert to uppercase.
format = format.Trim().ToUpperInvariant();

if (provider == null)
provider = NumberFormatInfo.CurrentInfo;

switch (format)
{
// Convert temperature to Fahrenheit and return string.
case "F":
return this.Fahrenheit.ToString("N2", provider) + "°F";
// Convert temperature to Kelvin and return string.
case "K":
return this.Kelvin.ToString("N2", provider) + "K";
// Return temperature in Celsius.
case "C":
case "G":
return this.Celsius.ToString("N2", provider) + "°C";
default:
throw new FormatException(String.Format("The '{0}' format string is not supported.", format));
}
}
}
Imports System.Globalization

Public Class Temperature : Implements IFormattable


Private m_Temp As Decimal

Public Sub New(temperature As Decimal)


Me.m_Temp = temperature
End Sub

Public ReadOnly Property Celsius() As Decimal


Get
Return Me.m_Temp
End Get
End Property

Public ReadOnly Property Kelvin() As Decimal


Get
Return Me.m_Temp + 273.15d
End Get
End Property

Public ReadOnly Property Fahrenheit() As Decimal


Get
Return Math.Round(CDec(Me.m_Temp * 9 / 5 + 32), 2)
End Get
End Property

Public Overrides Function ToString() As String


Return Me.ToString("G", Nothing)
End Function

Public Overloads Function ToString(format As String) As String


Return Me.ToString(format, Nothing)
End Function

Public Overloads Function ToString(format As String, provider As IFormatProvider) As String _


Implements IFormattable.ToString

' Handle null or empty arguments.


If String.IsNullOrEmpty(format) Then format = "G"
' Remove any white space and convert to uppercase.
format = format.Trim().ToUpperInvariant()

If provider Is Nothing Then provider = NumberFormatInfo.CurrentInfo

Select Case format


' Convert temperature to Fahrenheit and return string.
Case "F"
Return Me.Fahrenheit.ToString("N2", provider) & "°F"
' Convert temperature to Kelvin and return string.
Case "K"
Return Me.Kelvin.ToString("N2", provider) & "K"
' Return temperature in Celsius.
Case "C", "G"
Return Me.Celsius.ToString("N2", provider) & "°C"
Case Else
Throw New FormatException(String.Format("The '{0}' format string is not supported.",
format))
End Select
End Function
End Class

En el ejemplo siguiente se crea una instancia de un objeto Temperature . A continuación, llama al método
ToString y usa varias cadenas de formato compuesto para obtener representaciones de cadena diferentes de un
objeto Temperature . Cada una de estas llamadas al método, a su vez, llama a la implementación de
IFormattable de la clase Temperature .

public class Example


{
public static void Main()
{
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
Temperature temp = new Temperature(22m);
Console.WriteLine(Convert.ToString(temp, new CultureInfo("ja-JP")));
Console.WriteLine("Temperature: {0:K}", temp);
Console.WriteLine("Temperature: {0:F}", temp);
Console.WriteLine(String.Format(new CultureInfo("fr-FR"), "Temperature: {0:F}", temp));
}
}
// The example displays the following output:
// 22.00°C
// Temperature: 295.15K
// Temperature: 71.60°F
// Temperature: 71,60°F

Public Module Example


Public Sub Main()
Dim temp As New Temperature(22d)
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US")
Console.WriteLine(Convert.ToString(temp1, New CultureInfo("ja-JP")))
Console.WriteLine("Temperature: {0:K}", temp)
Console.WriteLine("Temperature: {0:F}", temp)
Console.WriteLine(String.Format(New CultureInfo("fr-FR"), "Temperature: {0:F}", temp))
End Sub
End Module
' The example displays the following output:
' 22.00°C
' Temperature: 295.15K
' Temperature: 71.60°F
' Temperature: 71,60°F

Formatos compuestos
Algunos métodos, como String.Format y StringBuilder.AppendFormat, admiten formatos compuestos. Una
cadena de formato compuesto es un tipo de plantilla que devuelve una sola cadena que incorpora la
representación de cadena de cero, uno o más objetos. Cada objeto se representa en la cadena de formato
compuesto mediante un elemento de formato indizado. El índice del elemento de formato corresponde a la
posición del objeto que representa en la lista de parámetros del método. Los índices son de base cero. Por
ejemplo, en la siguiente llamada al método String.Format , el primer elemento de formato, {0:D} se reemplaza
con la representación de cadena de thatDate ; el segundo elemento de formato, {1} , se reemplaza con la
representación de cadena de item1 y el tercer elemento de formato, {2:C2} , se reemplaza con la
representación de cadena de item1.Value .

result = String.Format("On {0:d}, the inventory of {1} was worth {2:C2}.",


thatDate, item1, item1.Value);
Console.WriteLine(result);
// The example displays output like the following if run on a system
// whose current culture is en-US:
// On 5/1/2009, the inventory of WidgetA was worth $107.44.
result = String.Format("On {0:d}, the inventory of {1} was worth {2:C2}.", _
thatDate, item1, item1.Value)
Console.WriteLine(result)
' The example displays output like the following if run on a system
' whose current culture is en-US:
' On 5/1/2009, the inventory of WidgetA was worth $107.44.

Además de reemplazar un elemento de formato con la representación de cadena de su objeto correspondiente,


los elementos de formato también permiten controlar lo siguiente:
La forma específica en que un objeto se representa como una cadena, si el objeto implementa la interfaz
IFormattable y admite cadenas de formato. Para ello, siga el índice del elemento de formato con un signo
: (dos puntos) seguido de una cadena de formato válida. En el ejemplo anterior, esto se hacía dando
formato a un valor de fecha con la cadena de formato "d" (patrón de fecha corta) (por ejemplo, {0:d} ) y
dando formato a un valor numérico con la cadena de formato "C2" (por ejemplo, {2:C2} ) para
representar el número como un valor de moneda con dos decimales fraccionarios.
El ancho del campo que contiene la representación de cadena del objeto y la alineación de la
representación de cadena en ese campo. Para ello, escriba una coma ( , ) seguida del ancho del campo.
La cadena se alinea a la derecha en el campo si el ancho del campo es un valor positivo, y se alinea a la
izquierda si el ancho del campo es un valor negativo. En el ejemplo siguiente los valores de fecha se
alinean a la izquierda en un campo de 20 caracteres y los valores decimales con un dígito fraccionario se
alinean a la derecha en un campo de 11 caracteres.

DateTime startDate = new DateTime(2015, 8, 28, 6, 0, 0);


decimal[] temps = { 73.452m, 68.98m, 72.6m, 69.24563m,
74.1m, 72.156m, 72.228m };
Console.WriteLine("{0,-20} {1,11}\n", "Date", "Temperature");
for (int ctr = 0; ctr < temps.Length; ctr++)
Console.WriteLine("{0,-20:g} {1,11:N1}", startDate.AddDays(ctr), temps[ctr]);

// The example displays the following output:


// Date Temperature
//
// 8/28/2015 6:00 AM 73.5
// 8/29/2015 6:00 AM 69.0
// 8/30/2015 6:00 AM 72.6
// 8/31/2015 6:00 AM 69.2
// 9/1/2015 6:00 AM 74.1
// 9/2/2015 6:00 AM 72.2
// 9/3/2015 6:00 AM 72.2
Dim startDate As New Date(2015, 8, 28, 6, 0, 0)
Dim temps() As Decimal = {73.452, 68.98, 72.6, 69.24563,
74.1, 72.156, 72.228}
Console.WriteLine("{0,-20} {1,11}", "Date", "Temperature")
Console.WriteLine()
For ctr As Integer = 0 To temps.Length - 1
Console.WriteLine("{0,-20:g} {1,11:N1}", startDate.AddDays(ctr), temps(ctr))
Next
' The example displays the following output:
' Date Temperature
'
' 8/28/2015 6:00 AM 73.5
' 8/29/2015 6:00 AM 69.0
' 8/30/2015 6:00 AM 72.6
' 8/31/2015 6:00 AM 69.2
' 9/1/2015 6:00 AM 74.1
' 9/2/2015 6:00 AM 72.2
' 9/3/2015 6:00 AM 72.2

Tenga en cuenta que, si están presentes tanto el componente de cadena de alineación como el
componente de cadena de formato, el primero precede al último (por ejemplo, {0,-20:g} ).
Para más información sobre los formatos compuestos, vea Formatos compuestos.

Formato personalizado con ICustomFormatter


Dos métodos de formato compuesto, String.Format(IFormatProvider, String, Object[]) y
StringBuilder.AppendFormat(IFormatProvider, String, Object[]), incluyen un parámetro de proveedor de formato
que admite formatos personalizados. Cuando se llama a alguno de estos métodos de formato, se pasa un objeto
Type que representa una interfaz ICustomFormatter al método GetFormat del proveedor de formato. A
continuación, el método GetFormat devuelve la implementación de ICustomFormatter que proporciona el
formato personalizado.
La interfaz ICustomFormatter tiene un único método, Format(String, Object, IFormatProvider), al que un método
de formato compuesto llama automáticamente una vez para cada elemento de formato de una cadena de
formato compuesto. El método Format(String, Object, IFormatProvider) tiene tres parámetros: una cadena de
formato, que representa el argumento formatString de un elemento de formato, un objeto para dar formato y
un objeto IFormatProvider que proporciona servicios de formato. Normalmente, la clase que implementa
ICustomFormatter también implementa IFormatProvider, de modo que este último parámetro es una referencia
a la propia clase de formato personalizado. El método devuelve una representación de cadena con formato
personalizado del objeto al que se va a dar formato. Si el método no puede dar formato al objeto, debe devolver
una referencia nula ( Nothing en Visual Basic).
En el ejemplo siguiente se proporciona una implementación de ICustomFormatter denominada
ByteByByteFormatter que muestra los valores enteros como una secuencia de valores hexadecimales de dos
dígitos seguidos de un espacio.
public class ByteByByteFormatter : IFormatProvider, ICustomFormatter
{
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
return this;
else
return null;
}

public string Format(string format, object arg,


IFormatProvider formatProvider)
{
if (! formatProvider.Equals(this)) return null;

// Handle only hexadecimal format string.


if (! format.StartsWith("X")) return null;

byte[] bytes;
string output = null;

// Handle only integral types.


if (arg is Byte)
bytes = BitConverter.GetBytes((Byte) arg);
else if (arg is Int16)
bytes = BitConverter.GetBytes((Int16) arg);
else if (arg is Int32)
bytes = BitConverter.GetBytes((Int32) arg);
else if (arg is Int64)
bytes = BitConverter.GetBytes((Int64) arg);
else if (arg is SByte)
bytes = BitConverter.GetBytes((SByte) arg);
else if (arg is UInt16)
bytes = BitConverter.GetBytes((UInt16) arg);
else if (arg is UInt32)
bytes = BitConverter.GetBytes((UInt32) arg);
else if (arg is UInt64)
bytes = BitConverter.GetBytes((UInt64) arg);
else
return null;

for (int ctr = bytes.Length - 1; ctr >= 0; ctr--)


output += String.Format("{0:X2} ", bytes[ctr]);

return output.Trim();
}
}
Public Class ByteByByteFormatter : Implements IFormatProvider, ICustomFormatter
Public Function GetFormat(formatType As Type) As Object _
Implements IFormatProvider.GetFormat
If formatType Is GetType(ICustomFormatter) Then
Return Me
Else
Return Nothing
End If
End Function

Public Function Format(fmt As String, arg As Object,


formatProvider As IFormatProvider) As String _
Implements ICustomFormatter.Format

If Not formatProvider.Equals(Me) Then Return Nothing

' Handle only hexadecimal format string.


If Not fmt.StartsWith("X") Then
Return Nothing
End If

' Handle only integral types.


If Not typeof arg Is Byte AndAlso
Not typeof arg Is Int16 AndAlso
Not typeof arg Is Int32 AndAlso
Not typeof arg Is Int64 AndAlso
Not typeof arg Is SByte AndAlso
Not typeof arg Is UInt16 AndAlso
Not typeof arg Is UInt32 AndAlso
Not typeof arg Is UInt64 Then _
Return Nothing

Dim bytes() As Byte = BitConverter.GetBytes(arg)


Dim output As String = Nothing

For ctr As Integer = bytes.Length - 1 To 0 Step -1


output += String.Format("{0:X2} ", bytes(ctr))
Next

Return output.Trim()
End Function
End Class

En el ejemplo siguiente se usa la clase ByteByByteFormatter para dar formato a valores enteros. Observe que en
el ejemplo no se llama explícitamente al método ICustomFormatter.Format más de una vez en la segunda
llamada al método String.Format(IFormatProvider, String, Object[]) y que el proveedor NumberFormatInfo
predeterminado se usa en la tercera llamada al método porque el método ByteByByteFormatter.Format no
reconoce la cadena de formato "N0" y devuelve una referencia nula ( Nothing en Visual Basic).
public class Example
{
public static void Main()
{
long value = 3210662321;
byte value1 = 214;
byte value2 = 19;

Console.WriteLine(String.Format(new ByteByByteFormatter(), "{0:X}", value));


Console.WriteLine(String.Format(new ByteByByteFormatter(), "{0:X} And {1:X} = {2:X} ({2:000})",
value1, value2, value1 & value2));
Console.WriteLine(String.Format(new ByteByByteFormatter(), "{0,10:N0}", value));
}
}
// The example displays the following output:
// 00 00 00 00 BF 5E D1 B1
// 00 D6 And 00 13 = 00 12 (018)
// 3,210,662,321

Public Module Example


Public Sub Main()
Dim value As Long = 3210662321
Dim value1 As Byte = 214
Dim value2 As Byte = 19

Console.WriteLine((String.Format(New ByteByByteFormatter(), "{0:X}", value)))


Console.WriteLine((String.Format(New ByteByByteFormatter(), "{0:X} And {1:X} = {2:X} ({2:000})",
value1, value2, value1 And value2)))
Console.WriteLine(String.Format(New ByteByByteFormatter(), "{0,10:N0}", value))
End Sub
End Module
' The example displays the following output:
' 00 00 00 00 BF 5E D1 B1
' 00 D6 And 00 13 = 00 12 (018)
' 3,210,662,321

Temas relacionados
T IT L E DEF IN IC IÓ N

Standard Numeric Format Strings Describe cadenas de formato estándar que crean
representaciones de cadena usadas con frecuencia de valores
numéricos.

Custom Numeric Format Strings Describe cadenas de formato personalizado que crean
formatos específicos de la aplicación para valores numéricos.

Cadenas con formato de fecha y hora estándar Describe cadenas de formato estándar que crean
representaciones de cadena usadas con frecuencia de valores
DateTime .

Custom Date and Time Format Strings Describe cadenas de formato personalizado que crean
formatos específicos de la aplicación para valores DateTime .

Cadenas de formato TimeSpan estándar Describe cadenas de formato estándar que crean
representaciones de cadena usadas con frecuencia de
intervalos de tiempo.
T IT L E DEF IN IC IÓ N

Cadenas de formato TimeSpan personalizado Describe cadenas de formato personalizado que crean
formatos específicos de la aplicación para intervalos de
tiempo.

Enumeration Format Strings Describe cadenas de formato estándar que se usan para
crear representaciones de cadena de valores de
enumeración.

Formatos compuestos Describe cómo incrustar uno o más valores con formato en
una cadena. Posteriormente se puede mostrar la cadena en
la consola o escrita en una secuencia.

Analizar cadenas Describe cómo inicializar objetos en los valores descritos por
representaciones de cadena de dichos objetos. El análisis es
la operación inversa de la aplicación de formato.

Referencia
System.IFormattable
System.IFormatProvider
System.ICustomFormatter
Cadenas con formato numérico estándar
16/09/2020 • 49 minutes to read • Edit Online

Las cadenas de formato numérico estándar se utilizan para dar formato a tipos numéricos comunes. La forma de
una cadena de formato numérico estándar es Axx , donde:
A es un carácter alfabético único denominado especificador de formato. Cualquier cadena de formato
numérico que contenga más de un carácter alfabético, incluido el espacio en blanco, se interpreta como
una cadena de formato numérico personalizado. Para obtener más información, consulte Cadenas con
formato numérico personalizado.
xx es un entero opcional denominado especificador de precisión. El especificador de precisión está
comprendido entre el 0 y el 99 y afecta al número de dígitos del resultado. Observe que el especificador
de precisión controla el número de dígitos en la representación de cadena de un número. No redondea el
número en sí. Para realizar una operación de redondeo, use el método Math.Ceiling, Math.Floor o
Math.Round.
Cuando el especificador de precisión controla el número de dígitos fraccionarios de la cadena de
resultado, esta refleja un número redondeado al resultado representable más cercano al resultado de
precisión infinita. En el caso de que haya dos resultados representables igualmente cercanos:
En .NET Framework y .NET Core (hasta la versión 2.0) , el runtime selecciona el resultado con el
dígito menos significativo más elevado (es decir, usando MidpointRounding.AwayFromZero).
En .NET Core 2.1 y versiones posteriores , el runtime selecciona el resultado con un dígito menos
significativo par (es decir, usando MidpointRounding.ToEven).

NOTE
El especificador de precisión determina el número de dígitos de la cadena de resultado. Para rellenar una cadena de
resultado con espacios iniciales o finales, use la característica formatos compuestos y defina un componente de
alineación en el elemento de formato.

Las cadenas con formato numérico estándar son compatibles con:


Algunas sobrecargas del método ToString de todos los tipos numéricos. Por ejemplo, se puede
proporcionar una cadena de formato numérico a los métodos Int32.ToString(String) y
Int32.ToString(String, IFormatProvider).
La característica de formato compuesto de .NET, que utilizan algunos métodos Write y WriteLine de las
clases Console y StreamWriter, el método String.Format y el método StringBuilder.AppendFormat. La
característica de formato compuesto permite incluir la representación de varios elementos de datos en
una sola cadena a fin de especificar el ancho de campo y alinear números en un campo. Para obtener más
información, consulte Formatos compuestos.
Cadenas interpoladas en C# y Visual Basic, que proporcionan una sintaxis simplificada cuando se
comparan con las cadenas de formato compuesto.
TIP
Puede descargar la Utilidad de formato , que es una aplicación de .NET Core Windows Forms que permite aplicar
cadenas de formato a valores numéricos o de fecha y hora, y que muestra la cadena de resultado. El código fuente está
disponible para C# y Visual Basic.

En la tabla siguiente se describen los especificadores de formato numérico estándar y se muestran los
resultados de ejemplo generados por cada especificador de formato. Consulte la sección Notas para obtener
información adicional sobre cómo usar las cadenas con formato numérico estándar y la sección Ejemplo para
ver una ilustración completa de su uso.

ESP EC IF IC A DO R DE
F O RM ATO N O M B RE DESC RIP C IÓ N E JEM P LO S

"C" o "c" Moneda Resultado: un valor de 123.456 ("C", en-US) ->


divisa. \$123.46

Compatible con: todos los 123.456 ("C", fr-FR) ->


tipos numéricos. 123,46 €

Especificador de precisión: 123.456 ("C", ja-JP) ->


número de dígitos ¥123
decimales.
-123.456 ("C3", en-US) ->
Especificador de precisión (\$123.456)
predeterminado: Definido
por -123.456 ("C3", fr-FR) -> -
NumberFormatInfo.Currenc 123,456 €
yDecimalDigits.
-123.456 ("C3", ja-JP) ->
Más información: -¥123.456
Especificador de formato de
divisa ("C").

"D" o "d" Decimal Resultado: dígitos enteros 1234 ("D") -> 1234
con signo negativo
opcional. -1234 ("D6") -> -001234

Compatible con: solo tipos


enteros.

Especificador de precisión:
número mínimo de dígitos.

Especificador de precisión
predeterminado: número
mínimo de dígitos
necesario.

Más información:
Especificador de formato
decimal ("D").
ESP EC IF IC A DO R DE
F O RM ATO N O M B RE DESC RIP C IÓ N E JEM P LO S

"E" o "e" Exponencial (científico) Resultado: notación 1052.0329112756 ("E", en-


exponencial. US) -> 1.052033E+003

Compatible con: todos los 1052.0329112756 ("e", fr-


tipos numéricos. FR) -> 1,052033e+003

Especificador de precisión: -1052.0329112756 ("e2",


número de dígitos en-US) -> -1.05e+003
decimales.
-1052.0329112756 ("E2",
Especificador de precisión fr-FR) -> -1,05E+003
predeterminado: 6.

Más información:
Especificador de formato
exponencial ("E").

"F" o "f" Punto fijo Resultado: dígitos integrales 1234.567 ("F", en-US) ->
y decimales con signo 1234.57
negativo opcional.
1234.567 ("F", de-DE) ->
Compatible con: todos los 1234,57
tipos numéricos.
1234 ("F1", en-US) ->
Especificador de precisión: 1234.0
número de dígitos
decimales. 1234 ("F1", de-DE) ->
1234,0
Especificador de precisión
predeterminado: Definido -1234.56 ("F4", en-US) -> -
por 1234.5600
NumberFormatInfo.Number
DecimalDigits. -1234.56 ("F4", de-DE) -> -
1234,5600
Más información:
Especificador de formato de
punto fijo ("F").

"G" o "g" General Resultado: notación de -123.456 ("G", en-US) -> -


punto fijo o científica, la que 123.456
sea más compacta.
-123.456 ("G", sv-SE) -> -
Compatible con: todos los 123,456
tipos numéricos.
123.4546 ("G4", en-US) ->
Especificador de precisión: 123.5
número de dígitos
significativos. 123.4546 ("G4", sv-SE) ->
123,5
Especificador de precisión
predeterminado: depende -1.234567890e-25 ("G",
del tipo numérico. en-US) -> -1.23456789E-
25
Más información:
Especificador de formato -1.234567890e-25 ("G", sv-
general ("G"). SE) -> -1,23456789E-25
ESP EC IF IC A DO R DE
F O RM ATO N O M B RE DESC RIP C IÓ N E JEM P LO S

"N" o "n" número Resultado: dígitos integrales 1234.567 ("N", en-US) ->
y decimales, separadores de 1,234.57
grupos y un separador
decimal con signo negativo 1234.567 ("N", ru-RU) -> 1
opcional. 234,57

Compatible con: todos los 1234 ("N1", en-US) ->


tipos numéricos. 1,234.0

Especificador de precisión: 1234 ("N1", ru-RU) -> 1


número deseado de 234,0
decimales.
-1234.56 ("N3", en-US) -> -
Especificador de precisión 1,234.560
predeterminado: Definido
por -1234.56 ("N3", ru-RU) -> -
NumberFormatInfo.Number 1 234,560
DecimalDigits.

Más información:
Especificador de formato
numérico ("N").

"P" o "p" Porcentaje Resultado: número 1 ("P", en-US) -> 100.00 %


multiplicado por 100 y
mostrado con un símbolo 1 ("P", fr-FR) -> 100,00 %
de porcentaje.
-0.39678 ("P1", en-US) -> -
Compatible con: todos los 39.7 %
tipos numéricos.
-0.39678 ("P1", fr-FR) -> -
Especificador de precisión: 39,7 %
número deseado de
decimales.

Especificador de precisión
predeterminado: Definido
por
NumberFormatInfo.Percent
DecimalDigits.

Más información:
Especificador de formato de
porcentaje ("P").
ESP EC IF IC A DO R DE
F O RM ATO N O M B RE DESC RIP C IÓ N E JEM P LO S

"R" o "r" Acción de ida y vuelta Resultado: cadena que 123456789.12345678 ("R")
puede aplicar acciones de -> 123456789.12345678
ida y vuelta (round-trip) a
un número idéntico. -1234567890.12345678
("R") -> -
Compatible con: Single, 1234567890.1234567
Double y BigInteger.

Nota: Se recomienda solo


para el tipo BigInteger. Para
los tipos Double, use "G17";
para los tipos Single, use
"G9".
Especificador de precisión:
ignorado.

Más información:
Especificador de formato de
operación de ida y vuelta
("R").

"X" o "x" Hexadecimal Resultado: cadena 255 ("X") -> FF


hexadecimal.
-1 ("x") -> ff
Compatible con: solo tipos
enteros. 255 ("x4") -> 00ff

Especificador de precisión: -1 ("X4") -> 00FF


número de dígitos en la
cadena de resultado.

Más información:
Especificador de formato
hexadecimal ("X").

Cualquier otro carácter Especificador desconocido Resultado: produce


único FormatException en tiempo
de ejecución.

Usar cadenas de formato numérico estándar


NOTE
Los ejemplos de C# de este artículo se ejecutan en el ejecutor de código en línea y área de juegos de Try.NET. Haga clic en
el botón Ejecutar para ejecutar un ejemplo en una ventana interactiva. Una vez que se ejecuta el código, puede modificar
y ejecutar el código modificado si vuelve a hacer clic en Ejecutar . El código modificado se ejecuta en la ventana interactiva
o, si se produce un error en la compilación, en la ventana interactiva se muestran todos los mensajes de error del
compilador de C#.

Una cadena de formato numérico estándar se puede usar para definir el formato de un valor numérico de una
de dos maneras:
Se puede pasar a una sobrecarga del método ToString que tiene un parámetro format . En el ejemplo
siguiente se da formato a un valor numérico como una cadena de divisa en la referencia cultural actual
(en este caso, en-US).
Decimal value = static_cast<Decimal>(123.456);
Console::WriteLine(value.ToString("C2"));
// Displays $123.46

decimal value = 123.456m;


Console.WriteLine(value.ToString("C2"));
// Displays $123.46

Dim value As Decimal = 123.456d


Console.WriteLine(value.ToString("C2"))
' Displays $123.46

Se puede proporcionar como el argumento formatString de un elemento de formato usado con


métodos como String.Format, Console.WriteLine y StringBuilder.AppendFormat. Para obtener más
información, consulte Formatos compuestos. En el ejemplo siguiente se usa un elemento de formato para
insertar un valor de divisa en una cadena.

Decimal value = static_cast<Decimal>(123.456);


Console::WriteLine("Your account balance is {0:C2}.", value);
// Displays "Your account balance is $123.46."

decimal value = 123.456m;


Console.WriteLine("Your account balance is {0:C2}.", value);
// Displays "Your account balance is $123.46."

Dim value As Decimal = 123.456d


Console.WriteLine("Your account balance is {0:C2}.", value)
' Displays "Your account balance is $123.46."

Opcionalmente, puede facilitar un argumento alignment para especificar el ancho del campo numérico y
si su valor está alineado a izquierda o derecha. En el ejemplo siguiente se alinea a la izquierda un valor de
moneda en un campo de 28 caracteres y se alinea a la derecha un valor de moneda en un campo de 14
caracteres.

array<Decimal>^ amounts = { static_cast<Decimal>(16305.32),


static_cast<Decimal>(18794.16) };
Console::WriteLine(" Beginning Balance Ending Balance");
Console::WriteLine(" {0,-28:C2}{1,14:C2}", amounts[0], amounts[1]);
// Displays:
// Beginning Balance Ending Balance
// $16,305.32 $18,794.16

decimal[] amounts = { 16305.32m, 18794.16m };


Console.WriteLine(" Beginning Balance Ending Balance");
Console.WriteLine(" {0,-28:C2}{1,14:C2}", amounts[0], amounts[1]);
// Displays:
// Beginning Balance Ending Balance
// $16,305.32 $18,794.16
Dim amounts() As Decimal = {16305.32d, 18794.16d}
Console.WriteLine(" Beginning Balance Ending Balance")
Console.WriteLine(" {0,-28:C2}{1,14:C2}", amounts(0), amounts(1))
' Displays:
' Beginning Balance Ending Balance
' $16,305.32 $18,794.16

Se puede proporcionar como el argumento formatString en un elemento de la expresión interpolada de


una cadena interpolada. Para más información, vea el tema $ (Referencia de C#) en la referencia de C# o
el tema Interpolated Strings (Visual Basic Reference) [Cadenas interpoladas (Referencia de Visual Basic)]
en la referencia de Visual Basic.
En las secciones siguientes se proporciona información detallada sobre cada una de las cadenas de formato
numérico estándar.

Especificador de formato de divisa ("C")


El especificador de formato "C" (divisa) convierte un número en una cadena que representa una cantidad de
divisa. El especificador de precisión indica el número deseado de posiciones decimales en la cadena de
resultado. Si se omite el especificador de precisión, la precisión predeterminada está definida por la propiedad
NumberFormatInfo.CurrencyDecimalDigits.
Si el valor al que se va a dar formato tiene más posiciones decimales que el número especificado o
predeterminado, el valor fraccionario se redondea en la cadena de resultado. Si el valor situado a la derecha del
número de posiciones decimales especificadas es 5 o superior, el último dígito de la cadena de resultado se
redondea desde cero.
La información de formato del objeto NumberFormatInfo actual afecta a la cadena de resultado. En la tabla
siguiente se enumeran las propiedades de NumberFormatInfo que controlan el formato de la cadena devuelta.

P RO P IEDA D DE N UM B ERF O RM AT IN F O DESC RIP C IÓ N

CurrencyPositivePattern Define la posición del símbolo de divisa para los valores


positivos.

CurrencyNegativePattern Define la posición del símbolo de divisa para los valores


negativos y especifica si el signo negativo está representado
por paréntesis o por la propiedad NegativeSign.

NegativeSign Define el signo negativo usado si CurrencyNegativePattern


indica que no se emplean paréntesis.

CurrencySymbol Define el símbolo de divisa.

CurrencyDecimalDigits Define el número predeterminado de dígitos decimales en un


valor de divisa. Este valor puede reemplazarse por el uso del
especificador de precisión.

CurrencyDecimalSeparator Define la cadena que separa los dígitos integrales y


decimales.

CurrencyGroupSeparator Define la cadena que separa los grupos de números


integrales.
P RO P IEDA D DE N UM B ERF O RM AT IN F O DESC RIP C IÓ N

CurrencyGroupSizes Define el número de dígitos enteros que aparecen en un


grupo.

En el ejemplo siguiente se da formato a un valor Double con el especificador de formato de divisa:

double value = 12345.6789;


Console::WriteLine(value.ToString("C", CultureInfo::CurrentCulture));

Console::WriteLine(value.ToString("C3", CultureInfo::CurrentCulture));

Console::WriteLine(value.ToString("C3",
CultureInfo::CreateSpecificCulture("da-DK")));
// The example displays the following output on a system whose
// current culture is English (United States):
// $12,345.68
// $12,345.679
// kr 12.345,679

double value = 12345.6789;


Console.WriteLine(value.ToString("C", CultureInfo.CurrentCulture));

Console.WriteLine(value.ToString("C3", CultureInfo.CurrentCulture));

Console.WriteLine(value.ToString("C3",
CultureInfo.CreateSpecificCulture("da-DK")));
// The example displays the following output on a system whose
// current culture is English (United States):
// $12,345.68
// $12,345.679
// 12.345,679 kr

Dim value As Double = 12345.6789


Console.WriteLine(value.ToString("C", CultureInfo.CurrentCulture))

Console.WriteLine(value.ToString("C3", CultureInfo.CurrentCulture))

Console.WriteLine(value.ToString("C3", _
CultureInfo.CreateSpecificCulture("da-DK")))
' The example displays the following output on a system whose
' current culture is English (United States):
' $12,345.68
' $12,345.679
' kr 12.345,679

Volver a la tabla

Especificador de formato decimal ("D")


El especificador de formato "D" (o decimal) convierte un número en una cadena de dígitos decimales (0-9),
precedida por un signo menos si el número es negativo. Este formato sólo es compatible con los tipos enteros.
El especificador de precisión indica el número mínimo de dígitos deseado en la cadena resultante. Si es preciso,
el número se rellena con ceros a la izquierda para generar el número de dígitos que aporta el especificador de
precisión. Si no se indica ningún especificador de precisión, el valor predeterminado es el valor mínimo
necesario para representar el entero sin ceros iniciales.
La información de formato del objeto NumberFormatInfo actual afecta a la cadena de resultado. Como se
muestra en la tabla siguiente, una única propiedad afecta al formato de la cadena de resultado.

P RO P IEDA D DE N UM B ERF O RM AT IN F O DESC RIP C IÓ N

NegativeSign Define la cadena que indica que un número es negativo.

En el ejemplo siguiente se da formato a un valor Int32 con el especificador de formato decimal.

int value;

value = 12345;
Console::WriteLine(value.ToString("D"));
// Displays 12345
Console::WriteLine(value.ToString("D8"));
// Displays 00012345

value = -12345;
Console::WriteLine(value.ToString("D"));
// Displays -12345
Console::WriteLine(value.ToString("D8"));
// Displays -00012345

int value;

value = 12345;
Console.WriteLine(value.ToString("D"));
// Displays 12345
Console.WriteLine(value.ToString("D8"));
// Displays 00012345

value = -12345;
Console.WriteLine(value.ToString("D"));
// Displays -12345
Console.WriteLine(value.ToString("D8"));
// Displays -00012345

Dim value As Integer

value = 12345
Console.WriteLine(value.ToString("D"))
' Displays 12345
Console.WriteLine(value.ToString("D8"))
' Displays 00012345

value = -12345
Console.WriteLine(value.ToString("D"))
' Displays -12345
Console.WriteLine(value.ToString("D8"))
' Displays -00012345

Volver a la tabla

Especificador de formato exponencial ("E")


El especificador de formato exponencial ("E") convierte un número en una cadena con el formato "-d.ddd…
E+ddd" o "-d.ddd…e+ddd", donde cada "d" indica un dígito (0-9). La cadena comienza con un signo menos si el
número es negativo. El separador decimal siempre va precedido por exactamente un dígito.
El especificador de precisión indica el número deseado de dígitos después del separador decimal. Si se omite el
especificador de precisión, se emplea uno predeterminado que tiene seis dígitos después del separador decimal.
El modelo de mayúsculas o minúsculas del especificador de formato indica si se debe prefijar el exponente con
una "E" o con una "e". El exponente siempre consta de un signo más o menos y de un mínimo de tres dígitos. El
exponente se rellena con ceros para adaptarlo a este mínimo, si es necesario.
La información de formato del objeto NumberFormatInfo actual afecta a la cadena de resultado. En la tabla
siguiente se enumeran las propiedades de NumberFormatInfo que controlan el formato de la cadena devuelta.

P RO P IEDA D DE N UM B ERF O RM AT IN F O DESC RIP C IÓ N

NegativeSign Define la cadena que indica que un número es negativo


tanto para el coeficiente como para el exponente.

NumberDecimalSeparator Define la cadena que separa el dígito integral de los dígitos


decimales en el coeficiente.

PositiveSign Define la cadena que indica que un exponente es positivo.

En el ejemplo siguiente se da formato a un valor Double con el especificador de formato exponencial:

double value = 12345.6789;


Console::WriteLine(value.ToString("E", CultureInfo::InvariantCulture));
// Displays 1.234568E+004

Console::WriteLine(value.ToString("E10", CultureInfo::InvariantCulture));
// Displays 1.2345678900E+004

Console::WriteLine(value.ToString("e4", CultureInfo::InvariantCulture));
// Displays 1.2346e+004

Console::WriteLine(value.ToString("E",
CultureInfo::CreateSpecificCulture("fr-FR")));
// Displays 1,234568E+004

double value = 12345.6789;


Console.WriteLine(value.ToString("E", CultureInfo.InvariantCulture));
// Displays 1.234568E+004

Console.WriteLine(value.ToString("E10", CultureInfo.InvariantCulture));
// Displays 1.2345678900E+004

Console.WriteLine(value.ToString("e4", CultureInfo.InvariantCulture));
// Displays 1.2346e+004

Console.WriteLine(value.ToString("E",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays 1,234568E+004
Dim value As Double = 12345.6789
Console.WriteLine(value.ToString("E", CultureInfo.InvariantCulture))
' Displays 1.234568E+004

Console.WriteLine(value.ToString("E10", CultureInfo.InvariantCulture))
' Displays 1.2345678900E+004

Console.WriteLine(value.ToString("e4", CultureInfo.InvariantCulture))
' Displays 1.2346e+004

Console.WriteLine(value.ToString("E", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays 1,234568E+004

Volver a la tabla

Especificador de formato de punto fijo ("F")


El especificador de formato de punto fijo ("F") convierte un número en una cadena con el formato "-ddd.ddd…",
donde cada "d" indica un dígito (0-9). La cadena comienza con un signo menos si el número es negativo.
El especificador de precisión indica el número deseado de cifras decimales. Si se omite el especificador de
precisión, la propiedad NumberFormatInfo.NumberDecimalDigits actual proporciona la precisión numérica.
La información de formato del objeto NumberFormatInfo actual afecta a la cadena de resultado. En la tabla
siguiente se enumeran las propiedades del objeto NumberFormatInfo que controlan el formato de la cadena de
resultado.

P RO P IEDA D DE N UM B ERF O RM AT IN F O DESC RIP C IÓ N

NegativeSign Define la cadena que indica que un número es negativo.

NumberDecimalSeparator Define la cadena que separa los dígitos integrales de los


decimales.

NumberDecimalDigits Define el número predeterminado de dígitos decimales. Este


valor puede reemplazarse por el uso del especificador de
precisión.

En el ejemplo siguiente se da formato a un valor Double e Int32 con el especificador de formato de punto fijo:
int integerNumber;
integerNumber = 17843;
Console::WriteLine(integerNumber.ToString("F",
CultureInfo::InvariantCulture));
// Displays 17843.00

integerNumber = -29541;
Console::WriteLine(integerNumber.ToString("F3",
CultureInfo::InvariantCulture));
// Displays -29541.000

double doubleNumber;
doubleNumber = 18934.1879;
Console::WriteLine(doubleNumber.ToString("F", CultureInfo::InvariantCulture));
// Displays 18934.19

Console::WriteLine(doubleNumber.ToString("F0", CultureInfo::InvariantCulture));
// Displays 18934

doubleNumber = -1898300.1987;
Console::WriteLine(doubleNumber.ToString("F1", CultureInfo::InvariantCulture));
// Displays -1898300.2

Console::WriteLine(doubleNumber.ToString("F3",
CultureInfo::CreateSpecificCulture("es-ES")));
// Displays -1898300,199

int integerNumber;
integerNumber = 17843;
Console.WriteLine(integerNumber.ToString("F",
CultureInfo.InvariantCulture));
// Displays 17843.00

integerNumber = -29541;
Console.WriteLine(integerNumber.ToString("F3",
CultureInfo.InvariantCulture));
// Displays -29541.000

double doubleNumber;
doubleNumber = 18934.1879;
Console.WriteLine(doubleNumber.ToString("F", CultureInfo.InvariantCulture));
// Displays 18934.19

Console.WriteLine(doubleNumber.ToString("F0", CultureInfo.InvariantCulture));
// Displays 18934

doubleNumber = -1898300.1987;
Console.WriteLine(doubleNumber.ToString("F1", CultureInfo.InvariantCulture));
// Displays -1898300.2

Console.WriteLine(doubleNumber.ToString("F3",
CultureInfo.CreateSpecificCulture("es-ES")));
// Displays -1898300,199
Dim integerNumber As Integer
integerNumber = 17843
Console.WriteLine(integerNumber.ToString("F", CultureInfo.InvariantCulture))
' Displays 17843.00

integerNumber = -29541
Console.WriteLine(integerNumber.ToString("F3", CultureInfo.InvariantCulture))
' Displays -29541.000

Dim doubleNumber As Double


doubleNumber = 18934.1879
Console.WriteLine(doubleNumber.ToString("F", CultureInfo.InvariantCulture))
' Displays 18934.19

Console.WriteLine(doubleNumber.ToString("F0", CultureInfo.InvariantCulture))
' Displays 18934

doubleNumber = -1898300.1987
Console.WriteLine(doubleNumber.ToString("F1", CultureInfo.InvariantCulture))
' Displays -1898300.2

Console.WriteLine(doubleNumber.ToString("F3", _
CultureInfo.CreateSpecificCulture("es-ES")))
' Displays -1898300,199

Volver a la tabla

Especificador de formato general ("G")


El especificador de formato general ("G") convierte un número a la notación de punto fijo o científica más
compacta, dependiendo del tipo del número y de si hay un especificador de precisión o no. El especificador de
precisión define el número máximo de dígitos significativos que pueden aparecer en la cadena de resultado. Si el
especificador de precisión se omite o es cero, el tipo del número determina la precisión predeterminada, como
se indica en la tabla siguiente.

T IP O N UM ÉRIC O P REC ISIÓ N P REDET ERM IN A DA

Byte o SByte 3 dígitos

Int16 o UInt16 5 dígitos

Int32 o UInt32 10 dígitos

Int64 19 dígitos

UInt64 20 dígitos

BigInteger Sin límite (igual que "R")

Single 7 dígitos

Double 15 dígitos

Decimal 29 dígitos

La notación de punto fijo se utiliza si el exponente que resultaría de la expresión del número en notación
científica es mayor que -5 y menor que el especificador de precisión, de lo contrario se utiliza la notación
científica. El resultado contiene un separador decimal si es necesario y se omiten los ceros finales después de ese
separador. Si el especificador de precisión está presente y el número de dígitos significativos del resultado
supera la precisión especificada, los dígitos finales sobrantes se quitan mediante redondeo.
Sin embargo, si el número es Decimal y se omite el especificador de precisión, siempre se usa la notación de
punto fijo y se conservan los ceros finales.
Si se usa la notación científica, el exponente del resultado lleva el prefijo "E" si el especificador de formato es "G",
o "e" si el especificador de formato es "g". El exponente contiene un mínimo de dos dígitos. Esto difiere del
formato para la notación científica que genera el especificador de formato exponencial, que incluye un mínimo
de tres dígitos en el exponente.
Tenga en cuenta que, cuando se usa con un valor Double, el especificador de formato "G17" garantiza que el
valor Double original realice un recorrido de ida y vuelta correctamente. Esto se debe a que Double es un
número de punto flotante de doble precisión compatible con IEEE 754-2008 ( binary64 ) que ofrece un máximo
de 17 dígitos de precisión significativos. Se recomienda su uso en lugar del especificador de formato "R", ya que,
en algunos casos, "R" no logra realizar un recorrido de ida y vuelta correcto por los valores de números de
punto flotante de doble precisión. Esto se ilustra en el siguiente ejemplo.

double original = 0.84551240822557006;


var rSpecifier = original.ToString("R");
var g17Specifier = original.ToString("G17");

var rValue = Double.Parse(rSpecifier);


var g17Value = Double.Parse(g17Specifier);

Console.WriteLine($"{original:G17} = {rSpecifier} (R): {original.Equals(rValue)}");


Console.WriteLine($"{original:G17} = {g17Specifier} (G17): {original.Equals(g17Value)}");
// The example displays the following output:
// 0.84551240822557006 = 0.84551240822557: False
// 0.84551240822557006 = 0.84551240822557006: True

Module Example
Public Sub Main()
Dim original As Double = 0.84551240822557006
Dim rSpecifier = original.ToString("R")
Dim g17Specifier = original.ToString("G17")

Dim rValue = Double.Parse(rSpecifier)


Dim g17Value = Double.Parse(g17Specifier)

Console.WriteLine($"{original:G17} = {rSpecifier} (R): {original.Equals(rValue)}")


Console.WriteLine($"{original:G17} = {g17Specifier} (G17): {original.Equals(g17Value)}")
End Sub
End Module
' The example displays the following output:
' 0.84551240822557006 = 0.84551240822557 (R): False
' 0.84551240822557006 = 0.84551240822557006 (G17): True

Cuando se usa con un valor Single, el especificador de formato "G9" garantiza que el valor Single original realice
un recorrido de ida y vuelta correctamente. Esto se debe a que Single es un número de punto flotante de
precisión sencilla compatible con IEEE 754-2008 ( binary32 ) que ofrece hasta nueve dígitos de precisión
significativos. Por motivos de rendimiento, se recomienda su uso en lugar del especificador de formato "R".
La información de formato del objeto NumberFormatInfo actual afecta a la cadena de resultado. En la tabla
siguiente se enumeran las propiedades de NumberFormatInfo que controlan el formato de la cadena de
resultado.
P RO P IEDA D DE N UM B ERF O RM AT IN F O DESC RIP C IÓ N

NegativeSign Define la cadena que indica que un número es negativo.

NumberDecimalSeparator Define la cadena que separa los dígitos integrales de los


decimales.

PositiveSign Define la cadena que indica que un exponente es positivo.

En el ejemplo siguiente se da formato a valores de punto flotante ordenados con el especificador de formato
general:

double number;

number = 12345.6789;
Console::WriteLine(number.ToString("G", CultureInfo::InvariantCulture));
// Displays 12345.6789
Console::WriteLine(number.ToString("G",
CultureInfo::CreateSpecificCulture("fr-FR")));
// Displays 12345,6789

Console::WriteLine(number.ToString("G7", CultureInfo::InvariantCulture));
// Displays 12345.68

number = .0000023;
Console::WriteLine(number.ToString("G", CultureInfo::InvariantCulture));
// Displays 2.3E-06
Console::WriteLine(number.ToString("G",
CultureInfo::CreateSpecificCulture("fr-FR")));
// Displays 2,3E-06

number = .0023;
Console::WriteLine(number.ToString("G", CultureInfo::InvariantCulture));
// Displays 0.0023

number = 1234;
Console::WriteLine(number.ToString("G2", CultureInfo::InvariantCulture));
// Displays 1.2E+03

number = Math::PI;
Console::WriteLine(number.ToString("G5", CultureInfo::InvariantCulture));
// Displays 3.1416
double number;

number = 12345.6789;
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture));
// Displays 12345.6789
Console.WriteLine(number.ToString("G",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays 12345,6789

Console.WriteLine(number.ToString("G7", CultureInfo.InvariantCulture));
// Displays 12345.68

number = .0000023;
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture));
// Displays 2.3E-06
Console.WriteLine(number.ToString("G",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays 2,3E-06

number = .0023;
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture));
// Displays 0.0023

number = 1234;
Console.WriteLine(number.ToString("G2", CultureInfo.InvariantCulture));
// Displays 1.2E+03

number = Math.PI;
Console.WriteLine(number.ToString("G5", CultureInfo.InvariantCulture));
// Displays 3.1416

Dim number As Double

number = 12345.6789
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture))
' Displays 12345.6789
Console.WriteLine(number.ToString("G", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays 12345,6789

Console.WriteLine(number.ToString("G7", CultureInfo.InvariantCulture))
' Displays 12345.68

number = .0000023
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture))
' Displays 2.3E-06
Console.WriteLine(number.ToString("G", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays 2,3E-06

number = .0023
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture))
' Displays 0.0023

number = 1234
Console.WriteLine(number.ToString("G2", CultureInfo.InvariantCulture))
' Displays 1.2E+03

number = Math.Pi
Console.WriteLine(number.ToString("G5", CultureInfo.InvariantCulture))
' Displays 3.1416

Volver a la tabla
Especificador de formato numérico ("N")
El especificador de formato numérico ("N") convierte un número en una cadena con el formato "-
d,ddd,ddd.ddd…", donde "-" indica el símbolo de número negativo, si es necesario; "d", representa cada dígito (0-
9); "," es el separador de grupo; y "." es el símbolo de punto decimal. El especificador de precisión indica el
número deseado de dígitos después del separador decimal. Si se omite el especificador de precisión, el número
de posiciones decimales está definido por la propiedad NumberFormatInfo.NumberDecimalDigits actual.
La información de formato del objeto NumberFormatInfo actual afecta a la cadena de resultado. En la tabla
siguiente se enumeran las propiedades de NumberFormatInfo que controlan el formato de la cadena de
resultado.

P RO P IEDA D DE N UM B ERF O RM AT IN F O DESC RIP C IÓ N

NegativeSign Define la cadena que indica que un número es negativo.

NumberNegativePattern Define el formato de los valores negativos y especifica si el


signo negativo se representa mediante paréntesis o por la
propiedad NegativeSign.

NumberGroupSizes Define el número de dígitos enteros que aparecen entre los


separadores de grupos.

NumberGroupSeparator Define la cadena que separa los grupos de números


integrales.

NumberDecimalSeparator Define la cadena que separa los dígitos integrales y


decimales.

NumberDecimalDigits Define el número predeterminado de dígitos decimales. Este


valor puede reemplazarse por el uso de un especificador de
precisión.

En el ejemplo siguiente se da formato a valores de punto flotante ordenados con el especificador de formato
numérico:

double dblValue = -12445.6789;


Console::WriteLine(dblValue.ToString("N", CultureInfo::InvariantCulture));
// Displays -12,445.68
Console::WriteLine(dblValue.ToString("N1",
CultureInfo::CreateSpecificCulture("sv-SE")));
// Displays -12 445,7

int intValue = 123456789;


Console::WriteLine(intValue.ToString("N1", CultureInfo::InvariantCulture));
// Displays 123,456,789.0

double dblValue = -12445.6789;


Console.WriteLine(dblValue.ToString("N", CultureInfo.InvariantCulture));
// Displays -12,445.68
Console.WriteLine(dblValue.ToString("N1",
CultureInfo.CreateSpecificCulture("sv-SE")));
// Displays -12 445,7

int intValue = 123456789;


Console.WriteLine(intValue.ToString("N1", CultureInfo.InvariantCulture));
// Displays 123,456,789.0
Dim dblValue As Double = -12445.6789
Console.WriteLine(dblValue.ToString("N", CultureInfo.InvariantCulture))
' Displays -12,445.68
Console.WriteLine(dblValue.ToString("N1", _
CultureInfo.CreateSpecificCulture("sv-SE")))
' Displays -12 445,7

Dim intValue As Integer = 123456789


Console.WriteLine(intValue.ToString("N1", CultureInfo.InvariantCulture))
' Displays 123,456,789.0

Volver a la tabla

Especificador de formato de porcentaje ("P")


El especificador de formato de porcentaje ("P") multiplica un número por 100 y lo convierte en una cadena que
representa un porcentaje. El especificador de precisión indica el número deseado de cifras decimales. Si se omite
el especificador de precisión, se usará la precisión numérica predeterminada proporcionada por la propiedad
PercentDecimalDigits actual.
En la tabla siguiente se enumeran las propiedades de NumberFormatInfo que controlan el formato de la cadena
devuelta.

P RO P IEDA D DE N UM B ERF O RM AT IN F O DESC RIP C IÓ N

PercentPositivePattern Define la posición del símbolo de porcentaje para los valores


positivos.

PercentNegativePattern Define la posición del símbolo de porcentaje y del símbolo


negativo para los valores negativos.

NegativeSign Define la cadena que indica que un número es negativo.

PercentSymbol Define el símbolo de porcentaje.

PercentDecimalDigits Define el número predeterminado de dígitos decimales en un


valor de porcentaje. Este valor puede reemplazarse por el
uso del especificador de precisión.

PercentDecimalSeparator Define la cadena que separa los dígitos integrales y


decimales.

PercentGroupSeparator Define la cadena que separa los grupos de números


integrales.

PercentGroupSizes Define el número de dígitos enteros que aparecen en un


grupo.

En el ejemplo siguiente se da formato a valores de punto flotante con el especificador de formato de porcentaje:
double number = .2468013;
Console::WriteLine(number.ToString("P", CultureInfo::InvariantCulture));
// Displays 24.68 %
Console::WriteLine(number.ToString("P",
CultureInfo::CreateSpecificCulture("hr-HR")));
// Displays 24,68%
Console::WriteLine(number.ToString("P1", CultureInfo::InvariantCulture));
// Displays 24.7 %

double number = .2468013;


Console.WriteLine(number.ToString("P", CultureInfo.InvariantCulture));
// Displays 24.68 %
Console.WriteLine(number.ToString("P",
CultureInfo.CreateSpecificCulture("hr-HR")));
// Displays 24,68%
Console.WriteLine(number.ToString("P1", CultureInfo.InvariantCulture));
// Displays 24.7 %

Dim number As Double = .2468013


Console.WriteLine(number.ToString("P", CultureInfo.InvariantCulture))
' Displays 24.68 %
Console.WriteLine(number.ToString("P", _
CultureInfo.CreateSpecificCulture("hr-HR")))
' Displays 24,68%
Console.WriteLine(number.ToString("P1", CultureInfo.InvariantCulture))
' Displays 24.7 %

Volver a la tabla

Especificador de formato de operación de ida y vuelta ("R")


El especificador de formato de operación de ida y vuelta ("R") intenta garantizar que un valor numérico que se
convierte en una cadena vuelva a tomar el mismo valor numérico. Este formato solo es compatible para los
tipos Single, Double y BigInteger.
Para los valores Double, el especificador de formato "R", en algunos casos, no logra realizar un recorrido de ida y
vuelta correcto por el valor original. Para los dos valores Double y Single, también ofrece un rendimiento
relativamente bajo. En su lugar, se recomienda usar el especificador de formato "G17" para los valores Double y
el especificador de formato "G9" para realizar un recorrido de ida y vuelta correcto por los valores Single.
Cuando se da formato a un valor BigInteger mediante este especificador, su representación de cadena contiene
todos los dígitos significativos del valor BigInteger.
Aunque puede incluir un especificador de precisión, se omite. Los especificadores de ida y vuelta tienen
prioridad sobre la precisión al utilizar este especificador. La información de formato del objeto
NumberFormatInfo actual afecta a la cadena de resultado. En la tabla siguiente se enumeran las propiedades de
NumberFormatInfo que controlan el formato de la cadena de resultado.

P RO P IEDA D DE N UM B ERF O RM AT IN F O DESC RIP C IÓ N

NegativeSign Define la cadena que indica que un número es negativo.

NumberDecimalSeparator Define la cadena que separa los dígitos integrales de los


decimales.

PositiveSign Define la cadena que indica que un exponente es positivo.


En el ejemplo siguiente se da formato a un valor BigInteger con el especificador de formato de ida y vuelta.

#using <System.Numerics.dll>

using namespace System;


using namespace System::Numerics;

void main()
{
BigInteger value = BigInteger::Pow(Int64::MaxValue, 2);
Console::WriteLine(value.ToString("R"));
}
// The example displays the following output:
// 85070591730234615847396907784232501249

using System;
using System.Numerics;

public class Example


{
public static void Main()
{
var value = BigInteger.Pow(Int64.MaxValue, 2);
Console.WriteLine(value.ToString("R"));
}
}
// The example displays the following output:
// 85070591730234615847396907784232501249

Imports System.Numerics

Module Example
Public Sub Main()
Dim value = BigInteger.Pow(Int64.MaxValue, 2)
Console.WriteLine(value.ToString("R"))
End Sub
End Module
' The example displays the following output:
' 85070591730234615847396907784232501249

IMPORTANT
En algunos casos, los valores Double con el formato de cadena numérica estándar "R" no realizan correctamente la
operación de ida y vuelta si se compilan usando los modificadores /platform:x64 o /platform:anycpu y se ejecutan
en sistemas de 64 bits. Para más información, consulte el siguiente párrafo.

Para solucionar el problema de los valores Double con formato de cadena numérico estándar “R” que no hacen
correctamente el viaje de ida y vuelta si se compilan con conmutadores /platform:x64 o /platform:anycpu y se
ejecutan en sistemas de 64 bits, puede dar formato a los valores Double usando la cadena de formato numérico
estándar “G17”. En el ejemplo siguiente se usa la cadena de formato "R" con un valor Double que no realiza
correctamente la operación de ida y vuelta, y usa también la cadena de formato "G17" para realizar
correctamente la operación de ida y vuelta del valor original:
Console.WriteLine("Attempting to round-trip a Double with 'R':");
double initialValue = 0.6822871999174;
string valueString = initialValue.ToString("R",
CultureInfo.InvariantCulture);
double roundTripped = double.Parse(valueString,
CultureInfo.InvariantCulture);
Console.WriteLine("{0:R} = {1:R}: {2}\n",
initialValue, roundTripped, initialValue.Equals(roundTripped));

Console.WriteLine("Attempting to round-trip a Double with 'G17':");


string valueString17 = initialValue.ToString("G17",
CultureInfo.InvariantCulture);
double roundTripped17 = double.Parse(valueString17,
CultureInfo.InvariantCulture);
Console.WriteLine("{0:R} = {1:R}: {2}\n",
initialValue, roundTripped17, initialValue.Equals(roundTripped17));
// If compiled to an application that targets anycpu or x64 and run on an x64 system,
// the example displays the following output:
// Attempting to round-trip a Double with 'R':
// 0.6822871999174 = 0.68228719991740006: False
//
// Attempting to round-trip a Double with 'G17':
// 0.6822871999174 = 0.6822871999174: True

Imports System.Globalization

Module Example
Public Sub Main()
Console.WriteLine("Attempting to round-trip a Double with 'R':")
Dim initialValue As Double = 0.6822871999174
Dim valueString As String = initialValue.ToString("R",
CultureInfo.InvariantCulture)
Dim roundTripped As Double = Double.Parse(valueString,
CultureInfo.InvariantCulture)
Console.WriteLine("{0:R} = {1:R}: {2}",
initialValue, roundTripped, initialValue.Equals(roundTripped))
Console.WriteLine()

Console.WriteLine("Attempting to round-trip a Double with 'G17':")


Dim valueString17 As String = initialValue.ToString("G17",
CultureInfo.InvariantCulture)
Dim roundTripped17 As Double = double.Parse(valueString17,
CultureInfo.InvariantCulture)
Console.WriteLine("{0:R} = {1:R}: {2}",
initialValue, roundTripped17, initialValue.Equals(roundTripped17))
End Sub
End Module
' If compiled to an application that targets anycpu or x64 and run on an x64 system,
' the example displays the following output:
' Attempting to round-trip a Double with 'R':
' 0.6822871999174 = 0.68228719991740006: False
'
' Attempting to round-trip a Double with 'G17':
' 0.6822871999174 = 0.6822871999174: True

Volver a la tabla

Especificador de formato hexadecimal ("X")


El especificador de formato hexadecimal ("X") convierte un número en una cadena de dígitos hexadecimales. El
modelo de mayúsculas y minúsculas del especificador de formato indica si se van a usar caracteres en
mayúsculas o en minúsculas para los dígitos hexadecimales mayores de 9. Por ejemplo, use "X" para generar
"ABCDEF" y "x" para generar "abcdef". Este formato sólo es compatible con los tipos enteros.
El especificador de precisión indica el número mínimo de dígitos deseado en la cadena resultante. Si es preciso,
el número se rellena con ceros a la izquierda para generar el número de dígitos que aporta el especificador de
precisión.
La información de formato del objeto NumberFormatInfo actual no afecta a la cadena de resultado.
En el ejemplo siguiente se da formato a valores Int32 con el especificador de formato hexadecimal.

int value;

value = 0x2045e;
Console::WriteLine(value.ToString("x"));
// Displays 2045e
Console::WriteLine(value.ToString("X"));
// Displays 2045E
Console::WriteLine(value.ToString("X8"));
// Displays 0002045E

value = 123456789;
Console::WriteLine(value.ToString("X"));
// Displays 75BCD15
Console::WriteLine(value.ToString("X2"));
// Displays 75BCD15

int value;

value = 0x2045e;
Console.WriteLine(value.ToString("x"));
// Displays 2045e
Console.WriteLine(value.ToString("X"));
// Displays 2045E
Console.WriteLine(value.ToString("X8"));
// Displays 0002045E

value = 123456789;
Console.WriteLine(value.ToString("X"));
// Displays 75BCD15
Console.WriteLine(value.ToString("X2"));
// Displays 75BCD15

Dim value As Integer

value = &h2045e
Console.WriteLine(value.ToString("x"))
' Displays 2045e
Console.WriteLine(value.ToString("X"))
' Displays 2045E
Console.WriteLine(value.ToString("X8"))
' Displays 0002045E

value = 123456789
Console.WriteLine(value.ToString("X"))
' Displays 75BCD15
Console.WriteLine(value.ToString("X2"))
' Displays 75BCD15

Volver a la tabla

Notas
Configuración del Panel de control
Los valores de configuración del elemento Configuración regional y de idioma del Panel de control influyen
en la cadena de resultado generada por una operación de formato. Esas configuraciones se usan para inicializar
el objeto NumberFormatInfo asociado a la referencia cultural del subproceso actual, que proporciona valores
que se usan para controlar el formato. Los equipos que usan configuraciones diferentes generarán cadenas de
resultado distintas.
Asimismo, si se utiliza el constructor CultureInfo(String) para crear instancias de un nuevo objeto CultureInfo
que representa la misma referencia cultural que la referencia cultural del sistema actual, cualquier
personalización establecida por el elemento Configuración regional y de idioma del Panel de control se
aplicará al nuevo objeto CultureInfo. Puede usar el constructor CultureInfo(String, Boolean) para crear un objeto
CultureInfo que no refleje las personalizaciones de un sistema.
Propiedades NumberFormatInfo
El formato se ve influenciado por las propiedades del objeto NumberFormatInfo actual, proporcionado
implícitamente por la referencia cultural del subproceso actual o explícitamente por el parámetro
IFormatProvider del método que invoca el formato. Especifique un objeto NumberFormatInfo o CultureInfo para
dicho parámetro.

NOTE
Para obtener información sobre la personalización de patrones o cadenas que se usan para dar formato a valores
numéricos, vea el tema de la clase NumberFormatInfo.

Tipos numéricos enteros y de punto flotante


Algunas descripciones de especificadores de formato numérico estándar hacen referencia a tipos numéricos
enteros o de punto flotante. Los tipos numéricos integrales son Byte, SByte, Int16, Int32, Int64, UInt16, UInt32,
UInt64 y BigInteger. Los tipos numéricos de punto flotante son Decimal, Single y Double.
Infinitos de punto flotante y NaN
Independientemente de la cadena de formato, si el valor de un tipo de punto flotante Single o Double es infinito
positivo, infinito negativo o NaN (Not a Number, no es un número), la cadena con formato será el valor de la
propiedad PositiveInfinitySymbol, NegativeInfinitySymbol o NaNSymbol respectiva especificada por el objeto
NumberFormatInfo aplicable actualmente.

Ejemplo
NOTE
Algunos de los ejemplos de C# de este artículo se ejecutan en el ejecutor de código en línea y área de juegos de Try.NET.
Haga clic en el botón Ejecutar para ejecutar un ejemplo en una ventana interactiva. Una vez que se ejecuta el código,
puede modificar y ejecutar el código modificado si vuelve a hacer clic en Ejecutar . El código modificado se ejecuta en la
ventana interactiva o, si se produce un error en la compilación, en la ventana interactiva se muestran todos los mensajes
de error del compilador de C#.

En el ejemplo siguiente se da formato a un valor numérico integral y de punto flotante mediante la referencia
cultural en-US y todos los especificadores de formato numérico estándar. En este ejemplo se usan dos tipos
numéricos concretos (Double y Int32), pero se obtendrían resultados similares con cualquiera de los demás
tipos base numéricos (Byte, SByte, Int16, Int32, Int64, UInt16, UInt32, UInt64, BigInteger, Decimal y Single).
// Display string representations of numbers for en-us culture
CultureInfo ci = new CultureInfo("en-us");

// Output floating point values


double floating = 10761.937554;
Console.WriteLine("C: {0}",
floating.ToString("C", ci)); // Displays "C: $10,761.94"
Console.WriteLine("E: {0}",
floating.ToString("E03", ci)); // Displays "E: 1.076E+004"
Console.WriteLine("F: {0}",
floating.ToString("F04", ci)); // Displays "F: 10761.9376"
Console.WriteLine("G: {0}",
floating.ToString("G", ci)); // Displays "G: 10761.937554"
Console.WriteLine("N: {0}",
floating.ToString("N03", ci)); // Displays "N: 10,761.938"
Console.WriteLine("P: {0}",
(floating/10000).ToString("P02", ci)); // Displays "P: 107.62 %"
Console.WriteLine("R: {0}",
floating.ToString("R", ci)); // Displays "R: 10761.937554"
Console.WriteLine();

// Output integral values


int integral = 8395;
Console.WriteLine("C: {0}",
integral.ToString("C", ci)); // Displays "C: $8,395.00"
Console.WriteLine("D: {0}",
integral.ToString("D6", ci)); // Displays "D: 008395"
Console.WriteLine("E: {0}",
integral.ToString("E03", ci)); // Displays "E: 8.395E+003"
Console.WriteLine("F: {0}",
integral.ToString("F01", ci)); // Displays "F: 8395.0"
Console.WriteLine("G: {0}",
integral.ToString("G", ci)); // Displays "G: 8395"
Console.WriteLine("N: {0}",
integral.ToString("N01", ci)); // Displays "N: 8,395.0"
Console.WriteLine("P: {0}",
(integral/10000.0).ToString("P02", ci)); // Displays "P: 83.95 %"
Console.WriteLine("X: 0x{0}",
integral.ToString("X", ci)); // Displays "X: 0x20CB"
Console.WriteLine();
Option Strict On

Imports System.Globalization
Imports System.Threading

Module NumericFormats
Public Sub Main()
' Display string representations of numbers for en-us culture
Dim ci As New CultureInfo("en-us")

' Output floating point values


Dim floating As Double = 10761.937554
Console.WriteLine("C: {0}", _
floating.ToString("C", ci)) ' Displays "C: $10,761.94"
Console.WriteLine("E: {0}", _
floating.ToString("E03", ci)) ' Displays "E: 1.076E+004"
Console.WriteLine("F: {0}", _
floating.ToString("F04", ci)) ' Displays "F: 10761.9376"
Console.WriteLine("G: {0}", _
floating.ToString("G", ci)) ' Displays "G: 10761.937554"
Console.WriteLine("N: {0}", _
floating.ToString("N03", ci)) ' Displays "N: 10,761.938"
Console.WriteLine("P: {0}", _
(floating / 10000).ToString("P02", ci)) ' Displays "P: 107.62 %"
Console.WriteLine("R: {0}", _
floating.ToString("R", ci)) ' Displays "R: 10761.937554"
Console.WriteLine()

' Output integral values


Dim integral As Integer = 8395
Console.WriteLine("C: {0}", _
integral.ToString("C", ci)) ' Displays "C: $8,395.00"
Console.WriteLine("D: {0}", _
integral.ToString("D6")) ' Displays "D: 008395"
Console.WriteLine("E: {0}", _
integral.ToString("E03", ci)) ' Displays "E: 8.395E+003"
Console.WriteLine("F: {0}", _
integral.ToString("F01", ci)) ' Displays "F: 8395.0"
Console.WriteLine("G: {0}", _
integral.ToString("G", ci)) ' Displays "G: 8395"
Console.WriteLine("N: {0}", _
integral.ToString("N01", ci)) ' Displays "N: 8,395.0"
Console.WriteLine("P: {0}", _
(integral / 10000).ToString("P02", ci)) ' Displays "P: 83.95 %"
Console.WriteLine("X: 0x{0}", _
integral.ToString("X", ci)) ' Displays "X: 0x20CB"
Console.WriteLine()
End Sub
End Module

Vea también
NumberFormatInfo
Cadenas con formato numérico personalizado
Aplicación de formato a tipos
Cómo: Rellenar un número con ceros a la izquierda
Formatos compuestos
Ejemplo: Utilidad de formato WinForms de .NET Core (C#)
Ejemplo: Utilidad de formato WinForms de .NET Core (Visual Basic)
Cadenas con formato numérico personalizado
16/09/2020 • 38 minutes to read • Edit Online

Puede crear una cadena de formato numérico personalizado, formada por uno o varios especificadores
numéricos personalizados, para definir cómo debe darse formato a los datos numéricos. Una cadena de formato
numérico personalizado es cualquier cadena que no sea una cadena de formato numérico estándar.
Algunas sobrecargas del método ToString de todos los tipos numéricos admiten las cadenas de formato
numérico personalizado. Por ejemplo, se puede proporcionar una cadena de formato numérico a los métodos
ToString(String) y ToString(String, IFormatProvider) del tipo Int32 . La característica de formato compuesto de
.NET, que utilizan algunos métodos Write y WriteLine de las clases Console y StreamWriter, el método
String.Format y el método StringBuilder.AppendFormat, admite también cadenas de formato numérico
personalizado. La característica interpolación de cadenas admite también cadenas de formato numérico
personalizado.

TIP
Puede descargar la Utilidad de formato , que es una aplicación de .NET Core Windows Forms que permite aplicar
cadenas de formato a valores numéricos o de fecha y hora, y que muestra la cadena de resultado. El código fuente está
disponible para C# y Visual Basic.

En la tabla siguiente se describen los especificadores de formato numérico personalizado y se muestran las
salidas de ejemplo generadas por cada especificador de formato. Vea la sección Notas para obtener información
adicional sobre cómo usar las cadenas de formato numérico personalizado y la sección Ejemplo para ver una
ilustración completa de su uso.

ESP EC IF IC A DO R DE
F O RM ATO N O M B RE DESC RIP C IÓ N E JEM P LO S

"0" Marcador de posición cero Reemplaza el cero con el 1234.5678 ("00000") ->
dígito correspondiente si 01235
hay alguno presente; de lo
contrario, el cero aparece en 0.45678 ("0.00", en-US) ->
la cadena de resultado. 0.46

Más información: 0.45678 ("0.00", fr-FR) ->


Especificador personalizado 0,46
"0".
ESP EC IF IC A DO R DE
F O RM ATO N O M B RE DESC RIP C IÓ N E JEM P LO S

"#" Marcador de posición de Reemplaza el símbolo "#" 1234.5678 ("#####") ->


dígito. por el dígito 1235
correspondiente si hay
alguno presente; de lo 0.45678 ("#.##", en-US) ->
contrario, no aparece .46
ningún dígito en la cadena
de resultado. 0.45678 ("#.##", fr-FR) ->
,46
Tenga en cuenta que no se
mostrará ningún dígito en
la cadena de resultado si el
dígito que se encuentra en
la cadena de entrada es un
0 no significativo. Por
ejemplo, 0003 ("####") ->
3.

Más información:
Especificador personalizado
"#".

"." Separador decimal Determina la ubicación del 0.45678 ("0.00", en-US) ->
separador decimal en la 0.46
cadena de resultado.
0.45678 ("0.00", fr-FR) ->
Más información: El 0,46
especificador personalizado
".".

"," Separador de grupos y Actúa como separador de Especificador de separador


escala numérica grupos y como de grupos:
especificador de escala
numérica. Como separador 2147483647 ("##,#", en-
de grupos, inserta un US) -> 2,147,483,647
carácter separador de
grupos adaptado entre 2147483647 ("##,#", es-ES)
cada grupo. Como -> 2.147.483.647
especificador de escala
numérica, divide un número Especificador de escala:
por 1000 por cada coma
especificada. 2147483647 ("#,#,,", en-US)
-> 2,147
Más información:
Especificador personalizado 2147483647 ("#,#,,", es-ES)
",". -> 2.147

"%" Marcador de posición de Multiplica un número por 0.3697 ("%#0.00", en-US) -


porcentaje. 100 e inserta un símbolo de > %36.97
porcentaje adaptado en la
cadena de resultado. 0.3697 ("%#0.00", el-GR) ->
%36,97
Más información:
Especificador personalizado 0.3697 ("##.0 %", en-US) ->
"%". 37.0 %

0.3697 ("##.0 %", el-GR) ->


37,0 %
ESP EC IF IC A DO R DE
F O RM ATO N O M B RE DESC RIP C IÓ N E JEM P LO S

"‰" Marcador de posición de Multiplica un número por 0.03697 ("#0.00‰", en-US)


"por mil" 1000 e inserta un símbolo -> 36.97‰
de "por mil" adaptado en la
cadena de resultado. 0.03697 ("#0.00‰", ru-RU)
-> 36,97‰
Más información:
Especificador personalizado
"‰".

"E0" Notación exponencial Si va seguido al menos de 987654 ("#0.0e0") ->


un 0 (cero), da formato al 98.8e4
"E+0" resultado usando notación
exponencial. El modelo de 1503.92311 ("0.0##e+00")
"E-0" mayúsculas de "E" o "e" -> 1.504e+03
indica el modelo de
"E0" mayúsculas del símbolo de 1.8901385E-16 ("0.0e+00")
exponente en la cadena de -> 1.9e-16
"E+0" resultado. El número de
ceros que siguen al carácter
"E-0" "E" o "e" determina el
número mínimo de dígitos
en el exponente. Un signo
más (+) indica que un
carácter de signo precede
siempre al exponente. Un
signo menos (-) indica que
un carácter de signo solo
precede a los exponentes
negativos.

Más información:
Especificadores
personalizados "E" y "e".

"\" Carácter de escape Hace que el carácter 987654 ("\###00\#") ->


siguiente se interprete #987654#
como un literal en lugar de
como un especificador de
formato personalizado.

Más información: Carácter


de escape "\".

'cadena' Delimitador de cadena Indica que los caracteres 68 ("# ' grados'") -> 68
literal que encierra se deben grados
"string" copiar en la cadena de
resultado sin modificar. 68 ("# ' grados'") -> 68
grados
Más información: Literales
de carácter.
ESP EC IF IC A DO R DE
F O RM ATO N O M B RE DESC RIP C IÓ N E JEM P LO S

; Separador de secciones Define secciones con 12.345 ("#0.0#;(#0.0#);-\0-


cadenas de formato ") -> 12.35
diferentes para los números
positivos, negativos y cero. 0 ("#0.0#;(#0.0#);-\0-") -> -
0-
Más información: Separador
de sección ";". -12.345 ("#0.0#;(#0.0#);-\0-
") -> (12.35)

12.345 ("#0.0#;(#0.0#)") ->


12.35

0 ("#0.0#;(#0.0#)") -> 0.0

-12.345 ("#0.0#;(#0.0#)") ->


(12.35)

Otros Todos los demás caracteres El carácter se copia en la 68 ("# °") -> 68 °
cadena de resultado sin
modificar.

Más información: Literales


de carácter.

En las secciones siguientes se proporciona información detallada sobre cada uno de los especificadores de
formato numérico personalizado.

NOTE
Algunos de los ejemplos de C# de este artículo se ejecutan en el ejecutor de código en línea y área de juegos de Try.NET.
Haga clic en el botón Ejecutar para ejecutar un ejemplo en una ventana interactiva. Una vez que se ejecuta el código,
puede modificar y ejecutar el código modificado si vuelve a hacer clic en Ejecutar . El código modificado se ejecuta en la
ventana interactiva o, si se produce un error en la compilación, en la ventana interactiva se muestran todos los mensajes
de error del compilador de C#.

Especificador personalizado "0"


El especificador de formato personalizado "0" actúa como un símbolo de marcador de posición cero. Si el valor
al que se está dando formato tiene un dígito en la posición donde aparece el cero en la cadena de formato, se
copia ese dígito a la cadena de resultado; de lo contrario, aparecerá un cero en la cadena de resultado. La
posición del cero que aparece más a la izquierda antes del separador decimal y la del cero que está más a la
derecha después del separador decimal determinan el intervalo de dígitos que están siempre presentes en la
cadena de resultado.
El especificador "00" hace que el valor se redondee al dígito más próximo que precede al decimal, donde
siempre se utiliza el redondeo para evitar el cero. Por ejemplo, al aplicar el formato a 34.5 con "00" el resultado
del valor es 35.
En el ejemplo siguiente se muestran varios valores a los que se les ha aplicado cadenas de formato
personalizado que incluyen marcadores de posición cero.
double value;

value = 123;
Console::WriteLine(value.ToString("00000"));
Console::WriteLine(String::Format("{0:00000}", value));
// Displays 00123

value = 1.2;
Console::WriteLine(value.ToString("0.00", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.00}", value));
// Displays 1.20

Console::WriteLine(value.ToString("00.00", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:00.00}", value));
// Displays 01.20

CultureInfo^ daDK = CultureInfo::CreateSpecificCulture("da-DK");


Console::WriteLine(value.ToString("00.00", daDK));
Console::WriteLine(String::Format(daDK, "{0:00.00}", value));
// Displays 01,20

value = .56;
Console::WriteLine(value.ToString("0.0", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.0}", value));
// Displays 0.6

value = 1234567890;
Console::WriteLine(value.ToString("0,0", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0,0}", value));
// Displays 1,234,567,890

CultureInfo^ elGR = CultureInfo::CreateSpecificCulture("el-GR");


Console::WriteLine(value.ToString("0,0", elGR));
Console::WriteLine(String::Format(elGR, "{0:0,0}", value));
// Displays 1.234.567.890

value = 1234567890.123456;
Console::WriteLine(value.ToString("0,0.0", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0,0.0}", value));
// Displays 1,234,567,890.1

value = 1234.567890;
Console::WriteLine(value.ToString("0,0.00", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0,0.00}", value));
// Displays 1,234.57
double value;

value = 123;
Console.WriteLine(value.ToString("00000"));
Console.WriteLine(String.Format("{0:00000}", value));
// Displays 00123

value = 1.2;
Console.WriteLine(value.ToString("0.00", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.00}", value));
// Displays 1.20

Console.WriteLine(value.ToString("00.00", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:00.00}", value));
// Displays 01.20

CultureInfo daDK = CultureInfo.CreateSpecificCulture("da-DK");


Console.WriteLine(value.ToString("00.00", daDK));
Console.WriteLine(String.Format(daDK, "{0:00.00}", value));
// Displays 01,20

value = .56;
Console.WriteLine(value.ToString("0.0", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.0}", value));
// Displays 0.6

value = 1234567890;
Console.WriteLine(value.ToString("0,0", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0}", value));
// Displays 1,234,567,890

CultureInfo elGR = CultureInfo.CreateSpecificCulture("el-GR");


Console.WriteLine(value.ToString("0,0", elGR));
Console.WriteLine(String.Format(elGR, "{0:0,0}", value));
// Displays 1.234.567.890

value = 1234567890.123456;
Console.WriteLine(value.ToString("0,0.0", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0.0}", value));
// Displays 1,234,567,890.1

value = 1234.567890;
Console.WriteLine(value.ToString("0,0.00", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0.00}", value));
// Displays 1,234.57
Dim value As Double

value = 123
Console.WriteLine(value.ToString("00000"))
Console.WriteLine(String.Format("{0:00000}", value))
' Displays 00123

value = 1.2
Console.Writeline(value.ToString("0.00", CultureInfo.InvariantCulture))
Console.Writeline(String.Format(CultureInfo.InvariantCulture,
"{0:0.00}", value))
' Displays 1.20
Console.WriteLine(value.ToString("00.00", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:00.00}", value))
' Displays 01.20
Dim daDK As CultureInfo = CultureInfo.CreateSpecificCulture("da-DK")
Console.WriteLine(value.ToString("00.00", daDK))
Console.WriteLine(String.Format(daDK, "{0:00.00}", value))
' Displays 01,20

value = .56
Console.WriteLine(value.ToString("0.0", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.0}", value))
' Displays 0.6

value = 1234567890
Console.WriteLine(value.ToString("0,0", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0}", value))
' Displays 1,234,567,890
Dim elGR As CultureInfo = CultureInfo.CreateSpecificCulture("el-GR")
Console.WriteLine(value.ToString("0,0", elGR))
Console.WriteLine(String.Format(elGR, "{0:0,0}", value))
' Displays 1.234.567.890

value = 1234567890.123456
Console.WriteLine(value.ToString("0,0.0", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0.0}", value))
' Displays 1,234,567,890.1

value = 1234.567890
Console.WriteLine(value.ToString("0,0.00", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0.00}", value))
' Displays 1,234.57

Volver a la tabla

Especificador personalizado "#"


El especificador de formato personalizado "#" actúa como un símbolo de marcador de posición de dígitos. Si el
valor al que se está dando formato tiene un dígito en la posición donde aparece el símbolo "#" en la cadena de
formato, ese dígito se copia a la cadena de resultado. En caso contrario, no se almacena nada en esa posición de
la cadena de resultado.
Tenga en cuenta que este especificador nunca muestra un cero que no sea un dígito significativo, incluso aunque
el cero sea el único dígito de la cadena. Solo mostrará cero si es un dígito significativo del número que se está
mostrando.
La cadena de formato "##" hace que el valor se redondee al dígito más próximo que precede al decimal, donde
siempre se utiliza el redondeo para evitar el cero. Por ejemplo, al aplicar el formato a 34.5 con "##" el resultado
del valor es 35.
En el ejemplo siguiente se muestran varios valores a los que se les ha aplicado cadenas de formato
personalizado que incluyen marcadores de posición de dígitos.

double value;

value = 1.2;
Console::WriteLine(value.ToString("#.##", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#.##}", value));
// Displays 1.2

value = 123;
Console::WriteLine(value.ToString("#####"));
Console::WriteLine(String::Format("{0:#####}", value));
// Displays 123

value = 123456;
Console::WriteLine(value.ToString("[##-##-##]"));
Console::WriteLine(String::Format("{0:[##-##-##]}", value));
// Displays [12-34-56]

value = 1234567890;
Console::WriteLine(value.ToString("#"));
Console::WriteLine(String::Format("{0:#}", value));
// Displays 1234567890

Console::WriteLine(value.ToString("(###) ###-####"));
Console::WriteLine(String::Format("{0:(###) ###-####}", value));
// Displays (123) 456-7890

double value;

value = 1.2;
Console.WriteLine(value.ToString("#.##", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#.##}", value));
// Displays 1.2

value = 123;
Console.WriteLine(value.ToString("#####"));
Console.WriteLine(String.Format("{0:#####}", value));
// Displays 123

value = 123456;
Console.WriteLine(value.ToString("[##-##-##]"));
Console.WriteLine(String.Format("{0:[##-##-##]}", value));
// Displays [12-34-56]

value = 1234567890;
Console.WriteLine(value.ToString("#"));
Console.WriteLine(String.Format("{0:#}", value));
// Displays 1234567890

Console.WriteLine(value.ToString("(###) ###-####"));
Console.WriteLine(String.Format("{0:(###) ###-####}", value));
// Displays (123) 456-7890
Dim value As Double

value = 1.2
Console.WriteLine(value.ToString("#.##", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#.##}", value))
' Displays 1.2

value = 123
Console.WriteLine(value.ToString("#####"))
Console.WriteLine(String.Format("{0:#####}", value))
' Displays 123

value = 123456
Console.WriteLine(value.ToString("[##-##-##]"))
Console.WriteLine(String.Format("{0:[##-##-##]}", value))
' Displays [12-34-56]

value = 1234567890
Console.WriteLine(value.ToString("#"))
Console.WriteLine(String.Format("{0:#}", value))
' Displays 1234567890

Console.WriteLine(value.ToString("(###) ###-####"))
Console.WriteLine(String.Format("{0:(###) ###-####}", value))
' Displays (123) 456-7890

Para devolver una cadena de resultado en la que los dígitos que faltan o los ceros iniciales se reemplazan por
espacios, use la característica de formato compuesto y especifique un ancho de campo, como se muestra en el
ejemplo siguiente.

using namespace System;

void main()
{
Double value = .324;
Console::WriteLine("The value is: '{0,5:#.###}'", value);
}
// The example displays the following output if the current culture
// is en-US:
// The value is: ' .324'

using System;

public class Example


{
public static void Main()
{
Double value = .324;
Console.WriteLine("The value is: '{0,5:#.###}'", value);
}
}
// The example displays the following output if the current culture
// is en-US:
// The value is: ' .324'
Module Example
Public Sub Main()
Dim value As Double = .324
Console.WriteLine("The value is: '{0,5:#.###}'", value)
End Sub
End Module
' The example displays the following output if the current culture
' is en-US:
' The value is: ' .324'

Volver a la tabla

Especificador personalizado "."


El especificador de formato personalizado "." inserta un separador decimal localizado en la cadena del resultado.
El primer punto de la cadena de formato determina la ubicación del separador decimal en el valor con formato y
se omite cualquier punto adicional.
El carácter que se usa como separador decimal en la cadena de resultado no es siempre un punto; viene
determinado por la propiedad NumberDecimalSeparator del objeto NumberFormatInfo que controla la
aplicación de formato.
En el ejemplo siguiente se utiliza el especificador de formato "." para definir la ubicación del separador decimal
en varias cadenas de resultado.

double value;

value = 1.2;
Console::WriteLine(value.ToString("0.00", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.00}", value));
// Displays 1.20

Console::WriteLine(value.ToString("00.00", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:00.00}", value));
// Displays 01.20

Console::WriteLine(value.ToString("00.00",
CultureInfo::CreateSpecificCulture("da-DK")));
Console::WriteLine(String::Format(CultureInfo::CreateSpecificCulture("da-DK"),
"{0:00.00}", value));
// Displays 01,20

value = .086;
Console::WriteLine(value.ToString("#0.##%", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#0.##%}", value));
// Displays 8.6%

value = 86000;
Console::WriteLine(value.ToString("0.###E+0", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.###E+0}", value));
// Displays 8.6E+4
double value;

value = 1.2;
Console.WriteLine(value.ToString("0.00", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.00}", value));
// Displays 1.20

Console.WriteLine(value.ToString("00.00", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:00.00}", value));
// Displays 01.20

Console.WriteLine(value.ToString("00.00",
CultureInfo.CreateSpecificCulture("da-DK")));
Console.WriteLine(String.Format(CultureInfo.CreateSpecificCulture("da-DK"),
"{0:00.00}", value));
// Displays 01,20

value = .086;
Console.WriteLine(value.ToString("#0.##%", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#0.##%}", value));
// Displays 8.6%

value = 86000;
Console.WriteLine(value.ToString("0.###E+0", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+0}", value));
// Displays 8.6E+4

Dim value As Double

value = 1.2
Console.Writeline(value.ToString("0.00", CultureInfo.InvariantCulture))
Console.Writeline(String.Format(CultureInfo.InvariantCulture,
"{0:0.00}", value))
' Displays 1.20

Console.WriteLine(value.ToString("00.00", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:00.00}", value))
' Displays 01.20

Console.WriteLine(value.ToString("00.00", _
CultureInfo.CreateSpecificCulture("da-DK")))
Console.WriteLine(String.Format(CultureInfo.CreateSpecificCulture("da-DK"),
"{0:00.00}", value))
' Displays 01,20

value = .086
Console.WriteLine(value.ToString("#0.##%", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#0.##%}", value))
' Displays 8.6%

value = 86000
Console.WriteLine(value.ToString("0.###E+0", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+0}", value))
' Displays 8.6E+4

Volver a la tabla
Especificador personalizado ","
El carácter "," actúa como separador de grupos y como especificador de escala numérica.
Separador de grupos: si se especifican una o varias comas dos marcadores de posición de dígitos (0 o #)
que dan formato a los dígitos enteros de un número, se insertará un carácter separador de grupos entre
cada grupo de números en la parte entera de la salida.
Las propiedades NumberGroupSeparator y NumberGroupSizes del objeto NumberFormatInfo actual
determinan el carácter utilizado como separador de grupos de números y el tamaño de cada grupo de
números. Por ejemplo, si se utiliza la cadena "#,#" y la referencia cultural de todos los idiomas para dar
formato al número 1000, el resultado será "1,000".
Especificador de escala numérica: si se especifican una o varias comas inmediatamente a la izquierda del
signo decimal explícito o implícito, el número al que se va a dar formato se divide por 1000 por cada
coma. Por ejemplo, si se utiliza la cadena "0,," para dar formato al número 100 millones, el resultado será
"100".
Puede usar especificadores de separador de grupos y de escala numérica en la misma cadena de formato. Por
ejemplo, si se utiliza la cadena "#,0,," y la referencia cultural de todos los idiomas para dar formato al número
mil millones, el resultado será "1,000".
En el ejemplo siguiente se muestra el uso de la coma como separador de grupos.

double value = 1234567890;


Console::WriteLine(value.ToString("#,#", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#,#}", value));
// Displays 1,234,567,890

Console::WriteLine(value.ToString("#,##0,,", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#,##0,,}", value));
// Displays 1,235

double value = 1234567890;


Console.WriteLine(value.ToString("#,#", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,#}", value));
// Displays 1,234,567,890

Console.WriteLine(value.ToString("#,##0,,", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,##0,,}", value));
// Displays 1,235

Dim value As Double = 1234567890


Console.WriteLine(value.ToString("#,#", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,#}", value))
' Displays 1,234,567,890

Console.WriteLine(value.ToString("#,##0,,", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,##0,,}", value))
' Displays 1,235

En el ejemplo siguiente se muestra el uso de la coma como especificador de escala numérica.


double value = 1234567890;
Console::WriteLine(value.ToString("#,,", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#,,}", value));
// Displays 1235

Console::WriteLine(value.ToString("#,,,", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#,,,}", value));
// Displays 1

Console::WriteLine(value.ToString("#,##0,,", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#,##0,,}", value));
// Displays 1,235

double value = 1234567890;


Console.WriteLine(value.ToString("#,,", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,,}", value));
// Displays 1235

Console.WriteLine(value.ToString("#,,,", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,,,}", value));
// Displays 1

Console.WriteLine(value.ToString("#,##0,,", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,##0,,}", value));
// Displays 1,235

Dim value As Double = 1234567890


Console.WriteLine(value.ToString("#,,", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0:#,,}", value))
' Displays 1235

Console.WriteLine(value.ToString("#,,,", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,,,}", value))
' Displays 1

Console.WriteLine(value.ToString("#,##0,,", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,##0,,}", value))
' Displays 1,235

Volver a la tabla

Especificador personalizado "%"


Un signo de porcentaje (%) en una cadena de formato hace que se multiplique un número por 100 antes de
darle formato. El símbolo de porcentaje adaptado se inserta en el número en la ubicación donde aparece % en la
cadena de formato. La propiedad PercentSymbol del objeto NumberFormatInfo actual define el carácter de
porcentaje empleado.
En el ejemplo siguiente se definen varias cadenas de formato personalizado que incluyen el especificador
personalizado "%".
double value = .086;
Console::WriteLine(value.ToString("#0.##%", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#0.##%}", value));
// Displays 8.6%

double value = .086;


Console.WriteLine(value.ToString("#0.##%", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#0.##%}", value));
// Displays 8.6%

Dim value As Double = .086


Console.WriteLine(value.ToString("#0.##%", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#0.##%}", value))
' Displays 8.6%

Volver a la tabla

Especificador personalizado "‰"


Un carácter de "por mil" (‰ o \u2030) en una cadena de formato hace que un número se multiplique por 1000
antes de darle formato. El símbolo de "por mil" adecuado se inserta en la cadena devuelta, en la ubicación de la
cadena de formato en la que aparece el símbolo ‰. La propiedad NumberFormatInfo.PerMilleSymbol del objeto
que proporciona la información de formato específica de la referencia cultural es la que determina el carácter de
"por mil" que se utiliza.
En el ejemplo siguiente se define una cadena de formato personalizado que incluye el especificador
personalizado "‰".

double value = .00354;


String^ perMilleFmt = "#0.## " + '\u2030';
Console::WriteLine(value.ToString(perMilleFmt, CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:" + perMilleFmt + "}", value));
// Displays 3.54‰

double value = .00354;


string perMilleFmt = "#0.## " + '\u2030';
Console.WriteLine(value.ToString(perMilleFmt, CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:" + perMilleFmt + "}", value));
// Displays 3.54‰

Dim value As Double = .00354


Dim perMilleFmt As String = "#0.## " & ChrW(&h2030)
Console.WriteLine(value.ToString(perMilleFmt, CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:" + perMilleFmt + "}", value))
' Displays 3.54 ‰

Volver a la tabla
Especificadores personalizados "E" y "e"
Si alguna de las cadenas "E", "E+", "E-", "e", "e+", o "e-" está presente en la cadena de formato y va seguida
inmediatamente de al menos un cero, se da formato al número mediante notación científica con una 'E' o una 'e'
insertadas entre el número y el exponente. El número de ceros que hay a continuación del indicador de notación
científica determina el número mínimo de dígitos para el exponente. Los formatos 'E+' y 'e+' indican que un
signo más o un signo menos debe preceder siempre al exponente. Los formatos 'E', 'E-', 'e' o 'e-' indican que un
carácter de signo debe preceder solo a exponentes negativos.
En el ejemplo siguiente se da formato a varios valores numéricos utilizando los especificadores de notación
científica.

double value = 86000;


Console::WriteLine(value.ToString("0.###E+0", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.###E+0}", value));
// Displays 8.6E+4

Console::WriteLine(value.ToString("0.###E+000", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.###E+000}", value));
// Displays 8.6E+004

Console::WriteLine(value.ToString("0.###E-000", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.###E-000}", value));
// Displays 8.6E004

double value = 86000;


Console.WriteLine(value.ToString("0.###E+0", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+0}", value));
// Displays 8.6E+4

Console.WriteLine(value.ToString("0.###E+000", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+000}", value));
// Displays 8.6E+004

Console.WriteLine(value.ToString("0.###E-000", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E-000}", value));
// Displays 8.6E004

Dim value As Double = 86000


Console.WriteLine(value.ToString("0.###E+0", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+0}", value))
' Displays 8.6E+4

Console.WriteLine(value.ToString("0.###E+000", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+000}", value))
' Displays 8.6E+004

Console.WriteLine(value.ToString("0.###E-000", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E-000}", value))
' Displays 8.6E004
Volver a la tabla

Carácter de escape "\"


Los símbolos "#", "0", ".", ",", "%" y "‰" en una cadena de formato se interpretan como especificadores de
formato en lugar de como caracteres literales. Dependiendo de su posición en una cadena de formato
personalizado, la "E" en mayúsculas y minúsculas así como los símbolos + y - también se pueden interpretar
como especificadores de formato.
Para evitar que un carácter se interprete como un especificador de formato, puede precederlo con una barra
diagonal inversa, que es el carácter de escape. El carácter de escape significa que el siguiente carácter es un
carácter literal que se debe incluir en la cadena de resultado sin modificar.
Para incluir una barra diagonal inversa en una cadena de resultado, debe indicar su secuencia de escape con otra
barra diagonal inversa ( \\ ).

NOTE
Algunos compiladores, como los compiladores de C# y C++, también pueden interpretar un único carácter de barra
diagonal inversa como un carácter de escape. Para asegurarse de que una cadena se interpreta correctamente al darle
formato, puede usar el carácter literal de cadena textual (el carácter @) antes de la cadena en C# o puede agregar otro
carácter de barra diagonal inversa delante de cada barra diagonal inversa en C# y C++. En el siguiente ejemplo de C# se
muestran ambos enfoques.

En el ejemplo siguiente se usa el carácter de escape para evitar que la operación de formato interprete los
caracteres "#", "0" y "\" como caracteres de escape o especificadores de formato. En el ejemplo de C# se usa una
barra diagonal inversa adicional para asegurarse de que una barra diagonal inversa se interprete como un
carácter literal.

int value = 123;


Console::WriteLine(value.ToString("\\#\\#\\# ##0 dollars and \\0\\0 cents \\#\\#\\#"));
Console::WriteLine(String::Format("{0:\\#\\#\\# ##0 dollars and \\0\\0 cents \\#\\#\\#}",
value));
// Displays ### 123 dollars and 00 cents ###

Console::WriteLine(value.ToString("\\#\\#\\# ##0 dollars and \0\0 cents \\#\\#\\#"));


Console::WriteLine(String::Format("{0:\\#\\#\\# ##0 dollars and \0\0 cents \\#\\#\\#}",
value));
// Displays ### 123 dollars and 00 cents ###

Console::WriteLine(value.ToString("\\\\\\\\\\\\ ##0 dollars and \\0\\0 cents \\\\\\\\\\\\"));


Console::WriteLine(String::Format("{0:\\\\\\\\\\\\ ##0 dollars and \\0\\0 cents \\\\\\\\\\\\}",
value));
// Displays \\\ 123 dollars and 00 cents \\\

Console::WriteLine(value.ToString("\\\\\\ ##0 dollars and \0\0 cents \\\\\\"));


Console::WriteLine(String::Format("{0:\\\\\\ ##0 dollars and \0\0 cents \\\\\\}",
value));
// Displays \\\ 123 dollars and 00 cents \\\
int value = 123;
Console.WriteLine(value.ToString("\\#\\#\\# ##0 dollars and \\0\\0 cents \\#\\#\\#"));
Console.WriteLine(String.Format("{0:\\#\\#\\# ##0 dollars and \\0\\0 cents \\#\\#\\#}",
value));
// Displays ### 123 dollars and 00 cents ###

Console.WriteLine(value.ToString(@"\#\#\# ##0 dollars and \0\0 cents \#\#\#"));


Console.WriteLine(String.Format(@"{0:\#\#\# ##0 dollars and \0\0 cents \#\#\#}",
value));
// Displays ### 123 dollars and 00 cents ###

Console.WriteLine(value.ToString("\\\\\\\\\\\\ ##0 dollars and \\0\\0 cents \\\\\\\\\\\\"));


Console.WriteLine(String.Format("{0:\\\\\\\\\\\\ ##0 dollars and \\0\\0 cents \\\\\\\\\\\\}",
value));
// Displays \\\ 123 dollars and 00 cents \\\

Console.WriteLine(value.ToString(@"\\\\\\ ##0 dollars and \0\0 cents \\\\\\"));


Console.WriteLine(String.Format(@"{0:\\\\\\ ##0 dollars and \0\0 cents \\\\\\}",
value));
// Displays \\\ 123 dollars and 00 cents \\\

Dim value As Integer = 123


Console.WriteLine(value.ToString("\#\#\# ##0 dollars and \0\0 cents \#\#\#"))
Console.WriteLine(String.Format("{0:\#\#\# ##0 dollars and \0\0 cents \#\#\#}",
value))
' Displays ### 123 dollars and 00 cents ###

Console.WriteLine(value.ToString("\\\\\\ ##0 dollars and \0\0 cents \\\\\\"))


Console.WriteLine(String.Format("{0:\\\\\\ ##0 dollars and \0\0 cents \\\\\\}",
value))
' Displays \\\ 123 dollars and 00 cents \\\

Volver a la tabla

Separador de sección ";"


El punto y coma (;) es un especificador de formato condicional que aplica distinto formato a un número
dependiendo de si su valor es positivo, negativo o cero. Para generar este comportamiento, una cadena de
formato personalizado puede contener hasta tres secciones separadas por signos de punto y coma. Estas
secciones se describen en la siguiente tabla.

N ÚM ERO DE SEC C IO N ES DESC RIP C IÓ N

Una sección La cadena de formato se aplica a todos los valores.

Dos secciones La primera sección se aplica a valores positivos y ceros, y la


segunda, sólo a valores negativos.

Si el número al que se va a dar formato es negativo, pero se


convierte en cero después de redondearlo según el formato
de la segunda sección, se da formato al cero resultante
según la primera sección.
N ÚM ERO DE SEC C IO N ES DESC RIP C IÓ N

Tres secciones. La primera sección se aplica a valores positivos y ceros, la


segunda, sólo a valores negativos, y la tercera, a ceros.

La segunda sección se puede dejar vacía (no dejando nada


entre los signos de punto y coma) y, en ese caso, la primera
sección se aplica a los valores distintos de cero.

Si el número al que se va a dar formato es distinto de cero,


pero se convierte en cero después de redondearlo según el
formato de la primera o la segunda sección, se da formato al
cero resultante según la tercera sección.

Los separadores de sección omiten cualquier formato preexistente asociado a un número al dar formato al valor
final. Por ejemplo, los valores negativos se muestran siempre con signo menos cuando se utilizan separadores
de sección. Si se desea que el valor con formato final tenga un signo menos, debe incluir explícitamente el signo
menos como parte del especificador de formato personalizado.
En el ejemplo siguiente se usa el especificador de formato ";" para aplicar un formato diferente a los números
positivos, negativos y cero.

double posValue = 1234;


double negValue = -1234;
double zeroValue = 0;

String^ fmt2 = "##;(##)";


String^ fmt3 = "##;(##);**Zero**";

Console::WriteLine(posValue.ToString(fmt2));
Console::WriteLine(String::Format("{0:" + fmt2 + "}", posValue));
// Displays 1234

Console::WriteLine(negValue.ToString(fmt2));
Console::WriteLine(String::Format("{0:" + fmt2 + "}", negValue));
// Displays (1234)

Console::WriteLine(zeroValue.ToString(fmt3));
Console::WriteLine(String::Format("{0:" + fmt3 + "}", zeroValue));
// Displays **Zero**

double posValue = 1234;


double negValue = -1234;
double zeroValue = 0;

string fmt2 = "##;(##)";


string fmt3 = "##;(##);**Zero**";

Console.WriteLine(posValue.ToString(fmt2));
Console.WriteLine(String.Format("{0:" + fmt2 + "}", posValue));
// Displays 1234

Console.WriteLine(negValue.ToString(fmt2));
Console.WriteLine(String.Format("{0:" + fmt2 + "}", negValue));
// Displays (1234)

Console.WriteLine(zeroValue.ToString(fmt3));
Console.WriteLine(String.Format("{0:" + fmt3 + "}", zeroValue));
// Displays **Zero**
Dim posValue As Double = 1234
Dim negValue As Double = -1234
Dim zeroValue As Double = 0

Dim fmt2 As String = "##;(##)"


Dim fmt3 As String = "##;(##);**Zero**"

Console.WriteLine(posValue.ToString(fmt2))
Console.WriteLine(String.Format("{0:" + fmt2 + "}", posValue))
' Displays 1234

Console.WriteLine(negValue.ToString(fmt2))
Console.WriteLine(String.Format("{0:" + fmt2 + "}", negValue))
' Displays (1234)

Console.WriteLine(zeroValue.ToString(fmt3))
Console.WriteLine(String.Format("{0:" + fmt3 + "}", zeroValue))
' Displays **Zero**

Volver a la tabla

Literales de carácter
Los especificadores de formato que aparecen en una cadena de formato numérico personalizado siempre se
interpretan como caracteres de formato y no como caracteres literales. Se incluyen los caracteres siguientes:
0
#
%

'
\
.
,
E o e, según su posición en la cadena de formato.
Todos los demás caracteres se interpretan siempre como literales de carácter y, en una operación de formato, se
incluyen en la cadena de resultado sin modificar. En una operación de análisis, deben coincidir exactamente con
los caracteres de la cadena de entrada; la comparación distingue entre mayúsculas y minúsculas.
En el ejemplo siguiente se muestra un uso habitual de las unidades de carácter literal (en este caso, los millares):

double n = 123.8;
Console.WriteLine($"{n:#,##0.0K}");
// The example displays the following output:
// 123.8K

Dim n As Double = 123.8


Console.WriteLine($"{n:#,##0.0K}")
' The example displays the following output:
' 123.8K

Hay dos formas de indicar que los caracteres se han de interpretar como caracteres literales y no como
caracteres de formato, para que se puedan incluir en una cadena de resultado o analizarse correctamente en una
cadena de entrada:
Mediante el escape de un carácter de formato. Para obtener más información, vea Carácter de escape "\".
Mediante la inclusión de toda la cadena literal entre comillas o apóstrofes.
En el ejemplo siguiente se usan los dos enfoques para incluir caracteres reservados en una cadena de formato
numérico personalizada.

double n = 9.3;
Console.WriteLine($@"{n:##.0\%}");
Console.WriteLine($@"{n:\'##\'}");
Console.WriteLine($@"{n:\\##\\}");
Console.WriteLine();
Console.WriteLine($"{n:##.0'%'}");
Console.WriteLine($@"{n:'\'##'\'}");
// The example displays the following output:
// 9.3%
// '9'
// \9\
//
// 9.3%
// \9\

Dim n As Double = 9.3


Console.WriteLine($"{n:##.0\%}")
Console.WriteLine($"{n:\'##\'}")
Console.WriteLine($"{n:\\##\\}")
Console.WriteLine()
Console.WriteLine($"{n:##.0'%'}")
Console.WriteLine($"{n:'\'##'\'}")
' The example displays the following output:
' 9.3%
' '9'
' \9\
'
' 9.3%
' \9\

Notas
Infinitos de punto flotante y NaN
Independientemente de la cadena de formato, si el valor de un tipo de punto flotante Single o Double es infinito
positivo, infinito negativo o NaN (Not a Number, no es un número), la cadena con formato será el valor de la
propiedad PositiveInfinitySymbol, NegativeInfinitySymbolo NaNSymbol respectiva especificada por el objeto
NumberFormatInfo aplicable actualmente.
Configuración del Panel de control
Los valores de configuración del elemento Configuración regional y de idioma del Panel de control influyen
en la cadena de resultado generada por una operación de formato. Estos valores de configuración se utilizan
para inicializar el objeto NumberFormatInfo asociado a la referencia cultural del subproceso actual, y la
referencia cultural del subproceso actual proporciona valores que se utilizan para controlar el formato. Los
equipos que usan configuraciones diferentes generarán cadenas de resultado distintas.
Asimismo, si se usa el constructor CultureInfo(String) para crear instancias de un nuevo objeto CultureInfo que
representa la misma referencia cultural que la referencia cultural del sistema actual, cualquier personalización
establecida por el elemento Configuración regional y de idioma del Panel de control se aplicará al nuevo
objeto CultureInfo . Puede usar el constructor CultureInfo(String, Boolean) para crear un objeto CultureInfo que
no refleje las personalizaciones de un sistema.
Cadenas de formato de punto fijo y redondeo
Para las cadenas de formato de punto fijo (es decir, las cadenas de formato que no contienen caracteres de
formato de notación científica), los números se redondean hasta tantos decimales como marcadores de posición
de dígitos haya a la derecha del separador decimal. Si la cadena de formato no contiene ningún separador
decimal, el número se redondea al entero más próximo. Si el número tiene más dígitos que marcadores de
posición de dígitos a la izquierda del separador decimal, los dígitos adicionales se copian en la cadena de
resultado justo antes del primer marcador de posición de dígitos.
Volver a la tabla

Ejemplo
En el siguiente ejemplo se muestran dos cadenas de formato numérico personalizado. En ambos casos, el
marcador de posición de dígitos ( # ) muestra los datos numéricos, y todos los demás caracteres se copian en la
cadena de resultado.

double number1 = 1234567890;


String^ value1 = number1.ToString("(###) ###-####");
Console::WriteLine(value1);

int number2 = 42;


String^ value2 = number2.ToString("My Number = #");
Console::WriteLine(value2);
// The example displays the following output:
// (123) 456-7890
// My Number = 42

double number1 = 1234567890;


string value1 = number1.ToString("(###) ###-####");
Console.WriteLine(value1);

int number2 = 42;


string value2 = number2.ToString("My Number = #");
Console.WriteLine(value2);
// The example displays the following output:
// (123) 456-7890
// My Number = 42

Dim number1 As Double = 1234567890


Dim value1 As String = number1.ToString("(###) ###-####")
Console.WriteLine(value1)

Dim number2 As Integer = 42


Dim value2 As String = number2.ToString("My Number = #")
Console.WriteLine(value2)
' The example displays the following output:
' (123) 456-7890
' My Number = 42

Volver a la tabla

Vea también
System.Globalization.NumberFormatInfo
Aplicación de formato a tipos
Cadenas con formato numérico estándar
Cómo: Rellenar un número con ceros a la izquierda
Ejemplo: Utilidad de formato WinForms de .NET Core (C#)
Ejemplo: Utilidad de formato WinForms de .NET Core (Visual Basic)
Cadenas con formato de fecha y hora estándar
16/09/2020 • 54 minutes to read • Edit Online

Una cadena de formato de fecha y hora estándar usa un único especificador de formato para definir la
representación de texto de un valor de fecha y hora. Cualquier cadena con formato de fecha y hora que contenga
más de un carácter, incluido un espacio en blanco, se interpreta como una cadena con formato de fecha y hora
personalizado; para obtener más información, consulte Cadenas con formato de fecha y hora personalizado. Una
cadena de formato estándar o personalizado se puede usar de dos maneras:
Para definir la cadena resultante una operación de formato.
Para definir la representación de texto de un valor de fecha y hora que se puede convertir en un valor
DateTime o DateTimeOffset mediante una operación de análisis.

TIP
Puede descargar la Utilidad de formato , que es una aplicación de .NET Core Windows Forms que permite aplicar
cadenas de formato a valores numéricos o de fecha y hora, y que muestra la cadena de resultado. El código fuente está
disponible para C# y Visual Basic.

Las cadenas con formato de fecha y hora estándar se pueden utilizar tanto con valores DateTime como con
valores DateTimeOffset.

NOTE
Algunos de los ejemplos de C# de este artículo se ejecutan en el ejecutor de código en línea y área de juegos de Try.NET.
Haga clic en el botón Ejecutar para ejecutar un ejemplo en una ventana interactiva. Una vez que se ejecuta el código,
puede modificar y ejecutar el código modificado si vuelve a hacer clic en Ejecutar . El código modificado se ejecuta en la
ventana interactiva o, si se produce un error en la compilación, en la ventana interactiva se muestran todos los mensajes
de error del compilador de C#.
La zona horaria local del ejecutor de código en línea de Try.NET y del área de juegos es la hora universal coordinada o UTC.
Esto puede afectar al comportamiento y la salida de ejemplos que ilustran los tipos DateTime, DateTimeOffset y
TimeZoneInfo y sus miembros.

En la tabla siguiente se describen los especificadores de formato de fecha y hora estándar. A menos que se
indique lo contrario, un determinado especificador de formato de fecha y hora estándar genera una
representación de cadena idéntica independientemente de que se use con un valor DateTime o DateTimeOffset.
Consulte la sección Notas para obtener información adicional sobre cómo usar cadenas de formato de fecha y
hora estándar.

ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO S

"d" Patrón de fecha corta. 2009-06-15T13:45:30 -> 6/15/2009


(en-US)
Más información: El especificador de
formato de fecha corta ("d"). 2009-06-15T13:45:30 -> 15/06/2009
(fr-FR)

2009-06-15T13:45:30 -> 2009/06/15


(ja-JP)
ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO S

"D" Patrón de fecha larga. 2009-06-15T13:45:30 -> Monday,


June 15, 2009 (en-US)
Más información: El especificador de
formato de fecha larga ("D"). 2009-06-15T13:45:30 -> 15 июня
2009 г. (ru-RU)

2009-06-15T13:45:30 -> Montag, 15.


Juni 2009 (de-DE)

"f" Patrón de fecha y hora completa (hora 2009-06-15T13:45:30 -> Monday,


corta). June 15, 2009 1:45 PM (en-US)

Más información: El especificador de 2009-06-15T13:45:30 -> den 15 juni


formato de fecha completa y hora 2009 13:45 (sv-SE)
corta ("f").
2009-06-15T13:45:30 -> Δευτέρα, 15
Ιουνίου 2009 1:45 μμ (el-GR)

"F" Patrón de fecha y hora completa (hora 2009-06-15T13:45:30 -> Monday,


larga). June 15, 2009 1:45:30 PM (en-US)

Más información: El especificador de 2009-06-15T13:45:30 -> den 15 juni


formato de fecha completa y hora 2009 13:45:30 (sv-SE)
larga ("F").
2009-06-15T13:45:30 -> Δευτέρα, 15
Ιουνίου 2009 1:45:30 μμ (el-GR)

"g" Patrón de fecha y hora general (hora 2009-06-15T13:45:30 -> 6/15/2009


corta). 1:45 PM (en-US)

Más información: El especificador de 2009-06-15T13:45:30 -> 15/06/2009


formato de fecha general y hora corta 13:45 (es-ES)
("g").
2009-06-15T13:45:30 -> 2009/6/15
13:45 (zh-CN)

"G" Patrón de fecha y hora general (hora 2009-06-15T13:45:30 -> 6/15/2009


larga). 1:45:30 PM (en-US)

Más información: El especificador de 2009-06-15T13:45:30 -> 15/06/2009


formato de fecha general y hora larga 13:45:30 (es-ES)
("G").
2009-06-15T13:45:30 -> 2009/6/15
13:45:30 (zh-CN)

"M", "m" Patrón de mes/día. 2009-06-15T13:45:30 -> June 15 (en-


US)
Más información: El especificador de
formato de mes ("M", "m"). 2009-06-15T13:45:30 -> 15. juni (da-
DK)

2009-06-15T13:45:30 -> 15 Juni (id-


ID)
ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO S

"O", "o" Patrón de fecha y hora de ida y vuelta. Valores de DateTime:

Más información: El especificador de 2009-06-15T13:45:30


formato de operación de ida y vuelta (DateTimeKind.Local) --> 2009-06-
("O", "o"). 15T13:45:30.0000000-07:00

2009-06-15T13:45:30
(DateTimeKind.Utc) --> 2009-06-
15T13:45:30.0000000Z

2009-06-15T13:45:30
(DateTimeKind.Unspecified) --> 2009-
06-15T13:45:30.0000000

Valores de DateTimeOffset:

2009-06-15T13:45:30-07:00 -->
2009-06-15T13:45:30.0000000-07:00

"R", "r" Patrón RFC1123. 2009-06-15T13:45:30 -> Mon, 15 Jun


2009 20:45:30 GMT
Más información: El especificador de
formato RFC1123 ("R", "r").

"s" Patrón de fecha y hora que se puede 2009-06-15T13:45:30


ordenar. (DateTimeKind.Local) -> 2009-06-
15T13:45:30
Más información: El especificador de
formato que se puede ordenar ("s"). 2009-06-15T13:45:30
(DateTimeKind.Utc) -> 2009-06-
15T13:45:30

"t" Patrón de hora corta. 2009-06-15T13:45:30 -> 1:45 PM


(en-US)
Más información: El especificador de
formato de hora corta ("t"). 2009-06-15T13:45:30 -> 13:45 (hr-
HR)

2009-06-15T13:45:30 -> 01:45 ‫( م‬ar-


EG)

"T" Patrón de hora larga. 2009-06-15T13:45:30 -> 1:45:30 PM


(en-US)
Más información: El especificador de
formato de hora larga ("T"). 2009-06-15T13:45:30 -> 13:45:30
(hr-HR)

2009-06-15T13:45:30 -> 01:45:30 ‫م‬


(ar-EG)

"u" Patrón de fecha y hora universal que se Con un valor DateTime de: 2009-06-
puede ordenar. 15T13:45:30 -> 2009-06-15
13:45:30Z
Más información: El especificador de
formato universal que se puede Con un valor DateTimeOffset de:
ordenar ("u"). 2009-06-15T13:45:30 -> 2009-06-15
20:45:30Z
ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO S

"U" Patrón de fecha y hora completa 2009-06-15T13:45:30 -> Monday,


universal. June 15, 2009 8:45:30 PM (en-US)

Más información: El especificador de 2009-06-15T13:45:30 -> den 15 juni


formato completo universal ("U"). 2009 20:45:30 (sv-SE)

2009-06-15T13:45:30 -> Δευτέρα, 15


Ιουνίου 2009 8:45:30 μμ (el-GR)

"Y", "y" Patrón de mes y año. 2009-06-15T13:45:30 -> June 2009


(en-US)
Más información: El especificador de
formato de mes y año ("Y"). 2009-06-15T13:45:30 -> juni 2009
(da-DK)

2009-06-15T13:45:30 -> Juni 2009


(id-ID)

Cualquier otro carácter único Especificador desconocido. Produce una excepción


FormatException en tiempo de
ejecución.

Cómo funcionan las cadenas con formato estándar


En una operación de formato, una cadena de formato estándar es simplemente un alias de una cadena con
formato personalizado. La ventaja de usar un alias para referirse a una cadena de formato personalizado es que,
aunque el alias permanece invariable, la propia cadena de formato personalizado puede variar. Esto es
importante, porque las representaciones de cadena de valores de fecha y hora suelen variar con las referencias
culturales. Por ejemplo, la cadena de formato estándar "d" indica que un valor de fecha y hora se va a mostrar
utilizando un patrón de fecha corta. En la referencia cultural de todos los idiomas, este patrón es "MM/dd/aaaa".
En la referencia cultural fr-FR, es "dd/MM/aaaa". En la referencia cultural ja-JP, es "aaaa/MM/dd."
Si una cadena con formato estándar en una operación de formato se asigna a una cadena con formato
personalizado de una referencia cultural específica, la aplicación puede definir la referencia cultural concreta
cuyas cadenas con formato personalizado se usan de uno de los modos siguientes:
Puede utilizar la referencia cultural predeterminada (o la actual). En el ejemplo siguiente se muestra una
fecha con el formato de fecha abreviado de la referencia cultural. En este caso, la referencia cultural actual
es en-US.

// Display using current (en-us) culture's short date format


DateTime thisDate = new DateTime(2008, 3, 15);
Console.WriteLine(thisDate.ToString("d")); // Displays 3/15/2008

' Display using current (en-us) culture's short date format


Dim thisDate As Date = #03/15/2008#
Console.WriteLine(thisDate.ToString("d")) ' Displays 3/15/2008

Puede pasar un objeto CultureInfo que represente la referencia cultural cuyo formato se va a usar a un
método que tenga un parámetro IFormatProvider. En el ejemplo siguiente se muestra una fecha con el
formato de fecha abreviado de la referencia cultural pt-BR.
// Display using pt-BR culture's short date format
DateTime thisDate = new DateTime(2008, 3, 15);
CultureInfo culture = new CultureInfo("pt-BR");
Console.WriteLine(thisDate.ToString("d", culture)); // Displays 15/3/2008

' Display using pt-BR culture's short date format


Dim thisDate As Date = #03/15/2008#
Dim culture As New CultureInfo("pt-BR")
Console.WriteLine(thisDate.ToString("d", culture)) ' Displays 15/3/2008

Puede pasar un objeto DateTimeFormatInfo que proporcione información sobre el formato a un método
que tenga un parámetro IFormatProvider. En el ejemplo siguiente se muestra una fecha con el formato de
fecha abreviado de un objeto DateTimeFormatInfo en la referencia cultural hr-HR.

// Display using date format information from hr-HR culture


DateTime thisDate = new DateTime(2008, 3, 15);
DateTimeFormatInfo fmt = (new CultureInfo("hr-HR")).DateTimeFormat;
Console.WriteLine(thisDate.ToString("d", fmt)); // Displays 15.3.2008

' Display using date format information from hr-HR culture


Dim thisDate As Date = #03/15/2008#
Dim fmt As DateTimeFormatInfo = (New CultureInfo("hr-HR")).DateTimeFormat
Console.WriteLine(thisDate.ToString("d", fmt)) ' Displays 15.3.2008

NOTE
Para obtener información sobre la personalización de patrones o cadenas usadas para dar formato a valores de fecha y
hora, vea el tema sobre la clase NumberFormatInfo.

En algunos casos, la cadena con formato estándar actúa como la abreviatura correspondiente de una cadena con
formato personalizado más larga que es invariable. Hay cuatro cadenas de formato estándar que pertenecen a
esta categoría: "O" (u "o"), "R" (o "r"), "s" y "u". Estas cadenas se corresponden con las cadenas de formato
personalizado definidas en la referencia cultural de todos los idiomas. Generan representaciones de cadena de
valores de fecha y hora que están pensados para que sean idénticos en todas las referencias culturales. En la
tabla siguiente se proporciona información sobre estas cuatro cadenas de formato de fecha y hora estándar.

SE DEF IN E EN L A P RO P IEDA D
DAT ET IM EF O RM AT IN F O. IN VA RIA N T IN F C A DEN A C O N F O RM ATO
C A DEN A C O N F O RM ATO ESTÁ N DA R O P ERSO N A L IZ A DO

"O" u "o" None yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffzz

"R" o "r" RFC1123Pattern ddd, dd MMM yyyy HH':'mm':'ss 'GMT'

"s" SortableDateTimePattern yyyy'-'MM'-'dd'T'HH':'mm':'ss

"u" UniversalSortableDateTimePattern yyyy'-'MM'-'dd HH':'mm':'ss'Z'

Las cadenas de formato estándar también se pueden usar en operaciones de análisis con los métodos
DateTime.ParseExact o DateTimeOffset.ParseExact, que necesitan que una cadena de entrada se ajuste
exactamente a un patrón determinado para que la operación de análisis se realice correctamente. Muchas
cadenas de formato estándar se asignan a varias cadenas de formato personalizado, por lo que un valor de fecha
y hora se pueden representar en diversos formatos y la operación de análisis todavía se realizará correctamente.
Puede determinar la cadena o las cadenas con formato personalizado correspondientes a una cadena con
formato estándar llamando al método DateTimeFormatInfo.GetAllDateTimePatterns(Char). En el ejemplo
siguiente se muestran las cadenas con formato personalizado que se asignan a la cadena de formato estándar
"d" (patrón de fecha corta).

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
Console.WriteLine("'d' standard format string:");
foreach (var customString in DateTimeFormatInfo.CurrentInfo.GetAllDateTimePatterns('d'))
Console.WriteLine(" {0}", customString);
}
}
// The example displays the following output:
// 'd' standard format string:
// M/d/yyyy
// M/d/yy
// MM/dd/yy
// MM/dd/yyyy
// yy/MM/dd
// yyyy-MM-dd
// dd-MMM-yy

Imports System.Globalization

Module Example
Public Sub Main()
Console.WriteLine("'d' standard format string:")
For Each customString In DateTimeFormatInfo.CurrentInfo.GetAllDateTimePatterns("d"c)
Console.WriteLine(" {0}", customString)
Next
End Sub
End Module
' The example displays the following output:
' 'd' standard format string:
' M/d/yyyy
' M/d/yy
' MM/dd/yy
' MM/dd/yyyy
' yy/MM/dd
' yyyy-MM-dd
' dd-MMM-yy

En las próximas secciones se describen los especificadores de formato estándar para los valores DateTime y
DateTimeOffset.

El especificador de formato de fecha corta ("d")


El especificador de formato estándar "d" representa una cadena de formato de fecha y hora personalizado que
está definida por la propiedad DateTimeFormatInfo.ShortDatePattern de una referencia cultural concreta. Por
ejemplo, la cadena de formato personalizado devuelta por la propiedad ShortDatePattern de la referencia
cultural de todos los idiomas es "MM/dd/yyyy".
En la tabla siguiente se enumeran las propiedades del objeto DateTimeFormatInfo que controlan el formato de la
cadena devuelta.
P RO P IEDA D. DESC RIP C IÓ N

ShortDatePattern Define el formato global de la cadena de resultado.

DateSeparator Define la cadena que separa los componentes de año, mes y


día de una fecha.

En el ejemplo siguiente se usa el especificador de formato "d" para mostrar un valor de fecha y hora.

DateTime date1 = new DateTime(2008,4, 10);


Console.WriteLine(date1.ToString("d", DateTimeFormatInfo.InvariantInfo));
// Displays 04/10/2008
Console.WriteLine(date1.ToString("d",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays 4/10/2008
Console.WriteLine(date1.ToString("d",
CultureInfo.CreateSpecificCulture("en-NZ")));
// Displays 10/04/2008
Console.WriteLine(date1.ToString("d",
CultureInfo.CreateSpecificCulture("de-DE")));
// Displays 10.04.2008

Dim date1 As Date = #4/10/2008#


Console.WriteLine(date1.ToString("d", DateTimeFormatInfo.InvariantInfo))
' Displays 04/10/2008
Console.WriteLine(date1.ToString("d", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays 4/10/2008
Console.WriteLine(date1.ToString("d", _
CultureInfo.CreateSpecificCulture("en-NZ")))
' Displays 10/04/2008
Console.WriteLine(date1.ToString("d", _
CultureInfo.CreateSpecificCulture("de-DE")))
' Displays 10.04.2008

Volver a la tabla

El especificador de formato de fecha larga ("D")


El especificador de formato estándar "D" representa una cadena de formato de fecha y hora personalizado que
está definida por la propiedad DateTimeFormatInfo.LongDatePattern actual. Por ejemplo, la cadena de formato
personalizado para la referencia cultural de todos los idiomas es "dddd, dd MMMM yyyy".
En la tabla siguiente se enumeran las propiedades del objeto DateTimeFormatInfo que controlan el formato de la
cadena devuelta.

P RO P IEDA D. DESC RIP C IÓ N

LongDatePattern Define el formato global de la cadena de resultado.

DayNames Define los nombres de días traducidos que pueden aparecer


en la cadena de resultado.

MonthNames Define los nombres de meses traducidos que pueden


aparecer en la cadena de resultado.

En el ejemplo siguiente se usa el especificador de formato "D" para mostrar un valor de fecha y hora.
DateTime date1 = new DateTime(2008, 4, 10);
Console.WriteLine(date1.ToString("D",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Thursday, April 10, 2008
Console.WriteLine(date1.ToString("D",
CultureInfo.CreateSpecificCulture("pt-BR")));
// Displays quinta-feira, 10 de abril de 2008
Console.WriteLine(date1.ToString("D",
CultureInfo.CreateSpecificCulture("es-MX")));
// Displays jueves, 10 de abril de 2008

Dim date1 As Date = #4/10/2008#


Console.WriteLine(date1.ToString("D", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Thursday, April 10, 2008
Console.WriteLine(date1.ToString("D", _
CultureInfo.CreateSpecificCulture("pt-BR")))
' Displays quinta-feira, 10 de abril de 2008
Console.WriteLine(date1.ToString("D", _
CultureInfo.CreateSpecificCulture("es-MX")))
' Displays jueves, 10 de abril de 2008

Volver a la tabla

El especificador de formato de fecha completa y hora corta ("f")


El especificador de formato estándar "f" representa una combinación de los patrones de fecha larga ("D") y hora
corta ("t"), separados por un espacio.
La información de formato de un objeto DateTimeFormatInfo específico afecta a la cadena de resultado. En la
tabla siguiente se enumeran las propiedades del objeto DateTimeFormatInfo que pueden controlar el formato de
la cadena devuelta. El especificador de formato personalizado devuelto por las propiedades
DateTimeFormatInfo.LongDatePattern y DateTimeFormatInfo.ShortTimePattern de algunas referencias culturales
quizás no use todas las propiedades.

P RO P IEDA D. DESC RIP C IÓ N

LongDatePattern Define el formato del componente de fecha de la cadena de


resultado.

ShortTimePattern Define el formato del componente de hora de la cadena de


resultado.

DayNames Define los nombres de días traducidos que pueden aparecer


en la cadena de resultado.

MonthNames Define los nombres de meses traducidos que pueden


aparecer en la cadena de resultado.

TimeSeparator Define la cadena que separa los componentes de hora,


minutos y segundos de una hora.

AMDesignator Define la cadena que indica las horas comprendidas desde


medianoche hasta antes del mediodía en un reloj de 12
horas.
P RO P IEDA D. DESC RIP C IÓ N

PMDesignator Define la cadena que indica las horas comprendidas desde el


mediodía hasta antes de medianoche en un reloj de 12
horas.

En el ejemplo siguiente se usa el especificador de formato "f" para mostrar un valor de fecha y hora.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("f",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Thursday, April 10, 2008 6:30 AM
Console.WriteLine(date1.ToString("f",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays jeudi 10 avril 2008 06:30

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("f", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Thursday, April 10, 2008 6:30 AM
Console.WriteLine(date1.ToString("f", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays jeudi 10 avril 2008 06:30

Volver a la tabla

El especificador de formato de fecha completa y hora larga ("F")


El especificador de formato estándar "F" representa una cadena de formato de fecha y hora personalizado que
está definida por la propiedad DateTimeFormatInfo.FullDateTimePattern actual. Por ejemplo, la cadena de
formato personalizado para la referencia cultural de todos los idiomas es "dddd, dd MMMM yyyy HH:mm:ss".
En la tabla siguiente se enumeran las propiedades del objeto DateTimeFormatInfo que pueden controlar el
formato de la cadena devuelta. El especificador de formato personalizado devuelto por la propiedad
FullDateTimePattern de algunas referencias culturales quizás no use todas las propiedades.

P RO P IEDA D. DESC RIP C IÓ N

FullDateTimePattern Define el formato global de la cadena de resultado.

DayNames Define los nombres de días traducidos que pueden aparecer


en la cadena de resultado.

MonthNames Define los nombres de meses traducidos que pueden


aparecer en la cadena de resultado.

TimeSeparator Define la cadena que separa los componentes de hora,


minutos y segundos de una hora.

AMDesignator Define la cadena que indica las horas comprendidas desde


medianoche hasta antes del mediodía en un reloj de 12
horas.

PMDesignator Define la cadena que indica las horas comprendidas desde el


mediodía hasta antes de medianoche en un reloj de 12
horas.
En el ejemplo siguiente se usa el especificador de formato "F" para mostrar un valor de fecha y hora.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("F",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Thursday, April 10, 2008 6:30:00 AM
Console.WriteLine(date1.ToString("F",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays jeudi 10 avril 2008 06:30:00

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("F", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Thursday, April 10, 2008 6:30:00 AM
Console.WriteLine(date1.ToString("F", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays jeudi 10 avril 2008 06:30:00

Volver a la tabla

El especificador de formato de fecha general y hora corta ("g")


El especificador de formato estándar "g" representa una combinación de los patrones de fecha corta ("d") y hora
corta ("t"), separados por un espacio.
La información de formato de un objeto DateTimeFormatInfo específico afecta a la cadena de resultado. En la
tabla siguiente se enumeran las propiedades del objeto DateTimeFormatInfo que pueden controlar el formato de
la cadena devuelta. El especificador de formato personalizado devuelto por las propiedades
DateTimeFormatInfo.ShortDatePattern y DateTimeFormatInfo.ShortTimePattern de algunas referencias
culturales quizás no use todas las propiedades.

P RO P IEDA D. DESC RIP C IÓ N

ShortDatePattern Define el formato del componente de fecha de la cadena de


resultado.

ShortTimePattern Define el formato del componente de hora de la cadena de


resultado.

DateSeparator Define la cadena que separa los componentes de año, mes y


día de una fecha.

TimeSeparator Define la cadena que separa los componentes de hora,


minutos y segundos de una hora.

AMDesignator Define la cadena que indica las horas comprendidas desde


medianoche hasta antes del mediodía en un reloj de 12
horas.

PMDesignator Define la cadena que indica las horas comprendidas desde el


mediodía hasta antes de medianoche en un reloj de 12
horas.

En el ejemplo siguiente se usa el especificador de formato "g" para mostrar un valor de fecha y hora.
DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);
Console.WriteLine(date1.ToString("g",
DateTimeFormatInfo.InvariantInfo));
// Displays 04/10/2008 06:30
Console.WriteLine(date1.ToString("g",
CultureInfo.CreateSpecificCulture("en-us")));
// Displays 4/10/2008 6:30 AM
Console.WriteLine(date1.ToString("g",
CultureInfo.CreateSpecificCulture("fr-BE")));
// Displays 10/04/2008 6:30

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("g", _
DateTimeFormatInfo.InvariantInfo))
' Displays 04/10/2008 06:30
Console.WriteLine(date1.ToString("g", _
CultureInfo.CreateSpecificCulture("en-us")))
' Displays 4/10/2008 6:30 AM
Console.WriteLine(date1.ToString("g", _
CultureInfo.CreateSpecificCulture("fr-BE")))
' Displays 10/04/2008 6:30

Volver a la tabla

El especificador de formato de fecha general y hora larga ("G")


El especificador de formato estándar "G" representa una combinación de los patrones de fecha corta ("d") y hora
larga ("T"), separados por un espacio.
La información de formato de un objeto DateTimeFormatInfo específico afecta a la cadena de resultado. En la
tabla siguiente se enumeran las propiedades del objeto DateTimeFormatInfo que pueden controlar el formato de
la cadena devuelta. El especificador de formato personalizado devuelto por las propiedades
DateTimeFormatInfo.ShortDatePattern y DateTimeFormatInfo.LongTimePattern de algunas referencias culturales
quizás no use todas las propiedades.

P RO P IEDA D. DESC RIP C IÓ N

ShortDatePattern Define el formato del componente de fecha de la cadena de


resultado.

LongTimePattern Define el formato del componente de hora de la cadena de


resultado.

DateSeparator Define la cadena que separa los componentes de año, mes y


día de una fecha.

TimeSeparator Define la cadena que separa los componentes de hora,


minutos y segundos de una hora.

AMDesignator Define la cadena que indica las horas comprendidas desde


medianoche hasta antes del mediodía en un reloj de 12
horas.

PMDesignator Define la cadena que indica las horas comprendidas desde el


mediodía hasta antes de medianoche en un reloj de 12
horas.
En el ejemplo siguiente se usa el especificador de formato "G" para mostrar un valor de fecha y hora.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("G",
DateTimeFormatInfo.InvariantInfo));
// Displays 04/10/2008 06:30:00
Console.WriteLine(date1.ToString("G",
CultureInfo.CreateSpecificCulture("en-us")));
// Displays 4/10/2008 6:30:00 AM
Console.WriteLine(date1.ToString("G",
CultureInfo.CreateSpecificCulture("nl-BE")));
// Displays 10/04/2008 6:30:00

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("G", _
DateTimeFormatInfo.InvariantInfo))
' Displays 04/10/2008 06:30:00
Console.WriteLine(date1.ToString("G", _
CultureInfo.CreateSpecificCulture("en-us")))
' Displays 4/10/2008 6:30:00 AM
Console.WriteLine(date1.ToString("G", _
CultureInfo.CreateSpecificCulture("nl-BE")))
' Displays 10/04/2008 6:30:00

Volver a la tabla

El especificador de formato de mes ("M", "m")


El especificador de formato estándar "M" o "m" representa una cadena de formato de fecha y hora personalizado
que está definida por la propiedad DateTimeFormatInfo.MonthDayPattern actual. Por ejemplo, la cadena de
formato personalizado para la referencia cultural de todos los idiomas es "MMMM dd".
En la tabla siguiente se enumeran las propiedades del objeto DateTimeFormatInfo que controlan el formato de la
cadena devuelta.

P RO P IEDA D. DESC RIP C IÓ N

MonthDayPattern Define el formato global de la cadena de resultado.

MonthNames Define los nombres de meses traducidos que pueden


aparecer en la cadena de resultado.

En el ejemplo siguiente se usa el especificador de formato "m" para mostrar un valor de fecha y hora.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("m",
CultureInfo.CreateSpecificCulture("en-us")));
// Displays April 10
Console.WriteLine(date1.ToString("m",
CultureInfo.CreateSpecificCulture("ms-MY")));
// Displays 10 April
Dim date1 As Date = #4/10/2008 6:30AM#
Console.WriteLine(date1.ToString("m", _
CultureInfo.CreateSpecificCulture("en-us")))
' Displays April 10
Console.WriteLine(date1.ToString("m", _
CultureInfo.CreateSpecificCulture("ms-MY")))
' Displays 10 April

Volver a la tabla

El especificador de formato de operación de ida y vuelta ("O", "o")


El especificador de formato estándar "O" u "o" representa una cadena de formato de fecha y hora personalizado
mediante un patrón que conserva la información de la zona horaria y emite una cadena de resultado que cumple
con la norma ISO 8601. En los valores DateTime, este especificador de formato está diseñado para conservar los
valores de fecha y hora junto con la propiedad DateTime.Kind en el texto. La cadena con formato se puede
recuperar usando el método DateTime.Parse(String, IFormatProvider, DateTimeStyles) o DateTime.ParseExact si el
parámetro styles está establecido en DateTimeStyles.RoundtripKind.
El especificador de formato estándar "O" u "o" corresponde a la cadena de formato personalizado "yyyy'-'MM'-
'dd'T'HH':'mm':'ss'.'fffffffK" para los valores DateTime y a "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffzzz" para los
valores DateTimeOffset. En esta cadena, los pares de comillas que delimitan los caracteres individuales (como
guiones, signos de dos puntos y la letra "T") indican que el carácter individual es un literal que no se puede
cambiar. Los apóstrofos no aparecen en la cadena de salida.
El especificador de formato estándar "O" u "o" (y la cadena de formato personalizado "yyyy'-'MM'-
'dd'T'HH':'mm':'ss'.'fffffffK") aprovecha los tres modos en que se representa la información de zona horaria en
ISO 8601 para conservar la propiedad Kind de valores DateTime:
El componente de zona horaria de valores de fecha y hora DateTimeKind.Local es un desfase con respecto
a la hora UTC (por ejemplo, +01:00, -07:00). Todos los valores DateTimeOffset también se representan en
este formato.
El componente de zona horaria de valores de fecha y hora DateTimeKind.Utc usa "Z" (que es un desfase
cero) para representar la hora UTC.
Los valores de fecha y hora DateTimeKind.Unspecified no tienen información de zona horaria.
Dado que el especificador de formato estándar "O" u "o" se ajusta a un estándar internacional, la operación de
formato o análisis que utiliza el especificador siempre usa la referencia cultural de todos los idiomas y el
calendario gregoriano.
Las cadenas que se pasan a los métodos Parse , TryParse , ParseExact y TryParseExact de DateTime y
DateTimeOffset se pueden analizar con el especificador de formato "O" u "o" si están en uno de estos formatos.
En el caso de objetos DateTime, la sobrecarga de análisis a la que llama también debe incluir un parámetro
styles con un valor de DateTimeStyles.RoundtripKind. Observe que si llama a un método de análisis con la
cadena de formato personalizado que se corresponde con el especificador de formato "O" u "o", no obtendrá los
mismos resultados que "O" u "o". Esto se debe a que los métodos de análisis que usan una cadena de formato
personalizado no pueden analizar la representación de cadena de aquellos valores de fecha y hora que carecen
de un componente de zona horaria o que usan "Z" para indicar la hora UTC.
En el ejemplo siguiente se utiliza el especificador de formato "o" para mostrar una serie de valores DateTime y
un valor DateTimeOffset en un sistema de la zona horaria del Pacífico de EE. UU.
using System;

public class Example


{
public static void Main()
{
DateTime dat = new DateTime(2009, 6, 15, 13, 45, 30,
DateTimeKind.Unspecified);
Console.WriteLine("{0} ({1}) --> {0:O}", dat, dat.Kind);

DateTime uDat = new DateTime(2009, 6, 15, 13, 45, 30,


DateTimeKind.Utc);
Console.WriteLine("{0} ({1}) --> {0:O}", uDat, uDat.Kind);

DateTime lDat = new DateTime(2009, 6, 15, 13, 45, 30,


DateTimeKind.Local);
Console.WriteLine("{0} ({1}) --> {0:O}\n", lDat, lDat.Kind);

DateTimeOffset dto = new DateTimeOffset(lDat);


Console.WriteLine("{0} --> {0:O}", dto);
}
}
// The example displays the following output:
// 6/15/2009 1:45:30 PM (Unspecified) --> 2009-06-15T13:45:30.0000000
// 6/15/2009 1:45:30 PM (Utc) --> 2009-06-15T13:45:30.0000000Z
// 6/15/2009 1:45:30 PM (Local) --> 2009-06-15T13:45:30.0000000-07:00
//
// 6/15/2009 1:45:30 PM -07:00 --> 2009-06-15T13:45:30.0000000-07:00

Module Example
Public Sub Main()
Dim dat As New Date(2009, 6, 15, 13, 45, 30,
DateTimeKind.Unspecified)
Console.WriteLine("{0} ({1}) --> {0:O}", dat, dat.Kind)

Dim uDat As New Date(2009, 6, 15, 13, 45, 30, DateTimeKind.Utc)


Console.WriteLine("{0} ({1}) --> {0:O}", uDat, uDat.Kind)

Dim lDat As New Date(2009, 6, 15, 13, 45, 30, DateTimeKind.Local)


Console.WriteLine("{0} ({1}) --> {0:O}", lDat, lDat.Kind)
Console.WriteLine()

Dim dto As New DateTimeOffset(lDat)


Console.WriteLine("{0} --> {0:O}", dto)
End Sub
End Module
' The example displays the following output:
' 6/15/2009 1:45:30 PM (Unspecified) --> 2009-06-15T13:45:30.0000000
' 6/15/2009 1:45:30 PM (Utc) --> 2009-06-15T13:45:30.0000000Z
' 6/15/2009 1:45:30 PM (Local) --> 2009-06-15T13:45:30.0000000-07:00
'
' 6/15/2009 1:45:30 PM -07:00 --> 2009-06-15T13:45:30.0000000-07:00

En el ejemplo siguiente se usa el especificador de formato "o" para crear una cadena con formato y, a
continuación, se restaura el valor de fecha y hora original llamando a un método Parse de fecha y hora.
// Round-trip DateTime values.
DateTime originalDate, newDate;
string dateString;
// Round-trip a local time.
originalDate = DateTime.SpecifyKind(new DateTime(2008, 4, 10, 6, 30, 0), DateTimeKind.Local);
dateString = originalDate.ToString("o");
newDate = DateTime.Parse(dateString, null, DateTimeStyles.RoundtripKind);
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind,
newDate, newDate.Kind);
// Round-trip a UTC time.
originalDate = DateTime.SpecifyKind(new DateTime(2008, 4, 12, 9, 30, 0), DateTimeKind.Utc);
dateString = originalDate.ToString("o");
newDate = DateTime.Parse(dateString, null, DateTimeStyles.RoundtripKind);
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind,
newDate, newDate.Kind);
// Round-trip time in an unspecified time zone.
originalDate = DateTime.SpecifyKind(new DateTime(2008, 4, 13, 12, 30, 0), DateTimeKind.Unspecified);
dateString = originalDate.ToString("o");
newDate = DateTime.Parse(dateString, null, DateTimeStyles.RoundtripKind);
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind,
newDate, newDate.Kind);

// Round-trip a DateTimeOffset value.


DateTimeOffset originalDTO = new DateTimeOffset(2008, 4, 12, 9, 30, 0, new TimeSpan(-8, 0, 0));
dateString = originalDTO.ToString("o");
DateTimeOffset newDTO = DateTimeOffset.Parse(dateString, null, DateTimeStyles.RoundtripKind);
Console.WriteLine("Round-tripped {0} to {1}.", originalDTO, newDTO);
// The example displays the following output:
// Round-tripped 4/10/2008 6:30:00 AM Local to 4/10/2008 6:30:00 AM Local.
// Round-tripped 4/12/2008 9:30:00 AM Utc to 4/12/2008 9:30:00 AM Utc.
// Round-tripped 4/13/2008 12:30:00 PM Unspecified to 4/13/2008 12:30:00 PM Unspecified.
// Round-tripped 4/12/2008 9:30:00 AM -08:00 to 4/12/2008 9:30:00 AM -08:00.

' Round-trip DateTime values.


Dim originalDate, newDate As Date
Dim dateString As String
' Round-trip a local time.
originalDate = Date.SpecifyKind(#4/10/2008 6:30AM#, DateTimeKind.Local)
dateString = originalDate.ToString("o")
newDate = Date.Parse(dateString, Nothing, DateTimeStyles.RoundtripKind)
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind, _
newDate, newDate.Kind)
' Round-trip a UTC time.
originalDate = Date.SpecifyKind(#4/12/2008 9:30AM#, DateTimeKind.Utc)
dateString = originalDate.ToString("o")
newDate = Date.Parse(dateString, Nothing, DateTimeStyles.RoundtripKind)
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind, _
newDate, newDate.Kind)
' Round-trip time in an unspecified time zone.
originalDate = Date.SpecifyKind(#4/13/2008 12:30PM#, DateTimeKind.Unspecified)
dateString = originalDate.ToString("o")
newDate = Date.Parse(dateString, Nothing, DateTimeStyles.RoundtripKind)
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind, _
newDate, newDate.Kind)

' Round-trip a DateTimeOffset value.


Dim originalDTO As New DateTimeOffset(#4/12/2008 9:30AM#, New TimeSpan(-8, 0, 0))
dateString = originalDTO.ToString("o")
Dim newDTO As DateTimeOffset = DateTimeOffset.Parse(dateString, Nothing, DateTimeStyles.RoundtripKind)
Console.WriteLine("Round-tripped {0} to {1}.", originalDTO, newDTO)
' The example displays the following output:
' Round-tripped 4/10/2008 6:30:00 AM Local to 4/10/2008 6:30:00 AM Local.
' Round-tripped 4/12/2008 9:30:00 AM Utc to 4/12/2008 9:30:00 AM Utc.
' Round-tripped 4/13/2008 12:30:00 PM Unspecified to 4/13/2008 12:30:00 PM Unspecified.
' Round-tripped 4/12/2008 9:30:00 AM -08:00 to 4/12/2008 9:30:00 AM -08:00.
Volver a la tabla

El especificador de formato RFC1123 ("R", "r")


El especificador de formato estándar "R" o "r" representa una cadena de formato de fecha y hora personalizado
que está definida por la propiedad DateTimeFormatInfo.RFC1123Pattern. El patrón refleja una norma definida y
la propiedad es de solo lectura. Por consiguiente, siempre es el mismo, sea cual fuere la referencia cultural
utilizada o el proveedor de formato proporcionado. La cadena de formato personalizado es "ddd, dd MMM yyyy
HH':'mm':'ss 'GMT'". Cuando se utiliza este especificador de formato estándar, la operación de formato o análisis
utiliza siempre la referencia cultural de todos los idiomas.
Las siguientes propiedades del objeto DateTimeFormatInfo devuelto por la propiedad
DateTimeFormatInfo.InvariantInfo que representa la referencia cultural de todos los idiomas afectan a la cadena
de resultado.

P RO P IEDA D. DESC RIP C IÓ N

RFC1123Pattern Define el formato de la cadena de resultado.

AbbreviatedDayNames Define los nombres de días abreviados que pueden aparecer


en la cadena de resultado.

AbbreviatedMonthNames Define los nombres de meses abreviados que pueden


aparecer en la cadena de resultado.

Aunque la norma RFC 1123 expresa una hora según la hora universal coordinada (hora UTC), la operación de
formato no modifica el valor del objeto DateTime al que se está dando formato. Por consiguiente, debe convertir
el valor DateTime en una hora UTC llamando al método DateTime.ToUniversalTime antes de realizar la operación
de formato. En cambio, los valores DateTimeOffset realizan esta conversión automáticamente; no es necesario
llamar al método DateTimeOffset.ToUniversalTime antes de la operación de formato.
En el ejemplo siguiente se utiliza el especificador de formato "r" para mostrar un DateTime y un
valor DateTimeOffset en un sistema de la zona horaria del Pacífico de EE. UU.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


DateTimeOffset dateOffset = new DateTimeOffset(date1,
TimeZoneInfo.Local.GetUtcOffset(date1));
Console.WriteLine(date1.ToUniversalTime().ToString("r"));
// Displays Thu, 10 Apr 2008 13:30:00 GMT
Console.WriteLine(dateOffset.ToUniversalTime().ToString("r"));
// Displays Thu, 10 Apr 2008 13:30:00 GMT

Dim date1 As Date = #4/10/2008 6:30AM#


Dim dateOffset As New DateTimeOffset(date1, TimeZoneInfo.Local.GetUtcOFfset(date1))
Console.WriteLine(date1.ToUniversalTime.ToString("r"))
' Displays Thu, 10 Apr 2008 13:30:00 GMT
Console.WriteLine(dateOffset.ToUniversalTime.ToString("r"))
' Displays Thu, 10 Apr 2008 13:30:00 GMT

Volver a la tabla

El especificador de formato que se puede ordenar ("s")


El especificador de formato estándar "s" representa una cadena de formato de fecha y hora personalizado que
está definida por la propiedad DateTimeFormatInfo.SortableDateTimePattern. El patrón refleja una norma
definida (ISO 8601) y la propiedad es de solo lectura. Por consiguiente, siempre es el mismo, sea cual fuere la
referencia cultural utilizada o el proveedor de formato proporcionado. La cadena de formato personalizado es
"yyyy'-'MM'-'dd'T'HH':'mm':'ss".
La finalidad del especificador de formato "s" es generar cadenas de resultado de forma coherente en orden
ascendente o descendente según los valores de fecha y hora. Como resultado, aunque el especificador de
formato estándar "s" representa un valor de fecha y hora en un formato coherente, la operación de formato no
modifica el valor del objeto de fecha y hora al que se está dando formato para reflejar su propiedad
DateTime.Kind o su valor DateTimeOffset.Offset. Por ejemplo, las cadenas de resultado que se producen al dar
formato a los valores de fecha y hora 2014-11-15T18:32:17+00:00 y 2014-11-15T18:32:17+08:00 son idénticas.
Cuando se utiliza este especificador de formato estándar, la operación de formato o análisis utiliza siempre la
referencia cultural de todos los idiomas.
En el ejemplo siguiente se utiliza el especificador de formato "s" para mostrar un valor DateTime y
DateTimeOffset en un sistema de la zona horaria del Pacífico de EE. UU.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("s"));
// Displays 2008-04-10T06:30:00

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("s"))
' Displays 2008-04-10T06:30:00

Volver a la tabla

El especificador de formato de hora corta ("t")


El especificador de formato estándar "t" representa una cadena de formato de fecha y hora personalizado que
está definida por la propiedad DateTimeFormatInfo.ShortTimePattern actual. Por ejemplo, la cadena de formato
personalizado para la referencia cultural de todos los idiomas es "HH:mm".
La información de formato de un objeto DateTimeFormatInfo específico afecta a la cadena de resultado. En la
tabla siguiente se enumeran las propiedades del objeto DateTimeFormatInfo que pueden controlar el formato de
la cadena devuelta. El especificador de formato personalizado devuelto por la propiedad
DateTimeFormatInfo.ShortTimePattern de algunas referencias culturales quizás no use todas las propiedades.

P RO P IEDA D. DESC RIP C IÓ N

ShortTimePattern Define el formato del componente de hora de la cadena de


resultado.

TimeSeparator Define la cadena que separa los componentes de hora,


minutos y segundos de una hora.

AMDesignator Define la cadena que indica las horas comprendidas desde


medianoche hasta antes del mediodía en un reloj de 12
horas.

PMDesignator Define la cadena que indica las horas comprendidas desde el


mediodía hasta antes de medianoche en un reloj de 12
horas.

En el ejemplo siguiente se usa el especificador de formato "t" para mostrar un valor de fecha y hora.
DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);
Console.WriteLine(date1.ToString("t",
CultureInfo.CreateSpecificCulture("en-us")));
// Displays 6:30 AM
Console.WriteLine(date1.ToString("t",
CultureInfo.CreateSpecificCulture("es-ES")));
// Displays 6:30

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("t", _
CultureInfo.CreateSpecificCulture("en-us")))
' Displays 6:30 AM
Console.WriteLine(date1.ToString("t", _
CultureInfo.CreateSpecificCulture("es-ES")))
' Displays 6:30

Volver a la tabla

El especificador de formato de hora larga ("T")


El especificador de formato estándar "T" representa una cadena de formato de fecha y hora personalizado que
está definida por la propiedad DateTimeFormatInfo.LongTimePattern de una referencia cultural concreta. Por
ejemplo, la cadena de formato personalizado para la referencia cultural de todos los idiomas es "HH:mm:ss".
En la tabla siguiente se enumeran las propiedades del objeto DateTimeFormatInfo que pueden controlar el
formato de la cadena devuelta. El especificador de formato personalizado devuelto por la propiedad
DateTimeFormatInfo.LongTimePattern de algunas referencias culturales quizás no use todas las propiedades.

P RO P IEDA D. DESC RIP C IÓ N

LongTimePattern Define el formato del componente de hora de la cadena de


resultado.

TimeSeparator Define la cadena que separa los componentes de hora,


minutos y segundos de una hora.

AMDesignator Define la cadena que indica las horas comprendidas desde


medianoche hasta antes del mediodía en un reloj de 12
horas.

PMDesignator Define la cadena que indica las horas comprendidas desde el


mediodía hasta antes de medianoche en un reloj de 12
horas.

En el ejemplo siguiente se usa el especificador de formato "T" para mostrar un valor de fecha y hora.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("T",
CultureInfo.CreateSpecificCulture("en-us")));
// Displays 6:30:00 AM
Console.WriteLine(date1.ToString("T",
CultureInfo.CreateSpecificCulture("es-ES")));
// Displays 6:30:00
Dim date1 As Date = #4/10/2008 6:30AM#
Console.WriteLine(date1.ToString("T", _
CultureInfo.CreateSpecificCulture("en-us")))
' Displays 6:30:00 AM
Console.WriteLine(date1.ToString("T", _
CultureInfo.CreateSpecificCulture("es-ES")))
' Displays 6:30:00

Volver a la tabla

El especificador de formato universal que se puede ordenar ("u")


El especificador de formato estándar "u" representa una cadena de formato de fecha y hora personalizado que
está definida por la propiedad DateTimeFormatInfo.UniversalSortableDateTimePattern. El patrón refleja una
norma definida y la propiedad es de solo lectura. Por consiguiente, siempre es el mismo, sea cual fuere la
referencia cultural utilizada o el proveedor de formato proporcionado. La cadena de formato personalizado es
"yyyy'-'MM'-'dd HH':'mm':'ss'Z'". Cuando se utiliza este especificador de formato estándar, la operación de
formato o análisis utiliza siempre la referencia cultural de todos los idiomas.
Aunque la cadena de resultado debe expresar una hora como una hora universal coordinada (hora UTC), no se
realiza ninguna conversión del valor DateTime original durante la operación de formato. Por consiguiente, debe
convertir un valor DateTime en una hora UTC llamando al método DateTime.ToUniversalTime antes de aplicarle
formato. En cambio, los valores DateTimeOffset realizan esta conversión automáticamente; no es necesario
llamar al método DateTimeOffset.ToUniversalTime antes de la operación de formato.
En el ejemplo siguiente se usa el especificador de formato "u" para mostrar un valor de fecha y hora.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToUniversalTime().ToString("u"));
// Displays 2008-04-10 13:30:00Z

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToUniversalTime.ToString("u"))
' Displays 2008-04-10 13:30:00Z

Volver a la tabla

El especificador de formato completo universal ("U")


El especificador de formato estándar "U" representa una cadena de formato de fecha y hora personalizado que
está definida por la propiedad DateTimeFormatInfo.FullDateTimePattern de una referencia cultural especificada.
El patrón es igual que el patrón de "F". Sin embargo, el valor DateTime se convierte automáticamente en una
hora UTC antes de darle formato.
En la tabla siguiente se enumeran las propiedades del objeto DateTimeFormatInfo que pueden controlar el
formato de la cadena devuelta. El especificador de formato personalizado devuelto por la propiedad
FullDateTimePattern de algunas referencias culturales quizás no use todas las propiedades.

P RO P IEDA D. DESC RIP C IÓ N

FullDateTimePattern Define el formato global de la cadena de resultado.

DayNames Define los nombres de días traducidos que pueden aparecer


en la cadena de resultado.
P RO P IEDA D. DESC RIP C IÓ N

MonthNames Define los nombres de meses traducidos que pueden


aparecer en la cadena de resultado.

TimeSeparator Define la cadena que separa los componentes de hora,


minutos y segundos de una hora.

AMDesignator Define la cadena que indica las horas comprendidas desde


medianoche hasta antes del mediodía en un reloj de 12
horas.

PMDesignator Define la cadena que indica las horas comprendidas desde el


mediodía hasta antes de medianoche en un reloj de 12
horas.

El especificador de formato "U" no es compatible con el tipo DateTimeOffset y provoca una excepción
FormatException si se usa para dar formato a un valor DateTimeOffset.
En el ejemplo siguiente se usa el especificador de formato "U" para mostrar un valor de fecha y hora.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("U",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Thursday, April 10, 2008 1:30:00 PM
Console.WriteLine(date1.ToString("U",
CultureInfo.CreateSpecificCulture("sv-FI")));
// Displays den 10 april 2008 13:30:00

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("U", CultureInfo.CreateSpecificCulture("en-US")))
' Displays Thursday, April 10, 2008 1:30:00 PM
Console.WriteLine(date1.ToString("U", CultureInfo.CreateSpecificCulture("sv-FI")))
' Displays den 10 april 2008 13:30:00

Volver a la tabla

El especificador de formato de mes y año ("Y", "y")


El especificador de formato estándar "Y" o "y" representa una cadena de formato de fecha y hora personalizado
que está definida por la propiedad DateTimeFormatInfo.YearMonthPattern de una referencia cultural
especificada. Por ejemplo, la cadena de formato personalizado para la referencia cultural de todos los idiomas es
"yyyy MMMM".
En la tabla siguiente se enumeran las propiedades del objeto DateTimeFormatInfo que controlan el formato de la
cadena devuelta.

P RO P IEDA D. DESC RIP C IÓ N

YearMonthPattern Define el formato global de la cadena de resultado.

MonthNames Define los nombres de meses traducidos que pueden


aparecer en la cadena de resultado.

En el ejemplo siguiente se usa el especificador de formato "y" para mostrar un valor de fecha y hora.
DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);
Console.WriteLine(date1.ToString("Y",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays April, 2008
Console.WriteLine(date1.ToString("y",
CultureInfo.CreateSpecificCulture("af-ZA")));
// Displays April 2008

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("Y", CultureInfo.CreateSpecificCulture("en-US")))
' Displays April, 2008
Console.WriteLine(date1.ToString("y", CultureInfo.CreateSpecificCulture("af-ZA")))
' Displays April 2008

Volver a la tabla

Notas
Configuración del Panel de control
Los valores de configuración del elemento Configuración regional y de idioma del Panel de control influyen
en la cadena de resultado generada por una operación de formato. Estas configuraciones se utilizan para
inicializar el objeto DateTimeFormatInfo asociado a la referencia cultural del subproceso actual, que proporciona
valores que se utilizan para controlar el formato. Los equipos que usan configuraciones diferentes generarán
cadenas de resultado distintas.
Asimismo, si se usa el constructor CultureInfo(String) para crear instancias de un nuevo objeto CultureInfo que
representa la misma referencia cultural que la referencia cultural del sistema actual, cualquier personalización
establecida por el elemento Configuración regional y de idioma del Panel de control se aplicará al nuevo
objeto CultureInfo . Puede usar el constructor CultureInfo(String, Boolean) para crear un objeto CultureInfo que
no refleje las personalizaciones de un sistema.
Propiedades de DateTimeFormatInfo
El formato se ve influenciado por las propiedades del objeto DateTimeFormatInfo actual, proporcionado
implícitamente por la referencia cultural del subproceso actual o explícitamente por el parámetro
IFormatProvider del método que invoca el formato. En el parámetro IFormatProvider, la aplicación debe
especificar un objeto CultureInfo, que representa una referencia cultural, o un objeto DateTimeFormatInfo, que
representa las convenciones de formato de fecha y hora de una determinada referencia cultural. Muchos de los
especificadores de formato de fecha y hora estándar son alias de patrones de formato definidos en las
propiedades del objeto DateTimeFormatInfo actual. La aplicación puede modificar el resultado generado por
algunos especificadores de formato de fecha y hora estándar al cambiar los patrones de formato de fecha y hora
correspondientes de la propiedad DateTimeFormatInfo apropiada.

Vea también
System.DateTime
System.DateTimeOffset
Aplicación de formato a tipos
Cadenas con formato de fecha y hora personalizado
Ejemplo: Utilidad de formato WinForms de .NET Core (C#)
Ejemplo: Utilidad de formato WinForms de .NET Core (Visual Basic)
Cadenas con formato de fecha y hora
personalizado
16/09/2020 • 96 minutes to read • Edit Online

Una cadena con formato de fecha y hora define la representación de texto de un valor DateTime o
DateTimeOffset que es el resultado de una operación de formato. También puede definir la representación de un
valor de fecha y hora que se necesite en una operación de análisis para convertir correctamente la cadena en
una fecha y hora. Una cadena de formato personalizado consta de uno o varios especificadores de formato de
fecha y hora personalizado. Una cadena que no sea una cadena con formato de fecha y hora estándar se
interpreta como una cadena con formato de fecha y hora personalizado.

TIP
Puede descargar la Utilidad de formato , que es una aplicación de .NET Core Windows Forms que permite aplicar
cadenas de formato a valores numéricos o de fecha y hora, y que muestra la cadena de resultado. El código fuente está
disponible para C# y Visual Basic.

Las cadenas con formato de fecha y hora personalizado se pueden utilizar tanto con valores DateTime como con
valores DateTimeOffset.

NOTE
Algunos de los ejemplos de C# de este artículo se ejecutan en el ejecutor de código en línea y área de juegos de Try.NET.
Haga clic en el botón Ejecutar para ejecutar un ejemplo en una ventana interactiva. Una vez que se ejecuta el código,
puede modificar y ejecutar el código modificado si vuelve a hacer clic en Ejecutar . El código modificado se ejecuta en la
ventana interactiva o, si se produce un error en la compilación, en la ventana interactiva se muestran todos los mensajes
de error del compilador de C#.
La zona horaria local del ejecutor de código en línea de Try.NET y del área de juegos es la hora universal coordinada o UTC.
Esto puede afectar al comportamiento y la salida de ejemplos que ilustran los tipos DateTime, DateTimeOffset y
TimeZoneInfo y sus miembros.

En operaciones de formato, las cadenas de formato de fecha y hora personalizado se pueden usar con el método
ToString de una instancia de fecha y hora o con un método que admita formato compuesto. En el ejemplo
siguiente se muestran ambos usos.

DateTime thisDate1 = new DateTime(2011, 6, 10);


Console.WriteLine("Today is " + thisDate1.ToString("MMMM dd, yyyy") + ".");

DateTimeOffset thisDate2 = new DateTimeOffset(2011, 6, 10, 15, 24, 16,


TimeSpan.Zero);
Console.WriteLine("The current date and time: {0:MM/dd/yy H:mm:ss zzz}",
thisDate2);
// The example displays the following output:
// Today is June 10, 2011.
// The current date and time: 06/10/11 15:24:16 +00:00
Dim thisDate1 As Date = #6/10/2011#
Console.WriteLine("Today is " + thisDate1.ToString("MMMM dd, yyyy") + ".")

Dim thisDate2 As New DateTimeOffset(2011, 6, 10, 15, 24, 16, TimeSpan.Zero)


Console.WriteLine("The current date and time: {0:MM/dd/yy H:mm:ss zzz}",
thisDate2)
' The example displays the following output:
' Today is June 10, 2011.
' The current date and time: 06/10/11 15:24:16 +00:00

En las operaciones de análisis, las cadenas de formato de fecha y hora personalizado se pueden usar con los
métodos DateTime.ParseExact, DateTime.TryParseExact, DateTimeOffset.ParseExact y
DateTimeOffset.TryParseExact. Estos métodos necesitan que una cadena de entrada se ajuste exactamente a un
modelo determinado para que la operación de análisis se realice correctamente. En el ejemplo siguiente se
muestra una llamada al método DateTimeOffset.ParseExact(String, String, IFormatProvider) para analizar una
fecha que debe incluir un día, un mes y un año de dos dígitos.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
string[] dateValues = { "30-12-2011", "12-30-2011",
"30-12-11", "12-30-11" };
string pattern = "MM-dd-yy";
DateTime parsedDate;

foreach (var dateValue in dateValues) {


if (DateTime.TryParseExact(dateValue, pattern, null,
DateTimeStyles.None, out parsedDate))
Console.WriteLine("Converted '{0}' to {1:d}.",
dateValue, parsedDate);
else
Console.WriteLine("Unable to convert '{0}' to a date and time.",
dateValue);
}
}
}
// The example displays the following output:
// Unable to convert '30-12-2011' to a date and time.
// Unable to convert '12-30-2011' to a date and time.
// Unable to convert '30-12-11' to a date and time.
// Converted '12-30-11' to 12/30/2011.
Imports System.Globalization

Module Example
Public Sub Main()
Dim dateValues() As String = {"30-12-2011", "12-30-2011",
"30-12-11", "12-30-11"}
Dim pattern As String = "MM-dd-yy"
Dim parsedDate As Date

For Each dateValue As String In dateValues


If DateTime.TryParseExact(dateValue, pattern, Nothing,
DateTimeStyles.None, parsedDate) Then
Console.WriteLine("Converted '{0}' to {1:d}.",
dateValue, parsedDate)
Else
Console.WriteLine("Unable to convert '{0}' to a date and time.",
dateValue)
End If
Next
End Sub
End Module
' The example displays the following output:
' Unable to convert '30-12-2011' to a date and time.
' Unable to convert '12-30-2011' to a date and time.
' Unable to convert '30-12-11' to a date and time.
' Converted '12-30-11' to 12/30/2011.

En la tabla siguiente se describen los especificadores de formato de fecha y hora personalizados, y se muestra la
cadena de resultado producida por cada especificador de formato. De forma predeterminada, las cadenas de
resultado reflejan las convenciones de formato de la referencia cultural en-us. Si un especificador de formato
determinado genera una cadena de resultado localizada, el ejemplo también indica la referencia cultural a la que
se aplica dicha cadena. Para más información sobre cómo usar cadenas de formato de fecha y hora
personalizado, vea la sección Notas.

ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO S

"d" El día del mes, de 1 a 31. 2009-06-01T13:45:30 -> 1

Más información: Especificador de 2009-06-15T13:45:30 -> 15


formato personalizado "d".

"dd" El día del mes, de 01 a 31. 2009-06-01T13:45:30 -> 01

Más información: Especificador de 2009-06-15T13:45:30 -> 15


formato personalizado "dd".

"ddd" El nombre abreviado del día de la 2009-06-15T13:45:30 -> Mon (en-


semana. US)

Más información: Especificador de 2009-06-15T13:45:30 -> Пн (ru-RU)


formato personalizado "ddd".
2009-06-15T13:45:30 -> lun. (fr-FR)

"dddd" El nombre completo del día de la 2009-06-15T13:45:30 -> Monday


semana. (en-US)

Más información: Especificador de 2009-06-15T13:45:30 ->


formato personalizado "dddd". понедельник (ru-RU)

2009-06-15T13:45:30 -> lundi (fr-FR)


ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO S

"f" Las décimas de segundo de un valor 2009-06-15T13:45:30.6170000 -> 6


de fecha y hora.
2009-06-15T13:45:30.05 -> 0
Más información: Especificador de
formato personalizado "f".

"ff" Las centésimas de segundo de un 2009-06-15T13:45:30.6170000 -> 61


valor de fecha y hora.
2009-06-15T13:45:30.0050000 -> 00
Más información: Especificador de
formato personalizado "ff".

"fff" Los milisegundos de un valor de fecha 6/15/2009 13:45:30.617 -> 617


y hora.
6/15/2009 13:45:30.0005 -> 000
Más información: Especificador de
formato personalizado "fff".

"ffff" Las diezmilésimas de segundo de un 2009-06-15T13:45:30.6175000 ->


valor de fecha y hora. 6175

Más información: Especificador de 2009-06-15T13:45:30.0000500 ->


formato personalizado "ffff". 0000

"fffff" Las cienmilésimas de segundo de un 2009-06-15T13:45:30.6175400 ->


valor de fecha y hora. 61754

Más información: Especificador de 6/15/2009 13:45:30.000005 -> 00000


formato personalizado "fffff".

"ffffff" Las millonésimas de segundo de un 2009-06-15T13:45:30.6175420 ->


valor de fecha y hora. 617542

Más información: Especificador de 2009-06-15T13:45:30.0000005 ->


formato personalizado "ffffff". 000000

"fffffff" Las diezmillonésimas de segundo de 2009-06-15T13:45:30.6175425 ->


un valor de fecha y hora. 6175425

Más información: Especificador de 2009-06-15T13:45:30.0001150 ->


formato personalizado "fffffff". 0001150

"F" Si es distinto de cero, las décimas de 2009-06-15T13:45:30.6170000 -> 6


segundo de un valor de fecha y hora.
2009-06-15T13:45:30.0500000 ->
Más información: Especificador de (ninguna salida)
formato personalizado "F".

"FF" Si es distinto de cero, las centésimas de 2009-06-15T13:45:30.6170000 -> 61


segundo de un valor de fecha y hora.
2009-06-15T13:45:30.0050000 ->
Más información: Especificador de (ninguna salida)
formato personalizado "FF".
ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO S

"FFF" Si es distinto de cero, los milisegundos 2009-06-15T13:45:30.6170000 ->


de un valor de fecha y hora. 617

Más información: Especificador de 2009-06-15T13:45:30.0005000 ->


formato personalizado "FFF". (ninguna salida)

"FFFF" Si es distinto de cero, las diezmilésimas 2009-06-15T13:45:30.5275000 ->


de segundo de un valor de fecha y 5275
hora.
2009-06-15T13:45:30.0000500 ->
Más información: Especificador de (ninguna salida)
formato personalizado "FFFF".

"FFFFF" Si es distinto de cero, las cienmilésimas 2009-06-15T13:45:30.6175400 ->


de segundo de un valor de fecha y 61754
hora.
2009-06-15T13:45:30.0000050 ->
Más información: Especificador de (ninguna salida)
formato personalizado "FFFFF".

"FFFFFF" Si es distinto de cero, las millonésimas 2009-06-15T13:45:30.6175420 ->


de segundo de un valor de fecha y 617542
hora.
2009-06-15T13:45:30.0000050 ->
Más información: Especificador de (ninguna salida)
formato personalizado "FFFFFF".

"FFFFFFF" Si es distinto de cero, las 2009-06-15T13:45:30.6175425 ->


diezmillonésimas de segundo de un 6175425
valor de fecha y hora.
2009-06-15T13:45:30.0001150 ->
Más información: Especificador de 000115
formato personalizado "FFFFFFF".

"g", "gg" El período o la era. 2009-06-15T13:45:30.6170000 ->


A.D.
Más información: Especificador de
formato personalizado "g" o "gg".

"h" La hora, usando un reloj de 12 horas 2009-06-15T01:45:30 -> 1


de 1 a 12.
2009-06-15T13:45:30 -> 1
Más información: Especificador de
formato personalizado "h".

"hh" La hora, usando un reloj de 12 horas 2009-06-15T01:45:30 -> 01


de 01 a 12.
2009-06-15T13:45:30 -> 01
Más información: Especificador de
formato personalizado "hh".

"H" La hora, usando un reloj de 24 horas 2009-06-15T01:45:30 -> 1


de 0 a 23.
2009-06-15T13:45:30 -> 13
Más información: Especificador de
formato personalizado "H".
ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO S

"HH" La hora, usando un reloj de 24 horas 2009-06-15T01:45:30 -> 01


de 00 a 23.
2009-06-15T13:45:30 -> 13
Más información: Especificador de
formato personalizado "HH".

"K" Información de la zona horaria. Con valores DateTime:

Más información: Especificador de 2009-06-15T13:45:30, Kind


formato personalizado "K". Unspecified ->

2009-06-15T13:45:30, Kind Utc -> Z

2009-06-15T13:45:30, Kind Local -> -


07:00 (depende de la configuración del
equipo local)

Con valores DateTimeOffset:

2009-06-15T01:45:30-07:00 --> -
07:00

2009-06-15T08:45:30+00:00 -->
+00:00

"m" Minutos, de 0 a 59. 2009-06-15T01:09:30 -> 9

Más información: Especificador de 2009-06-15T13:29:30 -> 29


formato personalizado "m".

"mm" El minuto, de 00 a 59. 2009-06-15T01:09:30 -> 09

Más información: Especificador de 2009-06-15T01:45:30 -> 45


formato personalizado "mm".

"M" El mes, de 1 a 12. 2009-06-15T13:45:30 -> 6

Más información: Especificador de


formato personalizado "M".

"MM" El mes, de 01 a 12. 2009-06-15T13:45:30 -> 06

Más información: Especificador de


formato personalizado "MM".

"MMM" El nombre abreviado del mes. 2009-06-15T13:45:30 -> Jun (en-US)

Más información: Especificador de 2009-06-15T13:45:30 -> juin (fr-FR)


formato personalizado "MMM".
2009-06-15T13:45:30 -> Jun (zu-ZA)

"MMMM" El nombre completo del mes. 2009-06-15T13:45:30 -> June (en-US)

Más información: Especificador de 2009-06-15T13:45:30 -> juni (da-DK)


formato personalizado "MMMM".
2009-06-15T13:45:30 -> uJuni (zu-
ZA)
ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO S

"s" El segundo, de 0 a 59. 2009-06-15T13:45:09 -> 9

Más información: Especificador de


formato personalizado "s".

"ss" El segundo, de 00 a 59. 2009-06-15T13:45:09 -> 09

Más información: Especificador de


formato personalizado "ss".

"t" El primer carácter del designador 2009-06-15T13:45:30 -> P (en-US)


AM/PM.
2009-06-15T13:45:30 -> 午 (ja-JP)
Más información: Especificador de
formato personalizado "t". 2009-06-15T13:45:30 -> (fr-FR)

"tt" El designador AM/PM. 2009-06-15T13:45:30 -> PM (en-US)

Más información: Especificador de 2009-06-15T13:45:30 -> 午後 (ja-JP)


formato personalizado "tt".
2009-06-15T13:45:30 -> (fr-FR)

"y" El año, de 0 a 99. 0001-01-01T00:00:00 -> 1

Más información: Especificador de 0900-01-01T00:00:00 -> 0


formato personalizado "y".
1900-01-01T00:00:00 -> 0

2009-06-15T13:45:30 -> 9

2019-06-15T13:45:30 -> 19

"yy" El año, de 00 a 99. 0001-01-01T00:00:00 -> 01

Más información: Especificador de 0900-01-01T00:00:00 -> 00


formato personalizado "yy".
1900-01-01T00:00:00 -> 00

2019-06-15T13:45:30 -> 19

"yyy" El año, con un mínimo de tres dígitos. 0001-01-01T00:00:00 -> 001

Más información: Especificador de 0900-01-01T00:00:00 -> 900


formato personalizado "yyy".
1900-01-01T00:00:00 -> 1900

2009-06-15T13:45:30 -> 2009

"yyyy" El año como un número de cuatro 0001-01-01T00:00:00 -> 0001


dígitos.
0900-01-01T00:00:00 -> 0900
Más información: Especificador de
formato personalizado "yyyy". 1900-01-01T00:00:00 -> 1900

2009-06-15T13:45:30 -> 2009


ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO S

"yyyyy" El año como un número de cinco 0001-01-01T00:00:00 -> 00001


dígitos.
2009-06-15T13:45:30 -> 02009
Más información: Especificador de
formato personalizado "yyyyy".

"z" Desfase de horas con respecto a la 2009-06-15T13:45:30-07:00 -> -7


hora UTC, sin ceros iniciales.

Más información: Especificador de


formato personalizado "z".

"zz" Desfase de horas con respecto a la 2009-06-15T13:45:30-07:00 -> -07


hora UTC, con un cero inicial para un
valor de un solo dígito.

Más información: Especificador de


formato personalizado "zz".

"zzz" Desfase de horas y minutos con 2009-06-15T13:45:30-07:00 -> -


respecto a la hora UTC. 07:00

Más información: Especificador de


formato personalizado "zzz".

":" El separador de hora. 2009-06-15T13:45:30 -> : (en-US)

Más información: Especificador de 2009-06-15T13:45:30 -> . (it-IT)


formato personalizado ":".
2009-06-15T13:45:30 -> : (ja-JP)

"/" El separador de fecha. 2009-06-15T13:45:30 -> / (en-US)

Más información: Especificador de 2009-06-15T13:45:30 -> - (ar-DZ)


formato personalizado "/".
2009-06-15T13:45:30 -> . (tr-TR)

"cadena" Delimitador de cadena literal. 2009-06-15T13:45:30 ("arr:" h:m t) ->


arr: 1:45 P
'cadena' Más información: Literales de carácter.
2009-06-15T13:45:30 ('arr:' h:m t) ->
arr: 1:45 P

% Define el siguiente carácter como un 2009-06-15T13:45:30 (%h) -> 1


especificador de formato
personalizado.

Más información:Usar especificadores


de formato personalizado únicos.

\ El carácter de escape. 2009-06-15T13:45:30 (h \h) -> 1 h

Más información: Literales de


caracteres y Usar el carácter de escape.
ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO S

Cualquier otro carácter El carácter se copia en la cadena de 2009-06-15T01:45:30 (arr hh:mm t) -


resultado sin modificar. > arr 01:45 A

Más información: Literales de carácter.

En las secciones siguientes se proporciona información adicional sobre cada especificador de formato de fecha y
hora personalizado. A menos que se indique lo contrario, cada especificador genera una representación de
cadena idéntica independientemente de que se use con un valor DateTime o DateTimeOffset.

Especificador de formato de día "d"


Especificador de formato personalizado "d"
El especificador de formato personalizado "d" representa el día del mes como un número de 1 a 31. Un día con
un solo dígito tiene un formato sin un cero inicial.
Si el especificador de formato "d" se usa sin otros especificadores de formato personalizado, se interpretará
como el especificador de formato de fecha y hora estándar "d". Para más información sobre cómo usar un
especificador de formato único, vea Usar especificadores de formato personalizado únicos más adelante en este
artículo.
En el ejemplo siguiente se incluye el especificador de formato personalizado "d" en varias cadenas de formato.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15);

Console.WriteLine(date1.ToString("d, M",
CultureInfo.InvariantCulture));
// Displays 29, 8

Console.WriteLine(date1.ToString("d MMMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays 29 August
Console.WriteLine(date1.ToString("d MMMM",
CultureInfo.CreateSpecificCulture("es-MX")));
// Displays 29 agosto

Dim date1 As Date = #08/29/2008 7:27:15PM#

Console.WriteLine(date1.ToString("d, M", _
CultureInfo.InvariantCulture))
' Displays 29, 8

Console.WriteLine(date1.ToString("d MMMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays 29 August
Console.WriteLine(date1.ToString("d MMMM", _
CultureInfo.CreateSpecificCulture("es-MX")))
' Displays 29 agosto

Volver a la tabla
Especificador de formato personalizado "dd"
La cadena de formato personalizado "dd" representa el día del mes como un número de 01 a 31. Un día con un
solo dígito tiene un formato con un cero inicial.
En el ejemplo siguiente se incluye el especificador de formato personalizado "dd" en una cadena de formato
personalizado.
DateTime date1 = new DateTime(2008, 1, 2, 6, 30, 15);

Console.WriteLine(date1.ToString("dd, MM",
CultureInfo.InvariantCulture));
// 02, 01

Dim date1 As Date = #1/2/2008 6:30:15AM#

Console.WriteLine(date1.ToString("dd, MM", _
CultureInfo.InvariantCulture))
' 02, 01

Volver a la tabla
Especificador de formato personalizado "ddd"
El especificador de formato personalizado "ddd" representa el nombre abreviado del día de la semana. El
nombre abreviado adaptado del día de la semana se recupera de la propiedad
DateTimeFormatInfo.AbbreviatedDayNames de la referencia cultural actual o especificada.
En el ejemplo siguiente se incluye el especificador de formato personalizado "ddd" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15);

Console.WriteLine(date1.ToString("ddd d MMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Fri 29 Aug
Console.WriteLine(date1.ToString("ddd d MMM",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays ven. 29 août

Dim date1 As Date = #08/29/2008 7:27:15PM#

Console.WriteLine(date1.ToString("ddd d MMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Fri 29 Aug
Console.WriteLine(date1.ToString("ddd d MMM", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays ven. 29 août

Volver a la tabla
Especificador de formato personalizado "dddd"
El especificador de formato personalizado "dddd" (más cualquier número de especificadores "d" adicionales)
representa el nombre completo del día de la semana. El nombre adaptado del día de la semana se recupera de la
propiedad DateTimeFormatInfo.DayNames de la referencia cultural actual o especificada.
En el ejemplo siguiente se incluye el especificador de formato personalizado "dddd" en una cadena de formato
personalizado.
DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15);

Console.WriteLine(date1.ToString("dddd dd MMMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Friday 29 August
Console.WriteLine(date1.ToString("dddd dd MMMM",
CultureInfo.CreateSpecificCulture("it-IT")));
// Displays venerdì 29 agosto

Dim date1 As Date = #08/29/2008 7:27:15PM#

Console.WriteLine(date1.ToString("dddd dd MMMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Friday 29 August
Console.WriteLine(date1.ToString("dddd dd MMMM", _
CultureInfo.CreateSpecificCulture("it-IT")))
' Displays venerdì 29 agosto

Volver a la tabla

Especificador de fracción de segundos en minúsculas "f"


Especificador de formato personalizado "f"
El especificador de formato personalizado "f" representa el dígito más significativo de la fracción de segundos;
es decir, representa las décimas de segundo de un valor de fecha y hora.
Si el especificador de formato "f" se usa sin otros especificadores de formato, se interpreta como el especificador
de formato de fecha y hora estándar "f". Para más información sobre cómo usar un especificador de formato
único, vea Usar especificadores de formato personalizado únicos más adelante en este artículo.
Cuando se usan especificadores de formato "f" como parte de una cadena de formato que se proporciona a los
métodos ParseExact, TryParseExact, ParseExact u TryParseExact, el número de especificadores de formato "f"
indica el número de dígitos más significativos de la fracción de segundos que debe haber presentes para
analizar la cadena correctamente.
En el ejemplo siguiente se incluye el especificador de formato personalizado "f" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);


CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018
Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)
Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Volver a la tabla
Especificador de formato personalizado "ff"
El especificador de formato personalizado "ff" representa los dos dígitos más significativos de la fracción de
segundos; es decir, representa las centésimas de segundo de un valor de fecha y hora.
En el ejemplo siguiente se incluye el especificador de formato personalizado "ff" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);


CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018

Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)


Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Volver a la tabla
Especificador de formato personalizado "fff"
El especificador de formato personalizado "fff" representa los tres dígitos más significativos de la fracción de
segundos; es decir, representa los milisegundos de un valor de fecha y hora.
En el ejemplo siguiente se incluye el especificador de formato personalizado "fff" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);


CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018

Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)


Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Volver a la tabla
Especificador de formato personalizado "ffff"
El especificador de formato personalizado "ffff" representa los cuatro dígitos más significativos de la fracción de
segundos; es decir, representa las diezmilésimas de segundo de un valor de fecha y hora.
Si bien se puede mostrar el componente correspondiente a las diezmilésimas de segundo de un valor de hora,
es muy posible que ese valor no sea significativo. La precisión de los valores de fecha y hora depende de la
resolución del reloj del sistema. En los sistemas operativos Windows NT 3.5 (y versiones posteriores) y Windows
Vista, la resolución del reloj es aproximadamente de 10 a 15 milisegundos.
Volver a la tabla
Especificador de formato personalizado "fffff"
El especificador de formato personalizado "fffff" representa los cinco dígitos más significativos de la fracción de
segundo; es decir, representa las cienmilésimas de segundo de un valor de fecha y hora.
Si bien se puede mostrar el componente correspondiente a las cienmilésimas de segundo de un valor de hora,
es muy posible que ese valor no sea significativo. La precisión de los valores de fecha y hora depende de la
resolución del reloj del sistema. En los sistemas operativos Windows NT 3.5 (y versiones posteriores) y Windows
Vista, la resolución del reloj es aproximadamente de 10 a 15 milisegundos.
Volver a la tabla
Especificador de formato personalizado "ffffff"
El especificador de formato personalizado "ffffff" representa los seis dígitos más significativos de la fracción de
segundos; es decir, representa las millonésimas de segundo de un valor de fecha y hora.
Si bien se puede mostrar el componente correspondiente a las millonésimas de segundo de un valor de hora, es
muy posible que ese valor no sea significativo. La precisión de los valores de fecha y hora depende de la
resolución del reloj del sistema. En los sistemas operativos Windows NT 3.5 (y versiones posteriores) y Windows
Vista, la resolución del reloj es aproximadamente de 10 a 15 milisegundos.
Volver a la tabla
Especificador de formato personalizado "fffffff"
El especificador de formato personalizado "fffffff" representa los siete dígitos más significativos de la fracción de
segundos; es decir, representa las diezmillonésimas de segundo de un valor de fecha y hora.
Si bien se puede mostrar el componente correspondiente a las diezmillonésimas de segundo de un valor de
hora, es muy posible que ese valor no sea significativo. La precisión de los valores de fecha y hora depende de la
resolución del reloj del sistema. En los sistemas operativos Windows NT 3.5 (y versiones posteriores) y Windows
Vista, la resolución del reloj es aproximadamente de 10 a 15 milisegundos.
Volver a la tabla

Especificador de fracción de segundos en mayúsculas "F"


Especificador de formato personalizado "F"
El especificador de formato personalizado "F" representa el dígito más significativo de la fracción de segundos;
es decir, representa las décimas de segundo de un valor de fecha y hora. Si el dígito es cero, no se muestra nada.
Si el especificador de formato "F" se usa sin otros especificadores de formato, se interpreta como el
especificador de formato de fecha y hora estándar "F". Para más información sobre cómo usar un especificador
de formato único, vea Usar especificadores de formato personalizado únicos más adelante en este artículo.
El número de especificadores de formato "F" que se usan con los métodos ParseExact, TryParseExact, ParseExact
u TryParseExact indica el número máximo de dígitos más significativos de la fracción de segundos que pueden
estar presentes para analizar correctamente la cadena.
En el ejemplo siguiente se incluye el especificador de formato personalizado "F" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);


CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018
Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)
Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Volver a la tabla
Especificador de formato personalizado "FF"
El especificador de formato personalizado "FF" representa los dos dígitos más significativos de la fracción de
segundos; es decir, representa las centésimas de segundo de un valor de fecha y hora. Sin embargo, no se
muestran los ceros finales ni los dígitos de dos ceros.
En el ejemplo siguiente se incluye el especificador de formato personalizado "FF" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);


CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018

Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)


Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Volver a la tabla
Especificador de formato personalizado "FFF"
El especificador de formato personalizado "FFF" representa los tres dígitos más significativos de la fracción de
segundos; es decir, representa los milisegundos de un valor de fecha y hora. Sin embargo, no se muestran los
ceros finales ni los dígitos de tres ceros.
En el ejemplo siguiente se incluye el especificador de formato personalizado "FFF" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);


CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018

Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)


Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Volver a la tabla
Especificador de formato personalizado "FFFF"
El especificador de formato personalizado "FFFF" representa los cuatro dígitos más significativos de la fracción
de segundos; es decir, representa las diezmilésimas de segundo de un valor de fecha y hora. Sin embargo, no se
muestran los ceros finales ni los dígitos de cuatro ceros.
Si bien se puede mostrar el componente correspondiente a las diezmilésimas de segundo de un valor de hora,
es muy posible que ese valor no sea significativo. La precisión de los valores de fecha y hora depende de la
resolución del reloj del sistema. En los sistemas operativos Windows NT 3.5 (y versiones posteriores) y Windows
Vista, la resolución del reloj es aproximadamente de 10 a 15 milisegundos.
Volver a la tabla
Especificador de formato personalizado "FFFFF"
El especificador de formato personalizado "FFFFF" representa los cinco dígitos más significativos de la fracción
de segundos; es decir, representa las cienmilésimas de segundo de un valor de fecha y hora. Sin embargo, no se
muestran los ceros finales ni los dígitos de cinco ceros.
Si bien se puede mostrar el componente correspondiente a las cienmilésimas de segundo de un valor de hora,
es muy posible que ese valor no sea significativo. La precisión de los valores de fecha y hora depende de la
resolución del reloj del sistema. En los sistemas operativos Windows NT 3.5 (y versiones posteriores) y Windows
Vista, la resolución del reloj es aproximadamente de 10 a 15 milisegundos.
Volver a la tabla
Especificador de formato personalizado "FFFFFF"
El especificador de formato personalizado "FFFFFF" representa los seis dígitos más significativos de la fracción
de segundos; es decir, representa las millonésimas de segundo de un valor de fecha y hora. Sin embargo, no se
muestran los ceros finales ni los dígitos de seis ceros.
Si bien se puede mostrar el componente correspondiente a las millonésimas de segundo de un valor de hora, es
muy posible que ese valor no sea significativo. La precisión de los valores de fecha y hora depende de la
resolución del reloj del sistema. En los sistemas operativos Windows NT 3.5 (y versiones posteriores) y Windows
Vista, la resolución del reloj es aproximadamente de 10 a 15 milisegundos.
Volver a la tabla
Especificador de formato personalizado "FFFFFFF"
El especificador de formato personalizado "FFFFFFF" representa los siete dígitos más significativos de la fracción
de segundos; es decir, representa las diezmillonésimas de segundo de un valor de fecha y hora. Sin embargo, no
se muestran los ceros finales ni los dígitos de siete ceros.
Si bien se puede mostrar el componente correspondiente a las diezmillonésimas de segundo de un valor de
hora, es muy posible que ese valor no sea significativo. La precisión de los valores de fecha y hora depende de la
resolución del reloj del sistema. En los sistemas operativos Windows NT 3.5 (y versiones posteriores) y Windows
Vista, la resolución del reloj es aproximadamente de 10 a 15 milisegundos.
Volver a la tabla

Especificador de formato de era "g"


Especificador de formato personalizado "g" o "gg"
Los especificadores de formato personalizado "g" o "gg" (más cualquier número de especificadores "g"
adicionales) representan el período o la era, como d.C. La operación de formato hace caso omiso de este
especificador si la fecha a la que se va a dar formato no tiene una cadena de período o de era asociada.
Si el especificador de formato "g" se usa sin otros especificadores de formato personalizado, se interpretará
como el especificador de formato de fecha y hora estándar "g". Para más información sobre cómo usar un
especificador de formato único, vea Usar especificadores de formato personalizado únicos más adelante en este
artículo.
En el ejemplo siguiente se incluye el especificador de formato personalizado "g" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(70, 08, 04);

Console.WriteLine(date1.ToString("MM/dd/yyyy g",
CultureInfo.InvariantCulture));
// Displays 08/04/0070 A.D.
Console.WriteLine(date1.ToString("MM/dd/yyyy g",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays 08/04/0070 ap. J.-C.
Dim date1 As Date = #08/04/0070#

Console.WriteLine(date1.ToString("MM/dd/yyyy g", _
CultureInfo.InvariantCulture))
' Displays 08/04/0070 A.D.
Console.WriteLine(date1.ToString("MM/dd/yyyy g", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays 08/04/0070 ap. J.-C.

Volver a la tabla

Especificador de formato de hora en minúsculas "h"


Especificador de formato personalizado "h"
El especificador de formato personalizado "h" representa la hora como un número del 1 al 12; es decir, la hora se
representa como en un reloj de 12 horas que cuenta las horas enteras desde medianoche o mediodía. Una hora
determinada después de la medianoche no se distingue de la misma hora después del mediodía. No se
redondea la hora y las horas con un solo dígito no tienen un cero inicial. Por ejemplo, dada una hora de 5:43 de
la mañana o de la tarde, este especificador de formato personalizado muestra "5".
Si el especificador de formato "h" se usa sin otros especificadores de formato personalizado, se interpretará
como un especificador de formato de fecha y hora estándar y producirá una excepción FormatException. Para
más información sobre cómo usar un especificador de formato único, vea Usar especificadores de formato
personalizado únicos más adelante en este artículo.
En el ejemplo siguiente se incluye el especificador de formato personalizado "h" en una cadena de formato
personalizado.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1 µ
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1.5 µ
Dim date1 As Date
date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1 µ
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1.5 µ

Volver a la tabla
Especificador de formato personalizado "hh"
El especificador de formato personalizado "hh" (más cualquier número de especificadores "h" adicionales)
representa la hora como un número del 01 al 12; es decir, la hora se representa como en un reloj de 12 horas
que cuenta las horas enteras desde medianoche o mediodía. Una hora determinada después de la medianoche
no se distingue de la misma hora después del mediodía. No se redondea la hora y las horas con un solo dígito
tienen un cero inicial. Por ejemplo, dada una hora de 5:43 de la mañana o de la tarde, este especificador de
formato muestra "05".
En el ejemplo siguiente se incluye el especificador de formato personalizado "hh" en una cadena de formato
personalizado.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01 du.
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01.50 du.

Dim date1 As Date


date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01 du.
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01.50 du.
Volver a la tabla

Especificador de formato de hora en mayúsculas "H"


Especificador de formato personalizado "H"
El especificador de formato personalizado "H" representa la hora como un número del 0 al 23; es decir, la hora
se representa como en un reloj de 24 horas de base cero que cuenta las horas desde medianoche. Una hora con
un solo dígito tiene un formato sin un cero inicial.
Si el especificador de formato "H" se usa sin otros especificadores de formato personalizado, se interpretará
como un especificador de formato de fecha y hora estándar y producirá una excepción FormatException. Para
más información sobre cómo usar un especificador de formato único, vea Usar especificadores de formato
personalizado únicos más adelante en este artículo.
En el ejemplo siguiente se incluye el especificador de formato personalizado "H" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 1, 1, 6, 9, 1);


Console.WriteLine(date1.ToString("H:mm:ss",
CultureInfo.InvariantCulture));
// Displays 6:09:01

Dim date1 As Date = #6:09:01AM#


Console.WriteLine(date1.ToString("H:mm:ss", _
CultureInfo.InvariantCulture))
' Displays 6:09:01

Volver a la tabla
Especificador de formato personalizado "HH"
El especificador de formato personalizado "HH" (más cualquier número de especificadores "H" adicionales)
representa la hora como un número del 00 al 23; es decir, la hora se representa como en un reloj de 24 horas de
base cero que cuenta las horas desde medianoche. Una hora con un solo dígito tiene un formato con un cero
inicial.
En el ejemplo siguiente se incluye el especificador de formato personalizado "HH" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 1, 1, 6, 9, 1);


Console.WriteLine(date1.ToString("HH:mm:ss",
CultureInfo.InvariantCulture));
// Displays 06:09:01

Dim date1 As Date = #6:09:01AM#


Console.WriteLine(date1.ToString("HH:mm:ss", _
CultureInfo.InvariantCulture))
' Displays 06:09:01

Volver a la tabla

Especificador de formato de zona horaria "K"


Especificador de formato personalizado "K"
El especificador de formato personalizado "K" representa la información de zona horaria de un valor de fecha y
hora. Cuando este formato se usa con valores DateTime, el valor de la propiedad DateTime.Kind define la cadena
de resultado.
En la zona horaria local (un valor de propiedad DateTime.Kind de DateTimeKind.Local), este especificador
es equivalente al especificador "zzz" y genera una cadena de resultado que contiene el desfase local con
respecto a la hora universal coordinada (UTC); por ejemplo, "-07: 00".
En una hora UTC (un valor de propiedad DateTime.Kind de DateTimeKind.Utc), la cadena de resultado
incluye un carácter "Z" para representar una fecha UTC.
En una hora de una zona horaria no especificada (una hora cuya propiedad DateTime.Kind es igual a
DateTimeKind.Unspecified), el resultado es equivalente a String.Empty.
En los valores DateTimeOffset, el especificador de formato "K" es equivalente al especificador de formato "zzz" y
genera una cadena de resultado que contiene el desfase del valor DateTimeOffset con respecto a la hora UTC.
Si el especificador de formato "K" se usa sin otros especificadores de formato personalizado, se interpretará
como un especificador de formato de fecha y hora estándar y producirá una excepción FormatException. Para
más información sobre cómo usar un especificador de formato único, vea Usar especificadores de formato
personalizado únicos más adelante en este artículo.
En el ejemplo siguiente se muestra la cadena que se obtiene al utilizar el especificador de formato personalizado
"K" con varios valores DateTime y DateTimeOffset en un sistema de la zona horaria del Pacífico de EE. UU.

Console.WriteLine(DateTime.Now.ToString("%K"));
// Displays -07:00
Console.WriteLine(DateTime.UtcNow.ToString("%K"));
// Displays Z
Console.WriteLine("'{0}'",
DateTime.SpecifyKind(DateTime.Now,
DateTimeKind.Unspecified).ToString("%K"));
// Displays ''
Console.WriteLine(DateTimeOffset.Now.ToString("%K"));
// Displays -07:00
Console.WriteLine(DateTimeOffset.UtcNow.ToString("%K"));
// Displays +00:00
Console.WriteLine(new DateTimeOffset(2008, 5, 1, 6, 30, 0,
new TimeSpan(5, 0, 0)).ToString("%K"));
// Displays +05:00

Console.WriteLine(Date.Now.ToString("%K"))
' Displays -07:00
Console.WriteLine(Date.UtcNow.ToString("%K"))
' Displays Z
Console.WriteLine("'{0}'", _
Date.SpecifyKind(Date.Now, _
DateTimeKind.Unspecified). _
ToString("%K"))
' Displays ''
Console.WriteLine(DateTimeOffset.Now.ToString("%K"))
' Displays -07:00
Console.WriteLine(DateTimeOffset.UtcNow.ToString("%K"))
' Displays +00:00
Console.WriteLine(New DateTimeOffset(2008, 5, 1, 6, 30, 0, _
New TimeSpan(5, 0, 0)). _
ToString("%K"))
' Displays +05:00

Volver a la tabla
Especificador de formato de minuto "m"
Especificador de formato personalizado "m"
El especificador de formato personalizado "m" representa el minuto como un número de 0 a 59. El minuto
representa los minutos enteros que han transcurrido desde la última hora. Un minuto con un solo dígito tiene
un formato sin un cero inicial.
Si el especificador de formato "m" se usa sin otros especificadores de formato personalizado, se interpretará
como el especificador de formato de fecha y hora estándar "m". Para más información sobre cómo usar un
especificador de formato único, vea Usar especificadores de formato personalizado únicos más adelante en este
artículo.
En el ejemplo siguiente se incluye el especificador de formato personalizado "m" en una cadena de formato
personalizado.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1 µ
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1.5 µ

Dim date1 As Date


date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1 µ
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1.5 µ

Volver a la tabla
Especificador de formato personalizado "mm"
El especificador de formato personalizado "mm" (más cualquier número de especificadores "m" adicionales)
representa el minuto como un número de 00 a 59. El minuto representa los minutos enteros que han
transcurrido desde la última hora. Un minuto con un solo dígito tiene un formato con un cero inicial.
En el ejemplo siguiente se incluye el especificador de formato personalizado "mm" en una cadena de formato
personalizado.
DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01 du.
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01.50 du.

Dim date1 As Date


date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01 du.
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01.50 du.

Volver a la tabla

Especificador de formato de mes "M"


Especificador de formato personalizado "M"
El especificador de formato personalizado "M" representa el mes como un número del 1 al 12 (o del 1 al 13 para
los calendarios con 13 meses). Un mes con un solo dígito tiene un formato sin un cero inicial.
Si el especificador de formato "M" se usa sin otros especificadores de formato personalizado, se interpretará
como el especificador de formato de fecha y hora estándar "M". Para más información sobre cómo usar un
especificador de formato único, vea Usar especificadores de formato personalizado únicos más adelante en este
artículo.
En el ejemplo siguiente se incluye el especificador de formato personalizado "M" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 8, 18);


Console.WriteLine(date1.ToString("(M) MMM, MMMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays (8) Aug, August
Console.WriteLine(date1.ToString("(M) MMM, MMMM",
CultureInfo.CreateSpecificCulture("nl-NL")));
// Displays (8) aug, augustus
Console.WriteLine(date1.ToString("(M) MMM, MMMM",
CultureInfo.CreateSpecificCulture("lv-LV")));
// Displays (8) Aug, augusts
Dim date1 As Date = #8/18/2008#
Console.WriteLine(date1.ToString("(M) MMM, MMMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays (8) Aug, August
Console.WriteLine(date1.ToString("(M) MMM, MMMM", _
CultureInfo.CreateSpecificCulture("nl-NL")))
' Displays (8) aug, augustus
Console.WriteLine(date1.ToString("(M) MMM, MMMM", _
CultureInfo.CreateSpecificCulture("lv-LV")))
' Displays (8) Aug, augusts

Volver a la tabla
Especificador de formato personalizado "MM"
El especificador de formato personalizado "MM" representa el mes como un número del 01 al 12 (o del 1 al 13
para los calendarios con 13 meses). Un mes con un solo dígito tiene un formato con un cero inicial.
En el ejemplo siguiente se incluye el especificador de formato personalizado "MM" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 1, 2, 6, 30, 15);

Console.WriteLine(date1.ToString("dd, MM",
CultureInfo.InvariantCulture));
// 02, 01

Dim date1 As Date = #1/2/2008 6:30:15AM#

Console.WriteLine(date1.ToString("dd, MM", _
CultureInfo.InvariantCulture))
' 02, 01

Volver a la tabla
Especificador de formato personalizado "MMM"
El especificador de formato personalizado "MMM" representa el nombre abreviado del mes. El nombre
abreviado adaptado del mes se recupera de la propiedad DateTimeFormatInfo.AbbreviatedMonthNames de la
referencia cultural actual o especificada.
En el ejemplo siguiente se incluye el especificador de formato personalizado "MMM" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15);

Console.WriteLine(date1.ToString("ddd d MMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Fri 29 Aug
Console.WriteLine(date1.ToString("ddd d MMM",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays ven. 29 août
Dim date1 As Date = #08/29/2008 7:27:15PM#

Console.WriteLine(date1.ToString("ddd d MMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Fri 29 Aug
Console.WriteLine(date1.ToString("ddd d MMM", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays ven. 29 août

Volver a la tabla
Especificador de formato personalizado "MMMM"
El especificador de formato personalizado "MMMM" representa el nombre completo del mes. El nombre
adaptado del mes se recupera de la propiedad DateTimeFormatInfo.MonthNames de la referencia cultural actual
o especificada.
En el ejemplo siguiente se incluye el especificador de formato personalizado "MMMM" en una cadena de
formato personalizado.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15);

Console.WriteLine(date1.ToString("dddd dd MMMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Friday 29 August
Console.WriteLine(date1.ToString("dddd dd MMMM",
CultureInfo.CreateSpecificCulture("it-IT")));
// Displays venerdì 29 agosto

Dim date1 As Date = #08/29/2008 7:27:15PM#

Console.WriteLine(date1.ToString("dddd dd MMMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Friday 29 August
Console.WriteLine(date1.ToString("dddd dd MMMM", _
CultureInfo.CreateSpecificCulture("it-IT")))
' Displays venerdì 29 agosto

Volver a la tabla

Especificador de formato de segundos "s"


Especificador de formato personalizado "s"
El especificador de formato personalizado "s" representa los segundos como un número de 0 a 59. El resultado
representa los segundos enteros que han transcurrido desde el último minuto. Un segundo con un solo dígito
tiene un formato sin un cero inicial.
Si el especificador de formato "s" se usa sin otros especificadores de formato personalizado, se interpretará
como el especificador de formato de fecha y hora estándar "s". Para más información sobre cómo usar un
especificador de formato único, vea Usar especificadores de formato personalizado únicos más adelante en este
artículo.
En el ejemplo siguiente se incluye el especificador de formato personalizado "s" en una cadena de formato
personalizado.
DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1 µ
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1.5 µ

Dim date1 As Date


date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1 µ
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1.5 µ

Volver a la tabla
Especificador de formato personalizado "ss"
El especificador de formato personalizado "ss" (más cualquier número de especificadores "s" adicionales)
representa los segundos como un número de 00 a 59. El resultado representa los segundos enteros que han
transcurrido desde el último minuto. Un segundo con un solo dígito tiene un formato con un cero inicial.
En el ejemplo siguiente se incluye el especificador de formato personalizado "ss" en una cadena de formato
personalizado.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01 du.
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01.50 du.
Dim date1 As Date
date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01 du.
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01.50 du.

Volver a la tabla

Especificador de formato de merídiem "t"


Especificador de formato personalizado "t"
El especificador de formato personalizado "t" representa el primer carácter del designador AM/PM. El
designador adaptado adecuado se recupera de la propiedad DateTimeFormatInfo.AMDesignator o
DateTimeFormatInfo.PMDesignator de la referencia cultural actual o especificada. El designador AM se usa para
todas las horas de 0:00:00 (medianoche) a 11:59:59.999. El designador PM se usa para todas las horas de
12:00:00 (mediodía) a 23:59:59.999.
Si el especificador de formato "t" se usa sin otros especificadores de formato personalizado, se interpretará
como el especificador de formato de fecha y hora estándar "t". Para más información sobre cómo usar un
especificador de formato único, vea Usar especificadores de formato personalizado únicos más adelante en este
artículo.
En el ejemplo siguiente se incluye el especificador de formato personalizado "t" en una cadena de formato
personalizado.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1 µ
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1.5 µ
Dim date1 As Date
date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1 µ
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1.5 µ

Volver a la tabla
Especificador de formato personalizado "tt"
El especificador de formato personalizado "tt" (más cualquier número de especificadores "t" adicionales)
representa designador AM/PM completo. El designador adaptado adecuado se recupera de la propiedad
DateTimeFormatInfo.AMDesignator o DateTimeFormatInfo.PMDesignator de la referencia cultural actual o
especificada. El designador AM se usa para todas las horas de 0:00:00 (medianoche) a 11:59:59.999. El
designador PM se usa para todas las horas de 12:00:00 (mediodía) a 23:59:59.999.
Asegúrese de usar el especificador "tt" para aquellos idiomas en los que sea necesario mantener la distinción
entre a. m. y p. m. Un ejemplo es el japonés, en el que los designadores de a.m. y p.m. se diferencian en el
segundo carácter en vez de en el primero.
En el ejemplo siguiente se incluye el especificador de formato personalizado "tt" en una cadena de formato
personalizado.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01 du.
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01.50 du.
Dim date1 As Date
date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01 du.
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01.50 du.

Volver a la tabla

Especificador de formato de año "y"


Especificador de formato personalizado "y"
El especificador de formato personalizado "y" representa el año como un número de uno o dos dígitos. Si el año
tiene más de dos dígitos, en el resultado sólo aparecen los dos dígitos de orden inferior. Si el primer dígito de un
año de dos dígitos comienza con un cero (por ejemplo, 2008), se aplica formato al número sin el cero inicial.
Si el especificador de formato "y" se usa sin otros especificadores de formato personalizado, se interpretará
como el especificador de formato de fecha y hora estándar "y". Para más información sobre cómo usar un
especificador de formato único, vea Usar especificadores de formato personalizado únicos más adelante en este
artículo.
En el ejemplo siguiente se incluye el especificador de formato personalizado "y" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(1, 12, 1);


DateTime date2 = new DateTime(2010, 1, 1);
Console.WriteLine(date1.ToString("%y"));
// Displays 1
Console.WriteLine(date1.ToString("yy"));
// Displays 01
Console.WriteLine(date1.ToString("yyy"));
// Displays 001
Console.WriteLine(date1.ToString("yyyy"));
// Displays 0001
Console.WriteLine(date1.ToString("yyyyy"));
// Displays 00001
Console.WriteLine(date2.ToString("%y"));
// Displays 10
Console.WriteLine(date2.ToString("yy"));
// Displays 10
Console.WriteLine(date2.ToString("yyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyyy"));
// Displays 02010
Dim date1 As Date = #12/1/0001#
Dim date2 As Date = #1/1/2010#
Console.WriteLine(date1.ToString("%y"))
' Displays 1
Console.WriteLine(date1.ToString("yy"))
' Displays 01
Console.WriteLine(date1.ToString("yyy"))
' Displays 001
Console.WriteLine(date1.ToString("yyyy"))
' Displays 0001
Console.WriteLine(date1.ToString("yyyyy"))
' Displays 00001
Console.WriteLine(date2.ToString("%y"))
' Displays 10
Console.WriteLine(date2.ToString("yy"))
' Displays 10
Console.WriteLine(date2.ToString("yyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyyy"))
' Displays 02010

Volver a la tabla
Especificador de formato personalizado "yy"
El especificador de formato personalizado "yy" representa el año como un número de dos dígitos. Si el año tiene
más de dos dígitos, en el resultado sólo aparecen los dos dígitos de orden inferior. Si el año de dos dígitos tiene
menos de dos dígitos significativos, el número se rellenará con ceros iniciales hasta obtener dos dígitos.
En una operación de análisis, un año de dos dígitos que se analiza mediante el especificador de formato
personalizado “yy” se interpreta basándose en la propiedad de Calendar.TwoDigitYearMax del calendario actual
del proveedor de formato. En el ejemplo siguiente se analiza la representación en forma de cadena de una fecha
con un año de dos dígitos utilizando el calendario gregoriano predeterminado de la referencia cultural actual (en
este caso, en-US). Modifica el objeto CultureInfo de la referencia cultural actual para utilizar un objeto
GregorianCalendar cuya propiedad TwoDigitYearMax se ha modificado.
using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
string fmt = "dd-MMM-yy";
string value = "24-Jan-49";

Calendar cal = (Calendar) CultureInfo.CurrentCulture.Calendar.Clone();


Console.WriteLine("Two Digit Year Range: {0} - {1}",
cal.TwoDigitYearMax - 99, cal.TwoDigitYearMax);

Console.WriteLine("{0:d}", DateTime.ParseExact(value, fmt, null));


Console.WriteLine();

cal.TwoDigitYearMax = 2099;
CultureInfo culture = (CultureInfo) CultureInfo.CurrentCulture.Clone();
culture.DateTimeFormat.Calendar = cal;
Thread.CurrentThread.CurrentCulture = culture;

Console.WriteLine("Two Digit Year Range: {0} - {1}",


cal.TwoDigitYearMax - 99, cal.TwoDigitYearMax);
Console.WriteLine("{0:d}", DateTime.ParseExact(value, fmt, null));
}
}
// The example displays the following output:
// Two Digit Year Range: 1930 - 2029
// 1/24/1949
//
// Two Digit Year Range: 2000 - 2099
// 1/24/2049
Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim fmt As String = "dd-MMM-yy"
Dim value As String = "24-Jan-49"

Dim cal As Calendar = CType(CultureInfo.CurrentCulture.Calendar.Clone(), Calendar)


Console.WriteLine("Two Digit Year Range: {0} - {1}",
cal.TwoDigitYearMax - 99, cal.TwoDigitYearMax)

Console.WriteLine("{0:d}", DateTime.ParseExact(value, fmt, Nothing))


Console.WriteLine()

cal.TwoDigitYearMax = 2099
Dim culture As CultureInfo = CType(CultureInfo.CurrentCulture.Clone(), CultureInfo)
culture.DateTimeFormat.Calendar = cal
Thread.CurrentThread.CurrentCulture = culture

Console.WriteLine("Two Digit Year Range: {0} - {1}",


cal.TwoDigitYearMax - 99, cal.TwoDigitYearMax)
Console.WriteLine("{0:d}", DateTime.ParseExact(value, fmt, Nothing))
End Sub
End Module
' The example displays the following output:
' Two Digit Year Range: 1930 - 2029
' 1/24/1949
'
' Two Digit Year Range: 2000 - 2099
' 1/24/2049

En el ejemplo siguiente se incluye el especificador de formato personalizado "yy" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(1, 12, 1);


DateTime date2 = new DateTime(2010, 1, 1);
Console.WriteLine(date1.ToString("%y"));
// Displays 1
Console.WriteLine(date1.ToString("yy"));
// Displays 01
Console.WriteLine(date1.ToString("yyy"));
// Displays 001
Console.WriteLine(date1.ToString("yyyy"));
// Displays 0001
Console.WriteLine(date1.ToString("yyyyy"));
// Displays 00001
Console.WriteLine(date2.ToString("%y"));
// Displays 10
Console.WriteLine(date2.ToString("yy"));
// Displays 10
Console.WriteLine(date2.ToString("yyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyyy"));
// Displays 02010
Dim date1 As Date = #12/1/0001#
Dim date2 As Date = #1/1/2010#
Console.WriteLine(date1.ToString("%y"))
' Displays 1
Console.WriteLine(date1.ToString("yy"))
' Displays 01
Console.WriteLine(date1.ToString("yyy"))
' Displays 001
Console.WriteLine(date1.ToString("yyyy"))
' Displays 0001
Console.WriteLine(date1.ToString("yyyyy"))
' Displays 00001
Console.WriteLine(date2.ToString("%y"))
' Displays 10
Console.WriteLine(date2.ToString("yy"))
' Displays 10
Console.WriteLine(date2.ToString("yyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyyy"))
' Displays 02010

Volver a la tabla
Especificador de formato personalizado "yyy"
El especificador de formato personalizado "yyy" representa el año con un mínimo de tres dígitos. Si el año tiene
más de tres dígitos significativos, se incluyen en la cadena de resultado. Si el año tiene menos de tres dígitos, el
número se rellenará con ceros iniciales hasta obtener tres dígitos.

NOTE
Para el calendario budista tailandés, que puede tener años de cinco dígitos, este especificador de formato muestra todos
los dígitos significativos.

En el ejemplo siguiente se incluye el especificador de formato personalizado "yyy" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(1, 12, 1);


DateTime date2 = new DateTime(2010, 1, 1);
Console.WriteLine(date1.ToString("%y"));
// Displays 1
Console.WriteLine(date1.ToString("yy"));
// Displays 01
Console.WriteLine(date1.ToString("yyy"));
// Displays 001
Console.WriteLine(date1.ToString("yyyy"));
// Displays 0001
Console.WriteLine(date1.ToString("yyyyy"));
// Displays 00001
Console.WriteLine(date2.ToString("%y"));
// Displays 10
Console.WriteLine(date2.ToString("yy"));
// Displays 10
Console.WriteLine(date2.ToString("yyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyyy"));
// Displays 02010
Dim date1 As Date = #12/1/0001#
Dim date2 As Date = #1/1/2010#
Console.WriteLine(date1.ToString("%y"))
' Displays 1
Console.WriteLine(date1.ToString("yy"))
' Displays 01
Console.WriteLine(date1.ToString("yyy"))
' Displays 001
Console.WriteLine(date1.ToString("yyyy"))
' Displays 0001
Console.WriteLine(date1.ToString("yyyyy"))
' Displays 00001
Console.WriteLine(date2.ToString("%y"))
' Displays 10
Console.WriteLine(date2.ToString("yy"))
' Displays 10
Console.WriteLine(date2.ToString("yyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyyy"))
' Displays 02010

Volver a la tabla
Especificador de formato personalizado "yyyy"
El especificador de formato personalizado "Yyyy" representa el año con un mínimo de cuatro dígitos. Si el año
tiene más de cuatro dígitos significativos, se incluyen en la cadena resultante. Si el año tiene menos de cuatro
dígitos, el número se completa con ceros iniciales hasta obtener cuatro dígitos.

NOTE
En el calendario budista tailandés, que puede tener años de cinco dígitos, este especificador de formato muestra un
mínimo de cuatro dígitos.

En el ejemplo siguiente se incluye el especificador de formato personalizado "yyyy" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(1, 12, 1);


DateTime date2 = new DateTime(2010, 1, 1);
Console.WriteLine(date1.ToString("%y"));
// Displays 1
Console.WriteLine(date1.ToString("yy"));
// Displays 01
Console.WriteLine(date1.ToString("yyy"));
// Displays 001
Console.WriteLine(date1.ToString("yyyy"));
// Displays 0001
Console.WriteLine(date1.ToString("yyyyy"));
// Displays 00001
Console.WriteLine(date2.ToString("%y"));
// Displays 10
Console.WriteLine(date2.ToString("yy"));
// Displays 10
Console.WriteLine(date2.ToString("yyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyyy"));
// Displays 02010
Dim date1 As Date = #12/1/0001#
Dim date2 As Date = #1/1/2010#
Console.WriteLine(date1.ToString("%y"))
' Displays 1
Console.WriteLine(date1.ToString("yy"))
' Displays 01
Console.WriteLine(date1.ToString("yyy"))
' Displays 001
Console.WriteLine(date1.ToString("yyyy"))
' Displays 0001
Console.WriteLine(date1.ToString("yyyyy"))
' Displays 00001
Console.WriteLine(date2.ToString("%y"))
' Displays 10
Console.WriteLine(date2.ToString("yy"))
' Displays 10
Console.WriteLine(date2.ToString("yyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyyy"))
' Displays 02010

Volver a la tabla
Especificador de formato personalizado "yyyyy"
El especificador de formato personalizado "yyyyy" (más cualquier número de especificadores "y" adicionales)
representa el año con un mínimo de cinco dígitos. Si el año tiene más de cinco dígitos significativos, se incluyen
en la cadena resultante. Si el año tiene menos de cinco dígitos, el número se rellenará con ceros iniciales hasta
obtener cinco dígitos.
Si hay especificadores "y" adicionales, el número se rellenará con tantos ceros iniciales como sean necesarios
para obtener el número de especificadores "y".
En el ejemplo siguiente se incluye el especificador de formato personalizado "yyyyy" en una cadena de formato
personalizado.

DateTime date1 = new DateTime(1, 12, 1);


DateTime date2 = new DateTime(2010, 1, 1);
Console.WriteLine(date1.ToString("%y"));
// Displays 1
Console.WriteLine(date1.ToString("yy"));
// Displays 01
Console.WriteLine(date1.ToString("yyy"));
// Displays 001
Console.WriteLine(date1.ToString("yyyy"));
// Displays 0001
Console.WriteLine(date1.ToString("yyyyy"));
// Displays 00001
Console.WriteLine(date2.ToString("%y"));
// Displays 10
Console.WriteLine(date2.ToString("yy"));
// Displays 10
Console.WriteLine(date2.ToString("yyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyyy"));
// Displays 02010
Dim date1 As Date = #12/1/0001#
Dim date2 As Date = #1/1/2010#
Console.WriteLine(date1.ToString("%y"))
' Displays 1
Console.WriteLine(date1.ToString("yy"))
' Displays 01
Console.WriteLine(date1.ToString("yyy"))
' Displays 001
Console.WriteLine(date1.ToString("yyyy"))
' Displays 0001
Console.WriteLine(date1.ToString("yyyyy"))
' Displays 00001
Console.WriteLine(date2.ToString("%y"))
' Displays 10
Console.WriteLine(date2.ToString("yy"))
' Displays 10
Console.WriteLine(date2.ToString("yyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyyy"))
' Displays 02010

Volver a la tabla

Especificador de formato de desfase "z"


Especificador de formato personalizado "z"
Con valores DateTime, el especificador de formato personalizado "z" representa el desfase con signo de la zona
horaria del sistema operativo local respecto a la hora universal coordinada (UTC), medido en horas. No refleja el
valor de la propiedad DateTime.Kind de una instancia. Por esta razón, no se recomienda usar el especificador de
formato "z" con valores DateTime.
Con valores DateTimeOffset, este especificador de formato representa el desfase en horas del valor
DateTimeOffset con respecto a la hora UTC.
La diferencia horaria se muestra siempre con un signo inicial. Un signo más (+) indica las horas de adelanto y un
signo menos (-) indica las horas de retraso con respecto a la hora UTC. Un desfase con un solo dígito tiene un
formato sin un cero inicial.
Si el especificador de formato "z" se usa sin otros especificadores de formato personalizado, se interpretará
como un especificador de formato de fecha y hora estándar y producirá una excepción FormatException. Para
más información sobre cómo usar un especificador de formato único, vea Usar especificadores de formato
personalizado únicos más adelante en este artículo.
En el ejemplo siguiente se incluye el especificador de formato personalizado "z" en una cadena de formato
personalizado.

DateTime date1 = DateTime.UtcNow;


Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date1));
// Displays -7, -07, -07:00

DateTimeOffset date2 = new DateTimeOffset(2008, 8, 1, 0, 0, 0,


new TimeSpan(6, 0, 0));
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date2));
// Displays +6, +06, +06:00
Dim date1 As Date = Date.UtcNow
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date1))
' Displays -7, -07, -07:00

Dim date2 As New DateTimeOffset(2008, 8, 1, 0, 0, 0, _


New Timespan(6, 0, 0))
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date2))
' Displays +6, +06, +06:00

Volver a la tabla
Especificador de formato personalizado "zz"
Con valores DateTime, el especificador de formato personalizado "zz" representa el desfase con signo de la zona
horaria del sistema operativo local respecto a la hora UTC, medido en horas. No refleja el valor de la propiedad
DateTime.Kind de una instancia. Por esta razón, no se recomienda usar el especificador de formato "zz" con
valores DateTime.
Con valores DateTimeOffset, este especificador de formato representa el desfase en horas del valor
DateTimeOffset con respecto a la hora UTC.
La diferencia horaria se muestra siempre con un signo inicial. Un signo más (+) indica las horas de adelanto y un
signo menos (-) indica las horas de retraso con respecto a la hora UTC. Un desfase con un solo dígito tiene un
formato con un cero inicial.
En el ejemplo siguiente se incluye el especificador de formato personalizado "zz" en una cadena de formato
personalizado.

DateTime date1 = DateTime.UtcNow;


Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date1));
// Displays -7, -07, -07:00

DateTimeOffset date2 = new DateTimeOffset(2008, 8, 1, 0, 0, 0,


new TimeSpan(6, 0, 0));
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date2));
// Displays +6, +06, +06:00

Dim date1 As Date = Date.UtcNow


Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date1))
' Displays -7, -07, -07:00

Dim date2 As New DateTimeOffset(2008, 8, 1, 0, 0, 0, _


New Timespan(6, 0, 0))
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date2))
' Displays +6, +06, +06:00

Volver a la tabla
Especificador de formato personalizado "zzz"
Con valores DateTime, el especificador de formato personalizado "zzz" representa el desfase con signo de la
zona horaria del sistema operativo local respecto a la hora UTC, medido en horas y minutos. No refleja el valor
de la propiedad DateTime.Kind de una instancia. Por esta razón, no se recomienda usar el especificador de
formato "zzz" con valores DateTime.
Con valores DateTimeOffset, este especificador de formato representa el desfase en horas y minutos del valor
DateTimeOffset con respecto a la hora UTC.
La diferencia horaria se muestra siempre con un signo inicial. Un signo más (+) indica las horas de adelanto y un
signo menos (-) indica las horas de retraso con respecto a la hora UTC. Un desfase con un solo dígito tiene un
formato con un cero inicial.
En el ejemplo siguiente se incluye el especificador de formato personalizado "zzz" en una cadena de formato
personalizado.

DateTime date1 = DateTime.UtcNow;


Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date1));
// Displays -7, -07, -07:00

DateTimeOffset date2 = new DateTimeOffset(2008, 8, 1, 0, 0, 0,


new TimeSpan(6, 0, 0));
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date2));
// Displays +6, +06, +06:00

Dim date1 As Date = Date.UtcNow


Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date1))
' Displays -7, -07, -07:00

Dim date2 As New DateTimeOffset(2008, 8, 1, 0, 0, 0, _


New Timespan(6, 0, 0))
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date2))
' Displays +6, +06, +06:00

Volver a la tabla

Especificadores de separador de fecha y hora


Especificador de formato personalizado ":"
El especificador de formato personalizado ":" representa el separador de hora, que se usa para diferenciar horas,
minutos y segundos. El separador de hora adaptado adecuado se recupera de la propiedad
DateTimeFormatInfo.TimeSeparator de la referencia cultural actual o especificada.

NOTE
Para cambiar el separador de hora en una determinada cadena de fecha y hora, especifique el carácter separador en un
delimitador de cadena literal. Por ejemplo, la cadena de formato personalizado hh'_'dd'_'ss genera una cadena en que
"_" (guion bajo) siempre se utiliza como separador de hora. Para cambiar el separador de hora en todas las fechas de una
referencia cultural, cambie el valor de la propiedad DateTimeFormatInfo.TimeSeparator de la referencia cultural actual, o
cree una instancia de un objeto DateTimeFormatInfo, asigne el carácter a su propiedad TimeSeparator y llame a una
sobrecarga del método de formato que incluya un parámetro IFormatProvider.

Si el especificador de formato ":" se usa sin otros especificadores de formato personalizado, se interpretará
como un especificador de formato de fecha y hora estándar y producirá una excepción FormatException. Para
más información sobre cómo usar un especificador de formato único, vea Usar especificadores de formato
personalizado únicos más adelante en este artículo.
Volver a la tabla
Especificador de formato personalizado "/"
El especificador de formato personalizado "/" representa el separador de fecha, que se usa para diferenciar años,
meses y días. El separador de fecha adaptado adecuado se recupera de la propiedad
DateTimeFormatInfo.DateSeparator de la referencia cultural actual o especificada.

NOTE
Para cambiar el separador de fecha en una determinada cadena de fecha y hora, especifique el carácter separador en un
delimitador de cadena literal. Por ejemplo, la cadena de formato personalizado mm'/'dd'/'yyyy genera una cadena en
que "/" siempre se utiliza como separador de fecha. Para cambiar el separador de fecha en todas las fechas de una
referencia cultural, cambie el valor de la propiedad DateTimeFormatInfo.DateSeparator de la referencia cultural actual, o
cree una instancia de un objeto DateTimeFormatInfo, asigne el carácter a su propiedad DateSeparator y llame a una
sobrecarga del método de formato que incluya un parámetro IFormatProvider.

Si el especificador de formato "/" se usa sin otros especificadores de formato personalizado, se interpretará
como un especificador de formato de fecha y hora estándar y producirá una excepción FormatException. Para
más información sobre cómo usar un especificador de formato único, vea Usar especificadores de formato
personalizado únicos más adelante en este artículo.
Volver a la tabla

Literales de carácter
Los siguientes caracteres de una cadena de formato de fecha y hora personalizada están reservados y siempre
se interpretan como caracteres de formato o, en el caso de " , ' , / y \ , como caracteres especiales.

F H K M d

f g h m s

t y z % :

/ " ' \

Todos los demás caracteres se interpretan siempre como literales de carácter y, en una operación de formato, se
incluyen en la cadena de resultado sin modificar. En una operación de análisis, deben coincidir exactamente con
los caracteres de la cadena de entrada; la comparación distingue entre mayúsculas y minúsculas.
En el ejemplo siguiente se incluyen los caracteres literales "PST" (para hora estándar del Pacífico) y "PDT" (para
horario de verano del Pacífico) para representar la zona horaria local en una cadena de formato. Tenga en cuenta
que la cadena se incluye en la cadena de resultado y que una cadena que incluye la cadena de zona horaria local
también se analiza correctamente.
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
String[] formats = { "dd MMM yyyy hh:mm tt PST",
"dd MMM yyyy hh:mm tt PDT" };
var dat = new DateTime(2016, 8, 18, 16, 50, 0);
// Display the result string.
Console.WriteLine(dat.ToString(formats[1]));

// Parse a string.
String value = "25 Dec 2016 12:00 pm PST";
DateTime newDate;
if (DateTime.TryParseExact(value, formats, null,
DateTimeStyles.None, out newDate))
Console.WriteLine(newDate);
else
Console.WriteLine("Unable to parse '{0}'", value);
}
}
// The example displays the following output:
// 18 Aug 2016 04:50 PM PDT
// 12/25/2016 12:00:00 PM

Imports System.Globalization

Module Example
Public Sub Main()
Dim formats() As String = {"dd MMM yyyy hh:mm tt PST",
"dd MMM yyyy hh:mm tt PDT"}
Dim dat As New Date(2016, 8, 18, 16, 50, 0)
' Display the result string.
Console.WriteLine(dat.ToString(formats(1)))

' Parse a string.


Dim value As String = "25 Dec 2016 12:00 pm PST"
Dim newDate As Date
If Date.TryParseExact(value, formats, Nothing,
DateTimeStyles.None, newDate) Then
Console.WriteLine(newDate)
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If
End Sub
End Module
' The example displays the following output:
' 18 Aug 2016 04:50 PM PDT
' 12/25/2016 12:00:00 PM

Hay dos formas de indicar que los caracteres se han de interpretar como caracteres literales y no como
caracteres de reserva, para que se puedan incluir en una cadena de resultado o analizarse correctamente en una
cadena de entrada:
Al incluir un escape con cada carácter reservado. Para obtener más información, consulte Usar el carácter de
escape.
En el ejemplo siguiente se incluyen los caracteres literales "pst" (para hora estándar del Pacífico) para
representar la zona horaria local en una cadena de formato. Como "s" y "t" son cadenas de formato
personalizado, ambos caracteres deben incluir un escape para interpretarse como literales de carácter.
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
String format = "dd MMM yyyy hh:mm tt p\\s\\t";
var dat = new DateTime(2016, 8, 18, 16, 50, 0);
// Display the result string.
Console.WriteLine(dat.ToString(format));

// Parse a string.
String value = "25 Dec 2016 12:00 pm pst";
DateTime newDate;
if (DateTime.TryParseExact(value, format, null,
DateTimeStyles.None, out newDate))
Console.WriteLine(newDate);
else
Console.WriteLine("Unable to parse '{0}'", value);
}
}
// The example displays the following output:
// 18 Aug 2016 04:50 PM PDT
// 12/25/2016 12:00:00 PM

Imports System.Globalization

Module Example
Public Sub Main()
Dim fmt As String = "dd MMM yyyy hh:mm tt p\s\t"
Dim dat As New Date(2016, 8, 18, 16, 50, 0)
' Display the result string.
Console.WriteLine(dat.ToString(fmt))

' Parse a string.


Dim value As String = "25 Dec 2016 12:00 pm pst"
Dim newDate As Date
If Date.TryParseExact(value, fmt, Nothing,
DateTimeStyles.None, newDate) Then
Console.WriteLine(newDate)
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If
End Sub
End Module
' The example displays the following output:
' 18 Aug 2016 04:50 PM pst
' 12/25/2016 12:00:00 PM

Al incluir toda la cadena literal entre comillas o apóstrofes. El siguiente ejemplo es igual al anterior, excepto
que "pst" se incluye entre comillas para indicar que toda la cadena delimitada debe interpretarse como
literales de carácter.
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
String format = "dd MMM yyyy hh:mm tt \"pst\"";
var dat = new DateTime(2016, 8, 18, 16, 50, 0);
// Display the result string.
Console.WriteLine(dat.ToString(format));

// Parse a string.
String value = "25 Dec 2016 12:00 pm pst";
DateTime newDate;
if (DateTime.TryParseExact(value, format, null,
DateTimeStyles.None, out newDate))
Console.WriteLine(newDate);
else
Console.WriteLine("Unable to parse '{0}'", value);
}
}
// The example displays the following output:
// 18 Aug 2016 04:50 PM PDT
// 12/25/2016 12:00:00 PM

Imports System.Globalization

Module Example
Public Sub Main()
Dim fmt As String = "dd MMM yyyy hh:mm tt ""pst"""
Dim dat As New Date(2016, 8, 18, 16, 50, 0)
' Display the result string.
Console.WriteLine(dat.ToString(fmt))

' Parse a string.


Dim value As String = "25 Dec 2016 12:00 pm pst"
Dim newDate As Date
If Date.TryParseExact(value, fmt, Nothing,
DateTimeStyles.None, newDate) Then
Console.WriteLine(newDate)
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If
End Sub
End Module
' The example displays the following output:
' 18 Aug 2016 04:50 PM pst
' 12/25/2016 12:00:00 PM

Notas
Usar especificadores de formato personalizado únicos
Una cadena con formato de fecha y hora personalizado se compone de dos o más caracteres. Los métodos de
formato de fecha y hora interpretan cualquier cadena de un único carácter como una cadena de formato de
fecha y hora estándar. Si no reconocen el carácter como un especificador de formato válido, producen una
excepción FormatException. Por ejemplo, una cadena de formato que solo se compone del especificador "h" se
interpreta como una cadena de formato de fecha y hora estándar. Sin embargo, en este caso concreto, se
produce una excepción porque no existe ningún especificador de formato de fecha y hora estándar "h".
Para usar cualquiera de los especificadores de formato de fecha y hora personalizado como el único
especificador en una cadena de formato (es decir, usar el especificador de formato personalizado "d", "f", "F", "g",
"h", "H", "K", "m", "M", "s", "t", "y", "z", ":" o "/"), incluya un espacio delante o detrás del especificador, o incluya un
especificador de formato de porcentaje ("%") delante del único especificador de fecha y hora personalizado.
Por ejemplo, " %h" se interpreta como una cadena de formato de fecha y hora personalizado que muestra la
hora representada por el valor de fecha y hora actual. También puede usar la cadena de formato " h" o "h ",
aunque esto incluye un espacio en la cadena de resultado junto con la hora. En el ejemplo siguiente se muestran
estas tres cadenas de formato.

DateTime dat1 = new DateTime(2009, 6, 15, 13, 45, 0);

Console.WriteLine("'{0:%h}'", dat1);
Console.WriteLine("'{0: h}'", dat1);
Console.WriteLine("'{0:h }'", dat1);
// The example displays the following output:
// '1'
// ' 1'
// '1 '

Dim dat1 As Date = #6/15/2009 1:45PM#

Console.WriteLine("'{0:%h}'", dat1)
Console.WriteLine("'{0: h}'", dat1)
Console.WriteLine("'{0:h }'", dat1)
' The example displays the following output:
' '1'
' ' 1'
' '1 '

Uso del carácter de escape


Los caracteres "d", "f", "F", "g", "h", "H", "K", "m", "M", "s", "t", "y", "z", ":" o "/" en una cadena de formato se
interpretan como especificadores de formato personalizado en lugar de como caracteres literales. Para evitar
que un carácter se interprete como un especificador de formato, puede precederlo de una barra diagonal
inversa (\), que es el carácter de escape. El carácter de escape significa que el siguiente carácter es un carácter
literal que se debe incluir en la cadena de resultado sin modificar.
Para incluir una barra diagonal inversa en una cadena de resultado, debe indicar su secuencia de escape con otra
barra diagonal inversa ( \\ ).

NOTE
Algunos compiladores, como los compiladores de C# y C++, también pueden interpretar un único carácter de barra
diagonal inversa como un carácter de escape. Para asegurarse de que una cadena se interpreta correctamente al darle
formato, puede usar el carácter literal de cadena textual (el carácter @) antes de la cadena en C# o puede agregar otro
carácter de barra diagonal inversa delante de cada barra diagonal inversa en C# y C++. En el siguiente ejemplo de C# se
muestran ambos enfoques.

En el ejemplo siguiente se usa el carácter de escape para evitar que la operación de formato interprete los
caracteres "h" y "m" como especificadores de formato.
DateTime date = new DateTime(2009, 06, 15, 13, 45, 30, 90);
string fmt1 = "h \\h m \\m";
string fmt2 = @"h \h m \m";

Console.WriteLine("{0} ({1}) -> {2}", date, fmt1, date.ToString(fmt1));


Console.WriteLine("{0} ({1}) -> {2}", date, fmt2, date.ToString(fmt2));
// The example displays the following output:
// 6/15/2009 1:45:30 PM (h \h m \m) -> 1 h 45 m
// 6/15/2009 1:45:30 PM (h \h m \m) -> 1 h 45 m

Dim date1 As Date = #6/15/2009 13:45#


Dim fmt As String = "h \h m \m"

Console.WriteLine("{0} ({1}) -> {2}", date1, fmt, date1.ToString(fmt))


' The example displays the following output:
' 6/15/2009 1:45:00 PM (h \h m \m) -> 1 h 45 m

Configuración del Panel de control


La configuración de Configuración regional y de idioma del Panel de control influye en la cadena de
resultado generada por una operación de formato que incluye muchos de los especificadores de formato de
fecha y hora personalizado. Estas configuraciones se utilizan para inicializar el objeto DateTimeFormatInfo
asociado a la referencia cultural del subproceso actual, que proporciona valores que se utilizan para controlar el
formato. Los equipos que usan configuraciones diferentes generarán cadenas de resultado distintas.
Asimismo, si se usa el constructor CultureInfo(String) para crear instancias de un nuevo objeto CultureInfo que
representa la misma referencia cultural que la referencia cultural del sistema actual, cualquier personalización
establecida por el elemento Configuración regional y de idioma del Panel de control se aplicará al nuevo
objeto CultureInfo . Puede usar el constructor CultureInfo(String, Boolean) para crear un objeto CultureInfo que
no refleje las personalizaciones de un sistema.
Propiedades de DateTimeFormatInfo
El formato se ve influenciado por las propiedades del objeto DateTimeFormatInfo actual, proporcionado
implícitamente por la referencia cultural del subproceso actual o explícitamente por el parámetro
IFormatProvider del método que invoca el formato. Para el parámetro IFormatProvider, debe especificar un
objeto CultureInfo, que representa una referencia cultural, o un objeto DateTimeFormatInfo.
La cadena de resultado generada por muchos de los especificadores de formato de fecha y hora personalizado
también depende de las propiedades del objeto DateTimeFormatInfo actual. La aplicación puede modificar el
resultado generado por algunos de los especificadores de formato de fecha y hora personalizado al cambiar la
propiedad DateTimeFormatInfo correspondiente. Por ejemplo, el especificador de formato "ddd" agrega a la
cadena de resultado el nombre abreviado de un día de la semana que se encuentra en la matriz de cadenas
AbbreviatedDayNames. De igual forma, el especificador de formato "MMMM" agrega a la cadena de resultado el
nombre completo de un mes que se encuentra en la matriz de cadenas MonthNames.

Vea también
System.DateTime
System.IFormatProvider
Aplicar formato a tipos
Cadenas con formato de fecha y hora estándar
Ejemplo: utilidad de formato WinForms de .NET Core (C#)
Ejemplo: utilidad de formato WinForms de .NET Core (Visual Basic)
Cadenas de formato TimeSpan estándar
16/09/2020 • 15 minutes to read • Edit Online

Una cadena de formato estándar TimeSpan usa un único especificador de formato para definir la representación
de texto de un valor TimeSpan resultante de una operación de formato. Cualquier cadena de formato que
contenga más de un carácter, incluido el espacio en blanco, se interpreta como una cadena de formato TimeSpan
personalizado. Para más información, consulte Cadenas de formato TimeSpan personalizadas.
Las representaciones de cadena de los valores TimeSpan se generan mediante llamadas a las sobrecargas del
método TimeSpan.ToString, y también mediante métodos que admiten formatos compuestos, como String.Format.
Para obtener más información, consulte Aplicar formato a tipos y Formatos compuestos. En el siguiente ejemplo
se muestra el uso de cadenas de formato estándar en operaciones de formato.

using System;

public class Example


{
public static void Main()
{
TimeSpan duration = new TimeSpan(1, 12, 23, 62);
string output = "Time of Travel: " + duration.ToString("c");
Console.WriteLine(output);

Console.WriteLine("Time of Travel: {0:c}", duration);


}
}
// The example displays the following output:
// Time of Travel: 1.12:24:02
// Time of Travel: 1.12:24:02

Module Example
Public Sub Main()
Dim duration As New TimeSpan(1, 12, 23, 62)
Dim output As String = "Time of Travel: " + duration.ToString("c")
Console.WriteLine(output)

Console.WriteLine("Time of Travel: {0:c}", duration)


End Sub
End Module
' The example displays the following output:
' Time of Travel: 1.12:24:02
' Time of Travel: 1.12:24:02

Los métodos TimeSpan y TimeSpan.ParseExact también usan cadenas de formato TimeSpan.TryParseExact


estándar para definir el formato que deben tener las cadenas de entrada de las operaciones de análisis. (Estas
operaciones convierten la representación de cadena de un valor en ese valor.) En el siguiente ejemplo, se muestra
el uso de cadenas de formato estándar en operaciones de análisis.
using System;

public class Example


{
public static void Main()
{
string value = "1.03:14:56.1667";
TimeSpan interval;
try {
interval = TimeSpan.ParseExact(value, "c", null);
Console.WriteLine("Converted '{0}' to {1}", value, interval);
}
catch (FormatException) {
Console.WriteLine("{0}: Bad Format", value);
}
catch (OverflowException) {
Console.WriteLine("{0}: Out of Range", value);
}

if (TimeSpan.TryParseExact(value, "c", null, out interval))


Console.WriteLine("Converted '{0}' to {1}", value, interval);
else
Console.WriteLine("Unable to convert {0} to a time interval.",
value);
}
}
// The example displays the following output:
// Converted '1.03:14:56.1667' to 1.03:14:56.1667000
// Converted '1.03:14:56.1667' to 1.03:14:56.1667000

Module Example
Public Sub Main()
Dim value As String = "1.03:14:56.1667"
Dim interval As TimeSpan
Try
interval = TimeSpan.ParseExact(value, "c", Nothing)
Console.WriteLine("Converted '{0}' to {1}", value, interval)
Catch e As FormatException
Console.WriteLine("{0}: Bad Format", value)
Catch e As OverflowException
Console.WriteLine("{0}: Out of Range", value)
End Try

If TimeSpan.TryParseExact(value, "c", Nothing, interval) Then


Console.WriteLine("Converted '{0}' to {1}", value, interval)
Else
Console.WriteLine("Unable to convert {0} to a time interval.",
value)
End If
End Sub
End Module
' The example displays the following output:
' Converted '1.03:14:56.1667' to 1.03:14:56.1667000
' Converted '1.03:14:56.1667' to 1.03:14:56.1667000

En la tabla siguiente se muestran los especificadores de formato de intervalo de tiempo estándar.

ESP EC IF IC A DO R DE
F O RM ATO N O M B RE DESC RIP C IÓ N E JEM P LO S
ESP EC IF IC A DO R DE
F O RM ATO N O M B RE DESC RIP C IÓ N E JEM P LO S

"c" Formato constante Este especificador no tiene TimeSpan.Zero ->


(invariable) en cuenta la referencia 00:00:00
cultural. Toma la forma
[-] New TimeSpan(0, 0, 30,
[d'.']hh':'mm':'ss['.'fffffff]0)
. -> 00:30:00

(Las cadenas de formato "t" New TimeSpan(3, 17, 25,


y "T" producen los mismos 30, 500)
resultados). -> 3.17:25:30.5000000

Más información:
Especificador de formato
constante ("c").

"g" Formato corto general Este especificador solo New TimeSpan(1, 3, 16,
genera lo necesario. Tiene en 50, 500)
cuenta la referencia cultural -> 1:3:16:50.5 (en-US)
y su forma es
[-] New TimeSpan(1, 3, 16,
[d':']h':'mm':'ss[.FFFFFFF] 50, 500)
. -> 1:3:16:50,5 (fr-FR)

Más información: New TimeSpan(1, 3, 16,


50, 599)
Especificador de formato
corto general ("g"). -> 1:3:16:50.599 (en-US)

New TimeSpan(1, 3, 16,


50, 599)
-> 1:3:16:50,599 (fr-FR)

"G" Formato general largo Este especificador siempre New TimeSpan(18, 30, 0)
genera días y siete dígitos -> 0:18:30:00.0000000 (en-
fraccionarios. Tiene en US)
cuenta la referencia cultural
y su forma es New TimeSpan(18, 30, 0)
[- -> 0:18:30:00,0000000 (fr-
]d':'hh':'mm':'ss.fffffff
FR)
.

Más información:
Especificador de formato
largo general ("G").

Especificador de formato constante ("c")


El especificador de formato "c" devuelve la representación de cadena de un valor TimeSpan de la siguiente forma:
[-][d.]hh:mm:ss[.fffffff]
Los elementos de los corchetes ([ y ]) son opcionales. El punto (.) y los dos puntos (:) son símbolos literales. En la
siguiente tabla se describen los elementos restantes.

EL EM EN TO DESC RIP C IÓ N

- Signo negativo opcional, que indica un intervalo de tiempo


negativo.
EL EM EN TO DESC RIP C IÓ N

d Número opcional de días, sin ceros a la izquierda.

hh Número de horas, entre "00" y "23".

mm Número de minutos, entre "00" y "59".

ss Número de segundos, entre "00" y "59".

fffffff La parte fraccionaria opcional de un segundo. Su valor puede


oscilar entre "0000001" (un tic o una diez millonésima de
segundo) y "9999999" (9.999.999 diez millonésimas de
segundo, o un segundo menos un tic).

A diferencia de los especificadores de formato de "g" y "G", el especificador de formato "c" no tiene en cuenta la
referencia cultural. Produce la representación de cadena de un valor TimeSpan que es invariable y común a todas
las versiones anteriores de .NET previas a .NET Framework 4. "c" es la cadena de formato TimeSpan
predeterminado; el método TimeSpan.ToString() da formato a un valor de intervalo de tiempo mediante la cadena
de formato "c".

NOTE
TimeSpan también admite las cadenas de formato estándar "t" y "T", cuyo comportamiento es idéntico al de la cadena de
formato estándar "c".

En el ejemplo siguiente se crea una instancia de dos objetos TimeSpan, que se usan para realizar operaciones
aritméticas y se muestra el resultado. En cada caso, se utiliza un formato compuesto para mostrar el valor
TimeSpan mediante el especificador de formato "c".

using System;

public class Example


{
public static void Main()
{
TimeSpan interval1, interval2;
interval1 = new TimeSpan(7, 45, 16);
interval2 = new TimeSpan(18, 12, 38);

Console.WriteLine("{0:c} - {1:c} = {2:c}", interval1,


interval2, interval1 - interval2);
Console.WriteLine("{0:c} + {1:c} = {2:c}", interval1,
interval2, interval1 + interval2);

interval1 = new TimeSpan(0, 0, 1, 14, 365);


interval2 = TimeSpan.FromTicks(2143756);
Console.WriteLine("{0:c} + {1:c} = {2:c}", interval1,
interval2, interval1 + interval2);
}
}
// The example displays the following output:
// 07:45:16 - 18:12:38 = -10:27:22
// 07:45:16 + 18:12:38 = 1.01:57:54
// 00:01:14.3650000 + 00:00:00.2143756 = 00:01:14.5793756
Module Example
Public Sub Main()
Dim interval1, interval2 As TimeSpan
interval1 = New TimeSpan(7, 45, 16)
interval2 = New TimeSpan(18, 12, 38)

Console.WriteLine("{0:c} - {1:c} = {2:c}", interval1,


interval2, interval1 - interval2)
Console.WriteLine("{0:c} + {1:c} = {2:c}", interval1,
interval2, interval1 + interval2)

interval1 = New TimeSpan(0, 0, 1, 14, 365)


interval2 = TimeSpan.FromTicks(2143756)
Console.WriteLine("{0:c} + {1:c} = {2:c}", interval1,
interval2, interval1 + interval2)
End Sub
End Module
' The example displays the following output:
' 07:45:16 - 18:12:38 = -10:27:22
' 07:45:16 + 18:12:38 = 1.01:57:54
' 00:01:14.3650000 + 00:00:00.2143756 = 00:01:14.5793756

Especificador de formato corto general ("g")


El especificador de formato TimeSpan "g" devuelve la representación de cadena de un valor TimeSpan en una
forma compacta, incluyendo únicamente los elementos que sean necesarios. Tiene la forma siguiente:
[-][d:]h:mm:ss[.FFFFFFF]
Los elementos de los corchetes ([ y ]) son opcionales. Los dos puntos (:) son un símbolo literal. En la siguiente
tabla se describen los elementos restantes.

EL EM EN TO DESC RIP C IÓ N

- Signo negativo opcional, que indica un intervalo de tiempo


negativo.

d Número opcional de días, sin ceros a la izquierda.

h El número de horas, entre "0" a "23", sin ceros a la izquierda.

mm Número de minutos, entre "00" y "59".

ss Número de segundos, entre "00" y "59".

. Separador de fracciones de segundo. Es equivalente a la


propiedad NumberDecimalSeparator de la referencia cultural
especificada sin invalidaciones del usuario.

FFFFFFF Fracciones de segundo. Se muestra la menor cantidad de


dígitos posible.

Al igual que el especificador de formato "G", el especificador de formato "g" se localiza. Su separador de fracciones
de segundo se basa en la referencia cultural actual o en la propiedad NumberDecimalSeparator de una referencia
cultural especificada.
En el ejemplo siguiente se crea una instancia de dos objetos TimeSpan, que se usan para realizar operaciones
aritméticas y se muestra el resultado. En cada caso, se utiliza un formato compuesto para mostrar el valor
TimeSpan mediante el especificador de formato "g". Además, se da formato al valor TimeSpan mediante las
convenciones de formato de la referencia cultural del sistema actual (que, en este caso, es inglés - Estados Unidos
o en-US) y la referencia cultural de Francia de francés (fr-FR).

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
TimeSpan interval1, interval2;
interval1 = new TimeSpan(7, 45, 16);
interval2 = new TimeSpan(18, 12, 38);

Console.WriteLine("{0:g} - {1:g} = {2:g}", interval1,


interval2, interval1 - interval2);
Console.WriteLine(String.Format(new CultureInfo("fr-FR"),
"{0:g} + {1:g} = {2:g}", interval1,
interval2, interval1 + interval2));

interval1 = new TimeSpan(0, 0, 1, 14, 36);


interval2 = TimeSpan.FromTicks(2143756);
Console.WriteLine("{0:g} + {1:g} = {2:g}", interval1,
interval2, interval1 + interval2);
}
}
// The example displays the following output:
// 7:45:16 - 18:12:38 = -10:27:22
// 7:45:16 + 18:12:38 = 1:1:57:54
// 0:01:14.036 + 0:00:00.2143756 = 0:01:14.2503756

Imports System.Globalization

Module Example
Public Sub Main()
Dim interval1, interval2 As TimeSpan
interval1 = New TimeSpan(7, 45, 16)
interval2 = New TimeSpan(18, 12, 38)

Console.WriteLine("{0:g} - {1:g} = {2:g}", interval1,


interval2, interval1 - interval2)
Console.WriteLine(String.Format(New CultureInfo("fr-FR"),
"{0:g} + {1:g} = {2:g}", interval1,
interval2, interval1 + interval2))

interval1 = New TimeSpan(0, 0, 1, 14, 36)


interval2 = TimeSpan.FromTicks(2143756)
Console.WriteLine("{0:g} + {1:g} = {2:g}", interval1,
interval2, interval1 + interval2)
End Sub
End Module
' The example displays the following output:
' 7:45:16 - 18:12:38 = -10:27:22
' 7:45:16 + 18:12:38 = 1:1:57:54
' 0:01:14.036 + 0:00:00.2143756 = 0:01:14.2503756

Especificador de formato largo general ("G")


El especificador de formato TimeSpan "G" devuelve la representación de cadena de un valor TimeSpan en un
formato largo que siempre incluye los días y las fracciones de segundo. La cadena resultante del especificador de
formato estándar "G" tiene la forma siguiente:
[-]d:hh:mm:ss.fffffff
Los elementos de los corchetes ([ y ]) son opcionales. Los dos puntos (:) son un símbolo literal. En la siguiente
tabla se describen los elementos restantes.

EL EM EN TO DESC RIP C IÓ N

- Signo negativo opcional, que indica un intervalo de tiempo


negativo.

d Número de días, sin ceros a la izquierda.

hh Número de horas, entre "00" y "23".

mm Número de minutos, entre "00" y "59".

ss Número de segundos, entre "00" y "59".

. Separador de fracciones de segundo. Es equivalente a la


propiedad NumberDecimalSeparator de la referencia cultural
especificada sin invalidaciones del usuario.

fffffff Fracciones de segundo.

Al igual que el especificador de formato "G", el especificador de formato "g" se localiza. Su separador de fracciones
de segundo se basa en la referencia cultural actual o en la propiedad NumberDecimalSeparator de una referencia
cultural especificada.
En el ejemplo siguiente se crea una instancia de dos objetos TimeSpan, que se usan para realizar operaciones
aritméticas y se muestra el resultado. En cada caso, se utiliza un formato compuesto para mostrar el valor
TimeSpan mediante el especificador de formato "G". Además, se da formato al valor TimeSpan mediante las
convenciones de formato de la referencia cultural del sistema actual (que, en este caso, es inglés - Estados Unidos
o en-US) y la referencia cultural de Francia de francés (fr-FR).
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
TimeSpan interval1, interval2;
interval1 = new TimeSpan(7, 45, 16);
interval2 = new TimeSpan(18, 12, 38);

Console.WriteLine("{0:G} - {1:G} = {2:G}", interval1,


interval2, interval1 - interval2);
Console.WriteLine(String.Format(new CultureInfo("fr-FR"),
"{0:G} + {1:G} = {2:G}", interval1,
interval2, interval1 + interval2));

interval1 = new TimeSpan(0, 0, 1, 14, 36);


interval2 = TimeSpan.FromTicks(2143756);
Console.WriteLine("{0:G} + {1:G} = {2:G}", interval1,
interval2, interval1 + interval2);
}
}
// The example displays the following output:
// 0:07:45:16.0000000 - 0:18:12:38.0000000 = -0:10:27:22.0000000
// 0:07:45:16,0000000 + 0:18:12:38,0000000 = 1:01:57:54,0000000
// 0:00:01:14.0360000 + 0:00:00:00.2143756 = 0:00:01:14.2503756

Imports System.Globalization

Module Example
Public Sub Main()
Dim interval1, interval2 As TimeSpan
interval1 = New TimeSpan(7, 45, 16)
interval2 = New TimeSpan(18, 12, 38)

Console.WriteLine("{0:G} - {1:G} = {2:G}", interval1,


interval2, interval1 - interval2)
Console.WriteLine(String.Format(New CultureInfo("fr-FR"),
"{0:G} + {1:G} = {2:G}", interval1,
interval2, interval1 + interval2))

interval1 = New TimeSpan(0, 0, 1, 14, 36)


interval2 = TimeSpan.FromTicks(2143756)
Console.WriteLine("{0:G} + {1:G} = {2:G}", interval1,
interval2, interval1 + interval2)
End Sub
End Module
' The example displays the following output:
' 0:07:45:16.0000000 - 0:18:12:38.0000000 = -0:10:27:22.0000000
' 0:07:45:16,0000000 + 0:18:12:38,0000000 = 1:01:57:54,0000000
' 0:00:01:14.0360000 + 0:00:00:00.2143756 = 0:00:01:14.2503756

Vea también
Aplicación de formato a tipos
Cadenas de formato TimeSpan personalizado
Analizar cadenas
Cadenas de formato TimeSpan personalizado
16/09/2020 • 64 minutes to read • Edit Online

Una cadena de formato TimeSpan define la representación de cadena de un valor TimeSpan generado por una
operación de formato. Una cadena de formato personalizado consta de uno o varios especificadores de formato
TimeSpan personalizado, además de un número de caracteres literales. Cualquier cadena que no sea una cadena
de formato TimeSpan estándar se interpreta como una cadena de formato TimeSpan personalizado.

IMPORTANT
Los especificadores de formato TimeSpan personalizado no incluyen símbolos de separador de marcadores de posición,
como los símbolos que separan los días de las horas, las horas de los minutos o los segundos de las fracciones de segundo.
Estos símbolos deben incluirse en la cadena de formato personalizado como literales de cadena. Por ejemplo,
"dd\.hh\:mm" define un punto (.) como separador entre los días y las horas, y un signo de dos puntos (:) como separador
entre las horas y los minutos.
Los especificadores de formato TimeSpan personalizado tampoco incluyen un símbolo de signo que permita distinguir entre
los intervalos de tiempo negativos y positivos. Para incluir un símbolo de signo, es necesario construir una cadena de
formato utilizando lógica condicional. En la sección Otros caracteres se incluye un ejemplo.

Las representaciones de cadena de los valores TimeSpan se generan mediante llamadas a las sobrecargas del
método TimeSpan.ToString, y también mediante métodos que admiten formatos compuestos, como
String.Format. Para obtener más información, consulte Aplicar formato a tipos y Formatos compuestos. En el
siguiente ejemplo, se muestra el uso de cadenas de formato personalizado en operaciones de formato.

using System;

public class Example


{
public static void Main()
{
TimeSpan duration = new TimeSpan(1, 12, 23, 62);

string output = null;


output = "Time of Travel: " + duration.ToString("%d") + " days";
Console.WriteLine(output);
output = "Time of Travel: " + duration.ToString(@"dd\.hh\:mm\:ss");
Console.WriteLine(output);

Console.WriteLine("Time of Travel: {0:%d} day(s)", duration);


Console.WriteLine("Time of Travel: {0:dd\\.hh\\:mm\\:ss} days", duration);
}
}
// The example displays the following output:
// Time of Travel: 1 days
// Time of Travel: 01.12:24:02
// Time of Travel: 1 day(s)
// Time of Travel: 01.12:24:02 days
Module Example
Public Sub Main()
Dim duration As New TimeSpan(1, 12, 23, 62)

Dim output As String = Nothing


output = "Time of Travel: " + duration.ToString("%d") + " days"
Console.WriteLine(output)
output = "Time of Travel: " + duration.ToString("dd\.hh\:mm\:ss")
Console.WriteLine(output)

Console.WriteLine("Time of Travel: {0:%d} day(s)", duration)


Console.WriteLine("Time of Travel: {0:dd\.hh\:mm\:ss} days", duration)
End Sub
End Module
' The example displays the following output:
' Time of Travel: 1 days
' Time of Travel: 01.12:24:02
' Time of Travel: 1 day(s)
' Time of Travel: 01.12:24:02 days

Los métodos TimeSpan y TimeSpan.ParseExact también usan cadenas de formato TimeSpan.TryParseExact


personalizadas para definir el formato que deben tener las cadenas de entrada de las operaciones de análisis.
(Estas operaciones convierten la representación de cadena de un valor en ese valor.) En el siguiente ejemplo, se
muestra el uso de cadenas de formato estándar en operaciones de análisis.

using System;

public class Example


{
public static void Main()
{
string value = null;
TimeSpan interval;

value = "6";
if (TimeSpan.TryParseExact(value, "%d", null, out interval))
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"));
else
Console.WriteLine("Unable to parse '{0}'", value);

value = "16:32.05";
if (TimeSpan.TryParseExact(value, @"mm\:ss\.ff", null, out interval))
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"));
else
Console.WriteLine("Unable to parse '{0}'", value);

value= "12.035";
if (TimeSpan.TryParseExact(value, "ss\\.fff", null, out interval))
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"));
else
Console.WriteLine("Unable to parse '{0}'", value);
}
}
// The example displays the following output:
// 6 --> 6.00:00:00
// 16:32.05 --> 00:16:32.0500000
// 12.035 --> 00:00:12.0350000
Module Example
Public Sub Main()
Dim value As String = Nothing
Dim interval As TimeSpan

value = "6"
If TimeSpan.TryParseExact(value, "%d", Nothing, interval) Then
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"))
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If

value = "16:32.05"
If TimeSpan.TryParseExact(value, "mm\:ss\.ff", Nothing, interval) Then
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"))
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If

value = "12.035"
If TimeSpan.TryParseExact(value, "ss\.fff", Nothing, interval) Then
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"))
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If
End Sub
End Module
' The example displays the following output:
' 6 --> 6.00:00:00
' 16:32.05 --> 00:16:32.0500000
' 12.035 --> 00:00:12.0350000

En la siguiente tabla se describen los especificadores de formato de fecha y hora personalizado.

ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO

"d", "%d" Número de días completos de un new TimeSpan(6, 14, 32, 17, 685):
intervalo de tiempo.
%d --> "6"
Más información: Especificador de
formato personalizado "d". d\.hh\:mm --> "6.14:32"

"dd"-"dddddddd" Número de días completos de un new TimeSpan(6, 14, 32, 17, 685):
intervalo de tiempo, que se completa
con tantos ceros iniciales como sean ddd --> "006"
necesarios.
dd\.hh\:mm --> "06.14:32"
Más información: Especificadores de
formato personalizado "dd"-
"dddddddd".

"h", "%h" Número de horas completas de un new TimeSpan(6, 14, 32, 17, 685):
intervalo de tiempo que no se cuentan
como parte de los días. Las horas con %h --> "14"
un solo dígito no se escriben con un
cero a la izquierda. hh\:mm --> "14:32"
Más información: Especificador de
formato personalizado "h".
ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO

"hh" Número de horas completas de un new TimeSpan(6, 14, 32, 17, 685):
intervalo de tiempo que no se cuentan
como parte de los días. Las horas con hh --> "14"
un solo dígito se escriben con un cero a
la izquierda. new TimeSpan(6, 8, 32, 17, 685):

Más información: Especificador de hh --> 08


formato personalizado "hh".

"m", "%m" Número de minutos completos de un new TimeSpan(6, 14, 8, 17, 685):
intervalo de tiempo que no se incluyen
como parte de las horas o los días. Los %m --> "8"
minutos con un solo dígito no se
escriben con un cero a la izquierda. h\:m --> "14:8"
Más información: Especificador de
formato personalizado "m".

"mm" Número de minutos completos de un new TimeSpan(6, 14, 8, 17, 685):


intervalo de tiempo que no se incluyen
como parte de las horas o los días. Los mm --> "08"
minutos con un solo dígito se escriben
con un cero a la izquierda. new TimeSpan(6, 8, 5, 17, 685):

Más información: Especificador de d\.hh\:mm\:ss --> 6.08:05:17


formato personalizado "mm".

"s", "%s" Número de segundos completos de un TimeSpan.FromSeconds(12.965) :


intervalo de tiempo que no se incluyen
como parte de las horas, los días o los %s --> 12
minutos. Los segundos con un solo
dígito no se escriben con un cero a la s\.fff --> 12.965
izquierda.

Más información: Especificador de


formato personalizado "s".

"ss" Número de segundos completos de un TimeSpan.FromSeconds(6.965) :


intervalo de tiempo que no se incluyen
como parte de las horas, los días o los ss --> 06
minutos. Los segundos con un solo
dígito se escriben con un cero a la ss\.fff --> 06.965
izquierda.

Más información: Especificador de


formato personalizado "ss".

"f", "%f" Décimas de segundo de un intervalo TimeSpan.FromSeconds(6.895) :


de tiempo.
f --> 8
Más información: Especificador de
formato personalizado "f". ss\.f --> 06.8

"ff" Centésimas de segundo de un TimeSpan.FromSeconds(6.895) :


intervalo de tiempo.
ff --> 89
Más información: Especificador de
formato personalizado "ff". ss\.ff --> 06.89
ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO

"fff" Milisegundos de un intervalo de TimeSpan.FromSeconds(6.895) :


tiempo.
fff --> 895
Más información: Especificador de
formato personalizado "fff". ss\.fff --> 06.895

"ffff" Diezmilésimas de segundo de un TimeSpan.Parse("0:0:6.8954321") :


intervalo de tiempo.
ffff --> 8954
Más información: Especificador de
formato personalizado "ffff". ss\.ffff --> 06.8954

"fffff" Cienmilésimas de segundo de un TimeSpan.Parse("0:0:6.8954321") :


intervalo de tiempo.
fffff --> 89543
Más información: Especificador de
formato personalizado "fffff". ss\.fffff --> 06.89543

"ffffff" Millonésimas de segundo de un TimeSpan.Parse("0:0:6.8954321") :


intervalo de tiempo.
ffffff --> 895432
Más información: Especificador de
formato personalizado "ffffff". ss\.ffffff --> 06.895432

"fffffff" Diezmillonésimas de segundo (o TimeSpan.Parse("0:0:6.8954321") :


fracciones de paso) de un intervalo de
tiempo. fffffff --> 8954321

Más información: Especificador de ss\.fffffff --> 06.8954321


formato personalizado "fffffff".

"F", "%F" Décimas de segundo de un intervalo TimeSpan.Parse("00:00:06.32") :


de tiempo. Si el dígito es cero, no se
muestra nada. %F :3

Más información: Especificador de TimeSpan.Parse("0:0:3.091") :


formato personalizado "F".
ss\.F : 03.

"FF" Centésimas de segundo de un TimeSpan.Parse("00:00:06.329") :


intervalo de tiempo. No se incluyen los
ceros finales fraccionarios ni los dígitos FF : 32
de dos ceros.
TimeSpan.Parse("0:0:3.101") :
Más información: Especificador de
formato personalizado "FF". ss\.FF : 03.1

"FFF" Milisegundos de un intervalo de TimeSpan.Parse("00:00:06.3291") :


tiempo. No se incluyen los ceros finales
fraccionarios. FFF : 329

Más información: TimeSpan.Parse("0:0:3.1009") :

ss\.FFF : 03.1
ESP EC IF IC A DO R DE F O RM ATO DESC RIP C IÓ N E JEM P LO

"FFFF" Diezmilésimas de segundo de un TimeSpan.Parse("00:00:06.32917") :


intervalo de tiempo. No se incluyen los
ceros finales fraccionarios. FFFFF : 3291

Más información: Especificador de TimeSpan.Parse("0:0:3.10009") :


formato personalizado "FFFF".
ss\.FFFF : 03.1

"FFFFF" Cienmilésimas de segundo de un TimeSpan.Parse("00:00:06.329179")


intervalo de tiempo. No se incluyen los :
ceros finales fraccionarios.
FFFFF : 32917
Más información: Especificador de
formato personalizado "FFFFF". TimeSpan.Parse("0:0:3.100009") :

ss\.FFFFF : 03.1

"FFFFFF" Millonésimas de segundo de un TimeSpan.Parse("00:00:06.3291791")


intervalo de tiempo. No se muestran :
los ceros finales fraccionarios.
FFFFFF : 329179
Más información: Especificador de
formato personalizado "FFFFFF". TimeSpan.Parse("0:0:3.1000009") :

ss\.FFFFFF : 03.1

"FFFFFFF" Diezmillonésimas de segundo de un TimeSpan.Parse("00:00:06.3291791")


intervalo de tiempo. No se muestran :
los ceros finales fraccionarios ni los
dígitos de siete ceros. FFFFFF : 3291791

Más información: Especificador de TimeSpan.Parse("0:0:3.1900000") :


formato personalizado "FFFFFFF".
ss\.FFFFFF : 03.19

'cadena' Delimitador de cadena literal. new TimeSpan(14, 32, 17):

Más información: Otros caracteres. hh':'mm':'ss --> "14:32:17"

\ El carácter de escape. new TimeSpan(14, 32, 17):

Más información: Otros caracteres. hh\:mm\:ss --> "14:32:17"

Cualquier otro carácter Cualquier otro carácter sin escape se new TimeSpan(14, 32, 17):
interpreta como especificador de
formato personalizado. hh\:mm\:ss --> "14:32:17"

Más información: Otros caracteres.

Especificador de formato personalizado "d"


El especificador de formato personalizado "d" presenta el valor de la propiedad TimeSpan.Days, que representa el
número de días completos de un intervalo de tiempo. Presenta el número completo de días de un valor
TimeSpan, incluso si el valor tiene más de un dígito. Si el valor de la propiedad TimeSpan.Days es cero, el
especificador presentará "0".
Si el especificador de formato personalizado "d" se utiliza solo, especifique "%d" de modo que no se interprete
por error como una cadena de formato estándar. Esto se muestra en el ejemplo siguiente.

TimeSpan ts1 = new TimeSpan(16, 4, 3, 17, 250);


Console.WriteLine(ts1.ToString("%d"));
// Displays 16

Dim ts As New TimeSpan(16, 4, 3, 17, 250)


Console.WriteLine(ts.ToString("%d"))
' Displays 16

En el siguiente ejemplo, se muestra el uso del especificador de formato personalizado "d".

TimeSpan ts2 = new TimeSpan(4, 3, 17);


Console.WriteLine(ts2.ToString(@"d\.hh\:mm\:ss"));

TimeSpan ts3 = new TimeSpan(3, 4, 3, 17);


Console.WriteLine(ts3.ToString(@"d\.hh\:mm\:ss"));
// The example displays the following output:
// 0.04:03:17
// 3.04:03:17

Dim ts2 As New TimeSpan(4, 3, 17)


Console.WriteLine(ts2.ToString("d\.hh\:mm\:ss"))

Dim ts3 As New TimeSpan(3, 4, 3, 17)


Console.WriteLine(ts3.ToString("d\.hh\:mm\:ss"))
' The example displays the following output:
' 0.04:03:17
' 3.04:03:17

Volver a la tabla

Especificadores de formato personalizado "dd"-"dddddddd"


Los especificadores de formato personalizado "dd", "ddd", "dddd", "ddddd", "dddddd", "ddddddd" y "dddddddd"
presentan el valor de la propiedad TimeSpan.Days, que representa el número de días completos de un intervalo
de tiempo.
La cadena de salida incluye un número mínimo de dígitos que viene determinado por el número de caracteres "d"
en el especificador de formato y se completa con tantos ceros iniciales como sean necesarios. Si los dígitos del
número de días superan el número de caracteres "d" en el especificador de formato, la cadena de resultado
mostrará el número completo de días.
En el siguiente ejemplo, se utilizan estos especificadores de formato para mostrar la representación de cadena de
dos valores TimeSpan. El valor del componente de días en el primer intervalo de tiempo es cero; el valor del
componente de días en el segundo es 365.
TimeSpan ts1 = new TimeSpan(0, 23, 17, 47);
TimeSpan ts2 = new TimeSpan(365, 21, 19, 45);

for (int ctr = 2; ctr <= 8; ctr++)


{
string fmt = new String('d', ctr) + @"\.hh\:mm\:ss";
Console.WriteLine("{0} --> {1:" + fmt + "}", fmt, ts1);
Console.WriteLine("{0} --> {1:" + fmt + "}", fmt, ts2);
Console.WriteLine();
}
// The example displays the following output:
// dd\.hh\:mm\:ss --> 00.23:17:47
// dd\.hh\:mm\:ss --> 365.21:19:45
//
// ddd\.hh\:mm\:ss --> 000.23:17:47
// ddd\.hh\:mm\:ss --> 365.21:19:45
//
// dddd\.hh\:mm\:ss --> 0000.23:17:47
// dddd\.hh\:mm\:ss --> 0365.21:19:45
//
// ddddd\.hh\:mm\:ss --> 00000.23:17:47
// ddddd\.hh\:mm\:ss --> 00365.21:19:45
//
// dddddd\.hh\:mm\:ss --> 000000.23:17:47
// dddddd\.hh\:mm\:ss --> 000365.21:19:45
//
// ddddddd\.hh\:mm\:ss --> 0000000.23:17:47
// ddddddd\.hh\:mm\:ss --> 0000365.21:19:45
//
// dddddddd\.hh\:mm\:ss --> 00000000.23:17:47
// dddddddd\.hh\:mm\:ss --> 00000365.21:19:45

Dim ts1 As New TimeSpan(0, 23, 17, 47)


Dim ts2 As New TimeSpan(365, 21, 19, 45)

For ctr As Integer = 2 To 8


Dim fmt As String = New String("d"c, ctr) + "\.hh\:mm\:ss"
Console.WriteLine("{0} --> {1:" + fmt + "}", fmt, ts1)
Console.WriteLine("{0} --> {1:" + fmt + "}", fmt, ts2)
Console.WriteLine()
Next
' The example displays the following output:
' dd\.hh\:mm\:ss --> 00.23:17:47
' dd\.hh\:mm\:ss --> 365.21:19:45
'
' ddd\.hh\:mm\:ss --> 000.23:17:47
' ddd\.hh\:mm\:ss --> 365.21:19:45
'
' dddd\.hh\:mm\:ss --> 0000.23:17:47
' dddd\.hh\:mm\:ss --> 0365.21:19:45
'
' ddddd\.hh\:mm\:ss --> 00000.23:17:47
' ddddd\.hh\:mm\:ss --> 00365.21:19:45
'
' dddddd\.hh\:mm\:ss --> 000000.23:17:47
' dddddd\.hh\:mm\:ss --> 000365.21:19:45
'
' ddddddd\.hh\:mm\:ss --> 0000000.23:17:47
' ddddddd\.hh\:mm\:ss --> 0000365.21:19:45
'
' dddddddd\.hh\:mm\:ss --> 00000000.23:17:47
' dddddddd\.hh\:mm\:ss --> 00000365.21:19:45

Volver a la tabla
Especificador de formato personalizado "h"
El especificador de formato personalizado "h" presenta el valor de la propiedad TimeSpan.Hours, que representa
el número de horas completas de un intervalo de tiempo que no se cuentan como parte del componente de días.
Devuelve un valor de cadena de un dígito si el valor de la propiedad TimeSpan.Hours es de 0 a 9; devuelve un
valor de cadena de dos dígitos si el valor de la propiedad TimeSpan.Hours es de 10 a 23.
Si el especificador de formato personalizado "h" se utiliza solo, especifique "%h" de modo que no se interprete
por error como una cadena de formato estándar. Esto se muestra en el ejemplo siguiente.

TimeSpan ts = new TimeSpan(3, 42, 0);


Console.WriteLine("{0:%h} hours {0:%m} minutes", ts);
// The example displays the following output:
// 3 hours 42 minutes

Dim ts As New TimeSpan(3, 42, 0)


Console.WriteLine("{0:%h} hours {0:%m} minutes", ts)
' The example displays the following output:
' 3 hours 42 minutes

Normalmente, en las operaciones de análisis, una cadena de entrada que incluye solamente un número se
interpreta como número de días. Se puede utilizar el especificador de formato personalizado "%h"" para que la
cadena numérica se interprete como número de horas. Esto se muestra en el ejemplo siguiente.

string value = "8";


TimeSpan interval;
if (TimeSpan.TryParseExact(value, "%h", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
// The example displays the following output:
// 08:00:00

Dim value As String = "8"


Dim interval As TimeSpan
If TimeSpan.TryParseExact(value, "%h", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
' The example displays the following output:
' 08:00:00

En el siguiente ejemplo, se muestra el uso del especificador de formato personalizado "h".

TimeSpan ts1 = new TimeSpan(14, 3, 17);


Console.WriteLine(ts1.ToString(@"d\.h\:mm\:ss"));

TimeSpan ts2 = new TimeSpan(3, 4, 3, 17);


Console.WriteLine(ts2.ToString(@"d\.h\:mm\:ss"));
// The example displays the following output:
// 0.14:03:17
// 3.4:03:17
Dim ts1 As New TimeSpan(14, 3, 17)
Console.WriteLine(ts1.ToString("d\.h\:mm\:ss"))

Dim ts2 As New TimeSpan(3, 4, 3, 17)


Console.WriteLine(ts2.ToString("d\.h\:mm\:ss"))
' The example displays the following output:
' 0.14:03:17
' 3.4:03:17

Volver a la tabla

Especificador de formato personalizado "hh"


El especificador de formato personalizado "hh" presenta el valor de la propiedad TimeSpan.Hours, que representa
el número de horas completas de un intervalo de tiempo que no se cuentan como parte del componente de días.
Para los valores de 0 a 9, la cadena de salida incluye un cero inicial.
Normalmente, en las operaciones de análisis, una cadena de entrada que incluye solamente un número se
interpreta como número de días. Se puede utilizar el especificador de formato personalizado "hh"" para que la
cadena numérica se interprete como número de horas. Esto se muestra en el ejemplo siguiente.

string value = "08";


TimeSpan interval;
if (TimeSpan.TryParseExact(value, "hh", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
// The example displays the following output:
// 08:00:00

Dim value As String = "08"


Dim interval As TimeSpan
If TimeSpan.TryParseExact(value, "hh", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
' The example displays the following output:
' 08:00:00

En el siguiente ejemplo, se muestra el uso del especificador de formato personalizado "hh".

TimeSpan ts1 = new TimeSpan(14, 3, 17);


Console.WriteLine(ts1.ToString(@"d\.hh\:mm\:ss"));

TimeSpan ts2 = new TimeSpan(3, 4, 3, 17);


Console.WriteLine(ts2.ToString(@"d\.hh\:mm\:ss"));
// The example displays the following output:
// 0.14:03:17
// 3.04:03:17
Dim ts1 As New TimeSpan(14, 3, 17)
Console.WriteLine(ts1.ToString("d\.hh\:mm\:ss"))

Dim ts2 As New TimeSpan(3, 4, 3, 17)


Console.WriteLine(ts2.ToString("d\.hh\:mm\:ss"))
' The example displays the following output:
' 0.14:03:17
' 3.04:03:17

Volver a la tabla

Especificador de formato personalizado "m"


El especificador de formato personalizado "m" presenta el valor de la propiedad TimeSpan.Minutes, que
representa el número de minutos completos de un intervalo de tiempo que no se cuentan como parte del
componente de días. Devuelve un valor de cadena de un dígito si el valor de la propiedad TimeSpan.Minutes es
de 0 a 9; devuelve un valor de cadena de dos dígitos si el valor de la propiedad TimeSpan.Minutes es de 10 a 59.
Si el especificador de formato personalizado "m" se utiliza solo, especifique "%m" de modo que no se interprete
por error como una cadena de formato estándar. Esto se muestra en el ejemplo siguiente.

TimeSpan ts = new TimeSpan(3, 42, 0);


Console.WriteLine("{0:%h} hours {0:%m} minutes", ts);
// The example displays the following output:
// 3 hours 42 minutes

Dim ts As New TimeSpan(3, 42, 0)


Console.WriteLine("{0:%h} hours {0:%m} minutes", ts)
' The example displays the following output:
' 3 hours 42 minutes

Normalmente, en las operaciones de análisis, una cadena de entrada que incluye solamente un número se
interpreta como número de días. Se puede utilizar el especificador de formato personalizado "%m"" para que la
cadena numérica se interprete como número de minutos. Esto se muestra en el ejemplo siguiente.

string value = "3";


TimeSpan interval;
if (TimeSpan.TryParseExact(value, "%m", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
// The example displays the following output:
// 00:03:00

Dim value As String = "3"


Dim interval As TimeSpan
If TimeSpan.TryParseExact(value, "%m", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
' The example displays the following output:
' 00:03:00
En el siguiente ejemplo, se muestra el uso del especificador de formato personalizado "m".

TimeSpan ts1 = new TimeSpan(0, 6, 32);


Console.WriteLine("{0:m\\:ss} minutes", ts1);

TimeSpan ts2 = new TimeSpan(3, 4, 3, 17);


Console.WriteLine("Elapsed time: {0:m\\:ss}", ts2);
// The example displays the following output:
// 6:32 minutes
// Elapsed time: 18:44

Dim ts1 As New TimeSpan(0, 6, 32)


Console.WriteLine("{0:m\:ss} minutes", ts1)

Dim ts2 As New TimeSpan(0, 18, 44)


Console.WriteLine("Elapsed time: {0:m\:ss}", ts2)
' The example displays the following output:
' 6:32 minutes
' Elapsed time: 18:44

Volver a la tabla

Especificador de formato personalizado "mm"


El especificador de formato personalizado "mm" presenta el valor de la propiedad TimeSpan.Minutes, que
representa el número de minutos completos de un intervalo de tiempo que no se cuentan como parte del
componente de horas o días. Para los valores de 0 a 9, la cadena de salida incluye un cero inicial.
Normalmente, en las operaciones de análisis, una cadena de entrada que incluye solamente un número se
interpreta como número de días. Se puede utilizar el especificador de formato personalizado "mm"" para que la
cadena numérica se interprete como número de minutos. Esto se muestra en el ejemplo siguiente.

string value = "07";


TimeSpan interval;
if (TimeSpan.TryParseExact(value, "mm", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
// The example displays the following output:
// 00:07:00

Dim value As String = "05"


Dim interval As TimeSpan
If TimeSpan.TryParseExact(value, "mm", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
' The example displays the following output:
' 00:05:00

En el siguiente ejemplo, se muestra el uso del especificador de formato personalizado "mm".


TimeSpan departTime = new TimeSpan(11, 12, 00);
TimeSpan arriveTime = new TimeSpan(16, 28, 00);
Console.WriteLine("Travel time: {0:hh\\:mm}",
arriveTime - departTime);
// The example displays the following output:
// Travel time: 05:16

Dim departTime As New TimeSpan(11, 12, 00)


Dim arriveTime As New TimeSpan(16, 28, 00)
Console.WriteLine("Travel time: {0:hh\:mm}",
arriveTime - departTime)
' The example displays the following output:
' Travel time: 05:16

Volver a la tabla

Especificador de formato personalizado "s"


El especificador de formato personalizado "s" presenta el valor de la propiedad TimeSpan.Seconds, que
representa el número de segundos completos de un intervalo de tiempo que no se cuentan como parte del
componente de horas, días o minutos. Devuelve un valor de cadena de un dígito si el valor de la propiedad
TimeSpan.Seconds es de 0 a 9; devuelve un valor de cadena de dos dígitos si el valor de la propiedad
TimeSpan.Seconds es de 10 a 59.
Si el especificador de formato personalizado "s" se utiliza solo, especifique "%s" de modo que no se interprete por
error como una cadena de formato estándar. Esto se muestra en el ejemplo siguiente.

TimeSpan ts = TimeSpan.FromSeconds(12.465);
Console.WriteLine(ts.ToString("%s"));
// The example displays the following output:
// 12

Dim ts As TimeSpan = TimeSpan.FromSeconds(12.465)


Console.WriteLine(ts.ToString("%s"))
' The example displays the following output:
' 12

Normalmente, en las operaciones de análisis, una cadena de entrada que incluye solamente un número se
interpreta como número de días. Se puede utilizar el especificador de formato personalizado "%s"" para que la
cadena numérica se interprete como número de segundos. Esto se muestra en el ejemplo siguiente.

string value = "9";


TimeSpan interval;
if (TimeSpan.TryParseExact(value, "%s", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
// The example displays the following output:
// 00:00:09
Dim value As String = "9"
Dim interval As TimeSpan
If TimeSpan.TryParseExact(value, "%s", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
' The example displays the following output:
' 00:00:09

En el siguiente ejemplo, se muestra el uso del especificador de formato personalizado "s".

TimeSpan startTime = new TimeSpan(0, 12, 30, 15, 0);


TimeSpan endTime = new TimeSpan(0, 12, 30, 21, 3);
Console.WriteLine(@"Elapsed Time: {0:s\:fff} seconds",
endTime - startTime);
// The example displays the following output:
// Elapsed Time: 6:003 seconds

Dim startTime As New TimeSpan(0, 12, 30, 15, 0)


Dim endTime As New TimeSpan(0, 12, 30, 21, 3)
Console.WriteLine("Elapsed Time: {0:s\:fff} seconds",
endTime - startTime)
' The example displays the following output:
' Elapsed Time: 6:003 seconds

Volver a la tabla

Especificador de formato personalizado "ss"


El especificador de formato personalizado "ss" presenta el valor de la propiedad TimeSpan.Seconds, que
representa el número de segundos completos de un intervalo de tiempo que no se cuentan como parte del
componente de horas, días o minutos. Para los valores de 0 a 9, la cadena de salida incluye un cero inicial.
Normalmente, en las operaciones de análisis, una cadena de entrada que incluye solamente un número se
interpreta como número de días. Se puede utilizar el especificador de formato personalizado "ss"" para que la
cadena numérica se interprete como número de segundos. Esto se muestra en el ejemplo siguiente.

string[] values = { "49", "9", "06" };


TimeSpan interval;
foreach (string value in values)
{
if (TimeSpan.TryParseExact(value, "ss", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
}
// The example displays the following output:
// 00:00:49
// Unable to convert '9' to a time interval
// 00:00:06
Dim values() As String = {"49", "9", "06"}
Dim interval As TimeSpan
For Each value As String In values
If TimeSpan.TryParseExact(value, "ss", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
Next
' The example displays the following output:
' 00:00:49
' Unable to convert '9' to a time interval
' 00:00:06

En el siguiente ejemplo, se muestra el uso del especificador de formato personalizado "ss".

TimeSpan interval1 = TimeSpan.FromSeconds(12.60);


Console.WriteLine(interval1.ToString(@"ss\.fff"));

TimeSpan interval2 = TimeSpan.FromSeconds(6.485);


Console.WriteLine(interval2.ToString(@"ss\.fff"));
// The example displays the following output:
// 12.600
// 06.485

Dim interval1 As TimeSpan = TimeSpan.FromSeconds(12.60)


Console.WriteLine(interval1.ToString("ss\.fff"))
Dim interval2 As TimeSpan = TimeSpan.FromSeconds(6.485)
Console.WriteLine(interval2.ToString("ss\.fff"))
' The example displays the following output:
' 12.600
' 06.485

Volver a la tabla

Especificador de formato personalizado "f"


El especificador de formato personalizado "f" presenta las décimas de segundo de un intervalo de tiempo. En una
operación de formato, se truncan los dígitos fraccionarios restantes. En una operación de análisis que llama al
método TimeSpan.ParseExact o TimeSpan.TryParseExact, la cadena de entrada debe contener exactamente un
dígito fraccionario.
Si el especificador de formato personalizado "f" se utiliza solo, especifique "%f" de modo que no se interprete por
error como una cadena de formato estándar.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "f" para mostrar las décimas de
segundo de un valor TimeSpan. "f" se usa primero solo y luego junto con el especificador "s" en una cadena de
formato personalizado.
TimeSpan ts = new TimeSpan(1003498765432);
string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432

Dim ts As New TimeSpan(1003498765432)


Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432
Volver a la tabla

Especificador de formato personalizado "ff"


El especificador de formato personalizado "ff" presenta las centésimas de segundo de un intervalo de tiempo. En
una operación de formato, se truncan los dígitos fraccionarios restantes. En una operación de análisis que llama al
método TimeSpan.ParseExact o TimeSpan.TryParseExact, la cadena de entrada debe contener exactamente dos
dígitos fraccionarios.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "ff" para mostrar las centésimas de
segundo de un valor TimeSpan. "ff" se usa primero solo luego junto con el especificador "s" en una cadena de
formato personalizado.

TimeSpan ts = new TimeSpan(1003498765432);


string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432
Dim ts As New TimeSpan(1003498765432)
Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432

Volver a la tabla

Especificador de formato personalizado "fff"


El especificador de formato personalizado "fff" (tres caracteres "f") presenta las milésimas de segundo de un
intervalo de tiempo. En una operación de formato, se truncan los dígitos fraccionarios restantes. En una operación
de análisis que llama al método TimeSpan.ParseExact o TimeSpan.TryParseExact, la cadena de entrada debe
contener exactamente tres dígitos fraccionarios.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "fff" para mostrar los milisegundos
de un valor TimeSpan. "fff" se usa primero solo y luego junto con el especificador "s" en una cadena de formato
personalizado.
TimeSpan ts = new TimeSpan(1003498765432);
string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432

Dim ts As New TimeSpan(1003498765432)


Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432
Volver a la tabla

Especificador de formato personalizado "ffff"


El especificador de formato personalizado "ffff" (cuatro caracteres "f") presenta las diezmilésimas de segundo de
un intervalo de tiempo. En una operación de formato, se truncan los dígitos fraccionarios restantes. En una
operación de análisis que llama al método TimeSpan.ParseExact o TimeSpan.TryParseExact, la cadena de entrada
debe contener exactamente cuatro dígitos fraccionarios.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "ffff" para mostrar las diezmilésimas
de segundo de un valor TimeSpan. "ffff" se usa primero solo y luego junto con el especificador "s" en una cadena
de formato personalizado.

TimeSpan ts = new TimeSpan(1003498765432);


string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432
Dim ts As New TimeSpan(1003498765432)
Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432

Volver a la tabla

Especificador de formato personalizado "fffff"


El especificador de formato personalizado "fffff" (cinco caracteres "f") presenta las cienmilésimas de segundo de
un intervalo de tiempo. En una operación de formato, se truncan los dígitos fraccionarios restantes. En una
operación de análisis que llama al método TimeSpan.ParseExact o TimeSpan.TryParseExact, la cadena de entrada
debe contener exactamente cinco dígitos fraccionarios.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "fffff" para mostrar las cienmilésimas
de segundo de un valor TimeSpan. "fffff" se usa primero solo y luego junto con el especificador "s" en una cadena
de formato personalizado.
TimeSpan ts = new TimeSpan(1003498765432);
string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432

Dim ts As New TimeSpan(1003498765432)


Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432
Volver a la tabla

Especificador de formato personalizado "ffffff"


El especificador de formato personalizado "ffffff" (seis caracteres "f") presenta las millonésimas de segundo de un
intervalo de tiempo. En una operación de formato, se truncan los dígitos fraccionarios restantes. En una operación
de análisis que llama al método TimeSpan.ParseExact o TimeSpan.TryParseExact, la cadena de entrada debe
contener exactamente seis dígitos fraccionarios.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "ffffff" para mostrar las millonésimas
de segundo de un valor TimeSpan. Este especificador de formato se utiliza primero solo y, a continuación, junto
con el especificador "s" en una cadena de formato personalizado.

TimeSpan ts = new TimeSpan(1003498765432);


string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432
Dim ts As New TimeSpan(1003498765432)
Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432

Volver a la tabla

Especificador de formato personalizado "fffffff"


El especificador de formato personalizado "fffffff" (siete caracteres "f") presenta las diezmillonésimas de segundo
(o fracciones de paso) de un intervalo de tiempo. En una operación de análisis que llama al método
TimeSpan.ParseExact o TimeSpan.TryParseExact, la cadena de entrada debe contener exactamente siete dígitos
fraccionarios.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "fffffff" para mostrar las fracciones de
paso de un valor TimeSpan. Este especificador de formato se utiliza primero solo y, a continuación, junto con el
especificador "s" en una cadena de formato personalizado.
TimeSpan ts = new TimeSpan(1003498765432);
string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432

Dim ts As New TimeSpan(1003498765432)


Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432
Volver a la tabla

Especificador de formato personalizado "F"


El especificador de formato personalizado "F" presenta las décimas de segundo de un intervalo de tiempo. En una
operación de formato, se truncan los dígitos fraccionarios restantes. Si el valor de las décimas de segundo de un
intervalo de tiempo es cero, no se incluirá en la cadena de resultado. En una operación de análisis que llama al
método TimeSpan.ParseExact o TimeSpan.TryParseExact, la presencia de las décimas de segundo es opcional.
Si el especificador de formato personalizado "F" se utiliza solo, especifique "%F" de modo que no se interprete por
error como una cadena de formato estándar.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "F" para mostrar las décimas de
segundo de un valor TimeSpan. También se utiliza este especificador de formato personalizado en una operación
de análisis.

Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.669");
Console.WriteLine("{0} ('%F') --> {0:%F}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.091");


Console.WriteLine("{0} ('ss\\.F') --> {0:ss\\.F}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.1", "0:0:03.12" };
string fmt = @"h\:m\:ss\.F";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6690000 ('%F') --> 6
// 00:00:03.0910000 ('ss\.F') --> 03.
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.F') --> 00:00:03
// 0:0:03.1 ('h\:m\:ss\.F') --> 00:00:03.1000000
// Cannot parse 0:0:03.12 with 'h\:m\:ss\.F'.
Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.669")
Console.WriteLine("{0} ('%F') --> {0:%F}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.091")


Console.WriteLine("{0} ('ss\.F') --> {0:ss\.F}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.1", "0:0:03.12"}
Dim fmt As String = "h\:m\:ss\.F"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6690000 ('%F') --> 6
' 00:00:03.0910000 ('ss\.F') --> 03.
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.F') --> 00:00:03
' 0:0:03.1 ('h\:m\:ss\.F') --> 00:00:03.1000000
' Cannot parse 0:0:03.12 with 'h\:m\:ss\.F'.

Volver a la tabla

Especificador de formato personalizado "FF"


El especificador de formato personalizado "FF" presenta las centésimas de segundo de un intervalo de tiempo. En
una operación de formato, se truncan los dígitos fraccionarios restantes. Si hay ceros fraccionarios finales, estos
no se incluyen en la cadena de resultado. En una operación de análisis que llama al método TimeSpan.ParseExact
o TimeSpan.TryParseExact, la presencia de las décimas y centésimas de segundo es opcional.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "FF" para mostrar las centésimas de
segundo de un valor TimeSpan. También se utiliza este especificador de formato personalizado en una operación
de análisis.
Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.697");
Console.WriteLine("{0} ('FF') --> {0:FF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.809");


Console.WriteLine("{0} ('ss\\.FF') --> {0:ss\\.FF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.1", "0:0:03.127" };
string fmt = @"h\:m\:ss\.FF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6970000 ('FF') --> 69
// 00:00:03.8090000 ('ss\.FF') --> 03.8
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FF') --> 00:00:03
// 0:0:03.1 ('h\:m\:ss\.FF') --> 00:00:03.1000000
// Cannot parse 0:0:03.127 with 'h\:m\:ss\.FF'.

Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.697")
Console.WriteLine("{0} ('FF') --> {0:FF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.809")


Console.WriteLine("{0} ('ss\.FF') --> {0:ss\.FF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.1", "0:0:03.127"}
Dim fmt As String = "h\:m\:ss\.FF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6970000 ('FF') --> 69
' 00:00:03.8090000 ('ss\.FF') --> 03.8
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FF') --> 00:00:03
' 0:0:03.1 ('h\:m\:ss\.FF') --> 00:00:03.1000000
' Cannot parse 0:0:03.127 with 'h\:m\:ss\.FF'.

Volver a la tabla
Especificador de formato personalizado "FFF"
El especificador de formato personalizado "FFF" (tres caracteres "F") presenta las milésimas de segundo de un
intervalo de tiempo. En una operación de formato, se truncan los dígitos fraccionarios restantes. Si hay ceros
fraccionarios finales, estos no se incluyen en la cadena de resultado. En una operación de análisis que llama al
método TimeSpan.ParseExact o TimeSpan.TryParseExact, la presencia de las décimas, centésimas y milésimas de
segundo es opcional.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "FFF" para mostrar las milésimas de
segundo de un valor TimeSpan. También se utiliza este especificador de formato personalizado en una operación
de análisis.

Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.6974");
Console.WriteLine("{0} ('FFF') --> {0:FFF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.8009");


Console.WriteLine("{0} ('ss\\.FFF') --> {0:ss\\.FFF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.12", "0:0:03.1279" };
string fmt = @"h\:m\:ss\.FFF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6974000 ('FFF') --> 697
// 00:00:03.8009000 ('ss\.FFF') --> 03.8
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FFF') --> 00:00:03
// 0:0:03.12 ('h\:m\:ss\.FFF') --> 00:00:03.1200000
// Cannot parse 0:0:03.1279 with 'h\:m\:ss\.FFF'.
Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.6974")
Console.WriteLine("{0} ('FFF') --> {0:FFF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.8009")


Console.WriteLine("{0} ('ss\.FFF') --> {0:ss\.FFF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.12", "0:0:03.1279"}
Dim fmt As String = "h\:m\:ss\.FFF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6974000 ('FFF') --> 697
' 00:00:03.8009000 ('ss\.FFF') --> 03.8
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FFF') --> 00:00:03
' 0:0:03.12 ('h\:m\:ss\.FFF') --> 00:00:03.1200000
' Cannot parse 0:0:03.1279 with 'h\:m\:ss\.FFF'.

Volver a la tabla

Especificador de formato personalizado "FFFF"


El especificador de formato personalizado "FFFF" (cuatro caracteres "F") presenta las diezmilésimas de segundo
de un intervalo de tiempo. En una operación de formato, se truncan los dígitos fraccionarios restantes. Si hay
ceros fraccionarios finales, estos no se incluyen en la cadena de resultado. En una operación de análisis que llama
al método TimeSpan.ParseExact o TimeSpan.TryParseExact, la presencia de las décimas, centésimas, milésimas y
diezmilésimas de segundo es opcional.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "FFFF" para mostrar las
diezmilésimas de segundo de un valor TimeSpan. También se utiliza este especificador de formato personalizado
en una operación de análisis.
Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.69749");
Console.WriteLine("{0} ('FFFF') --> {0:FFFF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.80009");


Console.WriteLine("{0} ('ss\\.FFFF') --> {0:ss\\.FFFF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.12", "0:0:03.12795" };
string fmt = @"h\:m\:ss\.FFFF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6974900 ('FFFF') --> 6974
// 00:00:03.8000900 ('ss\.FFFF') --> 03.8
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FFFF') --> 00:00:03
// 0:0:03.12 ('h\:m\:ss\.FFFF') --> 00:00:03.1200000
// Cannot parse 0:0:03.12795 with 'h\:m\:ss\.FFFF'.

Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.69749")
Console.WriteLine("{0} ('FFFF') --> {0:FFFF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.80009")


Console.WriteLine("{0} ('ss\.FFFF') --> {0:ss\.FFFF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.12", "0:0:03.12795"}
Dim fmt As String = "h\:m\:ss\.FFFF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6974900 ('FFFF') --> 6974
' 00:00:03.8000900 ('ss\.FFFF') --> 03.8
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FFFF') --> 00:00:03
' 0:0:03.12 ('h\:m\:ss\.FFFF') --> 00:00:03.1200000
' Cannot parse 0:0:03.12795 with 'h\:m\:ss\.FFFF'.

Volver a la tabla
Especificador de formato personalizado "FFFFF"
El especificador de formato personalizado "FFFFF" (cinco caracteres "F") presenta las cienmilésimas de segundo
de un intervalo de tiempo. En una operación de formato, se truncan los dígitos fraccionarios restantes. Si hay
ceros fraccionarios finales, estos no se incluyen en la cadena de resultado. En una operación de análisis que llama
al método TimeSpan.ParseExact o TimeSpan.TryParseExact, la presencia de las décimas, centésimas, milésimas,
diezmilésimas y cienmilésimas de segundo es opcional.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "FFFFF" para mostrar las
cienmilésimas de segundo de un valor TimeSpan. También se utiliza este especificador de formato personalizado
en una operación de análisis.

Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.697497");
Console.WriteLine("{0} ('FFFFF') --> {0:FFFFF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.800009");


Console.WriteLine("{0} ('ss\\.FFFFF') --> {0:ss\\.FFFFF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.12", "0:0:03.127956" };
string fmt = @"h\:m\:ss\.FFFFF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6974970 ('FFFFF') --> 69749
// 00:00:03.8000090 ('ss\.FFFFF') --> 03.8
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FFFF') --> 00:00:03
// 0:0:03.12 ('h\:m\:ss\.FFFF') --> 00:00:03.1200000
// Cannot parse 0:0:03.127956 with 'h\:m\:ss\.FFFF'.
Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.697497")
Console.WriteLine("{0} ('FFFFF') --> {0:FFFFF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.800009")


Console.WriteLine("{0} ('ss\.FFFFF') --> {0:ss\.FFFFF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.12", "0:0:03.127956"}
Dim fmt As String = "h\:m\:ss\.FFFFF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6974970 ('FFFFF') --> 69749
' 00:00:03.8000090 ('ss\.FFFFF') --> 03.8
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FFFF') --> 00:00:03
' 0:0:03.12 ('h\:m\:ss\.FFFF') --> 00:00:03.1200000
' Cannot parse 0:0:03.127956 with 'h\:m\:ss\.FFFF'.

Volver a la tabla

Especificador de formato personalizado "FFFFFF"


El especificador de formato personalizado "FFFFFF" (seis caracteres "F") presenta las millonésimas de segundo de
un intervalo de tiempo. En una operación de formato, se truncan los dígitos fraccionarios restantes. Si hay ceros
fraccionarios finales, estos no se incluyen en la cadena de resultado. En una operación de análisis que llama al
método TimeSpan.ParseExact o TimeSpan.TryParseExact, la presencia de las décimas, centésimas, milésimas,
diezmilésimas, cienmilésimas y millonésimas de segundo es opcional.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "FFFFFF" para mostrar las
millonésimas de segundo de un valor TimeSpan. También se utiliza este especificador de formato personalizado
en una operación de análisis.
Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.6974974");
Console.WriteLine("{0} ('FFFFFF') --> {0:FFFFFF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.8000009");


Console.WriteLine("{0} ('ss\\.FFFFFF') --> {0:ss\\.FFFFFF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.12", "0:0:03.1279569" };
string fmt = @"h\:m\:ss\.FFFFFF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6974974 ('FFFFFF') --> 697497
// 00:00:03.8000009 ('ss\.FFFFFF') --> 03.8
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FFFFFF') --> 00:00:03
// 0:0:03.12 ('h\:m\:ss\.FFFFFF') --> 00:00:03.1200000
// Cannot parse 0:0:03.1279569 with 'h\:m\:ss\.FFFFFF'.

Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.6974974")
Console.WriteLine("{0} ('FFFFFF') --> {0:FFFFFF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.8000009")


Console.WriteLine("{0} ('ss\.FFFFFF') --> {0:ss\.FFFFFF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.12", "0:0:03.1279569"}
Dim fmt As String = "h\:m\:ss\.FFFFFF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6974974 ('FFFFFF') --> 697497
' 00:00:03.8000009 ('ss\.FFFFFF') --> 03.8
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FFFFFF') --> 00:00:03
' 0:0:03.12 ('h\:m\:ss\.FFFFFF') --> 00:00:03.1200000
' Cannot parse 0:0:03.1279569 with 'h\:m\:ss\.FFFFFF'.

Volver a la tabla
Especificador de formato personalizado "FFFFFFF"
El especificador de formato personalizado "FFFFFFF" (siete caracteres "F") presenta las diezmillonésimas de
segundo (o fracciones de paso) de un intervalo de tiempo. Si hay ceros fraccionarios finales, estos no se incluyen
en la cadena de resultado. En una operación de análisis que llama al método TimeSpan.ParseExact or
TimeSpan.TryParseExact, la presencia de los siete dígitos fraccionarios en la cadena de entrada es opcional.
En el siguiente ejemplo, se utiliza el especificador de formato personalizado "FFFFFFF" para mostrar las fracciones
de segundo de un valor TimeSpan. También se utiliza este especificador de formato personalizado en una
operación de análisis.

Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.6974974");
Console.WriteLine("{0} ('FFFFFFF') --> {0:FFFFFFF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.9500000");


Console.WriteLine("{0} ('ss\\.FFFFFFF') --> {0:ss\\.FFFFFFF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.12", "0:0:03.1279569" };
string fmt = @"h\:m\:ss\.FFFFFFF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6974974 ('FFFFFFF') --> 6974974
// 00:00:03.9500000 ('ss\.FFFFFFF') --> 03.95
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FFFFFFF') --> 00:00:03
// 0:0:03.12 ('h\:m\:ss\.FFFFFFF') --> 00:00:03.1200000
// 0:0:03.1279569 ('h\:m\:ss\.FFFFFFF') --> 00:00:03.1279569
Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.6974974")
Console.WriteLine("{0} ('FFFFFFF') --> {0:FFFFFFF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.9500000")


Console.WriteLine("{0} ('ss\.FFFFFFF') --> {0:ss\.FFFFFFF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.12", "0:0:03.1279569"}
Dim fmt As String = "h\:m\:ss\.FFFFFFF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6974974 ('FFFFFFF') --> 6974974
' 00:00:03.9500000 ('ss\.FFFFFFF') --> 03.95
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FFFFFFF') --> 00:00:03
' 0:0:03.12 ('h\:m\:ss\.FFFFFFF') --> 00:00:03.1200000
' 0:0:03.1279569 ('h\:m\:ss\.FFFFFFF') --> 00:00:03.1279569

Volver a la tabla

Otros caracteres
Cualquier otro carácter sin escape de una cadena de formato, incluido el carácter de espacio en blanco, se
interpreta como especificador de formato personalizado. En la mayoría de los casos, la presencia de cualquier
otro carácter sin escape da lugar a una excepción FormatException.
Para incluir un carácter literal en una cadena de formato, se puede proceder de dos formas:
Se puede escribirlo entre comillas sencillas (delimitador de cadena literal).
Se puede anteponer una barra diagonal inversa ("\"), que se interpreta como un carácter de escape. En C#,
esto significa que la cadena de formato debe ser @-quoted o que el carácter literal debe ir precedido de
una barra diagonal inversa adicional.
En algunos casos, puede que sea necesario usar lógica condicional para incluir un carácter literal de escape
en una cadena de formato. En el ejemplo siguiente se usa lógica condicional para incluir un símbolo de
signo para los intervalos de tiempo negativos.
using System;

public class Example


{
public static void Main()
{
TimeSpan result = new DateTime(2010, 01, 01) - DateTime.Now;
String fmt = (result < TimeSpan.Zero ? "\\-" : "") + "dd\\.hh\\:mm";

Console.WriteLine(result.ToString(fmt));
Console.WriteLine("Interval: {0:" + fmt + "}", result);
}
}
// The example displays output like the following:
// -1291.10:54
// Interval: -1291.10:54

Module Example
Public Sub Main()
Dim result As TimeSpan = New DateTime(2010, 01, 01) - Date.Now
Dim fmt As String = If(result < TimeSpan.Zero, "\-", "") + "dd\.hh\:mm"

Console.WriteLine(result.ToString(fmt))
Console.WriteLine("Interval: {0:" + fmt + "}", result)
End Sub
End Module
' The example displays output like the following:
' -1291.10:54
' Interval: -1291.10:54

.NET no define ninguna gramática para los separadores en los intervalos de tiempo. Esto significa que los
separadores entre los días y las horas, las horas y los minutos, los minutos y los segundos, y los segundos y las
fracciones de segundo deben tratarse todos como literales de carácter en una cadena de formato.
En el siguiente ejemplo, se utilizan el carácter de escape y la comilla simple para definir una cadena de formato
personalizado que incluye la palabra "minutes" en la cadena de salida.

TimeSpan interval = new TimeSpan(0, 32, 45);


// Escape literal characters in a format string.
string fmt = @"mm\:ss\ \m\i\n\u\t\e\s";
Console.WriteLine(interval.ToString(fmt));
// Delimit literal characters in a format string with the ' symbol.
fmt = "mm':'ss' minutes'";
Console.WriteLine(interval.ToString(fmt));
// The example displays the following output:
// 32:45 minutes
// 32:45 minutes

Dim interval As New TimeSpan(0, 32, 45)


' Escape literal characters in a format string.
Dim fmt As String = "mm\:ss\ \m\i\n\u\t\e\s"
Console.WriteLine(interval.ToString(fmt))
' Delimit literal characters in a format string with the ' symbol.
fmt = "mm':'ss' minutes'"
Console.WriteLine(interval.ToString(fmt))
' The example displays the following output:
' 32:45 minutes
' 32:45 minutes

Volver a la tabla
Vea también
Aplicación de formato a tipos
Cadenas de formato TimeSpan estándar
Cadenas de formato de enumeración
16/09/2020 • 5 minutes to read • Edit Online

Puede usar el método Enum.ToString para crear un objeto de cadena que represente el valor de cadena, numérico
o hexadecimal del miembro de una enumeración. Este método toma una de las cadenas de formato de
enumeración para especificar el valor que quiere que se devuelva.
En las secciones siguientes se enumeran las cadenas de formato de enumeración y los valores que devuelven.
Estos especificadores de formato no distinguen mayúsculas de minúsculas.

Gog
Si es posible, muestra la entrada de enumeración como valor de cadena y, si no, muestra el valor entero de la
instancia actual. Si la enumeración se define con el conjunto de atributos Flags , los valores de cadena de cada
entrada válida se concatenan, separados por comas. Si no se establece el atributo Flags , se muestra un valor no
válido como entrada numérica. En el siguiente ejemplo se muestra el uso del especificador de formato G.

Console.WriteLine(ConsoleColor.Red.ToString("G")); // Displays Red


FileAttributes attributes = FileAttributes.Hidden |
FileAttributes.Archive;
Console.WriteLine(attributes.ToString("G")); // Displays Hidden, Archive

Console.WriteLine(ConsoleColor.Red.ToString("G")) ' Displays Red


Dim attributes As FileAttributes = FileAttributes.Hidden Or _
FileAttributes.Archive
Console.WriteLine(attributes.ToString("G")) ' Displays Hidden, Archive

Fof
Si es posible, muestra la entrada de enumeración como valor de cadena. Si el valor se puede mostrar por
completo como suma de las entradas de la enumeración (aunque el atributo Flags no esté presente), los valores
de cadena de cada entrada válida se concatenan, separados por comas. Si las entradas de enumeración no
pueden determinar completamente el valor, a este se le da formato como valor entero. En el siguiente ejemplo se
muestra el uso del especificador de formato F.

Console.WriteLine(ConsoleColor.Blue.ToString("F")); // Displays Blue


FileAttributes attributes = FileAttributes.Hidden |
FileAttributes.Archive;
Console.WriteLine(attributes.ToString("F")); // Displays Hidden, Archive

Console.WriteLine(ConsoleColor.Blue.ToString("F")) ' Displays Blue


Dim attributes As FileAttributes = FileAttributes.Hidden Or _
FileAttributes.Archive
Console.WriteLine(attributes.ToString("F")) ' Displays Hidden, Archive

Dod
Muestra la entrada de enumeración como valor entero en la representación más corta posible. En el siguiente
ejemplo se muestra el uso del especificador de formato D.
Console.WriteLine(ConsoleColor.Cyan.ToString("D")); // Displays 11
FileAttributes attributes = FileAttributes.Hidden |
FileAttributes.Archive;
Console.WriteLine(attributes.ToString("D")); // Displays 34

Console.WriteLine(ConsoleColor.Cyan.ToString("D")) ' Displays 11


Dim attributes As FileAttributes = FileAttributes.Hidden Or _
FileAttributes.Archive
Console.WriteLine(attributes.ToString("D")) ' Displays 34

Xox
Muestra la entrada de enumeración como valor hexadecimal. El valor se representa con ceros a la izquierda,
según sea necesario, para garantizar que la cadena de resultados tenga dos caracteres para cada byte en el tipo
numérico que subyace en el tipo de enumeración. En el siguiente ejemplo se muestra el uso del especificador de
formato X. En el ejemplo, el tipo subyacente tanto de ConsoleColor como de FileAttributes es Int32, o un entero
de 32 bits (o 4 bytes), que produce una cadena de resultados de ocho caracteres.

Console.WriteLine(ConsoleColor.Cyan.ToString("X")); // Displays 0000000B


FileAttributes attributes = FileAttributes.Hidden |
FileAttributes.Archive;
Console.WriteLine(attributes.ToString("X")); // Displays 00000022

Console.WriteLine(ConsoleColor.Cyan.ToString("X")) ' Displays 0000000B


Dim attributes As FileAttributes = FileAttributes.Hidden Or _
FileAttributes.Archive
Console.WriteLine(attributes.ToString("X")) ' Displays 00000022

Ejemplo
En el ejemplo siguiente se define una enumeración denominada Colors que consta de tres entradas: Red , Blue
y Green .

public enum Color {Red = 1, Blue = 2, Green = 3}

Public Enum Color


Red = 1
Blue = 2
Green = 3
End Enum

Una vez definida la enumeración, se puede declarar una instancia de la siguiente manera.

Color myColor = Color.Green;

Dim myColor As Color = Color.Green

Luego se puede usar el método Color.ToString(System.String) para mostrar el valor de enumeración de maneras
diferentes, según el especificador de formato pasado.
Console.WriteLine("The value of myColor is {0}.",
myColor.ToString("G"));
Console.WriteLine("The value of myColor is {0}.",
myColor.ToString("F"));
Console.WriteLine("The value of myColor is {0}.",
myColor.ToString("D"));
Console.WriteLine("The value of myColor is 0x{0}.",
myColor.ToString("X"));
// The example displays the following output to the console:
// The value of myColor is Green.
// The value of myColor is Green.
// The value of myColor is 3.
// The value of myColor is 0x00000003.

Console.WriteLine("The value of myColor is {0}.", _


myColor.ToString("G"))
Console.WriteLine("The value of myColor is {0}.", _
myColor.ToString("F"))
Console.WriteLine("The value of myColor is {0}.", _
myColor.ToString("D"))
Console.WriteLine("The value of myColor is 0x{0}.", _
myColor.ToString("X"))
' The example displays the following output to the console:
' The value of myColor is Green.
' The value of myColor is Green.
' The value of myColor is 3.
' The value of myColor is 0x00000003.

Vea también
Aplicación de formato a tipos
Formatos compuestos
16/09/2020 • 24 minutes to read • Edit Online

La característica de formato compuesto de .NET toma una lista de objetos y una cadena de formato
compuesto como entrada. Una cadena de formato compuesto está formada por texto fijo combinado con
marcadores de posición indizados, que reciben el nombre de elementos de formato, y que se corresponden
con los objetos de la lista. La operación de formato genera una cadena de resultado compuesta por el texto
fijo original combinado con la representación de cadena de los objetos de la lista.

IMPORTANT
En lugar de usar cadenas de formato compuesto, puede usar cadenas interpoladas si el idioma y la versión de idioma
que está usando son compatibles con ellos. Una cadena interpolada es una cadena que contiene expresiones
interpoladas. Cada expresión interpolada se resuelve con el valor de la expresión y se incluye en la cadena de resultado
cuando se asigna la cadena. Para obtener más información, vea Interpolación de cadenas (Referencia de C#) y Cadenas
interpoladas (referencia de Visual Basic).

La característica de formato compuesto se admite mediante métodos como los siguientes:


String.Format, que devuelve una cadena de resultado con formato.
StringBuilder.AppendFormat, que anexa una cadena de resultado con formato a un objeto
StringBuilder.
Algunas sobrecargas del método Console.WriteLine, que muestran una cadena de resultado con
formato en la consola.
Algunas sobrecargas del método TextWriter.WriteLine, que escriben la cadena de resultado con
formato en una secuencia o un archivo. Las clases derivadas de TextWriter, como StreamWriter y
HtmlTextWriter, también comparten esta funcionalidad.
Debug.WriteLine(String, Object[]), que genera un mensaje con formato para los agentes de escucha de
traza.
Los métodos Trace.TraceError(String, Object[]), Trace.TraceInformation(String, Object[]) y
Trace.TraceWarning(String, Object[]), que generan mensajes con formato para los agentes de escucha
de traza.
El método TraceSource.TraceInformation(String, Object[]), que escribe un método informativo para los
agentes de escucha de traza.

Cadena de formato compuesto


Los métodos compatibles con la característica de formato compuesto utilizan como argumentos una cadena
de formato compuesto y una lista de objetos. Una cadena de formato compuesto consta de cero o más
ejecuciones de texto fijo combinadas con uno o varios elementos de formato. El texto fijo es cualquier cadena
que elija y cada elemento de formato se corresponde con un objeto o estructura de conversión boxing de la
lista. La característica de formato compuesto devuelve una nueva cadena de resultado donde cada elemento
de formato se reemplaza por la representación de cadena del objeto correspondiente de la lista.
Observe el siguiente fragmento de código Format.
string name = "Fred";
String.Format("Name = {0}, hours = {1:hh}", name, DateTime.Now);

Dim name As String = "Fred"


String.Format("Name = {0}, hours = {1:hh}", name, DateTime.Now)

El texto fijo es " Name = " y " , hours = ". Los elementos de formato son " {0} ", cuyo índice es 0, que
corresponde al objeto name , y " {1:hh} ", cuyo índice es 1, que corresponde al objeto DateTime.Now .

Sintaxis de elemento de formato


Cada elemento de formato presenta la siguiente sintaxis, formada por los siguientes componentes:
{ index[ , alignment][ : formatString] }
Las llaves ("{" y "}") son necesarias.
Index (Componente )
El componente index obligatorio, denominado también especificador de parámetros, es un número que
empieza por 0 que identifica un elemento correspondiente de la lista de objetos. O sea, el elemento de
formato cuyo especificador de parámetro es 0 da formato al primer objeto de la lista, el elemento de formato
cuyo especificador de parámetro es 1 da formato al segundo objeto de la lista, etc. En el ejemplo siguiente se
incluyen cuatro especificadores de parámetros, numerados del cero al tres, para representar números primos
menores que diez:

string primes;
primes = String.Format("Prime numbers less than 10: {0}, {1}, {2}, {3}",
2, 3, 5, 7 );
Console.WriteLine(primes);
// The example displays the following output:
// Prime numbers less than 10: 2, 3, 5, 7

Dim primes As String


primes = String.Format("Prime numbers less than 10: {0}, {1}, {2}, {3}",
2, 3, 5, 7)
Console.WriteLine(primes)
' The example displays the following output:
' Prime numbers less than 10: 2, 3, 5, 7

Los elementos de formato múltiple se pueden referir al mismo elemento de la lista de objetos mediante la
especificación del mismo especificador de parámetro. Por ejemplo, se puede dar formato al mismo valor
numérico en formato hexadecimal, científico y de número mediante la especificación de una cadena de
formato compuesto como esta: "0x{0:X} {0:E} {0:N}", como se muestra en el ejemplo siguiente.

string multiple = String.Format("0x{0:X} {0:E} {0:N}",


Int64.MaxValue);
Console.WriteLine(multiple);
// The example displays the following output:
// 0x7FFFFFFFFFFFFFFF 9.223372E+018 9,223,372,036,854,775,807.00
Dim multiple As String = String.Format("0x{0:X} {0:E} {0:N}",
Int64.MaxValue)
Console.WriteLine(multiple)
' The example displays the following output:
' 0x7FFFFFFFFFFFFFFF 9.223372E+018 9,223,372,036,854,775,807.00

Cada elemento de formato puede hacer referencia a cualquier objeto de la lista. Por ejemplo, si existen tres
objetos, se puede dar formato al segundo, primero y tercer objeto mediante la especificación de una cadena
de formato compuesto como esta: "{1} {0} {2}". Un objeto al que no hace referencia ningún elemento de
formato se omite. Se produce una excepción de tipo FormatException en tiempo de ejecución si un
especificador de parámetro designa un elemento fuera de los límites de la lista de objetos.
Alignment (Componente )
El componente opcional alignment es un entero con signo que indica el ancho de campo con formato
preferido. Si el valor de alignment es menor que la longitud de la cadena con formato, se omite alignment y
se usa la longitud de la cadena con formato como el ancho de campo. Los datos con formato del campo
están alineados a la derecha si alignment es positivo y a la izquierda si alignment es negativo. Si hace falta
relleno, se utiliza un espacio en blanco. Si se especifica alignment, es necesaria la coma.
El siguiente ejemplo define dos matrices, que contiene los nombres de empleados y otra contiene las horas
que han trabajado en un período de dos semanas. La cadena de formato compuesto alinea a la izquierda los
nombres en un campo de 20 caracteres y alinea a la derecha las horas en un campo de 5 caracteres. Tenga en
cuenta que la cadena de formato estándar "N1" también se usa para dar formato a las horas con un dígito
fraccionario.

using System;

public class Example


{
public static void Main()
{
string[] names = { "Adam", "Bridgette", "Carla", "Daniel",
"Ebenezer", "Francine", "George" };
decimal[] hours = { 40, 6.667m, 40.39m, 82, 40.333m, 80,
16.75m };

Console.WriteLine("{0,-20} {1,5}\n", "Name", "Hours");


for (int ctr = 0; ctr < names.Length; ctr++)
Console.WriteLine("{0,-20} {1,5:N1}", names[ctr], hours[ctr]);
}
}
// The example displays the following output:
// Name Hours
//
// Adam 40.0
// Bridgette 6.7
// Carla 40.4
// Daniel 82.0
// Ebenezer 40.3
// Francine 80.0
// George 16.8
Module Example
Public Sub Main()
Dim names() As String = {"Adam", "Bridgette", "Carla", "Daniel",
"Ebenezer", "Francine", "George"}
Dim hours() As Decimal = {40, 6.667d, 40.39d, 82, 40.333d, 80,
16.75d}

Console.WriteLine("{0,-20} {1,5}", "Name", "Hours")


Console.WriteLine()
For ctr As Integer = 0 To names.Length - 1
Console.WriteLine("{0,-20} {1,5:N1}", names(ctr), hours(ctr))
Next
End Sub
End Module
' The example displays the following output:
' Name Hours
'
' Adam 40.0
' Bridgette 6.7
' Carla 40.4
' Daniel 82.0
' Ebenezer 40.3
' Francine 80.0
' George 16.8

Format String (Componente )


El componente formatString opcional es una cadena de formato adecuada para el tipo de objeto al que se da
formato. Especifique una cadena de formato numérico estándar o personalizado si el objeto correspondiente
es un valor numérico, una cadena de formato de fecha y hora estándar o personalizado si el objeto
correspondiente es un objeto DateTime, o una cadena de formato de enumeración si el objeto
correspondiente es un valor de enumeración. Si no se especifica formatString, se usa el especificador de
formato general ("G") para un tipo numérico, de fecha y hora o de enumeración. Si se especifica
formatString, son necesarios los dos puntos.
En la tabla siguiente se enumeran los tipos o las categorías de tipo de la biblioteca de clases de .NET
Framework que admiten un conjunto predefinido de cadenas de formato, y se proporcionan vínculos a
temas que muestran las cadenas de formato admitidas. Observe que la asignación de formato a cadenas es
un mecanismo extensible que permite definir cadenas de formato nuevas para todos los tipos existentes, así
como definir un conjunto de cadenas de formato admitidas por un tipo definido por la aplicación. Para
obtener más información, consulte los temas sobre las interfaces IFormattable y ICustomFormatter.

T IP O O C AT EGO RÍA DE T IP O VEA

Tipos de fecha y hora (DateTime, DateTimeOffset) Cadenas con formato de fecha y hora estándar

Cadenas con formato de fecha y hora personalizado

Tipos de enumeración (todos los tipos derivados de Cadenas de formato de enumeración


System.Enum)

Tipos numéricos (BigInteger, Byte, Decimal, Double, Int16, Cadenas con formato numérico estándar
Int32, Int64, SByte, Single, UInt16, UInt32, UInt64)
Cadenas con formato numérico personalizado

Guid Guid.ToString(String)
T IP O O C AT EGO RÍA DE T IP O VEA

TimeSpan Cadenas de formato TimeSpan estándar

Cadenas de formato TimeSpan personalizado

Llaves de escape
Las llaves de apertura y de cierre se interpretan como el inicio y el final de un elemento de formato. Por lo
tanto, debe utilizar una secuencia de escape para que se muestre una llave de apertura o de cierre literal.
Especifique dos llaves de apertura ("{{") en el texto fijo para que se muestre una llave de apertura ("{"), o dos
llaves de cierre ("}}") para que se muestre una llave de cierre ("}"). Las llaves de un elemento de formato se
interpretan secuencialmente, en el orden en que se encuentran. No se admite la interpretación de llaves
anidadas.
El modo de interpretar las llaves de escape puede dar lugar a resultados inesperados. Tomemos como
ejemplo el elemento de formato "{{{0:D}}}", cuyo propósito es mostrar una llave de apertura, un valor
numérico con formato de número decimal y una llave de cierre; pero que, en la práctica, se interpreta de la
siguiente forma:
1. Las dos primeras llaves de apertura ("{{") son llaves de escape y dan lugar a en una llave de apertura.
2. Los tres caracteres siguientes ("{0:") se interpretan como el inicio de un elemento de formato.
3. El siguiente carácter ("D") se interpretaría como el especificador de formato numérico estándar
decimal, pero las dos llaves de escape siguientes ("}}") dan lugar a una única llave. Como la cadena
resultante ("D}") no es un especificador de formato numérico estándar, se interpreta como una cadena
de formato personalizado que significa que debe mostrarse la cadena literal "D}".
4. La última llave ("}") se interpreta como el final del elemento de formato.
5. Como resultado final, se muestra la cadena literal "{D}". No se muestra el valor numérico al que se
debía dar formato.
Una forma de escribir código e impedir que las llaves de escape y los elementos de formato se
malinterpreten consiste en dar formato a las llaves y elementos de formato por separado. Es decir, en la
primera operación de formato mostrar una llave de apertura literal, en la siguiente operación mostrar el
resultado del elemento de formato y, por último, en la operación final mostrar una llave de cierre literal. En el
ejemplo siguiente se muestra este enfoque.

int value = 6324;


string output = string.Format("{0}{1:D}{2}",
"{", value, "}");
Console.WriteLine(output);
// The example displays the following output:
// {6324}

Dim value As Integer = 6324


Dim output As String = String.Format("{0}{1:D}{2}", _
"{", value, "}")
Console.WriteLine(output)
' The example displays the following output:
' {6324}

Orden de procesamiento
Si la llamada al método de formato compuesto incluye un argumento IFormatProvider cuyo valor no es
null , el runtime llama al método IFormatProvider.GetFormat para solicitar una implementación de
ICustomFormatter. Si el método es capaz de devolver una implementación de ICustomFormatter, se
almacena en caché el tiempo que dure la llamada de método de formato compuesto.
Cada valor de la lista de parámetros que se corresponda con un elemento de formato se convierte en una
cadena del siguiente modo:
1. Si el valor al que se va a dar formato es null , se devuelve una cadena vacía String.Empty.
2. Si hay disponible una implementación de ICustomFormatter, el runtime llama al método Format. Pasa
al método el valor formatString del elemento de formato, si hay alguno, o null si no lo hay, junto con
la implementación de IFormatProvider. Si la llamada al método ICustomFormatter.Format devuelve
null , la ejecución avanza al siguiente paso; en caso contrario, se devuelve el resultado de la llamada
a ICustomFormatter.Format.
3. Si el valor implementa la interfaz IFormattable, se llama al método ToString(String, IFormatProvider)
de esta. Se pasa al método el valor formatString, si hubiera uno presente en el elemento de formato, o
null si no lo hubiera. El argumento IFormatProvider se determina de la siguiente forma:

Para un valor numérico, si se llama a un método de formato compuesto con un argumento


IFormatProvider que no sea null, el runtime solicita un objeto NumberFormatInfo a su método
IFormatProvider.GetFormat. En caso de no poder proporcionar uno, si el valor del argumento es
null , o si el método de formato compuesto no tiene un parámetro IFormatProvider, se usa el
objeto NumberFormatInfo para la referencia cultural del subproceso actual.
Para un valor de fecha y hora, si se llama a un método de formato compuesto con un
argumento IFormatProvider que no sea nulo, el runtime solicita un objeto DateTimeFormatInfo
a su método IFormatProvider.GetFormat. En caso de no poder proporcionar uno, si el valor del
argumento es null , o si el método de formato compuesto no tiene un parámetro
IFormatProvider, se usa el objeto DateTimeFormatInfo para la referencia cultural del
subproceso actual.
Para objetos de otros tipos, si se llama a un formato compuesto con un argumento
IFormatProvider, su valor se pasa directamente a la implementación IFormattable.ToString. En
caso contrario, null se pasa a la implementación IFormattable.ToString.
4. Se llama al método sin parámetros ToString del tipo, que reemplaza a Object.ToString() o hereda el
comportamiento de su clase base. En este caso, se omite la cadena de formato especificada por el
componente formatString en el elemento de formato, si estuviera presente.
La alineación se aplica después de que se hayan realizado los pasos anteriores.

Ejemplos de código
En el ejemplo siguiente se muestra una cadena creada mediante formato compuesto y otra creada mediante
el método ToString de un objeto. Los dos tipos de formato producen resultados equivalentes.

string FormatString1 = String.Format("{0:dddd MMMM}", DateTime.Now);


string FormatString2 = DateTime.Now.ToString("dddd MMMM");

Dim FormatString1 As String = String.Format("{0:dddd MMMM}", DateTime.Now)


Dim FormatString2 As String = DateTime.Now.ToString("dddd MMMM")

Si tomamos como día actual un jueves del mes de mayo, el valor de ambas cadenas del ejemplo anterior
será Thursday May para la referencia cultural Inglés (Estados Unidos).
Console.WriteLine expone la misma funcionalidad que String.Format. La única diferencia que existe entre
estos dos métodos es que String.Format devuelve el resultado como una cadena, mientras que
Console.WriteLine escribe el resultado en el flujo de salida asociado al objeto Console. En el ejemplo
siguiente se usa el método Console.WriteLine para dar formato al valor de MyInt como un valor de divisa.

int MyInt = 100;


Console.WriteLine("{0:C}", MyInt);
// The example displays the following output
// if en-US is the current culture:
// $100.00

Dim MyInt As Integer = 100


Console.WriteLine("{0:C}", MyInt)
' The example displays the following output
' if en-US is the current culture:
' $100.00

En el siguiente ejemplo se muestra la aplicación de formato a objetos múltiples, incluida la aplicación de


formato a un objeto de dos formas diferentes.

string myName = "Fred";


Console.WriteLine(String.Format("Name = {0}, hours = {1:hh}, minutes = {1:mm}",
myName, DateTime.Now));
// Depending on the current time, the example displays output like the following:
// Name = Fred, hours = 11, minutes = 30

Dim myName As String = "Fred"


Console.WriteLine(String.Format("Name = {0}, hours = {1:hh}, minutes = {1:mm}", _
myName, DateTime.Now))
' Depending on the current time, the example displays output like the following:
' Name = Fred, hours = 11, minutes = 30

En el ejemplo siguiente se muestra el uso de la alineación en la aplicación de formato. Los argumentos a los
que se da formato se colocan entre caracteres verticales (|) para resaltar la alineación resultante.
string myFName = "Fred";
string myLName = "Opals";
int myInt = 100;
string FormatFName = String.Format("First Name = |{0,10}|", myFName);
string FormatLName = String.Format("Last Name = |{0,10}|", myLName);
string FormatPrice = String.Format("Price = |{0,10:C}|", myInt);
Console.WriteLine(FormatFName);
Console.WriteLine(FormatLName);
Console.WriteLine(FormatPrice);
Console.WriteLine();

FormatFName = String.Format("First Name = |{0,-10}|", myFName);


FormatLName = String.Format("Last Name = |{0,-10}|", myLName);
FormatPrice = String.Format("Price = |{0,-10:C}|", myInt);
Console.WriteLine(FormatFName);
Console.WriteLine(FormatLName);
Console.WriteLine(FormatPrice);
// The example displays the following output on a system whose current
// culture is en-US:
// First Name = | Fred|
// Last Name = | Opals|
// Price = | $100.00|
//
// First Name = |Fred |
// Last Name = |Opals |
// Price = |$100.00 |

Dim myFName As String = "Fred"


Dim myLName As String = "Opals"

Dim myInt As Integer = 100


Dim FormatFName As String = String.Format("First Name = |{0,10}|", myFName)
Dim FormatLName As String = String.Format("Last Name = |{0,10}|", myLName)
Dim FormatPrice As String = String.Format("Price = |{0,10:C}|", myInt)
Console.WriteLine(FormatFName)
Console.WriteLine(FormatLName)
Console.WriteLine(FormatPrice)
Console.WriteLine()

FormatFName = String.Format("First Name = |{0,-10}|", myFName)


FormatLName = String.Format("Last Name = |{0,-10}|", myLName)
FormatPrice = String.Format("Price = |{0,-10:C}|", myInt)
Console.WriteLine(FormatFName)
Console.WriteLine(FormatLName)
Console.WriteLine(FormatPrice)
' The example displays the following output on a system whose current
' culture is en-US:
' First Name = | Fred|
' Last Name = | Opals|
' Price = | $100.00|
'
' First Name = |Fred |
' Last Name = |Opals |
' Price = |$100.00 |

Vea también
WriteLine
String.Format
Interpolación de cadenas en C#
Cadenas interpoladas (referencia de Visual Basic)
Aplicación de formato a tipos
Cadenas con formato numérico estándar
Cadenas con formato numérico personalizado
Cadenas con formato de fecha y hora estándar
Cadenas con formato de fecha y hora personalizado
Cadenas de formato TimeSpan estándar
Cadenas de formato TimeSpan personalizado
Cadenas de formato de enumeración
Procedimiento para rellenar un número con ceros a
la izquierda
16/09/2020 • 10 minutes to read • Edit Online

Si quiere agregar ceros a la izquierda de un entero, puede hacerlo mediante la cadena de formato numérico
estándar "D" con un especificador de precisión. Para agregar ceros a la izquierda tanto de enteros como de
números de punto flotante, use una cadena de formato numérico personalizada. En este artículo se explica cómo
usar ambos métodos para rellenar un número con ceros a la izquierda.

Para rellenar un entero con ceros a la izquierda hasta una longitud


concreta
1. Determine el número mínimo de dígitos que desea que muestre el valor entero. Incluya los dígitos a la
izquierda en este número.
2. Determine si desea mostrar el entero como valor decimal o hexadecimal.
Para mostrar el entero como valor decimal, llame a su método ToString(String) y pase la cadena
"Dn" como valor del parámetro format , donde n representa la longitud mínima de la cadena.
Para mostrar el entero como valor hexadecimal, llame a su método ToString(String) y pase la
cadena "Xn" como valor del parámetro format, donde n representa la longitud mínima de la cadena.
También puede usar la cadena de formato en una cadena interpolada en ambos, C# y Visual Basic, o puede llamar
a un método, como String.Format o Console.WriteLine, que usa formato compuesto.
En el ejemplo siguiente se aplica formato a varios valores enteros con ceros a la izquierda de modo que la longitud
mínima total del número con formato sea de ocho caracteres.
byte byteValue = 254;
short shortValue = 10342;
int intValue = 1023983;
long lngValue = 6985321;
ulong ulngValue = UInt64.MaxValue;

// Display integer values by calling the ToString method.


Console.WriteLine("{0,22} {1,22}", byteValue.ToString("D8"), byteValue.ToString("X8"));
Console.WriteLine("{0,22} {1,22}", shortValue.ToString("D8"), shortValue.ToString("X8"));
Console.WriteLine("{0,22} {1,22}", intValue.ToString("D8"), intValue.ToString("X8"));
Console.WriteLine("{0,22} {1,22}", lngValue.ToString("D8"), lngValue.ToString("X8"));
Console.WriteLine("{0,22} {1,22}", ulngValue.ToString("D8"), ulngValue.ToString("X8"));
Console.WriteLine();

// Display the same integer values by using composite formatting.


Console.WriteLine("{0,22:D8} {0,22:X8}", byteValue);
Console.WriteLine("{0,22:D8} {0,22:X8}", shortValue);
Console.WriteLine("{0,22:D8} {0,22:X8}", intValue);
Console.WriteLine("{0,22:D8} {0,22:X8}", lngValue);
Console.WriteLine("{0,22:D8} {0,22:X8}", ulngValue);
// The example displays the following output:
// 00000254 000000FE
// 00010342 00002866
// 01023983 000F9FEF
// 06985321 006A9669
// 18446744073709551615 FFFFFFFFFFFFFFFF
//
// 00000254 000000FE
// 00010342 00002866
// 01023983 000F9FEF
// 06985321 006A9669
// 18446744073709551615 FFFFFFFFFFFFFFFF
// 18446744073709551615 FFFFFFFFFFFFFFFF
Dim byteValue As Byte = 254
Dim shortValue As Short = 10342
Dim intValue As Integer = 1023983
Dim lngValue As Long = 6985321
Dim ulngValue As ULong = UInt64.MaxValue

' Display integer values by calling the ToString method.


Console.WriteLine("{0,22} {1,22}", byteValue.ToString("D8"), byteValue.ToString("X8"))
Console.WriteLine("{0,22} {1,22}", shortValue.ToString("D8"), shortValue.ToString("X8"))
Console.WriteLine("{0,22} {1,22}", intValue.ToString("D8"), intValue.ToString("X8"))
Console.WriteLine("{0,22} {1,22}", lngValue.ToString("D8"), lngValue.ToString("X8"))
Console.WriteLine("{0,22} {1,22}", ulngValue.ToString("D8"), ulngValue.ToString("X8"))
Console.WriteLine()

' Display the same integer values by using composite formatting.


Console.WriteLine("{0,22:D8} {0,22:X8}", byteValue)
Console.WriteLine("{0,22:D8} {0,22:X8}", shortValue)
Console.WriteLine("{0,22:D8} {0,22:X8}", intValue)
Console.WriteLine("{0,22:D8} {0,22:X8}", lngValue)
Console.WriteLine("{0,22:D8} {0,22:X8}", ulngValue)
' The example displays the following output:
' 00000254 000000FE
' 00010342 00002866
' 01023983 000F9FEF
' 06985321 006A9669
' 18446744073709551615 FFFFFFFFFFFFFFFF
'
' 00000254 000000FE
' 00010342 00002866
' 01023983 000F9FEF
' 06985321 006A9669
' 18446744073709551615 FFFFFFFFFFFFFFFF

Para rellenar un entero con una determinada cantidad de ceros a la


izquierda
1. Determine cuántos ceros a la izquierda desea que muestre el valor entero.
2. Determine si desea mostrar el entero como valor decimal o hexadecimal.
Para darle formato como valor decimal, se requiere que use el especificador de formato estándar "D".
Para darle formato como valor hexadecimal, se requiere que use el especificador de formato estándar
"X".
3. Determine la longitud de la cadena numérica sin rellenar mediante una llamada a los métodos
ToString("D").Length o ToString("X").Length del valor entero.

4. Agregue el número de ceros a la izquierda que desea incluir en la cadena con formato a la longitud de la
cadena numérica sin rellenar. Al agregar el número de ceros iniciales se define la longitud total de la cadena
rellenada.
5. Llame al método ToString(String) del valor entero y pase la cadena "Dn" para cadenas decimales y "Xn"
para cadenas hexadecimales, donde n representa la longitud total de la cadena rellenada. También puede
usar la cadena de formato "Dn" o "Xn" en un método que admita formato compuesto.
En el ejemplo siguiente se rellena un valor entero con cinco ceros a la izquierda.
int value = 160934;
int decimalLength = value.ToString("D").Length + 5;
int hexLength = value.ToString("X").Length + 5;
Console.WriteLine(value.ToString("D" + decimalLength.ToString()));
Console.WriteLine(value.ToString("X" + hexLength.ToString()));
// The example displays the following output:
// 00000160934
// 00000274A6

Dim value As Integer = 160934


Dim decimalLength As Integer = value.ToString("D").Length + 5
Dim hexLength As Integer = value.ToString("X").Length + 5
Console.WriteLine(value.ToString("D" + decimalLength.ToString()))
Console.WriteLine(value.ToString("X" + hexLength.ToString()))
' The example displays the following output:
' 00000160934
' 00000274A6

Para rellenar un valor numérico con ceros a la izquierda hasta una


longitud concreta
1. Determine con cuántos dígitos a la izquierda del decimal desea que se represente la cadena del número.
Incluya los ceros a la izquierda en este número total de dígitos.
2. Defina una cadena de formato numérico personalizado en la que se use el marcador de posición cero "0"
para representar el número mínimo de ceros.
3. Llame al método ToString(String) del número y pase la cadena de formato personalizado. También puede
usar la cadena de formato personalizado con interpolación de cadena o con un método que admita formato
compuesto.
En el ejemplo siguiente se da formato a varios valores numéricos con ceros iniciales. Como resultado, la longitud
total del número con formato es de al menos ocho dígitos a la izquierda del separador decimal.
string fmt = "00000000.##";
int intValue = 1053240;
decimal decValue = 103932.52m;
float sngValue = 1549230.10873992f;
double dblValue = 9034521202.93217412;

// Display the numbers using the ToString method.


Console.WriteLine(intValue.ToString(fmt));
Console.WriteLine(decValue.ToString(fmt));
Console.WriteLine(sngValue.ToString(fmt));
Console.WriteLine(dblValue.ToString(fmt));
Console.WriteLine();

// Display the numbers using composite formatting.


string formatString = " {0,15:" + fmt + "}";
Console.WriteLine(formatString, intValue);
Console.WriteLine(formatString, decValue);
Console.WriteLine(formatString, sngValue);
Console.WriteLine(formatString, dblValue);
// The example displays the following output:
// 01053240
// 00103932.52
// 01549230
// 9034521202.93
//
// 01053240
// 00103932.52
// 01549230
// 9034521202.93

Dim fmt As String = "00000000.##"


Dim intValue As Integer = 1053240
Dim decValue As Decimal = 103932.52d
Dim sngValue As Single = 1549230.10873992
Dim dblValue As Double = 9034521202.93217412

' Display the numbers using the ToString method.


Console.WriteLine(intValue.ToString(fmt))
Console.WriteLine(decValue.ToString(fmt))
Console.WriteLine(sngValue.ToString(fmt))
Console.WriteLine(dblValue.ToString(fmt))
Console.WriteLine()

' Display the numbers using composite formatting.


Dim formatString As String = " {0,15:" + fmt + "}"
Console.WriteLine(formatString, intValue)
Console.WriteLine(formatString, decValue)
Console.WriteLine(formatString, sngValue)
Console.WriteLine(formatString, dblValue)
' The example displays the following output:
' 01053240
' 00103932.52
' 01549230
' 9034521202.93
'
' 01053240
' 00103932.52
' 01549230
' 9034521202.93

Para rellenar un valor numérico con una determinada cantidad de ceros


a la izquierda
1. Determine cuántos ceros a la izquierda desea que tenga el valor numérico.
2. Determine el número de dígitos a la izquierda del separador decimal que tendrá la cadena numérica sin
rellenar:
a. Determine si la representación de cadena de un número incluye un símbolo de separador decimal.
b. Si incluye un símbolo de separador decimal, determine el número de caracteres a la izquierda del
separador decimal.
o bien
Si no incluye un símbolo de separador decimal, determine la longitud de la cadena.
3. Cree una cadena de formato personalizado que utilice:
El marcador de posición cero "0" para cada uno de los ceros a la izquierda que aparecen en la cadena.
El marcador de posición cero o el marcador de posición de dígito "#" para representar cada dígito en
la cadena predeterminada.
4. Proporcione la cadena de formato personalizado como un parámetro bien al método ToString(String) del
número, bien a un método que admita el formato compuesto.
En el ejemplo siguiente se rellenan dos valores Double con cinco ceros a la izquierda.

double[] dblValues = { 9034521202.93217412, 9034521202 };


foreach (double dblValue in dblValues)
{
string decSeparator = System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
string fmt, formatString;

if (dblValue.ToString().Contains(decSeparator))
{
int digits = dblValue.ToString().IndexOf(decSeparator);
fmt = new String('0', 5) + new String('#', digits) + ".##";
}
else
{
fmt = new String('0', dblValue.ToString().Length);
}
formatString = "{0,20:" + fmt + "}";

Console.WriteLine(dblValue.ToString(fmt));
Console.WriteLine(formatString, dblValue);
}
// The example displays the following output:
// 000009034521202.93
// 000009034521202.93
// 9034521202
// 9034521202
Dim dblValues() As Double = {9034521202.93217412, 9034521202}
For Each dblValue As Double In dblValues
Dim decSeparator As String = System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator
Dim fmt, formatString As String

If dblValue.ToString.Contains(decSeparator) Then
Dim digits As Integer = dblValue.ToString().IndexOf(decSeparator)
fmt = New String("0"c, 5) + New String("#"c, digits) + ".##"
Else
fmt = New String("0"c, dblValue.ToString.Length)
End If
formatString = "{0,20:" + fmt + "}"

Console.WriteLine(dblValue.ToString(fmt))
Console.WriteLine(formatString, dblValue)
Next
' The example displays the following output:
' 000009034521202.93
' 000009034521202.93
' 9034521202
' 9034521202

Vea también
Cadenas con formato numérico personalizado
Cadenas con formato numérico estándar
Formatos compuestos
Procedimiento para extraer el día de la semana de
una fecha concreta
16/09/2020 • 12 minutes to read • Edit Online

.NET Framework permite determinar fácilmente el número de día de la semana correspondiente a una fecha
concreta, así como mostrar el nombre localizado del día de la semana para una fecha en particular. Las propiedades
DayOfWeek o DayOfWeek proporcionan un valor enumerado que indica el día de la semana correspondiente a una
fecha concreta. Por el contrario, la recuperación del nombre del día de la semana es una operación de formato que
puede realizarse con una llamada a un método de formato como, por ejemplo, un método ToString de un valor de
fecha y hora o un método String.Format. En este tema se muestra cómo realizar estas operaciones de formato.
Para obtener un número que indique el día de la semana a partir de una fecha específica
1. Cuando trabaje con la representación de cadena de una fecha, conviértala en un valor DateTime o
DateTimeOffset mediante el método estático DateTime.Parse o DateTimeOffset.Parse.
2. Use las propiedades DateTime.DayOfWeek o DateTimeOffset.DayOfWeek para recuperar un valor
DayOfWeek que indique el día de la semana.
3. En caso necesario, convierta (en C# o en Visual Basic) el valor DayOfWeek en un entero.
En el ejemplo siguiente se muestra un entero que representa el día de la semana de una fecha concreta.

using System;

public class Example


{
public static void Main()
{
DateTime dateValue = new DateTime(2008, 6, 11);
Console.WriteLine((int) dateValue.DayOfWeek);
}
}
// The example displays the following output:
// 3

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#
Console.WriteLine(dateValue.DayOfWeek)
End Sub
End Module
' The example displays the following output:
' 3

Para obtener el nombre abreviado del día de la semana a partir de una fecha específica
1. Cuando trabaje con la representación de cadena de una fecha, conviértala en un valor DateTime o
DateTimeOffset mediante el método estático DateTime.Parse o DateTimeOffset.Parse.
2. Puede obtener el nombre abreviado de un día de la semana de la referencia cultural actual o de una
referencia cultural específica:
a. Para obtener el nombre abreviado del día de la semana de la referencia cultural actual, llame al
método de instancia DateTime.ToString(String) o DateTimeOffset.ToString(String) del valor de fecha y
hora, y pase la cadena "ddd" como parámetro format . En el ejemplo siguiente se muestra la llamada
al método ToString(String).

using System;

public class Example


{
public static void Main()
{
DateTime dateValue = new DateTime(2008, 6, 11);
Console.WriteLine(dateValue.ToString("ddd"));
}
}
// The example displays the following output:
// Wed

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#
Console.WriteLine(dateValue.ToString("ddd"))
End Sub
End Module
' The example displays the following output:
' Wed

b. Para obtener el nombre abreviado del día de la semana de una referencia cultural específica, llame al
método de instancia DateTime.ToString(String, IFormatProvider) o DateTimeOffset.ToString(String,
IFormatProvider) del valor de fecha y hora. Pase la cadena "ddd" como parámetro format . Pase un
objeto CultureInfo o DateTimeFormatInfo que represente la referencia cultural cuyo nombre del día
de la semana desee recuperar como parámetro provider . En el código siguiente se muestra una
llamada al método ToString(String, IFormatProvider) con un objeto CultureInfo que representa la
referencia cultural fr-FR.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
DateTime dateValue = new DateTime(2008, 6, 11);
Console.WriteLine(dateValue.ToString("ddd",
new CultureInfo("fr-FR")));
}
}
// The example displays the following output:
// mer.

Imports System.Globalization

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#
Console.WriteLine(dateValue.ToString("ddd",
New CultureInfo("fr-FR")))
End Sub
End Module
' The example displays the following output:
' mer.
Para obtener el nombre completo del día de la semana a partir de una fecha específica
1. Cuando trabaje con la representación de cadena de una fecha, conviértala en un valor DateTime o
DateTimeOffset mediante el método estático DateTime.Parse o DateTimeOffset.Parse.
2. Puede obtener el nombre completo de un día de la semana de la referencia cultural actual o de una
referencia cultural específica:
a. Para obtener el nombre del día de la semana de la referencia cultural actual, llame al método de
instancia DateTime.ToString(String) o DateTimeOffset.ToString(String) del valor de fecha y hora, y pase
la cadena "dddd" como parámetro format . En el ejemplo siguiente se muestra la llamada al método
ToString(String).

using System;

public class Example


{
public static void Main()
{
DateTime dateValue = new DateTime(2008, 6, 11);
Console.WriteLine(dateValue.ToString("dddd"));
}
}
// The example displays the following output:
// Wednesday

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#
Console.WriteLine(dateValue.ToString("dddd"))
End Sub
End Module
' The example displays the following output:
' Wednesday

b. Para obtener el nombre del día de la semana de una referencia cultural específica, llame al método de
instancia DateTime.ToString(String, IFormatProvider) o DateTimeOffset.ToString(String,
IFormatProvider) del valor de fecha y hora. Pase la cadena "dddd" como parámetro format . Pase un
objeto CultureInfo o DateTimeFormatInfo que represente la referencia cultural cuyo nombre del día
de la semana desee recuperar como parámetro provider . En el código siguiente se muestra una
llamada al método ToString(String, IFormatProvider) con un objeto CultureInfo que representa la
referencia cultural es-ES.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
DateTime dateValue = new DateTime(2008, 6, 11);
Console.WriteLine(dateValue.ToString("dddd",
new CultureInfo("es-ES")));
}
}
// The example displays the following output:
// miércoles.
Imports System.Globalization

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#
Console.WriteLine(dateValue.ToString("dddd", _
New CultureInfo("es-ES")))
End Sub
End Module
' The example displays the following output:
' miércoles.

Ejemplo
En el ejemplo se muestran llamadas a las propiedades DateTime.DayOfWeek y DateTimeOffset.DayOfWeek y los
métodos DateTime.ToString y DateTimeOffset.ToString para recuperar el número que representa el día de la
semana, el nombre abreviado del día de la semana y el nombre completo del día de la semana para una fecha en
particular.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
string dateString = "6/11/2007";
DateTime dateValue;
DateTimeOffset dateOffsetValue;

try
{
DateTimeFormatInfo dateTimeFormats;
// Convert date representation to a date value
dateValue = DateTime.Parse(dateString, CultureInfo.InvariantCulture);
dateOffsetValue = new DateTimeOffset(dateValue,
TimeZoneInfo.Local.GetUtcOffset(dateValue));

// Convert date representation to a number indicating the day of week


Console.WriteLine((int) dateValue.DayOfWeek);
Console.WriteLine((int) dateOffsetValue.DayOfWeek);

// Display abbreviated weekday name using current culture


Console.WriteLine(dateValue.ToString("ddd"));
Console.WriteLine(dateOffsetValue.ToString("ddd"));

// Display full weekday name using current culture


Console.WriteLine(dateValue.ToString("dddd"));
Console.WriteLine(dateOffsetValue.ToString("dddd"));

// Display abbreviated weekday name for de-DE culture


Console.WriteLine(dateValue.ToString("ddd", new CultureInfo("de-DE")));
Console.WriteLine(dateOffsetValue.ToString("ddd",
new CultureInfo("de-DE")));

// Display abbreviated weekday name with de-DE DateTimeFormatInfo object


dateTimeFormats = new CultureInfo("de-DE").DateTimeFormat;
Console.WriteLine(dateValue.ToString("ddd", dateTimeFormats));
Console.WriteLine(dateOffsetValue.ToString("ddd", dateTimeFormats));

// Display full weekday name for fr-FR culture


Console.WriteLine(dateValue.ToString("ddd", new CultureInfo("fr-FR")));
Console.WriteLine(dateOffsetValue.ToString("ddd",
new CultureInfo("fr-FR")));
// Display abbreviated weekday name with fr-FR DateTimeFormatInfo object
dateTimeFormats = new CultureInfo("fr-FR").DateTimeFormat;
Console.WriteLine(dateValue.ToString("dddd", dateTimeFormats));
Console.WriteLine(dateOffsetValue.ToString("dddd", dateTimeFormats));
}
catch (FormatException)
{
Console.WriteLine("Unable to convert {0} to a date.", dateString);
}
}
}
// The example displays the following output:
// 1
// 1
// Mon
// Mon
// Monday
// Monday
// Mo
// Mo
// Mo
// Mo
// lun.
// lun.
// lundi
// lundi
Imports System.Globalization

Module Example
Public Sub Main()
Dim dateString As String = "6/11/2007"
Dim dateValue As Date
Dim dateOffsetValue As DateTimeOffset

Try
Dim dateTimeFormats As DateTimeFormatInfo
' Convert date representation to a date value
dateValue = Date.Parse(dateString, CultureInfo.InvariantCulture)
dateOffsetValue = New DateTimeOffset(dateValue, _
TimeZoneInfo.Local.GetUtcOffset(dateValue))
' Convert date representation to a number indicating the day of week
Console.WriteLine(dateValue.DayOfWeek)
Console.WriteLine(dateOffsetValue.DayOfWeek)

' Display abbreviated weekday name using current culture


Console.WriteLine(dateValue.ToString("ddd"))
Console.WriteLine(dateOffsetValue.ToString("ddd"))

' Display full weekday name using current culture


Console.WriteLine(dateValue.ToString("dddd"))
Console.WriteLine(dateOffsetValue.ToString("dddd"))

' Display abbreviated weekday name for de-DE culture


Console.WriteLine(dateValue.ToString("ddd", New CultureInfo("de-DE")))
Console.WriteLine(dateOffsetValue.ToString("ddd", _
New CultureInfo("de-DE")))

' Display abbreviated weekday name with de-DE DateTimeFormatInfo object


dateTimeFormats = New CultureInfo("de-DE").DateTimeFormat
Console.WriteLine(dateValue.ToString("ddd", dateTimeFormats))
Console.WriteLine(dateOffsetValue.ToString("ddd", dateTimeFormats))

' Display full weekday name for fr-FR culture


Console.WriteLine(dateValue.ToString("ddd", New CultureInfo("fr-FR")))
Console.WriteLine(dateOffsetValue.ToString("ddd", _
New CultureInfo("fr-FR")))

' Display abbreviated weekday name with fr-FR DateTimeFormatInfo object


dateTimeFormats = New CultureInfo("fr-FR").DateTimeFormat
Console.WriteLine(dateValue.ToString("dddd", dateTimeFormats))
Console.WriteLine(dateOffsetValue.ToString("dddd", dateTimeFormats))
Catch e As FormatException
Console.WriteLine("Unable to convert {0} to a date.", dateString)
End Try
End Sub
End Module
' The example displays the following output to the console:
' 1
' 1
' Mon
' Mon
' Monday
' Monday
' Mo
' Mo
' Mo
' Mo
' lun.
' lun.
' lundi
' lundi
Puede haber lenguajes que ofrezcan una funcionalidad que duplique o complemente a la proporcionada por .NET
Framework. Por ejemplo, Visual Basic incluye dos funciones así:
Weekday , que devuelve un número que indica el día de la semana a partir de una fecha específica. Considera
que el valor ordinal del primer día de la semana es uno, mientras que la propiedad DateTime.DayOfWeek
considera que es cero.
WeekdayName , que devuelve el nombre de la semana en la referencia cultural actual que corresponde a un
número concreto del día de la semana.
En el ejemplo siguiente se demuestra el uso de las funciones Weekday y WeekdayName .

Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#

' Get weekday number using Visual Basic Weekday function


Console.WriteLine(Weekday(dateValue)) ' Displays 4
' Compare with .NET DateTime.DayOfWeek property
Console.WriteLine(dateValue.DayOfWeek) ' Displays 3

' Get weekday name using Weekday and WeekdayName functions


Console.WriteLine(WeekdayName(Weekday(dateValue))) ' Displays Wednesday

' Change culture to de-DE


Dim originalCulture As CultureInfo = Thread.CurrentThread.CurrentCulture
Thread.CurrentThread.CurrentCulture = New CultureInfo("de-DE")
' Get weekday name using Weekday and WeekdayName functions
Console.WriteLine(WeekdayName(Weekday(dateValue))) ' Displays Donnerstag

' Restore original culture


Thread.CurrentThread.CurrentCulture = originalCulture
End Sub
End Module

También se puede usar el valor devuelto por la propiedad DateTime.DayOfWeek para recuperar el nombre del día
de la semana de una fecha en particular. Para ello, solo se necesita hacer una llamada al método ToString en el valor
DayOfWeek devuelto por la propiedad. Sin embargo, esta técnica no produce el nombre localizado de un día de la
semana para la referencia cultural actual, tal como se muestra en el ejemplo siguiente.
using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
// Change current culture to fr-FR
CultureInfo originalCulture = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

DateTime dateValue = new DateTime(2008, 6, 11);


// Display the DayOfWeek string representation
Console.WriteLine(dateValue.DayOfWeek.ToString());
// Restore original current culture
Thread.CurrentThread.CurrentCulture = originalCulture;
}
}
// The example displays the following output:
// Wednesday

Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
' Change current culture to fr-FR
Dim originalCulture As CultureInfo = Thread.CurrentThread.CurrentCulture
Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-FR")

Dim dateValue As Date = #6/11/2008#


' Display the DayOfWeek string representation
Console.WriteLine(dateValue.DayOfWeek.ToString())
' Restore original current culture
Thread.CurrentThread.CurrentCulture = originalCulture
End Sub
End Module
' The example displays the following output:
' Wednesday

Vea también
Cadenas con formato de fecha y hora estándar
Cadenas con formato de fecha y hora personalizado
Procedimiento para definir y usar proveedores de
formato numérico personalizado
16/09/2020 • 12 minutes to read • Edit Online

.NET Framework ofrece un amplio control sobre la representación de cadena de valores numéricos. Admite las
siguientes características para personalizar el formato de los valores numéricos:
Cadenas con formato numérico estándar, que proporcionan un conjunto predefinido de formatos para
convertir números en su representación de cadena. Se pueden usar con cualquier método de formato
numérico, como Decimal.ToString(String), que tiene un parámetro format . Para obtener detalles, vea
Cadenas con formato numérico estándar.
Cadenas con formato numérico personalizado, que proporcionan un conjunto de símbolos que pueden
combinarse para definir especificadores de formato numérico personalizado. Se pueden usar también con
cualquier método de formato numérico, como Decimal.ToString(String), que tiene un parámetro format .
Para obtener detalles, consulte Cadenas con formato numérico personalizado.
Objetos personalizados CultureInfo o NumberFormatInfo, que definen los símbolos y los modelos de
formato que se usan para mostrar las representaciones de cadena de valores numéricos. Se pueden usar con
cualquier método de formato numérico, como ToString, que tiene un parámetro provider . Normalmente, el
parámetro provider se usa para especificar el formato específico de la referencia cultural.
En algunos casos (por ejemplo, cuando una aplicación debe mostrar un número de cuenta con formato, un número
de identificación o un código postal) estas tres técnicas no resultan apropiadas. .NET Framework también permite
definir un objeto de formato que no es ni un objeto CultureInfo ni NumberFormatInfo para determinar cómo se
aplica formato a un valor numérico. En este tema se proporcionan instrucciones paso a paso para implementar este
tipo de objeto y se ofrece un ejemplo que da formato a números de teléfono.
Para definir un proveedor de formato personalizado
1. Defina una clase que implementa las interfaces IFormatProvider y ICustomFormatter.
2. Implemente el método IFormatProvider.GetFormat. GetFormat es un método de devolución de llamada que
el método de formato (como el método String.Format(IFormatProvider, String, Object[])) invoca para
recuperar el objeto realmente responsable del formato personalizado. Una implementación típica de
GetFormat hace lo siguiente:
a. Determina si el objeto Type pasado como un parámetro de método representa a una interfaz
ICustomFormatter.
b. Si el parámetro representa a la interfaz ICustomFormatter, GetFormat devuelve un objeto que
implementa la interfaz ICustomFormatter, que es responsable de proporcionar el formato
personalizado. Normalmente, el objeto de formato personalizado se devuelve a sí mismo.
c. Si el parámetro no representa la interfaz ICustomFormatter, GetFormat devuelve null .
3. Implemente el método Format. Este método es invocado por el método String.Format(IFormatProvider,
String, Object[]) y es responsable de devolver la representación de cadena de un número. La implementación
del método normalmente implica lo siguiente:
a. Opcionalmente, asegúrese de que el método se haya diseñado para proporcionar servicios de
formato al examinar el parámetro provider . En el caso de los objetos de formato que implementan
IFormatProvider y ICustomFormatter, esto implica probar la igualdad del parámetro provider y el
objeto de formato actual.
b. Determine si el objeto de formato debe admitir especificadores de formato personalizado. (Por
ejemplo, un especificador de formato "N" podría indicar que debe generarse un número de teléfono
de los Estados Unidos en formato NANP, mientras que una "I" podría indicar la salida en el formato de
la recomendación E.123 de ITU-T). Si se usan especificadores de formato, el método debe controlar el
especificador de formato específico. Se pasa al método en el parámetro format . Si no hay ningún
especificador, el valor del parámetro format es String.Empty.
c. Recupere el valor numérico pasado al método como parámetro arg . Realice todas las
manipulaciones necesarias para convertirlo en su representación de cadena.
d. Devuelva la representación de cadena del parámetro arg .
Para usar un objeto de formato numérico personalizado
1. Cree una nueva instancia de la clase de formato personalizado.
2. Llame al método de formato String.Format(IFormatProvider, String, Object[]) y pásele el objeto de formato
personalizado, el especificador de formato (o String.Empty si no se usa ninguno) y el valor numérico al que
se va a dar formato.

Ejemplo
En el ejemplo siguiente se define un proveedor de formato numérico personalizado denominado
TelephoneFormatter que convierte un número que representa un número de teléfono de los Estados Unidos en su
formato NANP o E.123. El método controla dos especificadores de formato, "N" (que genera el formato NANP) e "I"
(que genera el formato E.123 internacional).

using System;
using System.Globalization;

public class TelephoneFormatter : IFormatProvider, ICustomFormatter


{
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
return this;
else
return null;
}

public string Format(string format, object arg, IFormatProvider formatProvider)


{
// Check whether this is an appropriate callback
if (! this.Equals(formatProvider))
return null;

// Set default format specifier


if (string.IsNullOrEmpty(format))
format = "N";

string numericString = arg.ToString();

if (format == "N")
{
if (numericString.Length <= 4)
return numericString;
else if (numericString.Length == 7)
return numericString.Substring(0, 3) + "-" + numericString.Substring(3, 4);
else if (numericString.Length == 10)
return "(" + numericString.Substring(0, 3) + ") " +
numericString.Substring(3, 3) + "-" + numericString.Substring(6);
else
else
throw new FormatException(
string.Format("'{0}' cannot be used to format {1}.",
format, arg.ToString()));
}
else if (format == "I")
{
if (numericString.Length < 10)
throw new FormatException(string.Format("{0} does not have 10 digits.", arg.ToString()));
else
numericString = "+1 " + numericString.Substring(0, 3) + " " + numericString.Substring(3, 3) + " " +
numericString.Substring(6);
}
else
{
throw new FormatException(string.Format("The {0} format specifier is invalid.", format));
}
return numericString;
}
}

public class TestTelephoneFormatter


{
public static void Main()
{
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 0));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 911));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 8490216));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 4257884748));

Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 0));


Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 911));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 8490216));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 4257884748));

Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:I}", 4257884748));


}
}
Public Class TelephoneFormatter : Implements IFormatProvider, ICustomFormatter
Public Function GetFormat(formatType As Type) As Object _
Implements IFormatProvider.GetFormat
If formatType Is GetType(ICustomFormatter) Then
Return Me
Else
Return Nothing
End If
End Function

Public Function Format(fmt As String, arg As Object, _


formatProvider As IFormatProvider) As String _
Implements ICustomFormatter.Format
' Check whether this is an appropriate callback
If Not Me.Equals(formatProvider) Then Return Nothing

' Set default format specifier


If String.IsNullOrEmpty(fmt) Then fmt = "N"

Dim numericString As String = arg.ToString

If fmt = "N" Then


Select Case numericString.Length
Case <= 4
Return numericString
Case 7
Return Left(numericString, 3) & "-" & Mid(numericString, 4)
Case 10
Return "(" & Left(numericString, 3) & ") " & _
Mid(numericString, 4, 3) & "-" & Mid(numericString, 7)
Case Else
Throw New FormatException( _
String.Format("'{0}' cannot be used to format {1}.", _
fmt, arg.ToString()))
End Select
ElseIf fmt = "I" Then
If numericString.Length < 10 Then
Throw New FormatException(String.Format("{0} does not have 10 digits.", arg.ToString()))
Else
numericString = "+1 " & Left(numericString, 3) & " " & Mid(numericString, 4, 3) & " " &
Mid(numericString, 7)
End If
Else
Throw New FormatException(String.Format("The {0} format specifier is invalid.", fmt))
End If
Return numericString
End Function
End Class

Public Module TestTelephoneFormatter


Public Sub Main
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 0))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 911))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 8490216))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 4257884748))

Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 0))


Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 911))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 8490216))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 4257884748))

Console.WriteLine(String.Format(New TelephoneFormatter, "{0:I}", 4257884748))


End Sub
End Module

El proveedor de formato numérico personalizado puede utilizarse solo con el método


String.Format(IFormatProvider, String, Object[]). Las demás sobrecargas de métodos de formato numérico (como
ToString ) que tienen un parámetro de tipo IFormatProvider pasan a la implementación de
IFormatProvider.GetFormat un objeto Type que representa al tipo NumberFormatInfo. A cambio, esperan que el
método devuelva un objeto NumberFormatInfo. Si no es así, se omite el proveedor de formato numérico
personalizado y se usa el objeto NumberFormatInfo de la referencia cultural actual en su lugar. En el ejemplo, el
método TelephoneFormatter.GetFormat controla la posibilidad de que se pueda pasar incorrectamente a un método
de formato numérico al examinar el parámetro de método y devolver null si representa a un tipo distinto de
ICustomFormatter.
Si un proveedor de formato numérico personalizado admite un conjunto de especificadores de formato, asegúrese
de proporcionar un comportamiento predeterminado si no se proporciona ningún especificador de formato en el
elemento de formato usado en la llamada al método String.Format(IFormatProvider, String, Object[]). En el ejemplo,
"N" es el especificador de formato predeterminado. Esto permite convertir un número en un número de teléfono
con formato al proporcionar un especificador de formato explícito. En el ejemplo siguiente se muestra una llamada
al método así.

Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 4257884748));

Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 4257884748))

Pero también permite que se produzca la conversión si no hay ningún especificador de formato. En el ejemplo
siguiente se muestra una llamada al método así.

Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 4257884748));

Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 4257884748))

Si no se ha definido ningún especificador de formato predeterminado, la implementación del método


ICustomFormatter.Format debe incluir código como el siguiente para que .NET pueda proporcionar formato no
admitido por el código.

if (arg is IFormattable)
s = ((IFormattable)arg).ToString(format, formatProvider);
else if (arg != null)
s = arg.ToString();

If TypeOf (arg) Is IFormattable Then


s = DirectCast(arg, IFormattable).ToString(fmt, formatProvider)
ElseIf arg IsNot Nothing Then
s = arg.ToString()
End If

En el caso de este ejemplo, el método que implementa ICustomFormatter.Format está diseñado para que actúe
como un método de devolución de llamada para el método String.Format(IFormatProvider, String, Object[]). Por lo
tanto, examina el parámetro formatProvider para determinar si contiene una referencia al objeto
TelephoneFormatter actual. Pero también se puede llamar al método directamente desde el código. En ese caso,
puede usar el parámetro formatProvider para proporcionar un objeto CultureInfo o NumberFormatInfo que
aporte información de formato específica de la referencia cultural.
Procedimiento para valores de fecha y hora de ida y
vuelta
16/09/2020 • 12 minutes to read • Edit Online

En muchas aplicaciones, un valor de fecha y hora sirve para identificar inequívocamente un único punto en el
tiempo. En este artículo se muestra cómo guardar y restaurar un valor DateTime, DateTimeOffset y de fecha y hora
con información sobre la zona horaria, de manera que el valor restaurado identifique la misma hora que el
guardado.

Acción de ida y vuelta para un valor DateTime


1. Convierta el valor DateTime en su representación de cadena mediante una llamada al método
DateTime.ToString(String) con el especificador de formato "o".
2. Guarde la representación de cadena del valor DateTime en un archivo o pásela a través de un límite de
proceso, dominio de aplicación o máquina.
3. Recupere la cadena que representa el valor DateTime.
4. Llame al método DateTime.Parse(String, IFormatProvider, DateTimeStyles) y pase
DateTimeStyles.RoundtripKind como el valor del parámetro styles .
En el ejemplo siguiente se muestra cómo usar un valor DateTime de ida y vuelta.

const string fileName = @".\DateFile.txt";

StreamWriter outFile = new StreamWriter(fileName);

// Save DateTime value.


DateTime dateToSave = DateTime.SpecifyKind(new DateTime(2008, 6, 12, 18, 45, 15),
DateTimeKind.Local);
string dateString = dateToSave.ToString("o");
Console.WriteLine("Converted {0} ({1}) to {2}.",
dateToSave.ToString(),
dateToSave.Kind.ToString(),
dateString);
outFile.WriteLine(dateString);
Console.WriteLine("Wrote {0} to {1}.", dateString, fileName);
outFile.Close();

// Restore DateTime value.


DateTime restoredDate;

StreamReader inFile = new StreamReader(fileName);


dateString = inFile.ReadLine();
inFile.Close();
restoredDate = DateTime.Parse(dateString, null, DateTimeStyles.RoundtripKind);
Console.WriteLine("Read {0} ({2}) from {1}.", restoredDate.ToString(),
fileName,
restoredDate.Kind.ToString());
// The example displays the following output:
// Converted 6/12/2008 6:45:15 PM (Local) to 2008-06-12T18:45:15.0000000-05:00.
// Wrote 2008-06-12T18:45:15.0000000-05:00 to .\DateFile.txt.
// Read 6/12/2008 6:45:15 PM (Local) from .\DateFile.txt.
Const fileName As String = ".\DateFile.txt"

Dim outFile As New StreamWriter(fileName)

' Save DateTime value.


Dim dateToSave As Date = DateTime.SpecifyKind(#06/12/2008 6:45:15 PM#, _
DateTimeKind.Local)
Dim dateString As String = dateToSave.ToString("o")
Console.WriteLine("Converted {0} ({1}) to {2}.", dateToSave.ToString(), _
dateToSave.Kind.ToString(), dateString)
outFile.WriteLine(dateString)
Console.WriteLine("Wrote {0} to {1}.", dateString, fileName)
outFile.Close()

' Restore DateTime value.


Dim restoredDate As Date

Dim inFile As New StreamReader(fileName)


dateString = inFile.ReadLine()
inFile.Close()
restoredDate = DateTime.Parse(dateString, Nothing, DateTimeStyles.RoundTripKind)
Console.WriteLine("Read {0} ({2}) from {1}.", restoredDate.ToString(), _
fileName, restoredDAte.Kind.ToString())
' The example displays the following output:
' Converted 6/12/2008 6:45:15 PM (Local) to 2008-06-12T18:45:15.0000000-05:00.
' Wrote 2008-06-12T18:45:15.0000000-05:00 to .\DateFile.txt.
' Read 6/12/2008 6:45:15 PM (Local) from .\DateFile.txt.

Cuando se usa un valor DateTime de ida y vuelta, esta técnica mantiene correctamente la hora para todas las horas
locales y universales. Por ejemplo, si un valor local DateTime se guarda en un sistema de la zona horaria estándar
del Pacífico de Estados Unidos y se restaura en un sistema de la zona horaria estándar central de Estados Unidos, la
fecha y hora restauradas serán dos horas posteriores a la hora original, lo que refleja la diferencia horaria entre las
dos zonas. Pero esta técnica no es necesariamente precisa para horas no especificadas. Todos los valores DateTime
cuya propiedad Kind es Unspecified se tratan como si fueran horas locales. Si no es una hora local, DateTime no
identificará correctamente el punto en el tiempo. La solución alternativa para esta limitación es acoplar
estrechamente un valor de fecha y hora con su zona horaria para la operación de guardado y restauración.

Acción de ida y vuelta para un valor DateTimeOffset


1. Convierta el valor DateTimeOffset en su representación de cadena mediante una llamada al método
DateTimeOffset.ToString(String) con el especificador de formato "o".
2. Guarde la representación de cadena del valor DateTimeOffset en un archivo o pásela a través de un límite de
proceso, dominio de aplicación o máquina.
3. Recupere la cadena que representa el valor DateTimeOffset.
4. Llame al método DateTimeOffset.Parse(String, IFormatProvider, DateTimeStyles) y pase
DateTimeStyles.RoundtripKind como el valor del parámetro styles .
En el ejemplo siguiente se muestra cómo usar un valor DateTimeOffset de ida y vuelta.
const string fileName = @".\DateOff.txt";

StreamWriter outFile = new StreamWriter(fileName);

// Save DateTime value.


DateTimeOffset dateToSave = new DateTimeOffset(2008, 6, 12, 18, 45, 15,
new TimeSpan(7, 0, 0));
string dateString = dateToSave.ToString("o");
Console.WriteLine("Converted {0} to {1}.", dateToSave.ToString(),
dateString);
outFile.WriteLine(dateString);
Console.WriteLine("Wrote {0} to {1}.", dateString, fileName);
outFile.Close();

// Restore DateTime value.


DateTimeOffset restoredDateOff;

StreamReader inFile = new StreamReader(fileName);


dateString = inFile.ReadLine();
inFile.Close();
restoredDateOff = DateTimeOffset.Parse(dateString, null,
DateTimeStyles.RoundtripKind);
Console.WriteLine("Read {0} from {1}.", restoredDateOff.ToString(),
fileName);
// The example displays the following output:
// Converted 6/12/2008 6:45:15 PM +07:00 to 2008-06-12T18:45:15.0000000+07:00.
// Wrote 2008-06-12T18:45:15.0000000+07:00 to .\DateOff.txt.
// Read 6/12/2008 6:45:15 PM +07:00 from .\DateOff.txt.

Const fileName As String = ".\DateOff.txt"

Dim outFile As New StreamWriter(fileName)

' Save DateTime value.


Dim dateToSave As New DateTimeOffset(2008, 6, 12, 18, 45, 15, _
New TimeSpan(7, 0, 0))
Dim dateString As String = dateToSave.ToString("o")
Console.WriteLine("Converted {0} to {1}.", dateToSave.ToString(), dateString)
outFile.WriteLine(dateString)
Console.WriteLine("Wrote {0} to {1}.", dateString, fileName)
outFile.Close()

' Restore DateTime value.


Dim restoredDateOff As DateTimeOffset

Dim inFile As New StreamReader(fileName)


dateString = inFile.ReadLine()
inFile.Close()
restoredDateOff = DateTimeOffset.Parse(dateString, Nothing, DateTimeStyles.RoundTripKind)
Console.WriteLine("Read {0} from {1}.", restoredDateOff.ToString(), fileName)
' The example displays the following output:
' Converted 6/12/2008 6:45:15 PM +07:00 to 2008-06-12T18:45:15.0000000+07:00.
' Wrote 2008-06-12T18:45:15.0000000+07:00 to .\DateOff.txt.
' Read 6/12/2008 6:45:15 PM +07:00 from .\DateOff.txt.

Esta técnica identifica siempre de forma inequívoca un valor DateTimeOffset como un único punto en el tiempo. El
valor puede convertirse entonces en la hora universal coordinada (UTC) al llamar al método
DateTimeOffset.ToUniversalTime, o bien puede convertirse en la hora de una zona horaria concreta al llamar a los
métodos DateTimeOffset.ToOffset o TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo). La limitación
principal de esta técnica está en que las operaciones aritméticas de fecha y hora, cuando se realizan en un valor
DateTimeOffset que representa la hora en una zona horaria determinada, podrían no generar resultados precisos
para esa zona horaria. Esto se debe a que, cuando se crea una instancia de un valor DateTimeOffset, se desasocia de
su zona horaria. Por lo tanto, ya no se pueden aplicar las reglas de ajuste de esa zona de horaria al realizar cálculos
de fecha y hora. Para evitar este problema, puede definir un tipo personalizado que incluya tanto el valor de fecha y
hora como la zona horaria correspondiente.

Acción de ida y vuelta para un valor de fecha y hora con su zona horaria
1. Defina una clase o una estructura con dos campos. El primer campo es un objeto DateTime o
DateTimeOffset y el segundo es un objeto TimeZoneInfo. El ejemplo siguiente es una versión sencilla de ese
tipo.

[Serializable] public class DateInTimeZone


{
private TimeZoneInfo tz;
private DateTimeOffset thisDate;

public DateInTimeZone() {}

public DateInTimeZone(DateTimeOffset date, TimeZoneInfo timeZone)


{
if (timeZone == null)
throw new ArgumentNullException("The time zone cannot be null.");

this.thisDate = date;
this.tz = timeZone;
}

public DateTimeOffset DateAndTime


{
get {
return this.thisDate;
}
set {
if (value.Offset != this.tz.GetUtcOffset(value))
this.thisDate = TimeZoneInfo.ConvertTime(value, tz);
else
this.thisDate = value;
}
}

public TimeZoneInfo TimeZone


{
get {
return this.tz;
}
}
}
<Serializable> Public Class DateInTimeZone
Private tz As TimeZoneInfo
Private thisDate As DateTimeOffset

Public Sub New()


End Sub

Public Sub New(date1 As DateTimeOffset, timeZone As TimeZoneInfo)


If timeZone Is Nothing Then
Throw New ArgumentNullException("The time zone cannot be null.")
End If
Me.thisDate = date1
Me.tz = timeZone
End Sub

Public Property DateAndTime As DateTimeOffset


Get
Return Me.thisDate
End Get
Set
If Value.Offset <> Me.tz.GetUtcOffset(Value) Then
Me.thisDate = TimeZoneInfo.ConvertTime(Value, tz)
Else
Me.thisDate = Value
End If
End Set
End Property

Public ReadOnly Property TimeZone As TimeZoneInfo


Get
Return tz
End Get
End Property
End Class

2. Marque la clase con el atributo SerializableAttribute.


3. Serialice el objeto con el método BinaryFormatter.Serialize.
4. Restaure el objeto con el método Deserialize.
5. Convierta (en C# o en Visual Basic) el objeto deserializado en un objeto del tipo adecuado.
En el ejemplo siguiente se muestra cómo realizar una acción de ida y vuelta para un objeto que almacena
información de zona horaria y de fecha y hora.
const string fileName = @".\DateWithTz.dat";

DateTime tempDate = new DateTime(2008, 9, 3, 19, 0, 0);


TimeZoneInfo tempTz = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
DateInTimeZone dateWithTz = new DateInTimeZone(new DateTimeOffset(tempDate,
tempTz.GetUtcOffset(tempDate)),
tempTz);

// Store DateInTimeZone value to a file


FileStream outFile = new FileStream(fileName, FileMode.Create);
try
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(outFile, dateWithTz);
Console.WriteLine("Saving {0} {1} to {2}", dateWithTz.DateAndTime,
dateWithTz.TimeZone.IsDaylightSavingTime(dateWithTz.DateAndTime) ?
dateWithTz.TimeZone.DaylightName : dateWithTz.TimeZone.DaylightName,
fileName);
}
catch (SerializationException)
{
Console.WriteLine("Unable to serialize time data to {0}.", fileName);
}
finally
{
outFile.Close();
}

// Retrieve DateInTimeZone value


if (File.Exists(fileName))
{
FileStream inFile = new FileStream(fileName, FileMode.Open);
DateInTimeZone dateWithTz2 = new DateInTimeZone();
try
{
BinaryFormatter formatter = new BinaryFormatter();
dateWithTz2 = formatter.Deserialize(inFile) as DateInTimeZone;
Console.WriteLine("Restored {0} {1} from {2}", dateWithTz2.DateAndTime,
dateWithTz2.TimeZone.IsDaylightSavingTime(dateWithTz2.DateAndTime) ?
dateWithTz2.TimeZone.DaylightName : dateWithTz2.TimeZone.DaylightName,
fileName);
}
catch (SerializationException)
{
Console.WriteLine("Unable to retrieve date and time information from {0}",
fileName);
}
finally
{
inFile.Close();
}
}
// This example displays the following output to the console:
// Saving 9/3/2008 7:00:00 PM -05:00 Central Daylight Time to .\DateWithTz.dat
// Restored 9/3/2008 7:00:00 PM -05:00 Central Daylight Time from .\DateWithTz.dat
Const fileName As String = ".\DateWithTz.dat"

Dim tempDate As Date = #9/3/2008 7:00:00 PM#


Dim tempTz As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time")
Dim dateWithTz As New DateInTimeZone(New DateTimeOffset(tempDate, _
tempTz.GetUtcOffset(tempDate)), _
tempTz)

' Store DateInTimeZone value to a file


Dim outFile As New FileStream(fileName, FileMode.Create)
Try
Dim formatter As New BinaryFormatter()
formatter.Serialize(outFile, dateWithTz)
Console.WriteLine("Saving {0} {1} to {2}", dateWithTz.DateAndTime, _
IIf(dateWithTz.TimeZone.IsDaylightSavingTime(dateWithTz.DateAndTime), _
dateWithTz.TimeZone.DaylightName, dateWithTz.TimeZone.DaylightName), _
fileName)
Catch e As SerializationException
Console.WriteLine("Unable to serialize time data to {0}.", fileName)
Finally
outFile.Close()
End Try

' Retrieve DateInTimeZone value


If File.Exists(fileName) Then
Dim inFile As New FileStream(fileName, FileMode.Open)
Dim dateWithTz2 As New DateInTimeZone()
Try
Dim formatter As New BinaryFormatter()
dateWithTz2 = DirectCast(formatter.Deserialize(inFile), DateInTimeZone)
Console.WriteLine("Restored {0} {1} from {2}", dateWithTz2.DateAndTime, _
IIf(dateWithTz2.TimeZone.IsDaylightSavingTime(dateWithTz2.DateAndTime), _
dateWithTz2.TimeZone.DaylightName, dateWithTz2.TimeZone.DaylightName), _
fileName)
Catch e As SerializationException
Console.WriteLine("Unable to retrieve date and time information from {0}", _
fileName)
Finally
inFile.Close
End Try
End If
' This example displays the following output to the console:
' Saving 9/3/2008 7:00:00 PM -05:00 Central Daylight Time to .\DateWithTz.dat
' Restored 9/3/2008 7:00:00 PM -05:00 Central Daylight Time from .\DateWithTz.dat

Esta técnica debe reflejar siempre de forma inequívoca el punto correcto de tiempo antes y después de guardar y
restaurar, siempre que la implementación del objeto combinado de fecha y hora y de zona horaria no permita que
el valor de fecha quede fuera de la sincronización con el valor de zona horaria.

Compilar el código
Para estos ejemplos se necesita lo siguiente:
Que los espacios de nombres siguientes se importen con directivas using de C# o con instrucciones
Imports de Visual Basic:

System (solo C#)


System.Globalization
System.IO
System.Runtime.Serialization
System.Runtime.Serialization.Formatters.Binary
Que cada ejemplo de código, excepto la clase DateInTimeZone , se incluyan en una clase o módulo de Visual
Basic, encapsulado en métodos y llamado desde el método Main .

Vea también
Elección entre DateTime, DateTimeOffset, TimeSpan y TimeZoneInfo
Cadenas con formato de fecha y hora estándar
Procedimiento para mostrar milisegundos en los
valores de fecha y hora
16/09/2020 • 7 minutes to read • Edit Online

Los métodos de formato de fecha y hora predeterminados, como DateTime.ToString(), incluyen las horas, minutos y
segundos de un valor de tiempo, pero excluyen el componente correspondiente a los milisegundos. En este tema se
muestra cómo se incluye un componente de milisegundos de un valor de fecha y hora en cadenas de fecha y hora
con formato.
Para mostrar el componente de milisegundos de un valor DateTime
1. Cuando trabaje con la representación de cadena de una fecha, conviértala en un valor DateTime o
DateTimeOffset mediante el método estático DateTime.Parse(String) o DateTimeOffset.Parse(String).
2. Para extraer la representación de cadena del componente de milisegundos de una hora, llame al método
DateTime.ToString(String) o ToString del valor de fecha y hora y pase el modelo de formato personalizado
fff o FFF en solitario o junto a otros especificadores de formato personalizado como el parámetro
format .

Ejemplo
En el ejemplo se muestra el componente de milisegundos de un valor DateTime y DateTimeOffset en la consola en
su presentación en solitario e incluido en una cadena de fecha y hora más larga.
using System;
using System.Globalization;
using System.Text.RegularExpressions;

public class MillisecondDisplay


{
public static void Main()
{
string dateString = "7/16/2008 8:32:45.126 AM";

try
{
DateTime dateValue = DateTime.Parse(dateString);
DateTimeOffset dateOffsetValue = DateTimeOffset.Parse(dateString);

// Display Millisecond component alone.


Console.WriteLine("Millisecond component only: {0}",
dateValue.ToString("fff"));
Console.WriteLine("Millisecond component only: {0}",
dateOffsetValue.ToString("fff"));

// Display Millisecond component with full date and time.


Console.WriteLine("Date and Time with Milliseconds: {0}",
dateValue.ToString("MM/dd/yyyy hh:mm:ss.fff tt"));
Console.WriteLine("Date and Time with Milliseconds: {0}",
dateOffsetValue.ToString("MM/dd/yyyy hh:mm:ss.fff tt"));

// Append millisecond pattern to current culture's full date time pattern


string fullPattern = DateTimeFormatInfo.CurrentInfo.FullDateTimePattern;
fullPattern = Regex.Replace(fullPattern, "(:ss|:s)", "$1.fff");

// Display Millisecond component with modified full date and time pattern.
Console.WriteLine("Modified full date time pattern: {0}",
dateValue.ToString(fullPattern));
Console.WriteLine("Modified full date time pattern: {0}",
dateOffsetValue.ToString(fullPattern));
}
catch (FormatException)
{
Console.WriteLine("Unable to convert {0} to a date.", dateString);
}
}
}
// The example displays the following output if the current culture is en-US:
// Millisecond component only: 126
// Millisecond component only: 126
// Date and Time with Milliseconds: 07/16/2008 08:32:45.126 AM
// Date and Time with Milliseconds: 07/16/2008 08:32:45.126 AM
// Modified full date time pattern: Wednesday, July 16, 2008 8:32:45.126 AM
// Modified full date time pattern: Wednesday, July 16, 2008 8:32:45.126 AM
Imports System.Globalization
Imports System.Text.REgularExpressions

Module MillisecondDisplay
Public Sub Main()

Dim dateString As String = "7/16/2008 8:32:45.126 AM"

Try
Dim dateValue As Date = Date.Parse(dateString)
Dim dateOffsetValue As DateTimeOffset = DateTimeOffset.Parse(dateString)

' Display Millisecond component alone.


Console.WriteLine("Millisecond component only: {0}", _
dateValue.ToString("fff"))
Console.WriteLine("Millisecond component only: {0}", _
dateOffsetValue.ToString("fff"))

' Display Millisecond component with full date and time.


Console.WriteLine("Date and Time with Milliseconds: {0}", _
dateValue.ToString("MM/dd/yyyy hh:mm:ss.fff tt"))
Console.WriteLine("Date and Time with Milliseconds: {0}", _
dateOffsetValue.ToString("MM/dd/yyyy hh:mm:ss.fff tt"))

' Append millisecond pattern to current culture's full date time pattern
Dim fullPattern As String = DateTimeFormatInfo.CurrentInfo.FullDateTimePattern
fullPattern = Regex.Replace(fullPattern, "(:ss|:s)", "$1.fff")

' Display Millisecond component with modified full date and time pattern.
Console.WriteLine("Modified full date time pattern: {0}", _
dateValue.ToString(fullPattern))
Console.WriteLine("Modified full date time pattern: {0}", _
dateOffsetValue.ToString(fullPattern))
Catch e As FormatException
Console.WriteLine("Unable to convert {0} to a date.", dateString)
End Try
End Sub
End Module
' The example displays the following output if the current culture is en-US:
' Millisecond component only: 126
' Millisecond component only: 126
' Date and Time with Milliseconds: 07/16/2008 08:32:45.126 AM
' Date and Time with Milliseconds: 07/16/2008 08:32:45.126 AM
' Modified full date time pattern: Wednesday, July 16, 2008 8:32:45.126 AM
' Modified full date time pattern: Wednesday, July 16, 2008 8:32:45.126 AM

El modelo de formato fff incluye todos los ceros finales en el valor de milisegundos. El modelo de formato FFF
suprime todos estos ceros. En el siguiente ejemplo se ilustra la diferencia.

DateTime dateValue = new DateTime(2008, 7, 16, 8, 32, 45, 180);


Console.WriteLine(dateValue.ToString("fff"));
Console.WriteLine(dateValue.ToString("FFF"));
// The example displays the following output to the console:
// 180
// 18

Dim dateValue As New Date(2008, 7, 16, 8, 32, 45, 180)


Console.WriteLIne(dateValue.ToString("fff"))
Console.WriteLine(dateValue.ToString("FFF"))
' The example displays the following output to the console:
' 180
' 18
Un problema que surge al definir un especificador de formato personalizado completo que incluya el componente
de milisegundos de un valor de fecha y hora es que éste establece un formato codificado de forma rígida que es
posible que no se corresponda con la organización de elementos horarios de la referencia cultural actual de la
aplicación. Una alternativa más conveniente consiste en recuperar uno de los modelos de presentación de fecha y
hora definidos por el objeto DateTimeFormatInfo de la referencia cultural actual y modificarlo para incluir los
milisegundos. En el ejemplo se muestra también este enfoque. En este ejemplo, se recupera el modelo completo de
fecha y hora de la referencia cultural actual de la propiedad DateTimeFormatInfo.FullDateTimePattern y, a
continuación, se inserta el modelo personalizado .ffff tras el segundo modelo. Observe que en el ejemplo se
utiliza una expresión regular para realizar esta operación en una única llamada al método.
También puede utilizar un especificador de formato personalizado para mostrar una fracción de segundo distinta
de los milisegundos. Por ejemplo, el especificador de formato personalizado f o F muestra las décimas de
segundo, el especificador de formato personalizado ff o FF muestra las centésimas de segundo y el
especificador de formato personalizado ffff o FFFF muestra las diezmilésimas de segundo. Las fracciones de
milisegundo se truncan en lugar de redondearse en la cadena devuelta. Estos especificadores de formato se utilizan
en el ejemplo siguiente.

DateTime dateValue = new DateTime(2008, 7, 16, 8, 32, 45, 180);


Console.WriteLine("{0} seconds", dateValue.ToString("s.f"));
Console.WriteLine("{0} seconds", dateValue.ToString("s.ff"));
Console.WriteLine("{0} seconds", dateValue.ToString("s.ffff"));
// The example displays the following output to the console:
// 45.1 seconds
// 45.18 seconds
// 45.1800 seconds

Dim dateValue As New DateTime(2008, 7, 16, 8, 32, 45, 180)


Console.WriteLine("{0} seconds", dateValue.ToString("s.f"))
Console.WriteLine("{0} seconds", dateValue.ToString("s.ff"))
Console.WriteLine("{0} seconds", dateValue.ToString("s.ffff"))
' The example displays the following output to the console:
' 45.1 seconds
' 45.18 seconds
' 45.1800 seconds

NOTE
Es posible mostrar unidades fraccionarias de segundo muy pequeñas, como diezmilésimas o cienmilésimas de segundo. Sin
embargo, estos valores no suelen ser significativos. La precisión de los valores de fecha y hora depende de la resolución del
reloj del sistema. En los sistemas operativos Windows NT 3.5 (y versiones posteriores) y Windows Vista, la resolución del reloj
es aproximadamente de 10 a 15 milisegundos.

Vea también
DateTimeFormatInfo
Cadenas con formato de fecha y hora personalizado
Procedimiento para mostrar fechas en calendarios no
gregorianos
16/09/2020 • 13 minutes to read • Edit Online

Los tipos DateTime y DateTimeOffset usan el calendario gregoriano como calendario predeterminado. Esto
significa que al llamar al método ToString de un valor de fecha y hora se muestra la representación de cadena de
esa fecha y hora en el calendario gregoriano, aunque se creara con otro calendario. Esto se muestra en el ejemplo
siguiente, que usa dos maneras diferentes de crear un valor de fecha y hora con el calendario persa, pero muestra
esos valores de fecha y hora en el calendario gregoriano cuando llama al método ToString. En este ejemplo se
reflejan dos técnicas usadas habitualmente, aunque incorrectas, para mostrar la fecha en un calendario
determinado.

PersianCalendar persianCal = new PersianCalendar();

DateTime persianDate = persianCal.ToDateTime(1387, 3, 18, 12, 0, 0, 0);


Console.WriteLine(persianDate.ToString());

persianDate = new DateTime(1387, 3, 18, persianCal);


Console.WriteLine(persianDate.ToString());
// The example displays the following output to the console:
// 6/7/2008 12:00:00 PM
// 6/7/2008 12:00:00 AM

Dim persianCal As New PersianCalendar()

Dim persianDate As Date = persianCal.ToDateTime(1387, 3, 18, _


12, 0, 0, 0)
Console.WriteLine(persianDate.ToString())

persianDate = New DateTime(1387, 3, 18, persianCal)


Console.WriteLine(persianDate.ToString())
' The example displays the following output to the console:
' 6/7/2008 12:00:00 PM
' 6/7/2008 12:00:00 AM

Se pueden usar dos técnicas distintas para mostrar la fecha en un calendario determinado. La primera exige que el
calendario sea el predeterminado de una referencia cultural determinada. La segunda se puede usar con cualquier
calendario.
Para mostrar la fecha del calendario predeterminado de una referencia cultural
1. Cree una instancia de un objeto de calendario derivado de la clase Calendar que representa al calendario
que se va a usar.
2. Cree una instancia de un objeto CultureInfo que representa la referencia cultural cuyo formato se va a usar
para mostrar la fecha.
3. Llame al método Array.Exists para determinar si el objeto de calendario es miembro de la matriz devuelta
por la propiedad CultureInfo.OptionalCalendars. Esto indica que el calendario puede actuar como calendario
predeterminado del objeto CultureInfo. Si no es miembro de la matriz, siga las instrucciones de la sección
"Para mostrar la fecha en cualquier calendario".
4. Asigne el objeto de calendario a la propiedad Calendar del objeto DateTimeFormatInfo devuelto por la
propiedad CultureInfo.DateTimeFormat.

NOTE
La clase CultureInfo también tiene una propiedad Calendar. Pero es de solo lectura y constante; no cambia para
reflejar el nuevo calendario predeterminado asignado a la propiedad DateTimeFormatInfo.Calendar.

5. Llame al método ToString o ToString y pásele el objeto CultureInfo cuyo calendario predeterminado se
modificó en el paso anterior.
Para mostrar la fecha en cualquier calendario
1. Cree una instancia de un objeto de calendario derivado de la clase Calendar que representa al calendario
que se va a usar.
2. Determine qué elementos de fecha y hora deben aparecer en la representación de cadena del valor de fecha
y hora.
3. Para cada elemento de fecha y hora que quiera mostrar, llame al método Get del objeto de calendario... .
Están disponibles los siguientes métodos:
GetYear, para mostrar el año en el calendario adecuado.
GetMonth, para mostrar el mes en el calendario adecuado.
GetDayOfMonth, para mostrar el número del día del mes en el calendario adecuado.
GetHour, para mostrar la hora del día en el calendario adecuado.
GetMinute, para mostrar los minutos de la hora en el calendario adecuado.
GetSecond, para mostrar los segundos del minuto en el calendario adecuado.
GetMilliseconds, para mostrar los milisegundos del segundo en el calendario adecuado.

Ejemplo
En el ejemplo se muestra una fecha con dos calendarios diferentes. Se muestra la fecha después de definir el
calendario Hijri como calendario predeterminado de la referencia cultural ar-JO y se muestra la fecha con el
calendario persa, que no se admite como calendario opcional de la referencia cultural fa-IR.

using System;
using System.Globalization;

public class CalendarDates


{
public static void Main()
{
HijriCalendar hijriCal = new HijriCalendar();
CalendarUtility hijriUtil = new CalendarUtility(hijriCal);
DateTime dateValue1 = new DateTime(1429, 6, 29, hijriCal);
DateTimeOffset dateValue2 = new DateTimeOffset(dateValue1,
TimeZoneInfo.Local.GetUtcOffset(dateValue1));
CultureInfo jc = CultureInfo.CreateSpecificCulture("ar-JO");

// Display the date using the Gregorian calendar.


Console.WriteLine("Using the system default culture: {0}",
dateValue1.ToString("d"));
// Display the date using the ar-JO culture's original default calendar.
Console.WriteLine("Using the ar-JO culture's original default calendar: {0}",
dateValue1.ToString("d", jc));
// Display the date using the Hijri calendar.
Console.WriteLine("Using the ar-JO culture with Hijri as the default calendar:");
Console.WriteLine("Using the ar-JO culture with Hijri as the default calendar:");
// Display a Date value.
Console.WriteLine(hijriUtil.DisplayDate(dateValue1, jc));
// Display a DateTimeOffset value.
Console.WriteLine(hijriUtil.DisplayDate(dateValue2, jc));

Console.WriteLine();

PersianCalendar persianCal = new PersianCalendar();


CalendarUtility persianUtil = new CalendarUtility(persianCal);
CultureInfo ic = CultureInfo.CreateSpecificCulture("fa-IR");

// Display the date using the ir-FA culture's default calendar.


Console.WriteLine("Using the ir-FA culture's default calendar: {0}",
dateValue1.ToString("d", ic));
// Display a Date value.
Console.WriteLine(persianUtil.DisplayDate(dateValue1, ic));
// Display a DateTimeOffset value.
Console.WriteLine(persianUtil.DisplayDate(dateValue2, ic));
}
}

public class CalendarUtility


{
private Calendar thisCalendar;
private CultureInfo targetCulture;

public CalendarUtility(Calendar cal)


{
this.thisCalendar = cal;
}

private bool CalendarExists(CultureInfo culture)


{
this.targetCulture = culture;
return Array.Exists(this.targetCulture.OptionalCalendars,
this.HasSameName);
}

private bool HasSameName(Calendar cal)


{
if (cal.ToString() == thisCalendar.ToString())
return true;
else
return false;
}

public string DisplayDate(DateTime dateToDisplay, CultureInfo culture)


{
DateTimeOffset displayOffsetDate = dateToDisplay;
return DisplayDate(displayOffsetDate, culture);
}

public string DisplayDate(DateTimeOffset dateToDisplay,


CultureInfo culture)
{
string specifier = "yyyy/MM/dd";

if (this.CalendarExists(culture))
{
Console.WriteLine("Displaying date in supported {0} calendar...",
this.thisCalendar.GetType().Name);
culture.DateTimeFormat.Calendar = this.thisCalendar;
return dateToDisplay.ToString(specifier, culture);
}
else
{
Console.WriteLine("Displaying date in unsupported {0} calendar...",
thisCalendar.GetType().Name);
string separator = targetCulture.DateTimeFormat.DateSeparator;

return thisCalendar.GetYear(dateToDisplay.DateTime).ToString("0000") +
separator +
thisCalendar.GetMonth(dateToDisplay.DateTime).ToString("00") +
separator +
thisCalendar.GetDayOfMonth(dateToDisplay.DateTime).ToString("00");
}
}
}
// The example displays the following output to the console:
// Using the system default culture: 7/3/2008
// Using the ar-JO culture's original default calendar: 03/07/2008
// Using the ar-JO culture with Hijri as the default calendar:
// Displaying date in supported HijriCalendar calendar...
// 1429/06/29
// Displaying date in supported HijriCalendar calendar...
// 1429/06/29
//
// Using the ir-FA culture's default calendar: 7/3/2008
// Displaying date in unsupported PersianCalendar calendar...
// 1387/04/13
// Displaying date in unsupported PersianCalendar calendar...
// 1387/04/13

Imports System.Globalization

Public Class CalendarDates


Public Shared Sub Main()
Dim hijriCal As New HijriCalendar()
Dim hijriUtil As New CalendarUtility(hijriCal)
Dim dateValue1 As Date = New Date(1429, 6, 29, hijriCal)
Dim dateValue2 As DateTimeOffset = New DateTimeOffset(dateValue1, _
TimeZoneInfo.Local.GetUtcOffset(dateValue1))
Dim jc As CultureInfo = CultureInfo.CreateSpecificCulture("ar-JO")

' Display the date using the Gregorian calendar.


Console.WriteLine("Using the system default culture: {0}", _
dateValue1.ToString("d"))
' Display the date using the ar-JO culture's original default calendar.
Console.WriteLine("Using the ar-JO culture's original default calendar: {0}", _
dateValue1.ToString("d", jc))
' Display the date using the Hijri calendar.
Console.WriteLine("Using the ar-JO culture with Hijri as the default calendar:")
' Display a Date value.
Console.WriteLine(hijriUtil.DisplayDate(dateValue1, jc))
' Display a DateTimeOffset value.
Console.WriteLine(hijriUtil.DisplayDate(dateValue2, jc))

Console.WriteLine()

Dim persianCal As New PersianCalendar()


Dim persianUtil As New CalendarUtility(persianCal)
Dim ic As CultureInfo = CultureInfo.CreateSpecificCulture("fa-IR")

' Display the date using the ir-FA culture's default calendar.
Console.WriteLine("Using the ir-FA culture's default calendar: {0}", _
dateValue1.ToString("d", ic))
' Display a Date value.
Console.WriteLine(persianUtil.DisplayDate(dateValue1, ic))
' Display a DateTimeOffset value.
Console.WriteLine(persianUtil.DisplayDate(dateValue2, ic))
End Sub
End Class

Public Class CalendarUtility


Private thisCalendar As Calendar
Private thisCalendar As Calendar
Private targetCulture As CultureInfo

Public Sub New(cal As Calendar)


Me.thisCalendar = cal
End Sub

Private Function CalendarExists(culture As CultureInfo) As Boolean


Me.targetCulture = culture
Return Array.Exists(Me.targetCulture.OptionalCalendars, _
AddressOf Me.HasSameName)
End Function

Private Function HasSameName(cal As Calendar) As Boolean


If cal.ToString() = thisCalendar.ToString() Then
Return True
Else
Return False
End If
End Function

Public Function DisplayDate(dateToDisplay As Date, _


culture As CultureInfo) As String
Dim displayOffsetDate As DateTimeOffset = dateToDisplay
Return DisplayDate(displayOffsetDate, culture)
End Function

Public Function DisplayDate(dateToDisplay As DateTimeOffset, _


culture As CultureInfo) As String
Dim specifier As String = "yyyy/MM/dd"

If Me.CalendarExists(culture) Then
Console.WriteLine("Displaying date in supported {0} calendar...", _
thisCalendar.GetType().Name)
culture.DateTimeFormat.Calendar = Me.thisCalendar
Return dateToDisplay.ToString(specifier, culture)
Else
Console.WriteLine("Displaying date in unsupported {0} calendar...", _
thisCalendar.GetType().Name)

Dim separator As String = targetCulture.DateTimeFormat.DateSeparator

Return thisCalendar.GetYear(dateToDisplay.DateTime).ToString("0000") & separator & _


thisCalendar.GetMonth(dateToDisplay.DateTime).ToString("00") & separator & _
thisCalendar.GetDayOfMonth(dateToDisplay.DateTime).ToString("00")
End If
End Function
End Class
' The example displays the following output to the console:
' Using the system default culture: 7/3/2008
' Using the ar-JO culture's original default calendar: 03/07/2008
' Using the ar-JO culture with Hijri as the default calendar:
' Displaying date in supported HijriCalendar calendar...
' 1429/06/29
' Displaying date in supported HijriCalendar calendar...
' 1429/06/29
'
' Using the ir-FA culture's default calendar: 7/3/2008
' Displaying date in unsupported PersianCalendar calendar...
' 1387/04/13
' Displaying date in unsupported PersianCalendar calendar...
' 1387/04/13

Cada objeto CultureInfo puede admitir uno o varios calendarios, que se indican mediante la propiedad
OptionalCalendars. Uno de ellos se designa como calendario predeterminado de la referencia cultural y es devuelto
por la propiedad de solo lectura CultureInfo.Calendar. Otro de los calendarios opcionales se puede designar como
valor predeterminado si se asigna un objeto Calendar que represente ese calendario a la propiedad
DateTimeFormatInfo.Calendar devuelta por la propiedad CultureInfo.DateTimeFormat. Pero algunos calendarios,
como el persa representado por la clase PersianCalendar, no actúan como calendarios opcionales de ninguna
referencia cultural.
En el ejemplo se define una clase de utilidad de calendario reutilizable, CalendarUtility , para controlar muchos de
los detalles de generación de la representación de cadena de una fecha mediante un calendario determinado. La
clase CalendarUtility tiene los siguientes miembros:
Un constructor parametrizado cuyo único parámetro es un objeto Calendar en el que se va a representar
una fecha. Se asigna a un campo privado de la clase.
CalendarExists , un método privado que devuelve un valor booleano que indica si el calendario
representado por el objeto CalendarUtility es compatible con el objeto CultureInfo pasado al método
como parámetro. El método encapsula una llamada al método Array.Exists, al que pasa la matriz
CultureInfo.OptionalCalendars.
HasSameName , un método privado asignado al delegado Predicate<T> que se pasa como parámetro al
método Array.Exists. Cada miembro de la matriz se pasa al método hasta que este devuelve true . El método
determina si el nombre de un calendario opcional es igual que el calendario representado por el objeto
CalendarUtility .

DisplayDate , un método público sobrecargado al que se pasan dos parámetros: un valor DateTime o
DateTimeOffset para expresar en el calendario representado por el objeto CalendarUtility ; y la referencia
cultural cuyas reglas de formato se van a usar. Su comportamiento a la hora de devolver la representación
de cadena de una fecha depende de si la referencia cultural cuyas reglas de formato se van a usar admite el
calendario de destino.
Independientemente del calendario usado para crear un valor DateTime o DateTimeOffset en este ejemplo, ese
valor normalmente se expresa como una fecha gregoriana. Esto se debe a que los tipos DateTime y DateTimeOffset
no conservan ninguna información del calendario. Internamente, se representan como el número de tics
transcurridos desde la medianoche del 1 de enero de 0001. La interpretación de ese número depende del
calendario. En la mayoría de las referencias culturales, el calendario predeterminado es el gregoriano.
Codificación de caracteres de .NET
16/09/2020 • 31 minutes to read • Edit Online

En este artículo se proporciona una introducción a los sistemas de codificación de character que se usan con .NET.
Se explica cómo funcionan los tipos String, Char, Rune y StringInfo con Unicode, UTF-16 y UTF-8.
El término character se usa aquí en el sentido general de lo que un lector percibe como un solo elemento de
presentación. Algunos ejemplos comunes son la letra "a", el símbolo "@" y el emoji " ". A veces, lo que parece un
charcter consta en realidad de varios elementos de visualización independientes, como se explica en la sección
sobre los clústeres de grafemas.

Tipos string y char


Una instancia de la clase string representa texto. Un elemento string es de forma lógica una secuencia de valores
de 16 bits, cada uno de los cuales es una instancia de la estructura char. La propiedad string.Length devuelve el
número de instancias char de la instancia string .
La función de ejemplo siguiente imprime los valores en notación hexadecimal de todas las instancias de char en
una instancia de string :
:::code language="csharp" source="snippets/character-encoding-introduction/csharp/PrintStringChars.cs"
id="SnippetPrintChars":::
Pase el elemento string "Hello" a esta función y obtendrá el siguiente resultado:

PrintChars("Hello");

"Hello".Length = 5
s[0] = 'H' ('\u0048')
s[1] = 'e' ('\u0065')
s[2] = 'l' ('\u006c')
s[3] = 'l' ('\u006c')
s[4] = 'o' ('\u006f')

Cada char acter se representa mediante un solo valor de char. Ese patrón se aplica a la mayoría de los idiomas del
mundo. Por ejemplo, esta es la salida de dos acter chinos que suenan como charnǐ hǎo y significan Hola:

PrintChars("你好");

"你好".Length = 2
s[0] = '你' ('\u4f60')
s[1] = '好' ('\u597d')

Pero en algunos idiomas, símbolos y emoji, se necesitan dos instancias de char para representar un único
character. Por ejemplo, compare los acter y las instancias de char de la palabra que significa char Osage en el
idioma osage:

PrintChars(" ");
" ".Length = 17
s[0] = '�' ('\ud801')
s[1] = '�' ('\udccf')
s[2] = '�' ('\ud801')
s[3] = '�' ('\udcd8')
s[4] = '�' ('\ud801')
s[5] = '�' ('\udcfb')
s[6] = '�' ('\ud801')
s[7] = '�' ('\udcd8')
s[8] = '�' ('\ud801')
s[9] = '�' ('\udcfb')
s[10] = '�' ('\ud801')
s[11] = '�' ('\udcdf')
s[12] = ' ' ('\u0020')
s[13] = '�' ('\ud801')
s[14] = '�' ('\udcbb')
s[15] = '�' ('\ud801')
s[16] = '�' ('\udcdf')

En el ejemplo anterior, cada character excepto el espacio se representa mediante dos instancias de char .
Un solo emoji Unicode se representa mediante dos instancias de char , tal como se ilustra en el ejemplo siguiente,
que muestra un emoji de buey:

" ".Length = 2
s[0] = '�' ('\ud83d')
s[1] = '�' ('\udc02')

En estos ejemplos se muestra que el valor de string.Length , que indica el número de instancias de char , no
indica necesariamente el número de character mostrados. Una sola instancia de char no representa
necesariamente un character.
Los pares de char que se asignan a un único acter se denominan charpares suplentes. Para entender cómo
funcionan, debe comprender la codificación Unicode y UTF-16.

Puntos de código Unicode


Unicode es un estándar de codificación internacional que se usa en varias plataformas y con varios idiomas y
scripts.
El estándar Unicode define más de 1,1 millones de puntos de código. Un punto de código es un valor entero
comprendido entre 0 y U+10FFFF (decimal 1.114.111). Algunos puntos de código se asignan a letras, símbolos o
emoji. Otros se asignan a acciones que controlan el modo en el que se muestra el texto o los character, como el
avance a una nueva línea. Muchos puntos de código todavía no se han asignado.
Estos son algunos ejemplos de asignaciones de puntos de código, con vínculos a charst Unicode en los que
aparecen:

DEC IM A L H EX E JEM P LO DESC RIP C IÓ N

10 U+000A N/D LINE FEED

65 U+0061 a LATIN SMALL LETTER A

562 U+0232 Ȳ LATIN CAPITAL LETTER Y


WITH MACRON
DEC IM A L H EX E JEM P LO DESC RIP C IÓ N

68.675 U+10C43 OLD TURKIC LETTER


ORKHON AT

127.801 U+1F339 ROSE emoji

Normalmente se hace referencia a los puntos de código mediante la sintaxis U+xxxx , donde xxxx es el valor
entero con codificación hexadecimal.
Dentro del intervalo completo de puntos de código hay dos subintervalos:
El plano básico multilingüe (BMP) en el intervalo U+0000..U+FFFF . Este intervalo de 16 bits proporciona
65 536 puntos de código, suficientes para cubrir la mayoría de los sistemas de escritura del mundo.
Los puntos de código suplementarios en el intervalo U+10000..U+10FFFF . Este intervalo de 21 bits
proporciona más de un millón de puntos de código adicionales que se pueden usar con idiomas menos
conocidos y otros fines, como los emoji.
En el diagrama siguiente se ilustra la relación entre el BMP y los puntos de código suplementarios.

Unidades de código UTF-16


El formato de transformación Unicode de 16 bits (UTF-16) es un sistema de codificación de acter que emplea
charunidades de código de 16 bits para representar puntos de código Unicode. .NET usa UTF-16 para codificar el
texto de una instancia de string . Una instancia de char representa una unidad de código de 16 bits.
Una sola unidad de código de 16 bits puede representar cualquier punto de código en el intervalo de 16 bits del
plano básico multilingüe. Sin embargo, un punto de código en el intervalo suplementario necesita dos instancias
de char .

Pares suplentes
La conversión de dos valores de 16 bits en un único valor de 21 bits se facilita mediante un intervalo especial
denominado puntos de código suplentes, de U+D800 a U+DFFF (decimal 55.296 a 57.343), ambos incluidos.
En el diagrama siguiente se ilustra la relación entre el BMP y los puntos de código suplentes.

Cuando un punto de código suplente superior ( U+D800..U+DBFF ) va seguido inmediatamente por un punto de
código suplente inferior ( U+DC00..U+DFFF ), el par se interpreta como un punto de código suplementario mediante
la fórmula siguiente:

code point = 0x10000 +


((high surrogate code point - 0xD800) * 0x0400) +
(low surrogate code point - 0xDC00)

Esta es la misma fórmula, pero con notación decimal:

code point = 65,536 +


((high surrogate code point - 55,296) * 1,024) +
(low surrogate code point - 56,320)
Un punto de código suplente superior no tiene un valor numérico mayor que un punto de código suplente inferior.
El punto de código suplente superior se denomina así porque se usa para calcular los 11 bits de orden superior del
intervalo completo de puntos de código de 21 bits. El punto de código suplente inferior se usa para calcular los 10
bits de orden inferior.
Por ejemplo, el punto de código real que corresponde al par suplente 0xD83C y 0xDF39 se calcula de la manera
siguiente:

actual = 0x10000 + ((0xD83C - 0xD800) * 0x0400) + (0xDF39 - 0xDC00)


= 0x10000 + ( 0x003C * 0x0400) + 0x0339
= 0x10000 + 0xF000 + 0x0339
= 0x1F339

Este es el mismo cálculo, pero con notación decimal:

actual = 65,536 + ((55,356 - 55,296) * 1,024) + (57,145 - 56320)


= 65,536 + ( 60 * 1,024) + 825
= 65,536 + 61,440 + 825
= 127,801

En el ejemplo anterior se muestra que "\ud83c\udf39" es la codificación UTF-16 del punto de código
U+1F339 ROSE (' ') mencionado anteriormente.

Valores escalares Unicode


El término valor escalar Unicode hace referencia a todos los puntos de código distintos de los puntos de código
suplentes. En otras palabras, un valor escalar es cualquier punto de código al que se le asigna un character o se le
puede asignar un character en el futuro. Aquí "carácter" hace referencia a todo lo que se puede asignar a un punto
de código, lo que incluye cosas como acciones que controlan cómo se muestra el texto o character.
En el diagrama siguiente se muestran los puntos de código de valor escalar.

El tipo Rune como un valor escalar.


A partir de .NET Core 3.0, el tipo System.Text.Rune representa un valor escalar Unicode. Rune no está disponible
en .NET Core 2. x o .NET Framework 4.x.
Los constructores Rune validan que la instancia resultante sea un valor escalar Unicode válido; de lo contrario,
inician una excepción. En el ejemplo siguiente se muestra código que crea una instancia correcta de las instancias
de Rune porque la entrada representa valores escalares válidos:
:::code language="csharp" source="snippets/character-encoding-introduction/csharp/InstantiateRunes.cs"
id="SnippetValid":::
En el siguiente ejemplo se produce una excepción porque el punto de código está en el intervalo suplente y no es
parte de un par suplente:
:::code language="csharp" source="snippets/character-encoding-introduction/csharp/InstantiateRunes.cs"
id="SnippetInvalidSurrogate":::
En el siguiente ejemplo se produce una excepción porque el punto de código está por encima del intervalo
suplementario:
:::code language="csharp" source="snippets/character-encoding-introduction/csharp/InstantiateRunes.cs"
id="SnippetInvalidHigh":::
Ejemplo de uso de Rune: cambio de las mayúsculas y minúsculas.
Una API que toma una instancia de char y da por supuesto que trabaja con un punto de código que es un valor
escalar no funciona correctamente si char procede de un par suplente. Por ejemplo, considere el siguiente
método que llama a Char.ToUpperInvariant en cada char en una instancia de string:
:::code language="csharp" source="snippets/character-encoding-introduction/csharp/ConvertToUpper.cs"
id="SnippetBadExample":::
Si string de input contiene la letra minúscula er de Deseret ( ), este código no lo convertirá a mayúsculas ( ).
El código llama a char.ToUpperInvariant por separado en cada punto de código suplente, U+D801 y U+DC49 . Sin
embargo, U+D801 no tiene suficiente información para identificarlo como una letra minúscula, por lo que
char.ToUpperInvariant lo deja así. Y trata U+DC49 de la misma manera. El resultado es que la " " minúscula de la
instancia de string de input no se convierte a " " mayúscula.
A continuación se muestran dos opciones para convertir correctamente una instancia de string a mayúsculas:
Llame a String.ToUpperInvariant en la instancia de string de entrada en lugar de recorrer en iteración char
mediante char . El método string.ToUpperInvariant tiene acceso a ambas partes de cada par suplente, por
lo que puede tratar correctamente todos los puntos de código Unicode.
Recorra en iteración los valores escalares Unicode como instancias de Rune y no de char , como se
muestra en el ejemplo siguiente. Dado que una instancia de Rune es un valor escalar Unicode válido, se
puede pasar a las API que esperan operar en un valor escalar. Por ejemplo, al llamar a
Rune.ToUpperInvariant, como se ilustra en el ejemplo siguiente, se muestran los resultados correctos:
:::code language="csharp" source="snippets/character-encoding-introduction/csharp/ConvertToUpper.cs"
id="SnippetGoodExample":::
Otras API de Rune
El tipo Rune expone las analogías de muchas de las API de char . Por ejemplo, los métodos siguientes reflejan las
API estáticas en el tipo char :
Rune.IsLetter
Rune.IsWhiteSpace
Rune.IsLetterOrDigit
Rune.GetUnicodeCategory
Para obtener el valor escalar sin formato de una instancia de Rune , utilice la propiedad Rune.Value.
Para volver a convertir una instancia de Rune en una secuencia de instancias de char , use Rune.ToString o el
método Rune.EncodeToUtf16.
Dado que cualquier valor escalar Unicode se puede representar con una sola instancia de char o mediante un par
suplente, cualquier instancia de Rune se puede representar como máximo con 2 instancias de char . Use
Rune.Utf16SequenceLength para ver el número de instancias de char necesarias para representar una instancia
de Rune .
Para más información sobre el tipo Rune de .NET, consulte la referencia de API Rune .

Clústeres de grafemas
Lo que parece un acter podría ser el resultado de una combinación de varios puntos de código, por lo que un
término más descriptivo que se usa a menudo en lugar de "acter" es charclúster de grafemaschar. El término
equivalente en .NET es elemento de texto.
Tenga en cuenta las instancias de string "a", "á", "á" y " ". Si el sistema operativo las trata como se especifica
en el estándar Unicode, cada una de estas instancias de string aparece como un solo elemento de texto o clúster
de grafemas. Sin embargo, las dos últimas están representadas por más de un punto de código de valor escalar.
La instancia "a" de string está representada por un valor escalar y contiene una instancia de char .
U+0061 LATIN SMALL LETTER A
La instancia "á" de string está representada por un valor escalar y contiene una instancia de char .
U+00E1 LATIN SMALL LETTER A WITH ACUTE
La instancia "á" de string parece igual que "á" pero está representada por dos valores escalares y contiene
dos instancias de char .
U+0061 LATIN SMALL LETTER A
U+0301 COMBINING ACUTE ACCENT
Por último, la instancia " " de string está representada por cuatro valores y contiene siete instancias de
char .

U+1F469 WOMAN (intervalo suplementario, requiere un par suplente)


U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4 (intervalo suplementario, requiere un par suplente)
U+200D ZERO WIDTH JOINER
U+1F692 FIRE ENGINE (intervalo suplementario, requiere un par suplente)

En algunos de los ejemplos anteriores, como el de la combinación del modificador del acento o el modificador del
tono de piel, el punto de código no se muestra como un elemento independiente en la pantalla, sino que sirve para
modificar el aspecto de un elemento de texto que venía antes. En estos ejemplos se muestra que puede tomar
varios valores escalares para componer lo que consideramos un solo "character" o un "clúster de grafemas".
Para enumerar los clústeres de grafemas de una instancia de string , use la clase StringInfo como se muestra en el
ejemplo siguiente. Si está familiarizado con Swift, el tipo StringInfo de .NET es conceptualmente similar al tipo
character de Swift.

Ejemplo: recuento de instancias de elementos char, Rune y de texto


En las API de .NET, un clúster de grafemas se conoce como elemento de texto. El método siguiente muestra las
diferencias entre las instancias de elementos char , Rune y de texto en una instancia de string :
:::code language="csharp" source="snippets/character-encoding-introduction/csharp/CountTextElements.cs"
id="SnippetCountMethod":::
:::code language="csharp" source="snippets/character-encoding-introduction/csharp/CountTextElements.cs"
id="SnippetCallCountMethod":::
Si ejecuta este código en .NET Framework o .NET Core 3.1 o una versión anterior, el recuento de elementos de texto
para el emoji muestra 4 . Esto se debe a un error en la clase StringInfo que se ha corregido en .NET 5.
Ejemplo: división de las instancias de string
Al dividir las instancias de string , evite dividir los pares suplentes y los clústeres de grafemas. Considere el
siguiente ejemplo de código incorrecto, que intenta insertar saltos de línea cada 10 caracteres en una instancia de
char:
:::code language="csharp" source="snippets/character-encoding-introduction/csharp/InsertNewlines.cs"
id="SnippetBadExample":::
Dado que este código enumera instancias de char , un par suplente que abarca un límite de 10 instancias de char
se dividirá y una nueva línea se insertará entre ellas. Esta inserción introduce daños en los datos, porque los puntos
de código suplentes solo son significativos como pares.
No se elimina la posibilidad de daños en los datos si se enumeran instancias de Rune (valores escalares) en lugar
de instancias de char . Un conjunto de instancias de Rune podría constituir un clúster de grafemas que abarca un
límite de 10 instancias de char . Si el conjunto de clústeres de grafemas está dividido, no se podrá interpretar
correctamente.
Lo mejor sería romper la instancia de string mediante el recuento de clústeres de grafemas, o elementos de texto,
como en el ejemplo siguiente:
:::code language="csharp" source="snippets/character-encoding-introduction/csharp/InsertNewlines.cs"
id="SnippetGoodExample":::
Como se indicó anteriormente, sin embargo, en implementaciones de .NET distintas de .NET 5, la clase StringInfo
podría tratar de forma incorrecta algunos clústeres de grafemas.

UTF-8 y UTF-32
Las secciones anteriores se centraban en UTF-16 porque es lo que usa .NET para codificar instancias de string .
Existen otros sistemas de codificación para Unicode: UTF-8 y UTF-32. Estas codificaciones usan unidades de código
de 8 bits y unidades de código de 32 bits, respectivamente.
Al igual que UTF-16, UTF-8 requiere varias unidades de código para representar algunos valores escalares
Unicode. UTF-32 puede representar cualquier valor escalar en una sola unidad de código de 32 bits.
Estos son algunos ejemplos en los que se muestra cómo se representa el mismo punto de código Unicode en cada
uno de estos tres sistemas de codificación Unicode:

Scalar: U+0061 LATIN SMALL LETTER A ('a')


UTF-8 : [ 61 ] (1x 8-bit code unit = 8 bits total)
UTF-16: [ 0061 ] (1x 16-bit code unit = 16 bits total)
UTF-32: [ 00000061 ] (1x 32-bit code unit = 32 bits total)

Scalar: U+0429 CYRILLIC CAPITAL LETTER SHCHA ('Щ')


UTF-8 : [ D0 A9 ] (2x 8-bit code units = 16 bits total)
UTF-16: [ 0429 ] (1x 16-bit code unit = 16 bits total)
UTF-32: [ 00000429 ] (1x 32-bit code unit = 32 bits total)

Scalar: U+A992 JAVANESE LETTER GA (' ')


UTF-8 : [ EA A6 92 ] (3x 8-bit code units = 24 bits total)
UTF-16: [ A992 ] (1x 16-bit code unit = 16 bits total)
UTF-32: [ 0000A992 ] (1x 32-bit code unit = 32 bits total)

Scalar: U+104CC OSAGE CAPITAL LETTER TSHA (' ')


UTF-8 : [ F0 90 93 8C ] (4x 8-bit code units = 32 bits total)
UTF-16: [ D801 DCCC ] (2x 16-bit code units = 32 bits total)
UTF-32: [ 000104CC ] (1x 32-bit code unit = 32 bits total)

Como se indicó anteriormente, una única unidad de código UTF-16 de un par suplente no tiene sentido. Del mismo
modo, una sola unidad de código UTF-8 no tiene sentido si se encuentra en una secuencia de dos, tres o cuatro
usadas para calcular un valor escalar.
Modos endian
En .NET, las unidades de código UTF-16 de una instancia de string se almacenan en memoria contigua como una
secuencia de enteros de 16 bits (instancias de char ). Los bits de las unidades de código individuales se disponen
de acuerdo con el modo endian de la arquitectura actual.
En una arquitectura little-endian, la instancia de string que consta de los puntos de código UTF-16 [ D801 DCCC ]
se dispondría en memoria como los bytes [ 0x01, 0xD8, 0xCC, 0xDC ] . En una arquitectura big-endian, la misma
instancia de string se dispondría en memoria como los bytes [ 0xD8, 0x01, 0xDC, 0xCC ] .
Los sistemas informáticos que se comunican entre sí deben estar de acuerdo con la representación de los datos
que atraviesan la conexión. La mayoría de los protocolos de red usan UTF-8 como estándar al transmitir texto, en
parte para evitar problemas que podrían derivarse de una máquina big-endian que se comunica con una máquina
little-endian. La instancia de string que consta de los puntos de código UTF-8 [ F0 90 93 8C ] siempre se
representará como los bytes [ 0xF0, 0x90, 0x93, 0x8C ] con independencia del modo endian.
Para usar UTF-8 para transmitir texto, las aplicaciones .NET a menudo usan código como el del ejemplo siguiente:

string stringToWrite = GetString();


byte[] stringAsUtf8Bytes = Encoding.UTF8.GetBytes(stringToWrite);
await outputStream.WriteAsync(stringAsUtf8Bytes, 0, stringAsUtf8Bytes.Length);

En el ejemplo anterior, el método Encoding.UTF8.GetBytes descodifica de nuevo la instancia de string UTF-16 en


una serie de valores escalares Unicode, después vuelve a codificar esos valores escalares en UTF-8 y coloca la
secuencia resultante en una matriz de byte . El método Encoding.UTF8.GetString realiza la transformación opuesta,
al convertir una matriz de byte UTF-8 en una instancia de string UTF-16.

WARNING
Dado que UTF-8 es habitual en Internet, puede resultar tentador leer los bytes sin formato de la conexión y tratar los datos
como si fueran UTF-8. Sin embargo, debe validar que el formato es correcto. Un cliente malintencionado podría enviar UTF-8
con un formato incorrecto a su servicio. Si trabaja en esos datos como si tuvieran el formato correcto, podría provocar
errores o vulnerabilidades de seguridad en la aplicación. Para validar los datos UTF-8, puede usar un método como
Encoding.UTF8.GetString , que realizará la validación mientras convierte los datos entrantes en una instancia de string .

Codificación con un formato correcto


Una codificación Unicode con un formato correcto es una instancia de string de unidades de código que se puede
descodificar de forma inequívoca y sin errores en una secuencia de valores escalares Unicode. Los datos con un
formato correcto se pueden transcodificar libremente entre UTF-8, UTF-16 y UTF-32.
La cuestión de si una secuencia de codificación tiene el formato correcto o no está relacionada con el modo endian
de la arquitectura de una máquina. Una secuencia UTF-8 mal formada tiene un formato incorrecto tanto en
máquinas big-endian como little-endian.
Estos son algunos ejemplos de codificaciones con un formato incorrecto:
En UTF-8, la secuencia [ 6C C2 61 ] tiene un formato incorrecto porque C2 no puede ir seguido de 61 .
En UTF-16, la secuencia [ DC00 DD00 ] (o, en C#, la instancia de string "\udc00\udd00" ) tiene un formato
incorrecto porque el suplente inferior DC00 no puede ir seguido de otro suplente inferior DD00 .
En UTF-32, la secuencia [ 0011ABCD ] tiene un formato incorrecto porque 0011ABCD está fuera del intervalo
de valores escalares Unicode.
En .NET, las instancias de string casi siempre contienen datos UTF-16 con un formato correcto, pero no se
garantiza. En los siguientes ejemplos se muestra un código válido de C# que crea datos UTF-16 con un formato
incorrecto en instancias de string .
Un literal con un formato incorrecto:

const string s = "\ud800";

Una subcadena zzstring que divide un par suplente:


string x = "\ud83e\udd70"; // " "
string y = x.Substring(1, 1); // "\udd70" standalone low surrogate

Las API como Encoding.UTF8.GetString nunca devuelven instancias de string con un formato incorrecto. Los
métodos Encoding.GetString y Encoding.GetBytes detectan secuencias con un formato incorrecto en la entrada y
realizan la sustitución de caracteres zzchar al generar la salida. Por ejemplo, si Encoding.ASCII.GetString(byte[])
observa un byte no ASCII en la entrada (fuera del intervalo de U+0000..U+007F), inserta un "?" en la instancia de
string devuelta. Encoding.UTF8.GetString(byte[]) reemplaza las secuencias UTF-8 con un formato incorrecto por
U+FFFD REPLACEMENT CHARACTER ('�') en la instancia de string devuelta. Para más información, consulte el
estándar Unicode, secciones 5.22 y 3.9.
Las clases Encoding integradas también se pueden configurar para producir una excepción en lugar de realizar la
sustitución de caracteres zzchar cuando se observan secuencias con un formato incorrecto. Este enfoque se suele
usar en aplicaciones que dependen de la seguridad donde la sustitución de caracteres zzchar podría no ser
aceptable.

byte[] utf8Bytes = ReadFromNetwork();


UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
string asString = encoding.GetString(utf8Bytes); // will throw if 'utf8Bytes' is ill-formed

Para leer información sobre cómo usar las clases Encoding integradas, vea Uso de las clases de codificación de
caracteres en .NETzzchar.

Vea también
String
Char
Rune
Globalización y localización
Procedimiento para usar clases de codificación de
caracteres en .NET
16/09/2020 • 67 minutes to read • Edit Online

En este artículo se explica cómo usar las clases que proporciona .NET para codificar y descodificar texto mediante
varios esquemas de codificación. En las instrucciones se da por hecho que ha leído Introducción a la codificación
de caracteres en .NET.

Codificadores y descodificadores
.NET proporciona clases de codificación que codifican y descodifican texto mediante varios sistemas de
codificación. Por ejemplo, la clase UTF8Encoding describe las reglas de codificación y descodificación de UTF-8.
.NET usa la codificación UTF-16 (representada por la clase UnicodeEncoding) con las instancias de string . Existen
codificadores y descodificadores para otros esquemas de codificación.
La codificación y la descodificación también pueden incluir validación. Por ejemplo, la clase UnicodeEncoding
comprueba todas las instancias de char en el intervalo suplente para asegurarse de que están en pares suplentes
válidos. Una estrategia de reserva determina cómo trata un codificador los caracteres no válidos o cómo trata un
descodificador los bytes no válidos.

WARNING
Las clases de codificación de .NET proporcionan una manera de almacenar y convertir datos de caracteres. No se deben usar
para almacenar datos binarios en formato de cadena. Dependiendo de la codificación empleada, la conversión de datos
binarios al formato de cadena con las clases de codificación puede presentar un comportamiento inesperado y mostrar datos
inexactos o dañados. Para convertir datos binarios en un formato de cadena, use el método Convert.ToBase64String .

Todas las clases de codificación de caracteres de .NET heredan de la clase System.Text.Encoding, que es una clase
abstracta que define la funcionalidad común a todas las codificaciones de caracteres. Para acceder a los objetos
individuales de codificación implementados en .NET, haga lo siguiente:
Use las propiedades estáticas de la clase Encoding, que devuelven objetos que representan las
codificaciones de caracteres estándar disponibles en .NET (ASCII, UTF-7, UTF-8, UTF-16 y UTF-32). Por
ejemplo, la propiedad Encoding.Unicode devuelve un objeto UnicodeEncoding : Cada objeto usa la reserva
de reemplazo para controlar las cadenas que no puede codificar y los bytes que no puede descodificar. Para
más información, consulte Reserva de reemplazo.
Llame al constructor de clase de la codificación. Se pueden crear instancias de los objetos para las
codificaciones ASCII, UTF-7, UTF-8, UTF-16 y UTF-32 de esta manera. De forma predeterminada, cada objeto
usa la reserva de reemplazo para controlar las cadenas que no puede codificar y los bytes que no puede
descodificar, pero puede especificar que se debe producir una excepción en su lugar. Para más información,
consulte Reserva de reemplazo y Reserva de excepción.
Llame al constructor Encoding(Int32) y pásele un entero que represente la codificación. Los objetos de
codificación estándar usan la reserva de reemplazo, y los objetos de codificación para la página de códigos
y el juego de caracteres de doble byte (DBCS) usan el retroceso de ajuste perfecto para controlar las
cadenas que no pueden codificar y los bytes que no pueden descodificar. Para más información, consulte
Reserva con ajuste perfecto.
Llame al método Encoding.GetEncoding, que devuelve cualquier estándar, página de códigos o codificación
DBCS disponible en .NET. Las sobrecargas permiten especificar un objeto de reserva para el codificador y
para el descodificador.
Se puede recuperar información sobre todas las codificaciones disponibles en .NET llamando al método
Encoding.GetEncodings. .NET admite los esquemas de codificación de caracteres que se muestran en la tabla
siguiente.

C L A SE DE C O DIF IC A C IÓ N DESC RIP C IÓ N

ASCII Codifica un intervalo limitado de caracteres usando los siete


bits inferiores de un byte. Como esta codificación solo admite
valores de caracteres de U+0000 a U+007F, en la mayoría de
los casos no resulta suficiente para aplicaciones de uso
internacional.

UTF-7 Representa los caracteres como secuencias de caracteres ASCII


de 7 bits. Los caracteres Unicode no ASCII se representan con
una secuencia de escape de caracteres ASCII. UTF-7 admite
protocolos como correo electrónico y grupos de noticias. Sin
embargo, la codificación UTF-7 no es particularmente segura
ni sólida. En algunos casos, cambiar un bit puede modificar
radicalmente la interpretación de toda una cadena UTF-7. En
otros casos, diferentes cadenas UTF-7 pueden codificar el
mismo texto. Para las secuencias que incluyen caracteres no
ASCII, UTF-7 necesita más espacio que UTF-8, y la codificación
y descodificación son más lentas. Por tanto, debe usar UTF-8
en lugar de UTF-7 si es posible.

UTF-8 Representa cada punto de código Unicode como una


secuencia de uno a cuatro bytes. UTF-8 admite tamaños de
datos de 8 bits y funciona bien con muchos sistemas
operativos existentes. Para el intervalo ASCII de caracteres,
UTF-8 es idéntico a la codificación ASCII y permite un
conjunto mayor de caracteres. Sin embargo, para los scripts
de chino-japonés-coreano (CJK), UTF-8 puede necesitar tres
bytes para cada carácter y puede generar tamaños de datos
mayores que UTF-16. Algunas veces, la cantidad de datos
ASCII, como las etiquetas HTML, justifica el mayor tamaño del
intervalo de CJK.

UTF-16 Representa cada punto de código Unicode como una


secuencia de uno o dos enteros de 16 bits. La mayoría de los
caracteres Unicode comunes solo necesitan un punto de
código UTF-16, aunque los caracteres Unicode suplementarios
(U+10000 y posteriores) necesitan dos puntos de código
UTF-16 suplentes. Se admiten tanto el orden de bytes little-
endian como el big-endian. Common Language Runtime usa
la codificación UTF-16 para representar valores de tipo Char y
String , y el sistema operativo Windows la usa para
representar valores de tipo WCHAR .

UTF-32 Representa cada punto de código Unicode como un entero de


32 bits. Se admiten tanto el orden de bytes little-endian como
el big-endian. La codificación UTF-32 se usa cuando las
aplicaciones desean evitar el comportamiento de punto de
código suplente de la codificación UTF-16 en sistemas
operativos para los que el espacio codificado es muy
importante. Los glifos únicos representados en una pantalla
aún se pueden codificar con más de un carácter UTF-32.
C L A SE DE C O DIF IC A C IÓ N DESC RIP C IÓ N

Codificación ANSI/ISO Proporciona compatibilidad con diversas páginas de códigos.


En los sistemas operativos Windows, las páginas de códigos se
usan para admitir un idioma o un grupo de idiomas concreto.
Para obtener una tabla que muestra las páginas de códigos
admitidas por .NET, vea la clase Encoding. Puede recuperar un
objeto de codificación para una página de códigos
determinada llamando al método
Encoding.GetEncoding(Int32) . Una página de códigos
contiene 256 puntos de código y se basa en cero. En la
mayoría de las páginas de códigos, los puntos de código 0 a
127 representan el juego de caracteres ASCII y los puntos de
código 128 a 255 difieren de forma significativa entre las
páginas de códigos. Por ejemplo, la página de códigos 1252
proporciona los caracteres para los sistemas de escritura
latinos, incluidos el inglés, el alemán y el francés. Los últimos
128 puntos de código de la página de códigos 1252
contienen los caracteres de acento. La página de códigos
1253 proporciona códigos de caracteres necesarios en el
sistema de escritura griego. Los últimos 128 puntos de código
de la página de códigos 1253 contienen los caracteres
griegos. Como resultado, una aplicación que se basa en
páginas de códigos ANSI no puede almacenar griego y alemán
en la misma secuencia de texto a menos que incluya un
identificador que indique la página de códigos a la que se hace
referencia.

Codificaciones de juegos de caracteres de doble byte (DBCS) Admite idiomas que contienen más de 256 caracteres, como
el chino, el japonés y el coreano. En un DBCS, un par de
puntos de código (un byte doble) representa cada carácter. La
propiedad Encoding.IsSingleByte devuelve false para las
codificaciones DBCS. Puede recuperar un objeto de
codificación para un DBCS determinado llamando al método
Encoding.GetEncoding(Int32) . Cuando una aplicación controla
datos DBCS, el primer byte de un carácter DBCS (el byte
inicial) se procesa junto con el byte final que le sigue
inmediatamente. Puesto que un único par de puntos de
código de doble byte puede representar caracteres diferentes
dependiendo de la página de códigos, este esquema aún no
permite la combinación de dos idioma, como el japonés y el
chino, en el mismo flujo de datos.

Estas codificaciones permiten trabajar con caracteres Unicode, así como con codificaciones que son las más usadas
en aplicaciones heredadas. Además, puede crear una codificación personalizada definiendo una clase que se deriva
de Encoding e invalidar sus miembros.

Compatibilidad con la codificación de .NET Core


De manera predeterminada, .NET Core no pone a disposición codificaciones de páginas de código que no sean la
página de códigos 28591 y las codificaciones Unicode, como UTF-8 y UTF-16. Sin embargo, puede agregar que las
codificaciones de páginas de código que se encuentran en las aplicaciones estándar de Windows que tienen como
destino .NET a la aplicación. Para obtener más información, vea el tema CodePagesEncodingProvider.

Seleccionar una clase de codificación


Si tiene la oportunidad de elegir la codificación que se usará en la aplicación, debe usar una codificación Unicode,
preferiblemente UTF8Encoding o UnicodeEncoding. (.NET también admite una tercera codificación Unicode,
UTF32Encoding).
Si piensa usar una codificación ASCII (ASCIIEncoding), elija UTF8Encoding en su lugar. Las dos codificaciones son
idénticas para el juego de caracteres ASCII, pero UTF8Encoding presenta las ventajas siguientes:
Puede representar todos los caracteres Unicode, mientras que ASCIIEncoding solo admite los valores de
caracteres Unicode entre U+0000 y U+007F.
Proporciona detección de errores y una mayor seguridad.
Se ha mejorado en materia de velocidad y debe ser más rápida que cualquier otra codificación. Incluso
cuando todo el contenido es ASCII, las operaciones realizadas con UTF8Encoding son más rápidas que las
operaciones realizadas con ASCIIEncoding.
Debe considerar la posibilidad de usar ASCIIEncoding solo para las aplicaciones heredadas. Sin embargo, incluso
para las aplicaciones heredadas, UTF8Encoding podría ser una opción mejor por las razones siguientes
(suponiendo la configuración predeterminada):
Si la aplicación tiene contenido que no es estrictamente ASCII y lo codifica con ASCIIEncoding, cada carácter
no ASCII se codifica como un signo de interrogación (?). Si la aplicación descodifica después estos datos, la
información se pierde.
Si la aplicación tiene contenido que no es estrictamente ASCII y lo codifica con UTF8Encoding, el resultado
parece ininteligible si se interpreta como ASCII. Sin embargo, si la aplicación usa un descodificador UTF-8
para descodificar estos datos, los datos realizan una acción de ida y vuelta correctamente.
En una aplicación web, los caracteres enviados al cliente como respuesta a una solicitud web deben reflejar la
codificación empleada en el cliente. En la mayoría de los casos, debe establecer la propiedad
HttpResponse.ContentEncoding en el valor devuelto por la propiedad HttpRequest.ContentEncoding para mostrar
el texto en la codificación que el usuario espera.

Usar un objeto de codificación


Un codificador convierte una cadena de caracteres (normalmente, caracteres Unicode) en su equivalente numérico
(bytes). Por ejemplo, podría usar un codificador ASCII para convertir caracteres Unicode en ASCII de forma que se
puedan mostrar en la consola. Para realizar la conversión, se llama al método Encoding.GetBytes . Si desea
determinar cuántos bytes son necesarios para almacenar los caracteres codificados antes de realizar la
codificación, puede llamar al método GetByteCount .
En el ejemplo siguiente se usa una única matriz de bytes para codificar cadenas en dos operaciones
independientes. Mantiene un índice que indica la posición inicial de la matriz de bytes para el siguiente conjunto de
bytes codificados con ASCII. Llama al método ASCIIEncoding.GetByteCount(String) para asegurarse de que la
matriz de bytes es suficiente para alojar la cadena codificada. A continuación, llama al método
ASCIIEncoding.GetBytes(String, Int32, Int32, Byte[], Int32) para codificar los caracteres de la cadena.
using System;
using System.Text;

public class Example


{
public static void Main()
{
string[] strings= { "This is the first sentence. ",
"This is the second sentence. " };
Encoding asciiEncoding = Encoding.ASCII;

// Create array of adequate size.


byte[] bytes = new byte[49];
// Create index for current position of array.
int index = 0;

Console.WriteLine("Strings to encode:");
foreach (var stringValue in strings) {
Console.WriteLine(" {0}", stringValue);

int count = asciiEncoding.GetByteCount(stringValue);


if (count + index >= bytes.Length)
Array.Resize(ref bytes, bytes.Length + 50);

int written = asciiEncoding.GetBytes(stringValue, 0,


stringValue.Length,
bytes, index);

index = index + written;


}
Console.WriteLine("\nEncoded bytes:");
Console.WriteLine("{0}", ShowByteValues(bytes, index));
Console.WriteLine();

// Decode Unicode byte array to a string.


string newString = asciiEncoding.GetString(bytes, 0, index);
Console.WriteLine("Decoded: {0}", newString);
}

private static string ShowByteValues(byte[] bytes, int last )


{
string returnString = " ";
for (int ctr = 0; ctr <= last - 1; ctr++) {
if (ctr % 20 == 0)
returnString += "\n ";
returnString += String.Format("{0:X2} ", bytes[ctr]);
}
return returnString;
}
}
// The example displays the following output:
// Strings to encode:
// This is the first sentence.
// This is the second sentence.
//
// Encoded bytes:
//
// 54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65
// 6E 74 65 6E 63 65 2E 20 54 68 69 73 20 69 73 20 74 68 65 20
// 73 65 63 6F 6E 64 20 73 65 6E 74 65 6E 63 65 2E 20
//
// Decoded: This is the first sentence. This is the second sentence.
Imports System.Text

Module Example
Public Sub Main()
Dim strings() As String = {"This is the first sentence. ",
"This is the second sentence. "}
Dim asciiEncoding As Encoding = Encoding.ASCII

' Create array of adequate size.


Dim bytes(50) As Byte
' Create index for current position of array.
Dim index As Integer = 0

Console.WriteLine("Strings to encode:")
For Each stringValue In strings
Console.WriteLine(" {0}", stringValue)

Dim count As Integer = asciiEncoding.GetByteCount(stringValue)


If count + index >= bytes.Length Then
Array.Resize(bytes, bytes.Length + 50)
End If
Dim written As Integer = asciiEncoding.GetBytes(stringValue, 0,
stringValue.Length,
bytes, index)

index = index + written


Next
Console.WriteLine()
Console.WriteLine("Encoded bytes:")
Console.WriteLine("{0}", ShowByteValues(bytes, index))
Console.WriteLine()

' Decode Unicode byte array to a string.


Dim newString As String = asciiEncoding.GetString(bytes, 0, index)
Console.WriteLine("Decoded: {0}", newString)
End Sub

Private Function ShowByteValues(bytes As Byte(), last As Integer) As String


Dim returnString As String = " "
For ctr As Integer = 0 To last - 1
If ctr Mod 20 = 0 Then returnString += vbCrLf + " "
returnString += String.Format("{0:X2} ", bytes(ctr))
Next
Return returnString
End Function
End Module
' The example displays the following output:
' Strings to encode:
' This is the first sentence.
' This is the second sentence.
'
' Encoded bytes:
'
' 54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65
' 6E 74 65 6E 63 65 2E 20 54 68 69 73 20 69 73 20 74 68 65 20
' 73 65 63 6F 6E 64 20 73 65 6E 74 65 6E 63 65 2E 20
'
' Decoded: This is the first sentence. This is the second sentence.

Un descodificador convierte una matriz de bytes que refleja una codificación de caracteres determinada en un
juego de caracteres, ya sea en una matriz de caracteres o en una cadena. Para descodificar una matriz de bytes en
una matriz de caracteres, se llama al método Encoding.GetChars . Para descodificar una matriz de bytes en una
cadena, se llama al método GetString . Si desea determinar cuántos caracteres son necesarios para almacenar los
bytes descodificados antes de realizar la descodificación, puede llamar al método GetCharCount .
En el ejemplo siguiente se codifican tres cadenas y después se descodifican en una sola matriz de caracteres. Se
mantiene un índice que indica la posición inicial de la matriz de caracteres para el siguiente juego de caracteres
descodificados. Se llama al método GetCharCount para asegurarse de que la matriz de caracteres es
suficientemente grande para alojar todos los caracteres descodificados. A continuación, se llama al método
ASCIIEncoding.GetChars(Byte[], Int32, Int32, Char[], Int32) para descodificar la matriz de bytes.
using System;
using System.Text;

public class Example


{
public static void Main()
{
string[] strings = { "This is the first sentence. ",
"This is the second sentence. ",
"This is the third sentence. " };
Encoding asciiEncoding = Encoding.ASCII;
// Array to hold encoded bytes.
byte[] bytes;
// Array to hold decoded characters.
char[] chars = new char[50];
// Create index for current position of character array.
int index = 0;

foreach (var stringValue in strings) {


Console.WriteLine("String to Encode: {0}", stringValue);
// Encode the string to a byte array.
bytes = asciiEncoding.GetBytes(stringValue);
// Display the encoded bytes.
Console.Write("Encoded bytes: ");
for (int ctr = 0; ctr < bytes.Length; ctr++)
Console.Write(" {0}{1:X2}",
ctr % 20 == 0 ? Environment.NewLine : "",
bytes[ctr]);
Console.WriteLine();

// Decode the bytes to a single character array.


int count = asciiEncoding.GetCharCount(bytes);
if (count + index >= chars.Length)
Array.Resize(ref chars, chars.Length + 50);

int written = asciiEncoding.GetChars(bytes, 0,


bytes.Length,
chars, index);
index = index + written;
Console.WriteLine();
}

// Instantiate a single string containing the characters.


string decodedString = new string(chars, 0, index - 1);
Console.WriteLine("Decoded string: ");
Console.WriteLine(decodedString);
}
}
// The example displays the following output:
// String to Encode: This is the first sentence.
// Encoded bytes:
// 54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65
// 6E 74 65 6E 63 65 2E 20
//
// String to Encode: This is the second sentence.
// Encoded bytes:
// 54 68 69 73 20 69 73 20 74 68 65 20 73 65 63 6F 6E 64 20 73
// 65 6E 74 65 6E 63 65 2E 20
//
// String to Encode: This is the third sentence.
// Encoded bytes:
// 54 68 69 73 20 69 73 20 74 68 65 20 74 68 69 72 64 20 73 65
// 6E 74 65 6E 63 65 2E 20
//
// Decoded string:
// This is the first sentence. This is the second sentence. This is the third sentence.
Imports System.Text

Module Example
Public Sub Main()
Dim strings() As String = {"This is the first sentence. ",
"This is the second sentence. ",
"This is the third sentence. "}
Dim asciiEncoding As Encoding = Encoding.ASCII
' Array to hold encoded bytes.
Dim bytes() As Byte
' Array to hold decoded characters.
Dim chars(50) As Char
' Create index for current position of character array.
Dim index As Integer

For Each stringValue In strings


Console.WriteLine("String to Encode: {0}", stringValue)
' Encode the string to a byte array.
bytes = asciiEncoding.GetBytes(stringValue)
' Display the encoded bytes.
Console.Write("Encoded bytes: ")
For ctr As Integer = 0 To bytes.Length - 1
Console.Write(" {0}{1:X2}", If(ctr Mod 20 = 0, vbCrLf, ""),
bytes(ctr))
Next
Console.WriteLine()

' Decode the bytes to a single character array.


Dim count As Integer = asciiEncoding.GetCharCount(bytes)
If count + index >= chars.Length Then
Array.Resize(chars, chars.Length + 50)
End If
Dim written As Integer = asciiEncoding.GetChars(bytes, 0,
bytes.Length,
chars, index)
index = index + written
Console.WriteLine()
Next

' Instantiate a single string containing the characters.


Dim decodedString As New String(chars, 0, index - 1)
Console.WriteLine("Decoded string: ")
Console.WriteLine(decodedString)
End Sub
End Module
' The example displays the following output:
' String to Encode: This is the first sentence.
' Encoded bytes:
' 54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65
' 6E 74 65 6E 63 65 2E 20
'
' String to Encode: This is the second sentence.
' Encoded bytes:
' 54 68 69 73 20 69 73 20 74 68 65 20 73 65 63 6F 6E 64 20 73
' 65 6E 74 65 6E 63 65 2E 20
'
' String to Encode: This is the third sentence.
' Encoded bytes:
' 54 68 69 73 20 69 73 20 74 68 65 20 74 68 69 72 64 20 73 65
' 6E 74 65 6E 63 65 2E 20
'
' Decoded string:
' This is the first sentence. This is the second sentence. This is the third sentence.

Los métodos de codificación y descodificación de una clase derivada de Encoding están diseñados para funcionar
en un conjunto completo de datos; es decir, todos los datos que se va a codificar o descodificar se proporciona en
una única llamada al método. Sin embargo, en algunos casos, los datos están disponibles en una secuencia y los
datos que se va a codificar o descodificar pueden estar disponibles solo desde operaciones de lectura
independientes. Para ello, la operación de codificación o descodificación debe recordar cualquier estado guardado
de su invocación anterior. Los métodos de clases derivadas de Encoder y Decoder pueden controlar las
operaciones de codificación y descodificación que abarcan varias llamadas a métodos.
Hay disponible un objeto Encoder para una codificación determinada desde la propiedad Encoding.GetEncoder de
esa codificación. Hay disponible un objeto Decoder para una codificación determinada desde la propiedad
Encoding.GetDecoder de esa codificación. Para las operaciones de descodificación, tenga en cuenta que las clases
derivadas de Decoder incluyen un método Decoder.GetChars , pero no tienen un método que se corresponda con
Encoding.GetString.
En el ejemplo siguiente se muestra la diferencia entre el uso de los métodos Encoding.GetString y
Decoder.GetChars para descodificar una matriz de bytes Unicode. En el ejemplo se codifica una cadena que
contiene algunos caracteres Unicode en un archivo y, a continuación, se usan los dos métodos de descodificación
para descodificarlos de diez bytes en diez bytes. Puesto que hay un par suplente en los bytes décimo y undécimo,
se descodifica en llamadas a métodos independientes. Como muestra el resultado, el método Encoding.GetString
no puede descodificar los bytes correctamente y, en su lugar, los reemplaza con U+FFFD (carácter de reemplazo).
Por otra parte, el método Decoder.GetChars puede descodificar correctamente la matriz de bytes para obtener la
cadena original.

using System;
using System.IO;
using System.Text;

public class Example


{
public static void Main()
{
// Use default replacement fallback for invalid encoding.
UnicodeEncoding enc = new UnicodeEncoding(true, false, false);

// Define a string with various Unicode characters.


string str1 = "AB YZ 19 \uD800\udc05 \u00e4";
str1 += "Unicode characters. \u00a9 \u010C s \u0062\u0308";
Console.WriteLine("Created original string...\n");

// Convert string to byte array.


byte[] bytes = enc.GetBytes(str1);

FileStream fs = File.Create(@".\characters.bin");
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(bytes);
bw.Close();

// Read bytes from file.


FileStream fsIn = File.OpenRead(@".\characters.bin");
BinaryReader br = new BinaryReader(fsIn);

const int count = 10; // Number of bytes to read at a time.


byte[] bytesRead = new byte[10]; // Buffer (byte array).
int read; // Number of bytes actually read.
string str2 = String.Empty; // Decoded string.

// Try using Encoding object for all operations.


do {
read = br.Read(bytesRead, 0, count);
str2 += enc.GetString(bytesRead, 0, read);
} while (read == count);
br.Close();
Console.WriteLine("Decoded string using UnicodeEncoding.GetString()...");
CompareForEquality(str1, str2);
Console.WriteLine();
// Use Decoder for all operations.
fsIn = File.OpenRead(@".\characters.bin");
br = new BinaryReader(fsIn);
Decoder decoder = enc.GetDecoder();
char[] chars = new char[50];
int index = 0; // Next character to write in array.
int written = 0; // Number of chars written to array.
do {
read = br.Read(bytesRead, 0, count);
if (index + decoder.GetCharCount(bytesRead, 0, read) - 1 >= chars.Length)
Array.Resize(ref chars, chars.Length + 50);

written = decoder.GetChars(bytesRead, 0, read, chars, index);


index += written;
} while (read == count);
br.Close();
// Instantiate a string with the decoded characters.
string str3 = new String(chars, 0, index);
Console.WriteLine("Decoded string using UnicodeEncoding.Decoder.GetString()...");
CompareForEquality(str1, str3);
}

private static void CompareForEquality(string original, string decoded)


{
bool result = original.Equals(decoded);
Console.WriteLine("original = decoded: {0}",
original.Equals(decoded, StringComparison.Ordinal));
if (! result) {
Console.WriteLine("Code points in original string:");
foreach (var ch in original)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));
Console.WriteLine();

Console.WriteLine("Code points in decoded string:");


foreach (var ch in decoded)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));
Console.WriteLine();
}
}
}
// The example displays the following output:
// Created original string...
//
// Decoded string using UnicodeEncoding.GetString()...
// original = decoded: False
// Code points in original string:
// 0041 0042 0020 0059 005A 0020 0031 0039 0020 D800 DC05 0020 00E4 0055 006E 0069 0063 006F
// 0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C
// 0020 0073 0020 0062 0308
// Code points in decoded string:
// 0041 0042 0020 0059 005A 0020 0031 0039 0020 FFFD FFFD 0020 00E4 0055 006E 0069 0063 006F
// 0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C
// 0020 0073 0020 0062 0308
//
// Decoded string using UnicodeEncoding.Decoder.GetString()...
// original = decoded: True

Imports System.IO
Imports System.Text

Module Example
Public Sub Main()
' Use default replacement fallback for invalid encoding.
Dim enc As New UnicodeEncoding(True, False, False)

' Define a string with various Unicode characters.


Dim str1 As String = String.Format("AB YZ 19 {0}{1} {2}",
ChrW(&hD800), ChrW(&hDC05), ChrW(&h00e4))
str1 += String.Format("Unicode characters. {0} {1} s {2}{3}",
ChrW(&h00a9), ChrW(&h010C), ChrW(&h0062), ChrW(&h0308))
Console.WriteLine("Created original string...")
Console.WriteLine()

' Convert string to byte array.


Dim bytes() As Byte = enc.GetBytes(str1)

Dim fs As FileStream = File.Create(".\characters.bin")


Dim bw As New BinaryWriter(fs)
bw.Write(bytes)
bw.Close()

' Read bytes from file.


Dim fsIn As FileStream = File.OpenRead(".\characters.bin")
Dim br As New BinaryReader(fsIn)

Const count As Integer = 10 ' Number of bytes to read at a time.


Dim bytesRead(9) As Byte ' Buffer (byte array).
Dim read As Integer ' Number of bytes actually read.
Dim str2 As String = "" ' Decoded string.

' Try using Encoding object for all operations.


Do
read = br.Read(bytesRead, 0, count)
str2 += enc.GetString(bytesRead, 0, read)
Loop While read = count
br.Close()
Console.WriteLine("Decoded string using UnicodeEncoding.GetString()...")
CompareForEquality(str1, str2)
Console.WriteLine()

' Use Decoder for all operations.


fsIn = File.OpenRead(".\characters.bin")
br = New BinaryReader(fsIn)
Dim decoder As Decoder = enc.GetDecoder()
Dim chars(50) As Char
Dim index As Integer = 0 ' Next character to write in array.
Dim written As Integer = 0 ' Number of chars written to array.
Do
read = br.Read(bytesRead, 0, count)
If index + decoder.GetCharCount(bytesRead, 0, read) - 1 >= chars.Length Then
Array.Resize(chars, chars.Length + 50)
End If
written = decoder.GetChars(bytesRead, 0, read, chars, index)
index += written
Loop While read = count
br.Close()
' Instantiate a string with the decoded characters.
Dim str3 As New String(chars, 0, index)
Console.WriteLine("Decoded string using UnicodeEncoding.Decoder.GetString()...")
CompareForEquality(str1, str3)
End Sub

Private Sub CompareForEquality(original As String, decoded As String)


Dim result As Boolean = original.Equals(decoded)
Console.WriteLine("original = decoded: {0}",
original.Equals(decoded, StringComparison.Ordinal))
If Not result Then
Console.WriteLine("Code points in original string:")
For Each ch In original
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()

Console.WriteLine("Code points in decoded string:")


For Each ch In decoded
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
End If
End Sub
End Module
' The example displays the following output:
' Created original string...
'
' Decoded string using UnicodeEncoding.GetString()...
' original = decoded: False
' Code points in original string:
' 0041 0042 0020 0059 005A 0020 0031 0039 0020 D800 DC05 0020 00E4 0055 006E 0069 0063 006F
' 0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C
' 0020 0073 0020 0062 0308
' Code points in decoded string:
' 0041 0042 0020 0059 005A 0020 0031 0039 0020 FFFD FFFD 0020 00E4 0055 006E 0069 0063 006F
' 0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C
' 0020 0073 0020 0062 0308
'
' Decoded string using UnicodeEncoding.Decoder.GetString()...
' original = decoded: True

Elegir una estrategia de reinterpretación


Cuando un método intenta codificar o descodificar un carácter pero no existe ninguna asignación, debe
implementar una estrategia de reserva que determine cómo se debe tratar la asignación incorrecta. Hay tres tipos
de estrategias de reserva:
Best-Fit Fallback
Replacement Fallback
Exception Fallback

IMPORTANT
Los problemas más frecuentes en las operaciones de codificación se producen cuando un carácter Unicode no se puede
asignar a una codificación determinada de la página de códigos. Los problemas más comunes de las operaciones de
descodificación se producen cuando las secuencias no válidas de bytes no se pueden traducir a caracteres Unicode válidos.
Por estas razones, debe saber qué estrategia de reserva emplea un determinado objeto de codificación. Siempre que sea
posible, debe especificar la estrategia de reserva usada por un objeto de codificación cuando se crea una instancia del objeto.

Best-Fit Fallback
Cuando un carácter no tiene una coincidencia exacta en la codificación de destino, el codificador puede intentar
asignarle a un carácter similar. (La reserva con ajuste perfecto es principalmente un problema de codificación en
lugar de un problema de descodificación. Hay muy pocas páginas de códigos que contengan caracteres que no se
puedan asignar correctamente a Unicode.) La reserva con ajuste perfecto es el valor predeterminado para las
codificaciones de páginas de códigos y de juegos de caracteres de doble byte recuperadas por las sobrecargas de
Encoding.GetEncoding(Int32) y Encoding.GetEncoding(String).

NOTE
En teoría, las clases de codificación Unicode proporcionadas en .NET (UTF8Encoding, UnicodeEncoding y UTF32Encoding)
admiten cada carácter de todos los juegos de caracteres, por lo que se pueden usar para eliminar los problemas de reserva
con ajuste perfecto.

Las estrategias de ajuste perfecto varían en páginas de códigos diferentes. Por ejemplo, para algunas páginas de
códigos, los caracteres latinos de ancho completo se asignarán a caracteres latinos de ancho medio, que son más
comunes. Para otras páginas de códigos no se realiza esta asignación. Incluso con una estrategia de ajuste perfecto
dinámica, algunos caracteres no tienen un ajuste imaginable en algunas codificaciones. Por ejemplo, un ideograma
chino no tiene ninguna asignación razonable a la página de códigos 1252. En este caso, se emplea una cadena de
reemplazo. De forma predeterminada, esta cadena es simplemente un carácter QUESTION MARK (U+003F).

NOTE
Las estrategias de ajuste perfecto no están documentadas de forma detallada. Sin embargo, varias páginas de códigos se
documentan en el sitio web de Unicode Consortium. Revise el archivo Léame.txt de esa carpeta para obtener una
descripción de cómo interpretar los archivos de asignación.

En el ejemplo siguiente se usa la página de códigos 1252 (la página de códigos de Windows para los idiomas de
Europa occidental) para mostrar la asignación con ajuste perfecto y sus desventajas. El método
Encoding.GetEncoding(Int32) se usa para recuperar un objeto de codificación para la página de códigos 1252. De
forma predeterminada, usa una asignación con ajuste perfecto para los caracteres Unicode que no admite. En el
ejemplo se crea una instancia de una cadena que contiene tres caracteres no ASCII, CIRCLED LATIN CAPITAL
LETTER S (U+24C8), SUPERSCRIPT FIVE (U+2075) e INFINITY (U+221E), separados por espacios en blanco. Como
muestra el resultado del ejemplo, cuando se codifica la cadena, los tres caracteres originales que no son espacios
en blanco se reemplazan con QUESTION MARK (U+003F), DIGIT FIVE (U+0035) y DIGIT EIGHT (U+0038). DIGIT
EIGHT es un reemplazo especialmente deficiente para el carácter INFINITY no compatible y QUESTION MARK
indica que no había ninguna asignación disponible para el carácter original.
using System;
using System.Text;

public class Example


{
public static void Main()
{
// Get an encoding for code page 1252 (Western Europe character set).
Encoding cp1252 = Encoding.GetEncoding(1252);

// Define and display a string.


string str = "\u24c8 \u2075 \u221e";
Console.WriteLine("Original string: " + str);
Console.Write("Code points in string: ");
foreach (var ch in str)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine("\n");

// Encode a Unicode string.


Byte[] bytes = cp1252.GetBytes(str);
Console.Write("Encoded bytes: ");
foreach (byte byt in bytes)
Console.Write("{0:X2} ", byt);
Console.WriteLine("\n");

// Decode the string.


string str2 = cp1252.GetString(bytes);
Console.WriteLine("String round-tripped: {0}", str.Equals(str2));
if (! str.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));
}
}
}
// The example displays the following output:
// Original string: Ⓢ ⁵ ∞
// Code points in string: 24C8 0020 2075 0020 221E
//
// Encoded bytes: 3F 20 35 20 38
//
// String round-tripped: False
// ? 5 8
// 003F 0020 0035 0020 0038
Imports System.Text

Module Example
Public Sub Main()
' Get an encoding for code page 1252 (Western Europe character set).
Dim cp1252 As Encoding = Encoding.GetEncoding(1252)

' Define and display a string.


Dim str As String = String.Format("{0} {1} {2}", ChrW(&h24c8), ChrW(&H2075), ChrW(&h221E))
Console.WriteLine("Original string: " + str)
Console.Write("Code points in string: ")
For Each ch In str
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
Console.WriteLine()

' Encode a Unicode string.


Dim bytes() As Byte = cp1252.GetBytes(str)
Console.Write("Encoded bytes: ")
For Each byt In bytes
Console.Write("{0:X2} ", byt)
Next
Console.WriteLine()
Console.WriteLine()

' Decode the string.


Dim str2 As String = cp1252.GetString(bytes)
Console.WriteLine("String round-tripped: {0}", str.Equals(str2))
If Not str.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
End If
End Sub
End Module
' The example displays the following output:
' Original string: Ⓢ ⁵ ∞
' Code points in string: 24C8 0020 2075 0020 221E
'
' Encoded bytes: 3F 20 35 20 38
'
' String round-tripped: False
' ? 5 8
' 003F 0020 0035 0020 0038

La asignación con ajuste perfecto es el comportamiento predeterminado para un objeto Encoding que codifica los
datos Unicode en datos de página de códigos, y hay aplicaciones heredadas que se basan en este comportamiento.
Sin embargo, la mayoría de las aplicaciones nuevas deben evitarlo por razones de seguridad. Por ejemplo, las
aplicaciones no deben asignar nombres de dominio mediante una codificación con ajuste perfecto.

NOTE
También puede implementar una asignación personalizada de reserva con ajuste perfecto para una codificación. Para más
información, vea la sección Implementing a Custom Fallback Strategy .

Si la reserva con ajuste perfecto es el valor predeterminado para un objeto de codificación, puede elegir otra
estrategia de reserva cuando se recupera un objeto Encoding llamando a la sobrecarga de
Encoding.GetEncoding(Int32, EncoderFallback, DecoderFallback) o Encoding.GetEncoding(String, EncoderFallback,
DecoderFallback) . La próxima sección incluye un ejemplo que reemplaza con un asterisco (*) cada carácter que no
se puede asignar a la página de códigos 1252.
using System;
using System.Text;

public class Example


{
public static void Main()
{
Encoding cp1252r = Encoding.GetEncoding(1252,
new EncoderReplacementFallback("*"),
new DecoderReplacementFallback("*"));

string str1 = "\u24C8 \u2075 \u221E";


Console.WriteLine(str1);
foreach (var ch in str1)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();

byte[] bytes = cp1252r.GetBytes(str1);


string str2 = cp1252r.GetString(bytes);
Console.WriteLine("Round-trip: {0}", str1.Equals(str2));
if (! str1.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();
}
}
}
// The example displays the following output:
// Ⓢ ⁵ ∞
// 24C8 0020 2075 0020 221E
// Round-trip: False
// * * *
// 002A 0020 002A 0020 002A
Imports System.Text

Module Example
Public Sub Main()
Dim cp1252r As Encoding = Encoding.GetEncoding(1252,
New EncoderReplacementFallback("*"),
New DecoderReplacementFallback("*"))

Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))


Console.WriteLine(str1)
For Each ch In str1
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()

Dim bytes() As Byte = cp1252r.GetBytes(str1)


Dim str2 As String = cp1252r.GetString(bytes)
Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
If Not str1.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
End If
End Sub
End Module
' The example displays the following output:
' Ⓢ ⁵ ∞
' 24C8 0020 2075 0020 221E
' Round-trip: False
' * * *
' 002A 0020 002A 0020 002A

Replacement Fallback
Cuando un carácter no tiene una coincidencia exacta en el esquema de destino, pero no hay ningún carácter
adecuado al que se pueda asignar, la aplicación puede especificar un carácter o una cadena de reemplazo. Este es el
comportamiento predeterminado del descodificador Unicode, que reemplaza cualquier secuencia de dos bytes que
no pueda descodificar con REPLACEMENT_CHARACTER (U+FFFD). También es el comportamiento predeterminado
de la clase ASCIIEncoding , que reemplaza cada carácter que no puede codificar o descodificar con un signo de
interrogación. En el ejemplo siguiente se muestra el reemplazo de caracteres para la cadena Unicode del ejemplo
anterior. Como muestra el resultado, cada carácter que no se puede descodificar en un valor de bytes ASCII se
reemplaza con 0x3F, que es el código ASCII de un signo de interrogación.
using System;
using System.Text;

public class Example


{
public static void Main()
{
Encoding enc = Encoding.ASCII;

string str1 = "\u24C8 \u2075 \u221E";


Console.WriteLine(str1);
foreach (var ch in str1)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine("\n");

// Encode the original string using the ASCII encoder.


byte[] bytes = enc.GetBytes(str1);
Console.Write("Encoded bytes: ");
foreach (var byt in bytes)
Console.Write("{0:X2} ", byt);
Console.WriteLine("\n");

// Decode the ASCII bytes.


string str2 = enc.GetString(bytes);
Console.WriteLine("Round-trip: {0}", str1.Equals(str2));
if (! str1.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();
}
}
}
// The example displays the following output:
// Ⓢ ⁵ ∞
// 24C8 0020 2075 0020 221E
//
// Encoded bytes: 3F 20 3F 20 3F
//
// Round-trip: False
// ? ? ?
// 003F 0020 003F 0020 003F
Imports System.Text

Module Example
Public Sub Main()
Dim enc As Encoding = Encoding.Ascii

Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))


Console.WriteLine(str1)
For Each ch In str1
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
Console.WriteLine()

' Encode the original string using the ASCII encoder.


Dim bytes() As Byte = enc.GetBytes(str1)
Console.Write("Encoded bytes: ")
For Each byt In bytes
Console.Write("{0:X2} ", byt)
Next
Console.WriteLine()
Console.WriteLine()

' Decode the ASCII bytes.


Dim str2 As String = enc.GetString(bytes)
Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
If Not str1.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
End If
End Sub
End Module
' The example displays the following output:
' Ⓢ ⁵ ∞
' 24C8 0020 2075 0020 221E
'
' Encoded bytes: 3F 20 3F 20 3F
'
' Round-trip: False
' ? ? ?
' 003F 0020 003F 0020 003F

.NET incluye las clases EncoderReplacementFallback y DecoderReplacementFallback, que sustituyen una cadena de
reemplazo si un carácter no se asigna exactamente en una operación de codificación o descodificación. De forma
predeterminada, esta cadena de reemplazo es un signo de interrogación, pero puede llamar a una sobrecarga del
constructor de clase para elegir otra cadena diferente. Normalmente, la cadena de reemplazo es un carácter único,
aunque esto no es un requisito. En el ejemplo siguiente se cambia el comportamiento del codificador de la página
de códigos 1252 creando una instancia de un objeto EncoderReplacementFallback que usa un asterisco (*) como
cadena de reemplazo.
using System;
using System.Text;

public class Example


{
public static void Main()
{
Encoding cp1252r = Encoding.GetEncoding(1252,
new EncoderReplacementFallback("*"),
new DecoderReplacementFallback("*"));

string str1 = "\u24C8 \u2075 \u221E";


Console.WriteLine(str1);
foreach (var ch in str1)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();

byte[] bytes = cp1252r.GetBytes(str1);


string str2 = cp1252r.GetString(bytes);
Console.WriteLine("Round-trip: {0}", str1.Equals(str2));
if (! str1.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();
}
}
}
// The example displays the following output:
// Ⓢ ⁵ ∞
// 24C8 0020 2075 0020 221E
// Round-trip: False
// * * *
// 002A 0020 002A 0020 002A
Imports System.Text

Module Example
Public Sub Main()
Dim cp1252r As Encoding = Encoding.GetEncoding(1252,
New EncoderReplacementFallback("*"),
New DecoderReplacementFallback("*"))

Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))


Console.WriteLine(str1)
For Each ch In str1
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()

Dim bytes() As Byte = cp1252r.GetBytes(str1)


Dim str2 As String = cp1252r.GetString(bytes)
Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
If Not str1.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
End If
End Sub
End Module
' The example displays the following output:
' Ⓢ ⁵ ∞
' 24C8 0020 2075 0020 221E
' Round-trip: False
' * * *
' 002A 0020 002A 0020 002A

NOTE
También puede implementar una clase de reemplazo para una codificación. Para más información, vea la sección
Implementing a Custom Fallback Strategy .

Además de QUESTION MARK (U+003F), el REPLACEMENT CHARACTER de Unicode (U+FFFD) se suele usar como
cadena de reemplazo, especialmente al descodificar secuencias de bytes que no se puede traducir correctamente a
caracteres Unicode. Sin embargo, se puede elegir cualquier cadena de reemplazo y esta puede contener varios
caracteres.
Exception Fallback
En lugar de proporcionar una reserva con ajuste perfecto o una cadena de reemplazo, un codificador puede
producir EncoderFallbackException si no puede codificar un juego de caracteres y un descodificador puede
producir DecoderFallbackException si no puede descodificar una matriz de bytes. Para producir una excepción en
operaciones de codificación y descodificación, se proporciona un objeto EncoderExceptionFallback y un objeto
DecoderExceptionFallback , respectivamente, al método Encoding.GetEncoding(String, EncoderFallback,
DecoderFallback) . En el ejemplo siguiente se muestra la reserva de excepción con la clase ASCIIEncoding .

using System;
using System.Text;

public class Example


{
public static void Main()
{
Encoding enc = Encoding.GetEncoding("us-ascii",
new EncoderExceptionFallback(),
new EncoderExceptionFallback(),
new DecoderExceptionFallback());

string str1 = "\u24C8 \u2075 \u221E";


Console.WriteLine(str1);
foreach (var ch in str1)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine("\n");

// Encode the original string using the ASCII encoder.


byte[] bytes = {};
try {
bytes = enc.GetBytes(str1);
Console.Write("Encoded bytes: ");
foreach (var byt in bytes)
Console.Write("{0:X2} ", byt);

Console.WriteLine();
}
catch (EncoderFallbackException e) {
Console.Write("Exception: ");
if (e.IsUnknownSurrogate())
Console.WriteLine("Unable to encode surrogate pair 0x{0:X4} 0x{1:X3} at index {2}.",
Convert.ToUInt16(e.CharUnknownHigh),
Convert.ToUInt16(e.CharUnknownLow),
e.Index);
else
Console.WriteLine("Unable to encode 0x{0:X4} at index {1}.",
Convert.ToUInt16(e.CharUnknown),
e.Index);
return;
}
Console.WriteLine();

// Decode the ASCII bytes.


try {
string str2 = enc.GetString(bytes);
Console.WriteLine("Round-trip: {0}", str1.Equals(str2));
if (! str1.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();
}
}
catch (DecoderFallbackException e) {
Console.Write("Unable to decode byte(s) ");
foreach (byte unknown in e.BytesUnknown)
Console.Write("0x{0:X2} ");

Console.WriteLine("at index {0}", e.Index);


}
}
}
// The example displays the following output:
// Ⓢ ⁵ ∞
// 24C8 0020 2075 0020 221E
//
// Exception: Unable to encode 0x24C8 at index 0.
Imports System.Text

Module Example
Public Sub Main()
Dim enc As Encoding = Encoding.GetEncoding("us-ascii",
New EncoderExceptionFallback(),
New DecoderExceptionFallback())

Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))


Console.WriteLine(str1)
For Each ch In str1
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
Console.WriteLine()

' Encode the original string using the ASCII encoder.


Dim bytes() As Byte = {}
Try
bytes = enc.GetBytes(str1)
Console.Write("Encoded bytes: ")
For Each byt In bytes
Console.Write("{0:X2} ", byt)
Next
Console.WriteLine()
Catch e As EncoderFallbackException
Console.Write("Exception: ")
If e.IsUnknownSurrogate() Then
Console.WriteLine("Unable to encode surrogate pair 0x{0:X4} 0x{1:X3} at index {2}.",
Convert.ToUInt16(e.CharUnknownHigh),
Convert.ToUInt16(e.CharUnknownLow),
e.Index)
Else
Console.WriteLine("Unable to encode 0x{0:X4} at index {1}.",
Convert.ToUInt16(e.CharUnknown),
e.Index)
End If
Exit Sub
End Try
Console.WriteLine()

' Decode the ASCII bytes.


Try
Dim str2 As String = enc.GetString(bytes)
Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
If Not str1.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
End If
Catch e As DecoderFallbackException
Console.Write("Unable to decode byte(s) ")
For Each unknown As Byte In e.BytesUnknown
Console.Write("0x{0:X2} ")
Next
Console.WriteLine("at index {0}", e.Index)
End Try
End Sub
End Module
' The example displays the following output:
' Ⓢ ⁵ ∞
' 24C8 0020 2075 0020 221E
'
' Exception: Unable to encode 0x24C8 at index 0.
NOTE
También puede implementar un controlador de excepciones personalizado para una operación de codificación. Para más
información, vea la sección Implementing a Custom Fallback Strategy .

Los objetos EncoderFallbackException y DecoderFallbackException proporcionan la siguiente información acerca de


la condición que provocó la excepción:
El objeto EncoderFallbackException incluye un método IsUnknownSurrogate , que indica si el carácter o los
caracteres que no se pueden codificar representan un par suplente desconocido (en este caso, el método
devuelve true ) o un único carácter desconocido (en este caso, el método devuelve false ). Los caracteres
del par suplente están disponibles a partir de las propiedades EncoderFallbackException.CharUnknownHigh
y EncoderFallbackException.CharUnknownLow . El carácter único desconocido está disponible en la
propiedad EncoderFallbackException.CharUnknown . La propiedad EncoderFallbackException.Index indica la
posición de la cadena en la que se encontró el primer carácter que no se pudo codificar.
El objeto DecoderFallbackException incluye una propiedad BytesUnknown que devuelve una matriz de bytes
que no se pueden descodificar. La propiedad DecoderFallbackException.Index indica la posición inicial de los
bytes desconocidos.
Aunque los objetos EncoderFallbackException y DecoderFallbackException proporcionan información suficiente de
diagnóstico sobre la excepción, no proporcionan acceso al búfer de codificación o descodificación. Por tanto, no
permiten reemplazar o corregir datos no válidos dentro del método de codificación o descodificación.

Implementing a Custom Fallback Strategy


Además de la asignación con ajuste perfecto implementada internamente por las páginas de códigos, .NET incluye
las siguientes clases para implementar una estrategia de reserva:
Use EncoderReplacementFallback y EncoderReplacementFallbackBuffer para reemplazar caracteres en
operaciones de codificación.
Use DecoderReplacementFallback y DecoderReplacementFallbackBuffer para reemplazar caracteres en
operaciones de descodificación.
Use EncoderExceptionFallback y EncoderExceptionFallbackBuffer para producir EncoderFallbackException
cuando un carácter no se puede codificar.
Use DecoderExceptionFallback y DecoderExceptionFallbackBuffer para producir DecoderFallbackException
cuando un carácter no se puede descodificar.
Además, puede implementar una solución personalizada que use reserva con ajuste perfecto, reserva de
reemplazo o reserva de excepción siguiendo estos pasos:
1. Derive una clase de EncoderFallback para las operaciones de codificación y de DecoderFallback para las
operaciones de descodificación.
2. Derive una clase de EncoderFallbackBuffer para las operaciones de codificación y de DecoderFallbackBuffer
para las operaciones de descodificación.
3. Para la reserva de excepción, si las clases EncoderFallbackException y DecoderFallbackException
predefinidas no satisfacen sus necesidades, derive una clase de un objeto de excepción como Exception o
ArgumentException.
Derivar de EncoderFallback o DecoderFallback
Para implementar una solución de reserva personalizada, debe crear una clase que herede de EncoderFallback para
las operaciones de codificación y de DecoderFallback para las operaciones de descodificación. Las instancias de
estas clases se pasan al método Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) y actúan como
intermediario entre la clase de codificación y la implementación de reserva.
Cuando se crea una solución de reserva personalizada para un codificador o un descodificador, debe implementar
los miembros siguientes:
La propiedad EncoderFallback.MaxCharCount o DecoderFallback.MaxCharCount , que devuelve el número
máximo de caracteres posible que el reemplazo con mejor ajuste o la reserva de excepción pueda devolver
para reemplazar un único carácter. En el caso de la reserva de excepción personalizada, su valor es cero.
El método EncoderFallback.CreateFallbackBuffer o DecoderFallback.CreateFallbackBuffer , que devuelve una
implementación personalizada de EncoderFallbackBuffer o DecoderFallbackBuffer . El codificador llama al
método cuando encuentra el primer carácter que no puede codificar correctamente o lo llama el
decodificador cuando encuentra el primer byte que no puede descodificar correctamente.
Derivar de EncoderFallbackBuffer o DecoderFallbackBuffer
Para implementar una solución de reserva personalizada, debe crear también una clase que herede de
EncoderFallbackBuffer para las operaciones de codificación y de DecoderFallbackBuffer para las operaciones de
descodificación. El método CreateFallbackBuffer de las clases EncoderFallback y DecoderFallback devuelve
instancias de estas clases. El codificador llama al método EncoderFallback.CreateFallbackBuffer cuando encuentra
el primer carácter que no puede codificar y el descodificador llama al método
DecoderFallback.CreateFallbackBuffer cuando encuentra uno o más bytes que no puede descodificar. Las clases
EncoderFallbackBuffer y DecoderFallbackBuffer proporcionan la implementación de reserva. Cada instancia
representa un búfer que contiene los caracteres de reserva que reemplazarán el carácter que no se puede codificar
o la secuencia de bytes que no se puede descodificar.
Cuando se crea una solución de reserva personalizada para un codificador o un descodificador, debe implementar
los miembros siguientes:
El método EncoderFallbackBuffer.Fallback o DecoderFallbackBuffer.Fallback . El codificador llama
aEncoderFallbackBuffer.Fallback para proporcionar el búfer de reserva con información sobre el carácter que
no puede codificar. Puesto que el carácter que se va a codificar puede ser un par suplente, este método está
sobrecargado. Se pasa a una sobrecarga el carácter que se va a codificar y su índice en la cadena. A la
segunda sobrecarga se le pasa el suplente máximo y mínimo junto con su índice en la cadena. El
descodificador llama al método DecoderFallbackBuffer.Fallback para proporcionar el búfer de reserva con
información sobre los bytes que no puede descodificar. Se pasa a este método una matriz de bytes que no
puede descodificar, junto con el índice del primer byte. El método de reserva debe devolver true si el búfer
de reserva puede proporcionar un carácter o caracteres de mejor ajuste o de reemplazo; de lo contrario,
debe devolver false . En el caso de una reserva de excepción, el método de reserva debe producir una
excepción.
El método EncoderFallbackBuffer.GetNextChar o DecoderFallbackBuffer.GetNextChar , al que llama
repetidamente el codificador o el descodificador para obtener el siguiente carácter del búfer de reserva.
Cuando se han devuelto todos los caracteres de reserva, el método debe devolver U+0000.
La propiedad EncoderFallbackBuffer.Remaining o DecoderFallbackBuffer.Remaining , que devuelve el
número de caracteres que permanecen en el búfer de reserva.
El método EncoderFallbackBuffer.MovePrevious o DecoderFallbackBuffer.MovePrevious , que mueve la
posición actual en el búfer de reserva al carácter anterior.
El método EncoderFallbackBuffer.Reset o DecoderFallbackBuffer.Reset , que se reinicializa el búfer de reserva.
Si la implementación de reserva es una reserva con ajuste perfecto o una reserva de reemplazo, las clases
derivadas de EncoderFallbackBuffer y DecoderFallbackBuffer también mantienen dos campos de instancia
privados: el número exacto de caracteres del búfer y el índice del carácter siguiente del búfer que se va a devolver.
Ejemplo de EncoderFallback
En un ejemplo anterior se usaba la reserva de reemplazo para reemplazar caracteres Unicode que no
correspondían a caracteres ASCII con un asterisco (*). En el ejemplo siguiente se usa una implementación
personalizada de reserva con ajuste perfecto en su lugar para proporcionar una mejor asignación de caracteres no
ASCII.
En el código siguiente se define una clase denominada CustomMapper que se deriva de EncoderFallback para
controlar la asignación con ajuste perfecto de caracteres no ASCII. Su método CreateFallbackBuffer devuelve un
objeto CustomMapperFallbackBuffer , que proporciona la implementación de EncoderFallbackBuffer . La clase
CustomMapper usa un objeto Dictionary<TKey,TValue> para almacenar las asignaciones de caracteres Unicode no
compatibles (el valor de clave) y sus caracteres de 8 bits correspondientes (que se almacenan en dos bytes
consecutivos en un entero de 64 bits). Para que esta asignación esté disponible para el búfer de reserva, la
instancia de CustomMapper se pasa como un parámetro al constructor de clase CustomMapperFallbackBuffer . Puesto
que la asignación más larga es la cadena "INF" por el carácter Unicode U+221E, la propiedad MaxCharCount
devuelve 3.

public class CustomMapper : EncoderFallback


{
public string DefaultString;
internal Dictionary<ushort, ulong> mapping;

public CustomMapper() : this("*")


{
}

public CustomMapper(string defaultString)


{
this.DefaultString = defaultString;

// Create table of mappings


mapping = new Dictionary<ushort, ulong>();
mapping.Add(0x24C8, 0x53);
mapping.Add(0x2075, 0x35);
mapping.Add(0x221E, 0x49004E0046);
}

public override EncoderFallbackBuffer CreateFallbackBuffer()


{
return new CustomMapperFallbackBuffer(this);
}

public override int MaxCharCount


{
get { return 3; }
}
}
Public Class CustomMapper : Inherits EncoderFallback
Public DefaultString As String
Friend mapping As Dictionary(Of UShort, ULong)

Public Sub New()


Me.New("?")
End Sub

Public Sub New(ByVal defaultString As String)


Me.DefaultString = defaultString

' Create table of mappings


mapping = New Dictionary(Of UShort, ULong)
mapping.Add(&H24C8, &H53)
mapping.Add(&H2075, &H35)
mapping.Add(&H221E, &H49004E0046)
End Sub

Public Overrides Function CreateFallbackBuffer() As System.Text.EncoderFallbackBuffer


Return New CustomMapperFallbackBuffer(Me)
End Function

Public Overrides ReadOnly Property MaxCharCount As Integer


Get
Return 3
End Get
End Property
End Class

En el código siguiente se define la clase CustomMapperFallbackBuffer , que se deriva de EncoderFallbackBuffer. El


diccionario que contiene las asignaciones con ajuste perfecto y que se define en la instancia de CustomMapper está
disponible desde su constructor de clase. Su método Fallback devuelve true si cualquiera de los caracteres
Unicode que el codificador ASCII no puede codificar se definen en el diccionario de asignación; de lo contrario,
devuelve false . Para cada reserva, la variable count privada indica el número de caracteres que quedan por
devolver y la variable index privada indica la posición en el búfer de cadena, charsToReturn , del siguiente carácter
que se va a devolver.

public class CustomMapperFallbackBuffer : EncoderFallbackBuffer


{
int count = -1; // Number of characters to return
int index = -1; // Index of character to return
CustomMapper fb;
string charsToReturn;

public CustomMapperFallbackBuffer(CustomMapper fallback)


{
this.fb = fallback;
}

public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)


{
// Do not try to map surrogates to ASCII.
return false;
}

public override bool Fallback(char charUnknown, int index)


{
// Return false if there are already characters to map.
if (count >= 1) return false;

// Determine number of characters to return.


charsToReturn = String.Empty;

ushort key = Convert.ToUInt16(charUnknown);


ushort key = Convert.ToUInt16(charUnknown);
if (fb.mapping.ContainsKey(key)) {
byte[] bytes = BitConverter.GetBytes(fb.mapping[key]);
int ctr = 0;
foreach (var byt in bytes) {
if (byt > 0) {
ctr++;
charsToReturn += (char) byt;
}
}
count = ctr;
}
else {
// Return default.
charsToReturn = fb.DefaultString;
count = 1;
}
this.index = charsToReturn.Length - 1;

return true;
}

public override char GetNextChar()


{
// We'll return a character if possible, so subtract from the count of chars to return.
count--;
// If count is less than zero, we've returned all characters.
if (count < 0)
return '\u0000';

this.index--;
return charsToReturn[this.index + 1];
}

public override bool MovePrevious()


{
// Original: if count >= -1 and pos >= 0
if (count >= -1) {
count++;
return true;
}
else {
return false;
}
}

public override int Remaining


{
get { return count < 0 ? 0 : count; }
}

public override void Reset()


{
count = -1;
index = -1;
}
}

Public Class CustomMapperFallbackBuffer : Inherits EncoderFallbackBuffer

Dim count As Integer = -1 ' Number of characters to return


Dim index As Integer = -1 ' Index of character to return
Dim fb As CustomMapper
Dim charsToReturn As String

Public Sub New(ByVal fallback As CustomMapper)


MyBase.New()
Me.fb = fallback
End Sub

Public Overloads Overrides Function Fallback(ByVal charUnknownHigh As Char, ByVal charUnknownLow As Char,
ByVal index As Integer) As Boolean
' Do not try to map surrogates to ASCII.
Return False
End Function

Public Overloads Overrides Function Fallback(ByVal charUnknown As Char, ByVal index As Integer) As Boolean
' Return false if there are already characters to map.
If count >= 1 Then Return False

' Determine number of characters to return.


charsToReturn = String.Empty

Dim key As UShort = Convert.ToUInt16(charUnknown)


If fb.mapping.ContainsKey(key) Then
Dim bytes() As Byte = BitConverter.GetBytes(fb.mapping.Item(key))
Dim ctr As Integer
For Each byt In bytes
If byt > 0 Then
ctr += 1
charsToReturn += Chr(byt)
End If
Next
count = ctr
Else
' Return default.
charsToReturn = fb.DefaultString
count = 1
End If
Me.index = charsToReturn.Length - 1

Return True
End Function

Public Overrides Function GetNextChar() As Char


' We'll return a character if possible, so subtract from the count of chars to return.
count -= 1
' If count is less than zero, we've returned all characters.
If count < 0 Then Return ChrW(0)

Me.index -= 1
Return charsToReturn(Me.index + 1)
End Function

Public Overrides Function MovePrevious() As Boolean


' Original: if count >= -1 and pos >= 0
If count >= -1 Then
count += 1
Return True
Else
Return False
End If
End Function

Public Overrides ReadOnly Property Remaining As Integer


Get
Return If(count < 0, 0, count)
End Get
End Property

Public Overrides Sub Reset()


count = -1
index = -1
End Sub
End Class
En el código siguiente se crean instancias del objeto CustomMapper y se pasa una instancia del mismo al método
Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) . El resultado indica que la implementación de
reserva con ajuste perfecto controla correctamente los tres caracteres no ASCII de la cadena original.

using System;
using System.Collections.Generic;
using System.Text;

class Program
{
static void Main()
{
Encoding enc = Encoding.GetEncoding("us-ascii", new CustomMapper(), new DecoderExceptionFallback());

string str1 = "\u24C8 \u2075 \u221E";


Console.WriteLine(str1);
for (int ctr = 0; ctr <= str1.Length - 1; ctr++) {
Console.Write("{0} ", Convert.ToUInt16(str1[ctr]).ToString("X4"));
if (ctr == str1.Length - 1)
Console.WriteLine();
}
Console.WriteLine();

// Encode the original string using the ASCII encoder.


byte[] bytes = enc.GetBytes(str1);
Console.Write("Encoded bytes: ");
foreach (var byt in bytes)
Console.Write("{0:X2} ", byt);

Console.WriteLine("\n");

// Decode the ASCII bytes.


string str2 = enc.GetString(bytes);
Console.WriteLine("Round-trip: {0}", str1.Equals(str2));
if (! str1.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();
}
}
}
Imports System.Text
Imports System.Collections.Generic

Module Module1

Sub Main()
Dim enc As Encoding = Encoding.GetEncoding("us-ascii", New CustomMapper(), New
DecoderExceptionFallback())

Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&H24C8), ChrW(&H2075), ChrW(&H221E))


Console.WriteLine(str1)
For ctr As Integer = 0 To str1.Length - 1
Console.Write("{0} ", Convert.ToUInt16(str1(ctr)).ToString("X4"))
If ctr = str1.Length - 1 Then Console.WriteLine()
Next
Console.WriteLine()

' Encode the original string using the ASCII encoder.


Dim bytes() As Byte = enc.GetBytes(str1)
Console.Write("Encoded bytes: ")
For Each byt In bytes
Console.Write("{0:X2} ", byt)
Next
Console.WriteLine()
Console.WriteLine()

' Decode the ASCII bytes.


Dim str2 As String = enc.GetString(bytes)
Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
If Not str1.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
End If
End Sub
End Module

Vea también
Introducción a la codificación de caracteres en .NET
Encoder
Decoder
DecoderFallback
Encoding
EncoderFallback
Globalización y localización
Procedimientos recomendados para el uso de
cadenas en .NET
16/09/2020 • 60 minutes to read • Edit Online

.NET proporciona una gran compatibilidad para desarrollar aplicaciones localizadas y globalizadas, y simplifica la
aplicación de las convenciones de la referencia cultural actual o de una referencia cultural concreta al realizar
operaciones comunes como ordenar y mostrar cadenas. Pero ordenar o comparar cadenas no es siempre una
operación dependiente de la referencia cultural. Por ejemplo, las cadenas usadas internamente por una aplicación
normalmente se deben administrar de forma idéntica en todas las referencias culturales. Cuando los datos de
cadenas independientes de la referencia cultural (como etiquetas XML, etiquetas HTML, nombres de usuario, rutas
de acceso de archivos y nombres de objetos del sistema) se interpretan como si fueran dependientes de la
referencia cultural, el código de aplicación puede estar sujeto a errores imperceptibles, un rendimiento inadecuado
y, en algunos casos, a problemas de seguridad.
En este tema, se examinan los métodos de ordenación, comparación y uso de mayúsculas y minúsculas de cadenas
de .NET, se presentan recomendaciones para seleccionar un método adecuado de control de cadenas y se
proporciona información adicional sobre los métodos de control de cadenas. También se examina cómo se usan
para la presentación y el almacenamiento los datos con formato, como los datos numéricos y los datos de fecha y
hora.

Recomendaciones sobre el uso de cadenas


Cuando desarrolle con .NET, siga estas recomendaciones sencillas a la hora de usar cadenas:
Use sobrecargas que especifiquen explícitamente las reglas de comparación de cadenas para las operaciones de
cadenas. Normalmente, esto implica llamar a una sobrecarga de método que tiene un parámetro de tipo
StringComparison.
Use StringComparison.Ordinal o StringComparison.OrdinalIgnoreCase para las comparaciones como su valor
predeterminado seguro para la coincidencia de cadenas válidas para la referencia cultural.
Use comparaciones con StringComparison.Ordinal o StringComparison.OrdinalIgnoreCase para lograr un
rendimiento mejor.
Use operaciones de cadena basadas en StringComparison.CurrentCulture al mostrar la salida al usuario.
Use los valores StringComparison.Ordinal o StringComparison.OrdinalIgnoreCase no lingüísticos en lugar de
operaciones de cadena basadas en CultureInfo.InvariantCulture cuando la comparación sea lingüísticamente no
pertinente (por ejemplo, nombre simbólico).
Emplee el método String.ToUpperInvariant en lugar del método String.ToLowerInvariant al normalizar cadenas
para la comparación.
Use una sobrecarga del método String.Equals para probar si dos cadenas son iguales.
Use los métodos String.Compare y String.CompareTo para ordenar cadenas, no para comprobar la igualdad.
Use el formato dependiente de la referencia cultural para mostrar datos que no son de cadena, como números
y fechas, en una interfaz de usuario. Use el formato con la referencia cultural invariable para conservar datos
que no son de cadena en forma de cadena.
Evite lo siguiente cuando use cadenas:
No emplee sobrecargas que no especifiquen explícita o implícitamente las reglas de comparación de cadenas
para las operaciones de cadena.
No use operaciones de cadena basadas en StringComparison.InvariantCulture en la mayoría de los casos. Una
de las pocas excepciones es cuando vaya a conservar datos lingüísticamente significativos pero válidos
culturalmente.
No emplee ninguna sobrecarga del método String.Compare o CompareTo y pruebe si se devuelve un valor cero
para determinar si dos cadenas son iguales.
No use el formato dependiente de la referencia cultural para conservar datos numéricos o datos de fecha y
hora en formato de cadena.

Especificar comparaciones de cadenas explícitamente


La mayoría de los métodos de manipulación de cadenas de .NET están sobrecargados. Normalmente, una o más
sobrecargas aceptan la configuración predeterminada, mientras que otras no aceptan ningún valor
predeterminado y en su lugar definen la manera precisa en la que se van a comparar o manipular las cadenas. La
mayoría de los métodos que no confían en los valores predeterminados incluye un parámetro de tipo
StringComparison, que es una enumeración que especifica explícitamente reglas para la comparación de cadenas
por referencia cultural y uso de mayúsculas y minúsculas. En la tabla siguiente se describen los miembros de la
enumeración StringComparison .

M IEM B RO DE ST RIN GC O M PA RISO N DESC RIP C IÓ N

CurrentCulture Realiza una comparación con distinción entre mayúsculas y


minúsculas usando la referencia cultural actual.

CurrentCultureIgnoreCase Realiza una comparación sin distinción entre mayúsculas y


minúsculas usando la referencia cultural actual.

InvariantCulture Realiza una comparación con distinción entre mayúsculas y


minúsculas usando la referencia cultural de todos los idiomas.

InvariantCultureIgnoreCase Realiza una comparación sin distinción entre mayúsculas y


minúsculas usando la referencia cultural de todos los idiomas.

Ordinal Realiza una comparación ordinal.

OrdinalIgnoreCase Realiza una comparación ordinal sin distinción entre


mayúsculas y minúsculas.

Por ejemplo, el método IndexOf , que devuelve el índice de una subcadena en un objeto String que coincide con un
carácter o una cadena, tiene nueve sobrecargas:
IndexOf(Char), IndexOf(Char, Int32)y IndexOf(Char, Int32, Int32), que de forma predeterminada realizan una
búsqueda ordinal (con distinción entre mayúsculas y minúsculas e independiente de la referencia cultural) de
un carácter de la cadena.
IndexOf(String), IndexOf(String, Int32)y IndexOf(String, Int32, Int32), que de forma predeterminada realizan una
búsqueda con distinción entre mayúsculas y minúsculas y dependiente de la referencia cultural de una
subcadena de la cadena.
IndexOf(String, StringComparison), IndexOf(String, Int32, StringComparison)y IndexOf(String, Int32, Int32,
StringComparison), que incluyen un parámetro de tipo StringComparison que permite especificar el formato de
la comparación.
Se recomienda seleccionar una sobrecarga que no use valores predeterminados, por las razones siguientes:
Algunas sobrecargas con parámetros predeterminados (las que buscan un valor Char en la instancia de la
cadena) realizan una comparación ordinal, mientras que otras (las que buscan una cadena en la instancia de
la cadena) son dependientes de la referencia cultural. Es difícil recordar qué método usa cada valor
predeterminado y resulta fácil confundir las sobrecargas.
La intención del código que usa valores predeterminados para las llamadas al método no está clara. En el
ejemplo siguiente, en el cual se usan valores predeterminados, es difícil saber si el desarrollador pretendía
realizar una comparación ordinal o lingüística de dos cadenas, o si había alguna diferencia al usar
mayúsculas y minúsculas entre protocol y "http" que pudiera hacer que la prueba de igualdad devolviera
el valor false .

string protocol = GetProtocol(url);


if (String.Equals(protocol, "http")) {
// ...Code to handle HTTP protocol.
}
else {
throw new InvalidOperationException();
}

Dim protocol As String = GetProtocol(url)


If String.Equals(protocol, "http") Then
' ...Code to handle HTTP protocol.
Else
Throw New InvalidOperationException()
End If

En general, se recomienda llamar a un método que no use los valores predeterminados, ya que hace que la
intención del código no sea ambigua. Esto, a su vez, hace el código más legible y más fácil de depurar y mantener.
En el ejemplo siguiente se abordan las cuestiones que se derivan del ejemplo anterior. Indica claramente que se
usa la comparación ordinal y que se omiten las diferencias en cuanto al uso de mayúsculas y minúsculas.

string protocol = GetProtocol(url);


if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
// ...Code to handle HTTP protocol.
}
else {
throw new InvalidOperationException();
}

Dim protocol As String = GetProtocol(url)


If String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase) Then
' ...Code to handle HTTP protocol.
Else
Throw New InvalidOperationException()
End If

Detalles de la comparación de cadenas


La comparación de cadenas es el corazón de muchas operaciones relacionadas con cadenas, especialmente la
ordenación y la comprobación de igualdad. Las cadenas se ordenan en un orden determinado: si "mi" aparece
antes que "cadena" en una lista ordenada de cadenas, "mi" debe compararse como menor o igual que "cadena".
Además, la comparación define la igualdad implícitamente. La operación de comparación devuelve cero para las
cadenas que considera iguales. Una buena interpretación es que ninguna cadena es menor que otra. La mayoría de
las operaciones significativas que implican cadenas incluyen uno o ambos de estos procedimientos: comparar con
otra cadena y ejecutar una operación de ordenación bien definida.
NOTE
Puede descargar las tablas de pesos de ordenación, un conjunto de archivos de texto que contienen información sobre los
pesos de caracteres que se usan en las operaciones de ordenación y comparación para los sistemas operativos Windows,
además de la tabla de elementos de intercalación Unicode predeterminada, que es la última versión de la tabla de pesos de
ordenación para Linux y macOS. La versión específica de la tabla de pesos de ordenación en Linux y macOS depende de la
versión de las bibliotecas de componentes internacionales de Unicode instaladas en el sistema. Para más información sobre
las versiones de los componentes internacionales de Unicode y las versiones de Unicode que implementan, vea la
información sobre la descarga de componentes internacionales de Unicode.

Sin embargo, la evaluación de dos cadenas para comprobar su igualdad o su criterio de ordenación no produce
ningún resultado correcto único; el resultado depende de los criterios empleados para comparar las cadenas. En
especial, las comparaciones de cadenas que son ordinales o que se basan en las convenciones de ordenación y uso
de mayúsculas y minúsculas de la referencia cultural actual o de la referencia cultural invariable (una referencia
cultural válida para la configuración regional basada en el idioma inglés) pueden producir resultados diferentes.
Además, las comparaciones de cadenas mediante las diferentes versiones de .NET o con .NET en distintos sistemas
operativos o versiones de sistema operativo pueden devolver resultados diferentes. Para más información, vea Las
cadenas y el estándar Unicode.
Comparaciones de cadenas que usan la referencia cultural actual
Un criterio implica usar las convenciones de la referencia cultural actual a la hora de comparar cadenas. Las
comparaciones que se basan en la referencia cultural actual usan la referencia cultural o la configuración regional
actual del subproceso. Si el usuario no establece la referencia cultural, se usa como valor predeterminado la
configuración de la ventana Opciones regionales del Panel de control. Siempre debe usar comparaciones
basadas en la referencia cultural actual cuando los datos sean lingüísticamente pertinentes y cuando refleje una
interacción con el usuario dependiente de la referencia cultural.
En cambio, el comportamiento de comparación y uso de mayúsculas y minúsculas de .NET cambia cuando la
referencia cultural cambia. Esto ocurre cuando una aplicación se ejecuta en un equipo que tiene una referencia
cultural diferente que el equipo en el que se desarrolló la aplicación o cuando el subproceso en ejecución cambia
su referencia cultural. Este comportamiento es deliberado, pero sigue resultando no obvio para muchos
desarrolladores. En el ejemplo siguiente, se muestran las diferencias en el criterio de ordenación entre las
referencias culturales de inglés de EE. UU. ("en-US") y sueco ("sv-SE"). Tenga en cuenta que las palabras
"ångström", "Windows" y "Visual Studio" aparecen en distintas posiciones en las matrices de cadenas ordenadas.
using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
string[] values= { "able", "ångström", "apple", "Æble",
"Windows", "Visual Studio" };
Array.Sort(values);
DisplayArray(values);

// Change culture to Swedish (Sweden).


string originalCulture = CultureInfo.CurrentCulture.Name;
Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
Array.Sort(values);
DisplayArray(values);

// Restore the original culture.


Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);
}

private static void DisplayArray(string[] values)


{
Console.WriteLine("Sorting using the {0} culture:",
CultureInfo.CurrentCulture.Name);
foreach (string value in values)
Console.WriteLine(" {0}", value);

Console.WriteLine();
}
}
// The example displays the following output:
// Sorting using the en-US culture:
// able
// Æble
// ångström
// apple
// Visual Studio
// Windows
//
// Sorting using the sv-SE culture:
// able
// Æble
// apple
// Windows
// Visual Studio
// ångström
Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim values() As String = {"able", "ångström", "apple", _
"Æble", "Windows", "Visual Studio"}
Array.Sort(values)
DisplayArray(values)

' Change culture to Swedish (Sweden).


Dim originalCulture As String = CultureInfo.CurrentCulture.Name
Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
Array.Sort(values)
DisplayArray(values)

' Restore the original culture.


Thread.CurrentThread.CurrentCulture = New CultureInfo(originalCulture)
End Sub

Private Sub DisplayArray(values() As String)


Console.WRiteLine("Sorting using the {0} culture:", _
CultureInfo.CurrentCulture.Name)
For Each value As String In values
Console.WriteLine(" {0}", value)
Next
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Sorting using the en-US culture:
' able
' Æble
' ångström
' apple
' Visual Studio
' Windows
'
' Sorting using the sv-SE culture:
' able
' Æble
' apple
' Windows
' Visual Studio
' ångström

Las comparaciones sin distinción entre mayúsculas y minúsculas que usan la referencia cultural actual son iguales
que las comparaciones dependientes de la referencia cultural, excepto que omiten el uso de mayúsculas y
minúsculas según indica la referencia cultural actual del subproceso. Este comportamiento también se puede
manifestar en los criterios de ordenación.
Las comparaciones que usan semántica de la referencia cultural actual son el valor predeterminado para los
métodos siguientes:
Sobrecargas deString.Compare que no incluyen un parámetro StringComparison .
Sobrecargas deString.CompareTo .
El método predeterminado String.StartsWith(String) y el método String.StartsWith(String, Boolean, CultureInfo)
con un parámetro null CultureInfo .
El método predeterminado String.EndsWith(String) y el método String.EndsWith(String, Boolean, CultureInfo)
con un parámetro null CultureInfo.
Sobrecargas deString.IndexOf que aceptan String como un parámetro de búsqueda y que no tienen un
parámetro StringComparison .
Sobrecargas deString.LastIndexOf que aceptan String como un parámetro de búsqueda y que no tienen un
parámetro StringComparison .
En cualquier caso, se recomienda llamar a una sobrecarga que tenga un parámetro StringComparison para aclarar
la intención de la llamada al método.
Pueden surgir errores imperceptibles y no tan imperceptibles cuando los datos de cadenas no lingüísticos se
interpretan lingüísticamente, o cuando los datos de cadenas de una referencia cultural determinada se interpretan
usando las convenciones de otra referencia cultural. El ejemplo canónico es el problema con I en turco.
Para casi todos los alfabetos latinos, incluso en inglés de EE. UU., el carácter "i" (\u0069) es la versión en
minúsculas del carácter "I" (\u0049). Esta regla de mayúsculas y minúsculas se convierte rápidamente en el valor
predeterminado para alguien que programe en esa referencia cultural. En cambio, el alfabeto turco ("tr-TR") incluye
un carácter "I con punto" "İ" (\u0130), que es la versión en mayúsculas de "i". El turco también incluye un carácter
"i sin punto" en minúscula, "ı" (\u0131), que en mayúsculas es "I". Este comportamiento también se produce en la
referencia cultural de azerbaiyano ("az").
Por tanto, los supuestos sobre poner en mayúsculas "i" o escribir "I" en minúsculas no son válidas en todas las
referencias culturales. Si usa las sobrecargas predeterminadas para las rutinas de comparación de cadenas, estarán
sujetas a variaciones entre distintas referencias culturales. Si los datos que se van a comparar son no lingüísticos,
el uso de las sobrecargas predeterminadas puede generar resultados no deseables, como ilustra el siguiente
intento de realizar una comparación sin distinción entre mayúsculas y minúsculas de las cadenas "file" y "FILE".

using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
string fileUrl = "file";
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine("Culture = {0}",
Thread.CurrentThread.CurrentCulture.DisplayName);
Console.WriteLine("(file == FILE) = {0}",
fileUrl.StartsWith("FILE", true, null));
Console.WriteLine();

Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");


Console.WriteLine("Culture = {0}",
Thread.CurrentThread.CurrentCulture.DisplayName);
Console.WriteLine("(file == FILE) = {0}",
fileUrl.StartsWith("FILE", true, null));
}
}
// The example displays the following output:
// Culture = English (United States)
// (file == FILE) = True
//
// Culture = Turkish (Turkey)
// (file == FILE) = False
Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim fileUrl = "file"
Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
Console.WriteLine("Culture = {0}", _
Thread.CurrentThread.CurrentCulture.DisplayName)
Console.WriteLine("(file == FILE) = {0}", _
fileUrl.StartsWith("FILE", True, Nothing))
Console.WriteLine()

Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")


Console.WriteLine("Culture = {0}", _
Thread.CurrentThread.CurrentCulture.DisplayName)
Console.WriteLine("(file == FILE) = {0}", _
fileUrl.StartsWith("FILE", True, Nothing))
End Sub
End Module
' The example displays the following output:
' Culture = English (United States)
' (file == FILE) = True
'
' Culture = Turkish (Turkey)
' (file == FILE) = False

Esta comparación podría producir problemas importantes si la referencia cultural se usa involuntariamente en
configuraciones que afectan a la seguridad, como en el ejemplo siguiente. Una llamada al método como
IsFileURI("file:") devuelve true si la referencia cultural actual es inglés de EE. U.U., pero false si la referencia
cultural actual es el turco. Así, en los sistemas turcos, alguien podría sortear las medidas de seguridad que
bloquean el acceso a los URI sin distinción entre mayúsculas y minúsculas que comienzan con "FILE":.

public static bool IsFileURI(String path)


{
return path.StartsWith("FILE:", true, null);
}

Public Shared Function IsFileURI(path As String) As Boolean


Return path.StartsWith("FILE:", True, Nothing)
End Function

En este caso, como "file:" se debe interpretar como un identificador no lingüístico e independiente de la referencia
cultural, el código se debe escribir como se muestra en el ejemplo siguiente:

public static bool IsFileURI(string path)


{
return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
}

Public Shared Function IsFileURI(path As String) As Boolean


Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function

Operaciones de cadenas ordinales


Al especificar el valor StringComparison.Ordinal o StringComparison.OrdinalIgnoreCase en una llamada al
método, se indica una comparación no lingüística en la que se omiten las características de los lenguajes naturales.
Los métodos que se invocan con estos valores StringComparison basan las decisiones sobre las operaciones con
cadenas en simples comparaciones de bytes en lugar de usos de mayúsculas y minúsculas o tablas de equivalencia
parametrizadas por referencia cultural. En la mayoría de los casos, este enfoque se adapta mejor a la interpretación
prevista de cadenas, y el código es más rápido y más confiable.
Las comparaciones ordinales son comparaciones de cadenas en las que cada byte de cada cadena se compara sin
ninguna interpretación lingüística; por ejemplo, "windows" no coincide con "Windows". Esta es, esencialmente, una
llamada a la función strcmp en tiempo de ejecución de C. Use esta comparación cuando el contexto indique que
las cadenas deben coincidir exactamente o exija una directiva de coincidencia conservadora. Además, la
comparación ordinal es la operación de comparación más rápida porque no aplica ninguna regla lingüística al
determinar un resultado.
En .NET, las cadenas pueden contener caracteres nulos incrustados. Una de las diferencias más claras entre la
comparación ordinal y dependiente de la referencia cultural (incluyendo las comparaciones que usan la referencia
cultural de todos los idiomas) tiene que ver con el control de caracteres nulos incrustados en una cadena. Estos
caracteres se omiten cuando usa métodos String.Compare y String.Equals para realizar comparaciones
dependientes de la referencia cultural (incluyendo las comparaciones que usan la referencia cultural de todos los
idiomas). Por tanto, en las comparaciones dependientes de la referencia cultural, las cadenas que contienen
caracteres nulos incrustados pueden considerarse iguales que las cadenas que no los contienen.

IMPORTANT
Aunque los métodos de comparación de cadenas hacen caso omiso de los caracteres nulos incrustados, los métodos de
búsqueda de cadenas como String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOfy String.StartsWith sí los
tienen en cuenta.

En el ejemplo siguiente se realiza una comparación dependiente de la referencia cultural de la cadena "Aa" con una
cadena similar que contiene varios caracteres nulos insertados entre "A" y "a", y se muestra cómo las dos cadenas
se consideran iguales:
using System;

public class Example


{
public static void Main()
{
string str1 = "Aa";
string str2 = "A" + new String('\u0000', 3) + "a";
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):",
str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine(" With String.Compare:");
Console.WriteLine(" Current Culture: {0}",
String.Compare(str1, str2, StringComparison.CurrentCulture));
Console.WriteLine(" Invariant Culture: {0}",
String.Compare(str1, str2, StringComparison.InvariantCulture));

Console.WriteLine(" With String.Equals:");


Console.WriteLine(" Current Culture: {0}",
String.Equals(str1, str2, StringComparison.CurrentCulture));
Console.WriteLine(" Invariant Culture: {0}",
String.Equals(str1, str2, StringComparison.InvariantCulture));
}

private static string ShowBytes(string str)


{
string hexString = String.Empty;
for (int ctr = 0; ctr < str.Length; ctr++)
{
string result = String.Empty;
result = Convert.ToInt32(str[ctr]).ToString("X4");
result = " " + result.Substring(0,2) + " " + result.Substring(2, 2);
hexString += result;
}
return hexString.Trim();
}
}
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Current Culture: 0
// Invariant Culture: 0
// With String.Equals:
// Current Culture: True
// Invariant Culture: True
Module Example
Public Sub Main()
Dim str1 As String = "Aa"
Dim str2 As String = "A" + New String(Convert.ToChar(0), 3) + "a"
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine(" With String.Compare:")
Console.WriteLine(" Current Culture: {0}", _
String.Compare(str1, str2, StringComparison.CurrentCulture))
Console.WriteLine(" Invariant Culture: {0}", _
String.Compare(str1, str2, StringComparison.InvariantCulture))

Console.WriteLine(" With String.Equals:")


Console.WriteLine(" Current Culture: {0}", _
String.Equals(str1, str2, StringComparison.CurrentCulture))
Console.WriteLine(" Invariant Culture: {0}", _
String.Equals(str1, str2, StringComparison.InvariantCulture))
End Sub

Private Function ShowBytes(str As String) As String


Dim hexString As String = String.Empty
For ctr As Integer = 0 To str.Length - 1
Dim result As String = String.Empty
result = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
result = " " + result.Substring(0, 2) + " " + result.Substring(2, 2)
hexString += result
Next
Return hexString.Trim()
End Function
End Module

Pero las cadenas no se consideran iguales cuando se usa la comparación ordinal, como se muestra en el ejemplo
siguiente:

Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):",


str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine(" With String.Compare:");
Console.WriteLine(" Ordinal: {0}",
String.Compare(str1, str2, StringComparison.Ordinal));

Console.WriteLine(" With String.Equals:");


Console.WriteLine(" Ordinal: {0}",
String.Equals(str1, str2, StringComparison.Ordinal));
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Ordinal: 97
// With String.Equals:
// Ordinal: False
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine(" With String.Compare:")
Console.WriteLine(" Ordinal: {0}", _
String.Compare(str1, str2, StringComparison.Ordinal))

Console.WriteLine(" With String.Equals:")


Console.WriteLine(" Ordinal: {0}", _
String.Equals(str1, str2, StringComparison.Ordinal))
' The example displays the following output:
' Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
' With String.Compare:
' Ordinal: 97
' With String.Equals:
' Ordinal: False

Las comparaciones ordinales sin distinción entre mayúsculas y minúsculas son el siguiente enfoque más
conservador. Estas comparaciones omiten la mayor parte del uso de mayúsculas y minúsculas; por ejemplo,
"windows" coincide con "Windows". A la hora de tratar con caracteres ASCII, esta directiva es equivalente a
StringComparison.Ordinal, salvo que omite el uso de mayúsculas y minúsculas habitual de ASCII. Por tanto,
cualquier carácter de [A, Z] (\u0041-\u005A) coincide con el carácter correspondiente de [a, z] (\u0061-\007A). El
uso de mayúsculas y minúsculas fuera del intervalo ASCII emplea las tablas de la referencia cultural de todos los
idiomas. Por tanto, la siguiente comparación:

String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);

String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)

es equivalente a esta comparación (pero más rápida):

String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(),
StringComparison.Ordinal);

String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(),
StringComparison.Ordinal)

Estas comparaciones siguen siendo muy rápidas.

NOTE
El comportamiento de las cadenas del sistema de archivos, claves del Registro y valores, y variables de entorno se representa
mejor mediante StringComparison.OrdinalIgnoreCase.

Tanto StringComparison.Ordinal como StringComparison.OrdinalIgnoreCase usan los valores binarios


directamente y son más adecuados para la búsqueda de coincidencias. Si no sabe con seguridad qué configuración
de comparación debe emplear, use uno de estos dos valores. Sin embargo, puesto que realizan una comparación
byte a byte, no ordenan según un criterio de ordenación lingüístico (como un diccionario de inglés) sino según un
criterio de ordenación binario. Los resultados pueden parecer extraños en la mayoría de los contextos si se
muestran a los usuarios.
La semántica ordinal es el valor predeterminado para las sobrecargas de String.Equals que no incluyen un
argumento StringComparison (incluyendo el operador de igualdad). En cualquier caso, se recomienda llamar a una
sobrecarga que tenga un parámetro StringComparison .
Operaciones de cadenas que usan la referencia cultural invariable
Las comparaciones con la referencia cultural de todos los idiomas usan la propiedad CompareInfo devuelta por la
propiedad estática CultureInfo.InvariantCulture . Este comportamiento es igual en todos los sistemas; traduce
cualquier carácter que esté fuera de su intervalo en lo que cree que son caracteres invariables equivalentes. Esta
directiva puede ser útil para mantener un conjunto de comportamientos de las cadenas en distintas referencias
culturales, pero a menudo proporciona resultados inesperados.
Las comparaciones sin distinción entre mayúsculas y minúsculas con la referencia cultural de todos los idiomas
usan también la propiedad estática CompareInfo devuelta por la propiedad estática CultureInfo.InvariantCulture
para obtener información de comparación. Cualquier diferencia en el uso de mayúsculas y minúsculas entre estos
caracteres traducidos se pasa por alto.
Las comparaciones que usan StringComparison.InvariantCulture y StringComparison.Ordinal funcionan de manera
idéntica en cadenas ASCII. Sin embargo, StringComparison.InvariantCulture toma decisiones lingüísticas que
podrían no ser adecuadas para las cadenas que tienen que interpretarse como un conjunto de bytes. El objeto
CultureInfo.InvariantCulture.CompareInfo hace que el método Compare interprete ciertos conjuntos de caracteres
como equivalentes. Por ejemplo, la siguiente equivalencia es válida en la referencia cultural de todos los idiomas:
InvariantCulture: a + ̊ = å
El carácter LETRA LATINA A MINÚSCULA "a" (\u0061), cuando está junto al carácter ANILLO SUPERIOR
COMBINABLE "+ " ̊" (\u030a), se interpreta como el carácter LETRA LATINA MINÚSCULA A CON ANILLO
SUPERIOR "å" (\u00e5). Como se muestra en el ejemplo siguiente, este comportamiento difiere de la comparación
ordinal.

string separated = "\u0061\u030a";


string combined = "\u00e5";

Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",


separated, combined,
String.Compare(separated, combined,
StringComparison.InvariantCulture) == 0);

Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",


separated, combined,
String.Compare(separated, combined,
StringComparison.Ordinal) == 0);
// The example displays the following output:
// Equal sort weight of a° and å using InvariantCulture: True
// Equal sort weight of a° and å using Ordinal: False

Dim separated As String = ChrW(&h61) + ChrW(&h30a)


Dim combined As String = ChrW(&he5)

Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}", _


separated, combined, _
String.Compare(separated, combined, _
StringComparison.InvariantCulture) = 0)

Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}", _


separated, combined, _
String.Compare(separated, combined, _
StringComparison.Ordinal) = 0)
' The example displays the following output:
' Equal sort weight of a° and å using InvariantCulture: True
' Equal sort weight of a° and å using Ordinal: False

A la hora de interpretar nombres de archivo, cookies u otros elementos donde pueda aparecer una combinación
como "å", las comparaciones ordinales siguen ofreciendo el comportamiento más transparente y adecuado.
En conjunto, la referencia cultural de todos los idiomas tiene muy pocas propiedades que la hagan útil para la
comparación. Realiza la comparación de manera lingüísticamente pertinente, lo que le impide garantizar una
equivalencia simbólica completa, pero no es la opción ideal para la presentación en cualquier referencia cultural.
Una de las pocas razones para usar StringComparison.InvariantCulture con el fin de realizar una comparación es
para conservar datos ordenados cuando se desea realizar una presentación idéntica transculturalmente. Por
ejemplo, si un archivo de datos grande que contiene una lista de identificadores ordenados para su presentación
acompaña una aplicación, al agregar datos a esta lista se necesitaría realizar una inserción con ordenación de estilo
invariable.

Elegir un miembro StringComparison para la llamada al método


En la tabla siguiente se describe la asignación del contexto de cadena semántico a un miembro de la enumeración
StringComparison:

VA LO R DE
SY ST EM . ST RIN GC O M PA RISO N

DATO S C O M P O RTA M IEN TO VA L UE

Identificadores internos con distinción Identificador no lingüístico, donde los Ordinal


entre mayúsculas y minúsculas. bytes coinciden exactamente.

Identificadores con distinción entre


mayúsculas y minúsculas en estándares
como XML y HTTP.

Configuraciones relacionadas con la


seguridad con distinción entre
mayúsculas y minúsculas.

Identificadores internos sin distinción Identificador no lingüístico, donde el OrdinalIgnoreCase


entre mayúsculas y minúsculas. uso de mayúsculas y minúsculas no es
pertinente; especialmente datos
Identificadores sin distinción entre almacenados en la mayoría de los
mayúsculas y minúsculas en estándares servicios del sistema de Windows.
como XML y HTTP.

Rutas de acceso a archivos.

Claves del Registro y valores.

Variables de entorno.

Identificadores de recursos (por


ejemplo, nombres de identificadores).

Configuraciones relacionadas con la


seguridad sin distinción entre
mayúsculas y minúsculas.

Algunos datos almacenados Datos válidos culturalmente que siguen InvariantCulture


lingüísticamente pertinentes. siendo lingüísticamente pertinentes.
o bien
Presentación de datos lingüísticos que
necesitan un criterio de ordenación fijo. InvariantCultureIgnoreCase
VA LO R DE
SY ST EM . ST RIN GC O M PA RISO N

DATO S C O M P O RTA M IEN TO VA L UE

Datos mostrados al usuario. Datos que necesitan personalizaciones CurrentCulture


lingüísticas locales.
La mayoría de los datos o bien
proporcionados por el usuario.
CurrentCultureIgnoreCase

Métodos comunes de comparación de cadenas en .NET


En las secciones siguientes se describen los métodos que se usan con más frecuencia para la comparación de
cadenas.
String.Compare
Interpretación predeterminada: StringComparison.CurrentCulture.
Al ser la operación fundamental para la interpretación de cadenas, todas las instancias de estas llamadas al método
se deben examinar para determinar si las cadenas se deben interpretar según la referencia cultural actual o se
deben separar de la referencia cultural (simbólicamente). Normalmente, se trata del último caso y se debe usar en
su lugar una comparación StringComparison.Ordinal .
La clase System.Globalization.CompareInfo , devuelta por la propiedad CultureInfo.CompareInfo , también incluye
un método Compare que proporciona un gran número de opciones de coincidencia (ordinal, omitir el espacio en
blanco, omitir el tipo de kana, etc.) por medio de la enumeración de marca CompareOptions .
String.CompareTo
Interpretación predeterminada: StringComparison.CurrentCulture.
Este método no ofrece actualmente una sobrecarga que especifique un tipo StringComparison . Normalmente es
posible convertir este método en el formato recomendado del método String.Compare(String, String,
StringComparison) .
Los tipos que implementan interfaces IComparable y IComparable<T> implementan este método. Puesto que no
ofrece la opción de un parámetro StringComparison , la implementación de tipos suele permitir al usuario
especificar StringComparer en su constructor. En el ejemplo siguiente se define una clase FileName cuyo
constructor de clase incluye un parámetro StringComparer . Este objeto StringComparer se usa entonces en el
método FileName.CompareTo .
using System;

public class FileName : IComparable


{
string fname;
StringComparer comparer;

public FileName(string name, StringComparer comparer)


{
if (String.IsNullOrEmpty(name))
throw new ArgumentNullException("name");

this.fname = name;

if (comparer != null)
this.comparer = comparer;
else
this.comparer = StringComparer.OrdinalIgnoreCase;
}

public string Name


{
get { return fname; }
}

public int CompareTo(object obj)


{
if (obj == null) return 1;

if (! (obj is FileName))
return comparer.Compare(this.fname, obj.ToString());
else
return comparer.Compare(this.fname, ((FileName) obj).Name);
}
}
Public Class FileName : Implements IComparable
Dim fname As String
Dim comparer As StringComparer

Public Sub New(name As String, comparer As StringComparer)


If String.IsNullOrEmpty(name) Then
Throw New ArgumentNullException("name")
End If

Me.fname = name

If comparer IsNot Nothing Then


Me.comparer = comparer
Else
Me.comparer = StringComparer.OrdinalIgnoreCase
End If
End Sub

Public ReadOnly Property Name As String


Get
Return fname
End Get
End Property

Public Function CompareTo(obj As Object) As Integer _


Implements IComparable.CompareTo
If obj Is Nothing Then Return 1

If Not TypeOf obj Is FileName Then


obj = obj.ToString()
Else
obj = CType(obj, FileName).Name
End If
Return comparer.Compare(Me.fname, obj)
End Function
End Class

String.Equals
Interpretación predeterminada: StringComparison.Ordinal.
La clase String le permite comprobar la igualdad llamando a las sobrecargas de método estático o de instancia
Equals , o usando el operador de igualdad estático. Las sobrecargas y el operador usan la comparación ordinal de
forma predeterminada. Sin embargo, todavía sigue siendo recomendable llamar a una sobrecarga que especifique
explícitamente el tipo StringComparison aunque desee realizar una comparación ordinal; esto facilita la búsqueda
de cierta interpretación de la cadena en el código.
String.ToUpper y String.ToLower
Interpretación predeterminada: StringComparison.CurrentCulture.
Debe tener cuidado al usar estos métodos, ya que forzar que una cadena esté en mayúsculas o en minúsculas se
usa a menudo como una pequeña normalización para comparar cadenas independientemente del uso de
mayúsculas y minúsculas. En tal caso, considere la posibilidad de emplear una comparación sin distinción entre
mayúsculas y minúsculas.
También están disponibles los métodos String.ToUpperInvariant y String.ToLowerInvariant . ToUpperInvariant es la
manera estándar de normalizar el uso de mayúsculas y minúsculas. Las comparaciones realizadas mediante
StringComparison.OrdinalIgnoreCase tienen un comportamiento que es la composición de dos llamadas: llamar a
ToUpperInvariant en ambos argumentos de cadena y realizar una comparación mediante
StringComparison.Ordinal.
También hay sobrecargas para convertir a mayúsculas y minúsculas en una referencia cultural concreta, pasando al
método un objeto CultureInfo que representa esa referencia cultural.
Char.ToUpper y Char.ToLower
Interpretación predeterminada: StringComparison.CurrentCulture.
Estos métodos funcionan de manera similar a los métodos String.ToUpper y String.ToLower descritos en la sección
anterior.
String.StartsWith y String.EndsWith
Interpretación predeterminada: StringComparison.CurrentCulture.
De forma predeterminada, estos dos métodos realizan una comparación dependiente de la referencia cultural.
String.IndexOf y String.LastIndexOf
Interpretación predeterminada: StringComparison.CurrentCulture.
No hay coherencia en cómo las sobrecargas predeterminadas de estos métodos realizan las comparaciones. Todos
los métodos String.IndexOf y String.LastIndexOf que incluyen un parámetro Char realizan una comparación
ordinal, pero los métodos String.IndexOf y String.LastIndexOf predeterminados que incluyen un parámetro String
realizan una comparación dependiente de la referencia cultural.
Si llama al método String.IndexOf(String) o String.LastIndexOf(String) y le pasa una cadena para ubicar en la
instancia actual, se recomienda llamar a una sobrecarga que especifique explícitamente el tipo StringComparison .
Las sobrecargas que incluyen un argumento Char no le permiten especificar un tipo StringComparison .

Métodos que realizan la comparación de cadenas indirectamente


Algunos métodos sin cadenas que tienen la comparación de cadenas como operación fundamental usan el tipo
StringComparer . La clase StringComparer incluye seis propiedades estáticas que devuelven instancias de
StringComparer cuyos métodos StringComparer.Compare realizan los siguientes tipos de comparaciones de
cadenas:
Comparaciones de cadenas dependientes de la referencia cultural usando la referencia cultural actual. Este
objeto StringComparer está devuelto por la propiedad StringComparer.CurrentCulture .
Comparaciones sin distinción entre mayúsculas y minúsculas usando la referencia cultural actual. Este objeto
StringComparer está devuelto por la propiedad StringComparer.CurrentCultureIgnoreCase .
Comparaciones independientes de la referencia cultural usando las reglas de comparación de palabras de la
referencia cultural de todos los idiomas. Este objeto StringComparer está devuelto por la propiedad
StringComparer.InvariantCulture .
Comparaciones sin distinción entre mayúsculas y minúsculas e independientes de la referencia cultural usando
las reglas de comparación de palabras de la referencia cultural de todos los idiomas. Este objeto
StringComparer está devuelto por la propiedad StringComparer.InvariantCultureIgnoreCase .
Comparación ordinal. Este objeto StringComparer está devuelto por la propiedad StringComparer.Ordinal .
Comparación ordinal sin distinción entre mayúsculas y minúsculas. Este objeto StringComparer está devuelto
por la propiedad StringComparer.OrdinalIgnoreCase .
Array.Sort y Array.BinarySearch
Interpretación predeterminada: StringComparison.CurrentCulture.
Cuando se almacenan datos en una colección, o cuando se leen datos almacenados de un archivo o una base de
datos en una colección, el cambio de la referencia cultural actual puede invalidar los valores invariables de la
colección. El método Array.BinarySearch supone que los elementos de la matriz que se van a buscar ya están
ordenados. Para ordenar cualquier elemento de cadena de la matriz, el método Array.Sort llama al método
String.Compare para ordenar los elementos individuales. El uso de un comparador dependiente de la referencia
cultural puede ser peligroso si la referencia cultural cambia desde que se ordena la matriz hasta que se busca en su
contenido. Por ejemplo, en el código siguiente, el almacenamiento y la recuperación funcionan en el comparador
que la propiedad Thread.CurrentThread.CurrentCulture . Si la referencia cultural puede cambiar entre las llamadas
a StoreNames y DoesNameExist , y especialmente si el contenido de la matriz se conserva en alguna parte entre las
dos llamadas al método, se puede producir un error en la búsqueda binaria.

// Incorrect.
string []storedNames;

public void StoreNames(string [] names)


{
int index = 0;
storedNames = new string[names.Length];

foreach (string name in names)


{
this.storedNames[index++] = name;
}

Array.Sort(names); // Line A.
}

public bool DoesNameExist(string name)


{
return (Array.BinarySearch(this.storedNames, name) >= 0); // Line B.
}

' Incorrect.
Dim storedNames() As String

Public Sub StoreNames(names() As String)


Dim index As Integer = 0
ReDim storedNames(names.Length - 1)

For Each name As String In names


Me.storedNames(index) = name
index += 1
Next

Array.Sort(names) ' Line A.


End Sub

Public Function DoesNameExist(name As String) As Boolean


Return Array.BinarySearch(Me.storedNames, name) >= 0 ' Line B.
End Function

Aparece una variación recomendada en el ejemplo siguiente, que usa el mismo método de comparación ordinal
(independiente de la referencia cultural) para ordenar y buscar en la matriz. El código cambiado se refleja en las
líneas etiquetadas como Line A y Line B en los dos ejemplos.
// Correct.
string []storedNames;

public void StoreNames(string [] names)


{
int index = 0;
storedNames = new string[names.Length];

foreach (string name in names)


{
this.storedNames[index++] = name;
}

Array.Sort(names, StringComparer.Ordinal); // Line A.


}

public bool DoesNameExist(string name)


{
return (Array.BinarySearch(this.storedNames, name, StringComparer.Ordinal) >= 0); // Line B.
}

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)


Dim index As Integer = 0
ReDim storedNames(names.Length - 1)

For Each name As String In names


Me.storedNames(index) = name
index += 1
Next

Array.Sort(names, StringComparer.Ordinal) ' Line A.


End Sub

Public Function DoesNameExist(name As String) As Boolean


Return Array.BinarySearch(Me.storedNames, name, StringComparer.Ordinal) >= 0 ' Line B.
End Function

Si estos datos se conservan y mueven entre distintas referencias culturales, y se usa la ordenación para presentar
estos datos al usuario, es mejor usar StringComparison.InvariantCulture, que funciona lingüísticamente para
obtener una mejor salida para el usuario pero no se ve afectado por los cambios en la referencia cultural. En el
ejemplo siguiente se modifican los dos ejemplos anteriores para usar la referencia cultural de todos los idiomas
con el fin de ordenar y buscar en la matriz.
// Correct.
string []storedNames;

public void StoreNames(string [] names)


{
int index = 0;
storedNames = new string[names.Length];

foreach (string name in names)


{
this.storedNames[index++] = name;
}

Array.Sort(names, StringComparer.InvariantCulture); // Line A.


}

public bool DoesNameExist(string name)


{
return (Array.BinarySearch(this.storedNames, name, StringComparer.InvariantCulture) >= 0); // Line B.
}

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)


Dim index As Integer = 0
ReDim storedNames(names.Length - 1)

For Each name As String In names


Me.storedNames(index) = name
index += 1
Next

Array.Sort(names, StringComparer.InvariantCulture) ' Line A.


End Sub

Public Function DoesNameExist(name As String) As Boolean


Return Array.BinarySearch(Me.storedNames, name, StringComparer.InvariantCulture) >= 0 ' Line B.
End Function

Ejemplo de colecciones: Constructor de tablas hash


Al aplicar un algoritmo hash a las cadenas se proporciona un segundo ejemplo de una operación que se ve
afectada por la forma en que se comparan las cadenas.
En el ejemplo siguiente se crea una instancia de un objeto Hashtable pasándole el objeto StringComparer devuelto
por la propiedad StringComparer.OrdinalIgnoreCase . Puesto que una clase StringComparer que se deriva de
StringComparer implementa la interfaz IEqualityComparer , su método GetHashCode se usa para calcular el código
hash de cadenas de la tabla hash.
const int initialTableCapacity = 100;
Hashtable h;

public void PopulateFileTable(string directory)


{
h = new Hashtable(initialTableCapacity,
StringComparer.OrdinalIgnoreCase);

foreach (string file in Directory.GetFiles(directory))


h.Add(file, File.GetCreationTime(file));
}

public void PrintCreationTime(string targetFile)


{
Object dt = h[targetFile];
if (dt != null)
{
Console.WriteLine("File {0} was created at time {1}.",
targetFile,
(DateTime) dt);
}
else
{
Console.WriteLine("File {0} does not exist.", targetFile);
}
}

Const initialTableCapacity As Integer = 100


Dim h As Hashtable

Public Sub PopulateFileTable(dir As String)


h = New Hashtable(initialTableCapacity, _
StringComparer.OrdinalIgnoreCase)

For Each filename As String In Directory.GetFiles(dir)


h.Add(filename, File.GetCreationTime(filename))
Next
End Sub

Public Sub PrintCreationTime(targetFile As String)


Dim dt As Object = h(targetFile)
If dt IsNot Nothing Then
Console.WriteLine("File {0} was created at {1}.", _
targetFile, _
CDate(dt))
Else
Console.WriteLine("File {0} does not exist.", targetFile)
End If
End Sub

Mostrar y conservar datos con formato


Cuando muestre a los usuarios datos que no sean de cadena, como números, y fechas y horas, asígneles formato
mediante la configuración de la referencia cultural del usuario. De forma predeterminada, los siguientes elementos
usan la referencia cultural del subproceso actual al dar formato a las operaciones:
Cadenas interpoladas compatibles con los compiladores C# y Visual Basic.
Operaciones de concatenación de cadenas que usan los operadores de concatenación C# o Visual Basic, o que
llaman al método String.Concat directamente.
El método String.Format .
Métodos ToString de los tipos numéricos y tipos de fecha y hora.
Para especificar explícitamente que se debe dar formato a una cadena mediante las convenciones de una
referencia cultural nombrada o la referencia cultural invariable, se puede hacer lo siguiente:
Cuando se usen los métodos String.Format y ToString , llame a una sobrecarga que tenga un parámetro
provider , como String.Format(IFormatProvider, String, Object[]) o DateTime.ToString(IFormatProvider), y
pásela a la propiedad CultureInfo.CurrentCulture, a una instancia CultureInfo que represente la referencia
cultural que se quiera o a la propiedad CultureInfo.InvariantCulture.
Para la concatenación de cadenas, no permita que el compilador realice ninguna de las conversiones
implícitas. En su lugar, realice una conversión explícita mediante una llamada a una sobrecarga ToString
que tenga un parámetro provider . Por ejemplo, el compilador utiliza implícitamente la referencia cultural
actual cuando convierte un valor Double en una cadena en el código siguiente:

string concat1 = "The amount is " + 126.03 + ".";


Console.WriteLine(concat1);

Dim concat1 As String = "The amount is " & 126.03 & "."
Console.WriteLine(concat1)

En su lugar, se puede especificar explícitamente la referencia cultural cuyas convenciones de formato se


usan en la conversión mediante una llamada al método Double.ToString(IFormatProvider), tal como hace el
código siguiente:

string concat2 = "The amount is " + 126.03.ToString(CultureInfo.InvariantCulture) + ".";


Console.WriteLine(concat2);

Dim concat2 As String = "The amount is " & 126.03.ToString(CultureInfo.InvariantCulture) & "."
Console.WriteLine(concat2)

Para la interpolación de cadenas, en lugar de asignar una cadena interpolada a una instancia String, asígnela
a un elemento FormattableString. Después, se puede llamar al método FormattableString.ToString() para
generar una cadena de resultado que refleje las convenciones de la referencia cultural actual, o bien puede
llamar al método FormattableString.ToString(IFormatProvider) para generar una cadena de resultado que
refleje las convenciones de la referencia cultural especificada. También se puede pasar la cadena que admite
formato al método estático FormattableString.Invariant con el fin de generar una cadena de resultado que
refleje las convenciones de la referencia cultural invariable. En el ejemplo siguiente se muestra este enfoque.
(La salida del ejemplo refleja una referencia cultural actual de en-US).
using System;
using System.Globalization;

class Program
{
static void Main()
{
Decimal value = 126.03m;
FormattableString amount = $"The amount is {value:C}";
Console.WriteLine(amount.ToString());
Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")));
Console.WriteLine(FormattableString.Invariant(amount));
}
}
// The example displays the following output:
// The amount is $126.03
// The amount is 126,03 €
// The amount is ¤126.03

Imports System.Globalization

Module Program
Sub Main()
Dim value As Decimal = 126.03
Dim amount As FormattableString = $"The amount is {value:C}"
Console.WriteLine(amount.ToString())
Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")))
Console.WriteLine(FormattableString.Invariant(amount))
End Sub
End Module
' The example displays the following output:
' The amount is $126.03
' The amount is 126,03 €
' The amount is ¤126.03

Puede conservar datos que no son de cadena como datos binarios o como datos con formato. Si decide guardarlos
como datos con formato, debe llamar a una sobrecarga del método de formato que incluya un parámetro
provider y pasarle la propiedad CultureInfo.InvariantCulture . La referencia cultural de todos los idiomas
proporciona un formato coherente para los datos con formato que es independiente de la referencia cultural y del
equipo. En cambio, si se conservan datos a los que se aplica formato con referencias culturales distintas de la
referencia cultural de todos los idiomas, se presentan varias limitaciones:
Es probable que los datos no puedan usarse si se recuperan en un sistema que tiene una referencia cultural
distinta, o si el usuario del sistema actual cambia la referencia cultural actual e intenta recuperar los datos.
Las propiedades de una referencia cultural en un equipo específico pueden diferir de los valores estándar. En
cualquier momento, un usuario puede personalizar la configuración de visualización que depende de la cultural.
Debido a esto, es posible que los datos con formato que se guardan en un sistema no sean legibles después de
que el usuario personalice la configuración de la referencia cultural. Es posible que la portabilidad de los datos
con formato entre equipos sea incluso más limitada.
Las normas internacionales, regionales o nacionales que rigen el formato de los números o las fechas y horas
cambian con el tiempo, y estos cambios se incorporan en las actualizaciones de los sistemas operativos
Windows. Cuando cambian las convenciones de formato, los datos a los que se aplicó formato usando las
convenciones anteriores pueden llegar a ser ilegibles.
En el ejemplo siguiente se muestra la portabilidad limitada que se deriva de usar el formato dependiente de la
referencia cultural para conservar los datos. En el ejemplo se guarda una matriz de valores de fecha y hora en un
archivo. Se les da formato con las convenciones de la referencia cultural Inglés (Estados Unidos). Después de que la
aplicación cambie la referencia cultural del subproceso actual a Francés (Suiza), intenta leer los valores guardados
usando las convenciones de formato de la referencia cultural actual. El intento de leer dos de los elementos de
datos genera una excepción FormatException y la matriz de fechas ahora contiene dos elementos incorrectos que
iguales a MinValue.

using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;

public class Example


{
private static string filename = @".\dates.dat";

public static void Main()


{
DateTime[] dates = { new DateTime(1758, 5, 6, 21, 26, 0),
new DateTime(1818, 5, 5, 7, 19, 0),
new DateTime(1870, 4, 22, 23, 54, 0),
new DateTime(1890, 9, 8, 6, 47, 0),
new DateTime(1905, 2, 18, 15, 12, 0) };
// Write the data to a file using the current culture.
WriteData(dates);
// Change the current culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH");
// Read the data using the current culture.
DateTime[] newDates = ReadData();
foreach (var newDate in newDates)
Console.WriteLine(newDate.ToString("g"));
}

private static void WriteData(DateTime[] dates)


{
StreamWriter sw = new StreamWriter(filename, false, Encoding.UTF8);
for (int ctr = 0; ctr < dates.Length; ctr++) {
sw.Write("{0}", dates[ctr].ToString("g", CultureInfo.CurrentCulture));
if (ctr < dates.Length - 1) sw.Write("|");
}
sw.Close();
}

private static DateTime[] ReadData()


{
bool exceptionOccurred = false;

// Read file contents as a single string, then split it.


StreamReader sr = new StreamReader(filename, Encoding.UTF8);
string output = sr.ReadToEnd();
sr.Close();

string[] values = output.Split( new char[] { '|' } );


DateTime[] newDates = new DateTime[values.Length];
for (int ctr = 0; ctr < values.Length; ctr++) {
try {
newDates[ctr] = DateTime.Parse(values[ctr], CultureInfo.CurrentCulture);
}
catch (FormatException) {
Console.WriteLine("Failed to parse {0}", values[ctr]);
exceptionOccurred = true;
}
}
if (exceptionOccurred) Console.WriteLine();
return newDates;
}
}
// The example displays the following output:
// Failed to parse 4/22/1870 11:54 PM
// Failed to parse 2/18/1905 3:12 PM
// Failed to parse 2/18/1905 3:12 PM
//
// 05.06.1758 21:26
// 05.05.1818 07:19
// 01.01.0001 00:00
// 09.08.1890 06:47
// 01.01.0001 00:00
// 01.01.0001 00:00
Imports System.Globalization
Imports System.IO
Imports System.Text
Imports System.Threading

Module Example
Private filename As String = ".\dates.dat"

Public Sub Main()


Dim dates() As Date = {#5/6/1758 9:26PM#, #5/5/1818 7:19AM#, _
#4/22/1870 11:54PM#, #9/8/1890 6:47AM#, _
#2/18/1905 3:12PM#}
' Write the data to a file using the current culture.
WriteData(dates)
' Change the current culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH")
' Read the data using the current culture.
Dim newDates() As Date = ReadData()
For Each newDate In newDates
Console.WriteLine(newDate.ToString("g"))
Next
End Sub

Private Sub WriteData(dates() As Date)


Dim sw As New StreamWriter(filename, False, Encoding.Utf8)
For ctr As Integer = 0 To dates.Length - 1
sw.Write("{0}", dates(ctr).ToString("g", CultureInfo.CurrentCulture))
If ctr < dates.Length - 1 Then sw.Write("|")
Next
sw.Close()
End Sub

Private Function ReadData() As Date()


Dim exceptionOccurred As Boolean = False

' Read file contents as a single string, then split it.


Dim sr As New StreamReader(filename, Encoding.Utf8)
Dim output As String = sr.ReadToEnd()
sr.Close()

Dim values() As String = output.Split({"|"c})


Dim newDates(values.Length - 1) As Date
For ctr As Integer = 0 To values.Length - 1
Try
newDates(ctr) = DateTime.Parse(values(ctr), CultureInfo.CurrentCulture)
Catch e As FormatException
Console.WriteLine("Failed to parse {0}", values(ctr))
exceptionOccurred = True
End Try
Next
If exceptionOccurred Then Console.WriteLine()
Return newDates
End Function
End Module
' The example displays the following output:
' Failed to parse 4/22/1870 11:54 PM
' Failed to parse 2/18/1905 3:12 PM
'
' 05.06.1758 21:26
' 05.05.1818 07:19
' 01.01.0001 00:00
' 09.08.1890 06:47
' 01.01.0001 00:00
' 01.01.0001 00:00
'
Pero si reemplaza la propiedad CultureInfo.CurrentCulture por CultureInfo.InvariantCulture en las llamadas a
DateTime.ToString(String, IFormatProvider) y a DateTime.Parse(String, IFormatProvider), los datos persistentes de
fecha y hora se restauran correctamente, como se muestra en la salida siguiente:

06.05.1758 21:26
05.05.1818 07:19
22.04.1870 23:54
08.09.1890 06:47
18.02.1905 15:12
Operaciones básicas de cadenas en .NET
16/09/2020 • 2 minutes to read • Edit Online

A menudo, las aplicaciones responden a los usuarios mediante la construcción de mensajes basados en los datos
proporcionados por el usuario. Por ejemplo, no es raro que los sitios web respondan a un usuario que acaba de
iniciar sesión con un saludo especializado que incluye el nombre del usuario.
Varios métodos de las clases System.String y System.Text.StringBuilder le permiten construir cadenas
personalizadas de forma dinámica para mostrarlas en la interfaz de usuario. Estos métodos también le ayudan a
realizar una serie de operaciones básicas de cadenas, como crear cadenas nuevas a partir de matrices de bytes,
comparar los valores de cadenas y modificar cadenas existentes.

Secciones relacionadas
Conversión de tipos en .NET
Se describe cómo convertir un tipo en otro.
Aplicar formato a tipos
Se describe cómo dar formato a cadenas mediante los especificadores de formato.
Creación de cadenas en .NET
16/09/2020 • 7 minutes to read • Edit Online

.NET Framework permite crear cadenas mediante asignaciones simples y además sobrecarga un constructor de
clases para admitir la creación de cadenas con una serie de parámetros distintos. .NET Framework también
proporciona varios métodos en la clase System.String que crean nuevos objetos de cadena al combinar varias
cadenas, matrices de cadenas u objetos.

Creación de cadenas mediante asignaciones


La manera más sencilla de crear un objeto String es asignar un literal de cadena a un objeto String.

Creación de cadenas mediante un constructor de clase


Puede usar las sobrecargas del constructor de clases String para crear cadenas a partir de matrices de caracteres.
También puede crear una nueva cadena al duplicar un determinado carácter un número especificado de veces.

Métodos que devuelven cadenas


En la tabla siguiente se enumeran varios métodos útiles que devuelven nuevos objetos de cadena.

N O M B RE DEL M ÉTO DO USA R

String.Format Compila una cadena con formato a partir de un conjunto de


objetos de entrada.

String.Concat Compila cadenas a partir de dos o más cadenas.

String.Join Compila una nueva cadena al combinar una matriz de


cadenas.

String.Insert Compila una nueva cadena al insertar una cadena en el índice


especificado de una cadena existente.

String.CopyTo Copia los caracteres especificados de una cadena en la


posición especificada de una matriz de caracteres.

Formato
Puede usar el método String.Format para crear cadenas con formato y concatenar cadenas que representan a
varios objetos. Este método convierte automáticamente cualquier objeto pasado en una cadena. Por ejemplo, si la
aplicación debe mostrar un valor Int32 y un valor DateTime al usuario, puede construir fácilmente una cadena
para representar estos valores con el método Format . Para más información sobre las convenciones de formato
usadas con este método, consulte la sección sobre formatos compuestos.
En el ejemplo siguiente se usa el método Format para crear una cadena que emplea una variable de entero.
int numberOfFleas = 12;
string miscInfo = String.Format("Your dog has {0} fleas. " +
"It is time to get a flea collar. " +
"The current universal date is: {1:u}.",
numberOfFleas, DateTime.Now);
Console.WriteLine(miscInfo);
// The example displays the following output:
// Your dog has 12 fleas. It is time to get a flea collar.
// The current universal date is: 2008-03-28 13:31:40Z.

Dim numberOfFleas As Integer = 12


Dim miscInfo As String = String.Format("Your dog has {0} fleas. " & _
"It is time to get a flea collar. " & _
"The current universal date is: {1:u}.", _
numberOfFleas, Date.Now)
Console.WriteLine(miscInfo)
' The example displays the following output:
' Your dog has 12 fleas. It is time to get a flea collar.
' The current universal date is: 2008-03-28 13:31:40Z.

En este ejemplo, DateTime.Now muestra la fecha y hora actuales en el formato especificado por la referencia
cultural asociada al subproceso actual.
Concat
El método String.Concat se puede usar para crear fácilmente un objeto de cadena a partir de dos o más objetos
existentes. Proporciona una manera independiente del lenguaje de concatenar cadenas. Este método acepta
cualquier clase que derive de System.Object . En el ejemplo siguiente se crea una cadena a partir de dos objetos
de cadena existentes y un carácter de separación.

string helloString1 = "Hello";


string helloString2 = "World!";
Console.WriteLine(String.Concat(helloString1, ' ', helloString2));
// The example displays the following output:
// Hello World!

Dim helloString1 As String = "Hello"


Dim helloString2 As String = "World!"
Console.WriteLine(String.Concat(helloString1, " "c, helloString2))
' The example displays the following output:
' Hello World!

Join
El método String.Join crea una cadena a partir de una matriz de cadenas y una cadena separadora. Este método
resulta útil si quiere concatenar varias cadenas para formar una lista quizás separada por una coma.
En el ejemplo siguiente se usa un espacio para enlazar una matriz de cadenas.

string[] words = {"Hello", "and", "welcome", "to", "my" , "world!"};


Console.WriteLine(String.Join(" ", words));
// The example displays the following output:
// Hello and welcome to my world!
Dim words() As String = {"Hello", "and", "welcome", "to", "my", "world!"}
Console.WriteLine(String.Join(" ", words))
' The example displays the following output:
' Hello and welcome to my world!

Insertar
El método String.Inser t crea una cadena al insertar una cadena en la posición especificada de otra cadena. Este
método usa un índice basado en cero. En el ejemplo siguiente se inserta una cadena en la quinta posición del índice
de MyString y se crea una nueva cadena con este valor.

string sentence = "Once a time.";


Console.WriteLine(sentence.Insert(4, " upon"));
// The example displays the following output:
// Once upon a time.

Dim sentence As String = "Once a time."


Console.WriteLine(sentence.Insert(4, " upon"))
' The example displays the following output:
' Once upon a time.

CopyTo
El método String.CopyTo copia partes de una cadena en una matriz de caracteres. Puede especificar el índice
inicial de la cadena y el número de caracteres que se va a copiar. Este método toma el índice de origen, una matriz
de caracteres, el índice de destino y el número de caracteres que se va a copiar. Todos los índices se basan en cero.
En el ejemplo siguiente se usa el método CopyTo para copiar los caracteres de la palabra "Hello" de un objeto de
cadena en la primera posición del índice de una matriz de caracteres.

string greeting = "Hello World!";


char[] charArray = {'W','h','e','r','e'};
Console.WriteLine("The original character array: {0}", new string(charArray));
greeting.CopyTo(0, charArray,0 ,5);
Console.WriteLine("The new character array: {0}", new string(charArray));
// The example displays the following output:
// The original character array: Where
// The new character array: Hello

Dim greeting As String = "Hello World!"


Dim charArray() As Char = {"W"c, "h"c, "e"c, "r"c, "e"c}
Console.WriteLine("The original character array: {0}", New String(charArray))
greeting.CopyTo(0, charArray, 0, 5)
Console.WriteLine("The new character array: {0}", New String(charArray))
' The example displays the following output:
' The original character array: Where
' The new character array: Hello

Vea también
Operaciones básicas de cadenas
Formatos compuestos
Recorte y eliminación de caracteres de cadenas en
.NET
16/09/2020 • 8 minutes to read • Edit Online

Si va a analizar una frase en las palabras que la forman, el resultado pueden ser palabras con espacios en blanco
delante y detrás. En este caso, puede usar uno de los métodos de recorte de la clase System.String para quitar
espacios u otros caracteres de una posición especificada de la cadena. En la tabla siguiente se describen los
métodos de recorte disponibles.

N O M B RE DEL M ÉTO DO USA R

String.Trim Quita del comienzo y del final de una cadena los espacios en
blanco o los caracteres especificados en una matriz de
caracteres.

String.TrimEnd Quita los caracteres especificados de una matriz de caracteres


del final de una cadena.

String.TrimStart Quita los caracteres especificados de una matriz de caracteres


del comienzo de una cadena.

String.Remove Quita un número especificado de caracteres de una posición


de índice especificada de una cadena.

Trim
Los espacios en blanco situados delante y detrás de una cadena se pueden quitar fácilmente con el método
String.Trim, como se muestra en el ejemplo siguiente.

String^ MyString = " Big ";


Console::WriteLine("Hello{0}World!", MyString);
String^ TrimString = MyString->Trim();
Console::WriteLine("Hello{0}World!", TrimString);
// The example displays the following output:
// Hello Big World!
// HelloBigWorld!

string MyString = " Big ";


Console.WriteLine("Hello{0}World!", MyString);
string TrimString = MyString.Trim();
Console.WriteLine("Hello{0}World!", TrimString);
// The example displays the following output:
// Hello Big World!
// HelloBigWorld!
Dim MyString As String = " Big "
Console.WriteLine("Hello{0}World!", MyString)
Dim TrimString As String = MyString.Trim()
Console.WriteLine("Hello{0}World!", TrimString)
' The example displays the following output:
' Hello Big World!
' HelloBigWorld!

También se pueden quitar del principio y el final de una cadena los caracteres especificados en una matriz de
caracteres. En el ejemplo siguiente se quitan los caracteres de espacio en blanco, los puntos y asteriscos.

using System;

public class Example


{
public static void Main()
{
String header = "* A Short String. *";
Console.WriteLine(header);
Console.WriteLine(header.Trim( new Char[] { ' ', '*', '.' } ));
}
}
// The example displays the following output:
// * A Short String. *
// A Short String

Module Example
Public Sub Main()
Dim header As String = "* A Short String. *"
Console.WriteLine(header)
Console.WriteLine(header.Trim({" "c, "*"c, "."c}))
End Sub
End Module
' The example displays the following output:
' * A Short String. *
' A Short String

TrimEnd
El método String.TrimEnd quita caracteres del final de una cadena, creando un nuevo objeto de cadena. A este
método se le pasa una matriz de caracteres para especificar los caracteres que se van a quitar. El orden de los
elementos en la matriz de caracteres no afecta a la operación de recorte. El recorte se detiene cuando se encuentra
un carácter no especificado en la matriz.
En el ejemplo siguiente se quitan las últimas letras de una cadena con el método TrimEnd . En este ejemplo, se
invierte la posición de los caracteres 'r' y 'W' para ilustrar que el orden de los caracteres en la matriz no tiene
importancia. Observe que este código quita la última palabra de MyString y parte de la primera.

String^ MyString = "Hello World!";


array<Char>^ MyChar = {'r','o','W','l','d','!',' '};
String^ NewString = MyString->TrimEnd(MyChar);
Console::WriteLine(NewString);
string MyString = "Hello World!";
char[] MyChar = {'r','o','W','l','d','!',' '};
string NewString = MyString.TrimEnd(MyChar);
Console.WriteLine(NewString);

Dim MyString As String = "Hello World!"


Dim MyChar() As Char = {"r", "o", "W", "l", "d", "!", " "}
Dim NewString As String = MyString.TrimEnd(MyChar)
Console.WriteLine(NewString)

Con este código se muestra He en la consola.


En el ejemplo siguiente se quita la última palabra de una cadena con el método TrimEnd . En este código hay una
coma después de Hello y, como la coma no está especificada en la matriz de caracteres que se deben recortar, la
operación se detiene en la coma.

String^ MyString = "Hello, World!";


array<Char>^ MyChar = {'r','o','W','l','d','!',' '};
String^ NewString = MyString->TrimEnd(MyChar);
Console::WriteLine(NewString);

string MyString = "Hello, World!";


char[] MyChar = {'r','o','W','l','d','!',' '};
string NewString = MyString.TrimEnd(MyChar);
Console.WriteLine(NewString);

Dim MyString As String = "Hello, World!"


Dim MyChar() As Char = {"r", "o", "W", "l", "d", "!", " "}
Dim NewString As String = MyString.TrimEnd(MyChar)
Console.WriteLine(NewString)

Con este código se muestra Hello, en la consola.

TrimStart
El método String.TrimStar t es parecido al método String.TrimEnd , con la diferencia de que crea una nueva
cadena quitando caracteres del comienzo de un objeto de cadena existente. Al método TrimStar t se le pasa una
matriz de caracteres para especificar los caracteres que se van a quitar. Lo mismo que en el método TrimEnd , el
orden de los elementos en la matriz de caracteres no afecta a la operación de recorte. El recorte se detiene cuando
se encuentra un carácter no especificado en la matriz.
En el siguiente ejemplo se quita la primera palabra de una cadena. En este ejemplo, se invierte la posición de los
caracteres 'l' y 'H' para ilustrar que el orden de los caracteres en la matriz no tiene importancia.

String^ MyString = "Hello World!";


array<Char>^ MyChar = {'e', 'H','l','o',' ' };
String^ NewString = MyString->TrimStart(MyChar);
Console::WriteLine(NewString);
string MyString = "Hello World!";
char[] MyChar = {'e', 'H','l','o',' ' };
string NewString = MyString.TrimStart(MyChar);
Console.WriteLine(NewString);

Dim MyString As String = "Hello World!"


Dim MyChar() As Char = {"e", "H", "l", "o", " "}
Dim NewString As String = MyString.TrimStart(MyChar)
Console.WriteLine(NewString)

Con este código se muestra World! en la consola.

Quitar
El método String.Remove quita un número especificado de caracteres, comenzando en una posición especificada de
una cadena existente. Este método utiliza un índice basado en cero.
En el ejemplo siguiente se quitan diez caracteres de una cadena, comenzando en la posición cinco de un índice de
base cero de la cadena.

String^ MyString = "Hello Beautiful World!";


Console::WriteLine(MyString->Remove(5,10));
// The example displays the following output:
// Hello World!

string MyString = "Hello Beautiful World!";


Console.WriteLine(MyString.Remove(5,10));
// The example displays the following output:
// Hello World!

Dim MyString As String = "Hello Beautiful World!"


Console.WriteLine(MyString.Remove(5, 10))
' The example displays the following output:
' Hello World!

Sustituya
También puede quitar un carácter o una subcadena de una cadena llamando al método String.Replace(String,
String) y especificando una cadena vacía (String.Empty) como reemplazo. En el ejemplo siguiente se quitan todas
las comas de una cadena.
using System;

public class Example


{
public static void Main()
{
String phrase = "a cold, dark night";
Console.WriteLine("Before: {0}", phrase);
phrase = phrase.Replace(",", "");
Console.WriteLine("After: {0}", phrase);
}
}
// The example displays the following output:
// Before: a cold, dark night
// After: a cold dark night

Module Example
Public Sub Main()
Dim phrase As String = "a cold, dark night"
Console.WriteLine("Before: {0}", phrase)
phrase = phrase.Replace(",", "")
Console.WriteLine("After: {0}", phrase)
End Sub
End Module
' The example displays the following output:
' Before: a cold, dark night
' After: a cold dark night

Vea también
Operaciones básicas de cadenas
Relleno de cadenas en .NET
16/09/2020 • 3 minutes to read • Edit Online

Use uno de los siguientes métodos String para crear una cadena que conste de una cadena original rellenada con
caracteres iniciales o finales hasta una longitud total especificada. El carácter de relleno puede ser un espacio o un
carácter especificado. La cadena resultante aparece alineada a la derecha o bien a la izquierda. Si la longitud de la
cadena original ya es igual o mayor que la longitud total deseada, los métodos de relleno devuelven la cadena
original sin modificarla. Para obtener más información, vea las secciones Devoluciones de las dos sobrecargas de
los métodos String.PadLeft y String.PadRight.

N O M B RE DEL M ÉTO DO USA R

String.PadLeft Rellena una cadena con caracteres iniciales hasta una longitud
total especificada.

String.PadRight Rellena una cadena con caracteres finales hasta una longitud
total especificada.

PadLeft
El método String.PadLeft crea una cadena mediante la concatenación de suficientes caracteres de relleno iniciales
con una cadena original para alcanzar la longitud total especificada. El método String.PadLeft(Int32) usa el espacio
en blanco como carácter de relleno y el método String.PadLeft(Int32, Char) le permite especificar su propio carácter
de relleno.
En el ejemplo de código siguiente se usa el método PadLeft para crear una cadena con una longitud de veinte
caracteres. En el ejemplo se muestra " --------Hello World! " en la consola.

String^ MyString = "Hello World!";


Console::WriteLine(MyString->PadLeft(20, '-'));

string MyString = "Hello World!";


Console.WriteLine(MyString.PadLeft(20, '-'));

Dim MyString As String = "Hello World!"


Console.WriteLine(MyString.PadLeft(20, "-"c))

PadRight
El método String.PadRight crea una cadena mediante la concatenación de suficientes caracteres de relleno finales
con una cadena original para alcanzar la longitud total especificada. El método String.PadRight(Int32) usa el espacio
en blanco como carácter de relleno y el método String.PadRight(Int32, Char) le permite especificar su propio
carácter de relleno.
En el ejemplo de código siguiente se usa el método PadRight para crear una cadena con una longitud de veinte
caracteres. En el ejemplo se muestra " Hello World!-------- " en la consola.
String^ MyString = "Hello World!";
Console::WriteLine(MyString->PadRight(20, '-'));

string MyString = "Hello World!";


Console.WriteLine(MyString.PadRight(20, '-'));

Dim MyString As String = "Hello World!"


Console.WriteLine(MyString.PadRight(20, "-"c))

Vea también
Operaciones básicas de cadenas
Comparación de cadenas en .NET
16/09/2020 • 13 minutes to read • Edit Online

.NET proporciona varios métodos para comparar los valores de cadenas. En la tabla siguiente se enumeran y
describen los métodos de comparación de valores.

N O M B RE DEL M ÉTO DO USA R

String.Compare Compara los valores de dos cadenas. Devuelve un valor


entero.

String.CompareOrdinal Compara dos cadenas independientemente de la referencia


cultural local. Devuelve un valor entero.

String.CompareTo Compara el objeto de cadena actual con otra cadena.


Devuelve un valor entero.

String.StartsWith Determina si una cadena comienza con la cadena que se pasa.


Devuelve un valor booleano.

String.EndsWith Determina si una cadena termina con la cadena que se pasa.


Devuelve un valor booleano.

String.Equals Determina si dos cadenas son iguales. Devuelve un valor


booleano.

String.IndexOf Devuelve la posición de índice de un carácter o cadena,


empezando en el comienzo de la cadena que se examina.
Devuelve un valor entero.

String.LastIndexOf Devuelve la posición de índice de un carácter o cadena,


empezando en el final de la cadena que se examina. Devuelve
un valor entero.

Comparar
El método String.Compare estático proporciona una manera de comparar dos cadenas exhaustivamente. En este
método se tiene en cuenta la referencia cultural. Esta función se puede usar para comparar dos cadenas o
subcadenas de dos cadenas. Además, se proporcionan sobrecargas para tener en cuenta o no las diferencias de
referencia cultural y de mayúsculas y minúsculas. En la tabla siguiente, se muestran los tres valores enteros que
este método puede devolver.

VA LO R DEVUELTO C O N DIC IÓ N

Un entero negativo La primera cadena precede a la segunda cadena en el criterio


de ordenación.

o bien

La primera cadena es null .


VA LO R DEVUELTO C O N DIC IÓ N

0 La primera y la segunda cadena son iguales.

o bien

Ambas cadenas son null .

Un entero positivo. La primera cadena sigue a la segunda cadena en el criterio de


ordenación.
o bien
o bien
1
La segunda cadena es null .

IMPORTANT
La finalidad principal del método String.Compare es que se utilice para la ordenación o clasificación de cadenas. El método
String.Compare no debe utilizarse para comprobar la igualdad (es decir, para buscar explícitamente un valor devuelto que sea
0 sin tener en cuenta si una cadena es menor o mayor que otra). En su lugar, para determinar si dos cadenas son iguales, use
el método String.Equals(String, String, StringComparison) .

En el ejemplo siguiente, se usa el método String.Compare para determinar los valores relativos de dos cadenas.

String^ string1 = "Hello World!";


Console::WriteLine(String::Compare(string1, "Hello World?"));

string string1 = "Hello World!";


Console.WriteLine(String.Compare(string1, "Hello World?"));

Dim string1 As String = "Hello World!"


Console.WriteLine(String.Compare(string1, "Hello World?"))

Con este ejemplo se muestra -1 en la consola.


En el ejemplo anterior se tienen en cuenta las referencias culturales de forma predeterminada. Para realizar una
comparación de cadenas que no tenga en cuenta las referencias culturales, utilice una sobrecarga del método
String.Compare ya que permite especificar la referencia cultural que se debe utilizar mediante un parámetro de
referencia cultural . Para obtener un ejemplo que muestra cómo utilizar el método String.Compare para realizar una
comparación de este tipo, consulte Realizar comparaciones de cadenas que no tienen en cuenta las referencias
culturales.

CompareOrdinal
El método String.CompareOrdinal compara dos objetos de cadena sin tener en cuenta la referencia cultural local.
Los valores devueltos de este método son idénticos a los que devolvía el método Compare en la tabla anterior.
IMPORTANT
La finalidad principal del método String.CompareOrdinal es que se utilice para la ordenación o clasificación de cadenas. El
método String.CompareOrdinal no debe utilizarse para comprobar la igualdad (es decir, para buscar explícitamente un valor
devuelto que sea 0 sin tener en cuenta si una cadena es menor o mayor que otra). En su lugar, para determinar si dos
cadenas son iguales, use el método String.Equals(String, String, StringComparison) .

En el ejemplo siguiente se usa el método CompareOrdinal para comparar los valores de dos cadenas.

String^ string1 = "Hello World!";


Console::WriteLine(String::CompareOrdinal(string1, "hello world!"));

string string1 = "Hello World!";


Console.WriteLine(String.CompareOrdinal(string1, "hello world!"));

Dim string1 As String = "Hello World!"


Console.WriteLine(String.CompareOrdinal(string1, "hello world!"))

Con este ejemplo se muestra -32 en la consola.

CompareTo
El método String.CompareTo compara la cadena que encapsula el objeto de cadena actual con otra cadena u objeto.
Los valores devueltos de este método son idénticos a los que devolvía el método String.Compare en la tabla
anterior.

IMPORTANT
La finalidad principal del método String.CompareTo es que se utilice para la ordenación o clasificación de cadenas. El método
String.CompareTo no debe utilizarse para comprobar la igualdad (es decir, para buscar explícitamente un valor devuelto que
sea 0 sin tener en cuenta si una cadena es menor o mayor que otra). En su lugar, para determinar si dos cadenas son iguales,
use el método String.Equals(String, String, StringComparison) .

En el ejemplo siguiente se usa el método String.CompareTo para comparar los objetos string1 y string2 .

String^ string1 = "Hello World";


String^ string2 = "Hello World!";
int MyInt = string1->CompareTo(string2);
Console::WriteLine( MyInt );

string string1 = "Hello World";


string string2 = "Hello World!";
int MyInt = string1.CompareTo(string2);
Console.WriteLine( MyInt );

Dim string1 As String = "Hello World"


Dim string2 As String = "Hello World!"
Dim MyInt As Integer = string1.CompareTo(string2)
Console.WriteLine(MyInt)
Con este ejemplo se muestra -1 en la consola.
Todas las sobrecargas del método String.CompareTo realizan comparaciones que tienen en cuenta las referencias
culturales y las mayúsculas y minúsculas de manera predeterminada. No se proporcionan sobrecargas de este
método que permitan realizar una comparación que no tenga en cuenta las referencias culturales Para lograr
claridad en el código, se recomienda utilizar el método String.Compare en su lugar, especificando
CultureInfo.CurrentCulture para las operaciones que tienen en cuenta la referencia cultural o
CultureInfo.InvariantCulture para las operaciones que no la tienen en cuenta. Para obtener ejemplos que muestran
cómo utilizar el método String.Compare para realizar comparaciones de este tipo, vea Realizar comparaciones de
cadenas que no tienen en cuenta las referencias culturales.

Es igual a
El método String.Equals puede determinar con facilidad si dos cadenas son iguales. Este método distingue entre
mayúsculas y minúsculas y devuelve un valor booleano True o False . Se puede usar desde una clase existente,
como se muestra en el siguiente ejemplo. En el ejemplo siguiente se usa el método Equals para determinar si un
objeto de cadena contiene la frase "Hello World".

String^ string1 = "Hello World";


Console::WriteLine(string1->Equals("Hello World"));

string string1 = "Hello World";


Console.WriteLine(string1.Equals("Hello World"));

Dim string1 As String = "Hello World"


Console.WriteLine(string1.Equals("Hello World"))

Con este ejemplo se muestra True en la consola.


Este método se puede usar también como método estático. En el ejemplo siguiente se comparan dos objetos de
cadena utilizando un método estático.

String^ string1 = "Hello World";


String^ string2 = "Hello World";
Console::WriteLine(String::Equals(string1, string2));

string string1 = "Hello World";


string string2 = "Hello World";
Console.WriteLine(String.Equals(string1, string2));

Dim string1 As String = "Hello World"


Dim string2 As String = "Hello World"
Console.WriteLine(String.Equals(string1, string2))

Con este ejemplo se muestra True en la consola.

StartsWith y EndsWith
El método String.Star tsWith se puede usar para determinar si un objeto de cadena comienza con los mismos
caracteres que forman otra cadena. Este método distingue entre mayúsculas y minúsculas y devuelve true si el
objeto de cadena actual comienza con la cadena que se pasa y false si no lo hace. En el ejemplo siguiente se usa
este método para determinar si un objeto de cadena comienza con "Hello".

String^ string1 = "Hello World";


Console::WriteLine(string1->StartsWith("Hello"));

string string1 = "Hello World";


Console.WriteLine(string1.StartsWith("Hello"));

Dim string1 As String = "Hello World!"


Console.WriteLine(string1.StartsWith("Hello"))

Con este ejemplo se muestra True en la consola.


El método String.EndsWith compara la cadena que se pasa con los caracteres del final del objeto de cadena
actual. También devuelve un valor booleano. En el ejemplo siguiente se comprueba el final de una cadena con el
método EndsWith .

String^ string1 = "Hello World";


Console::WriteLine(string1->EndsWith("Hello"));

string string1 = "Hello World";


Console.WriteLine(string1.EndsWith("Hello"));

Dim string1 As String = "Hello World!"


Console.WriteLine(string1.EndsWith("Hello"))

Con este ejemplo se muestra False en la consola.

IndexOf y LastIndexOf
El método String.IndexOf se puede usar para determinar la posición de la primera aparición de un carácter
concreto dentro de una cadena. Este método, que distingue entre mayúsculas y minúsculas, empieza a contar desde
el comienzo de una cadena y devuelve la posición del carácter que se pasa utilizando un índice de base cero. Si no
encuentra el carácter, se devuelve un valor de –1.
En el ejemplo siguiente se usa el método IndexOf para buscar la primera aparición del carácter ' l ' en una cadena.

String^ string1 = "Hello World";


Console::WriteLine(string1->IndexOf('l'));

string string1 = "Hello World";


Console.WriteLine(string1.IndexOf('l'));

Dim string1 As String = "Hello World!"


Console.WriteLine(string1.IndexOf("l"))

Con este ejemplo se muestra 2 en la consola.


El método String.LastIndexOf es parecido al método String.IndexOf , con la diferencia de que devuelve la
posición de la última instancia de un carácter concreto en una cadena. Distingue mayúsculas y minúsculas y utiliza
un índice de base cero.
En el ejemplo siguiente se usa el método LastIndexOf para buscar la última aparición del carácter ' l ' en una
cadena.

String^ string1 = "Hello World";


Console::WriteLine(string1->LastIndexOf('l'));

string string1 = "Hello World";


Console.WriteLine(string1.LastIndexOf('l'));

Dim string1 As String = "Hello World!"


Console.WriteLine(string1.LastIndexOf("l"))

Con este ejemplo se muestra 9 en la consola.


Los dos métodos resultan útiles si se usan junto con el método String.Remove . Se pueden usar los métodos
IndexOf o LastIndexOf para recuperar la posición de un carácter y, a continuación, proporcionar esa posición al
método Remove para quitar un carácter o una palabra que comience por ese carácter.

Vea también
Operaciones básicas de cadenas
Realizar operaciones de cadenas que no distinguen entre referencias culturales
Ordenación de tablas de peso (para .NET en Windows)
Tabla de elementos de intercalación predeterminada Unicode (para .NET Core en macOS y Linux)
Cambio de mayúsculas y minúsculas en .NET
16/09/2020 • 8 minutes to read • Edit Online

Si escribe una aplicación que acepta la entrada de un usuario, nunca podrá estar seguro de si usará mayúsculas o
minúsculas para escribir los datos. Normalmente querrá que las cadenas usen mayúsculas y minúsculas de forma
coherente, especialmente si se van a mostrar en la interfaz de usuario. En la tabla siguiente se describen tres
métodos para cambiar las mayúsculas y minúsculas. Los dos primeros métodos proporcionan una sobrecarga que
acepta una referencia cultural.

N O M B RE DEL M ÉTO DO USA R

String.ToUpper Convierte todos los caracteres de una cadena a mayúsculas.

String.ToLower Convierte todos los caracteres de una cadena a minúsculas.

TextInfo.ToTitleCase Convierte una cadena a mayúsculas de tipo título.

WARNING
Tenga en cuenta que los métodos String.ToUpper y String.ToLower no deben usarse para convertir cadenas para compararlas
ni para comprobar su igualdad. Para más información, vea la sección Comparar cadenas con mayúsculas y minúsculas
mezcladas.

Comparación de cadenas con mayúsculas y minúsculas mezcladas


Para comparar cadenas con mayúsculas y minúsculas mezcladas para determinar su orden, llame a una de las
sobrecargas del método String.CompareTo con un parámetro comparisonType y proporcione un valor
StringComparison.CurrentCultureIgnoreCase, StringComparison.InvariantCultureIgnoreCase o
StringComparison.OrdinalIgnoreCase para el argumento comparisonType . Para realizar una comparación usando
una referencia cultural específica que no sea la referencia cultural actual, llame a una sobrecarga del método
String.CompareTo con los parámetros culture y options , y proporcione el valor CompareOptions.IgnoreCase
como el argumento options .
Para comparar cadenas con mayúsculas y minúsculas mezcladas para determinar si son iguales, llame a una de las
sobrecargas del método String.Equals con un parámetro comparisonType y proporcione un valor
StringComparison.CurrentCultureIgnoreCase, StringComparison.InvariantCultureIgnoreCase o
StringComparison.OrdinalIgnoreCase para el argumento comparisonType .
Para obtener más información, consulte Procedimientos recomendados para el uso de cadenas.

ToUpper
El método String.ToUpper convierte todos los caracteres de una cadena a mayúsculas. En el siguiente ejemplo, se
convierte la cadena "Hello World!" de mayúsculas y minúsculas mezcladas a mayúsculas.

string properString = "Hello World!";


Console.WriteLine(properString.ToUpper());
// This example displays the following output:
// HELLO WORLD!
Dim MyString As String = "Hello World!"
Console.WriteLine(MyString.ToUpper())
' This example displays the following output:
' HELLO WORLD!

El ejemplo anterior tiene en cuenta la referencia cultural de forma predeterminada; aplica las convenciones de
mayúsculas y minúsculas de la referencia cultural actual. Para realizar un cambio de mayúsculas y minúsculas sin
tener en cuenta la referencia cultural o para aplicar las convenciones de mayúsculas y minúsculas de una referencia
cultural determinada, use la sobrecarga del método String.ToUpper(CultureInfo) y proporcione un valor
CultureInfo.InvariantCulture o un objeto System.Globalization.CultureInfo que representa la referencia cultural
especificada al parámetro culture. Para obtener un ejemplo que muestra cómo usar el método ToUpper para
realizar un cambio de mayúsculas y minúsculas sin tener en cuenta la referencia cultural, consulte Realizar cambios
de mayúsculas y minúsculas que no tienen en cuenta las referencias culturales.

ToLower
El método String.ToLower es similar al método anterior, pero en su lugar convierte todos los caracteres de una
cadena a minúsculas. En el siguiente ejemplo, se convierte la cadena "Hello World!" en minúsculas.

string properString = "Hello World!";


Console.WriteLine(properString.ToLower());
// This example displays the following output:
// hello world!

Dim MyString As String = "Hello World!"


Console.WriteLine(MyString.ToLower())
' This example displays the following output:
' hello world!

El ejemplo anterior tiene en cuenta la referencia cultural de forma predeterminada; aplica las convenciones de
mayúsculas y minúsculas de la referencia cultural actual. Para realizar un cambio de mayúsculas y minúsculas sin
tener en cuenta la referencia cultural o para aplicar las convenciones de mayúsculas y minúsculas de una referencia
cultural determinada, use la sobrecarga del método String.ToLower(CultureInfo) y proporcione un valor
CultureInfo.InvariantCulture o un objeto System.Globalization.CultureInfo que representa la referencia cultural
especificada al parámetro culture. Para obtener un ejemplo que muestra cómo usar el método
ToLower(CultureInfo) para realizar un cambio de mayúsculas y minúsculas sin tener en cuenta la referencia cultural,
consulte Realizar cambios de mayúsculas y minúsculas que no tienen en cuenta las referencias culturales.

ToTitleCase
El método TextInfo.ToTitleCase convierte el primer carácter de cada palabra a mayúsculas y el resto de los
caracteres a minúsculas. Sin embargo, se da por hecho que las palabras que están completamente en mayúsculas
son siglas y no se convierten.
El método TextInfo.ToTitleCase tiene en cuenta la referencia cultural; es decir, usa las convenciones de mayúsculas y
minúsculas de una referencia cultural determinada. Para llamar al método, recupere primero el objeto TextInfo que
representa las convenciones de mayúsculas y minúsculas de la referencia cultural determinada a partir de la
propiedad CultureInfo.TextInfo de una referencia cultural determinada.
En el ejemplo siguiente, se pasa cada cadena de una matriz al método TextInfo.ToTitleCase. Las cadenas incluyen
cadenas de título correctas así como acrónimos. Las cadenas se convierten a mayúsculas de tipo título usando las
convenciones de mayúsculas y minúsculas de la referencia cultural Inglés (Estados Unidos).
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
string[] values = { "a tale of two cities", "gROWL to the rescue",
"inside the US government", "sports and MLB baseball",
"The Return of Sherlock Holmes", "UNICEF and children"};

TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
foreach (var value in values)
Console.WriteLine("{0} --> {1}", value, ti.ToTitleCase(value));
}
}
// The example displays the following output:
// a tale of two cities --> A Tale Of Two Cities
// gROWL to the rescue --> Growl To The Rescue
// inside the US government --> Inside The US Government
// sports and MLB baseball --> Sports And MLB Baseball
// The Return of Sherlock Holmes --> The Return Of Sherlock Holmes
// UNICEF and children --> UNICEF And Children

Imports System.Globalization

Module Example
Public Sub Main()
Dim values() As String = {"a tale of two cities", "gROWL to the rescue",
"inside the US government", "sports and MLB baseball",
"The Return of Sherlock Holmes", "UNICEF and children"}

Dim ti As TextInfo = CultureInfo.CurrentCulture.TextInfo


For Each value In values
Console.WriteLine("{0} --> {1}", value, ti.ToTitleCase(value))
Next
End Sub
End Module
' The example displays the following output:
' a tale of two cities --> A Tale Of Two Cities
' gROWL to the rescue --> Growl To The Rescue
' inside the US government --> Inside The US Government
' sports and MLB baseball --> Sports And MLB Baseball
' The Return of Sherlock Holmes --> The Return Of Sherlock Holmes
' UNICEF and children --> UNICEF And Children

Recuerde que, aunque tiene en cuenta la referencia cultural, el método TextInfo.ToTitleCase no proporciona reglas
de mayúsculas y minúsculas lingüísticamente correctas. En el ejemplo anterior, el método convierte "a tale of two
cities" en "A Tale Of Two Cities". Sin embargo, el uso lingüísticamente correcto de las mayúsculas y minúsculas de
título para la referencia cultural en-US es "A Tale of Two Cities".

Vea también
Operaciones básicas de cadenas
Realizar operaciones de cadenas que no distinguen entre referencias culturales
Utilizar la clase StringBuilder en .NET
16/09/2020 • 12 minutes to read • Edit Online

El objeto String es inmutable. Cada vez que se usa uno de los métodos de la clase System.String, se crea un objeto
de cadena en la memoria, lo que requiere una nueva asignación de espacio para ese objeto. En las situaciones en
las que es necesario realizar modificaciones repetidas en una cadena, la sobrecarga asociada a la creación de un
objeto String puede ser costosa. La clase System.Text.StringBuilder se puede usar para modificar una cadena sin
crear un objeto. Por ejemplo, el uso de la clase StringBuilder puede mejorar el rendimiento al concatenar muchas
cadenas en un bucle.

Importar el espacio de nombres System.Text


La clase StringBuilder se encuentra en el espacio de nombres System.Text. Para evitar proporcionar un nombre de
tipo completo en el código, se puede importar el espacio de nombres System.Text:

using namespace System;


using namespace System::Text;

using System;
using System.Text;

Imports System.Text

Crear instancias de un objeto StringBuilder


Para crear una instancia de la clase StringBuilder, inicialice la variable con uno de los métodos de constructor
sobrecargado, como se muestra en el ejemplo siguiente.

StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!");

StringBuilder myStringBuilder = new StringBuilder("Hello World!");

Dim myStringBuilder As New StringBuilder("Hello World!")

Configurar la capacidad y la longitud


Aunque StringBuilder es un objeto dinámico que permite expandir el número de caracteres de la cadena que
encapsula, se puede especificar un valor para el número máximo de caracteres que puede contener. Este valor se
conoce como la capacidad del objeto y no debe confundirse con la longitud de la cadena que el objeto StringBuilder
actual contiene. Por ejemplo, puede crear una instancia de la clase StringBuilder con la cadena "Hello", que tiene una
longitud de 5, y especificar que el objeto tiene una capacidad máxima de 25. Al modificar StringBuilder, este no
reasigna el tamaño para sí mismo hasta que se alcanza la capacidad. Cuando esto sucede, el nuevo espacio se
asigna automáticamente y se duplica la capacidad. La capacidad de la clase StringBuilder se puede especificar con
uno de los constructores sobrecargados. En el ejemplo siguiente se especifica que el objeto myStringBuilder se
puede expandir hasta un máximo de 25 espacios.

StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!", 25);

StringBuilder myStringBuilder = new StringBuilder("Hello World!", 25);

Dim myStringBuilder As New StringBuilder("Hello World!", 25)

Además, se puede usar la propiedad de lectura y escritura Capacity para establecer la longitud máxima del objeto.
En el ejemplo siguiente se usa la propiedad Capacity para definir la longitud máxima del objeto.

myStringBuilder->Capacity = 25;

myStringBuilder.Capacity = 25;

myStringBuilder.Capacity = 25

El método EnsureCapacity se puede usar para comprobar la capacidad del objeto StringBuilder actual. Si la
capacidad es mayor que el valor transmitido, no se realiza ningún cambio, pero si es menor que este, la capacidad
actual se cambia para que coincida con el valor en cuestión.
También se puede ver o establecer la propiedad Length. Si la propiedad Length se establece en un valor mayor que
el de la propiedad Capacity , la propiedad Capacity se cambia automáticamente al mismo valor de la propiedad
Length . Si la propiedad Length se establece en un valor menor que la longitud de la cadena de StringBuilder
actual, se acorta la cadena.

Modificar la cadena StringBuilder


En la tabla siguiente se enumeran los métodos que se pueden usar para modificar el contenido de StringBuilder .

N O M B RE DEL M ÉTO DO USA R

StringBuilder.Append Anexa información al final del objeto StringBuilder actual.

StringBuilder.AppendFormat Reemplaza a un especificador de formato que se pasa en una


cadena con texto con formato

StringBuilder.Insert Inserta una cadena o un objeto en el índice especificado del


elemento StringBuilder actual.

StringBuilder.Remove Quita el número de caracteres especificado del objeto


StringBuilder actual.

StringBuilder.Replace Reemplaza todas las apariciones de un carácter o cadena


especificados en la instancia de StringBuilder por otro
carácter o cadena especificados.

Anexar
El método Append se puede usar para agregar texto o la representación de cadena de un objeto al final de una
cadena representada por el objeto StringBuilder actual. En el ejemplo siguiente, se inicializa StringBuilder en
"Hello World" y, después, se anexa texto al final del objeto. El espacio se asigna automáticamente según sea
necesario.

StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!");


myStringBuilder->Append(" What a beautiful day.");
Console::WriteLine(myStringBuilder);
// The example displays the following output:
// Hello World! What a beautiful day.

StringBuilder myStringBuilder = new StringBuilder("Hello World!");


myStringBuilder.Append(" What a beautiful day.");
Console.WriteLine(myStringBuilder);
// The example displays the following output:
// Hello World! What a beautiful day.

Dim myStringBuilder As New StringBuilder("Hello World!")


myStringBuilder.Append(" What a beautiful day.")
Console.WriteLine(myStringBuilder)
' The example displays the following output:
' Hello World! What a beautiful day.

AppendFormat
El método StringBuilder.AppendFormat agrega texto al final del objeto StringBuilder. Admite la característica de
formatos compuestos (para obtener más información, consulte Formatos compuestos) mediante la llamada a la
implementación de IFormattable del objeto u objetos a los que se va a dar formato. Por tanto, acepta las cadenas de
formato estándar para valores numéricos, de fecha y hora y de enumeración; las cadenas de formato personalizado
para valores numéricos y de fecha y hora; y las cadenas de formato definidas para los tipos personalizados. (Para
obtener información acerca del formato, consulte Aplicar formato a tipos.) Este método se puede usar para
personalizar el formato de las variables y anexar esos valores a StringBuilder. En el ejemplo siguiente se usa el
método AppendFormat para colocar un valor entero con formato de valor de divisa al final de un objeto
StringBuilder.

int MyInt = 25;


StringBuilder^ myStringBuilder = gcnew StringBuilder("Your total is ");
myStringBuilder->AppendFormat("{0:C} ", MyInt);
Console::WriteLine(myStringBuilder);
// The example displays the following output:
// Your total is $25.00

int MyInt = 25;


StringBuilder myStringBuilder = new StringBuilder("Your total is ");
myStringBuilder.AppendFormat("{0:C} ", MyInt);
Console.WriteLine(myStringBuilder);
// The example displays the following output:
// Your total is $25.00

Dim MyInt As Integer = 25


Dim myStringBuilder As New StringBuilder("Your total is ")
myStringBuilder.AppendFormat("{0:C} ", MyInt)
Console.WriteLine(myStringBuilder)
' The example displays the following output:
' Your total is $25.00

Insertar
El método Insert agrega una cadena o un objeto en una posición especificada del objeto StringBuilder actual. En el
ejemplo siguiente se usa este método para insertar una palabra en la sexta posición de un objeto StringBuilder.

StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!");


myStringBuilder->Insert(6,"Beautiful ");
Console::WriteLine(myStringBuilder);
// The example displays the following output:
// Hello Beautiful World!

StringBuilder myStringBuilder = new StringBuilder("Hello World!");


myStringBuilder.Insert(6,"Beautiful ");
Console.WriteLine(myStringBuilder);
// The example displays the following output:
// Hello Beautiful World!

Dim myStringBuilder As New StringBuilder("Hello World!")


myStringBuilder.Insert(6, "Beautiful ")
Console.WriteLine(myStringBuilder)
' The example displays the following output:
' Hello Beautiful World!

Quitar
El método Remove se puede usar para quitar un número de caracteres especificado del objeto StringBuilder, a
partir de un índice de base cero definido. En el ejemplo siguiente se usa el método Remove para acortar un objeto
StringBuilder.

StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!");


myStringBuilder->Remove(5,7);
Console::WriteLine(myStringBuilder);
// The example displays the following output:
// Hello

StringBuilder myStringBuilder = new StringBuilder("Hello World!");


myStringBuilder.Remove(5,7);
Console.WriteLine(myStringBuilder);
// The example displays the following output:
// Hello

Dim myStringBuilder As New StringBuilder("Hello World!")


myStringBuilder.Remove(5, 7)
Console.WriteLine(myStringBuilder)
' The example displays the following output:
' Hello

Sustituya
El método Replace se puede usar para reemplazar caracteres del objeto StringBuilder por otro carácter
especificado. En el ejemplo siguiente se usa el método Replace para buscar todas las instancias del carácter de
signo de exclamación (!) y reemplazarlas por el carácter de signo de interrogación (?) en un objeto StringBuilder.
StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!");
myStringBuilder->Replace('!', '?');
Console::WriteLine(myStringBuilder);
// The example displays the following output:
// Hello World?

StringBuilder myStringBuilder = new StringBuilder("Hello World!");


myStringBuilder.Replace('!', '?');
Console.WriteLine(myStringBuilder);
// The example displays the following output:
// Hello World?

Dim myStringBuilder As New StringBuilder("Hello World!")


myStringBuilder.Replace("!"c, "?"c)
Console.WriteLine(myStringBuilder)
' The example displays the following output:
' Hello World?

Convertir un objeto StringBuilder en String


Debe convertir primero el objeto StringBuilder en un objeto String para poder pasar la cadena representada por el
objeto StringBuilder a un método con un parámetro String o mostrarla en la interfaz de usuario. Para hacer esta
conversión, llame al método StringBuilder.ToString. En el ejemplo siguiente se llama a varios métodos de
StringBuilder y después se llama al método StringBuilder.ToString() para mostrar la cadena.

using System;
using System.Text;

public class Example


{
public static void Main()
{
StringBuilder sb = new StringBuilder();
bool flag = true;
string[] spellings = { "recieve", "receeve", "receive" };
sb.AppendFormat("Which of the following spellings is {0}:", flag);
sb.AppendLine();
for (int ctr = 0; ctr <= spellings.GetUpperBound(0); ctr++) {
sb.AppendFormat(" {0}. {1}", ctr, spellings[ctr]);
sb.AppendLine();
}
sb.AppendLine();
Console.WriteLine(sb.ToString());
}
}
// The example displays the following output:
// Which of the following spellings is True:
// 0. recieve
// 1. receeve
// 2. receive
Imports System.Text

Module Example
Public Sub Main()
Dim sb As New StringBuilder()
Dim flag As Boolean = True
Dim spellings() As String = {"recieve", "receeve", "receive"}
sb.AppendFormat("Which of the following spellings is {0}:", flag)
sb.AppendLine()
For ctr As Integer = 0 To spellings.GetUpperBound(0)
sb.AppendFormat(" {0}. {1}", ctr, spellings(ctr))
sb.AppendLine()
Next
sb.AppendLine()
Console.WriteLine(sb.ToString())
End Sub
End Module
' The example displays the following output:
' Which of the following spellings is True:
' 0. recieve
' 1. receeve
' 2. receive

Vea también
System.Text.StringBuilder
Operaciones básicas de cadenas
Aplicación de formato a tipos
Procedimiento para realizar manipulaciones de
cadena básicas en .NET
16/09/2020 • 5 minutes to read • Edit Online

En el ejemplo siguiente, se usan algunos de los métodos descritos en los temas de Operaciones básicas de cadenas
para construir una clase que realice manipulaciones de cadena de una manera que podría encontrarse en una
aplicación real. La clase MailToData almacena el nombre y dirección de los individuos en propiedades distintas y
proporciona una forma de combinar los campos City , State y Zip en una única cadena para mostrar al usuario.
Además, la clase permite al usuario que escriba la información sobre la ciudad, estado y código postal como una
sola cadena; de forma automática, la aplicación analiza la cadena única y escribe la información adecuada en la
propiedad correspondiente.
Para simplificar, en este ejemplo se usa una aplicación de consola con una interfaz de línea de comandos.

Ejemplo
using System;

class MainClass
{
static void Main()
{
MailToData MyData = new MailToData();

Console.Write("Enter Your Name: ");


MyData.Name = Console.ReadLine();
Console.Write("Enter Your Address: ");
MyData.Address = Console.ReadLine();
Console.Write("Enter Your City, State, and ZIP Code separated by spaces: ");
MyData.CityStateZip = Console.ReadLine();
Console.WriteLine();

if (MyData.Validated) {
Console.WriteLine("Name: {0}", MyData.Name);
Console.WriteLine("Address: {0}", MyData.Address);
Console.WriteLine("City: {0}", MyData.City);
Console.WriteLine("State: {0}", MyData.State);
Console.WriteLine("Zip: {0}", MyData.Zip);

Console.WriteLine("\nThe following address will be used:");


Console.WriteLine(MyData.Address);
Console.WriteLine(MyData.CityStateZip);
}
}
}

public class MailToData


{
string name = "";
string address = "";
string citystatezip = "";
string city = "";
string state = "";
string zip = "";
bool parseSucceeded = false;

public string Name


{
{
get{return name;}
set{name = value;}
}

public string Address


{
get{return address;}
set{address = value;}
}

public string CityStateZip


{
get {
return String.Format("{0}, {1} {2}", city, state, zip);
}
set {
citystatezip = value.Trim();
ParseCityStateZip();
}
}

public string City


{
get{return city;}
set{city = value;}
}

public string State


{
get{return state;}
set{state = value;}
}

public string Zip


{
get{return zip;}
set{zip = value;}
}

public bool Validated


{
get { return parseSucceeded; }
}

private void ParseCityStateZip()


{
string msg = "";
const string msgEnd = "\nYou must enter spaces between city, state, and zip code.\n";

// Throw a FormatException if the user did not enter the necessary spaces
// between elements.
try
{
// City may consist of multiple words, so we'll have to parse the
// string from right to left starting with the zip code.
int zipIndex = citystatezip.LastIndexOf(" ");
if (zipIndex == -1) {
msg = "\nCannot identify a zip code." + msgEnd;
throw new FormatException(msg);
}
zip = citystatezip.Substring(zipIndex + 1);

int stateIndex = citystatezip.LastIndexOf(" ", zipIndex - 1);


if (stateIndex == -1) {
msg = "\nCannot identify a state." + msgEnd;
throw new FormatException(msg);
}
state = citystatezip.Substring(stateIndex + 1, zipIndex - stateIndex - 1);
state = state.ToUpper();
state = state.ToUpper();

city = citystatezip.Substring(0, stateIndex);


if (city.Length == 0) {
msg = "\nCannot identify a city." + msgEnd;
throw new FormatException(msg);
}
parseSucceeded = true;
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
}
}

private string ReturnCityStateZip()


{
// Make state uppercase.
state = state.ToUpper();

// Put the value of city, state, and zip together in the proper manner.
string MyCityStateZip = String.Concat(city, ", ", state, " ", zip);

return MyCityStateZip;
}
}

Class MainClass
Public Shared Sub Main()
Dim MyData As New MailToData()

Console.Write("Enter Your Name: ")


MyData.Name = Console.ReadLine()
Console.Write("Enter Your Address: ")
MyData.Address = Console.ReadLine()
Console.Write("Enter Your City, State, and ZIP Code separated by spaces: ")
MyData.CityStateZip = Console.ReadLine()
Console.WriteLine()

If MyData.Validated Then
Console.WriteLine("Name: {0}", MyData.Name)
Console.WriteLine("Address: {0}", MyData.Address)
Console.WriteLine("City: {0}", MyData.City)
Console.WriteLine("State: {0}", MyData.State)
Console.WriteLine("ZIP Code: {0}", MyData.Zip)

Console.WriteLine("The following address will be used:")


Console.WriteLine(MyData.Address)
Console.WriteLine(MyData.CityStateZip)
End If
End Sub
End Class

Public Class MailToData


Private strName As String = ""
Private strAddress As String = ""
Private strCityStateZip As String = ""
Private strCity As String = ""
Private strState As String = ""
Private strZip As String = ""
Private parseSucceeded As Boolean = False

Public Property Name() As String


Get
Return strName
End Get
Set
strName = value
strName = value
End Set
End Property

Public Property Address() As String


Get
Return strAddress
End Get
Set
strAddress = value
End Set
End Property

Public Property CityStateZip() As String


Get
Return String.Format("{0}, {1} {2}", strCity, strState, strZip)
End Get
Set
strCityStateZip = value.Trim()
ParseCityStateZip()
End Set
End Property

Public Property City() As String


Get
Return strCity
End Get
Set
strCity = value
End Set
End Property

Public Property State() As String


Get
Return strState
End Get
Set
strState = value
End Set
End Property

Public Property Zip() As String


Get
Return strZip
End Get
Set
strZip = value
End Set
End Property

Public ReadOnly Property Validated As Boolean


Get
Return parseSucceeded
End Get
End Property

Private Sub ParseCityStateZip()


Dim msg As String = Nothing
Const msgEnd As String = vbCrLf +
"You must enter spaces between city, state, and zip code." +
vbCrLf

' Throw a FormatException if the user did not enter the necessary spaces
' between elements.
Try
' City may consist of multiple words, so we'll have to parse the
' string from right to left starting with the zip code.
Dim zipIndex As Integer = strCityStateZip.LastIndexOf(" ")
If zipIndex = -1 Then
msg = vbCrLf + "Cannot identify a zip code." + msgEnd
msg = vbCrLf + "Cannot identify a zip code." + msgEnd
Throw New FormatException(msg)
End If
strZip = strCityStateZip.Substring(zipIndex + 1)

Dim stateIndex As Integer = strCityStateZip.LastIndexOf(" ", zipIndex - 1)


If stateIndex = -1 Then
msg = vbCrLf + "Cannot identify a state." + msgEnd
Throw New FormatException(msg)
End If
strState = strCityStateZip.Substring(stateIndex + 1, zipIndex - stateIndex - 1)
strState = strState.ToUpper()

strCity = strCityStateZip.Substring(0, stateIndex)


If strCity.Length = 0 Then
msg = vbCrLf + "Cannot identify a city." + msgEnd
Throw New FormatException(msg)
End If
parseSucceeded = True
Catch ex As FormatException
Console.WriteLine(ex.Message)
End Try
End Sub
End Class

Cuando se ejecuta el código anterior, se pide al usuario que escriba su nombre y dirección. La aplicación coloca la
información en las propiedades adecuadas y muestra la información al usuario, creando una única cadena que
muestra la información sobre la ciudad, el estado y el código postal.

Vea también
Operaciones básicas de cadenas
Expresiones regulares de .NET
16/09/2020 • 17 minutes to read • Edit Online

Las expresiones regulares proporcionan un método eficaz y flexible para procesar texto. La notación extensa de
coincidencia de patrones de expresiones regulares permite analizar rápidamente grandes cantidades de texto
para lo siguiente:
Buscar patrones concretos de caracteres.
Validar el texto para garantizar que coincide con un patrón predefinido (como una dirección de correo
electrónico).
Extraer, editar, reemplazar o eliminar subcadenas de texto.
Agregar cadenas extraídas en una colección para generar un informe.
Para muchas aplicaciones que usan cadenas o analizan grandes bloques de texto, las expresiones regulares son
una herramienta indispensable.

Funcionamiento de las expresiones regulares


El eje del procesamiento de texto mediante expresiones regulares es el motor de expresiones regulares, que viene
representado por el objeto System.Text.RegularExpressions.Regex en .NET. Como mínimo, el procesamiento de
texto mediante expresiones regulares necesita que el motor de expresiones regulares disponga de los dos
elementos de información siguientes:
El patrón de expresión regular que se debe identificar en el texto.
En .NET, los patrones de expresiones regulares se definen mediante una sintaxis o un lenguaje especial, que
es compatible con las expresiones regulares de Perl 5 y agrega algunas características adicionales, como
búsquedas de coincidencias de derecha a izquierda. Para obtener más información, consulte Lenguaje de
expresiones regulares: Referencia rápida.
El texto que se debe analizar para el patrón de expresión regular.
Los métodos de la clase Regex permiten realizar las operaciones siguientes:
Determinar si el patrón de expresión regular se produce en el texto de entrada llamando al método
Regex.IsMatch. Para obtener un ejemplo en donde se utiliza el método IsMatch para validar texto, vea
Procedimiento: Comprobación de que las cadenas están en un formato de correo electrónico válido.
Recuperar una o todas las apariciones del texto que coincide con el patrón de expresión regular llamando
al método Regex.Match o Regex.Matches. El primer método devuelve un objeto
System.Text.RegularExpressions.Match que proporciona información sobre el texto coincidente. El segundo
método devuelve un objeto MatchCollection que contiene un objeto System.Text.RegularExpressions.Match
por cada coincidencia encontrada en el texto analizado.
Reemplazar el texto que coincide con el patrón de expresión regular llamando al método Regex.Replace.
Para obtener ejemplos en donde se utiliza el método Replace para cambiar formatos de fecha y quitar
caracteres no válidos de una cadena, vea Procedimiento: Eliminación de caracteres no válidos de una
cadena y Ejemplo: Cambio de formatos de fecha.
Para obtener información general acerca del modelo de objetos de expresiones regulares, consulte El modelo de
objetos de expresión regular.
Para obtener más información acerca del lenguaje de expresiones regulares, consulte Lenguaje de expresiones
regulares - Referencia rápida o descargue e imprima uno de estos folletos:
Referencia rápida en formato Word (.docx)
Referencia rápida en formato PDF (.pdf)

Ejemplos de expresiones regulares


La clase String incluye varios métodos de búsqueda y reemplazo de cadenas que puede usar cuando desee
buscar cadenas literales en una cadena mayor. Las expresiones regulares son muy útiles cuando se desea buscar
una de varias subcadenas en una cadena mayor o cuando se desea identificar patrones en una cadena, como se
muestra en los ejemplos siguientes.

WARNING
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de
expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions y provocar un ataque por
denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions pasan un tiempo de
expiración.

TIP
El espacio de nombres System.Web.RegularExpressions contiene un número de objetos de expresión regular que
implementan modelos de expresión regular predefinidos para el análisis de cadenas a partir de documentos HTML, XML y
ASP.NET. Por ejemplo, la clase TagRegex identifica las etiquetas de inicio en una cadena y la clase CommentRegex identifica
los comentarios de ASP.NET en una cadena.

Ejemplo 1: Reemplazo de subcadenas


Suponga que una lista de distribución de correo contiene nombres que a veces incluyen un tratamiento (Sr., Sra. o
Srta.) junto con un nombre y un apellido. Si no desea incluir los tratamientos al generar las etiquetas de los
sobres a partir de la lista, puede usar una expresión regular para quitarlos, como se muestra en el ejemplo
siguiente.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "(Mr\\.? |Mrs\\.? |Miss |Ms\\.? )";
string[] names = { "Mr. Henry Hunt", "Ms. Sara Samuels",
"Abraham Adams", "Ms. Nicole Norris" };
foreach (string name in names)
Console.WriteLine(Regex.Replace(name, pattern, String.Empty));
}
}
// The example displays the following output:
// Henry Hunt
// Sara Samuels
// Abraham Adams
// Nicole Norris
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(Mr\.? |Mrs\.? |Miss |Ms\.? )"
Dim names() As String = {"Mr. Henry Hunt", "Ms. Sara Samuels", _
"Abraham Adams", "Ms. Nicole Norris"}
For Each name As String In names
Console.WriteLine(Regex.Replace(name, pattern, String.Empty))
Next
End Sub
End Module
' The example displays the following output:
' Henry Hunt
' Sara Samuels
' Abraham Adams
' Nicole Norris

El patrón de expresión regular (Mr\.? |Mrs\.? |Miss |Ms\.? ) busca coincidencias con cualquier aparición de "Mr
", "Mr. ", "Mrs ", "Mrs. ", "Miss ", "Ms " o "Ms. ". La llamada al método Regex.Replace reemplaza la cadena
coincidente con String.Empty; es decir, la quita de la cadena original.
Ejemplo 2: Identificación de palabras duplicadas
Duplicar palabras accidentalmente es un error frecuente que cometen los escritores. Se puede usar una expresión
regular para identificar palabras duplicadas, como se muestra en el ejemplo siguiente.

using System;
using System.Text.RegularExpressions;

public class Class1


{
public static void Main()
{
string pattern = @"\b(\w+?)\s\1\b";
string input = "This this is a nice day. What about this? This tastes good. I saw a a dog.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("{0} (duplicates '{1}') at position {2}",
match.Value, match.Groups[1].Value, match.Index);
}
}
// The example displays the following output:
// This this (duplicates 'This') at position 0
// a a (duplicates 'a') at position 66

Imports System.Text.RegularExpressions

Module modMain
Public Sub Main()
Dim pattern As String = "\b(\w+?)\s\1\b"
Dim input As String = "This this is a nice day. What about this? This tastes good. I saw a a dog."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("{0} (duplicates '{1}') at position {2}", _
match.Value, match.Groups(1).Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' This this (duplicates 'This') at position 0
' a a (duplicates 'a') at position 66

El patrón de expresión regular \b(\w+?)\s\1\b se puede interpretar de la manera siguiente:


M O DELO IN T ERP RETA C IÓ N

\b Empieza en un límite de palabras.

(\w+?) Coincide con uno o más caracteres de palabra, pero con el


menor número de caracteres posible. Juntos, forman un
grupo al que se puede hacer referencia como \1 .

\s Coincide con un carácter de espacio en blanco.

\1 Coincide con la subcadena que es igual al grupo denominado


\1 .

\b Coincide con un límite de palabras.

Se llama al método Regex.Matches con las opciones de expresiones regulares establecidas en


RegexOptions.IgnoreCase. Por tanto, la operación de coincidencia no distingue mayúsculas de minúsculas y el
ejemplo identifica la subcadena "Esto esto" como una duplicación.
La cadena de entrada incluye la subcadena "this? This". Sin embargo, debido al signo de puntuación intermedio,
no se identifica como una duplicación.
Ejemplo 3: Compilación dinámica de una expresión regular de referencia cultural
En el ejemplo siguiente se muestra la eficacia de las expresiones regulares, además de la flexibilidad que ofrecen
las características de globalización de .NET. Se usa el objeto NumberFormatInfo para determinar el formato de los
valores de divisa en la referencia cultural actual del sistema. A continuación, se usa dicha información para
construir dinámicamente una expresión regular que extrae los valores de divisa del texto. Para cada coincidencia,
se extrae el subgrupo que solo contiene la cadena numérica, se convierte el subgrupo en un valor Decimal y se
calcula un total acumulativo.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
// Define text to be parsed.
string input = "Office expenses on 2/13/2008:\n" +
"Paper (500 sheets) $3.95\n" +
"Pencils (box of 10) $1.00\n" +
"Pens (box of 10) $4.49\n" +
"Erasers $2.19\n" +
"Ink jet printer $69.95\n\n" +
"Total Expenses $ 81.58\n";

// Get current culture's NumberFormatInfo object.


NumberFormatInfo nfi = CultureInfo.CurrentCulture.NumberFormat;
// Assign needed property values to variables.
string currencySymbol = nfi.CurrencySymbol;
bool symbolPrecedesIfPositive = nfi.CurrencyPositivePattern % 2 == 0;
string groupSeparator = nfi.CurrencyGroupSeparator;
string decimalSeparator = nfi.CurrencyDecimalSeparator;

// Form regular expression pattern.


string pattern = Regex.Escape( symbolPrecedesIfPositive ? currencySymbol : "") +
@"\s*[-+]?" + "([0-9]{0,3}(" + groupSeparator + "[0-9]{3})*(" +
Regex.Escape(decimalSeparator) + "[0-9]+)?)" +
(! symbolPrecedesIfPositive ? currencySymbol : "");
Console.WriteLine( "The regular expression pattern is:");
Console.WriteLine(" " + pattern);

// Get text that matches regular expression pattern.


MatchCollection matches = Regex.Matches(input, pattern,
RegexOptions.IgnorePatternWhitespace);
Console.WriteLine("Found {0} matches.", matches.Count);

// Get numeric string, convert it to a value, and add it to List object.


List<decimal> expenses = new List<Decimal>();

foreach (Match match in matches)


expenses.Add(Decimal.Parse(match.Groups[1].Value));

// Determine whether total is present and if present, whether it is correct.


decimal total = 0;
foreach (decimal value in expenses)
total += value;

if (total / 2 == expenses[expenses.Count - 1])


Console.WriteLine("The expenses total {0:C2}.", expenses[expenses.Count - 1]);
else
Console.WriteLine("The expenses total {0:C2}.", total);
}
}
// The example displays the following output:
// The regular expression pattern is:
// \$\s*[-+]?([0-9]{0,3}(,[0-9]{3})*(\.[0-9]+)?)
// Found 6 matches.
// The expenses total $81.58.
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Text.RegularExpressions

Public Module Example


Public Sub Main()
' Define text to be parsed.
Dim input As String = "Office expenses on 2/13/2008:" + vbCrLf + _
"Paper (500 sheets) $3.95" + vbCrLf + _
"Pencils (box of 10) $1.00" + vbCrLf + _
"Pens (box of 10) $4.49" + vbCrLf + _
"Erasers $2.19" + vbCrLf + _
"Ink jet printer $69.95" + vbCrLf + vbCrLf + _
"Total Expenses $ 81.58" + vbCrLf
' Get current culture's NumberFormatInfo object.
Dim nfi As NumberFormatInfo = CultureInfo.CurrentCulture.NumberFormat
' Assign needed property values to variables.
Dim currencySymbol As String = nfi.CurrencySymbol
Dim symbolPrecedesIfPositive As Boolean = CBool(nfi.CurrencyPositivePattern Mod 2 = 0)
Dim groupSeparator As String = nfi.CurrencyGroupSeparator
Dim decimalSeparator As String = nfi.CurrencyDecimalSeparator

' Form regular expression pattern.


Dim pattern As String = Regex.Escape(CStr(IIf(symbolPrecedesIfPositive, currencySymbol, ""))) + _
"\s*[-+]?" + "([0-9]{0,3}(" + groupSeparator + "[0-9]{3})*(" + _
Regex.Escape(decimalSeparator) + "[0-9]+)?)" + _
CStr(IIf(Not symbolPrecedesIfPositive, currencySymbol, ""))
Console.WriteLine("The regular expression pattern is: ")
Console.WriteLine(" " + pattern)

' Get text that matches regular expression pattern.


Dim matches As MatchCollection = Regex.Matches(input, pattern, RegexOptions.IgnorePatternWhitespace)
Console.WriteLine("Found {0} matches. ", matches.Count)

' Get numeric string, convert it to a value, and add it to List object.
Dim expenses As New List(Of Decimal)

For Each match As Match In matches


expenses.Add(Decimal.Parse(match.Groups.Item(1).Value))
Next

' Determine whether total is present and if present, whether it is correct.


Dim total As Decimal
For Each value As Decimal In expenses
total += value
Next

If total / 2 = expenses(expenses.Count - 1) Then


Console.WriteLine("The expenses total {0:C2}.", expenses(expenses.Count - 1))
Else
Console.WriteLine("The expenses total {0:C2}.", total)
End If
End Sub
End Module
' The example displays the following output:
' The regular expression pattern is:
' \$\s*[-+]?([0-9]{0,3}(,[0-9]{3})*(\.[0-9]+)?)
' Found 6 matches.
' The expenses total $81.58.

En un equipo cuya referencia cultural actual sea Inglés - Estados Unidos (en-US), el ejemplo crea dinámicamente
la expresión regular \$\s*[-+]?([0-9]{0,3}(,[0-9]{3})*(\.[0-9]+)?) . Este patrón de expresión regular se puede
interpretar de la manera siguiente:
M O DELO IN T ERP RETA C IÓ N

\$ Busque una sola aparición del símbolo de dólar ( $ ) en la


cadena de entrada. La cadena del patrón de expresión regular
incluye una barra diagonal inversa para indicar que el símbolo
de dólar debe interpretarse literalmente en lugar de
interpretarse como un delimitador de la expresión regular. (Si
solo apareciese el símbolo $ , esto indicaría que el motor de
expresión regular debe intentar comenzar su búsqueda de
coincidencias al final de una cadena). Para asegurarse de que
el símbolo de divisa de la referencia cultural actual no se
interprete erróneamente como un símbolo de expresión
regular, en el ejemplo se llama al método Regex.Escape como
escape de caracteres.

\s* Buscar cero o más apariciones de un carácter de espacio en


blanco.

[-+]? Buscar cero o una aparición de un signo positivo o un signo


negativo.

([0-9]{0,3}(,[0-9]{3})*(\.[0-9]+)?) Los paréntesis externos alrededor de esta expresión la


definen como un grupo de captura o una subexpresión. Si se
encuentra una coincidencia, la información sobre esta parte
de la cadena coincidente se puede recuperar del segundo
objeto Group en el objeto GroupCollection devuelto por la
propiedad Match.Groups. (El primer elemento de la colección
representa la coincidencia completa.)

[0-9]{0,3} Buscar de cero a tres apariciones de los dígitos decimales


comprendidos entre 0 y 9.

(,[0-9]{3})* Buscar cero o más apariciones de un separador de grupos


seguido de tres dígitos decimales.

\. Buscar una única aparición del separador decimal.

[0-9]+ Buscar uno o más dígitos decimales.

(\.[0-9]+)? Buscar cero o una aparición del separador decimal seguido de


al menos un dígito decimal.

Si se encuentra cada uno de estos subpatrones en la cadena de entrada, la búsqueda de coincidencias se realiza
correctamente y se agrega al objeto Match un objeto MatchCollection que contiene información sobre la
coincidencia.

Temas relacionados
T IT L E DESC RIP C IÓ N

Lenguaje de expresiones regulares: referencia rápida Ofrece información sobre el conjunto de caracteres,
operadores y construcciones que se pueden utilizar para
definir expresiones regulares.

Modelo de objetos de expresión regular Proporciona información y ejemplos de código que muestran
cómo usar las clases de expresiones regulares.
T IT L E DESC RIP C IÓ N

Detalles del comportamiento de expresiones regulares Proporciona información sobre las funcionalidades y el
comportamiento de las expresiones regulares de .NET.

Usar expresiones regulares en Visual Studio

Referencia
System.Text.RegularExpressions
System.Text.RegularExpressions.Regex
Expresiones regulares: referencia rápida (descarga en formato Word)
Expresiones regulares: referencia rápida (descarga en formato PDF)
Lenguaje de expresiones regulares - Referencia
rápida
16/09/2020 • 22 minutes to read • Edit Online

Una expresión regular es un modelo con el que el motor de expresiones regulares intenta buscar una
coincidencia en el texto de entrada. Un modelo consta de uno o más literales de carácter, operadores o
estructuras. Para obtener una breve introducción, consulte Expresiones regulares de .NET.
Cada sección de esta referencia rápida enumera una categoría determinada de caracteres, operadores y
construcciones que puede usar para definir expresiones regulares.
Esta información también se proporciona en dos formatos que se pueden descargar e imprimir para facilitar su
consulta:
Descargar en formato Word (.docx)
Descarga en formato PDF (.pdf)

Escapes de carácter
El carácter de barra diagonal inversa (\) en una expresión regular indica que el carácter que le sigue es un
carácter especial (como se muestra en la tabla siguiente) o que se debe interpretar literalmente. Para más
información, consulte Escapes de carácter.

C A RÁ C T ER DE ESC A P E DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

\a Coincide con un carácter de \a "\u0007" en


campana, \u0007. "Error!" + '\u0007'

\b En una clase de caracteres, [\b]{3,} "\b\b\b\b" en


coincide con un retroceso, "\b\b\b\b"
\u0008.

\t Coincide con una (\w+)\t "item1\t" , "item2\t"


tabulación, \u0009. en "item1\titem2\t"

\r Coincide con un retorno de \r\n(\w+) "\r\nThese" en


carro, \u000D. ( \r no es "\r\nThese are\ntwo
equivalente al carácter de lines."
nueva línea, \n ).

\v Coincide con una tabulación [\v]{2,} "\v\v\v" en "\v\v\v"


vertical, \u000B.

\f Coincide con un avance de [\f]{2,} "\f\f\f" en "\f\f\f"


página, \u000C.

\n Coincide con una nueva \r\n(\w+) "\r\nThese" en


línea, \u000A. "\r\nThese are\ntwo
lines."
C A RÁ C T ER DE ESC A P E DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

\e Coincide con un escape, \e "\x001B" en "\x001B"


\u001B.

\ nnn Usa la representación octal \w\040\w "a b" , "c d" en


para especificar un carácter "a bc d"
(nnn consta de dos o tres
dígitos).

\x nn Usa la representación \w\x20\w "a b" , "c d" en


hexadecimal para especificar "a bc d"
un carácter (nn consta de
exactamente dos dígitos).

\c X Coincide con el carácter de \cC "\x0003" en "\x0003"


control ASCII especificado (Ctrl-C)
\c x por X o x, donde X o x es la
letra del carácter de control.

\u nnnn Coincide con un carácter \w\u0020\w "a b" , "c d" en


Unicode usando la "a bc d"
representación hexadecimal
(exactamente cuatro dígitos,
según representa nnnn).

\ Cuando va seguido de un \d+[\+-x\*]\d+ "2+2" y "3*9" en


carácter que no se reconoce "(2+2) * 3*9"
como un carácter de escape
en esta y otras tablas de
este tema, coincide con ese
carácter. Por ejemplo, \*
es igual que \x2A y \. es
igual que \x2E . Esto
permite que el motor de
expresiones regulares
elimine la ambigüedad de
los elementos del lenguaje
(como * o ?) y los literales
de carácter (representados
por \* o \? ).

Clases de caracteres
Una clase de caracteres coincide con cualquiera de un juego de caracteres. Las clases de caracteres incluyen los
elementos del lenguaje enumerados en la tabla siguiente. Para más información, consulte Clases de caracteres.

C L A SE DE C A RÁ C T ER DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

[ grupo_caracteres ] Coincide con cualquier [ae] "a" en "gray"


carácter individual de
grupo_caracteres. De forma "a" , "e" en "lane"
predeterminada, la
coincidencia distingue entre
mayúsculas y minúsculas.
C L A SE DE C A RÁ C T ER DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

[^ grupo_caracteres ] Negativo: coincide con [^aei] "r" , "g" , "n" en


cualquier carácter individual "reign"
que no esté en
grupo_caracteres. De forma
predeterminada, los
caracteres de
grupo_caracteres distinguen
entre mayúsculas y
minúsculas.

[ primero - último ] Rango de caracteres: [A-Z] "A" , "B" en "AB123"


coincide con cualquier
carácter individual en el
intervalo de primero a
último.

. Carácter comodín: coincide a.e "ave" en "nave"


con cualquier carácter
excepto con \n. "ate" en "water"

Para coincidir con un


carácter de punto literal (. o
\u002E ), debe anteponerle
el carácter de escape ( \. ).

\p{ nombre } Coincide con cualquier \p{Lu} "C" , "L" en


carácter individual que "City Lights"
pertenezca a la categoría \p{IsCyrillic}
general Unicode o al bloque "Д" , "Ж" en "ДЖem"
con nombre especificado
por nombre.

\P{ nombre } Coincide con cualquier \P{Lu} "i" , "t" , "y" en


carácter individual que no "City"
pertenezca a la categoría \P{IsCyrillic}
general Unicode o al bloque "e" , "m" en "ДЖem"
con nombre especificado
por nombre.

\w Coincide con cualquier \w "I" , "D" , "A" , "1" ,


carácter de una palabra. "3" en "ID A1.3"

\W Coincide con cualquier \W " " , "." en "ID A1.3"


carácter que no pertenezca
a una palabra.

\s Coincide con cualquier \w\s "D " en "ID A1.3"


carácter que sea un espacio
en blanco.

\S Coincide con cualquier \s\S " _" en "int __ctr"


carácter que no sea un
espacio en blanco.

\d Coincide con cualquier \d "4" en "4 = IV"


dígito decimal.
C L A SE DE C A RÁ C T ER DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

\D Coincide con cualquier \D " " , "=" , " " , "I" ,


carácter que no sea un "V" en "4 = IV"
dígito decimal.

Delimitadores
Los delimitadores, o aserciones atómicas de ancho cero, hacen que una coincidencia tenga éxito o no
dependiendo de la posición actual en la cadena, pero no hacen que el motor avance por la cadena ni consuma
caracteres. Los metacaracteres enumerados en la tabla siguiente son delimitadores. Para obtener más
información, consulte Delimitadores.

A SERC IÓ N DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

^ De forma predeterminada, ^\d{3} "901" en "901-333-"


la coincidencia debe
comenzar al principio de la
cadena; en el modo
multilínea, debe comenzar
al principio de la línea.

$ De forma predeterminada, -\d{3}$ "-333" en "-901-333"


la coincidencia se debe
producir al final de la
cadena o antes de \n al
final de la cadena; en el
modo multilínea, se debe
producir antes del final de la
línea o antes de \n al final
de la línea.

\A La coincidencia se debe \A\d{3} "901" en "901-333-"


producir al principio de la
cadena.

\Z La coincidencia se debe -\d{3}\Z "-333" en "-901-333"


producir al final de la
cadena o antes de \n al
final de la cadena.

\z La coincidencia se debe -\d{3}\z "-333" en "-901-333"


producir al final de la
cadena.

\G La coincidencia se debe \G\(\d\) "(1)" , "(3)" , "(5)"


producir en el punto en el en "(1)(3)(5)[7](9)"
que finalizó la coincidencia
anterior.

\b La coincidencia se debe \b\w+\s\w+\b "them theme" ,


producir en un límite entre "them them" en
un carácter \w "them theme them them"
(alfanumérico) y un carácter
\W (no alfanumérico).
A SERC IÓ N DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

\B La coincidencia no se debe \Bend\w*\b "ends" , "ender" en


producir en un límite \b . "end sends endure
lender"

Construcciones de agrupamiento
Las construcciones de agrupamiento definen subexpresiones de una expresión regular y, normalmente,
capturan subcadenas de una cadena de entrada. Las construcciones de agrupamiento incluyen los elementos del
lenguaje enumerados en la tabla siguiente. Para obtener más información, consulte Construcciones de
agrupamiento.

C O N ST RUC C IÓ N DE
A GRUPA M IEN TO DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

( subexpresión ) Captura la subexpresión (\w)\1 "ee" en "deep"


coincidente y le asigna un
número ordinal basado en
uno.

(?< nombre > Captura la subexpresión (? "ee" en "deep"


subexpresión ) coincidente en un grupo <double>\w)\k<double>

o con nombre.
(?' nombre '
subexpresión )

(?<nombre1 - Define una definición de (((?'Open'\()[^\ "((1-3)*(3-1))" en


grupo de equilibrio. Para (\)]*)+((?'Close-
nombre2 > subexpresión Open'\))[^\(\)]*)+)*(?
"3+2^((1-3)*(3-1))"
) obtener más información, (Open)(?!))$
o consulte la sección
(?'nombre1 - "Definiciones de grupos de
nombre2 ' subexpresión equilibrio" en
Construcciones de
)
agrupamiento.

(?: subexpresión ) Define un grupo sin Write(?:Line)? "WriteLine" en


captura. "Console.WriteLine()"

"Write" en
"Console.Write(value)"

(?imnsx-imnsx: Aplica o deshabilita las A\d{2}(?i:\w+)\b "A12xl" , "A12XL" en


subexpresión ) opciones especificadas "A12xl A12XL a12xl"
dentro de subexpresión.
Para obtener más
información, consulte
Opciones de expresiones
regulares.

(?= subexpresión ) Aserción de búsqueda \w+(?=\.) "is" , "ran" y "out"


anticipada positiva de en
ancho cero. "He is. The dog ran.
The sun is out."
C O N ST RUC C IÓ N DE
A GRUPA M IEN TO DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

(?! subexpresión ) Aserción de búsqueda \b(?!un)\w+\b "sure" , "used" en


anticipada negativa de "unsure sure unity
ancho cero. used"

(?<= subexpresión ) Aserción de búsqueda (?<=19)\d{2}\b "99" , "50" , "05" en


tardía positiva de ancho "1851 1999 1950 1905
cero. 2003"

(?<! subexpresión ) Aserción de búsqueda (?<!19)\d{2}\b "51" , "03" en


tardía negativa de ancho "1851 1999 1950 1905
cero. 2003"

(?> subexpresión ) Grupo atómico. [13579](?>A+B+) "1ABB" , "3ABB" y


"5AB" en
"1ABB 3ABBC 5AB 5AC"

Cuantificadores
Un cuantificador especifica cuántas instancias del elemento anterior (que puede ser un carácter, un grupo o una
clase de caracteres) debe haber en la cadena de entrada para que se encuentre una coincidencia. Los
cuantificadores incluyen los elementos del lenguaje enumerados en la tabla siguiente. Para obtener más
información, consulte Cuantificadores.

C UA N T IF IC A DO R DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

* Coincide con el elemento \d*\.\d ".0" , "19.9" , "219.9"


anterior cero o más veces.

+ Coincide con el elemento "be+" "bee" en "been" , "be"


anterior una o más veces. en "bent"

? Coincide con el elemento "rai?n" "ran" , "rain"


anterior cero veces o una
vez.

{ n } Coincide con el elemento ",\d{3}" ",043" en "1,043.6" ,


anterior exactamente n ",876" , ",543" y
veces. ",210" en
"9,876,543,210"

{ n ,} Coincide con el elemento "\d{2,}" "166" , "29" , "1930"


anterior al menos n veces.

{ n , m } Coincide con el elemento "\d{3,5}" "166" , "17668"


anterior al menos n veces,
pero no más de m veces. "19302" en "193024"

*? Coincide con el elemento \d*?\.\d ".0" , "19.9" , "219.9"


anterior cero o más veces,
pero el menor número de
veces que sea posible.
C UA N T IF IC A DO R DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

+? Coincide con el elemento "be+?" "be" en "been" , "be"


anterior una o más veces, en "bent"
pero el menor número de
veces que sea posible.

?? Coincide con el elemento "rai??n" "ran" , "rain"


anterior cero o una vez,
pero el menor número de
veces que sea posible.

{ n }? Coincide con el elemento ",\d{3}?" ",043" en "1,043.6" ,


precedente exactamente n ",876" , ",543" y
veces. ",210" en
"9,876,543,210"

{ n ,}? Coincide con el elemento "\d{2,}?" "166" , "29" , "1930"


anterior al menos n veces,
pero el menor número de
veces posible.

{ n , m }? Coincide con el elemento "\d{3,5}?" "166" , "17668"


anterior entre n y m veces,
pero el menor número de "193" , "024" en
veces posible. "193024"

Construcciones de referencia inversa


Una referencia inversa permite identificar una subexpresión coincidente previamente más adelante en la misma
expresión regular. En la tabla siguiente se enumeran las construcciones de referencia inversa admitidas en las
expresiones regulares de .NET. Para obtener más información, consulte Construcciones de referencia inversa.

C O N ST RUC C IÓ N DE
REF EREN C IA S IN VERSA S DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

\ número Referencia inversa Coincide (\w)\1 "ee" en "seek"


con el valor de una
subexpresión numerada.

\k< nombre > Referencia inversa con (?<char>\w)\k<char> "ee" en "seek"


nombre Coincide con el
valor de una expresión con
nombre.

Construcciones de alternancia
Las estructuras de alternancia modifican una expresión regular para habilitar o no la coincidencia. Estas
construcciones incluyen los elementos del lenguaje enumerados en la tabla siguiente. Para obtener más
información, consulte Construcciones de alternancia.

C O N ST RUC C IO N ES DE
A LT ERN A N C IA DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S
C O N ST RUC C IO N ES DE
A LT ERN A N C IA DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

| Coincide con cualquier th(e|is|at) "the" , "this" en


elemento separado por el "this is the day."
carácter de barra vertical (
| ).

(?( expresión ) sí | Coincide con sí si el patrón (? "A10" , "910" en


no ) de expresión regular (A)A\d{2}\b|\b\d{3}\b) "A10 C103 910"
designado por expresión
coincide; de lo contrario,
coincide con la parte
opcional no. expresión se
interpreta como una
aserción de ancho cero.

(?( nombre ) sí | no Coincide con sí si nombre, (?<quoted>")?(? "Dogs.jpg " ,


) un grupo de captura con (quoted).+?"|\S+\s) "\"Yiska
nombre o numerado, tiene playing.jpg\""
una coincidencia; de lo en
contrario, coincide con la "Dogs.jpg \"Yiska
parte opcional no. playing.jpg\""

Sustituciones
Las sustituciones son elementos del lenguaje de expresiones regulares que se admiten en modelos de
reemplazo. Para obtener más información, consulte Substituciones. Los metacaracteres enumerados en la tabla
siguiente son aserciones atómicas de ancho cero.

M O DELO DE C A DEN A DE C A DEN A DE


C A RÁ C T ER DESC RIP C IÓ N M O DELO REEM P L A Z O EN T RA DA RESULTA DO

$ número Sustituye la \b(\w+)(\s) $3$2$1 "one two" "two one"


subcadena que (\w+)\b
coincide con el
grupo número.

${ nombre } Sustituye la \b(? ${word2} "one two" "two one"


subcadena que <word1>\w+) ${word1}
(\s)(?
coincide con el <word2>\w+)\b
grupo con
nombre nombre.

$$ Sustituye un "$" \b(\d+)\s?USD $$$1 "103 USD" "$103"


literal.

$& Sustituye una \$?\d*\.?\d+ **$&** "$1.30" "**$1.30**"


copia de toda la
coincidencia.

$` Sustituye todo el B+ $` "AABBCC" "AAAACC"


texto de la
cadena de
entrada delante
de la
coincidencia.
M O DELO DE C A DEN A DE C A DEN A DE
C A RÁ C T ER DESC RIP C IÓ N M O DELO REEM P L A Z O EN T RA DA RESULTA DO

$' Sustituye todo el B+ $' "AABBCC" "AACCCC"


texto de la
cadena de
entrada detrás
de la
coincidencia.

$+ Sustituye el B+(C+) $+ "AABBCCDD" "AACCDD"


último grupo
capturado.

$_ Sustituye toda la B+ $_ "AABBCC" "AAAABBCCCC"


cadena de
entrada.

Opciones de expresiones regulares


Puede especificar opciones que controlen cómo debe interpretar el motor de expresiones regulares un patrón
de expresión regular. Muchas de estas opciones pueden especificarse alineadas (en el patrón de expresión
regular) o como una o más constantes de RegexOptions. Esta referencia rápida solo muestra las opciones
alineadas. Para obtener más información sobre las opciones alineadas y RegexOptions, consulte el artículo
Opciones de expresiones regulares.
Puede especificar una opción alineada de dos formas:
Con la construcción miscelánea (?imnsx-imnsx) , donde el signo menos (-) delante de una opción o un
conjunto de opciones desactiva dichas opciones. Por ejemplo, (?i-mn) activa una coincidencia sin distinción
entre mayúsculas y minúsculas ( i ), desactiva el modo multilínea ( m ) y desactiva las capturas de grupo sin
nombre ( n ). La opción se aplica al patrón de expresión regular a partir del punto en el que esta se define y
es efectiva hasta el final del patrón o hasta el punto en el que otro constructor invierte la opción.
Con la Construcciones de agrupamiento (?imnsx-imnsx: subexpresión ) , que define opciones solo para el
grupo especificado.
El motor de expresiones regulares de .NET admite las siguientes opciones insertadas:

O P C IÓ N DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

i Usa la coincidencia sin \b(?i)a(?-i)a\w+\b "aardvark" , "aaaAuto"


distinción entre mayúsculas en
y minúsculas. "aardvark AAAuto
aaaAuto Adam
breakfast"

m Usa el modo multilínea. ^ Para obtener un ejemplo,


y $ coinciden con el consulte la sección "Modo
principio y el final de una multilínea" en Opciones de
línea, en lugar del principio expresiones regulares.
y el final de una cadena.

n No se capturan grupos sin Para obtener un ejemplo,


nombre. consulte la sección "Solo
capturas explícitas" en
Opciones de expresiones
regulares.
O P C IÓ N DESC RIP C IÓ N M O DELO C O IN C IDEN C IA S

s Usa el modo de una sola Para obtener un ejemplo,


línea. consulte la sección "Modo
de una sola línea" en
Opciones de expresiones
regulares.

x Se omite el espacio en \b(?x) \d+ \s \w+ "1 aardvark" , "2 cats"


blanco sin escape en el en
patrón de expresión regular. "1 aardvark 2 cats IV
centurions"

Construcciones misceláneas
Las estructuras misceláneas modifican un modelo de expresión regular o proporcionan información sobre él. En
la tabla siguiente se enumeran las construcciones misceláneas admitidas por .NET. Para obtener más
información, consulte Construcciones misceláneas.

C O N ST RUC C IÓ N DEF IN IC IÓ N E JEM P LO

(?imnsx-imnsx) Establece o deshabilita opciones como \bA(?i)b\w+\b coincide con "ABA" ,


la no distinción entre mayúsculas y "Able" en "ABA Able Act"
minúsculas en medio de un patrón.
Para más información, consulte
Opciones de expresiones regulares.

(?# comentario ) Comentario alineado El comentario \bA(?#Matches words starting


termina en el primer paréntesis de with A)\w+\b
cierre.

# [hasta el final de la línea] Comentario en modo X El comentario (?x)\bA\w+\b#Matches words


comienza en un carácter # sin escape starting with A
y continúa hasta el final de la línea.

Vea también
System.Text.RegularExpressions
System.Text.RegularExpressions.Regex
Expresiones regulares
Clases de expresiones regulares
Expresiones regulares: referencia rápida (descarga en formato Word)
Expresiones regulares: referencia rápida (descarga en formato PDF)
Escapes de carácter en expresiones regulares
16/09/2020 • 8 minutes to read • Edit Online

La barra diagonal inversa (\) en una expresión regular indica una de las siguientes situaciones:
El carácter que va detrás de ella es un carácter especial, como se muestra en la tabla de la sección siguiente.
Por ejemplo, \b es un delimitador que indica que una coincidencia de expresión regular debería comenzar
en un límite de palabras, \t representa un carácter de tabulación y \x020 representa un espacio.
Un carácter que de otro modo se interpretaría como una construcción de lenguaje sin escape, se debe
interpretar literalmente. Por ejemplo, una llave ( { ) inicia la definición de un cuantificador, pero una barra
diagonal inversa seguida de una llave ( \{ ) indica que el motor de expresiones regulares debería coincidir
con la llave. De igual forma, una sola barra diagonal inversa marca el principio de una construcción de
lenguaje con escape, pero dos barras diagonales inversas ( \\ ) indican que el motor de expresiones
regulares debería coincidir con la barra diagonal inversa.

NOTE
Los escapes de caracteres se reconocen en los patrones de expresiones regulares, pero no en los patrones de reemplazo.

Escapes de carácter en .NET


En la tabla siguiente se enumeran los escapes de caracteres admitidos en las expresiones regulares de .NET.

C A RÁ C T ER O SEC UEN C IA DESC RIP C IÓ N

Todos los caracteres excepto los siguientes: Los caracteres que no aparecen en la columna Carácter o
secuencia no tienen ningún significado especial en las
.$^{[(|)*+?\ expresiones regulares, sino que equivalen a sí mismos.

Los caracteres incluidos en la columna Carácter o


secuencia son elementos del lenguaje especial de
expresiones regulares. Para que coincidan en una expresión
regular, deben escribirse entre secuencias de escape o incluirse
en un grupo de caracteres positivos. Por ejemplo, las
expresiones regulares \$\d+ o [$]\d+ coinciden con
"$1200".

\a Coincide con un carácter de campana (alarma), \u0007 .

\b En una clase de caracteres [ grupo_caracteres ] , coincide


con un retroceso, \u0008 . (Consulte Clases de caracteres).
Fuera de una clase de caracteres, \b es un delimitador que
coincide con un límite de palabras. (Consulte Delimitadores).

\t Coincide con un carácter de tabulación, \u0009 .

\r Coincide con un retorno de carro, \u000D . Observe que \r


no es equivalente al carácter de nueva línea, \n .

\v Coincide con una tabulación vertical, \u000B .


C A RÁ C T ER O SEC UEN C IA DESC RIP C IÓ N

\f Coincide con un avance de página, \u000C .

\n Coincide con una nueva línea, \u000A .

\e Coincide con un escape, \u001B .

\ nnn Coincide con un carácter ASCII, donde nnn está compuesto


de dos o tres dígitos que representan el código de carácter
octal. Por ejemplo, \040 representa un carácter de espacio.
Esta construcción se interpreta como una referencia inversa si
tiene un solo dígito (por ejemplo, \2 ) o si se corresponde
con el número de un grupo de captura. (Consulte
Construcciones de referencia inversa).

\x nn Coincide con un carácter ASCII, donde nn es un código de


carácter hexadecimal de dos dígitos.

\c X Coincide con un carácter de control ASCII, donde X es la letra


del carácter de control. Por ejemplo, \cC es CTRL-C.

\u nnnn Coincide con una unidad de código UTF-16 cuyo valor


hexadecimal es nnnn. Nota: .NET no admite el escape de
caracteres de Perl 5 usado para especificar Unicode. El escape
de caracteres de Perl 5 tiene el formato \x{ #### …} ,
donde #### … es una serie de dígitos hexadecimales. En su
lugar, use \u nnnn.

\ Si va seguido de un carácter que no se reconoce como


carácter de escape, coincide con ese carácter. Por ejemplo, \*
coincide con un asterisco (*) y es igual que \x2A .

Un ejemplo
En el ejemplo siguiente se muestra el uso de escapes de carácter en una expresión regular. Analiza una cadena que
contiene los nombres de las ciudades más grandes del mundo y sus poblaciones en 2009. Cada nombre de ciudad
se separa de su población por un carácter de tabulación ( \t ) o una barra vertical (| o \u007c ). Cada ciudad y su
población está separada de la siguiente por un retorno de carro y un avance de línea.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string delimited = @"\G(.+)[\t\u007c](.+)\r?\n";
string input = "Mumbai, India|13,922,125\t\n" +
"Shanghai, China\t13,831,900\n" +
"Karachi, Pakistan|12,991,000\n" +
"Delhi, India\t12,259,230\n" +
"Istanbul, Turkey|11,372,613\n";
Console.WriteLine("Population of the World's Largest Cities, 2009");
Console.WriteLine();
Console.WriteLine("{0,-20} {1,10}", "City", "Population");
Console.WriteLine();
foreach (Match match in Regex.Matches(input, delimited))
Console.WriteLine("{0,-20} {1,10}", match.Groups[1].Value,
match.Groups[2].Value);
}
}
// The example displays the following output:
// Population of the World's Largest Cities, 2009
//
// City Population
//
// Mumbai, India 13,922,125
// Shanghai, China 13,831,900
// Karachi, Pakistan 12,991,000
// Delhi, India 12,259,230
// Istanbul, Turkey 11,372,613

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim delimited As String = "\G(.+)[\t\u007c](.+)\r?\n"
Dim input As String = "Mumbai, India|13,922,125" + vbCrLf + _
"Shanghai, China" + vbTab + "13,831,900" + vbCrLf + _
"Karachi, Pakistan|12,991,000" + vbCrLf + _
"Delhi, India" + vbTab + "12,259,230" + vbCrLf + _
"Istanbul, Turkey|11,372,613" + vbCrLf
Console.WriteLine("Population of the World's Largest Cities, 2009")
Console.WriteLine()
Console.WriteLine("{0,-20} {1,10}", "City", "Population")
Console.WriteLine()
For Each match As Match In Regex.Matches(input, delimited)
Console.WriteLine("{0,-20} {1,10}", match.Groups(1).Value, _
match.Groups(2).Value)
Next
End Sub
End Module
' The example displays the following output:
' Population of the World's Largest Cities, 2009
'
' City Population
'
' Mumbai, India 13,922,125
' Shanghai, China 13,831,900
' Karachi, Pakistan 12,991,000
' Delhi, India 12,259,230
' Istanbul, Turkey 11,372,613

La expresión regular \G(.+)[\t|\u007c](.+)\r?\n se interpreta como se muestra en la tabla siguiente.


M O DELO DESC RIP C IÓ N

\G Comienza la búsqueda de coincidencias donde finalizó la


última coincidencia.

(.+) Buscar cualquier carácter coincidente una o más veces. Este es


el primer grupo de captura.

[\t\u007c] Coincide con un carácter de tabulación ( \t ) o una barra


vertical (|).

(.+) Buscar cualquier carácter coincidente una o más veces. Este es


el segundo grupo de captura.

\r?\n Coincide con cero o un retorno de carro seguido de una


nueva línea.

Vea también
Lenguaje de expresiones regulares: referencia rápida
Clases de caracteres en expresiones regulares
16/09/2020 • 58 minutes to read • Edit Online

Una clase de caracteres define un conjunto de caracteres, cualquiera de los cuales puede estar en una cadena de
entrada para que se produzca una coincidencia. El lenguaje de expresiones regulares de .NET admite las
siguientes clases de caracteres:
Grupos de caracteres positivos. Un carácter de la cadena de entrada debe coincidir con uno de los
caracteres del conjunto especificado. Para obtener más información, consulte Grupo de caracteres
positivos.
Grupos de caracteres negativos. Ningún carácter de la cadena de entrada debe coincidir con ninguno de
los caracteres del conjunto especificado. Para obtener más información, consulte Grupo de caracteres
negativos.
Cualquier carácter. El carácter . (punto) en una expresión regular es un carácter comodín que coincide
con cualquier carácter excepto con \n . Para obtener más información, consulte Cualquier carácter.
Una categoría general o un bloque con nombre Unicode. Para que se produzca una coincidencia, un
carácter de la cadena de entrada debe ser miembro de una categoría Unicode determinada o debe estar
dentro de un intervalo contiguo de caracteres Unicode. Para obtener más información, consulte Categoría
Unicode o bloque Unicode.
Un bloque con nombre o una categoría general negativa Unicode. Para que se produzca una coincidencia,
un carácter de la cadena de entrada no debe ser miembro de una categoría Unicode determinada o no
debe estar dentro de un intervalo contiguo de caracteres Unicode. Para obtener más información, consulte
Categoría Unicode o bloque Unicode negativo.
Un carácter de palabra. Un carácter de la cadena de entrada puede pertenecer a cualquiera de las
categorías Unicode que son adecuadas para los caracteres que se usan para formar palabras. Para obtener
más información, consulte Carácter de palabra.
Un carácter que no se usa en las palabras. Un carácter de la cadena de entrada puede pertenecer a
cualquier categoría Unicode que no se usa para formar palabras. Para obtener más información, consulte
Carácter que no se usa en las palabras.
Un carácter de espacio en blanco. Un carácter de la cadena de entrada puede ser cualquiera de los
caracteres separadores Unicode, así como cualquiera de los caracteres de una serie de caracteres de
control. Para obtener más información, consulte Carácter de espacio en blanco.
Un carácter que no sea un espacio en blanco. Un carácter de la cadena de entrada puede ser cualquier
carácter que no sea un espacio en blanco. Para obtener más información, consulte Carácter que no sea un
espacio en blanco.
Un dígito decimal. Un carácter de la cadena de entrada puede ser cualquiera de los caracteres clasificados
como dígitos decimales de Unicode. Para obtener más información, consulte Carácter de dígito decimal.
Un carácter que no sea un dígito decimal. Un carácter de la cadena de entrada puede ser cualquier
carácter que no sea un dígito decimal de Unicode. Para obtener más información, consulte Carácter de
dígito decimal.
.NET admite expresiones de sustracción de clases de caracteres, que permiten definir un conjunto de caracteres
como el resultado de excluir una clase de caracteres de otra clase de caracteres. Para obtener más información,
consulte Sustracción de clases de caracteres.

NOTE
Las clases que coinciden con los caracteres por categoría, como \w para que coincidan con caracteres alfabéticos o \p{} para
que coincidan con una categoría Unicode, que se basan en la clase CharUnicodeInfo para proporcionar información sobre
las categorías de caracteres. A partir de .NET Framework 4.6.2, las categorías de caracteres se basan en el estándar Unicode,
versión 8.0.0. Desde .NET Framework 4 hasta .NET Framework 4.6.1, se basan en el estándar Unicode, versión 6.3.0.

Grupo de caracteres positivos: [ ]


Un grupo de caracteres positivos especifica una lista de caracteres cualquiera de los cuales puede aparecer en
una cadena de entrada para que se produzca una coincidencia. Los caracteres de la lista se pueden especificar
individualmente, como un intervalo o de ambas formas.
La sintaxis para especificar la lista de caracteres individuales es la siguiente:
[*character_group*]

donde grupo_caracteres es una lista de cada uno de los caracteres que pueden aparecer en la cadena de entrada
para que se produzca una coincidencia. grupo_caracteres puede estar formado por cualquier combinación de
uno o varios caracteres literales, caracteres de escape o clases de caracteres.
La sintaxis para especificar un intervalo de caracteres es la siguiente:
[firstCharacter-lastCharacter]

donde firstCharacter es el carácter que comienza el intervalo y lastCharacter es el carácter final del intervalo. Un
intervalo de caracteres es una serie contigua de caracteres que se define especificando el primer carácter de la
serie, un guion (-) y, a continuación, el último carácter de la serie. Dos caracteres son contiguos si tienen puntos
de código Unicode adyacentes. firstCharacter debe ser el carácter con el punto de código inferior y lastCharacter
debe ser el carácter con el punto de código superior.

NOTE
Dado que un grupo de caracteres positivos puede incluir un conjunto de caracteres y un rango de caracteres, un carácter
de guión ( - ) siempre se interpreta como el separador de rango, a menos que sea el primer carácter del grupo o el último.

En la tabla siguiente se recogen algunos de los patrones de expresiones regulares comunes que contienen clases
de caracteres positivos.

M O DELO DESC RIP C IÓ N

[aeiou] Coincide con todas las vocales.

[\p{P}\d] Coincide con todos los caracteres de puntuación y de dígitos


decimales.

[\s\p{P}] Coincide con todos los caracteres de espacio en blanco y de


puntuación.

En el ejemplo siguiente se define un grupo de caracteres positivos que contiene los caracteres "a" y "e" de
manera que la cadena de entrada deba contener las palabras "grey" o "gray" seguidas de cualquier otra palabra
para que se produzca una coincidencia.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"gr[ae]y\s\S+?[\s\p{P}]";
string input = "The gray wolf jumped over the grey wall.";
MatchCollection matches = Regex.Matches(input, pattern);
foreach (Match match in matches)
Console.WriteLine($"'{match.Value}'");
}
}
// The example displays the following output:
// 'gray wolf '
// 'grey wall.'

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "gr[ae]y\s\S+?[\s\p{P}]"
Dim input As String = "The gray wolf jumped over the grey wall."
Dim matches As MatchCollection = Regex.Matches(input, pattern)
For Each match As Match In matches
Console.WriteLine($"'{match.Value}'")
Next
End Sub
End Module
' The example displays the following output:
' 'gray wolf '
' 'grey wall.'

La expresión regular gr[ae]y\s\S+?[\s|\p{P}] se define de la siguiente manera:

M O DELO DESC RIP C IÓ N

gr Coincide con los caracteres literales "gr".

[ae] Coincide con una "a" o una "e".

y\s Coincide con el carácter literal "y" seguido de un carácter de


espacio en blanco.

\S+? Coincide con uno o varios caracteres que no sean un espacio


en blanco, pero con la menor cantidad posible de caracteres.

[\s\p{P}] Coincide con un carácter de espacio en blanco o un signo de


puntuación.

En el ejemplo siguiente se buscan palabras que comienzan por cualquier letra mayúscula. Utiliza la subexpresión
[A-Z] para representar el intervalo de letras mayúsculas de la A a la Z.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b[A-Z]\w*\b";
string input = "A city Albany Zulu maritime Marseilles";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// A
// Albany
// Zulu
// Marseilles

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b[A-Z]\w*\b"
Dim input As String = "A city Albany Zulu maritime Marseilles"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module

La expresión regular \b[A-Z]\w*\b se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

[A-Z] Coincide con cualquier letra mayúscula de la A a la Z.

\w* Buscar una coincidencia con cero o más caracteres


alfabéticos.

\b Coincide con un límite de palabras.

Grupo de caracteres negativos: [^]


Un grupo de caracteres negativos especifica una lista de caracteres que no deben aparecer en una cadena de
entrada para que se produzca una coincidencia. Los caracteres de la lista se pueden especificar individualmente,
como un intervalo o de ambas formas.
La sintaxis para especificar la lista de caracteres individuales es la siguiente:
[*^character_group*]

donde grupo_caracteres es una lista de cada uno de los caracteres que no pueden aparecer en la cadena de
entrada para que se produzca una coincidencia. grupo_caracteres puede estar formado por cualquier
combinación de uno o varios caracteres literales, caracteres de escape o clases de caracteres.
La sintaxis para especificar un intervalo de caracteres es la siguiente:
[^*firstCharacter*-*lastCharacter*]

donde firstCharacter es el carácter que comienza el intervalo y lastCharacter es el carácter final del intervalo. Un
intervalo de caracteres es una serie contigua de caracteres que se define especificando el primer carácter de la
serie, un guion (-) y, a continuación, el último carácter de la serie. Dos caracteres son contiguos si tienen puntos
de código Unicode adyacentes. firstCharacter debe ser el carácter con el punto de código inferior y lastCharacter
debe ser el carácter con el punto de código superior.

NOTE
Dado que un grupo de caracteres negativos puede incluir un conjunto de caracteres y un rango de caracteres, un carácter
de guión ( - ) siempre se interpreta como el separador de rango, a menos que sea el primer carácter del grupo o el último.

Es posible concatenar dos o más intervalos de caracteres. Por ejemplo, para especificar el intervalo de dígitos
decimales del "0" al "9", el intervalo de letras minúsculas de la "a" a la "f" y el intervalo de letras mayúsculas de la
"A" a la "F", utilice [0-9a-fA-F] .
El carácter inicial de acento circunflejo ( ^ ) de un grupo de caracteres negativos es obligatorio e indica que el
grupo de caracteres es un grupo de caracteres negativos en lugar de un grupo de caracteres positivos.

IMPORTANT
Un grupo de caracteres negativos dentro de un patrón de expresión regular más grande no es una aserción de ancho cero.
Es decir, después de evaluar el grupo de caracteres negativos, el motor de expresiones regulares avanza un carácter en la
cadena de entrada.

En la tabla siguiente se recogen algunos de los patrones de expresiones regulares comunes que contienen
grupos de caracteres negativos.

M O DELO DESC RIP C IÓ N

[^aeiou] Coincide con todos los caracteres excepto las vocales.

[^\p{P}\d] Coincide con todos los caracteres excepto los caracteres de


puntuación y de dígitos decimales.

En el ejemplo siguiente se busca cualquier palabra que comience por los caracteres "th" y no vaya seguida de
una "o".
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\bth[^o]\w+\b";
string input = "thought thing though them through thus thorough this";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// thing
// them
// through
// thus
// this

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\bth[^o]\w+\b"
Dim input As String = "thought thing though them through thus " + _
"thorough this"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' thing
' them
' through
' thus
' this

La expresión regular \bth[^o]\w+\b se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

th Coincide con los caracteres literales "th".

[^o] Coincide con cualquier carácter que no sea una "o".

\w+ Buscar coincidencias con uno o más caracteres alfabéticos.

\b Finaliza en un límite de palabras.

Cualquier carácter: .
El carácter de punto (.) coincide con cualquier carácter excepto con \n (carácter de nueva línea, \u000A), con los
dos requisitos siguientes:
Si la opción RegexOptions.Singleline modifica un patrón de expresión regular o si la opción . modifica la
parte del patrón que contiene la clase de caracteres s , . coincide con cualquier carácter. Para obtener
más información, consulte Opciones de expresiones regulares.
El ejemplo siguiente muestra el comportamiento predeterminado de la clase de caracteres . y con la
opción RegexOptions.Singleline. La expresión regular ^.+ comienza en el principio de la cadena y
coincide con todos los caracteres. De forma predeterminada, la coincidencia termina al final de la primera
línea; el patrón de la expresión regular coincide con el carácter de retorno de carro, \r o \u000D, pero no
coincide con \n . Dado que la opción RegexOptions.Singleline interpreta la cadena de entrada completa
como una sola línea, coincide con cada carácter de la cadena de entrada, incluido \n .

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "^.+";
string input = "This is one line and" + Environment.NewLine + "this is the second.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(Regex.Escape(match.Value));

Console.WriteLine();
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.Singleline))
Console.WriteLine(Regex.Escape(match.Value));
}
}
// The example displays the following output:
// This\ is\ one\ line\ and\r
//
// This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^.+"
Dim input As String = "This is one line and" + vbCrLf + "this is the second."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(Regex.Escape(match.Value))
Next
Console.WriteLine()
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.SingleLine)
Console.WriteLine(Regex.Escape(match.Value))
Next
End Sub
End Module
' The example displays the following output:
' This\ is\ one\ line\ and\r
'
' This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.

NOTE
Dado que coincide con cualquier carácter excepto con \n , la clase de caracteres . también coincide con \r (el carácter
de retorno de carro, \u000D).

En un grupo de caracteres positivos o negativos, un punto se considera un carácter de punto literal, no


una clase de caracteres. Para más información, consulte las secciones Grupo de caracteres positivos y
Grupo de caracteres negativos anteriormente en este tema. En el ejemplo siguiente se define una
expresión regular que incluye el carácter de punto ( . ) como una clase de caracteres y como un miembro
de un grupo de caracteres positivos. La expresión regular \b.*[.?!;:](\s|\z) comienza en un límite de
palabras, coincide con cualquier carácter hasta que encuentra uno de cinco signos de puntuación, incluido
el punto, y después coincide con un carácter de espacio en blanco o con el final de la cadena.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b.*[.?!;:](\s|\z)";
string input = "this. what: is? go, thing.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// this. what: is? go, thing.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As STring = "\b.*[.?!;:](\s|\z)"
Dim input As String = "this. what: is? go, thing."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' this. what: is? go, thing.

NOTE
Dado que coincide con cualquier carácter, el elemento del lenguaje . se utiliza a menudo con un cuantificador no
expansivo si un patrón de expresión regular intenta coincidir varias veces con cualquier carácter. Para obtener más
información, consulte Cuantificadores.

Categoría Unicode o bloque Unicode: \p{}


El estándar Unicode asigna una categoría general a cada carácter. Por ejemplo, un carácter concreto puede ser
una letra mayúscula (representada por la categoría Lu ), un dígito decimal (categoría Nd ), un símbolo
matemático (categoría Sm ) o un separador de párrafos (categoría Zl ). Determinados juegos de caracteres del
estándar Unicode también ocupan un intervalo o bloque específico de puntos de código consecutivos. Por
ejemplo, el juego de caracteres latinos básico se encuentra desde \u0000 hasta \u007F, mientras que el juego de
caracteres árabes se encuentra desde \u0600 hasta \u06FF.
La construcción de expresión regular
\p{ nombre }

coincide con cualquier carácter que pertenezca a una categoría general o bloque con nombre de Unicode, donde
nombre es la abreviatura de la categoría o el nombre del bloque con nombre. Para obtener una lista de
abreviaturas de categorías, consulte la sección Categorías generales Unicode compatibles más adelante en este
tema. Para obtener una lista de bloques con nombre, consulte la sección Bloques con nombre compatibles más
adelante en este tema.
En el ejemplo siguiente se usa la construcción \p{ nombre } para buscar coincidencias con una categoría
general de Unicode (en este caso, Pd o Punctuation, Dash) y un bloque con nombre (los bloques con nombre
IsGreek e IsBasicLatin ).

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\p{IsGreek}+(\s)?)+\p{Pd}\s(\p{IsBasicLatin}+(\s)?)+";
string input = "Κατα Μαθθαίον - The Gospel of Matthew";

Console.WriteLine(Regex.IsMatch(input, pattern)); // Displays True.


}
}

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\p{IsGreek}+(\s)?)+\p{Pd}\s(\p{IsBasicLatin}+(\s)?)+"
Dim input As String = "Κατα Μαθθαίον - The Gospel of Matthew"

Console.WriteLine(Regex.IsMatch(input, pattern)) ' Displays True.


End Sub
End Module

La expresión regular \b(\p{IsGreek}+(\s)?)+\p{Pd}\s(\p{IsBasicLatin}+(\s)?)+ se define como se muestra en la


tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

\p{IsGreek}+ Coincide con uno o varios caracteres griegos.

(\s)? Busca coincidencias con cero o un carácter de espacio en


blanco.

(\p{IsGreek}+(\s)?)+ Coincide una o varias veces con el patrón de uno o varios


caracteres griegos seguidos de cero o un carácter de espacio
en blanco.

\p{Pd} Coincide con un carácter de puntuación, guion.

\s Coincide con un carácter de espacio en blanco.

\p{IsBasicLatin}+ Coincide con uno o varios caracteres latinos básicos.


M O DELO DESC RIP C IÓ N

(\s)? Busca coincidencias con cero o un carácter de espacio en


blanco.

(\p{IsBasicLatin}+(\s)?)+ Coincide una o varias veces con el patrón de uno varios


caracteres latinos básicos seguidos de cero o un carácter de
espacio en blanco.

Categoría Unicode o bloque Unicode negativo: \P{}


El estándar Unicode asigna una categoría general a cada carácter. Por ejemplo, un carácter concreto puede ser
una letra mayúscula (representada por la categoría Lu ), un dígito decimal (categoría Nd ), un símbolo
matemático (categoría Sm ) o un separador de párrafos (categoría Zl ). Determinados juegos de caracteres del
estándar Unicode también ocupan un intervalo o bloque específico de puntos de código consecutivos. Por
ejemplo, el juego de caracteres latinos básico se encuentra desde \u0000 hasta \u007F, mientras que el juego de
caracteres árabes se encuentra desde \u0600 hasta \u06FF.
La construcción de expresión regular
\P{ nombre }

coincide con cualquier carácter que no pertenezca a una categoría general o bloque con nombre de Unicode,
donde nombre es la abreviatura de la categoría o el nombre del bloque con nombre. Para obtener una lista de
abreviaturas de categorías, consulte la sección Categorías generales Unicode compatibles más adelante en este
tema. Para obtener una lista de bloques con nombre, consulte la sección Bloques con nombre compatibles más
adelante en este tema.
En el siguiente ejemplo se usa la construcción \P{ nombre } para quitar cualquier símbolo de divisa (en este
caso, la categoría Sc , o Symbol, Currency) de las cadenas numéricas.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(\P{Sc})+";

string[] values = { "$164,091.78", "£1,073,142.68", "73¢", "€120" };


foreach (string value in values)
Console.WriteLine(Regex.Match(value, pattern).Value);
}
}
// The example displays the following output:
// 164,091.78
// 1,073,142.68
// 73
// 120
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(\P{Sc})+"

Dim values() As String = {"$164,091.78", "£1,073,142.68", "73¢", "€120"}


For Each value As String In values
Console.WriteLine(Regex.Match(value, pattern).Value)
Next
End Sub
End Module
' The example displays the following output:
' 164,091.78
' 1,073,142.68
' 73
' 120

El patrón de expresión regular (\P{Sc})+ coincide con uno o varios caracteres que no son símbolos de divisa;
quita eficazmente cualquier símbolo de divisa de la cadena de resultado.

Carácter de palabra: \w
\w coincide con cualquier carácter de palabra. Un carácter de palabra es un miembro de alguna de las
categorías Unicode enumeradas en la tabla siguiente.

C AT EGO RÍA DESC RIP C IÓ N

Ll Letra, minúscula

Lu Letra, mayúscula

Lt Letra, inicial en mayúscula

Lo Letra, otra

Lm Letra, modificador

Mn Marca, sin espacios

Nd Número, dígito decimal

Pc Puntuación, Conector Esta categoría incluye diez caracteres,


el más usado de los cuales es el carácter LOWLINE (),
u+005F.

Si se especifica un comportamiento conforme a ECMAScript, \w es equivalente a [a-zA-Z_0-9] . Para obtener


información sobre las expresiones regulares ECMAScript, consulte la sección "Comportamiento de la búsqueda
de coincidencias de ECMAScript" en Opciones de expresiones regulares.

NOTE
Dado que coincide con cualquier carácter de palabra, el elemento del lenguaje \w se suele usar con un cuantificador
diferido si un patrón de expresión regular intenta coincidir varias veces con cualquier carácter de palabra, seguido de un
carácter de palabra específico. Para obtener más información, consulte Cuantificadores.
En el ejemplo siguiente se usa el elemento del lenguaje \w para buscar coincidencias de caracteres duplicados
en una palabra. El ejemplo define un patrón de expresión regular, (\w)\1 , que se puede interpretar de la
siguiente manera.

EL EM EN TO DESC RIP C IÓ N

(\w) Coincide con un carácter de palabra. Este es el primer grupo


de captura.

\1 Coincide con el valor de la primera captura.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(\w)\1";
string[] words = { "trellis", "seer", "latter", "summer",
"hoarse", "lesser", "aardvark", "stunned" };
foreach (string word in words)
{
Match match = Regex.Match(word, pattern);
if (match.Success)
Console.WriteLine("'{0}' found in '{1}' at position {2}.",
match.Value, word, match.Index);
else
Console.WriteLine("No double characters in '{0}'.", word);
}
}
}
// The example displays the following output:
// 'll' found in 'trellis' at position 3.
// 'ee' found in 'seer' at position 1.
// 'tt' found in 'latter' at position 2.
// 'mm' found in 'summer' at position 2.
// No double characters in 'hoarse'.
// 'ss' found in 'lesser' at position 2.
// 'aa' found in 'aardvark' at position 0.
// 'nn' found in 'stunned' at position 3.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(\w)\1"
Dim words() As String = {"trellis", "seer", "latter", "summer", _
"hoarse", "lesser", "aardvark", "stunned"}
For Each word As String In words
Dim match As Match = Regex.Match(word, pattern)
If match.Success Then
Console.WriteLine("'{0}' found in '{1}' at position {2}.", _
match.Value, word, match.Index)
Else
Console.WriteLine("No double characters in '{0}'.", word)
End If
Next
End Sub
End Module
' The example displays the following output:
' 'll' found in 'trellis' at position 3.
' 'ee' found in 'seer' at position 1.
' 'tt' found in 'latter' at position 2.
' 'mm' found in 'summer' at position 2.
' No double characters in 'hoarse'.
' 'ss' found in 'lesser' at position 2.
' 'aa' found in 'aardvark' at position 0.
' 'nn' found in 'stunned' at position 3.

Carácter que no se usa para formar palabras: \W


\W coincide con cualquier carácter que no sea de palabra. El elemento del lenguaje \W es equivalente a la clase
de caracteres siguiente:
[^\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Pc}\p{Lm}]

En otras palabras, coincide con cualquier carácter excepto con los que figuran en las categorías Unicode de la
tabla siguiente.

C AT EGO RÍA DESC RIP C IÓ N

Ll Letra, minúscula

Lu Letra, mayúscula

Lt Letra, inicial en mayúscula

Lo Letra, otra

Lm Letra, modificador

Mn Marca, sin espacios

Nd Número, dígito decimal

Pc Puntuación, Conector Esta categoría incluye diez caracteres,


el más usado de los cuales es el carácter LOWLINE (),
u+005F.

Si se especifica un comportamiento conforme a ECMAScript, \W es equivalente a [^a-zA-Z_0-9] . Para obtener


información sobre las expresiones regulares ECMAScript, consulte la sección "Comportamiento de la búsqueda
de coincidencias de ECMAScript" en Opciones de expresiones regulares.

NOTE
Dado que coincide con cualquier carácter que no sea de palabra, el elemento del lenguaje \W se suele usar con un
cuantificador diferido si un patrón de expresión regular intenta coincidir varias veces con cualquier carácter que no sea de
palabra, seguido de un carácter que no sea de palabra específico. Para obtener más información, consulte Cuantificadores.

En el ejemplo siguiente se ilustra la clase de caracteres \W . Define un patrón de expresión regular,


\b(\w+)(\W){1,2} , que coincide con una palabra seguida de uno o dos caracteres que no son de palabra, como
un espacio en blanco o un signo de puntuación. La expresión regular se interpreta como se muestra en la tabla
siguiente.

EL EM EN TO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

(\w+) Buscar coincidencias con uno o más caracteres alfabéticos.


Este es el primer grupo de captura.

(\W){1,2} Coincide una o dos veces con un carácter que no se usa para
formar palabras. Este es el segundo grupo de captura.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\w+)(\W){1,2}";
string input = "The old, grey mare slowly walked across the narrow, green pasture.";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine(match.Value);
Console.Write(" Non-word character(s):");
CaptureCollection captures = match.Groups[2].Captures;
for (int ctr = 0; ctr < captures.Count; ctr++)
Console.Write(@"'{0}' (\u{1}){2}", captures[ctr].Value,
Convert.ToUInt16(captures[ctr].Value[0]).ToString("X4"),
ctr < captures.Count - 1 ? ", " : "");
Console.WriteLine();
}
}
}
// The example displays the following output:
// The
// Non-word character(s):' ' (\u0020)
// old,
// Non-word character(s):',' (\u002C), ' ' (\u0020)
// grey
// Non-word character(s):' ' (\u0020)
// mare
// Non-word character(s):' ' (\u0020)
// slowly
// Non-word character(s):' ' (\u0020)
// walked
// Non-word character(s):' ' (\u0020)
// across
// Non-word character(s):' ' (\u0020)
// the
// Non-word character(s):' ' (\u0020)
// narrow,
// Non-word character(s):',' (\u002C), ' ' (\u0020)
// green
// Non-word character(s):' ' (\u0020)
// pasture.
// Non-word character(s):'.' (\u002E)
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\w+)(\W){1,2}"
Dim input As String = "The old, grey mare slowly walked across the narrow, green pasture."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Console.Write(" Non-word character(s):")
Dim captures As CaptureCollection = match.Groups(2).Captures
For ctr As Integer = 0 To captures.Count - 1
Console.Write("'{0}' (\u{1}){2}", captures(ctr).Value, _
Convert.ToUInt16(captures(ctr).Value.Chars(0)).ToString("X4"), _
If(ctr < captures.Count - 1, ", ", ""))
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' The
' Non-word character(s):' ' (\u0020)
' old,
' Non-word character(s):',' (\u002C), ' ' (\u0020)
' grey
' Non-word character(s):' ' (\u0020)
' mare
' Non-word character(s):' ' (\u0020)
' slowly
' Non-word character(s):' ' (\u0020)
' walked
' Non-word character(s):' ' (\u0020)
' across
' Non-word character(s):' ' (\u0020)
' the
' Non-word character(s):' ' (\u0020)
' narrow,
' Non-word character(s):',' (\u002C), ' ' (\u0020)
' green
' Non-word character(s):' ' (\u0020)
' pasture.
' Non-word character(s):'.' (\u002E)

Dado que el objeto Group del segundo grupo de captura contiene solo un carácter que no se usa para formar
palabras, el ejemplo recupera todos los caracteres que no se usan para formar palabras capturados del objeto
CaptureCollection que devuelve la propiedad Group.Captures.

Carácter de espacio en blanco: \s


\s coincide con cualquier carácter de espacio en blanco. Equivale a las secuencias de escape y las categorías
Unicode que figuran en la tabla siguiente.

C AT EGO RÍA DESC RIP C IÓ N

\f El carácter de avance de página, \u000C.

\n El carácter de nueva línea, \u000A.

\r El carácter de retorno de carro, \u000D.

\t El carácter de tabulación, \u0009.


C AT EGO RÍA DESC RIP C IÓ N

\v El carácter de tabulación vertical, \u000B.

\x85 Los puntos suspensivos o el carácter de LÍNEA SIGUIENTE


(…), \u0085.

\p{Z} Coincide con cualquier carácter separador.

Si se especifica un comportamiento conforme a ECMAScript, \s es equivalente a [ \f\n\r\t\v] . Para obtener


información sobre las expresiones regulares ECMAScript, consulte la sección "Comportamiento de la búsqueda
de coincidencias de ECMAScript" en Opciones de expresiones regulares.
En el ejemplo siguiente se ilustra la clase de caracteres \s . Define un patrón de expresión regular,
\b\w+(e)?s(\s|$) , que coincide con una palabra que termina por "s" o "es" seguida de un carácter de espacio en
blanco o el final de la cadena de entrada. La expresión regular se interpreta como se muestra en la tabla
siguiente.

EL EM EN TO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

\w+ Buscar coincidencias con uno o más caracteres alfabéticos.

(e)? Coincide cero o una vez con una "e".

s Coincide con una "s".

(\s|$) Coincide con un carácter de espacio en blanco o el final de la


cadena de entrada.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b\w+(e)?s(\s|$)";
string input = "matches stores stops leave leaves";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// matches
// stores
// stops
// leaves
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b\w+(e)?s(\s|$)"
Dim input As String = "matches stores stops leave leaves"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' matches
' stores
' stops
' leaves

Carácter que no sea un espacio en blanco: \S


\S coincide con cualquier carácter que no sea un espacio en blanco. Equivale al patrón de expresión regular
[^\f\n\r\t\v\x85\p{Z}] o es lo contrario del patrón de expresión regular equivalente a \s , que coincide con los
caracteres de espacio en blanco. Para más información, consulte Carácter de espacio en blanco: \s.
Si se especifica un comportamiento conforme a ECMAScript, \S es equivalente a [^ \f\n\r\t\v] . Para obtener
información sobre las expresiones regulares ECMAScript, consulte la sección "Comportamiento de la búsqueda
de coincidencias de ECMAScript" en Opciones de expresiones regulares.
En el ejemplo siguiente se ilustra el elemento del lenguaje \S . El patrón de expresión regular \b(\S+)\s?
coincide con cadenas delimitadas por caracteres de espacio en blanco. El segundo elemento del objeto
GroupCollection de la coincidencia contiene la cadena coincidente. La expresión regular puede interpretarse
como se muestra en la tabla siguiente.

EL EM EN TO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

(\S+) Coincide con caracteres que no son espacio en blanco. Este


es el primer grupo de captura.

\s? Busca coincidencias con cero o un carácter de espacio en


blanco.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\S+)\s?";
string input = "This is the first sentence of the first paragraph. " +
"This is the second sentence.\n" +
"This is the only sentence of the second paragraph.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Groups[1]);
}
}
// The example displays the following output:
// This
// is
// the
// first
// sentence
// of
// the
// first
// paragraph.
// This
// is
// the
// second
// sentence.
// This
// is
// the
// only
// sentence
// of
// the
// second
// paragraph.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\S+)\s?"
Dim input As String = "This is the first sentence of the first paragraph. " + _
"This is the second sentence." + vbCrLf + _
"This is the only sentence of the second paragraph."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Groups(1))
Next
End Sub
End Module
' The example displays the following output:
' This
' is
' the
' first
' sentence
' of
' the
' first
' paragraph.
' This
' is
' the
' second
' sentence.
' This
' is
' the
' only
' sentence
' of
' the
' second
' paragraph.

Carácter de dígito decimal: \d


\d coincide con cualquier dígito decimal. Equivale al patrón de expresión regular \p{Nd} , que incluye los
dígitos decimales estándar 0-9 así como los dígitos decimales de varios juegos de caracteres.
Si se especifica un comportamiento conforme a ECMAScript, \d es equivalente a [0-9] . Para obtener
información sobre las expresiones regulares ECMAScript, consulte la sección "Comportamiento de la búsqueda
de coincidencias de ECMAScript" en Opciones de expresiones regulares.
En el ejemplo siguiente se ilustra el elemento del lenguaje \d . Comprueba si una cadena de entrada representa
un número de teléfono válido de los Estados Unidos y Canadá. El patrón de expresión regular
^(\(?\d{3}\)?[\s-])?\d{3}-\d{4}$ se define como se muestra en la tabla siguiente.

EL EM EN TO DESC RIP C IÓ N

^ Iniciar la búsqueda de coincidencias con el principio de la


cadena de entrada.

\(? Coincide con cero o un carácter "(" literal.

\d{3} Coincide con tres dígitos decimales.


EL EM EN TO DESC RIP C IÓ N

\)? Coincide con cero o un carácter ")" literal.

[\s-] Coincide con un guion o un carácter de espacio en blanco.

(\(?\d{3}\)?[\s-])? Coincide cero o una vez con un paréntesis de apertura


opcional seguido de tres dígitos decimales, un paréntesis de
cierre opcional y un carácter de espacio en blanco o un
guion. Este es el primer grupo de captura.

\d{3}-\d{4} Coincide con tres dígitos decimales seguidos de un guion y


otros cuatro dígitos decimales.

$ Coincide con el final de la cadena de entrada.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"^(\(?\d{3}\)?[\s-])?\d{3}-\d{4}$";
string[] inputs = { "111 111-1111", "222-2222", "222 333-444",
"(212) 111-1111", "111-AB1-1111",
"212-111-1111", "01 999-9999" };

foreach (string input in inputs)


{
if (Regex.IsMatch(input, pattern))
Console.WriteLine(input + ": matched");
else
Console.WriteLine(input + ": match failed");
}
}
}
// The example displays the following output:
// 111 111-1111: matched
// 222-2222: matched
// 222 333-444: match failed
// (212) 111-1111: matched
// 111-AB1-1111: match failed
// 212-111-1111: matched
// 01 999-9999: match failed
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^(\(?\d{3}\)?[\s-])?\d{3}-\d{4}$"
Dim inputs() As String = {"111 111-1111", "222-2222", "222 333-444", _
"(212) 111-1111", "111-AB1-1111", _
"212-111-1111", "01 999-9999"}

For Each input As String In inputs


If Regex.IsMatch(input, pattern) Then
Console.WriteLine(input + ": matched")
Else
Console.WriteLine(input + ": match failed")
End If
Next
End Sub
End Module
' The example displays the following output:
' 111 111-1111: matched
' 222-2222: matched
' 222 333-444: match failed
' (212) 111-1111: matched
' 111-AB1-1111: match failed
' 212-111-1111: matched
' 01 999-9999: match failed

Carácter que no sea un dígito: \D


\D coincide con cualquier carácter que no sea un dígito. Equivale al patrón de expresión regular \P{Nd} .
Si se especifica un comportamiento conforme a ECMAScript, \D es equivalente a [^0-9] . Para obtener
información sobre las expresiones regulares ECMAScript, consulte la sección "Comportamiento de la búsqueda
de coincidencias de ECMAScript" en Opciones de expresiones regulares.
En el ejemplo siguiente se muestra el elemento del lenguaje \D. Comprueba si una cadena, como un número de
pieza, consta de la combinación adecuada de caracteres decimales y no decimales. El patrón de expresión regular
^\D\d{1,5}\D*$ se define como se muestra en la tabla siguiente.

EL EM EN TO DESC RIP C IÓ N

^ Iniciar la búsqueda de coincidencias con el principio de la


cadena de entrada.

\D Coincide con un carácter que no sea un dígito.

\d{1,5} Coincide con entre uno y cinco dígitos decimales.

\D* Coincide con cero, uno o más caracteres no decimales.

$ Coincide con el final de la cadena de entrada.


using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"^\D\d{1,5}\D*$";
string[] inputs = { "A1039C", "AA0001", "C18A", "Y938518" };

foreach (string input in inputs)


{
if (Regex.IsMatch(input, pattern))
Console.WriteLine(input + ": matched");
else
Console.WriteLine(input + ": match failed");
}
}
}
// The example displays the following output:
// A1039C: matched
// AA0001: match failed
// C18A: matched
// Y938518: match failed

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^\D\d{1,5}\D*$"
Dim inputs() As String = {"A1039C", "AA0001", "C18A", "Y938518"}

For Each input As String In inputs


If Regex.IsMatch(input, pattern) Then
Console.WriteLine(input + ": matched")
Else
Console.WriteLine(input + ": match failed")
End If
Next
End Sub
End Module
' The example displays the following output:

Categorías generales Unicode compatibles


Unicode define las categorías generales que se muestran en la tabla siguiente. Para obtener más información,
consulte las secciones sobre el "formato de archivo UCD" y los "valores de categorías generales" en la base de
datos de caracteres Unicode.

C AT EGO RÍA DESC RIP C IÓ N

Lu Letra, mayúscula

Ll Letra, minúscula

Lt Letra, inicial en mayúscula

Lm Letra, modificador
C AT EGO RÍA DESC RIP C IÓ N

Lo Letra, otra

L Todos los caracteres de letras. Esto incluye los caracteres Lu ,


Ll , Lt , Lm y Lo .

Mn Marca, sin espacios

Mc Marca, con espacios y combinación

Me Marca, inclusión

M Todas las marcas diacríticas. Esto incluye las categorías Mn ,


Mc y Me .

Nd Número, dígito decimal

Nl Número, letra

No Número, otro

N Todos los números. Esto incluye las categorías Nd , Nl y


No .

Pc Puntuación, conector

Pd Puntuación, raya

Ps Puntuación, abrir

Pe Puntuación, cerrar

Pi Puntuación, comilla de apertura (puede comportarse como


Ps o Pe, en función del uso)

Pf Puntuación, comilla de cierre (puede comportarse como Ps o


Pe, en función del uso)

Po Puntuación, otro

P Todos los signos de puntuación. Esto incluye las categorías


Pc , Pd , Ps , Pe , Pi , Pf y Po .

Sm Símbolo, matemático

Sc Símbolo, divisa

Sk Símbolo, modificador

So Símbolo, otro
C AT EGO RÍA DESC RIP C IÓ N

S Todos los símbolos. Esto incluye las categorías Sm , Sc , Sk


y So .

Zs Separador, espacio

Zl Separador, línea

Zp Separador, párrafo

Z Todos los caracteres separadores. Esto incluye las categorías


Zs , Zl y Zp .

Cc Otro, control

Cf Otro, formato

Cs Otro, suplente

Co Otro, uso privado

Cn Otro, no asignado (ningún carácter tiene esta propiedad)

C Todos los caracteres de control. Esto incluye las categorías


Cc , Cf , Cs , Co y Cn .

Puede determinar la categoría Unicode de cualquier carácter concreto pasando dicho carácter al método
GetUnicodeCategory. En el ejemplo siguiente se utiliza el método GetUnicodeCategory para determinar la
categoría de cada elemento de una matriz que contiene determinados caracteres latinos.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
char[] chars = { 'a', 'X', '8', ',', ' ', '\u0009', '!' };

foreach (char ch in chars)


Console.WriteLine("'{0}': {1}", Regex.Escape(ch.ToString()),
Char.GetUnicodeCategory(ch));
}
}
// The example displays the following output:
// 'a': LowercaseLetter
// 'X': UppercaseLetter
// '8': DecimalDigitNumber
// ',': OtherPunctuation
// '\ ': SpaceSeparator
// '\t': Control
// '!': OtherPunctuation
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim chars() As Char = {"a"c, "X"c, "8"c, ","c, " "c, ChrW(9), "!"c}

For Each ch As Char In chars


Console.WriteLine("'{0}': {1}", Regex.Escape(ch.ToString()), _
Char.GetUnicodeCategory(ch))
Next
End Sub
End Module
' The example displays the following output:
' 'a': LowercaseLetter
' 'X': UppercaseLetter
' '8': DecimalDigitNumber
' ',': OtherPunctuation
' '\ ': SpaceSeparator
' '\t': Control
' '!': OtherPunctuation

Bloques con nombre compatibles


.NET proporciona los bloques con nombre que se muestran en la tabla siguiente. El conjunto de bloques con
nombre compatibles está basado en Unicode 4.0 y Perl 5.6. Para una expresión regular que utiliza bloques con
nombre, consulte la sección Categoría Unicode o bloque Unicode: \p{}.

IN T ERVA LO DE P UN TO S DE C Ó DIGO N O M B RE DEL B LO Q UE

0000 - 007F IsBasicLatin

0080 - 00FF IsLatin-1Supplement

0100 - 017F IsLatinExtended-A

0180 - 024F IsLatinExtended-B

0250 - 02AF IsIPAExtensions

02B0 - 02FF IsSpacingModifierLetters

0300 - 036F IsCombiningDiacriticalMarks

0370 - 03FF IsGreek

o bien

IsGreekandCoptic

0400 - 04FF IsCyrillic

0500 - 052F IsCyrillicSupplement

0530 - 058F IsArmenian


IN T ERVA LO DE P UN TO S DE C Ó DIGO N O M B RE DEL B LO Q UE

0590 - 05FF IsHebrew

0600 - 06FF IsArabic

0700 - 074F IsSyriac

0780 - 07BF IsThaana

0900 - 097F IsDevanagari

0980 - 09FF IsBengali

0A00 - 0A7F IsGurmukhi

0A80 - 0AFF IsGujarati

0B00 - 0B7F IsOriya

0B80 - 0BFF IsTamil

0C00 - 0C7F IsTelugu

0C80 - 0CFF IsKannada

0D00 - 0D7F IsMalayalam

0D80 - 0DFF IsSinhala

0E00 - 0E7F IsThai

0E80 - 0EFF IsLao

0F00 - 0FFF IsTibetan

1000 - 109F IsMyanmar

10A0 - 10FF IsGeorgian

1100 - 11FF IsHangulJamo

1200 - 137F IsEthiopic

13A0 - 13FF IsCherokee

1400 - 167F IsUnifiedCanadianAboriginalSyllabics

1680 - 169F IsOgham


IN T ERVA LO DE P UN TO S DE C Ó DIGO N O M B RE DEL B LO Q UE

16A0 - 16FF IsRunic

1700 - 171F IsTagalog

1720 - 173F IsHanunoo

1740 - 175F IsBuhid

1760 - 177F IsTagbanwa

1780 - 17FF IsKhmer

1800 - 18AF IsMongolian

1900 - 194F IsLimbu

1950 - 197F IsTaiLe

19E0 - 19FF IsKhmerSymbols

1D00 - 1D7F IsPhoneticExtensions

1E00 - 1EFF IsLatinExtendedAdditional

1F00 - 1FFF IsGreekExtended

2000 - 206F IsGeneralPunctuation

2070 - 209F IsSuperscriptsandSubscripts

20A0 - 20CF IsCurrencySymbols

20D0 - 20FF IsCombiningDiacriticalMarksforSymbols

o bien

IsCombiningMarksforSymbols

2100 - 214F IsLetterlikeSymbols

2150 - 218F IsNumberForms

2190 - 21FF IsArrows

2200 - 22FF IsMathematicalOperators

2300 - 23FF IsMiscellaneousTechnical

2400 - 243F IsControlPictures


IN T ERVA LO DE P UN TO S DE C Ó DIGO N O M B RE DEL B LO Q UE

2440 - 245F IsOpticalCharacterRecognition

2460 - 24FF IsEnclosedAlphanumerics

2500 - 257F IsBoxDrawing

2580 - 259F IsBlockElements

25A0 - 25FF IsGeometricShapes

2600 - 26FF IsMiscellaneousSymbols

2700 - 27BF IsDingbats

27C0 - 27EF IsMiscellaneousMathematicalSymbols-A

27F0 - 27FF IsSupplementalArrows-A

2800 - 28FF IsBraillePatterns

2900 - 297F IsSupplementalArrows-B

2980 - 29FF IsMiscellaneousMathematicalSymbols-B

2A00 - 2AFF IsSupplementalMathematicalOperators

2B00 - 2BFF IsMiscellaneousSymbolsandArrows

2E80 - 2EFF IsCJKRadicalsSupplement

2F00 - 2FDF IsKangxiRadicals

2FF0 - 2FFF IsIdeographicDescriptionCharacters

3000 - 303F IsCJKSymbolsandPunctuation

3040 - 309F IsHiragana

30A0 - 30FF IsKatakana

3100 - 312F IsBopomofo

3130 - 318F IsHangulCompatibilityJamo

3190 - 319F IsKanbun

31A0 - 31BF IsBopomofoExtended


IN T ERVA LO DE P UN TO S DE C Ó DIGO N O M B RE DEL B LO Q UE

31F0 - 31FF IsKatakanaPhoneticExtensions

3200 - 32FF IsEnclosedCJKLettersandMonths

3300 - 33FF IsCJKCompatibility

3400 - 4DBF IsCJKUnifiedIdeographsExtensionA

4DC0 - 4DFF IsYijingHexagramSymbols

4E00 - 9FFF IsCJKUnifiedIdeographs

A000 - A48F IsYiSyllables

A490 - A4CF IsYiRadicals

AC00 - D7AF IsHangulSyllables

D800 - DB7F IsHighSurrogates

DB80 - DBFF IsHighPrivateUseSurrogates

DC00 - DFFF IsLowSurrogates

E000 - F8FF IsPrivateUse o IsPrivateUseArea

F900 - FAFF IsCJKCompatibilityIdeographs

FB00 - FB4F IsAlphabeticPresentationForms

FB50 - FDFF IsArabicPresentationForms-A

FE00 - FE0F IsVariationSelectors

FE20 - FE2F IsCombiningHalfMarks

FE30 - FE4F IsCJKCompatibilityForms

FE50 - FE6F IsSmallFormVariants

FE70 - FEFF IsArabicPresentationForms-B

FF00 - FFEF IsHalfwidthandFullwidthForms

FFF0 - FFFF IsSpecials

Sustracción de clases de caracteres: [grupo_base - [grupo_excluido]]


Una clase de caracteres define un conjunto de caracteres. La sustracción de clases de caracteres genera un
conjunto de caracteres que es el resultado de excluir los caracteres de una clase de caracteres de otra clase de
caracteres.
Una expresión de sustracción de clases de caracteres tiene el formato siguiente:
[ base_group -[ excluded_group ]]

Los corchetes ( [] ) y el guion ( - ) son obligatorios. El grupo_base es un grupo de caracteres positivos o un


grupo de caracteres negativos. El componente grupo_excluido es otro grupo de caracteres positivos o negativos,
u otra expresión de sustracción de clases de caracteres (es decir, pueden anidarse expresiones de sustracción de
clases de caracteres).
Por ejemplo, supongamos que tiene un grupo base formado por el intervalo de caracteres de "a" a "z". Para
definir el conjunto de caracteres formado por el grupo base, salvo el carácter "m", utilice [a-z-[m]] . Para definir
el conjunto de caracteres formado por el grupo base, salvo el conjunto de caracteres "d", "j" y "p", utilice
[a-z-[djp]] . Para definir el conjunto de caracteres formado por el grupo base, salvo el intervalo de caracteres
de "m" a "p", utilice [a-z-[m-p]] .
Considere la expresión de sustracción de clases de caracteres anidada [a-z-[d-w-[m-o]]] . La expresión se evalúa
desde el intervalo de caracteres más profundo hacia el exterior. Primero, el intervalo de caracteres de "m" a "o" se
resta del intervalo de caracteres de "d" a "w", lo que da como resultado el conjunto de caracteres de "d" a "l" y de
"p" a "w". A continuación, ese conjunto se resta del intervalo de caracteres de "a" a "z", lo que da lugar al
conjunto de caracteres [abcmnoxyz] .
Puede utilizar cualquier clase de caracteres con la sustracción de clases de caracteres. Para definir el conjunto de
caracteres formado por todos los caracteres Unicode desde \u0000 hasta \uFFFF, a excepción de los caracteres
de espacio en blanco ( \s ), los caracteres de la categoría general de puntuación ( \p{P} ), los caracteres del
bloque con nombre IsGreek ( \p{IsGreek} ) y el carácter de control Unicode NEXT LINE (\x85), use
[\u0000-\uFFFF-[\s\p{P}\p{IsGreek}\x85]] .

Elija las clases de caracteres para que una expresión de sustracción de clases de caracteres produzca resultados
satisfactorios. Evite el uso de expresiones que produzcan un conjunto vacío de caracteres, que no coincidan con
nada, o expresiones equivalentes al grupo base original. Por ejemplo, el conjunto vacío es el resultado de la
expresión [\p{IsBasicLatin}-[\x00-\x7F]] , que resta todos los caracteres del intervalo de caracteres
IsBasicLatin de la categoría general IsBasicLatin . Ocurre lo mismo con el grupo base original, que es el
resultado de la expresión [a-z-[0-9]] . Esto se debe a que el grupo base, que está formado por el intervalo de
caracteres comprendido entre las letras de la "a" a la "z", no contiene ningún carácter del grupo excluido, que es
el intervalo de caracteres formado por los dígitos decimales del "0" al "9".
En el ejemplo siguiente se define una expresión regular, ^[0-9-[2468]]+$ , que coincide con cero y con los dígitos
impares de una cadena de entrada. La expresión regular se interpreta como se muestra en la tabla siguiente.

EL EM EN TO DESC RIP C IÓ N

^ Comienza la búsqueda de coincidencias al principio de la


cadena de entrada.

[0-9-[2468]]+ Coincide con una o varias apariciones de cualquier carácter


del 0 al 9, salvo con el 2, 4, 6 y 8. En otras palabras, busca
una o varias coincidencias con cero o un dígito impar.

$ Finalizar la búsqueda de coincidencias al final de la cadena de


entrada.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "123", "13579753", "3557798", "335599901" };
string pattern = @"^[0-9-[2468]]+$";

foreach (string input in inputs)


{
Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine(match.Value);
}
}
}
// The example displays the following output:
// 13579753
// 335599901

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"123", "13579753", "3557798", "335599901"}
Dim pattern As String = "^[0-9-[2468]]+$"

For Each input As String In inputs


Dim match As Match = Regex.Match(input, pattern)
If match.Success Then Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' 13579753
' 335599901

Vea también
GetUnicodeCategory
Lenguaje de expresiones regulares: referencia rápida
Opciones de expresiones regulares
Delimitadores en expresiones regulares
16/09/2020 • 31 minutes to read • Edit Online

Los delimitadores, o aserciones atómicas de ancho cero, especifican la posición de la cadena en que se debe
producir una coincidencia. Cuando se usa un delimitador en una expresión de búsqueda, el motor de expresiones
regulares no avanza por la cadena o ni consume caracteres, sino que solo busca una coincidencia en la posición
especificada. Por ejemplo, ^ especifica que la coincidencia debe empezar al principio de una cadena o línea. Por
consiguiente, la expresión regular ^http: coincide con "http": solo cuando se encuentra al principio de una línea.
En la tabla siguiente, se enumeran los delimitadores que admiten las expresiones regulares de .NET.

DEL IM ITA DO R DESC RIP C IÓ N

^ De forma predeterminada, la coincidencia se debe producir al


principio de la cadena. Por su parte, en el modo multilínea, se
debe producir al principio de la línea. Para obtener más
información, vea Principio de cadena o línea.

$ De forma predeterminada, la coincidencia se debe producir al


final de la cadena o antes de \n al final de la cadena. Por su
parte, en el modo multilínea, se debe producir al final de la
línea o antes de \n al final de la línea. Para obtener más
información, vea Final de cadena o línea.

\A La coincidencia se debe producir solo al principio de la cadena


(no se admiten varias líneas). Para obtener más información,
vea Principio de cadena solamente.

\Z La coincidencia se debe producir al final de la cadena o antes


de \n al final de la cadena. Para obtener más información,
vea Final de cadena o antes de nueva línea al final.

\z La coincidencia se debe producir solo al final de la cadena.


Para obtener más información, vea Final de cadena solamente.

\G La coincidencia se debe iniciar en la posición en que finalizó la


coincidencia anterior. Para obtener más información, vea
Coincidencias contiguas.

\b La coincidencia se debe producir en un límite de palabras.


Para obtener más información, vea Límite de palabras.

\B La coincidencia no se debe producir en un límite de palabras.


Para obtener más información, vea Fuera de un límite de
palabras.

Principio de cadena o línea: ^


De forma predeterminada, el delimitador ^ especifica que el patrón siguiente debe comenzar en la posición del
primer carácter de la cadena. Si usa ^ con la opción RegexOptions.Multiline (vea Opciones de expresiones
regulares), la coincidencia se debe producir al principio de cada línea.
En el ejemplo siguiente se usa el delimitador ^ en una expresión regular que extrae información sobre los años
durante los que existieron algunos equipos de béisbol profesionales. En el ejemplo se llama a dos sobrecargas del
método Regex.Matches :
La llamada a la sobrecarga Matches(String, String) encuentra solo la primera subcadena que coincide con el
patrón de la expresión regular en la cadena de entrada.
La llamada a la sobrecarga Matches(String, String, RegexOptions) con el parámetro options establecido en
RegexOptions.Multiline encuentra las cinco subcadenas.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957\n" +
"Chicago Cubs, National League, 1903-present\n" +
"Detroit Tigers, American League, 1901-present\n" +
"New York Giants, National League, 1885-1957\n" +
"Washington Senators, American League, 1901-1960\n";
string pattern = @"^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+";
Match match;

match = Regex.Match(input, pattern);


while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();

match = Regex.Match(input, pattern, RegexOptions.Multiline);


while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();
}
}
// The example displays the following output:
// The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
//
// The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
// The Chicago Cubs played in the National League in 1903-present.
// The Detroit Tigers played in the American League in 1901-present.
// The New York Giants played in the National League in 1885-1957.
// The Washington Senators played in the American League in 1901-1960.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957" + vbCrLf +
"Chicago Cubs, National League, 1903-present" + vbCrLf +
"Detroit Tigers, American League, 1901-present" + vbCrLf +
"New York Giants, National League, 1885-1957" + vbCrLf +
"Washington Senators, American League, 1901-1960" + vbCrLf

Dim pattern As String = "^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+"


Dim match As Match

match = Regex.Match(input, pattern)


Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
match = match.NextMatch()
Loop
Console.WriteLine()

match = Regex.Match(input, pattern, RegexOptions.Multiline)


Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
match = match.NextMatch()
Loop
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
'
' The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
' The Chicago Cubs played in the National League in 1903-present.
' The Detroit Tigers played in the American League in 1901-present.
' The New York Giants played in the National League in 1885-1957.
' The Washington Senators played in the American League in 1901-1960.

El patrón de expresión regular ^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+ se define como se


muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

^ Comienza la búsqueda de coincidencias al principio de la


cadena de entrada (o al principio de la línea si se llama al
método con la opción RegexOptions.Multiline ).

((\w+(\s?)){2,} Coincide con uno o varios caracteres que se usan para formar
palabras seguidos de cero o un espacio, al menos dos veces.
Este es el primer grupo de captura. Esta expresión también
define un segundo y tercer grupo de captura: El segundo
grupo está compuesto por la palabra capturada y el tercero
consta de los espacios en blanco capturados.
M O DELO DESC RIP C IÓ N

,\s Coincide con una coma seguida de un carácter de espacio en


blanco.

(\w+\s\w+) Coincide con uno o varios caracteres que se usan para formar
palabras seguidos de un espacio, seguidos de uno o varios
caracteres que se usan para formar palabras. Este es el cuarto
grupo de captura.

, Coincide con una coma.

\s\d{4} Coincide con un espacio seguido de cuatro dígitos decimales.

(-(\d{4}|present))? Coincide con cero o un guion seguido de cuatro dígitos


decimales o de la cadena "present". Este es el sexto grupo de
captura. También incluye un séptimo grupo de captura.

,? Coincide con una coma o ninguna.

(\s\d{4}(-(\d{4}|present))?,?)+ Coincide con una o más apariciones de lo siguiente: un


espacio, cuatro dígitos decimales, cero o un guion seguido de
cuatro dígitos decimales o de la cadena "present", y una coma
o ninguna. Este es el quinto grupo de captura.

Final de cadena o línea: $


El delimitador $ especifica que el patrón que le precede debe aparecer al final de la cadena de entrada o antes de
\n al final de la cadena de entrada.

Si usa $ con la opción RegexOptions.Multiline , la coincidencia también se puede producir al final de una línea.
Observe que $ coincide con \n , pero no coincide con \r\n (la combinación de caracteres de retorno de carro
y nueva línea, o CR/LF). Para buscar la combinación de caracteres CR/LF, incluya \r?$ en el patrón de expresión
regular.
En el ejemplo siguiente se agrega el delimitador $ al patrón de expresión regular usado en el ejemplo de la
sección Principio de cadena o línea . Cuando se usa con la cadena de entrada original, que incluye cinco líneas de
texto, el método Regex.Matches(String, String) no puede encontrar una coincidencia, porque el final de la primera
línea no coincide con el patrón $ . Cuando la cadena de entrada original se divide en una matriz de cadenas, el
método Regex.Matches(String, String) consigue encontrar coincidencias en cada una de las cinco líneas. Cuando se
llama al método Regex.Matches(String, String, RegexOptions) con el parámetro options establecido en
RegexOptions.Multiline, no se encuentra ninguna coincidencia porque el patrón de la expresión regular no tiene
en cuenta el elemento de retorno de carro (\u+000D). Sin embargo, cuando el patrón de la expresión regular se
modifica al remplazar $ por \r?$ , si se llama al método Regex.Matches(String, String, RegexOptions) con el
parámetro options establecido en RegexOptions.Multiline , ahora encuentra cinco coincidencias.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string cr = Environment.NewLine;
string input = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957" + cr +
"Chicago Cubs, National League, 1903-present" + cr +
"Detroit Tigers, American League, 1901-present" + cr +
"Detroit Tigers, American League, 1901-present" + cr +
"New York Giants, National League, 1885-1957" + cr +
"Washington Senators, American League, 1901-1960" + cr;
Match match;

string basePattern = @"^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+";


string pattern = basePattern + "$";
Console.WriteLine("Attempting to match the entire input string:");
match = Regex.Match(input, pattern);
while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();

string[] teams = input.Split(new String[] { cr }, StringSplitOptions.RemoveEmptyEntries);


Console.WriteLine("Attempting to match each element in a string array:");
foreach (string team in teams)
{
match = Regex.Match(team, pattern);
if (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);
Console.WriteLine(".");
}
}
Console.WriteLine();

Console.WriteLine("Attempting to match each line of an input string with '$':");


match = Regex.Match(input, pattern, RegexOptions.Multiline);
while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();

pattern = basePattern + "\r?$";


Console.WriteLine(@"Attempting to match each line of an input string with '\r?$':");
match = Regex.Match(input, pattern, RegexOptions.Multiline);
while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();
}
}
// The example displays the following output:
// Attempting to match the entire input string:
//
// Attempting to match each element in a string array:
// The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
// The Chicago Cubs played in the National League in 1903-present.
// The Detroit Tigers played in the American League in 1901-present.
// The New York Giants played in the National League in 1885-1957.
// The Washington Senators played in the American League in 1901-1960.
//
// Attempting to match each line of an input string with '$':
//
// Attempting to match each line of an input string with '\r?$':
// The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
// The Chicago Cubs played in the National League in 1903-present.
// The Detroit Tigers played in the American League in 1901-present.
// The New York Giants played in the National League in 1885-1957.
// The Washington Senators played in the American League in 1901-1960.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957" + vbCrLf +
"Chicago Cubs, National League, 1903-present" + vbCrLf +
"Detroit Tigers, American League, 1901-present" + vbCrLf +
"New York Giants, National League, 1885-1957" + vbCrLf +
"Washington Senators, American League, 1901-1960" + vbCrLf

Dim basePattern As String = "^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+"


Dim match As Match

Dim pattern As String = basePattern + "$"


Console.WriteLine("Attempting to match the entire input string:")
match = Regex.Match(input, pattern)
Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
match = match.NextMatch()
Loop
Console.WriteLine()

Dim teams() As String = input.Split(New String() {vbCrLf}, StringSplitOptions.RemoveEmptyEntries)


Console.WriteLine("Attempting to match each element in a string array:")
For Each team As String In teams
match = Regex.Match(team, pattern)
If match.Success Then
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
End If
Next
Console.WriteLine()

Console.WriteLine("Attempting to match each line of an input string with '$':")


match = Regex.Match(input, pattern, RegexOptions.Multiline)
Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
match = match.NextMatch()
Loop
Console.WriteLine()

pattern = basePattern + "\r?$"


Console.WriteLine("Attempting to match each line of an input string with '\r?$':")
match = Regex.Match(input, pattern, RegexOptions.Multiline)
Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")

match = match.NextMatch()
Loop
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Attempting to match the entire input string:
'
' Attempting to match each element in a string array:
' The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
' The Chicago Cubs played in the National League in 1903-present.
' The Detroit Tigers played in the American League in 1901-present.
' The New York Giants played in the National League in 1885-1957.
' The Washington Senators played in the American League in 1901-1960.
'
' Attempting to match each line of an input string with '$':
'
' Attempting to match each line of an input string with '\r?$':
' The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
' The Chicago Cubs played in the National League in 1903-present.
' The Detroit Tigers played in the American League in 1901-present.
' The New York Giants played in the National League in 1885-1957.
' The Washington Senators played in the American League in 1901-1960.

Principio de cadena solamente: \A


El delimitador \A especifica que debe producirse una coincidencia al principio de la cadena de entrada. Es
idéntico al delimitador ^ , salvo en que \A omite la opción RegexOptions.Multiline . Por consiguiente, solo
puede coincidir con el principio de la primera línea en una cadena de entrada de varias líneas.
El ejemplo siguiente es similar a los ejemplos de los delimitadores ^ y $ . Usa el delimitador \A en una
expresión regular que extrae información sobre los años durante los que existieron algunos equipos de béisbol
profesionales. La cadena de entrada incluye cinco líneas. La llamada al método Regex.Matches(String, String,
RegexOptions) encuentra solo la primera subcadena que coincide con el patrón de la expresión regular en la
cadena de entrada. Como muestra el ejemplo, la opción Multiline no tiene ningún efecto.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957\n" +
"Chicago Cubs, National League, 1903-present\n" +
"Detroit Tigers, American League, 1901-present\n" +
"New York Giants, National League, 1885-1957\n" +
"Washington Senators, American League, 1901-1960\n";

string pattern = @"\A((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+";

Match match = Regex.Match(input, pattern, RegexOptions.Multiline);


while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();
}
}
// The example displays the following output:
// The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957" + vbCrLf +
"Chicago Cubs, National League, 1903-present" + vbCrLf +
"Detroit Tigers, American League, 1901-present" + vbCrLf +
"New York Giants, National League, 1885-1957" + vbCrLf +
"Washington Senators, American League, 1901-1960" + vbCrLf

Dim pattern As String = "\A((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+"

Dim match As Match = Regex.Match(input, pattern, RegexOptions.Multiline)


Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
match = match.NextMatch()
Loop
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.

Final de cadena o antes de nueva línea al final: \Z


El delimitador \Z especifica que se debe producir una coincidencia al final de la cadena de entrada o antes de
\n al final de la cadena de entrada. Es idéntico al delimitador $ , salvo en que \Z omite la opción
RegexOptions.Multiline . Por consiguiente, en una cadena de varias líneas, solo puede coincidir con el final de la
última línea o la última línea antes de \n .
Observe que \Z coincide con \n , pero no coincide con \r\n (la combinación de caracteres de retorno de carro
y nueva línea, o CR/LF). Para buscar CR/LF, incluya \r?\Z en el patrón de expresión regular.
En el ejemplo siguiente, se usa el delimitador \Z en una expresión regular que es similar al ejemplo de la sección
Principio de cadena o línea , que extrae información sobre los años durante los que existieron algunos equipos del
béisbol profesionales. La subexpresión \r?\Z de la expresión regular
^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\Z coincide con el final de una cadena, y
también coincide con una cadena que termina por \n o \r\n . Como resultado, cada elemento de la matriz
coincide con el patrón de la expresión regular.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957",
"Chicago Cubs, National League, 1903-present" + Environment.NewLine,
"Detroit Tigers, American League, 1901-present" + Regex.Unescape(@"\n"),
"New York Giants, National League, 1885-1957",
"Washington Senators, American League, 1901-1960" + Environment.NewLine};
string pattern = @"^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\Z";

foreach (string input in inputs)


{
Console.WriteLine(Regex.Escape(input));
Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine(" Match succeeded.");
else
Console.WriteLine(" Match failed.");
}
}
}
// The example displays the following output:
// Brooklyn\ Dodgers,\ National\ League,\ 1911,\ 1912,\ 1932-1957
// Match succeeded.
// Chicago\ Cubs,\ National\ League,\ 1903-present\r\n
// Match succeeded.
// Detroit\ Tigers,\ American\ League,\ 1901-present\n
// Match succeeded.
// New\ York\ Giants,\ National\ League,\ 1885-1957
// Match succeeded.
// Washington\ Senators,\ American\ League,\ 1901-1960\r\n
// Match succeeded.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"Brooklyn Dodgers, National League, 1911, 1912, 1932-1957",
"Chicago Cubs, National League, 1903-present" + vbCrLf,
"Detroit Tigers, American League, 1901-present" + vbLf,
"New York Giants, National League, 1885-1957",
"Washington Senators, American League, 1901-1960" + vbCrLf}
Dim pattern As String = "^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\Z"

For Each input As String In inputs


Console.WriteLine(Regex.Escape(input))
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine(" Match succeeded.")
Else
Console.WriteLine(" Match failed.")
End If
Next
End Sub
End Module
' The example displays the following output:
' Brooklyn\ Dodgers,\ National\ League,\ 1911,\ 1912,\ 1932-1957
' Match succeeded.
' Chicago\ Cubs,\ National\ League,\ 1903-present\r\n
' Match succeeded.
' Detroit\ Tigers,\ American\ League,\ 1901-present\n
' Match succeeded.
' New\ York\ Giants,\ National\ League,\ 1885-1957
' Match succeeded.
' Washington\ Senators,\ American\ League,\ 1901-1960\r\n
' Match succeeded.

Final de cadena solamente: \z


El delimitador \z especifica que debe producirse una coincidencia al final de la cadena de entrada. Al igual que el
elemento del lenguaje $ , \z omite la opción RegexOptions.Multiline . A diferencia del elemento del lenguaje
\Z , \z no coincide con un carácter \n al final de una cadena. Por consiguiente, solo puede coincidir con la
última línea de la cadena de entrada.
En el ejemplo siguiente, se usa el delimitador \z en una expresión regular que por lo demás es idéntica al
ejemplo de la sección anterior, que extrae información sobre los años durante los que existieron algunos equipos
del béisbol profesionales. En el ejemplo, se intenta buscar coincidencias con cada uno de los cinco elementos de
una matriz de cadenas con el patrón de expresión regular
^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\z . Dos de las cadenas finalizan con caracteres
de retorno de carro y salto de línea, una finaliza con un carácter de salto de línea, y dos no finalizan con un
carácter de retorno de carro ni con un carácter de salto de línea. Como muestra la salida, solo coinciden con el
patrón las cadenas sin un carácter de retorno de carro ni de salto de línea.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957",
"Chicago Cubs, National League, 1903-present" + Environment.NewLine,
"Detroit Tigers, American League, 1901-present\n",
"New York Giants, National League, 1885-1957",
"Washington Senators, American League, 1901-1960" + Environment.NewLine };
string pattern = @"^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\z";

foreach (string input in inputs)


{
Console.WriteLine(Regex.Escape(input));
Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine(" Match succeeded.");
else
Console.WriteLine(" Match failed.");
}
}
}
// The example displays the following output:
// Brooklyn\ Dodgers,\ National\ League,\ 1911,\ 1912,\ 1932-1957
// Match succeeded.
// Chicago\ Cubs,\ National\ League,\ 1903-present\r\n
// Match failed.
// Detroit\ Tigers,\ American\ League,\ 1901-present\n
// Match failed.
// New\ York\ Giants,\ National\ League,\ 1885-1957
// Match succeeded.
// Washington\ Senators,\ American\ League,\ 1901-1960\r\n
// Match failed.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"Brooklyn Dodgers, National League, 1911, 1912, 1932-1957",
"Chicago Cubs, National League, 1903-present" + vbCrLf,
"Detroit Tigers, American League, 1901-present" + vbLf,
"New York Giants, National League, 1885-1957",
"Washington Senators, American League, 1901-1960" + vbCrLf}
Dim pattern As String = "^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\z"

For Each input As String In inputs


Console.WriteLine(Regex.Escape(input))
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine(" Match succeeded.")
Else
Console.WriteLine(" Match failed.")
End If
Next
End Sub
End Module
' The example displays the following output:
' Brooklyn\ Dodgers,\ National\ League,\ 1911,\ 1912,\ 1932-1957
' Match succeeded.
' Chicago\ Cubs,\ National\ League,\ 1903-present\r\n
' Match failed.
' Detroit\ Tigers,\ American\ League,\ 1901-present\n
' Match failed.
' New\ York\ Giants,\ National\ League,\ 1885-1957
' Match succeeded.
' Washington\ Senators,\ American\ League,\ 1901-1960\r\n
' Match failed.

Coincidencias contiguas: \G
El delimitador \G especifica que debe producirse una coincidencia en el punto en el que finalizó la coincidencia
anterior. El uso de este delimitador con el método Regex.Matches o Match.NextMatch permite asegurarse de que
todas las coincidencias son contiguas.
En el ejemplo siguiente se usa una expresión regular para extraer los nombres de especies de roedores de una
cadena delimitada por comas.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "capybara,squirrel,chipmunk,porcupine,gopher," +
"beaver,groundhog,hamster,guinea pig,gerbil," +
"chinchilla,prairie dog,mouse,rat";
string pattern = @"\G(\w+\s?\w*),?";
Match match = Regex.Match(input, pattern);
while (match.Success)
{
Console.WriteLine(match.Groups[1].Value);
match = match.NextMatch();
}
}
}
// The example displays the following output:
// capybara
// squirrel
// chipmunk
// porcupine
// gopher
// beaver
// groundhog
// hamster
// guinea pig
// gerbil
// chinchilla
// prairie dog
// mouse
// rat

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "capybara,squirrel,chipmunk,porcupine,gopher," +
"beaver,groundhog,hamster,guinea pig,gerbil," +
"chinchilla,prairie dog,mouse,rat"
Dim pattern As String = "\G(\w+\s?\w*),?"
Dim match As Match = Regex.Match(input, pattern)
Do While match.Success
Console.WriteLine(match.Groups(1).Value)
match = match.NextMatch()
Loop
End Sub
End Module
' The example displays the following output:
' capybara
' squirrel
' chipmunk
' porcupine
' gopher
' beaver
' groundhog
' hamster
' guinea pig
' gerbil
' chinchilla
' prairie dog
' mouse
' rat
La expresión regular \G(\w+\s?\w*),? se interpreta como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\G Comienza donde finalizó la última coincidencia.

\w+ Buscar coincidencias con uno o más caracteres alfabéticos.

\s? Coincide con cero o un espacio.

\w* Buscar una coincidencia con cero o más caracteres alfabéticos.

(\w+\s?\w*) Coincide con uno o varios caracteres que se usan para formar
palabras seguidos de cero o un espacio, seguidos de cero o
más caracteres que se usan para formar palabras. Este es el
primer grupo de captura.

,? Coincide con cero o un carácter de coma literal.

Límite de palabras: \b
El delimitador \b especifica que la coincidencia se debe producir en un límite entre un carácter que se usa para
formar palabras (el elemento del lenguaje \w ) y un carácter que no se usa para formar palabras (el elemento del
lenguaje \W ). Los caracteres que se usan para formar palabras son los caracteres alfanuméricos y de subrayado;
un carácter que no se usa para formar palabras es cualquier carácter que no es alfanumérico ni de subrayado.
(Para más información, vea Clases de carácter). La coincidencia también se puede producir en un límite de
palabras al principio o al final de la cadena.
El delimitador \b se usa con frecuencia para asegurarse de que una subexpresión coincide con una palabra
completa en lugar de solo con el principio o el final de una palabra. La expresión regular \bare\w*\b del ejemplo
siguiente muestra este uso. Coincide con cualquier palabra que comience por la subcadena "are". El resultado del
ejemplo también muestra que \b coincide tanto con el principio como con el final de la cadena de entrada.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "area bare arena mare";
string pattern = @"\bare\w*\b";
Console.WriteLine("Words that begin with 'are':");
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index);
}
}
// The example displays the following output:
// Words that begin with 'are':
// 'area' found at position 0
// 'arena' found at position 10
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "area bare arena mare"
Dim pattern As String = "\bare\w*\b"
Console.WriteLine("Words that begin with 'are':")
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Words that begin with 'are':
' 'area' found at position 0
' 'arena' found at position 10

El patrón de la expresión regular se interpreta como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

are Coincide con la subcadena "are".

\w* Buscar una coincidencia con cero o más caracteres alfabéticos.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

Fuera de un límite de palabras: \B


El delimitador \B especifica que la coincidencia no se debe producir en un límite de palabras. Es lo contrario del
delimitador \b .
En el ejemplo siguiente, se usa el delimitador \B para buscar apariciones de la subcadena "qu" en una palabra. El
patrón de expresión regular \Bqu\w+ coincide con una subcadena que comienza por "qu" que no está al principio
de una palabra y que continúa hasta el final de la palabra.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "equity queen equip acquaint quiet";
string pattern = @"\Bqu\w+";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index);
}
}
// The example displays the following output:
// 'quity' found at position 1
// 'quip' found at position 14
// 'quaint' found at position 21
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "equity queen equip acquaint quiet"
Dim pattern As String = "\Bqu\w+"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' 'quity' found at position 1
' 'quip' found at position 14
' 'quaint' found at position 21

El patrón de la expresión regular se interpreta como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\B La búsqueda de coincidencias no comienza en un límite de


palabras.

qu Coincide con la subcadena "qu".

\w+ Buscar coincidencias con uno o más caracteres alfabéticos.

Vea también
Lenguaje de expresiones regulares: referencia rápida
Opciones de expresiones regulares
Construcciones de agrupamiento en expresiones
regulares
16/09/2020 • 66 minutes to read • Edit Online

Las construcciones de agrupamiento definen las subexpresiones de una expresión regular y capturan las
subcadenas de una cadena de entrada. Puede utilizar construcciones de agrupamiento para hacer lo siguiente:
Buscar una subexpresión que se repite en la cadena de entrada.
Aplicar un cuantificador a una subexpresión que tiene varios elementos del lenguaje de expresiones
regulares. Para más información sobre los cuantificadores, vea Quantifiers.
Incluir una subexpresión en la cadena devuelta por los métodos Regex.Replace y Match.Result .
Recuperar subexpresiones individuales de la propiedad Match.Groups y procesarlas por separado del
texto coincidente en su conjunto.
En la tabla siguiente se enumeran las construcciones de agrupamiento admitidas por el motor de expresiones
regulares de .NET y se indica si son de captura o sin captura.

C O N ST RUC C IÓ N DE A GRUPA M IEN TO DE C A P T URA O SIN C A P T URA

Subexpresiones coincidentes Capturando

Subexpresiones coincidentes con nombre Capturando

Definiciones de grupos de compensación Capturando

Grupos sin captura Sin captura

Opciones de grupo Sin captura

Aserciones de búsqueda anticipada positiva de ancho cero Sin captura

Aserciones de búsqueda anticipada negativa de ancho cero Sin captura

Aserciones de búsqueda tardía positiva de ancho cero Sin captura

Aserciones de búsqueda tardía negativa de ancho cero Sin captura

Grupos atómicos Sin captura

Para obtener información sobre los grupos y el modelo de objetos de expresiones regulares, vea
Construcciones de agrupamiento y objetos de las expresiones regulares.

Subexpresiones coincidentes
La construcción de agrupación siguiente captura una subexpresión coincidente:
( subexpresión )
donde subexpresión es cualquier patrón de expresión regular válido. Las capturas que usan paréntesis se
numeran automáticamente de izquierda a derecha según el orden de los paréntesis de apertura de la
expresión regular, empezando desde uno. La captura con el número cero es el texto coincidente con el patrón
de la expresión regular completa.

NOTE
De manera predeterminada, el elemento de lenguaje ( subexpresión ) captura la subexpresión coincidente. No
obstante, la subexpresión coincidente no se captura si el parámetro RegexOptions del método de coincidencia de
patrones de una expresión regular incluye la marca RegexOptions.ExplicitCapture o si se aplica la opción n a esta
subexpresión (vea Opciones de grupo más adelante en este tema).

Existen cuatro formas de tener acceso a los grupos capturados:


Usando la construcción de referencia inversa dentro de la expresión regular. Para hacer referencia a la
subexpresión coincidente desde la misma expresión regular, se usa la sintaxis \ número, donde
número es el número ordinal de la subexpresión capturada.
Usando la construcción de referencia inversa con nombre dentro de la expresión regular. Para hacer
referencia a la subexpresión coincidente desde la misma expresión regular, se usa la sintaxis \k<
nombre > , donde nombre es el nombre de un grupo de captura, o \k< número > , donde número es
el número ordinal de un grupo de captura. Un grupo de captura tiene un nombre predeterminado que
es idéntico a su número ordinal. Para obtener más información, vea Subexpresiones coincidentes con
nombre más adelante en este tema.
Usando la secuencia de reemplazo $ número en una llamada al método Regex.Replace o Match.Result
, donde número es el número ordinal de la subexpresión capturada.
Mediante programación, usando el objeto GroupCollection devuelto por la propiedad Match.Groups .
El miembro en la posición cero de la colección representa la coincidencia de la expresión regular
completa. Cada miembro subsiguiente representa una subexpresión coincidente. Para más
información, vea la sección Grouping Constructs and Regular Expression Objects .
En el ejemplo siguiente se muestra una expresión regular que identifica las palabras duplicadas en el texto.
Los dos grupos de captura del patrón de la expresión regular representan las dos instancias de la palabra
duplicada. La segunda instancia se captura para notificar su posición inicial en la cadena de entrada.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(\w+)\s(\1)";
string input = "He said that that was the the correct answer.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("Duplicate '{0}' found at positions {1} and {2}.",
match.Groups[1].Value, match.Groups[1].Index, match.Groups[2].Index);
}
}
// The example displays the following output:
// Duplicate 'that' found at positions 8 and 13.
// Duplicate 'the' found at positions 22 and 26.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(\w+)\s(\1)\W"
Dim input As String = "He said that that was the the correct answer."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("Duplicate '{0}' found at positions {1} and {2}.", _
match.Groups(1).Value, match.Groups(1).Index, match.Groups(2).Index)
Next
End Sub
End Module
' The example displays the following output:
' Duplicate 'that' found at positions 8 and 13.
' Duplicate 'the' found at positions 22 and 26.

El patrón de la expresión regular es el siguiente:


(\w+)\s(\1)\W

En la siguiente tabla se muestra cómo se interpreta el patrón de expresión regular.

M O DELO DESC RIP C IÓ N

(\w+) Buscar coincidencias con uno o más caracteres alfabéticos.


Este es el primer grupo de captura.

\s Coincide con un carácter de espacio en blanco.

(\1) Coincide con la cadena del primer grupo capturado. Este es


el segundo grupo de captura. El ejemplo lo asigna a un
grupo capturado de forma que la posición inicial de la
palabra duplicada se pueda recuperar de la propiedad
Match.Index .

\W Coincide con un carácter que no se usa para formar


palabras, como los espacios en blanco y los signos de
puntuación. Esto evita que el patrón de la expresión regular
coincida con una palabra que comience por la palabra del
primer grupo capturado.

Subexpresiones coincidentes con nombre


La construcción de agrupamiento siguiente captura una subexpresión coincidente y permite tener acceso a
ella por nombre o por número:
(?<name>subexpression)

O bien
(?'name'subexpression)

donde nombre es un nombre de grupo válido, y subexpresión es cualquier patrón de expresión regular válido.
nombre no debe contener ningún carácter de puntuación y no puede comenzar por un número.
NOTE
Si el parámetro RegexOptions del método de coincidencia de patrones de una expresión regular incluye la marca
RegexOptions.ExplicitCapture o si se aplica la opción n a esta subexpresión (vea Opciones de grupo más adelante en
este tema), la única forma de capturar una subexpresión es asignar nombres explícitamente a los grupos de captura.

Puede tener acceso a los grupos capturados con nombre de las maneras siguientes:
Usando la construcción de referencia inversa con nombre dentro de la expresión regular. Para hacer
referencia a la subexpresión coincidente desde la misma expresión regular, se usa la sintaxis \k<
nombre > , donde nombre es el nombre de la subexpresión capturada.
Usando la construcción de referencia inversa dentro de la expresión regular. Para hacer referencia a la
subexpresión coincidente desde la misma expresión regular, se usa la sintaxis \ número, donde
número es el número ordinal de la subexpresión capturada. Las subexpresiones coincidentes con
nombre se numeran consecutivamente de izquierda a derecha después de las subexpresiones
coincidentes.
Usando la secuencia de reemplazo ${ nombre } en una llamada al método Regex.Replace o
Match.Result , donde nombre es el nombre de la subexpresión capturada.
Usando la secuencia de reemplazo $ número en una llamada al método Regex.Replace o Match.Result
, donde número es el número ordinal de la subexpresión capturada.
Mediante programación, usando el objeto GroupCollection devuelto por la propiedad Match.Groups .
El miembro en la posición cero de la colección representa la coincidencia de la expresión regular
completa. Cada miembro subsiguiente representa una subexpresión coincidente. Los grupos
capturados con nombre se almacenan en la colección después de los grupos capturados numerados.
Mediante programación, proporcionando el nombre de la subexpresión al indizador del objeto
GroupCollection (en C#) o a su propiedad Item[] (en Visual Basic).
Un patrón de expresión regular simple muestra cómo se puede hacer referencia a los grupos numerados (sin
nombre) y con nombre mediante programación o utilizando la sintaxis del lenguaje de expresiones regulares.
La expresión regular ((?<One>abc)\d+)?(?<Two>xyz)(.*) produce los siguientes grupos de captura por número
y por nombre. El primer grupo de captura (el número 0) siempre hace referencia al patrón completo.

N ÚM ERO N O M B RE M O DELO

0 0 (nombre predeterminado) ((?<One>abc)\d+)?(?<Two>xyz)


(.*)

1 1 (nombre predeterminado) ((?<One>abc)\d+)

2 2 (nombre predeterminado) (.*)

3 Uno (?<One>abc)

4 Dos (?<Two>xyz)

En el ejemplo siguiente se muestra una expresión regular que identifica las palabras duplicadas y la palabra
que sigue inmediatamente a cada palabra duplicada. El patrón de la expresión regular define dos
subexpresiones con nombre: duplicateWord , que representa la palabra duplicada; y nextWord , que representa
la palabra que sigue a la palabra duplicada.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(?<duplicateWord>\w+)\s\k<duplicateWord>\W(?<nextWord>\w+)";
string input = "He said that that was the the correct answer.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("A duplicate '{0}' at position {1} is followed by '{2}'.",
match.Groups["duplicateWord"].Value, match.Groups["duplicateWord"].Index,
match.Groups["nextWord"].Value);
}
}
// The example displays the following output:
// A duplicate 'that' at position 8 is followed by 'was'.
// A duplicate 'the' at position 22 is followed by 'correct'.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?<duplicateWord>\w+)\s\k<duplicateWord>\W(?<nextWord>\w+)"
Dim input As String = "He said that that was the the correct answer."
Console.WriteLine(Regex.Matches(input, pattern, RegexOptions.IgnoreCase).Count)
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("A duplicate '{0}' at position {1} is followed by '{2}'.", _
match.Groups("duplicateWord").Value, match.Groups("duplicateWord").Index, _
match.Groups("nextWord").Value)
Next
End Sub
End Module
' The example displays the following output:
' A duplicate 'that' at position 8 is followed by 'was'.
' A duplicate 'the' at position 22 is followed by 'correct'.

El patrón de la expresión regular es el siguiente:


(?<duplicateWord>\w+)\s\k<duplicateWord>\W(?<nextWord>\w+)

La tabla siguiente muestra cómo se interpreta la expresión regular.

M O DELO DESC RIP C IÓ N

(?<duplicateWord>\w+) Buscar coincidencias con uno o más caracteres alfabéticos.


Este grupo de captura se denomina duplicateWord .

\s Coincide con un carácter de espacio en blanco.

\k<duplicateWord> Coincide con la cadena del grupo capturado denominada


duplicateWord .

\W Coincide con un carácter que no se usa para formar


palabras, como los espacios en blanco y los signos de
puntuación. Esto evita que el patrón de la expresión regular
coincida con una palabra que comience por la palabra del
primer grupo capturado.
M O DELO DESC RIP C IÓ N

(?<nextWord>\w+) Buscar coincidencias con uno o más caracteres alfabéticos.


Este grupo de captura se denomina nextWord .

Tenga en cuenta que un nombre de grupo se puede repetir en una expresión regular. Por ejemplo, es posible
que más de un grupo se llame digit , como muestra el ejemplo siguiente. En el caso de nombres duplicados,
el valor del objeto Group viene determinado por la última captura correcta en la cadena de entrada. Además,
la colección CaptureCollection se rellena con información de cada captura igual que si el nombre de grupo no
estuviera duplicado.
En el ejemplo siguiente, la expresión regular \D+(?<digit>\d+)\D+(?<digit>\d+)? incluye dos apariciones de un
grupo llamado digit . El primer grupo llamado digit captura uno o más caracteres de dígito. El segundo
grupo llamado digit captura cero o una aparición de uno o más caracteres de dígito. Tal y como muestra la
salida del ejemplo, si el segundo grupo de captura coincide correctamente con el texto, el valor de ese texto
define el valor del objeto Group . Si el segundo grupo de captura no coincide con la cadena de entrada, el
valor de la última coincidencia correcta define el valor del objeto Group .

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
String pattern = @"\D+(?<digit>\d+)\D+(?<digit>\d+)?";
String[] inputs = { "abc123def456", "abc123def" };
foreach (var input in inputs) {
Match m = Regex.Match(input, pattern);
if (m.Success) {
Console.WriteLine("Match: {0}", m.Value);
for (int grpCtr = 1; grpCtr < m.Groups.Count; grpCtr++) {
Group grp = m.Groups[grpCtr];
Console.WriteLine("Group {0}: {1}", grpCtr, grp.Value);
for (int capCtr = 0; capCtr < grp.Captures.Count; capCtr++)
Console.WriteLine(" Capture {0}: {1}", capCtr,
grp.Captures[capCtr].Value);
}
}
else {
Console.WriteLine("The match failed.");
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: abc123def456
// Group 1: 456
// Capture 0: 123
// Capture 1: 456
//
// Match: abc123def
// Group 1: 123
// Capture 0: 123
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\D+(?<digit>\d+)\D+(?<digit>\d+)?"
Dim inputs() As String = {"abc123def456", "abc123def"}
For Each input As String In inputs
Dim m As Match = Regex.Match(input, pattern)
If m.Success Then
Console.WriteLine("Match: {0}", m.Value)
For grpCtr As Integer = 1 to m.Groups.Count - 1
Dim grp As Group = m.Groups(grpCtr)
Console.WriteLine("Group {0}: {1}", grpCtr, grp.Value)
For capCtr As Integer = 0 To grp.Captures.Count - 1
Console.WriteLine(" Capture {0}: {1}", capCtr,
grp.Captures(capCtr).Value)
Next
Next
Else
Console.WriteLine("The match failed.")
End If
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: abc123def456
' Group 1: 456
' Capture 0: 123
' Capture 1: 456
'
' Match: abc123def
' Group 1: 123
' Capture 0: 123

La tabla siguiente muestra cómo se interpreta la expresión regular.

M O DELO DESC RIP C IÓ N

\D+ Coincide con uno o más caracteres de dígito no decimal.

(?<digit>\d+) Coincide con uno o más caracteres de dígito decimal.


Asigna la coincidencia al grupo llamado digit .

\D+ Coincide con uno o más caracteres de dígito no decimal.

(?<digit>\d+)? Coincide con ninguna o una aparición de uno o más


caracteres de dígito decimal. Asigna la coincidencia al grupo
llamado digit .

Definiciones de grupos de compensación


Una definición de grupo de compensación elimina la definición de un grupo definido anteriormente y
almacena, en el grupo actual, el intervalo entre el grupo definido anteriormente y el grupo actual. Esta
construcción de agrupamiento tiene el formato siguiente:
(?<name1-name2>subexpression)

O bien
(?'name1-name2' subexpression)

donde nombre1 es el grupo actual (opcional), nombre2 es un grupo definido previamente y subexpresión es
cualquier patrón de expresión regular válido. La definición de grupo de compensación elimina la definición de
nombre2 y almacena el intervalo entre nombre2 y nombre1 en nombre1. Si no se ha definido el grupo
nombre2 , la búsqueda de coincidencias retrocede. Como al eliminar la última definición de nombre2 se
revela la definición anterior de nombre2, esta construcción permite usar la pila de capturas del grupo
nombre2 como contador para realizar el seguimiento de construcciones anidadas como paréntesis o
corchetes de apertura y cierre.
La definición del grupo de compensación utiliza nombre2 como pila. El carácter inicial de cada construcción
anidada se coloca en el grupo y en su colección Group.Captures . Cuando se encuentra una coincidencia con
el carácter de cierre, el carácter de apertura correspondiente se quita del grupo, y la colección Captures
disminuye en una unidad. Después de buscar las coincidencias con los caracteres de apertura y cierre de
todas las construcciones anidadas, nombre2 estará vacío.

NOTE
Después de modificar la expresión regular del ejemplo siguiente para que utilice el carácter de apertura y cierre
adecuado de una construcción anidada, puede utilizarla con la mayoría de las estructuras anidadas, como expresiones
matemáticas o líneas de código de programa que incluyen varias llamadas a métodos anidadas.

En el ejemplo siguiente se usa una definición de grupo de compensación para que coincida con los corchetes
angulares de apertura y de cierre (<>) de una cadena de entrada. En el ejemplo se definen dos grupos con
nombre, Open y Close , que se utilizan como una pila para realizar el seguimiento de los pares de corchetes
angulares coincidentes. Cada corchete angular de apertura capturado se inserta en la colección de captura del
grupo Open , y cada corchete angular de cierre capturado se inserta en la colección de captura del grupo
Close . Mediante la definición del grupo de compensación se comprueba que haya un corchete angular de
cierre para cada corchete angular de apertura. Si no lo hay, el subpatrón final, (?(Open)(?!)) , se evalúa solo si
el grupo Open no está vacío (y, por consiguiente, si no se han cerrado todas las construcciones anidadas). Si
se evalúa el subpatrón final, la coincidencia produce un error, porque el subpatrón (?!) es una aserción de
búsqueda anticipada negativa de ancho cero que siempre produce un error.
using System;
using System.Text.RegularExpressions;

class Example
{
public static void Main()
{
string pattern = "^[^<>]*" +
"(" +
"((?'Open'<)[^<>]*)+" +
"((?'Close-Open'>)[^<>]*)+" +
")*" +
"(?(Open)(?!))$";
string input = "<abc><mno<xyz>>";

Match m = Regex.Match(input, pattern);


if (m.Success == true)
{
Console.WriteLine("Input: \"{0}\" \nMatch: \"{1}\"", input, m);
int grpCtr = 0;
foreach (Group grp in m.Groups)
{
Console.WriteLine(" Group {0}: {1}", grpCtr, grp.Value);
grpCtr++;
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine(" Capture {0}: {1}", capCtr, cap.Value);
capCtr++;
}
}
}
else
{
Console.WriteLine("Match failed.");
}
}
}
// The example displays the following output:
// Input: "<abc><mno<xyz>>"
// Match: "<abc><mno<xyz>>"
// Group 0: <abc><mno<xyz>>
// Capture 0: <abc><mno<xyz>>
// Group 1: <mno<xyz>>
// Capture 0: <abc>
// Capture 1: <mno<xyz>>
// Group 2: <xyz
// Capture 0: <abc
// Capture 1: <mno
// Capture 2: <xyz
// Group 3: >
// Capture 0: >
// Capture 1: >
// Capture 2: >
// Group 4:
// Group 5: mno<xyz>
// Capture 0: abc
// Capture 1: xyz
// Capture 2: mno<xyz>
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^[^<>]*" & _
"(" + "((?'Open'<)[^<>]*)+" & _
"((?'Close-Open'>)[^<>]*)+" + ")*" & _
"(?(Open)(?!))$"
Dim input As String = "<abc><mno<xyz>>"
Dim rgx AS New Regex(pattern) '
Dim m As Match = Regex.Match(input, pattern)
If m.Success Then
Console.WriteLine("Input: ""{0}"" " & vbCrLf & "Match: ""{1}""", _
input, m)
Dim grpCtr As Integer = 0
For Each grp As Group In m.Groups
Console.WriteLine(" Group {0}: {1}", grpCtr, grp.Value)
grpCtr += 1
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: {1}", capCtr, cap.Value)
capCtr += 1
Next
Next
Else
Console.WriteLine("Match failed.")
End If
End Sub
End Module
' The example displays the following output:
' Input: "<abc><mno<xyz>>"
' Match: "<abc><mno<xyz>>"
' Group 0: <abc><mno<xyz>>
' Capture 0: <abc><mno<xyz>>
' Group 1: <mno<xyz>>
' Capture 0: <abc>
' Capture 1: <mno<xyz>>
' Group 2: <xyz
' Capture 0: <abc
' Capture 1: <mno
' Capture 2: <xyz
' Group 3: >
' Capture 0: >
' Capture 1: >
' Capture 2: >
' Group 4:
' Group 5: mno<xyz>
' Capture 0: abc
' Capture 1: xyz
' Capture 2: mno<xyz>

El patrón de la expresión regular es:


^[^<>]*(((?'Open'<)[^<>]*)+((?'Close-Open'>)[^<>]*)+)*(?(Open)(?!))$

La expresión regular se interpreta como sigue:

M O DELO DESC RIP C IÓ N

^ Comienza al principio de la cadena.

[^<>]* Coincide con cero o más caracteres que no son corchetes


angulares de apertura o cierre.
M O DELO DESC RIP C IÓ N

(?'Open'<) Coincide con un corchete angular de apertura y se lo


asigna a un grupo denominado Open .

[^<>]* Coincide con cero o más caracteres que no son corchetes


angulares de apertura o cierre.

((?'Open'<)[^<>]*)+ Coincide con una o más apariciones de un corchete angular


de apertura seguido de cero o más caracteres que no son
corchetes angulares de apertura o cierre. Este es el
segundo grupo de captura.

(?'Close-Open'>) Coincide con un corchete angular de cierre, asigna la


subcadena entre el grupo Open y el grupo actual al grupo
Close y elimina la definición del grupo Open .

[^<>]* Coincide con cero o más apariciones de cualquier carácter


que no sea un corchete angular de apertura ni de cierre.

((?'Close-Open'>)[^<>]*)+ Coincide con una o más apariciones de un corchete angular


de cierre, seguido de cero o más apariciones de cualquier
carácter que no sea un corchete angular de apertura ni de
cierre. Al buscar una coincidencia con el corchete angular
de cierre, asigna la subcadena entre el grupo Open y el
grupo actual al grupo Close , y elimina la definición del
grupo Open . Éste es el tercer grupo de captura.

(((?'Open'<)[^<>]*)+((?'Close-Open'>)[^<>]*)+)* Coincide con cero o más apariciones del patrón siguiente:


una o varias apariciones de un corchete angular de
apertura, seguidas de cero o más caracteres que no sean
corchetes angulares, seguidas de una o más apariciones de
un corchete angular de cierre, seguidas de cero o más
apariciones de caracteres que no sean corchetes angulares.
Al buscar una coincidencia con el corchete angular de
cierre, borra la definición del grupo Open y asigna la
subcadena entre el grupo Open y el grupo actual al grupo
Close . Este es el primer grupo de captura.

(?(Open)(?!)) Si existe el grupo Open , abandona la coincidencia si se


encuentra una cadena vacía, pero no avanza la posición del
motor de expresiones regulares en la cadena. Esta es una
aserción de búsqueda anticipada negativa de ancho cero.
Dado que siempre existe implícitamente una cadena vacía
en una cadena de entrada, esta coincidencia siempre
produce un error. Un error en esta coincidencia indica que
no hay el mismo número de corchetes angulares de
apertura y de cierre.

$ Coincide con el final de la cadena de entrada.

La subexpresión final, (?(Open)(?!)) , indica si las construcciones de anidamiento de la cadena de entrada


están compensadas correctamente (por ejemplo, si cada corchete angular de apertura coincide con un
corchete angular de cierre). Utiliza la coincidencia condicional basada en un grupo capturado válido; para más
información, vea Construcciones de alternancia. Si se define el grupo Open , el motor de expresiones
regulares intenta buscar la subexpresión (?!) en la cadena de entrada. El grupo Open solo se debería definir
si las construcciones de anidamiento están descompensadas. Por consiguiente, el patrón que se va a
comparar en la cadena de entrada debe ser uno que siempre produzca un error en la coincidencia. En este
caso, (?!) es una aserción de búsqueda anticipada negativa de ancho cero que siempre produce un error,
porque siempre existe implícitamente una cadena vacía en la posición siguiente de la cadena de entrada.
En el ejemplo, el motor de expresiones regulares evalúa la cadena de entrada "<abc><mno<xyz>>" como se
muestra en la tabla siguiente.

PA SO M O DELO RESULTA DO

1 ^ Comienza la búsqueda de
coincidencias al principio de la cadena
de entrada

2 [^<>]* Busca caracteres que no sean


corchetes angulares antes del
corchete angular de apertura; no
encuentra ninguna coincidencia.

3 (((?'Open'<) Encuentra el corchete angular de


apertura de "<abc>" y lo asigna al
grupo Open .

4 [^<>]* Encuentra "abc".

5 )+ "<abc" es el valor del segundo grupo


capturado.

El carácter siguiente de la cadena de


entrada no es un corchete angular de
apertura, por lo que el motor de
expresiones regulares no retrocede al
subpatrón (?'Open'<)[^<>]*) .

6 ((?'Close-Open'>) Encuentra el corchete angular de


cierre de "<abc>", asigna "abc", que
es la subcadena entre el grupo Open
y el corchete angular de cierre, al
grupo Close , y elimina el valor
actual ("<") del grupo Open ,
dejándolo vacío.

7 [^<>]* Busca caracteres que no sean


corchetes angulares después del
corchete angular de cierre; no
encuentra ninguna coincidencia.

8 )+ El valor del tercer grupo capturado es


">".

El carácter siguiente de la cadena de


entrada no es un corchete angular de
cierre, por lo que el motor de
expresiones regulares no retrocede al
subpatrón
((?'Close-Open'>)[^<>]*) .
PA SO M O DELO RESULTA DO

9 )* El valor del primer grupo capturado es


"<abc>".

El carácter siguiente de la cadena de


entrada es un corchete angular de
apertura, por lo que el motor de
expresiones regulares retrocede al
subpatrón (((?'Open'<) .

10 (((?'Open'<) Encuentra el corchete angular de


apertura de "<mno" and assigns it to
the Open group. Its Group.Captures
colección ahora tiene un valor único: "
<".

11 [^<>]* Encuentra "mno".

12 )+ "<mno" es el valor del segundo grupo


capturado.

El carácter siguiente de la cadena de


entrada es un corchete angular de
apertura, por lo que el motor de
expresiones regulares retrocede al
subpatrón (?'Open'<)[^<>]*) .

13 (((?'Open'<) Encuentra el corchete angular de


apertura de "<xyz>" y lo asigna al
grupo Open . La colección
Group.Captures del grupo Open
ahora incluye dos capturas: el
corchete angular de apertura de "
<mno", and the left angle bracket
from "<xyz>".

14 [^<>]* Encuentra "xyz".

15 )+ "<xyz" es el valor del segundo grupo


capturado.

El carácter siguiente de la cadena de


entrada no es un corchete angular de
apertura, por lo que el motor de
expresiones regulares no retrocede al
subpatrón (?'Open'<)[^<>]*) .
PA SO M O DELO RESULTA DO

16 ((?'Close-Open'>) Encuentra el corchete angular de


cierre de "<xyz>". "xyz", asigna la
subcadena entre el grupo Open y el
corchete angular de cierre al grupo
Close , y elimina el valor actual del
grupo Open . El valor de la captura
anterior (el corchete angular de
apertura de "<mno") becomes the
current value of the Open group. The
Captures colección del grupo Open
ahora incluye una única captura, el
corchete angular de apertura de "
<xyz>".

17 [^<>]* Busca caracteres que no sean


corchetes angulares; no encuentra
ninguna coincidencia.

18 )+ El valor del tercer grupo capturado es


">".

El carácter siguiente de la cadena de


entrada es un corchete angular de
cierre, por lo que el motor de
expresiones regulares retrocede al
subpatrón
((?'Close-Open'>)[^<>]*) .

19 ((?'Close-Open'>) Encuentra el último corchete angular


de cierre de "xyz>>", asigna
"mno<xyz>" (la subcadena entre el
grupo Open y el corchete angular de
cierre) al grupo Close y elimina el
valor actual del grupo Open . El
grupo Open está ahora vacío.

20 [^<>]* Busca caracteres que no sean


corchetes angulares; no encuentra
ninguna coincidencia.

21 )+ El valor del tercer grupo capturado es


">".

El carácter siguiente de la cadena de


entrada no es un corchete angular de
cierre, por lo que el motor de
expresiones regulares no retrocede al
subpatrón
((?'Close-Open'>)[^<>]*) .
PA SO M O DELO RESULTA DO

22 )* El valor del primer grupo capturado es


"<mno<xyz>>".

El carácter siguiente de la cadena de


entrada no es un corchete angular de
apertura, por lo que el motor de
expresiones regulares no retrocede al
subpatrón (((?'Open'<) .

23 (?(Open)(?!)) El grupo Open no está definido, por


lo que no se intenta encontrar
ninguna coincidencia.

24 $ Encuentra el final de la cadena de


entrada.

Grupos sin captura


La construcción de agrupamiento siguiente no captura la subcadena con la que coincide una subexpresión:
(?:subexpression)

donde subexpresión es cualquier patrón de expresión regular válido. La construcción de grupo sin captura se
utiliza normalmente cuando un cuantificador se aplica a un grupo, pero las subcadenas capturadas por el
grupo no tienen ningún interés.

NOTE
Si una expresión regular incluye construcciones de agrupamiento anidadas, no se aplica una construcción de grupo sin
captura exterior a las construcciones de grupo anidadas interiores.

En el ejemplo siguiente se muestra una expresión regular que incluye grupos sin captura. Observe que la
salida no incluye ningún grupo capturado.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(?:\b(?:\w+)\W*)+\.";
string input = "This is a short sentence.";
Match match = Regex.Match(input, pattern);
Console.WriteLine("Match: {0}", match.Value);
for (int ctr = 1; ctr < match.Groups.Count; ctr++)
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups[ctr].Value);
}
}
// The example displays the following output:
// Match: This is a short sentence.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?:\b(?:\w+)\W*)+\."
Dim input As String = "This is a short sentence."
Dim match As Match = Regex.Match(input, pattern)
Console.WriteLine("Match: {0}", match.Value)
For ctr As Integer = 1 To match.Groups.Count - 1
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups(ctr).Value)
Next
End Sub
End Module
' The example displays the following output:
' Match: This is a short sentence.

La expresión regular (?:\b(?:\w+)\W*)+\. coincide con una frase que termina en un punto. Dado que la
expresión regular se centra en frases y no en palabras individuales, las construcciones de agrupamiento se
usan exclusivamente como cuantificadores. El patrón de la expresión regular se interpreta como se muestra
en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de


palabras.

(?:\w+) Buscar coincidencias con uno o más caracteres alfabéticos.


No asigna el texto coincidente a un grupo capturado.

\W* Coincide con cero o más caracteres que no se usan para


formar palabras.

(?:\b(?:\w+)\W*)+ Coincide una o varias veces con el patrón de uno o varios


caracteres que se usan para formar palabras comenzando
por un límite de palabras, seguido de cero o más caracteres
que no se usan para formar palabras. No asigna el texto
coincidente a un grupo capturado.

\. Coincide con un punto.

Opciones de grupo
La siguiente construcción de agrupamiento aplica o deshabilita las opciones especificadas dentro de una
subexpresión:
(?imnsx-imnsx: subexpresión )

donde subexpresión es cualquier patrón de expresión regular válido. Por ejemplo, (?i-s:) activa la opción
que no hace distinción entre mayúsculas y minúsculas y deshabilita el modo de una sola línea. Para obtener
más información sobre las opciones insertadas que puede especificar, vea Opciones de expresiones regulares.
NOTE
Puede especificar opciones que se apliquen a una expresión regular completa en lugar de a una subexpresión usando
un constructor de la clase System.Text.RegularExpressions.Regex o un método estático. También puede especificar
opciones insertadas que se aplican después de un punto concreto en una expresión regular usando la construcción de
lenguaje (?imnsx-imnsx) .

La construcción de opciones de grupo no es un grupo de captura. Es decir, aunque cualquier parte de una
cadena capturada por subexpresión se incluye en la coincidencia, no se incluye en un grupo capturado ni se
usa para rellenar el objeto GroupCollection .
Por ejemplo, la expresión regular \b(?ix: d \w+)\s del ejemplo siguiente utiliza opciones insertadas en una
construcción de agrupamiento para habilitar la coincidencia sin distinción entre mayúsculas y minúsculas y
omitir el espacio en blanco del patrón para identificar todas las palabras que comienzan por la letra "d". La
expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de


palabras.

(?ix: d \w+) Usando una coincidencia sin distinción entre mayúsculas y


minúsculas y omitiendo los espacios en blanco en este
patrón, busca una "d" seguida de uno o varios caracteres
que se usan para formar palabras.

\s Coincide con un carácter de espacio en blanco.

string pattern = @"\b(?ix: d \w+)\s";


string input = "Dogs are decidedly good pets.";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine("'{0}// found at index {1}.", match.Value, match.Index);
// The example displays the following output:
// 'Dogs // found at index 0.
// 'decidedly // found at index 9.

Dim pattern As String = "\b(?ix: d \w+)\s"


Dim input As String = "Dogs are decidedly good pets."

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("'{0}' found at index {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Dogs ' found at index 0.
' 'decidedly ' found at index 9.

Aserciones de búsqueda anticipada positiva de ancho cero


La construcción de agrupamiento siguiente define una aserción de búsqueda anticipada positiva de ancho
cero:
(?= subexpresión )

donde subexpresión es cualquier patrón de expresión regular. Para que se produzca una coincidencia, la
cadena de entrada debe coincidir con el patrón de expresión regular de subexpresión, aunque la subcadena
coincidente no se incluya en el resultado de la coincidencia. Una aserción de búsqueda anticipada positiva de
ancho cero no retrocede.
Normalmente, una aserción de búsqueda anticipada positiva de ancho cero se encuentra al final de un patrón
de expresión regular. Define una subcadena que se debe encontrar al final de una cadena para que se
produzca una coincidencia, pero que no debe incluirse en la coincidencia. También resulta útil para evitar un
retroceso excesivo. Puede usar una aserción de búsqueda anticipada positiva de ancho cero para asegurarse
de que un grupo capturado determinado comienza por un texto que coincide con un subconjunto del patrón
definido para dicho grupo capturado. Por ejemplo, si un grupo de captura coincide con caracteres
consecutivos que se usan para formar palabras, puede usar una aserción de búsqueda anticipada positiva de
ancho cero para requerir que el primero de los caracteres sea alfabético y esté en mayúsculas.
En el ejemplo siguiente se usa una aserción de búsqueda anticipada positiva de ancho cero para buscar la
palabra que precede al verbo "is" en la cadena de entrada.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b\w+(?=\sis\b)";
string[] inputs = { "The dog is a Malamute.",
"The island has beautiful birds.",
"The pitch missed home plate.",
"Sunday is a weekend day." };

foreach (string input in inputs)


{
Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine("'{0}' precedes 'is'.", match.Value);
else
Console.WriteLine("'{0}' does not match the pattern.", input);
}
}
}
// The example displays the following output:
// 'dog' precedes 'is'.
// 'The island has beautiful birds.' does not match the pattern.
// 'The pitch missed home plate.' does not match the pattern.
// 'Sunday' precedes 'is'.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b\w+(?=\sis\b)"
Dim inputs() As String = {"The dog is a Malamute.", _
"The island has beautiful birds.", _
"The pitch missed home plate.", _
"Sunday is a weekend day."}

For Each input As String In inputs


Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine("'{0}' precedes 'is'.", match.Value)
Else
Console.WriteLine("'{0}' does not match the pattern.", input)
End If
Next
End Sub
End Module
' The example displays the following output:
' 'dog' precedes 'is'.
' 'The island has beautiful birds.' does not match the pattern.
' 'The pitch missed home plate.' does not match the pattern.
' 'Sunday' precedes 'is'.

La expresión regular \b\w+(?=\sis\b) se interpreta como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de


palabras.

\w+ Buscar coincidencias con uno o más caracteres alfabéticos.

(?=\sis\b) Determina si los caracteres que se usan para formar


palabras van seguidos de un carácter de espacio en blanco
y la cadena "is", seguida de un límite de palabras. En ese
caso, la coincidencia es correcta.

Aserciones de búsqueda anticipada negativa de ancho cero


La construcción de agrupamiento siguiente define una aserción de búsqueda anticipada negativa de ancho
cero:
(?! subexpresión )

donde subexpresión es cualquier patrón de expresión regular. Para que se produzca la coincidencia, la cadena
de entrada no debe coincidir con el patrón de expresión regular de subexpresión, aunque la cadena
coincidente no se incluya en el resultado de la coincidencia.
Una aserción de búsqueda anticipada negativa de ancho cero se utiliza normalmente al principio o al final de
una expresión regular. Al principio de una expresión regular, puede definir un patrón concreto que no se
debería buscar cuando el principio de la expresión regular define un patrón similar pero más general que se
desea buscar. En este caso, se usa a menudo para limitar el retroceso. Al final de una expresión regular, puede
definir una subexpresión que no se puede producir al final de una coincidencia.
En el ejemplo siguiente se define una expresión regular que utiliza una aserción de búsqueda anticipada
negativa de ancho cero al principio de la expresión regular para buscar palabras que no comienzan por "un".
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(?!un)\w+\b";
string input = "unite one unethical ethics use untie ultimate";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// one
// ethics
// use
// ultimate

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(?!un)\w+\b"
Dim input As String = "unite one unethical ethics use untie ultimate"
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' one
' ethics
' use
' ultimate

La expresión regular \b(?!un)\w+\b se interpreta como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de


palabras.

(?!un) Determina si los dos caracteres siguientes son "un". Si no lo


son, es posible una coincidencia.

\w+ Buscar coincidencias con uno o más caracteres alfabéticos.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

En el ejemplo siguiente se define una expresión regular que utiliza una aserción de búsqueda anticipada
negativa de ancho cero al final de la expresión regular para buscar palabras que no terminan por un carácter
de puntuación.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b\w+\b(?!\p{P})";
string input = "Disconnected, disjointed thoughts in a sentence fragment.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// disjointed
// thoughts
// in
// a
// sentence

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b\w+\b(?!\p{P})"
Dim input As String = "Disconnected, disjointed thoughts in a sentence fragment."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' disjointed
' thoughts
' in
' a
' sentence

La expresión regular \b\w+\b(?!\p{P}) se interpreta como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de


palabras.

\w+ Buscar coincidencias con uno o más caracteres alfabéticos.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

\p{P}) Si el carácter siguiente no es un signo de puntuación (como


un punto o una coma), la coincidencia se realiza.

Aserciones de búsqueda tardía positiva de ancho cero


La construcción de agrupamiento siguiente define una aserción de búsqueda tardía positiva de ancho cero:
(?<= subexpresión )

donde subexpresión es cualquier patrón de expresión regular. Para que se produzca una coincidencia,
subexpresión debe encontrarse en la cadena de entrada a la izquierda de la posición actual, aunque
subexpression no esté incluida en el resultado de la coincidencia. Una aserción de búsqueda tardía positiva
de ancho cero no retrocede.
Las aserciones de búsqueda tardía positiva de ancho cero se usan normalmente al principio de las
expresiones regulares. El patrón que definen es una condición previa de una coincidencia, aunque no forma
parte del resultado de la coincidencia.
Por ejemplo, el ejemplo siguiente coincide con los dos últimos dígitos del año para el siglo XXI (es decir,
requiere que los dígitos "20" precedan a la cadena coincidente).

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "2010 1999 1861 2140 2009";
string pattern = @"(?<=\b20)\d{2}\b";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// 10
// 09

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "2010 1999 1861 2140 2009"
Dim pattern As String = "(?<=\b20)\d{2}\b"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' 10
' 09

El patrón de la expresión regular (?<=\b20)\d{2}\b se interpreta como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\d{2} Coincide con dos dígitos decimales.

(?<=\b20) Continúa la búsqueda si los dos dígitos decimales van


precedidos de los dos dígitos decimales "20" en un límite
de palabra.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

Las aserciones de búsqueda tardía positiva de ancho cero también se usan para limitar el retroceso cuando el
último carácter o caracteres de un grupo capturado debe ser un subconjunto de los caracteres que coincide
con el patrón de la expresión regular de dicho grupo. Por ejemplo, si un grupo captura todos los caracteres
que se usan para formar palabras consecutivos, puede usar una aserción de búsqueda tardía positiva de
ancho cero para requerir que el último carácter sea alfabético.

Aserciones de búsqueda tardía negativa de ancho cero


La construcción de agrupamiento siguiente define una aserción de búsqueda tardía negativa de ancho cero:
(?<! subexpresión )

donde subexpresión es cualquier patrón de expresión regular. Para que se produzca una coincidencia,
subexpresión no debe encontrarse en la cadena de entrada a la izquierda de la posición actual. Sin embargo,
cualquier subcadena que no coincida con subexpression no se incluye en el resultado de la coincidencia.
Las aserciones de búsqueda tardía negativa de ancho cero se usan normalmente al principio de las
expresiones regulares. El patrón que definen impide una coincidencia en la cadena que sigue. También se usan
para limitar el retroceso cuando el último carácter o caracteres de un grupo capturado no debe ser uno o
varios de los caracteres que coinciden con el patrón de expresión regular de dicho grupo. Por ejemplo, si un
grupo captura todos los caracteres que se usan para formar palabras consecutivos, se puede usar una
aserción de búsqueda tardía positiva de ancho cero para requerir que el último carácter no sea de subrayado
(_).
El ejemplo siguiente busca la fecha de cualquier día de la semana que no sea fin de semana (es decir, que no
sea ni sábado ni domingo).

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] dates = { "Monday February 1, 2010",
"Wednesday February 3, 2010",
"Saturday February 6, 2010",
"Sunday February 7, 2010",
"Monday, February 8, 2010" };
string pattern = @"(?<!(Saturday|Sunday) )\b\w+ \d{1,2}, \d{4}\b";

foreach (string dateValue in dates)


{
Match match = Regex.Match(dateValue, pattern);
if (match.Success)
Console.WriteLine(match.Value);
}
}
}
// The example displays the following output:
// February 1, 2010
// February 3, 2010
// February 8, 2010
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim dates() As String = {"Monday February 1, 2010", _
"Wednesday February 3, 2010", _
"Saturday February 6, 2010", _
"Sunday February 7, 2010", _
"Monday, February 8, 2010"}
Dim pattern As String = "(?<!(Saturday|Sunday) )\b\w+ \d{1,2}, \d{4}\b"

For Each dateValue As String In dates


Dim match As Match = Regex.Match(dateValue, pattern)
If match.Success Then
Console.WriteLine(match.Value)
End If
Next
End Sub
End Module
' The example displays the following output:
' February 1, 2010
' February 3, 2010
' February 8, 2010

El patrón de la expresión regular (?<!(Saturday|Sunday) )\b\w+ \d{1,2}, \d{4}\b se interpreta como se


muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de


palabras.

\w+ Coincide con uno o varios caracteres que se usan para


formar palabras seguidos de un carácter de espacio en
blanco.

\d{1,2}, Coincide con uno o dos dígitos decimales seguidos de un


carácter de espacio en blanco y una coma.

\d{4}\b Coincide con cuatro dígitos decimales seguidos de un límite


de palabras.

(?<!(Saturday|Sunday) ) Si la coincidencia va precedida por algo distinto de las


cadenas "Saturday" o "Sunday" seguidas de un espacio, la
coincidencia es correcta.

Grupos atómicos
La construcción de agrupamiento siguiente representa un grupo atómico (conocido en otros motores de
expresiones regulares como subexpresión sin retroceso, subexpresión atómica o subexpresión de una sola
vez):
(?> subexpresión )

donde subexpresión es cualquier patrón de expresión regular.


Comúnmente, si una expresión regular incluye un patrón de coincidencia opcional o alternativo y no se
produce una coincidencia, el motor de expresiones regulares puede crear una bifurcación en varias
direcciones para buscar coincidencias de una cadena de entrada con un patrón. Si no se encuentra una
coincidencia cuando toma la primera bifurcación, el motor de expresiones regulares puede regresar o
retroceder al punto donde tomó la primera bifurcación e intentar la coincidencia usando la segunda
bifurcación. Este proceso puede continuar hasta que se hayan probado todas las bifurcaciones.
El grupo (?> subexpresión ) deshabilita el retroceso. El motor de expresiones regulares buscará
coincidencias con tantos caracteres de la cadena de entrada como pueda. Cuando ya no sean posibles más
coincidencias, no retrocederá para intentar coincidencias con patrones alternativos. (Es decir, la subexpresión
solo busca cadenas que coincidan exclusivamente con la subexpresión; no intenta buscar una cadena
basándose en la subexpresión y en cualquier subexpresión que la siga).
Se recomienda usar esta opción si se sabe que el retroceso no tendrá éxito. Si se evita que el motor de
expresiones regulares realice búsquedas innecesarias, se mejora el rendimiento.
En el ejemplo siguiente se muestra cómo un grupo atómico modifica los resultados de una coincidencia de
patrones. La expresión regular con retroceso coincide correctamente con una serie de caracteres repetidos
seguidos de una o varias apariciones del mismo carácter en un límite de palabras, pero la expresión regular
sin retroceso no lo hace.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "cccd.", "aaad", "aaaa" };
string back = @"(\w)\1+.\b";
string noback = @"(?>(\w)\1+).\b";

foreach (string input in inputs)


{
Match match1 = Regex.Match(input, back);
Match match2 = Regex.Match(input, noback);
Console.WriteLine("{0}: ", input);

Console.Write(" Backtracking : ");


if (match1.Success)
Console.WriteLine(match1.Value);
else
Console.WriteLine("No match");

Console.Write(" Nonbacktracking: ");


if (match2.Success)
Console.WriteLine(match2.Value);
else
Console.WriteLine("No match");
}
}
}
// The example displays the following output:
// cccd.:
// Backtracking : cccd
// Nonbacktracking: cccd
// aaad:
// Backtracking : aaad
// Nonbacktracking: aaad
// aaaa:
// Backtracking : aaaa
// Nonbacktracking: No match
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"cccd.", "aaad", "aaaa"}
Dim back As String = "(\w)\1+.\b"
Dim noback As String = "(?>(\w)\1+).\b"

For Each input As String In inputs


Dim match1 As Match = Regex.Match(input, back)
Dim match2 As Match = Regex.Match(input, noback)
Console.WriteLine("{0}: ", input)

Console.Write(" Backtracking : ")


If match1.Success Then
Console.WriteLine(match1.Value)
Else
Console.WriteLine("No match")
End If

Console.Write(" Nonbacktracking: ")


If match2.Success Then
Console.WriteLine(match2.Value)
Else
Console.WriteLine("No match")
End If
Next
End Sub
End Module
' The example displays the following output:
' cccd.:
' Backtracking : cccd
' Nonbacktracking: cccd
' aaad:
' Backtracking : aaad
' Nonbacktracking: aaad
' aaaa:
' Backtracking : aaaa
' Nonbacktracking: No match

La expresión regular sin retroceso (?>(\w)\1+).\b se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

(\w) Coincide con un único carácter que se usa para formar


palabras y se lo asigna al primer grupo de captura.

\1+ Coincide con el valor de la primera subcadena capturada


una o varias veces.

. Coincide con cualquier carácter.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

(?>(\w)\1+) Coincide con una o varias apariciones de un carácter que se


usa para formar palabras duplicado, pero no retrocede para
buscar el último carácter de un límite de palabras.

Construcciones de agrupamiento y objetos de las expresiones


regulares
Las subcadenas con las que coincide un grupo de captura de una expresión regular se representan mediante
objetos System.Text.RegularExpressions.Group , que se pueden recuperar del objeto
System.Text.RegularExpressions.GroupCollection que devuelve la propiedad Match.Groups . El objeto
GroupCollection se rellena como sigue:
El primer objeto Group de la colección (el objeto con el índice cero) representa la coincidencia
completa.
El siguiente conjunto de objetos Group representa los grupos de captura sin nombre (numerados).
Aparecen en el orden en el que se definen en la expresión regular, de izquierda a derecha. Los valores
de índice de estos grupos van de 1 al número de grupos de captura sin nombre de la colección. (El
índice de un grupo determinado es equivalente a su referencia inversa numerada. Para más
información sobre las referencias inversas, vea Construcciones de referencia inversa)).
El conjunto final de objetos Group representa los grupos de captura con nombre. Aparecen en el orden
en el que se definen en la expresión regular, de izquierda a derecha. El valor de índice del primer grupo
de captura con nombre es una unidad mayor que el índice del último grupo de captura sin nombre. Si
no hay ningún grupo de captura sin nombre en la expresión regular, el valor de índice del primer grupo
de captura con nombre es uno.
Si se aplica un cuantificador a un grupo de captura, las propiedades Group , Capture.Valuee Capture.Indexdel
objeto Capture.Length correspondiente reflejarán la última subcadena capturada por un grupo de captura. Se
puede recuperar un conjunto completo de subcadenas capturadas por grupos que tienen cuantificadores
desde el objeto CaptureCollection devuelto por la propiedad Group.Captures .
El ejemplo siguiente aclara la relación entre los objetos Group y Capture .
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(\b(\w+)\W+)+";
string input = "This is a short sentence.";
Match match = Regex.Match(input, pattern);
Console.WriteLine("Match: '{0}'", match.Value);
for (int ctr = 1; ctr < match.Groups.Count; ctr++)
{
Console.WriteLine(" Group {0}: '{1}'", ctr, match.Groups[ctr].Value);
int capCtr = 0;
foreach (Capture capture in match.Groups[ctr].Captures)
{
Console.WriteLine(" Capture {0}: '{1}'", capCtr, capture.Value);
capCtr++;
}
}
}
}
// The example displays the following output:
// Match: 'This is a short sentence.'
// Group 1: 'sentence.'
// Capture 0: 'This '
// Capture 1: 'is '
// Capture 2: 'a '
// Capture 3: 'short '
// Capture 4: 'sentence.'
// Group 2: 'sentence'
// Capture 0: 'This'
// Capture 1: 'is'
// Capture 2: 'a'
// Capture 3: 'short'
// Capture 4: 'sentence'
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(\b(\w+)\W+)+"
Dim input As String = "This is a short sentence."
Dim match As Match = Regex.Match(input, pattern)
Console.WriteLine("Match: '{0}'", match.Value)
For ctr As Integer = 1 To match.Groups.Count - 1
Console.WriteLine(" Group {0}: '{1}'", ctr, match.Groups(ctr).Value)
Dim capCtr As Integer = 0
For Each capture As Capture In match.Groups(ctr).Captures
Console.WriteLine(" Capture {0}: '{1}'", capCtr, capture.Value)
capCtr += 1
Next
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is a short sentence.'
' Group 1: 'sentence.'
' Capture 0: 'This '
' Capture 1: 'is '
' Capture 2: 'a '
' Capture 3: 'short '
' Capture 4: 'sentence.'
' Group 2: 'sentence'
' Capture 0: 'This'
' Capture 1: 'is'
' Capture 2: 'a'
' Capture 3: 'short'
' Capture 4: 'sentence'

El patrón de expresión regular (\b(\w+)\W+)+ extrae palabras individuales de una cadena. Se define como se
muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de


palabras.

(\w+) Buscar coincidencias con uno o más caracteres alfabéticos.


Juntos, estos caracteres forman una palabra. Este es el
segundo grupo de captura.

\W+ Coincide con uno o varios caracteres que no se usan para


formar palabras.

(\b(\w+)\W+) Coincide una o varias veces con el patrón de uno o varios


caracteres que se usan para formar palabras seguidos de
uno o varios caracteres que no se usan para formar
palabras. Este es el primer grupo de captura.

El segundo grupo de captura coincide con cada palabra de la frase. El primer grupo de captura coincide con
cada palabra, junto con la puntuación y el espacio en blanco que siguen a la palabra. El objeto Group cuyo
índice es 2 proporciona información sobre el texto coincidente con el segundo grupo de captura. El conjunto
de palabras completo capturado por el grupo de captura está disponible desde el objeto CaptureCollection
devuelto por la propiedad Group.Captures .

Vea también
Lenguaje de expresiones regulares: referencia rápida
Retroceso
cuantificadores en expresiones regulares
16/09/2020 • 35 minutes to read • Edit Online

Los cuantificadores especifican cuántas instancias de un carácter, grupo o clase de caracteres deben estar
presentes en la entrada para que se encuentre una coincidencia. En la tabla siguiente se indican los
cuantificadores compatibles con .NET.

C UA N T IF IC A DO R EXPA N SIVO C UA N T IF IC A DO R DIF ERIDO DESC RIP C IÓ N

* *? Coincide cero o más veces.

+ +? Coincide una o más veces.

? ?? Coincide cero o una vez.

{ n } { n }? Coincide exactamente n veces.

{ n ,} { n ,}? Coincide al menos n veces.

{ n , m } { n , m }? Coincide de n a m veces.

Las cantidades n y m son constantes de tipo entero. Normalmente, los cuantificadores son expansivos, ya que
hacen que el motor de expresiones regulares busque el mayor número posible de repeticiones de patrones
concretos. Si se anexa el carácter ? a un cuantificador se convierte en diferido, ya que hace que el motor de
expresiones regulares busque el menor número posible de repeticiones. Para obtener una descripción completa
de la diferencia entre los cuantificadores expansivos y diferidos, consulte la sección Cuantificadores expansivos
y diferidos más adelante en este tema.

IMPORTANT
El anidamiento de cuantificadores (por ejemplo, como hace el patrón de expresión regular (a*)* ) puede aumentar el
número de comparaciones que debe realizar el motor de expresiones regulares, como una función exponencial del
número de caracteres de la cadena de entrada. Para obtener más información sobre este comportamiento y sus
soluciones alternativas, consulte Retroceso.

Cuantificadores de expresiones regulares


En las secciones siguientes se enumeran los cuantificadores admitidos en expresiones regulares de .NET.

NOTE
Si los caracteres *, +, ?, { y } se encuentran en un patrón de expresión regular, el motor de expresiones regulares los
interpreta como cuantificadores o como parte de construcciones de cuantificador, a menos que se incluyan en una clase
de caracteres. Para interpretarlos como caracteres literales fuera de una clase de caracteres, debe anteponerles una barra
diagonal inversa para indicar su secuencia de escape. Por ejemplo, la cadena \* en un patrón de expresión regular se
interpreta como un carácter de asterisco literal ("*").

Coincidir cero o más veces: *


El cuantificador * coincide con el elemento anterior cero o más veces. Equivale al cuantificador {0,} . * es un
cuantificador expansivo cuyo equivalente diferido es *? .
En el ejemplo siguiente se muestra esta expresión regular. De los nueve grupos de dígitos de la cadena de
entrada, cinco coinciden con el patrón y cuatro ( 95 , 929 , 9219 y 9919 ) no coinciden.

string pattern = @"\b91*9*\b";


string input = "99 95 919 929 9119 9219 999 9919 91119";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// '99' found at position 0.
// '919' found at position 6.
// '9119' found at position 14.
// '999' found at position 24.
// '91119' found at position 33.

Dim pattern As String = "\b91*9*\b"


Dim input As String = "99 95 919 929 9119 9219 999 9919 91119"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' '99' found at position 0.
' '919' found at position 6.
' '9119' found at position 14.
' '999' found at position 24.
' '91119' found at position 33.

El patrón de expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

91* Coincide con un "9" seguido de cero o más caracteres "1".

9* Coincide con cero o más caracteres "9".

\b Finaliza en un límite de palabras.

Coincidir una o más veces: +


El cuantificador + coincide con el elemento anterior una o más veces. Equivale a {1,} . + es un cuantificador
expansivo cuyo equivalente diferido es +? .
Por ejemplo, la expresión regular \ban+\w*?\b intenta coincidir con palabras completas que empiezan por la
letra a seguida de una o más instancias de la letra n . En el ejemplo siguiente se muestra esta expresión
regular. La expresión regular coincide con las palabras an , annual , announcement y antique , y no coincide con
autumn y all .
string pattern = @"\ban+\w*?\b";

string input = "Autumn is a great time for an annual announcement to all antique collectors.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'an' found at position 27.
// 'annual' found at position 30.
// 'announcement' found at position 37.
// 'antique' found at position 57.

Dim pattern As String = "\ban+\w*?\b"

Dim input As String = "Autumn is a great time for an annual announcement to all antique collectors."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'an' found at position 27.
' 'annual' found at position 30.
' 'announcement' found at position 37.
' 'antique' found at position 57.

El patrón de expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

an+ Coincide con una "a" seguida de uno o más caracteres "n".

\w*? Coincide con un carácter de palabra cero o más veces, pero


el menor número de veces que sea posible.

\b Finaliza en un límite de palabras.

Coincidir cero o una vez: ?


El cuantificador ? coincide con el elemento anterior cero o una vez. Equivale a {0,1} . ? es un cuantificador
expansivo cuyo equivalente diferido es ?? .
Por ejemplo, la expresión regular \ban?\b intenta coincidir con palabras completas que empiezan por la letra
a seguida de cero o una instancia de la letra n . En otras palabras, intenta coincidir con las palabras a y an .
En el ejemplo siguiente se muestra esta expresión regular.

string pattern = @"\ban?\b";


string input = "An amiable animal with a large snount and an animated nose.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'An' found at position 0.
// 'a' found at position 23.
// 'an' found at position 42.
Dim pattern As String = "\ban?\b"
Dim input As String = "An amiable animal with a large snount and an animated nose."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'An' found at position 0.
' 'a' found at position 23.
' 'an' found at position 42.

El patrón de expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

an? Coincide con una "a" seguida de cero o un carácter "n".

\b Finaliza en un límite de palabras.

Coincidir exactamente n veces: {n}


El cuantificador { n } coincide con el elemento anterior exactamente n veces, donde n es un entero. { n } es
un cuantificador expansivo cuyo equivalente diferido es { n }? .
Por ejemplo, la expresión regular \b\d+\,\d{3}\b intenta coincidir con un límite de palabra seguido de uno o
más dígitos decimales, seguidos de tres dígitos decimales, seguidos de un límite de palabra. En el ejemplo
siguiente se muestra esta expresión regular.

string pattern = @"\b\d+\,\d{3}\b";


string input = "Sales totaled 103,524 million in January, " +
"106,971 million in February, but only " +
"943 million in March.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// '103,524' found at position 14.
// '106,971' found at position 45.

Dim pattern As String = "\b\d+\,\d{3}\b"


Dim input As String = "Sales totaled 103,524 million in January, " + _
"106,971 million in February, but only " + _
"943 million in March."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' '103,524' found at position 14.
' '106,971' found at position 45.

El patrón de expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.


M O DELO DESC RIP C IÓ N

\d+ Buscar coincidencias con uno o más dígitos decimales.

\, Coincide con un carácter de coma.

\d{3} Coincide con tres dígitos decimales.

\b Finaliza en un límite de palabras.

Coincidir al menos n veces: {n,}


El cuantificador { n ,} coincide con el elemento anterior al menos n, donde n es un entero. { n ,} es un
cuantificador expansivo cuyo equivalente diferido es { n ,}? .
Por ejemplo, la expresión regular \b\d{2,}\b\D+ intenta coincidir con un límite de palabra seguido de por lo
menos dos dígitos, seguidos de un límite de palabra y de un carácter que no sea un dígito. En el ejemplo
siguiente se muestra esta expresión regular. La expresión regular no coincide con la frase "7 days" porque solo
contiene un dígito decimal, pero coincide correctamente con las frases "10 weeks and 300 years" .

string pattern = @"\b\d{2,}\b\D+";


string input = "7 days, 10 weeks, 300 years";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// '10 weeks, ' found at position 8.
// '300 years' found at position 18.

Dim pattern As String = "\b\d{2,}\b\D+"


Dim input As String = "7 days, 10 weeks, 300 years"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' '10 weeks, ' found at position 8.
' '300 years' found at position 18.

El patrón de expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

\d{2,} Coincide con al menos dos dígitos decimales.

\b Coincide con un límite de palabras.

\D+ Coincide con al menos un carácter de dígito no decimal.

Coincidir de n a m veces: {n,m}


El cuantificador { n , m } coincide con el elemento anterior al menos n veces, pero no más de m veces, donde
n y m son enteros. { n , m } es un cuantificador expansivo cuyo equivalente diferido es { n , m }? .
En el ejemplo siguiente, la expresión regular (00\s){2,4} intenta coincidir con dos ceros seguidos de un
espacio que se repitan de dos a cuatro veces. Observe que la parte final de la cadena de entrada incluye este
patrón cinco veces en lugar del máximo de cuatro. Pero solo la parte inicial de esta subcadena (hasta el espacio
y el quinto par de ceros) coincide con el patrón de la expresión regular.

string pattern = @"(00\s){2,4}";


string input = "0x00 FF 00 00 18 17 FF 00 00 00 21 00 00 00 00 00";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// '00 00 ' found at position 8.
// '00 00 00 ' found at position 23.
// '00 00 00 00 ' found at position 35.

Dim pattern As String = "(00\s){2,4}"


Dim input As String = "0x00 FF 00 00 18 17 FF 00 00 00 21 00 00 00 00 00"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' '00 00 ' found at position 8.
' '00 00 00 ' found at position 23.
' '00 00 00 00 ' found at position 35.

Coincidir cero o más veces (coincidencia diferida): *?


El cuantificador *? coincide con el elemento anterior cero o más veces, pero el menor número de veces
posible. Es el equivalente diferido del cuantificador expansivo * .
En el ejemplo siguiente, la expresión regular \b\w*?oo\w*?\b coincide con todas las palabras que contienen la
cadena oo .

string pattern = @"\b\w*?oo\w*?\b";


string input = "woof root root rob oof woo woe";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'woof' found at position 0.
// 'root' found at position 5.
// 'root' found at position 10.
// 'oof' found at position 19.
// 'woo' found at position 23.

Dim pattern As String = "\b\w*?oo\w*?\b"


Dim input As String = "woof root root rob oof woo woe"
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'woof' found at position 0.
' 'root' found at position 5.
' 'root' found at position 10.
' 'oof' found at position 19.
' 'woo' found at position 23.

El patrón de expresión regular se define como se muestra en la tabla siguiente.


M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

\w*? Coincide con cero o más caracteres de palabra, pero con el


menor número de caracteres posible.

oo Coincide con la cadena "oo".

\w*? Coincide con cero o más caracteres de palabra, pero con el


menor número de caracteres posible.

\b Finaliza en un límite de palabras.

Coincidir una o más veces (coincidencia diferida): +?


El cuantificador +? coincide con el elemento anterior una o más veces, pero el menor número de veces posible.
Es el equivalente diferido del cuantificador expansivo + .
Por ejemplo, la expresión regular \b\w+?\b coincide con uno o más caracteres separados por límites de
palabra. En el ejemplo siguiente se muestra esta expresión regular.

string pattern = @"\b\w+?\b";


string input = "Aa Bb Cc Dd Ee Ff";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'Aa' found at position 0.
// 'Bb' found at position 3.
// 'Cc' found at position 6.
// 'Dd' found at position 9.
// 'Ee' found at position 12.
// 'Ff' found at position 15.

Dim pattern As String = "\b\w+?\b"


Dim input As String = "Aa Bb Cc Dd Ee Ff"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Aa' found at position 0.
' 'Bb' found at position 3.
' 'Cc' found at position 6.
' 'Dd' found at position 9.
' 'Ee' found at position 12.
' 'Ff' found at position 15.

Coincidir cero o una vez (coincidencia diferida): ??


El cuantificador ?? coincide con el elemento anterior cero o una vez, pero el menor número de veces posible.
Es el equivalente diferido del cuantificador expansivo ? .
Por ejemplo, la expresión regular ^\s*(System.)??Console.Write(Line)??\(?? intenta coincidir con las cadenas
"Console.Write" o "Console.WriteLine". La cadena también puede incluir "System." antes de "Console", y puede ir
seguida de un paréntesis de apertura. La cadena debe estar al principio de una línea, aunque puede ir precedida
de un espacio en blanco. En el ejemplo siguiente se muestra esta expresión regular.
string pattern = @"^\s*(System.)??Console.Write(Line)??\(??";
string input = "System.Console.WriteLine(\"Hello!\")\n" +
"Console.Write(\"Hello!\")\n" +
"Console.WriteLine(\"Hello!\")\n" +
"Console.ReadLine()\n" +
" Console.WriteLine";
foreach (Match match in Regex.Matches(input, pattern,
RegexOptions.IgnorePatternWhitespace |
RegexOptions.IgnoreCase |
RegexOptions.Multiline))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'System.Console.Write' found at position 0.
// 'Console.Write' found at position 36.
// 'Console.Write' found at position 61.
// ' Console.Write' found at position 110.

Dim pattern As String = "^\s*(System.)??Console.Write(Line)??\(??"


Dim input As String = "System.Console.WriteLine(""Hello!"")" + vbCrLf + _
"Console.Write(""Hello!"")" + vbCrLf + _
"Console.WriteLine(""Hello!"")" + vbCrLf + _
"Console.ReadLine()" + vbCrLf + _
" Console.WriteLine"
For Each match As Match In Regex.Matches(input, pattern, _
RegexOptions.IgnorePatternWhitespace Or RegexOptions.IgnoreCase Or
RegexOptions.MultiLine)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'System.Console.Write' found at position 0.
' 'Console.Write' found at position 36.
' 'Console.Write' found at position 61.
' ' Console.Write' found at position 110.

El patrón de expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

^ Coincide con el inicio del flujo de entrada.

\s* Busca coincidencias con cero o más caracteres de espacio en


blanco.

(System.)?? Coincide con cero o una repetición de la cadena "System.".

Console.Write Coincide con la cadena "Console.Write".

(Line)?? Coincide con cero o una repetición de la cadena "Line".

\(?? Coincide con cero o con una repetición del paréntesis de


apertura.

Coincidir exactamente n veces (coincidencia diferida): {n}?


El cuantificador { n }? coincide con el elemento anterior exactamente n veces, donde n es un entero. Es el
equivalente diferido del cuantificador expansivo { n } .
En el ejemplo siguiente, la expresión regular \b(\w{3,}?\.){2}?\w{3,}?\b se usa para identificar la dirección de
un sitio web. Observe que coincide con "www.microsoft.com" y con "msdn.microsoft.com", pero no coincide con
"mywebsite" ni con "mycompany.com".

string pattern = @"\b(\w{3,}?\.){2}?\w{3,}?\b";


string input = "www.microsoft.com msdn.microsoft.com mywebsite mycompany.com";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'www.microsoft.com' found at position 0.
// 'msdn.microsoft.com' found at position 18.

Dim pattern As String = "\b(\w{3,}?\.){2}?\w{3,}?\b"


Dim input As String = "www.microsoft.com msdn.microsoft.com mywebsite mycompany.com"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'www.microsoft.com' found at position 0.
' 'msdn.microsoft.com' found at position 18.

El patrón de expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

(\w{3,}?\.) Coincide con al menos 3 caracteres de palabra, pero con el


menor número de caracteres posible, seguidos de un
carácter de punto. Este es el primer grupo de captura.

(\w{3,}?\.){2}? Coincide con el patrón del primer grupo dos veces, pero el
menor número de veces posible.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

Coincidir al menos n veces (coincidencia diferida): {n,}?


El cuantificador { n ,}? coincide con el elemento anterior al menos n veces, donde n es un entero, pero el
menor número de veces posible. Es el equivalente diferido del cuantificador expansivo { n ,} .
Vea el ejemplo del cuantificador { n }? en la sección anterior para obtener una ilustración. La expresión
regular de ese ejemplo usa el cuantificador { n ,} para coincidir con una cadena que tenga al menos tres
caracteres seguidos de un punto.
Coincidir de n a m veces (coincidencia diferida): {n,m}?
El cuantificador { n , m }? coincide con el elemento anterior de n a m veces, donde n y m son enteros, pero
el menor número de veces posible. Es el equivalente diferido del cuantificador expansivo { n , m } .
En el ejemplo siguiente, la expresión regular \b[A-Z](\w*?\s*?){1,10}[.!?] coincide con las frases que
contengan de una a diez palabras. Coincidencia con todas las frases de la cadena de entrada, excepto con una
frase que contiene 18 palabras.
string pattern = @"\b[A-Z](\w*?\s*?){1,10}[.!?]";
string input = "Hi. I am writing a short note. Its purpose is " +
"to test a regular expression that attempts to find " +
"sentences with ten or fewer words. Most sentences " +
"in this note are short.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'Hi.' found at position 0.
// 'I am writing a short note.' found at position 4.
// 'Most sentences in this note are short.' found at position 132.

Dim pattern As String = "\b[A-Z](\w*\s?){1,10}?[.!?]"


Dim input As String = "Hi. I am writing a short note. Its purpose is " + _
"to test a regular expression that attempts to find " + _
"sentences with ten or fewer words. Most sentences " + _
"in this note are short."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Hi.' found at position 0.
' 'I am writing a short note.' found at position 4.
' 'Most sentences in this note are short.' found at position 132.

El patrón de expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

[A-Z] Coincide con cualquier letra mayúscula de la A a la Z.

(\w*?\s*?) Coincide con cero o más caracteres de palabra, seguidos de


uno o más caracteres de espacio en blanco, pero el menor
número de veces posible. Este es el primer grupo de
capturas.

{1,10} Coincide con el patrón anterior entre una y diez veces.

[.!?] Coincide con cualquiera de los caracteres de puntuación ".",


"!" o "?".

Cuantificadores expansivos y diferidos


Varios cuantificadores tienen dos versiones:
Una versión expansiva.
Un cuantificador expansivo intenta coincidir con un elemento tantas veces como sea posible.
Una versión no expansiva o diferida.
Un cuantificador no expansivo intenta coincidir con un elemento el menor número de veces que sea
posible. Para convertir un cuantificador expansivo en un cuantificador diferido con tan solo agregar el
signo ? .
Considere una expresión regular simple diseñada para extraer los cuatro últimos dígitos de una cadena de
números, como un número de tarjeta de crédito. La versión de la expresión regular que usa el cuantificador
expansivo * es \b.*([0-9]{4})\b . Pero si una cadena contiene dos números, esta expresión regular coincide
con los cuatro últimos dígitos del segundo número, como se muestra en el ejemplo siguiente.

string greedyPattern = @"\b.*([0-9]{4})\b";


string input1 = "1112223333 3992991999";
foreach (Match match in Regex.Matches(input1, greedyPattern))
Console.WriteLine("Account ending in ******{0}.", match.Groups[1].Value);

// The example displays the following output:


// Account ending in ******1999.

Dim greedyPattern As String = "\b.*([0-9]{4})\b"


Dim input1 As String = "1112223333 3992991999"
For Each match As Match In Regex.Matches(input1, greedypattern)
Console.WriteLine("Account ending in ******{0}.", match.Groups(1).Value)
Next
' The example displays the following output:
' Account ending in ******1999.

La expresión regular no coincide con el primer número porque el cuantificador * intenta coincidir con el
elemento anterior tantas veces como sea posible en toda la cadena y, por tanto, encuentra una coincidencia al
final de la cadena.
Este no es el comportamiento deseado. En su lugar, puede usar el cuantificador diferido *? para extraer los
dígitos de ambos números, tal como se muestra en el ejemplo siguiente.

string lazyPattern = @"\b.*?([0-9]{4})\b";


string input2 = "1112223333 3992991999";
foreach (Match match in Regex.Matches(input2, lazyPattern))
Console.WriteLine("Account ending in ******{0}.", match.Groups[1].Value);

// The example displays the following output:


// Account ending in ******3333.
// Account ending in ******1999.

Dim lazyPattern As String = "\b.*?([0-9]{4})\b"


Dim input2 As String = "1112223333 3992991999"
For Each match As Match In Regex.Matches(input2, lazypattern)
Console.WriteLine("Account ending in ******{0}.", match.Groups(1).Value)
Next
' The example displays the following output:
' Account ending in ******3333.
' Account ending in ******1999.

En la mayoría de los casos, las expresiones regulares con cuantificadores expansivos y diferidos devuelven las
mismas coincidencias. Suelen devolver resultados diferentes cuando se usan con el metacarácter comodín ( . ),
que coincide con cualquier carácter.

Cuantificadores y coincidencias vacías


Los cuantificadores * , + y { n , m } y sus equivalentes diferidos nunca se repiten tras una coincidencia
vacía cuando no se ha encontrado el número mínimo de capturas. Esta regla impide que los cuantificadores
entren en bucles infinitos en coincidencias de subexpresiones vacías cuando el número máximo de posibles
capturas de grupo es infinito o cerca de infinito.
Por ejemplo, en el código siguiente se muestra el resultado de una llamada al método Regex.Match con el
patrón de expresión regular (a?)* que coincide cero o más veces con cero o un carácter "a". Observe que el
único grupo de captura realiza una captura de todos los caracteres "a", así como de String.Empty, pero no hay
una segunda coincidencia vacía, ya que la primera coincidencia vacía hace que el cuantificador deje de repetirse.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "(a?)*";
string input = "aaabbb";
Match match = Regex.Match(input, pattern);
Console.WriteLine("Match: '{0}' at index {1}",
match.Value, match.Index);
if (match.Groups.Count > 1) {
GroupCollection groups = match.Groups;
for (int grpCtr = 1; grpCtr <= groups.Count - 1; grpCtr++) {
Console.WriteLine(" Group {0}: '{1}' at index {2}",
grpCtr,
groups[grpCtr].Value,
groups[grpCtr].Index);
int captureCtr = 0;
foreach (Capture capture in groups[grpCtr].Captures) {
captureCtr++;
Console.WriteLine(" Capture {0}: '{1}' at index {2}",
captureCtr, capture.Value, capture.Index);
}
}
}
}
}
// The example displays the following output:
// Match: 'aaa' at index 0
// Group 1: '' at index 3
// Capture 1: 'a' at index 0
// Capture 2: 'a' at index 1
// Capture 3: 'a' at index 2
// Capture 4: '' at index 3
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(a?)*"
Dim input As String = "aaabbb"
Dim match As Match = Regex.Match(input, pattern)
Console.WriteLine("Match: '{0}' at index {1}",
match.Value, match.Index)
If match.Groups.Count > 1 Then
Dim groups As GroupCollection = match.Groups
For grpCtr As Integer = 1 To groups.Count - 1
Console.WriteLine(" Group {0}: '{1}' at index {2}",
grpCtr,
groups(grpCtr).Value,
groups(grpCtr).Index)
Dim captureCtr As Integer = 0
For Each capture As Capture In groups(grpCtr).Captures
captureCtr += 1
Console.WriteLine(" Capture {0}: '{1}' at index {2}",
captureCtr, capture.Value, capture.Index)
Next
Next
End If
End Sub
End Module
' The example displays the following output:
' Match: 'aaa' at index 0
' Group 1: '' at index 3
' Capture 1: 'a' at index 0
' Capture 2: 'a' at index 1
' Capture 3: 'a' at index 2
' Capture 4: '' at index 3

Para ver la diferencia práctica entre un grupo de captura que define un número mínimo y máximo de capturas y
otro que define un número fijo de capturas, tenga en cuenta los patrones de expresiones regulares
(a\1|(?(1)\1)){0,2} y (a\1|(?(1)\1)){2} . Ambas expresiones regulares constan de un único grupo de captura,
que se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

(a\1 Coincide con "a", junto con el valor del primer grupo
capturado...

|(?(1) … o bien, prueba si se ha definido el primer grupo


capturado. (Tenga en cuenta que la construcción (?(1) no
define un grupo de captura).

\1)) Si el primer grupo capturado existe, coincide con su valor. Si


el grupo no existe, el grupo coincidirá con String.Empty.

La primera expresión regular intenta coincidir con este patrón de cero a dos veces; el segundo, exactamente dos
veces. Dado que el primer modelo alcanza el número mínimo de capturas con su primera captura de
String.Empty, nunca se repite para intentar coincidir con a\1 ; el cuantificador {0,2} solo permite las
coincidencias vacías en la última iteración. En cambio, la segunda expresión regular coincide con "a" porque
evalúa a\1 una segunda vez. El número mínimo de iteraciones (dos) obliga al motor a repetirse tras una
coincidencia vacía.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern, input;

pattern = @"(a\1|(?(1)\1)){0,2}";
input = "aaabbb";

Console.WriteLine("Regex pattern: {0}", pattern);


Match match = Regex.Match(input, pattern);
Console.WriteLine("Match: '{0}' at position {1}.",
match.Value, match.Index);
if (match.Groups.Count > 1) {
for (int groupCtr = 1; groupCtr <= match.Groups.Count - 1; groupCtr++)
{
Group group = match.Groups[groupCtr];
Console.WriteLine(" Group: {0}: '{1}' at position {2}.",
groupCtr, group.Value, group.Index);
int captureCtr = 0;
foreach (Capture capture in group.Captures) {
captureCtr++;
Console.WriteLine(" Capture: {0}: '{1}' at position {2}.",
captureCtr, capture.Value, capture.Index);
}
}
}
Console.WriteLine();

pattern = @"(a\1|(?(1)\1)){2}";
Console.WriteLine("Regex pattern: {0}", pattern);
match = Regex.Match(input, pattern);
Console.WriteLine("Matched '{0}' at position {1}.",
match.Value, match.Index);
if (match.Groups.Count > 1) {
for (int groupCtr = 1; groupCtr <= match.Groups.Count - 1; groupCtr++)
{
Group group = match.Groups[groupCtr];
Console.WriteLine(" Group: {0}: '{1}' at position {2}.",
groupCtr, group.Value, group.Index);
int captureCtr = 0;
foreach (Capture capture in group.Captures) {
captureCtr++;
Console.WriteLine(" Capture: {0}: '{1}' at position {2}.",
captureCtr, capture.Value, capture.Index);
}
}
}
}
}
// The example displays the following output:
// Regex pattern: (a\1|(?(1)\1)){0,2}
// Match: '' at position 0.
// Group: 1: '' at position 0.
// Capture: 1: '' at position 0.
//
// Regex pattern: (a\1|(?(1)\1)){2}
// Matched 'a' at position 0.
// Group: 1: 'a' at position 0.
// Capture: 1: '' at position 0.
// Capture: 2: 'a' at position 0.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern, input As String

pattern = "(a\1|(?(1)\1)){0,2}"
input = "aaabbb"

Console.WriteLine("Regex pattern: {0}", pattern)


Dim match As Match = Regex.Match(input, pattern)
Console.WriteLine("Match: '{0}' at position {1}.",
match.Value, match.Index)
If match.Groups.Count > 1 Then
For groupCtr As Integer = 1 To match.Groups.Count - 1
Dim group As Group = match.Groups(groupCtr)
Console.WriteLine(" Group: {0}: '{1}' at position {2}.",
groupCtr, group.Value, group.Index)
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
captureCtr += 1
Console.WriteLine(" Capture: {0}: '{1}' at position {2}.",
captureCtr, capture.Value, capture.Index)
Next
Next
End If
Console.WriteLine()

pattern = "(a\1|(?(1)\1)){2}"
Console.WriteLine("Regex pattern: {0}", pattern)
match = Regex.Match(input, pattern)
Console.WriteLine("Matched '{0}' at position {1}.",
match.Value, match.Index)
If match.Groups.Count > 1 Then
For groupCtr As Integer = 1 To match.Groups.Count - 1
Dim group As Group = match.Groups(groupCtr)
Console.WriteLine(" Group: {0}: '{1}' at position {2}.",
groupCtr, group.Value, group.Index)
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
captureCtr += 1
Console.WriteLine(" Capture: {0}: '{1}' at position {2}.",
captureCtr, capture.Value, capture.Index)
Next
Next
End If
End Sub
End Module
' The example displays the following output:
' Regex pattern: (a\1|(?(1)\1)){0,2}
' Match: '' at position 0.
' Group: 1: '' at position 0.
' Capture: 1: '' at position 0.
'
' Regex pattern: (a\1|(?(1)\1)){2}
' Matched 'a' at position 0.
' Group: 1: 'a' at position 0.
' Capture: 1: '' at position 0.
' Capture: 2: 'a' at position 0.

Vea también
Lenguaje de expresiones regulares: referencia rápida
Retroceso
Construcciones de referencia inversa en expresiones
regulares
16/09/2020 • 16 minutes to read • Edit Online

Las referencias inversas proporcionan una forma cómoda de identificar un carácter o subcadena repetidos dentro
de una cadena. Por ejemplo, si la cadena de entrada contiene varias apariciones de una subcadena arbitraria,
puede buscar una coincidencia con la primera aparición con un grupo de captura y después usar una referencia
inversa para buscar una coincidencia con las siguientes apariciones de la subcadena.

NOTE
Se usa una sintaxis independiente para hacer referencia a los grupos de captura con numeración y con nombre de las
cadenas de reemplazo. Para obtener más información, consulte Substituciones.

.NET define elementos del lenguaje independientes para hacer referencia a los grupos de captura con numeración
y con nombre. Para más información sobre los grupos de captura con nombre, vea Construcciones de
agrupamiento.

Referencias inversas con numeración


Una referencia inversa con numeración usa la siguiente sintaxis:
\ número
donde número es la posición ordinal del grupo de captura en la expresión regular. Por ejemplo, \4 coincide con
el contenido del cuarto grupo de captura. Si número no está definido en el patrón de expresión regular, se
produce un error de análisis y el motor de expresiones regulares produce una clase ArgumentException. Por
ejemplo, la expresión regular \b(\w+)\s\1 es válida, porque (\w+) es el primer y único grupo de captura de la
expresión. Por otro lado, \b(\w+)\s\2 no es válida y produce una excepción de argumento porque no hay ningún
grupo de captura con numeración \2 . Además, si número identifica un grupo de captura en una posición ordinal
determinada, pero a ese grupo de captura se le ha asignado un nombre numérico que no sea su posición ordinal,
el analizador de expresiones regulares también produce una excepción ArgumentException.
Tenga en cuenta la ambigüedad entre los códigos de escape octales (como \16 ) y las referencias inversas \
número que usan la misma notación. Esta ambigüedad se resuelve de la siguiente forma:
Las expresiones \1 a \9 siempre se interpretan como referencias inversas y no como códigos octales.
Si el primer dígito de una expresión con varios dígitos es 8 o 9 (como \80 o \91 ), la expresión se
interpreta como un literal.
Las expresiones a partir de \10 y superiores se consideran referencias inversas si hay una referencia
inversa que se corresponda con ese número; en caso contrario, se interpretan como códigos octales.
Si una expresión regular contiene una referencia inversa a un número de grupo sin definir, se produce un
error de análisis y el motor de expresiones regulares produce una clase ArgumentException.
Si la ambigüedad constituye un problema, puede usar la notación \k< nombre > , que es inequívoca y no se
puede confundir con códigos de caracteres octales. De forma similar, los códigos hexadecimales como \xdd son
inequívocos y no se pueden confundir con las referencias inversas.
En el ejemplo siguiente, se buscan caracteres de palabra duplicados en una cadena. Define una expresión regular,
(\w)\1 , que consta de los siguientes elementos.

EL EM EN TO DESC RIP T IO N

(\w) Coincide con un carácter que se usa para formar palabras y


se lo asigna al primer grupo de captura.

\1 Coincide con el siguiente carácter que sea igual que el valor


del primer grupo de captura.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(\w)\1";
string input = "trellis llama webbing dresser swagger";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Found '{0}' at position {1}.",
match.Value, match.Index);
}
}
// The example displays the following output:
// Found 'll' at position 3.
// Found 'll' at position 8.
// Found 'bb' at position 16.
// Found 'ss' at position 25.
// Found 'gg' at position 33.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(\w)\1"
Dim input As String = "trellis llama webbing dresser swagger"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found '{0}' at position {1}.", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Found 'll' at position 3.
' Found 'll' at position 8.
' Found 'bb' at position 16.
' Found 'ss' at position 25.
' Found 'gg' at position 33.

Referencias inversas con nombre


Una referencia inversa con nombre se define mediante la sintaxis siguiente:
\k< nombre >

O bien
\k' nombre '
donde nombre es el nombre de un grupo de captura definido en el patrón de expresión regular. Si nombre no
está definido en el patrón de expresión regular, se produce un error de análisis y el motor de expresiones
regulares produce una clase ArgumentException.
En el ejemplo siguiente, se buscan caracteres de palabra duplicados en una cadena. Define una expresión regular,
(?<char>\w)\k<char> , que consta de los siguientes elementos.

EL EM EN TO DESC RIP T IO N

(?<char>\w) Coincide con un carácter que se usa para formar palabras y


se lo asigna a un grupo de captura denominado char .

\k<char> Coincide con el siguiente carácter que sea igual que el valor
del grupo de captura char .

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(?<char>\w)\k<char>";
string input = "trellis llama webbing dresser swagger";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Found '{0}' at position {1}.",
match.Value, match.Index);
}
}
// The example displays the following output:
// Found 'll' at position 3.
// Found 'll' at position 8.
// Found 'bb' at position 16.
// Found 'ss' at position 25.
// Found 'gg' at position 33.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?<char>\w)\k<char>"
Dim input As String = "trellis llama webbing dresser swagger"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found '{0}' at position {1}.", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Found 'll' at position 3.
' Found 'll' at position 8.
' Found 'bb' at position 16.
' Found 'ss' at position 25.
' Found 'gg' at position 33.

Referencias inversas numéricas con nombre


En una referencia inversa con \k , nombre también puede ser la representación de cadena de un número. Por
ejemplo, en el ejemplo siguiente, se usa la expresión regular (?<2>\w)\k<2> para buscar caracteres de palabra
duplicados en una cadena. En este caso, el ejemplo define un grupo de captura que se denomina explícitamente
"2" y la referencia inversa se denomina también "2".

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(?<2>\w)\k<2>";
string input = "trellis llama webbing dresser swagger";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Found '{0}' at position {1}.",
match.Value, match.Index);
}
}
// The example displays the following output:
// Found 'll' at position 3.
// Found 'll' at position 8.
// Found 'bb' at position 16.
// Found 'ss' at position 25.
// Found 'gg' at position 33.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?<2>\w)\k<2>"
Dim input As String = "trellis llama webbing dresser swagger"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found '{0}' at position {1}.", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Found 'll' at position 3.
' Found 'll' at position 8.
' Found 'bb' at position 16.
' Found 'ss' at position 25.
' Found 'gg' at position 33.

Si nombre es la representación de cadena de un número y ningún grupo de captura tiene ese nombre, \k<
nombre > es igual que la referencia inversa \ número, donde número es la posición ordinal de la captura. En el
ejemplo siguiente, hay un único grupo de captura denominado char . La construcción de referencia inversa hace
referencia a él como \k<1> . Como muestra la salida del ejemplo, la llamada a Regex.IsMatch se realiza
correctamente porque char es el primer grupo de captura.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
Console.WriteLine(Regex.IsMatch("aa", @"(?<char>\w)\k<1>"));
// Displays "True".
}
}
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Console.WriteLine(Regex.IsMatch("aa", "(?<char>\w)\k<1>"))
' Displays "True".
End Sub
End Module

En cambio, si nombre es la representación de cadena de un número y al grupo de captura de esa posición se le ha


asignado explícitamente un nombre numérico, el analizador de expresiones regulares no puede identificar el
grupo de captura por su posición ordinal. En su lugar, produce una excepción ArgumentException. El único grupo
de captura del siguiente ejemplo se denomina "2". Dado que la construcción \k se usa para definir una
referencia inversa denominada "1", el analizador de expresiones regulares no puede identificar el primer grupo
de captura y produce una excepción.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
Console.WriteLine(Regex.IsMatch("aa", @"(?<2>\w)\k<1>"));
// Throws an ArgumentException.
}
}

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Console.WriteLine(Regex.IsMatch("aa", "(?<2>\w)\k<1>"))
' Throws an ArgumentException.
End Sub
End Module

Con qué coinciden las referencias inversas


Una referencia inversa constituye la definición más reciente de un grupo (la definición más inmediatamente a la
izquierda, al coincidir de izquierda a derecha). Cuando un grupo realiza varias capturas, una referencia inversa se
refiere a la captura más reciente.
En el ejemplo siguiente, se incluye un patrón de expresión regular, (?<1>a)(?<1>\1b)* , que redefine el grupo con
nombre \1. En la tabla siguiente, se describe cada patrón de la expresión regular.

M O DELO DESC RIP T IO N

(?<1>a) Coincide con el carácter "a" y asigna el resultado al grupo de


captura denominado 1 .
M O DELO DESC RIP T IO N

(?<1>\1b)* Coincide con ninguna o más apariciones del grupo


denominado 1 junto con una "b" y asigna el resultado al
grupo de captura denominado 1 .

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(?<1>a)(?<1>\1b)*";
string input = "aababb";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine("Match: " + match.Value);
foreach (Group group in match.Groups)
Console.WriteLine(" Group: " + group.Value);
}
}
}
// The example displays the following output:
// Group: aababb
// Group: abb

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?<1>a)(?<1>\1b)*"
Dim input As String = "aababb"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Match: " + match.Value)
For Each group As Group In match.Groups
Console.WriteLIne(" Group: " + group.Value)
Next
Next
End Sub
End Module
' The example display the following output:
' Group: aababb
' Group: abb

Al comparar la expresión regular con la cadena de entrada ("aababb"), el motor de expresiones regulares realiza
las siguientes operaciones:
1. Comienza al principio de la cadena y hace que "a" coincida correctamente con la expresión (?<1>a) .
Ahora, el valor del grupo 1 es "a".
2. Se desplaza hasta el segundo carácter y hace que la cadena "ab" coincida correctamente con la expresión
\1b , o "ab". Después, asigna el resultado "ab" a \1 .

3. Se desplaza hasta el cuarto carácter. La expresión (?<1>\1b)* puede coincidir cero o más veces, así que la
cadena "abb" coincide correctamente con la expresión \1b . Vuelve a asignar el resultado, "abb", a \1 .

En este ejemplo, * es un cuantificador de bucle: se evalúa repetidas veces hasta que el motor de expresiones
regulares no puede coincidir con el patrón que define. Los cuantificadores de bucle no borran las definiciones de
grupo.
Si un grupo no ha capturado ninguna subcadena, no se define una referencia inversa a ese grupo y no coincide
nunca. Así lo ilustra el patrón de expresión regular \b(\p{Lu}{2})(\d{2})?(\p{Lu}{2})\b que se define de la
siguiente forma:

M O DELO DESC RIP T IO N

\b Comienza la búsqueda de coincidencias en un límite de


palabras.

(\p{Lu}{2}) Coincide con dos letras mayúsculas. Este es el primer grupo


de captura.

(\d{2})? Coincide con una o ninguna aparición de dos dígitos


decimales. Este es el segundo grupo de captura.

(\p{Lu}{2}) Coincide con dos letras mayúsculas. Éste es el tercer grupo de


captura.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

Una cadena de entrada puede coincidir con esta expresión regular aunque no estén presentes los dos dígitos
decimales que define el segundo grupo de captura. En el ejemplo siguiente, se muestra que, aunque la
coincidencia es correcta, hay un grupo de captura vacío entre dos grupos de captura correctos.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\p{Lu}{2})(\d{2})?(\p{Lu}{2})\b";
string[] inputs = { "AA22ZZ", "AABB" };
foreach (string input in inputs)
{
Match match = Regex.Match(input, pattern);
if (match.Success)
{
Console.WriteLine("Match in {0}: {1}", input, match.Value);
if (match.Groups.Count > 1)
{
for (int ctr = 1; ctr <= match.Groups.Count - 1; ctr++)
{
if (match.Groups[ctr].Success)
Console.WriteLine("Group {0}: {1}",
ctr, match.Groups[ctr].Value);
else
Console.WriteLine("Group {0}: <no match>", ctr);
}
}
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match in AA22ZZ: AA22ZZ
// Group 1: AA
// Group 2: 22
// Group 3: ZZ
//
// Match in AABB: AABB
// Group 1: AA
// Group 2: <no match>
// Group 3: BB
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\p{Lu}{2})(\d{2})?(\p{Lu}{2})\b"
Dim inputs() As String = {"AA22ZZ", "AABB"}
For Each input As String In inputs
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine("Match in {0}: {1}", input, match.Value)
If match.Groups.Count > 1 Then
For ctr As Integer = 1 To match.Groups.Count - 1
If match.Groups(ctr).Success Then
Console.WriteLine("Group {0}: {1}", _
ctr, match.Groups(ctr).Value)
Else
Console.WriteLine("Group {0}: <no match>", ctr)
End If
Next
End If
End If
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match in AA22ZZ: AA22ZZ
' Group 1: AA
' Group 2: 22
' Group 3: ZZ
'
' Match in AABB: AABB
' Group 1: AA
' Group 2: <no match>
' Group 3: BB

Vea también
Lenguaje de expresiones regulares: referencia rápida
Construcciones de alternancia en expresiones
regulares
16/09/2020 • 14 minutes to read • Edit Online

Las construcciones de alternancia modifican una expresión regular para habilitar la coincidencia condicional o
“either/or”. .NET admite tres construcciones de alternancia:
Coincidencia de patrones con |
Coincidencia condicional con (?(expresión)sí|no)
Coincidencia condicional basada en un grupo capturado válido

Coincidencia de patrones con |


Puede usar el carácter de barra vertical ( | ) para hacer coincidir un elemento de una serie de patrones, donde el
carácter | separa cada patrón.
Como sucede con la clase de caracteres positivos, el carácter | puede utilizarse para hacer coincidir un elemento
de una serie de caracteres individuales. En el ejemplo siguiente se utiliza una clase de caracteres positivos y un
patrón either/or que coincide con el carácter | para buscar apariciones de las palabras "gray" o "grey" en una
cadena. En este caso, el carácter | produce una expresión regular que es más detallada.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
// Regular expression using character class.
string pattern1 = @"\bgr[ae]y\b";
// Regular expression using either/or.
string pattern2 = @"\bgr(a|e)y\b";

string input = "The gray wolf blended in among the grey rocks.";
foreach (Match match in Regex.Matches(input, pattern1))
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index);
Console.WriteLine();
foreach (Match match in Regex.Matches(input, pattern2))
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index);
}
}
// The example displays the following output:
// 'gray' found at position 4
// 'grey' found at position 35
//
// 'gray' found at position 4
// 'grey' found at position 35
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
' Regular expression using character class.
Dim pattern1 As String = "\bgr[ae]y\b"
' Regular expression using either/or.
Dim pattern2 As String = "\bgr(a|e)y\b"

Dim input As String = "The gray wolf blended in among the grey rocks."
For Each match As Match In Regex.Matches(input, pattern1)
Console.WriteLine("'{0}' found at position {1}", _
match.Value, match.Index)
Next
Console.WriteLine()
For Each match As Match In Regex.Matches(input, pattern2)
Console.WriteLine("'{0}' found at position {1}", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' 'gray' found at position 4
' 'grey' found at position 35
'
' 'gray' found at position 4
' 'grey' found at position 35

La expresión regular que usa el carácter | , \bgr(a|e)y\b , se interpreta como se muestra en la tabla siguiente:

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

gr Coincide con los caracteres "gr".

(a|e) Coincide con una "a" o una "e".

y\b Coincide con una "y" en un límite de palabras.

El carácter | también se puede usar para realizar una coincidencia either/or con varios caracteres o
subexpresiones, que pueden incluir cualquier combinación de literales de carácter y elementos de lenguaje de
expresión regular. (La clase de caracteres no proporciona esta funcionalidad). En el ejemplo siguiente, se usa el
carácter | para extraer un número de la seguridad social (SSN) de EE. UU., de nueve dígitos y en formato
ddd-dd-dddd, o un número de identificación de empleador (EIN), de nueve dígitos y en formato dd-ddddddd.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b";
string input = "01-9999999 020-333333 777-88-9999";
Console.WriteLine("Matches for {0}:", pattern);
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);
}
}
// The example displays the following output:
// Matches for \b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b:
// 01-9999999 at position 0
// 777-88-9999 at position 22

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b"
Dim input As String = "01-9999999 020-333333 777-88-9999"
Console.WriteLine("Matches for {0}:", pattern)
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Matches for \b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b:
' 01-9999999 at position 0
' 777-88-9999 at position 22

La expresión regular \b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b se interpreta como se muestra en la tabla siguiente:

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

(\d{2}-\d{7}|\d{3}-\d{2}-\d{4}) Coincide con cualquiera de las siguientes opciones: dos


dígitos decimales seguidos de un guión seguido de siete
dígitos decimales; o tres dígitos decimales, un guión, dos
dígitos decimales, otro guión y cuatro dígitos decimales.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

Coincidencia condicional con una expresión


Este elemento del lenguaje intenta coincidir con uno de dos patrones en función de si puede coincidir con un
patrón inicial. Su sintaxis es:
(?( expresión ) sí | no )

donde expresión es el patrón inicial de coincidencia, sí es el patrón de coincidencia si se encuentra una


coincidencia para expresión y no es el patrón opcional de coincidencia si no se encuentra ninguna coincidencia
para expresión . El motor de expresiones regulares trata a la expresión como una aserción de ancho cero; es decir,
el motor de expresiones regulares no avanza en el flujo de entrada después de evaluar la expresión. Por tanto,
esta construcción es equivalente a la siguiente:
(?(?= expresión ) sí | no )

donde (?= expresión ) es una construcción de aserción de ancho cero. Para más información, consulte la
sección sobre Construcciones de agrupamiento en expresiones regulares. Como el motor de expresiones
regulares interpreta la expresión como un delimitador (una aserción de ancho cero), la expresión debe ser una
aserción de ancho cero (para más información, consulte Delimitadores en expresiones regulares) o una
subexpresión que también esté incluida en sí. De lo contrario, no se pueden encontrar coincidencias para el
patrón sí .

NOTE
Si expresión es un grupo de captura con nombre o con numeración, la construcción de alternancia se interpreta como una
prueba de captura; para obtener más información, consulte la sección siguiente, Coincidencia condicional basada en un
grupo capturado válido. En otras palabras, el motor de expresiones regulares no intenta coincidir con la subcadena
capturada, sino que, en vez de eso, comprueba la presencia o la ausencia del grupo.

El siguiente ejemplo es una variación del que aparece en la sección Coincidencia de patrones either/or con |. En él
se usa la coincidencia condicional para determinar si los tres primeros caracteres después de un límite de palabra
son dos dígitos seguidos por un guión. Si es así, intenta buscar un número de identificación de empleador (EIN)
de EE. UU. Si no, intenta buscar un número de la seguridad social (SSN) de EE.UU.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(?(\d{2}-)\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b";
string input = "01-9999999 020-333333 777-88-9999";
Console.WriteLine("Matches for {0}:", pattern);
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);
}
}
// The example displays the following output:
// Matches for \b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b:
// 01-9999999 at position 0
// 777-88-9999 at position 22
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(?(\d{2}-)\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b"
Dim input As String = "01-9999999 020-333333 777-88-9999"
Console.WriteLine("Matches for {0}:", pattern)
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Matches for \b(?(\d{2}-)\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b:
' 01-9999999 at position 0
' 777-88-9999 at position 22

El patrón de la expresión regular \b(?(\d{2}-)\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b se interpreta como se muestra en


la tabla siguiente:

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

(?(\d{2}-) Determina si los tres caracteres siguientes están compuestos


de dos dígitos seguidos de un guión.

\d{2}-\d{7} Si el patrón anterior coincide, coincide con dos dígitos


seguidos de un guión seguido de siete dígitos.

\d{3}-\d{2}-\d{4} Si el patrón anterior no coincide, coincide con tres dígitos


decimales, un guión, dos dígitos decimales, otro guión y
cuatro dígitos decimales.

\b Coincide con un límite de palabras.

Coincidencia condicional basada en un grupo capturado válido


Este elemento del lenguaje intenta coincidir con uno de dos patrones en función de si coincidió con un grupo de
captura especificado. Su sintaxis es:
(?( nombre ) sí | no )

o
(?( número ) sí | no )

donde nombre es el nombre y número es el número de un grupo de captura, sí es la expresión que debe coincidir
si nombre o número tienen una coincidencia, y no es la expresión opcional que debe coincidir si no la tienen.
Si nombre no se corresponde con el nombre de un grupo de captura que se usa en el patrón de expresión regular,
la construcción de alternancia se interpreta como una prueba de expresión, tal como se explica en la sección
anterior. Normalmente, esto significa que expresión se evalúa como false . Si número no se corresponde con un
grupo de captura numerado que se usa en el patrón de expresión regular, el motor de expresiones regulares
genera una excepción ArgumentException.
El siguiente ejemplo es una variación del que aparece en la sección Coincidencia de patrones either/or con |. En él
se usa un grupo de captura denominado n2 que consta de dos dígitos seguidos por un guión. La construcción
de alternancia prueba si este grupo de captura ha coincidido en la cadena de entrada. En caso afirmativo, la
construcción de alternancia intenta hacer coincidir los últimos siete dígitos de un número de identificación de
empleador de identificación de empleador (EIN) de EE. UU. En caso negativo, intenta coincidir con un número de
la seguridad social (SSN) de EE.UU.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(?<n2>\d{2}-)?(?(n2)\d{7}|\d{3}-\d{2}-\d{4})\b";
string input = "01-9999999 020-333333 777-88-9999";
Console.WriteLine("Matches for {0}:", pattern);
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);
}
}
// The example displays the following output:
// Matches for \b(?<n2>\d{2}-)?(?(n2)\d{7}|\d{3}-\d{2}-\d{4})\b:
// 01-9999999 at position 0
// 777-88-9999 at position 22

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(?<n2>\d{2}-)?(?(n2)\d{7}|\d{3}-\d{2}-\d{4})\b"
Dim input As String = "01-9999999 020-333333 777-88-9999"
Console.WriteLine("Matches for {0}:", pattern)
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
End Sub
End Module

El patrón de la expresión regular \b(?<n2>\d{2}-)?(?(n2)\d{7}|\d{3}-\d{2}-\d{4})\b se interpreta como se


muestra en la tabla siguiente:

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

(?<n2>\d{2}-)? Coincide con cero o con dos dígitos seguidos por un guión.
Este grupo de captura se denomina n2 .

(?(n2) Prueba si n2 coincidió con la cadena de entrada.

\d{7} Si n2 coincidió, coincide con siete dígitos decimales.

|\d{3}-\d{2}-\d{4} Si n2 no coincidió, coincide con tres dígitos decimales, un


guión, dos dígitos decimales, otro guión y cuatro dígitos
decimales.

\b Coincide con un límite de palabras.

En el ejemplo siguiente se muestra una variación de este ejemplo, pero con un grupo numerado en lugar de un
grupo con nombre. Su patrón de expresión regular es \b(\d{2}-)?(?(1)\d{7}|\d{3}-\d{2}-\d{4})\b .

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\d{2}-)?(?(1)\d{7}|\d{3}-\d{2}-\d{4})\b";
string input = "01-9999999 020-333333 777-88-9999";
Console.WriteLine("Matches for {0}:", pattern);
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);
}
}
// The example display the following output:
// Matches for \b(\d{2}-)?(?(1)\d{7}|\d{3}-\d{2}-\d{4})\b:
// 01-9999999 at position 0
// 777-88-9999 at position 22

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\d{2}-)?(?(1)\d{7}|\d{3}-\d{2}-\d{4})\b"
Dim input As String = "01-9999999 020-333333 777-88-9999"
Console.WriteLine("Matches for {0}:", pattern)
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Matches for \b(\d{2}-)?(?(1)\d{7}|\d{3}-\d{2}-\d{4})\b:
' 01-9999999 at position 0
' 777-88-9999 at position 22

Vea también
Lenguaje de expresiones regulares: referencia rápida
Sustituciones en expresiones regulares
16/09/2020 • 28 minutes to read • Edit Online

Las sustituciones son elementos del lenguaje que se reconocen solo dentro de patrones de reemplazo. Usan un
patrón de expresión regular para definir todo o parte del texto que reemplazará el texto coincidente en la cadena
de entrada. El patrón de reemplazo puede estar compuesto de una o más sustituciones junto con caracteres
literales. Los patrones de reemplazo se proporcionan a las sobrecargas del método Regex.Replace que tiene un
parámetro replacement y al método Match.Result . Los métodos reemplazan el patrón que coincide con el patrón
que define el parámetro replacement .
.NET Framework define los elementos de sustitución que se enumeran en la siguiente tabla.

SUST IT UC IÓ N DESC RIP C IÓ N

$ number Incluye la última subcadena coincidente por el grupo


capturado que identifica number, donde number es un valor
decimal, en la cadena de reemplazo. Para obtener más
información, vea Sustituir un grupo numerado.

${ name } Incluye la última subcadena coincidente por el grupo con


nombre que designa (?< name > ) en la cadena de
reemplazo. Para obtener más información, vea Sustituir un
grupo con nombre.

$$ Incluye un literal "$" único en la cadena de reemplazo. Para


obtener más información, vea Sustituir un símbolo "$".

$& Incluye una copia de la coincidencia completa en la cadena de


reemplazo. Para obtener más información, vea Sustituir toda
la coincidencia.

$` Incluye todo el texto de la cadena de entrada delante de la


coincidencia en la cadena de reemplazo. Para obtener más
información, vea Sustituir el texto delante de la coincidencia.

$' Incluye todo el texto de la cadena de entrada detrás de la


coincidencia en la cadena de reemplazo. Para obtener más
información, vea Sustituir el texto detrás de la coincidencia.

$+ Incluye el último grupo capturado en la cadena de reemplazo.


Para obtener más información, vea Sustituir el último grupo
capturado.

$_ Incluye la cadena de entrada completa en la cadena de


reemplazo. Para obtener más información, vea Sustituir toda
la cadena de entrada.

Elementos de sustitución y patrones de reemplazo


Las sustituciones son las únicas construcciones especiales reconocidas en un patrón de reemplazo. No se admiten
otros elementos de lenguaje de expresiones regulares, incluidos los escapes de caracteres y el punto ( . ), que
coincidan con cualquier carácter. De igual forma, los elementos de lenguaje de sustitución se reconocen
únicamente en patrones de reemplazo y no son válidos en patrones de expresiones regulares.
El único carácter que puede aparecer en un patrón de expresión regular o en una sustitución es el carácter $ ,
aunque tiene un significado diferente en cada contexto. En un patrón de expresión regular, $ es un delimitador
que coincide con el final de la cadena. En un patrón de reemplazo, $ indica el principio de una sustitución.

NOTE
Para obtener una funcionalidad similar a la de un patrón de reemplazo dentro de una expresión regular, use una referencia
inversa. Para obtener más información acerca de las referencias inversas, vea Construcciones de referencia inversa.

Sustituir un grupo numerado


El elemento de lenguaje $ number incluye la última subcadena coincidente por el grupo de captura number en la
cadena de reemplazo, donde number es el índice del grupo de captura. Por ejemplo, el patrón de reemplazo $1
indica que el primer grupo capturado reemplazará la subcadena coincidente. Para más información sobre los
grupos de captura numerados, vea Grouping Constructs.
Todos los dígitos después del símbolo $ se interpretan como que pertenecen al grupo number . Si esto no es lo
que pretende, puede sustituir un grupo con nombre en su lugar. Por ejemplo, puede utilizar la cadena de
reemplazo ${1}1 en lugar de $11 para definir la cadena de reemplazo como el valor del primer grupo capturado
junto con el número "1". Para obtener más información, vea Sustituir un grupo con nombre.
Los grupos de captura que no tienen nombres asignados explícitamente mediante la sintaxis (?< name >) se
enumeran de izquierda a derecha a partir de uno. Los grupos con nombre también se numeran de izquierda a
derecha, comenzando por uno mayor que el índice del último grupo sin nombre. Por ejemplo, en la expresión
regular (\w)(?<digit>\d) , el índice del grupo con nombre digit es 2.
Si number no especifica ningún grupo de captura válido en el patrón de expresión regular, $ number se interpreta
como una secuencia de caracteres literales que se usa para reemplazar cada coincidencia.
En el ejemplo siguiente se usa la sustitución $ number para quitar el símbolo de divisa de un valor decimal. Quita
los símbolos de divisa encontrados al principio o al final de un valor monetario y reconoce los dos separadores
decimales más comunes ("." y ",").

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\p{Sc}*(\s?\d+[.,]?\d*)\p{Sc}*";
string replacement = "$1";
string input = "$16.32 12.19 £16.29 €18.29 €18,29";
string result = Regex.Replace(input, pattern, replacement);
Console.WriteLine(result);
}
}
// The example displays the following output:
// 16.32 12.19 16.29 18.29 18,29
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\p{Sc}*(\s?\d+[.,]?\d*)\p{Sc}*"
Dim replacement As String = "$1"
Dim input As String = "$16.32 12.19 £16.29 €18.29 €18,29"
Dim result As String = Regex.Replace(input, pattern, replacement)
Console.WriteLine(result)
End Sub
End Module
' The example displays the following output:
' 16.32 12.19 16.29 18.29 18,29

El patrón de expresión regular \p{Sc}*(\s?\d+[.,]?\d*)\p{Sc}* se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\p{Sc}* Busca una coincidencia con cero o más caracteres de símbolo


de divisa.

\s? Busca una coincidencia con cero o un carácter de espacio en


blanco.

\d+ Buscar coincidencias con uno o más dígitos decimales.

[.,]? Busca una coincidencia con cero o un punto o una coma.

\d* Busca cero o más dígitos decimales.

(\s?\d+[.,]?\d*) Busca un espacio en blanco seguido de uno o más dígitos


decimales, seguido de cero o un punto o una coma, seguido
de cero o más dígitos decimales. Este es el primer grupo de
captura. Dado que el patrón de reemplazo es $1 , la llamada
al método Regex.Replace reemplaza la subcadena coincidente
completa por este grupo capturado.

Sustituir un grupo con nombre


El elemento de lenguaje ${ name } sustituye a la última subcadena coincidente por el grupo de captura name ,
donde name es el nombre del grupo de captura definido por el elemento de lenguaje (?< name >) . Para más
información sobre los grupos de captura con nombre, vea Grouping Constructs.
Si name no especifica ningún grupo de captura con nombre válido en el patrón de expresión regular pero consta
de dígitos, ${ name } se interpreta como un grupo numerado.
Si name no especifica ningún grupo de captura con nombre o grupo de captura numerado válido en el patrón de
expresión regular, ${ name } se interpreta como una secuencia de caracteres literales que se utiliza para
reemplazar cada coincidencia.
En el ejemplo siguiente, se usa la sustitución ${ name } para quitar el símbolo de divisa de un valor decimal.
Quita los símbolos de divisa encontrados al principio o al final de un valor monetario y reconoce los dos
separadores decimales más comunes ("." y ",").
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\p{Sc}*(?<amount>\s?\d+[.,]?\d*)\p{Sc}*";
string replacement = "${amount}";
string input = "$16.32 12.19 £16.29 €18.29 €18,29";
string result = Regex.Replace(input, pattern, replacement);
Console.WriteLine(result);
}
}
// The example displays the following output:
// 16.32 12.19 16.29 18.29 18,29

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\p{Sc}*(?<amount>\s?\d+[.,]?\d*)\p{Sc}*"
Dim replacement As String = "${amount}"
Dim input As String = "$16.32 12.19 £16.29 €18.29 €18,29"
Dim result As String = Regex.Replace(input, pattern, replacement)
Console.WriteLine(result)
End Sub
End Module
' The example displays the following output:
' 16.32 12.19 16.29 18.29 18,29

El patrón de expresión regular \p{Sc}*(?<amount>\s?\d[.,]?\d*)\p{Sc}* se define como se muestra en la tabla


siguiente.

M O DELO DESC RIP C IÓ N

\p{Sc}* Busca una coincidencia con cero o más caracteres de símbolo


de divisa.

\s? Busca una coincidencia con cero o un carácter de espacio en


blanco.

\d+ Buscar coincidencias con uno o más dígitos decimales.

[.,]? Busca una coincidencia con cero o un punto o una coma.

\d* Busca cero o más dígitos decimales.

(?<amount>\s?\d[.,]?\d*) Busca un espacio en blanco, seguido de uno o más dígitos


decimales, seguido de cero o un punto o una coma, seguido
de cero o más dígitos decimales. Este es el grupo de captura
denominado amount . Dado que el patrón de reemplazo es
${amount} , la llamada al método Regex.Replace reemplaza la
subcadena coincidente completa por este grupo capturado.

Sustituir un carácter "$"


La sustitución $$ inserta un carácter "$" literal en la cadena reemplazada.
En el ejemplo siguiente, se usa el objeto NumberFormatInfo para determinar el símbolo de divisa de la referencia
cultural actual y su posición en una cadena de divisa. A continuación, compila dinámicamente un patrón de
expresión regular y un patrón de reemplazo. Si el ejemplo se ejecuta en un equipo cuya referencia cultural actual
es en-US, genera el patrón de expresión regular \b(\d+)(\.(\d+))? y el patrón de reemplazo $$ $1$2 . El patrón
de reemplazo sustituye el texto coincidente por un símbolo de divisa y un espacio seguido del primer y del
segundo grupo capturado.

using System;
using System.Globalization;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
// Define array of decimal values.
string[] values= { "16.35", "19.72", "1234", "0.99"};
// Determine whether currency precedes (True) or follows (False) number.
bool precedes = NumberFormatInfo.CurrentInfo.CurrencyPositivePattern % 2 == 0;
// Get decimal separator.
string cSeparator = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator;
// Get currency symbol.
string symbol = NumberFormatInfo.CurrentInfo.CurrencySymbol;
// If symbol is a "$", add an extra "$".
if (symbol == "$") symbol = "$$";

// Define regular expression pattern and replacement string.


string pattern = @"\b(\d+)(" + cSeparator + @"(\d+))?";
string replacement = "$1$2";
replacement = precedes ? symbol + " " + replacement : replacement + " " + symbol;
foreach (string value in values)
Console.WriteLine("{0} --> {1}", value, Regex.Replace(value, pattern, replacement));
}
}
// The example displays the following output:
// 16.35 --> $ 16.35
// 19.72 --> $ 19.72
// 1234 --> $ 1234
// 0.99 --> $ 0.99
Imports System.Globalization
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
' Define array of decimal values.
Dim values() As String = {"16.35", "19.72", "1234", "0.99"}
' Determine whether currency precedes (True) or follows (False) number.
Dim precedes As Boolean = (NumberFormatInfo.CurrentInfo.CurrencyPositivePattern Mod 2 = 0)
' Get decimal separator.
Dim cSeparator As String = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator
' Get currency symbol.
Dim symbol As String = NumberFormatInfo.CurrentInfo.CurrencySymbol
' If symbol is a "$", add an extra "$".
If symbol = "$" Then symbol = "$$"

' Define regular expression pattern and replacement string.


Dim pattern As String = "\b(\d+)(" + cSeparator + "(\d+))?"
Dim replacement As String = "$1$2"
replacement = If(precedes, symbol + " " + replacement, replacement + " " + symbol)
For Each value In values
Console.WriteLine("{0} --> {1}", value, Regex.Replace(value, pattern, replacement))
Next
End Sub
End Module
' The example displays the following output:
' 16.35 --> $ 16.35
' 19.72 --> $ 19.72
' 1234 --> $ 1234
' 0.99 --> $ 0.99

El patrón de expresión regular \b(\d+)(\.(\d+))? se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Comenzar la búsqueda de coincidencias al principio de un


límite de palabras.

(\d+) Buscar coincidencias con uno o más dígitos decimales. Este es


el primer grupo de captura.

\. Buscar coincidencias con un punto (el separador decimal).

(\d+) Buscar coincidencias con uno o más dígitos decimales. Éste es


el tercer grupo de captura.

(\.(\d+))? Buscar una coincidencia con cero o una aparición de un punto


seguido de uno o más dígitos decimales. Este es el segundo
grupo de captura.

Sustituir toda la coincidencia


La sustitución $& incluye toda la coincidencia en la cadena de reemplazo. A menudo, se usa para agregar una
subcadena al principio o final de la cadena coincidente. Por ejemplo, el patrón de reemplazo ($&) agrega un
paréntesis al principio y al final de cada coincidencia. Si no hay ninguna coincidencia, la sustitución $& no tiene
ningún efecto.
En el ejemplo siguiente, se usa la sustitución $& para agregar comillas al principio y al final de los títulos de los
libros almacenados en una matriz de cadena.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"^(\w+\s?)+$";
string[] titles = { "A Tale of Two Cities",
"The Hound of the Baskervilles",
"The Protestant Ethic and the Spirit of Capitalism",
"The Origin of Species" };
string replacement = "\"$&\"";
foreach (string title in titles)
Console.WriteLine(Regex.Replace(title, pattern, replacement));
}
}
// The example displays the following output:
// "A Tale of Two Cities"
// "The Hound of the Baskervilles"
// "The Protestant Ethic and the Spirit of Capitalism"
// "The Origin of Species"

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^(\w+\s?)+$"
Dim titles() As String = {"A Tale of Two Cities", _
"The Hound of the Baskervilles", _
"The Protestant Ethic and the Spirit of Capitalism", _
"The Origin of Species"}
Dim replacement As String = """$&"""
For Each title As String In titles
Console.WriteLine(Regex.Replace(title, pattern, replacement))
Next
End Sub
End Module
' The example displays the following output:
' "A Tale of Two Cities"
' "The Hound of the Baskervilles"
' "The Protestant Ethic and the Spirit of Capitalism"
' "The Origin of Species"

El patrón de expresión regular ^(\w+\s?)+$ se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

^ Comenzar la búsqueda de coincidencias al principio de la


cadena de entrada.

(\w+\s?)+ Buscar coincidencias con el patrón de uno o varios caracteres


de palabra seguidos de cero o un carácter de espacio en
blanco una o varias veces.

$ Coincide con el final de la cadena de entrada.

El patrón de reemplazo "$&" agrega una comilla literal al principio y al final de cada coincidencia.

Sustituir el texto delante de la coincidencia


La sustitución $` reemplaza la cadena coincidente por la cadena de entrada completa delante de la coincidencia.
Es decir, duplica la cadena de entrada hasta la coincidencia quitando el texto coincidente. Cualquier texto que siga
al texto coincidente no cambia en la cadena de resultado. Si hay varias coincidencias en una cadena de entrada, el
texto de reemplazo se deriva de la cadena de entrada original, en lugar de la cadena en la que coincidencias
anteriores han reemplazado el texto. (En el ejemplo se ofrece una ilustración.) Si no hay ninguna coincidencia, la
sustitución $` no tiene ningún efecto.
En el ejemplo siguiente, se usa el patrón de expresión regular \d+ para que coincida con una secuencia de uno o
más dígitos decimales en la cadena de entrada. La cadena de reemplazo $` reemplaza estos dígitos por el texto
que antecede a la coincidencia.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "aa1bb2cc3dd4ee5";
string pattern = @"\d+";
string substitution = "$`";
Console.WriteLine("Matches:");
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);

Console.WriteLine("Input string: {0}", input);


Console.WriteLine("Output string: " +
Regex.Replace(input, pattern, substitution));
}
}
// The example displays the following output:
// Matches:
// 1 at position 2
// 2 at position 5
// 3 at position 8
// 4 at position 11
// 5 at position 14
// Input string: aa1bb2cc3dd4ee5
// Output string: aaaabbaa1bbccaa1bb2ccddaa1bb2cc3ddeeaa1bb2cc3dd4ee
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "aa1bb2cc3dd4ee5"
Dim pattern As String = "\d+"
Dim substitution As String = "$`"
Console.WriteLine("Matches:")
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
Console.WriteLine("Input string: {0}", input)
Console.WriteLine("Output string: " + _
Regex.Replace(input, pattern, substitution))
End Sub
End Module
' The example displays the following output:
' Matches:
' 1 at position 2
' 2 at position 5
' 3 at position 8
' 4 at position 11
' 5 at position 14
' Input string: aa1bb2cc3dd4ee5
' Output string: aaaabbaa1bbccaa1bb2ccddaa1bb2cc3ddeeaa1bb2cc3dd4ee

En este ejemplo, la cadena de entrada "aa1bb2cc3dd4ee5" contiene cinco coincidencias. En la siguiente tabla se
muestra cómo la sustitución $` hace que el motor de expresiones regulares reemplace cada coincidencia en la
cadena de entrada. El texto insertado se muestra en negrita en la columna de resultados.

C A DEN A A N T ES DE L A
C O IN C IDIR C O N P O SIC IÓ N C O IN C IDEN C IA C A DEN A DE RESULTA DO

1 2 aa aaaa bb2cc3dd4ee5

2 5 aa1bb aaaabbaa1bb cc3dd4ee5

3 8 aa1bb2cc aaaabbaa1bbccaa1bb2ccdd
4ee5

4 11 aa1bb2cc3dd aaaabbaa1bbccaa1bb2ccdda
a1bb2cc3dd ee5

5 14 aa1bb2cc3dd4ee aaaabbaa1bbccaa1bb2ccdda
a1bb2cc3ddeeaa1bb2cc3d
d4ee

Sustituir el texto detrás de la coincidencia


La sustitución $' reemplaza la cadena coincidente por la cadena de entrada completa después de la coincidencia.
Es decir, duplica la cadena de entrada después de la coincidencia quitando el texto coincidente. Cualquier texto que
anteceda al texto coincidente no cambia en la cadena de resultado. Si no hay ninguna coincidencia, la sustitución
$' no tiene ningún efecto.

En el ejemplo siguiente, se usa el patrón de expresión regular \d+ para que coincida con una secuencia de uno o
más dígitos decimales en la cadena de entrada. La cadena de reemplazo $' reemplaza estos dígitos por el texto
que sigue a la coincidencia.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "aa1bb2cc3dd4ee5";
string pattern = @"\d+";
string substitution = "$'";
Console.WriteLine("Matches:");
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);
Console.WriteLine("Input string: {0}", input);
Console.WriteLine("Output string: " +
Regex.Replace(input, pattern, substitution));
}
}
// The example displays the following output:
// Matches:
// 1 at position 2
// 2 at position 5
// 3 at position 8
// 4 at position 11
// 5 at position 14
// Input string: aa1bb2cc3dd4ee5
// Output string: aabb2cc3dd4ee5bbcc3dd4ee5ccdd4ee5ddee5ee

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "aa1bb2cc3dd4ee5"
Dim pattern As String = "\d+"
Dim substitution As String = "$'"
Console.WriteLine("Matches:")
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
Console.WriteLine("Input string: {0}", input)
Console.WriteLine("Output string: " + _
Regex.Replace(input, pattern, substitution))
End Sub
End Module
' The example displays the following output:
' Matches:
' 1 at position 2
' 2 at position 5
' 3 at position 8
' 4 at position 11
' 5 at position 14
' Input string: aa1bb2cc3dd4ee5
' Output string: aabb2cc3dd4ee5bbcc3dd4ee5ccdd4ee5ddee5ee

En este ejemplo, la cadena de entrada "aa1bb2cc3dd4ee5" contiene cinco coincidencias. En la siguiente tabla se
muestra cómo la sustitución $' hace que el motor de expresiones regulares reemplace cada coincidencia en la
cadena de entrada. El texto insertado se muestra en negrita en la columna de resultados.

C A DEN A DESP UÉS DE L A


C O IN C IDIR C O N P O SIC IÓ N C O IN C IDEN C IA C A DEN A DE RESULTA DO
C A DEN A DESP UÉS DE L A
C O IN C IDIR C O N P O SIC IÓ N C O IN C IDEN C IA C A DEN A DE RESULTA DO

1 2 bb2cc3dd4ee5 aabb2cc3dd4ee5 bb2cc3dd


4ee5

2 5 cc3dd4ee5 aabb2cc3dd4ee5bbcc3dd4
ee5 cc3dd4ee5

3 8 dd4ee5 aabb2cc3dd4ee5bbcc3dd4e
e5ccdd4ee5 dd4ee5

4 11 ee5 aabb2cc3dd4ee5bbcc3dd4e
e5ccdd4ee5ddee5 ee5

5 14 String.Empty aabb2cc3dd4ee5bbcc3dd4e
e5ccdd4ee5ddee5ee

Sustituir el último grupo capturado


La sustitución $+ reemplaza la cadena coincidente por el último grupo capturado. Si no hay ningún grupo
capturado o si el valor del último grupo capturado es String.Empty, la sustitución $+ no tiene ningún efecto.
En el ejemplo siguiente se identifican las palabras duplicadas en una cadena y se usa la sustitución $+ para
reemplazarlas por una aparición única de la palabra. La opción RegexOptions.IgnoreCase se usa para garantizar
que palabras que difieren en el uso de mayúsculas y minúsculas, pero que de lo contrario son idénticas, se
consideren duplicadas.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\w+)\s\1\b";
string substitution = "$+";
string input = "The the dog jumped over the fence fence.";
Console.WriteLine(Regex.Replace(input, pattern, substitution,
RegexOptions.IgnoreCase));
}
}
// The example displays the following output:
// The dog jumped over the fence.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\w+)\s\1\b"
Dim substitution As String = "$+"
Dim input As String = "The the dog jumped over the fence fence."
Console.WriteLine(Regex.Replace(input, pattern, substitution, _
RegexOptions.IgnoreCase))
End Sub
End Module
' The example displays the following output:
' The dog jumped over the fence.
El patrón de expresión regular \b(\w+)\s\1\b se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

(\w+) Buscar coincidencias con uno o más caracteres alfabéticos.


Este es el primer grupo de captura.

\s Coincide con un carácter de espacio en blanco.

\1 Buscar una coincidencia con el primer grupo capturado.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

Sustituir toda la cadena de entrada


La sustitución $_ reemplaza la cadena coincidente por la cadena de entrada completa. Es decir, quita el texto
coincidente y lo reemplaza por la cadena completa, incluyendo el texto coincidente.
En el ejemplo siguiente se buscan coincidencias para uno o más dígitos decimales en la cadena de entrada. Se usa
la sustitución $_ para reemplazarlos por la cadena de entrada completa.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "ABC123DEF456";
string pattern = @"\d+";
string substitution = "$_";
Console.WriteLine("Original string: {0}", input);
Console.WriteLine("String with substitution: {0}",
Regex.Replace(input, pattern, substitution));
}
}
// The example displays the following output:
// Original string: ABC123DEF456
// String with substitution: ABCABC123DEF456DEFABC123DEF456

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "ABC123DEF456"
Dim pattern As String = "\d+"
Dim substitution As String = "$_"
Console.WriteLine("Original string: {0}", input)
Console.WriteLine("String with substitution: {0}", _
Regex.Replace(input, pattern, substitution))
End Sub
End Module
' The example displays the following output:
' Original string: ABC123DEF456
' String with substitution: ABCABC123DEF456DEFABC123DEF456
En este ejemplo, la cadena de entrada "ABC123DEF456" contiene dos coincidencias. En la siguiente tabla se muestra
cómo la sustitución $_ hace que el motor de expresiones regulares reemplace cada coincidencia en la cadena de
entrada. El texto insertado se muestra en negrita en la columna de resultados.

C O IN C IDIR C O N P O SIC IÓ N C O IN C IDIR C O N C A DEN A DE RESULTA DO

1 3 123 ABCABC123DEF456 DEF45


6

2 5 456 ABCABC123DEF456DEFAB
C123DEF456

Vea también
Lenguaje de expresiones regulares: referencia rápida
Opciones de expresiones regulares
16/09/2020 • 72 minutes to read • Edit Online

De forma predeterminada, al comparar una cadena de entrada con los caracteres literales de un patrón de
expresión regular, se distinguen mayúsculas de minúsculas, los espacios en blanco del patrón de expresión
regular se interpretan como caracteres de espacio en blanco literales, y los grupos de captura de la
expresión regular se denominan tanto implícita como explícitamente. Estos y otros aspectos del
comportamiento predeterminado de la expresión regular se pueden modificar mediante la especificación
de las opciones de la expresión regular. Estas opciones —que aparecen enumeradas en la tabla siguiente
— se pueden insertar como parte del patrón de expresión regular, o se pueden suministrar a un
constructor de clases System.Text.RegularExpressions.Regex o a un método estático de coincidencia de
patrones como valor de enumeración de System.Text.RegularExpressions.RegexOptions.

M IEM B RO REGEXO P T IO N S C A RÁ C T ER IN SERTA DO EF EC TO

None No disponible Usar el comportamiento


predeterminado. Para obtener más
información, consulte Opciones
predeterminadas.

IgnoreCase i Usa la coincidencia sin distinción


entre mayúsculas y minúsculas. Para
obtener más información, consulte la
sección Coincidencia sin distinción
entre mayúsculas y minúsculas.

Multiline m Usar el modo multilínea, donde ^ y


$ coinciden con el principio y el
final de cada línea (en lugar del
principio y el final de la cadena de
entrada). Para obtener más
información, consulte la sección
Modo multilínea.

Singleline s Usar el modo de una sola línea,


donde el punto (.) coincide con
todos los caracteres (en lugar de
todos los caracteres excepto \n ).
Para obtener más información,
consulte la sección Modo de una
sola línea.

ExplicitCapture n No se capturan grupos sin nombre.


Las únicas capturas válidas son
grupos con nombre explícito o
numerados con el formato (?<
nombre > subexpresión ) . Para
obtener más información, consulte la
sección Solo capturas explícitas.
M IEM B RO REGEXO P T IO N S C A RÁ C T ER IN SERTA DO EF EC TO

Compiled No disponible Compilar la expresión regular en un


ensamblado. Para obtener más
información, consulte la sección
Expresiones regulares compiladas.

IgnorePatternWhitespace x Excluir del patrón el espacio en


blanco sin escape y habilitar los
comentarios tras un signo de
número ( # ). Para obtener más
información, consulte la sección
Ignorar el espacio en blanco.

RightToLeft No disponible Cambiar la dirección de búsqueda.


La búsqueda se mueve de derecha a
izquierda en vez de de izquierda a
derecha. Para obtener más
información, consulte la sección
Modo de derecha a izquierda.

ECMAScript No disponible Habilitar un comportamiento


conforme a ECMAScript para la
expresión. Para obtener más
información, consulte la sección
Comportamiento de búsqueda de
coincidencias de ECMAScript.

CultureInvariant No disponible Ignorar las diferencias culturales de


idioma. Para obtener más
información, consulte la sección
Comparación con la referencia
cultural de todos los idiomas.

Especificación de opciones
Las opciones de las expresiones regulares se pueden especificar de tres modos:
En el parámetro options de un constructor de clases System.Text.RegularExpressions.Regex o
método de coincidencia de patrones estático ( Shared en Visual Basic), como Regex(String,
RegexOptions) o Regex.Match(String, String, RegexOptions). El parámetro options es una
combinación OR bit a bit de valores enumerados de System.Text.RegularExpressions.RegexOptions.
Cuando se proporcionan opciones a una instancia Regex mediante el parámetro options de un
constructor de clase, las opciones se asignan a la propiedad
System.Text.RegularExpressions.RegexOptions. Sin embargo, la propiedad
System.Text.RegularExpressions.RegexOptions no refleja las opciones insertadas en el mismo
patrón de expresión regular.
Esto se muestra en el ejemplo siguiente. Usa el parámetro options del método Regex.Match(String,
String, RegexOptions) para habilitar la coincidencia sin distinción entre mayúsculas y minúsculas,
así como para ignorar el espacio en blanco del patrón a la hora de identificar palabras que
empiecen por la letra “d”.
string pattern = @"d \w+ \s";
string input = "Dogs are decidedly good pets.";
RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace;

foreach (Match match in Regex.Matches(input, pattern, options))


Console.WriteLine("'{0}// found at index {1}.", match.Value, match.Index);
// The example displays the following output:
// 'Dogs // found at index 0.
// 'decidedly // found at index 9.

Dim pattern As String = "d \w+ \s"


Dim input As String = "Dogs are decidedly good pets."
Dim options As RegexOptions = RegexOptions.IgnoreCase Or RegexOptions.IgnorePatternWhitespace

For Each match As Match In Regex.Matches(input, pattern, options)


Console.WriteLine("'{0}' found at index {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Dogs ' found at index 0.
' 'decidedly ' found at index 9.

Con la aplicación de las opciones insertadas en un patrón de expresión regular con la sintaxis
(?imnsx-imnsx) . La opción se aplica al patrón a partir del punto en que la opción se define hasta el
final del patrón o hasta el punto en que otra opción insertada deja a la opción sin definir. Tenga en
cuenta que la propiedad System.Text.RegularExpressions.RegexOptions de una instancia Regex no
refleja estas opciones insertadas. Para más información, consulte el tema Construcciones
misceláneas.
Esto se muestra en el ejemplo siguiente. Usa opciones insertadas para habilitar la coincidencia sin
distinción entre mayúsculas y minúsculas, así como para ignorar el espacio en blanco del patrón a
la hora de identificar palabras que empiecen por la letra “d”.

string pattern = @"(?ix) d \w+ \s";


string input = "Dogs are decidedly good pets.";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine("'{0}// found at index {1}.", match.Value, match.Index);
// The example displays the following output:
// 'Dogs // found at index 0.
// 'decidedly // found at index 9.

Dim pattern As String = "\b(?ix) d \w+ \s"


Dim input As String = "Dogs are decidedly good pets."

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("'{0}' found at index {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Dogs ' found at index 0.
' 'decidedly ' found at index 9.

Con la aplicación de opciones insertadas en una construcción de agrupamiento determinada en un


patrón de expresión regular con la sintaxis (?imnsx-imnsx: subexpresión ) . El conjunto de
opciones se activa si no va precedido por un signo y se desactiva si va precedido por un signo
menos. ( ? es una parte fija de la sintaxis del constructor de lenguaje que se necesita si las
opciones están habilitadas o deshabilitadas). La opción solo se aplica a ese grupo. Para más
información, consulte Construcciones de agrupamiento.
Esto se muestra en el ejemplo siguiente. Usa opciones insertadas en una construcción de
agrupamiento para habilitar la coincidencia sin distinción entre mayúsculas y minúsculas, así como
para ignorar el espacio en blanco del patrón a la hora de identificar palabras que empiecen por la
letra “d”.

string pattern = @"\b(?ix: d \w+)\s";


string input = "Dogs are decidedly good pets.";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine("'{0}// found at index {1}.", match.Value, match.Index);
// The example displays the following output:
// 'Dogs // found at index 0.
// 'decidedly // found at index 9.

Dim pattern As String = "\b(?ix: d \w+)\s"


Dim input As String = "Dogs are decidedly good pets."

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("'{0}' found at index {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Dogs ' found at index 0.
' 'decidedly ' found at index 9.

Si las opciones están insertadas, un signo menos ( - ) antes de una opción o conjunto de opciones
desactiva dichas opciones. Por ejemplo, la construcción insertada (?ix-ms) activa las opciones
RegexOptions.IgnoreCase y RegexOptions.IgnorePatternWhitespace y desactiva las opciones
RegexOptions.Multiline y RegexOptions.Singleline. Todas las opciones de expresión regular están
desactivadas de forma predeterminada.

NOTE
Si las opciones de expresión regular especificadas en el parámetro options de un constructor o llamada a método
están en conflicto con las opciones insertadas en un patrón de expresión regular, se usan las opciones insertadas.

Las siguientes opciones de expresión regular se pueden establecer con el parámetro y mediante inserción:
RegexOptions.IgnoreCase
RegexOptions.Multiline
RegexOptions.Singleline
RegexOptions.ExplicitCapture
RegexOptions.IgnorePatternWhitespace
Las siguientes cinco opciones de expresión regular se pueden establecer con el parámetro options , pero
no mediante inserción:
RegexOptions.None
RegexOptions.Compiled
RegexOptions.RightToLeft
RegexOptions.CultureInvariant
RegexOptions.ECMAScript
Determinación de opciones
Se pueden determinar las opciones que se proporcionaron a un objeto Regex al crearse su instancia; para
ello, recupere el valor de la propiedad Regex.Options de solo lectura. Esta propiedad resulta especialmente
útil para determinar las opciones definidas para una expresión regular compilada que se ha creado con el
método Regex.CompileToAssembly.
Para probar la presencia de cualquier opción, excepto RegexOptions.None, realice una operación AND con
el valor de la propiedad Regex.Options y el valor de RegexOptions en el que esté interesado. Después,
pruebe si el resultado es igual a ese valor RegexOptions. En el ejemplo siguiente se prueba si se ha
establecido la opción RegexOptions.IgnoreCase.

if ((rgx.Options & RegexOptions.IgnoreCase) == RegexOptions.IgnoreCase)


Console.WriteLine("Case-insensitive pattern comparison.");
else
Console.WriteLine("Case-sensitive pattern comparison.");

If (rgx.Options And RegexOptions.IgnoreCase) = RegexOptions.IgnoreCase Then


Console.WriteLine("Case-insensitive pattern comparison.")
Else
Console.WriteLine("Case-sensitive pattern comparison.")
End If

Para probar RegexOptions.None, determine si el valor de la propiedad Regex.Options es igual a


RegexOptions.None, tal como se muestra en el ejemplo siguiente.

if (rgx.Options == RegexOptions.None)
Console.WriteLine("No options have been set.");

If rgx.Options = RegexOptions.None Then


Console.WriteLine("No options have been set.")
End If

En las secciones siguientes se enumeran las opciones admitidas en una expresión regular de .NET.

Opciones predeterminadas
La opción RegexOptions.None indica que no se ha especificado ninguna opción y que el motor de
expresiones regulares sigue su comportamiento predeterminado. Entre estas estructuras se incluyen las
siguientes:
El patrón se interpreta como un canónico en vez de como una expresión regular ECMAScript.
El patrón de expresión regular se compara con la cadena de entrada de izquierda a derecha.
Las comparaciones distinguen entre mayúsculas y minúsculas.
Los elementos de lenguaje ^ y $ coinciden con el principio y el final de la cadena de entrada.
El elemento de lenguaje . coincide con todos los caracteres excepto \n .
Un espacio en blanco en un patrón de expresión regular se interpreta como un carácter de espacio
literal.
Las convenciones de la referencia cultural actual se usan al comparar el patrón con la cadena de
entrada.
Los grupos de captura del patrón de expresión regular son implícitos y explícitos.

NOTE
La opción RegexOptions.None no tiene ningún equivalente de inserción. Cuando las opciones de expresión regular
se aplican mediante inserción, el comportamiento predeterminado se restaura opción por opción, mediante la
desactivación de una opción concreta. Por ejemplo, (?i) activa la comparación sin distinción entre mayúsculas y
minúsculas, y (?-i) restaura la comparación con distinción entre mayúsculas y minúsculas.

Debido a que la opción RegexOptions.None representa el comportamiento predeterminado del motor de


expresiones regulares, casi nunca se especifica de forma explícita en una llamada a método. En su lugar, se
llama a un constructor o método estático de coincidencia de patrones sin un parámetro options .

Coincidencia sin distinción entre mayúsculas y minúsculas


La opción IgnoreCase, o la opción insertada i , proporciona coincidencia sin distinción entre mayúsculas
y minúsculas. De forma predeterminada, se usan las convenciones sobre el uso de mayúsculas de la
referencia cultural actual.
En el ejemplo siguiente se define un patrón de expresión regular, \bthe\w*\b , por el que coinciden todas
las palabras que empiezan por “the”. Debido a que la primera llamada al método Match usa la
comparación predeterminada con distinción entre mayúsculas y minúsculas, el resultado indica que no
hay coincidencia con la cadena “The” del inicio de la oración. Hay coincidencia cuando se llama al método
Match con las opciones establecidas en IgnoreCase.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\bthe\w*\b";
string input = "The man then told them about that event.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index);

Console.WriteLine();
foreach (Match match in Regex.Matches(input, pattern,
RegexOptions.IgnoreCase))
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index);
}
}
// The example displays the following output:
// Found then at index 8.
// Found them at index 18.
//
// Found The at index 0.
// Found then at index 8.
// Found them at index 18.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\bthe\w*\b"
Dim input As String = "The man then told them about that event."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index)
Next
Console.WriteLine()
For Each match As Match In Regex.Matches(input, pattern, _
RegexOptions.IgnoreCase)
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Found then at index 8.
' Found them at index 18.
'
' Found The at index 0.
' Found then at index 8.
' Found them at index 18.

En el ejemplo siguiente se modifica el patrón de expresión regular del ejemplo anterior a fin de usar las
opciones insertadas en vez del parámetro options para realizar la comparación sin distinción entre
mayúsculas y minúsculas. El primer parámetro define la opción sin distinción entre mayúsculas y
minúsculas en una construcción de agrupamiento que se aplica solo a la letra “t” de la cadena “the”. Como
la construcción de opciones ocurre al principio del patrón, el segundo patrón aplica la opción sin
distinción entre mayúsculas y minúsculas a toda la expresión regular.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(?i:t)he\w*\b";
string input = "The man then told them about that event.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index);

Console.WriteLine();
pattern = @"(?i)\bthe\w*\b";
foreach (Match match in Regex.Matches(input, pattern,
RegexOptions.IgnoreCase))
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index);
}
}
// The example displays the following output:
// Found The at index 0.
// Found then at index 8.
// Found them at index 18.
//
// Found The at index 0.
// Found then at index 8.
// Found them at index 18.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(?i:t)he\w*\b"
Dim input As String = "The man then told them about that event."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index)
Next
Console.WriteLine()
pattern = "(?i)\bthe\w*\b"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Found The at index 0.
' Found then at index 8.
' Found them at index 18.
'
' Found The at index 0.
' Found then at index 8.
' Found them at index 18.

Modo multilínea
La opción RegexOptions.Multiline, o la opción insertada m , habilita al motor de expresiones regulares
para que controle una cadena de entrada que consta de varias líneas. Cambia la interpretación de los
elementos de lenguaje ^ y $ de modo que coincidan con el inicio y el final de una línea, en vez de con el
inicio y el final de la cadena de entrada.
De forma predeterminada, $ coincide solo con el final de la cadena de entrada. Si se especifica la opción
RegexOptions.Multiline, coincide con el carácter de nueva línea ( \n ) o con el final de la cadena de
entrada. Sin embargo, no coincide con la combinación de caracteres de retorno de carro y salto de línea.
Para que coincida correctamente, use la subexpresión \r?$ en vez de simplemente $ .
En el ejemplo siguiente se extraen los nombres y las puntuaciones de los jugadores de bolos y se agregan
a una colección SortedList<TKey,TValue> que los ordena en sentido descendente. Se realizan dos
llamadas al método Matches. En la primera llamada a método, la expresión regular es ^(\w+)\s(\d+)$ y
no se establece ninguna opción. Como muestra el resultado, no se encuentran coincidencias, ya que el
motor de expresiones regulares no puede hacer coincidir el patrón de entrada junto con el principio y el
final de la cadena de entrada. En la segunda llamada a método, la expresión regular se cambia a
^(\w+)\s(\d+)\r?$ y las opciones se establecen en RegexOptions.Multiline. Como muestra el resultado,
los nombres y las puntuaciones coinciden correctamente, y las puntuaciones aparecen en orden
descendente.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
SortedList<int, string> scores = new SortedList<int, string>(new DescendingComparer<int>());

string input = "Joe 164\n" +


"Sam 208\n" +
"Allison 211\n" +
"Gwen 171\n";
string pattern = @"^(\w+)\s(\d+)$";
bool matched = false;

Console.WriteLine("Without Multiline option:");


foreach (Match match in Regex.Matches(input, pattern))
{
scores.Add(Int32.Parse(match.Groups[2].Value), (string) match.Groups[1].Value);
matched = true;
}
if (! matched)
Console.WriteLine(" No matches.");
Console.WriteLine();

// Redefine pattern to handle multiple lines.


pattern = @"^(\w+)\s(\d+)\r*$";
Console.WriteLine("With multiline option:");
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.Multiline))
scores.Add(Int32.Parse(match.Groups[2].Value), (string) match.Groups[1].Value);

// List scores in descending order.


foreach (KeyValuePair<int, string> score in scores)
Console.WriteLine("{0}: {1}", score.Value, score.Key);
}
}

public class DescendingComparer<T> : IComparer<T>


{
public int Compare(T x, T y)
{
return Comparer<T>.Default.Compare(x, y) * -1;
}
}
// The example displays the following output:
// Without Multiline option:
// No matches.
//
// With multiline option:
// Allison: 211
// Sam: 208
// Gwen: 171
// Joe: 164
Imports System.Collections.Generic
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim scores As New SortedList(Of Integer, String)(New DescendingComparer(Of Integer)())

Dim input As String = "Joe 164" + vbCrLf + _


"Sam 208" + vbCrLf + _
"Allison 211" + vbCrLf + _
"Gwen 171" + vbCrLf
Dim pattern As String = "^(\w+)\s(\d+)$"
Dim matched As Boolean = False

Console.WriteLine("Without Multiline option:")


For Each match As Match In Regex.Matches(input, pattern)
scores.Add(CInt(match.Groups(2).Value), match.Groups(1).Value)
matched = True
Next
If Not matched Then Console.WriteLine(" No matches.")
Console.WriteLine()

' Redefine pattern to handle multiple lines.


pattern = "^(\w+)\s(\d+)\r*$"
Console.WriteLine("With multiline option:")
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.Multiline)
scores.Add(CInt(match.Groups(2).Value), match.Groups(1).Value)
Next
' List scores in descending order.
For Each score As KeyValuePair(Of Integer, String) In scores
Console.WriteLine("{0}: {1}", score.Value, score.Key)
Next
End Sub
End Module

Public Class DescendingComparer(Of T) : Implements IComparer(Of T)


Public Function Compare(x As T, y As T) As Integer _
Implements IComparer(Of T).Compare
Return Comparer(Of T).Default.Compare(x, y) * -1
End Function
End Class
' The example displays the following output:
' Without Multiline option:
' No matches.
'
' With multiline option:
' Allison: 211
' Sam: 208
' Gwen: 171
' Joe: 164

El patrón de expresión regular ^(\w+)\s(\d+)\r*$ se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

^ Comienza al principio de la línea.

(\w+) Buscar coincidencias con uno o más caracteres


alfabéticos. Este es el primer grupo de captura.

\s Coincide con un carácter de espacio en blanco.


M O DELO DESC RIP C IÓ N

(\d+) Buscar coincidencias con uno o más dígitos decimales.


Este es el segundo grupo de captura.

\r? Coincidencia con cero o con un carácter de retorno de


carro.

$ Finaliza al final de la línea.

El ejemplo siguiente es equivalente al anterior, a excepción de que usa la opción insertada (?m) para
establecer la opción multilínea.

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
SortedList<int, string> scores = new SortedList<int, string>(new DescendingComparer<int>());

string input = "Joe 164\n" +


"Sam 208\n" +
"Allison 211\n" +
"Gwen 171\n";
string pattern = @"(?m)^(\w+)\s(\d+)\r*$";

foreach (Match match in Regex.Matches(input, pattern, RegexOptions.Multiline))


scores.Add(Convert.ToInt32(match.Groups[2].Value), match.Groups[1].Value);

// List scores in descending order.


foreach (KeyValuePair<int, string> score in scores)
Console.WriteLine("{0}: {1}", score.Value, score.Key);
}
}

public class DescendingComparer<T> : IComparer<T>


{
public int Compare(T x, T y)
{
return Comparer<T>.Default.Compare(x, y) * -1;
}
}
// The example displays the following output:
// Allison: 211
// Sam: 208
// Gwen: 171
// Joe: 164
Imports System.Collections.Generic
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim scores As New SortedList(Of Integer, String)(New DescendingComparer(Of Integer)())

Dim input As String = "Joe 164" + vbCrLf + _


"Sam 208" + vbCrLf + _
"Allison 211" + vbCrLf + _
"Gwen 171" + vbCrLf
Dim pattern As String = "(?m)^(\w+)\s(\d+)\r*$"

For Each match As Match In Regex.Matches(input, pattern, RegexOptions.Multiline)


scores.Add(CInt(match.Groups(2).Value), match.Groups(1).Value)
Next
' List scores in descending order.
For Each score As KeyValuePair(Of Integer, String) In scores
Console.WriteLine("{0}: {1}", score.Value, score.Key)
Next
End Sub
End Module

Public Class DescendingComparer(Of T) : Implements IComparer(Of T)


Public Function Compare(x As T, y As T) As Integer _
Implements IComparer(Of T).Compare
Return Comparer(Of T).Default.Compare(x, y) * -1
End Function
End Class
' The example displays the following output:
' Allison: 211
' Sam: 208
' Gwen: 171
' Joe: 164

Modo de una sola línea


La opción RegexOptions.Singleline, o la opción insertada s , hace que el motor de expresiones regulares
trate a la cadena de entrada como si constase de una única línea. Para ello, se cambia el comportamiento
del elemento de lenguaje punto ( . ) para que coincida con todos los caracteres, en vez de coincidir con
todos los caracteres excepto con el de nueva línea \n o \u000A.
En el ejemplo siguiente se muestra cómo cambia el comportamiento del elemento de lenguaje . cuando
se usa la opción RegexOptions.Singleline. La expresión regular ^.+ comienza en el principio de la cadena
y coincide con todos los caracteres. De forma predeterminada, la coincidencia termina al final de la
primera línea; el patrón de la expresión regular coincide con el carácter de retorno de carro, \r o \u000D,
pero no coincide con \n . Dado que la opción RegexOptions.Singleline interpreta la cadena de entrada
completa como una sola línea, coincide con cada carácter de la cadena de entrada, incluido \n .
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "^.+";
string input = "This is one line and" + Environment.NewLine + "this is the second.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(Regex.Escape(match.Value));

Console.WriteLine();
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.Singleline))
Console.WriteLine(Regex.Escape(match.Value));
}
}
// The example displays the following output:
// This\ is\ one\ line\ and\r
//
// This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^.+"
Dim input As String = "This is one line and" + vbCrLf + "this is the second."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(Regex.Escape(match.Value))
Next
Console.WriteLine()
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.SingleLine)
Console.WriteLine(Regex.Escape(match.Value))
Next
End Sub
End Module
' The example displays the following output:
' This\ is\ one\ line\ and\r
'
' This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.

El ejemplo siguiente es equivalente al anterior, a excepción de que usa la opción insertada (?s) para
habilitar el modo de una sola línea.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "(?s)^.+";
string input = "This is one line and" + Environment.NewLine + "this is the second.";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine(Regex.Escape(match.Value));
}
}
// The example displays the following output:
// This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?s)^.+"
Dim input As String = "This is one line and" + vbCrLf + "this is the second."

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine(Regex.Escape(match.Value))
Next
End Sub
End Module
' The example displays the following output:
' This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.

Solo capturas explícitas


De forma predeterminada, los grupos de captura se definen con el uso de paréntesis en el patrón de
expresión regular. A los grupos con nombre se les asigna un nombre o un número con la opción de
lenguaje (?< nombre > subexpresión ) , mientras que a los grupos sin nombre se accede mediante el
índice. En el objeto GroupCollection, los grupos sin nombre preceden a los grupos con nombre.
A menudo, los constructores de agrupamiento se usan únicamente para aplicar cuantificadores a varios
elementos de lenguaje, y las subcadenas capturadas carecen de interés. Por ejemplo, en la expresión
regular siguiente:
\b\(?((\w+),?\s?)+[\.!?]\)?

Su única finalidad es extraer de un documento las oraciones que finalizan con un punto, signo de
exclamación o signo de interrogación, y solo la oración resultante (representada mediante el objeto
Match) es de interés. En cambio, las palabras individuales de la colección no son de interés.
Los grupos de captura que no se usan posteriormente pueden ser costosos, ya que el motor de
expresiones regulares debe rellenar los objetos de colección GroupCollection y CaptureCollection. Como
alternativa, se puede usar la opción RegexOptions.ExplicitCapture o la opción insertada n para especificar
que las únicas capturas válidas son grupos con nombre o número explícitos que se designan mediante la
construcción (?< nombre > subexpresión ) .
En el ejemplo siguiente se muestra información sobre las coincidencias devueltas por el patrón de
expresión regular \b\(?((\w+),?\s?)+[\.!?]\)? cuando se llama al método Match con y sin la opción
RegexOptions.ExplicitCapture. Tal como muestra el resultado de la primera llamada a método, el motor de
expresiones regulares rellena totalmente los objetos de colección GroupCollection y CaptureCollection
con información sobre las subcadenas capturadas. Dado que el segundo método se llama con options
establecido en RegexOptions.ExplicitCapture, no se captura información en grupos.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is the first sentence. Is it the beginning " +
"of a literary masterpiece? I think not. Instead, " +
"it is a nonsensical paragraph.";
string pattern = @"\b\(?((?>\w+),?\s?)+[\.!?]\)?";
Console.WriteLine("With implicit captures:");
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine("The match: {0}", match.Value);
Console.WriteLine("The match: {0}", match.Value);
int groupCtr = 0;
foreach (Group group in match.Groups)
{
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value);
groupCtr++;
int captureCtr = 0;
foreach (Capture capture in group.Captures)
{
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value);
captureCtr++;
}
}
}
Console.WriteLine();
Console.WriteLine("With explicit captures only:");
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.ExplicitCapture))
{
Console.WriteLine("The match: {0}", match.Value);
int groupCtr = 0;
foreach (Group group in match.Groups)
{
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value);
groupCtr++;
int captureCtr = 0;
foreach (Capture capture in group.Captures)
{
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value);
captureCtr++;
}
}
}
}
}
// The example displays the following output:
// With implicit captures:
// The match: This is the first sentence.
// Group 0: This is the first sentence.
// Capture 0: This is the first sentence.
// Group 1: sentence
// Capture 0: This
// Capture 1: is
// Capture 2: the
// Capture 3: first
// Capture 4: sentence
// Group 2: sentence
// Capture 0: This
// Capture 1: is
// Capture 2: the
// Capture 3: first
// Capture 4: sentence
// The match: Is it the beginning of a literary masterpiece?
// Group 0: Is it the beginning of a literary masterpiece?
// Capture 0: Is it the beginning of a literary masterpiece?
// Group 1: masterpiece
// Capture 0: Is
// Capture 1: it
// Capture 2: the
// Capture 3: beginning
// Capture 4: of
// Capture 5: a
// Capture 6: literary
// Capture 7: masterpiece
// Group 2: masterpiece
// Capture 0: Is
// Capture 1: it
// Capture 2: the
// Capture 3: beginning
// Capture 4: of
// Capture 5: a
// Capture 5: a
// Capture 6: literary
// Capture 7: masterpiece
// The match: I think not.
// Group 0: I think not.
// Capture 0: I think not.
// Group 1: not
// Capture 0: I
// Capture 1: think
// Capture 2: not
// Group 2: not
// Capture 0: I
// Capture 1: think
// Capture 2: not
// The match: Instead, it is a nonsensical paragraph.
// Group 0: Instead, it is a nonsensical paragraph.
// Capture 0: Instead, it is a nonsensical paragraph.
// Group 1: paragraph
// Capture 0: Instead,
// Capture 1: it
// Capture 2: is
// Capture 3: a
// Capture 4: nonsensical
// Capture 5: paragraph
// Group 2: paragraph
// Capture 0: Instead
// Capture 1: it
// Capture 2: is
// Capture 3: a
// Capture 4: nonsensical
// Capture 5: paragraph
//
// With explicit captures only:
// The match: This is the first sentence.
// Group 0: This is the first sentence.
// Capture 0: This is the first sentence.
// The match: Is it the beginning of a literary masterpiece?
// Group 0: Is it the beginning of a literary masterpiece?
// Capture 0: Is it the beginning of a literary masterpiece?
// The match: I think not.
// Group 0: I think not.
// Capture 0: I think not.
// The match: Instead, it is a nonsensical paragraph.
// Group 0: Instead, it is a nonsensical paragraph.
// Capture 0: Instead, it is a nonsensical paragraph.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is the first sentence. Is it the beginning " + _
"of a literary masterpiece? I think not. Instead, " + _
"it is a nonsensical paragraph."
Dim pattern As String = "\b\(?((?>\w+),?\s?)+[\.!?]\)?"
Console.WriteLine("With implicit captures:")
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("The match: {0}", match.Value)
Dim groupCtr As Integer = 0
For Each group As Group In match.Groups
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value)
groupCtr += 1
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value)
captureCtr += 1
Next
Next
Next
Next
Console.WriteLine()
Console.WriteLine("With explicit captures only:")
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.ExplicitCapture)
Console.WriteLine("The match: {0}", match.Value)
Dim groupCtr As Integer = 0
For Each group As Group In match.Groups
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value)
groupCtr += 1
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value)
captureCtr += 1
Next
Next
Next
End Sub
End Module
' The example displays the following output:
' With implicit captures:
' The match: This is the first sentence.
' Group 0: This is the first sentence.
' Capture 0: This is the first sentence.
' Group 1: sentence
' Capture 0: This
' Capture 1: is
' Capture 2: the
' Capture 3: first
' Capture 4: sentence
' Group 2: sentence
' Capture 0: This
' Capture 1: is
' Capture 2: the
' Capture 3: first
' Capture 4: sentence
' The match: Is it the beginning of a literary masterpiece?
' Group 0: Is it the beginning of a literary masterpiece?
' Capture 0: Is it the beginning of a literary masterpiece?
' Group 1: masterpiece
' Capture 0: Is
' Capture 1: it
' Capture 2: the
' Capture 3: beginning
' Capture 4: of
' Capture 5: a
' Capture 6: literary
' Capture 7: masterpiece
' Group 2: masterpiece
' Capture 0: Is
' Capture 1: it
' Capture 2: the
' Capture 3: beginning
' Capture 4: of
' Capture 5: a
' Capture 6: literary
' Capture 7: masterpiece
' The match: I think not.
' Group 0: I think not.
' Capture 0: I think not.
' Group 1: not
' Capture 0: I
' Capture 1: think
' Capture 2: not
' Group 2: not
' Capture 0: I
' Capture 1: think
' Capture 2: not
' The match: Instead, it is a nonsensical paragraph.
' Group 0: Instead, it is a nonsensical paragraph.
' Capture 0: Instead, it is a nonsensical paragraph.
' Group 1: paragraph
' Capture 0: Instead,
' Capture 1: it
' Capture 2: is
' Capture 3: a
' Capture 4: nonsensical
' Capture 5: paragraph
' Group 2: paragraph
' Capture 0: Instead
' Capture 1: it
' Capture 2: is
' Capture 3: a
' Capture 4: nonsensical
' Capture 5: paragraph
'
' With explicit captures only:
' The match: This is the first sentence.
' Group 0: This is the first sentence.
' Capture 0: This is the first sentence.
' The match: Is it the beginning of a literary masterpiece?
' Group 0: Is it the beginning of a literary masterpiece?
' Capture 0: Is it the beginning of a literary masterpiece?
' The match: I think not.
' Group 0: I think not.
' Capture 0: I think not.
' The match: Instead, it is a nonsensical paragraph.
' Group 0: Instead, it is a nonsensical paragraph.
' Capture 0: Instead, it is a nonsensical paragraph.

El patrón de expresión regular \b\(?((?>\w+),?\s?)+[\.!?]\)? se define como se muestra en la siguiente


tabla.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

\(? Busca una coincidencia con cero o con una aparición de


los paréntesis de apertura ("(").

(?>\w+),? Busca una coincidencia con uno o más caracteres


alfabéticos seguidos de cero o una coma. No hay
retroceso al coincidir con caracteres alfabéticos.

\s? Busca una coincidencia con cero o un carácter de espacio


en blanco.

((\w+),?\s?)+ Busca una coincidencia con la combinación de uno o


varios caracteres de palabra, cero o una coma, y cero o
un carácter de espacio en blanco una o varias veces.

[\.!?]\)? Busca una coincidencia con cualquiera de los tres


símbolos de puntuación, seguidos de cero o un
paréntesis de cierre (")").

También se puede usar el elemento insertado (?n) para suprimir las capturas automáticas. En el ejemplo
siguiente se modifica el patrón de expresión regular anterior para usar el elemento insertado (?n) en vez
de la opción RegexOptions.ExplicitCapture.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is the first sentence. Is it the beginning " +
"of a literary masterpiece? I think not. Instead, " +
"it is a nonsensical paragraph.";
string pattern = @"(?n)\b\(?((?>\w+),?\s?)+[\.!?]\)?";

foreach (Match match in Regex.Matches(input, pattern))


{
Console.WriteLine("The match: {0}", match.Value);
int groupCtr = 0;
foreach (Group group in match.Groups)
{
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value);
groupCtr++;
int captureCtr = 0;
foreach (Capture capture in group.Captures)
{
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value);
captureCtr++;
}
}
}
}
}
// The example displays the following output:
// The match: This is the first sentence.
// Group 0: This is the first sentence.
// Capture 0: This is the first sentence.
// The match: Is it the beginning of a literary masterpiece?
// Group 0: Is it the beginning of a literary masterpiece?
// Capture 0: Is it the beginning of a literary masterpiece?
// The match: I think not.
// Group 0: I think not.
// Capture 0: I think not.
// The match: Instead, it is a nonsensical paragraph.
// Group 0: Instead, it is a nonsensical paragraph.
// Capture 0: Instead, it is a nonsensical paragraph.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is the first sentence. Is it the beginning " + _
"of a literary masterpiece? I think not. Instead, " + _
"it is a nonsensical paragraph."
Dim pattern As String = "(?n)\b\(?((?>\w+),?\s?)+[\.!?]\)?"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("The match: {0}", match.Value)
Dim groupCtr As Integer = 0
For Each group As Group In match.Groups
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value)
groupCtr += 1
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value)
captureCtr += 1
Next
Next
Next
End Sub
End Module
' The example displays the following output:
' The match: This is the first sentence.
' Group 0: This is the first sentence.
' Capture 0: This is the first sentence.
' The match: Is it the beginning of a literary masterpiece?
' Group 0: Is it the beginning of a literary masterpiece?
' Capture 0: Is it the beginning of a literary masterpiece?
' The match: I think not.
' Group 0: I think not.
' Capture 0: I think not.
' The match: Instead, it is a nonsensical paragraph.
' Group 0: Instead, it is a nonsensical paragraph.
' Capture 0: Instead, it is a nonsensical paragraph.

Finalmente, se puede usar el elemento de grupo insertado (?n:) para suprimir las capturas automáticas
grupo a grupo. En el ejemplo siguiente se modifica el patrón anterior para suprimir las capturas sin
nombre en el grupo externo, ((?>\w+),?\s?) . Observe que, de este modo, también se suprimen las
capturas sin nombre en el grupo interno.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is the first sentence. Is it the beginning " +
"of a literary masterpiece? I think not. Instead, " +
"it is a nonsensical paragraph.";
string pattern = @"\b\(?(?n:(?>\w+),?\s?)+[\.!?]\)?";

foreach (Match match in Regex.Matches(input, pattern))


{
Console.WriteLine("The match: {0}", match.Value);
int groupCtr = 0;
foreach (Group group in match.Groups)
{
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value);
groupCtr++;
int captureCtr = 0;
foreach (Capture capture in group.Captures)
{
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value);
captureCtr++;
}
}
}
}
}
// The example displays the following output:
// The match: This is the first sentence.
// Group 0: This is the first sentence.
// Capture 0: This is the first sentence.
// The match: Is it the beginning of a literary masterpiece?
// Group 0: Is it the beginning of a literary masterpiece?
// Capture 0: Is it the beginning of a literary masterpiece?
// The match: I think not.
// Group 0: I think not.
// Capture 0: I think not.
// The match: Instead, it is a nonsensical paragraph.
// Group 0: Instead, it is a nonsensical paragraph.
// Capture 0: Instead, it is a nonsensical paragraph.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is the first sentence. Is it the beginning " + _
"of a literary masterpiece? I think not. Instead, " + _
"it is a nonsensical paragraph."
Dim pattern As String = "\b\(?(?n:(?>\w+),?\s?)+[\.!?]\)?"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("The match: {0}", match.Value)
Dim groupCtr As Integer = 0
For Each group As Group In match.Groups
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value)
groupCtr += 1
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value)
captureCtr += 1
Next
Next
Next
End Sub
End Module
' The example displays the following output:
' The match: This is the first sentence.
' Group 0: This is the first sentence.
' Capture 0: This is the first sentence.
' The match: Is it the beginning of a literary masterpiece?
' Group 0: Is it the beginning of a literary masterpiece?
' Capture 0: Is it the beginning of a literary masterpiece?
' The match: I think not.
' Group 0: I think not.
' Capture 0: I think not.
' The match: Instead, it is a nonsensical paragraph.
' Group 0: Instead, it is a nonsensical paragraph.
' Capture 0: Instead, it is a nonsensical paragraph.

Expresiones regulares compiladas


Las expresiones de .NET se interpretan de forma predeterminada. Cuando se crea una instancia de un
objeto Regex o se llama a un método Regex estático, el patrón de expresión regular se analiza en un
conjunto de códigos de operación para ejecutar la operación regular. Esto implica una contrapartida: el
coste de inicializar el motor de expresiones regulares se minimiza a costa del rendimiento en tiempo de
ejecución.
Si quiere usar expresiones regulares compiladas en vez de interpretadas, utilice la opción
RegexOptions.Compiled. En este caso, cuando un patrón se pasa al motor de expresiones regulares, se
analiza en un conjunto de códigos de operación y luego se convierte en Lenguaje intermedio de Microsoft
(MSIL, por sus siglas en inglés), que puede pasarse directamente a Common Language Runtime. Las
expresiones regulares compiladas maximizan el rendimiento en tiempo de ejecución a costa del tiempo de
inicialización.

NOTE
Una expresión regular solo se puede compilar si se suministra el valor RegexOptions.Compiled al parámetro
options de un constructor de clases Regex o de un método estático de coincidencia de patrones. No está
disponible como opción insertada.

Las expresiones regulares compiladas se pueden usar en llamadas a expresiones regulares estáticas y de
instancias. En expresiones regulares estáticas, la opción RegexOptions.Compiled se pasa al parámetro
options del método de coincidencia de patrones de expresión regular. En expresiones regulares de
instancia, se pasa al parámetro options del constructor de clases Regex. En ambos casos, tiene como
resultado una mejora en el rendimiento.
No obstante, esta mejora en el rendimiento solo se produce bajo las condiciones siguientes:
Un objeto Regex que representa una expresión regular concreta se usa en varias llamadas a
métodos de coincidencia de patrones de expresión regular.
El objeto Regex no puede estar fuera del ámbito y, por tanto, se puede volver a usar.
Una expresión regular estática se usa en varias llamadas a métodos de coincidencia de patrones de
expresión regular. (La mejora de rendimiento es posible porque el motor de expresiones regulares
almacena en la caché las expresiones regulares que se usan en llamadas de métodos estáticos).

NOTE
La opción RegexOptions.Compiled no está relacionada con el método Regex.CompileToAssembly, que crea un
ensamblado para fines especiales y que contiene expresiones regulares compiladas predefinidas.

Ignorar el espacio en blanco


De forma predeterminada, el espacio en blanco de un patrón de expresión regular es significativo; fuerza
al motor de expresiones regulares a buscar un carácter de espacio en blanco en la cadena de entrada.
Debido a ello, las expresiones regulares " \b\w+\s " y " \b\w+ " son, en líneas generales, expresiones
regulares equivalentes. Además, cuando el signo de número (#) se detecta en un patrón de expresión
regular, se interpreta como un carácter literal para el que se deben buscar coincidencias.
La opción RegexOptions.IgnorePatternWhitespace, o la opción insertada x , cambia su comportamiento
predeterminado del modo siguiente:
El espacio en blanco sin escape del patrón de expresión regular se ignora. Para formar parte de un
patrón de expresión regular, los caracteres de espacio en blanco se deben incluir en secuencias de
escape (por ejemplo, como \s o " \ ").
El signo de número (#) se interpreta como el inicio de un comentario, en vez de como carácter
literal. Todo el texto del patrón de expresión regular comprendido entre el carácter # y el final de la
cadena se interpreta como un comentario.
No obstante, en los casos siguientes, los caracteres de espacio de una expresión regular no se ignoran,
aunque se use la opción RegexOptions.IgnorePatternWhitespace:
Un espacio en blanco dentro de una clase de caracteres se interpreta siempre de forma literal. Por
ejemplo, el patrón de expresión regular [ .,;:] coincide con cualquier carácter de espacio en
blanco, punto, coma, punto y coma o dos puntos.
Los cuantificadores entre corchetes, como { n } , { n ,} y { n , m } , no admiten espacios en
blanco. Por ejemplo, el patrón de expresión regular \d{1, 3} no encontrará ninguna secuencia de
entre uno y tres guarismos porque contiene un carácter de espacio en blanco.
No se admiten espacios en blanco en una secuencia de caracteres que presenta un elemento de
lenguaje. Por ejemplo:
El elemento de lenguaje (?: subexpresión ) representa un grupo sin captura, y la parte
(?: del elemento no puede tener espacios insertados. El modelo (? : subexpresión )
produce una clase ArgumentException en tiempo de ejecución porque el motor de
expresiones regulares no puede analizar el modelo, y el modelo ( ?: subexpresión ) no
coincide con subexpresión.
El elemento de lenguaje \p{ nombre } , que representa una categoría Unicode o bloque con
nombre, no puede incluir espacios insertados en la parte \p{ del elemento. Si se incluye un
espacio en blanco, el elemento produce una ArgumentException en tiempo de ejecución.
Esta opción sirve para simplificar las expresiones regulares que a menudo resultan difíciles de analizar y
entender. Además, mejora la legibilidad y posibilita la documentación de una expresión regular.
Este ejemplo define el siguiente patrón de expresión regular:
\b \(? ( (?>\w+) ,?\s? )+ [\.!?] \)? # Matches an entire sentence.

El modelo es similar al modelo definido en la sección Solo capturas explícitas a excepción de que utiliza la
opción RegexOptions.IgnorePatternWhitespace para ignorar el espacio en blanco del modelo.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is the first sentence. Is it the beginning " +
"of a literary masterpiece? I think not. Instead, " +
"it is a nonsensical paragraph.";
string pattern = @"\b \(? ( (?>\w+) ,?\s? )+ [\.!?] \)? # Matches an entire sentence.";

foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnorePatternWhitespace))


Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// This is the first sentence.
// Is it the beginning of a literary masterpiece?
// I think not.
// Instead, it is a nonsensical paragraph.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is the first sentence. Is it the beginning " + _
"of a literary masterpiece? I think not. Instead, " + _
"it is a nonsensical paragraph."
Dim pattern As String = "\b \(? ( (?>\w+) ,?\s? )+ [\.!?] \)? # Matches an entire sentence."

For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnorePatternWhitespace)


Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' This is the first sentence.
' Is it the beginning of a literary masterpiece?
' I think not.
' Instead, it is a nonsensical paragraph.

En el ejemplo siguiente se usa la opción insertada (?x) para ignorar el espacio en blanco del patrón.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is the first sentence. Is it the beginning " +
"of a literary masterpiece? I think not. Instead, " +
"it is a nonsensical paragraph.";
string pattern = @"(?x)\b \(? ( (?>\w+) ,?\s? )+ [\.!?] \)? # Matches an entire sentence.";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// This is the first sentence.
// Is it the beginning of a literary masterpiece?
// I think not.
// Instead, it is a nonsensical paragraph.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is the first sentence. Is it the beginning " + _
"of a literary masterpiece? I think not. Instead, " + _
"it is a nonsensical paragraph."
Dim pattern As String = "(?x)\b \(? ( (?>\w+) ,?\s? )+ [\.!?] \)? # Matches an entire
sentence."

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' This is the first sentence.
' Is it the beginning of a literary masterpiece?
' I think not.
' Instead, it is a nonsensical paragraph.

Modo de derecha a izquierda


De forma predeterminada, el motor de expresiones regulares realiza las búsquedas de izquierda a
derecha. Para invertir la dirección de búsqueda, se puede usar la opción RegexOptions.RightToLeft. De este
modo, la búsqueda empieza automáticamente en la posición del último carácter de la cadena. En los
métodos de coincidencia de patrones que incluyen un parámetro de posición inicial, como
Regex.Match(String, Int32), la posición inicial es el índice del carácter situado más a la derecha en el que
debe empezar la búsqueda.

NOTE
El modo de patrón de derecha a izquierda solo está disponible si se suministra el valor RegexOptions.RightToLeft al
parámetro options de un constructor de clases Regex o de un método estático de coincidencia de patrones. No
está disponible como opción insertada.

La opción RegexOptions.RightToLeft únicamente cambia la dirección de búsqueda, no interpreta el patrón


de expresión regular de derecha a izquierda. Por ejemplo, la expresión regular \bb\w+\s coincide con las
palabras que empiezan por la letra “b” y van seguidas por un carácter de espacio en blanco. En el ejemplo
siguiente, la cadena de entrada consta de tres palabras que incluyen uno o varios caracteres “b”. La
primera palabra empieza por “b”, la segunda finaliza en “b” y la tercera incluye dos caracteres “b” en el
medio de la palabra. Tal como muestra el resultado del ejemplo, solo la primera palabra coincide con el
patrón de expresión regular.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\bb\w+\s";
string input = "builder rob rabble";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.RightToLeft))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);
}
}
// The example displays the following output:
// 'builder ' found at position 0.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\bb\w+\s"
Dim input As String = "builder rob rabble"
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.RightToLeft)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' 'builder ' found at position 0.

Observe también que la aserción de búsqueda anticipada (el elemento de lenguaje (?= subexpresión ) )
y la aserción de búsqueda tardía (el elemento de lenguaje (?<= subexpresión ) ) no cambian de dirección.
Las aserciones de búsqueda anticipada miran hacia la derecha, mientras que las de búsqueda tardía lo
hacen hacia la izquierda. Por ejemplo, la expresión regular (?<=\d{1,2}\s)\w+,?\s\d{4} usa la aserción de
búsqueda tardía para probar una fecha que precede al nombre de un mes. Después, la expresión regular
busca coincidencias con el mes y el año. Para información sobre aserciones de búsqueda anticipada y
tardía, consulte Construcciones de agrupamiento.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "1 May 1917", "June 16, 2003" };
string pattern = @"(?<=\d{1,2}\s)\w+,?\s\d{4}";

foreach (string input in inputs)


{
Match match = Regex.Match(input, pattern, RegexOptions.RightToLeft);
if (match.Success)
Console.WriteLine("The date occurs in {0}.", match.Value);
else
Console.WriteLine("{0} does not match.", input);
}
}
}
// The example displays the following output:
// The date occurs in May 1917.
// June 16, 2003 does not match.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"1 May 1917", "June 16, 2003"}
Dim pattern As String = "(?<=\d{1,2}\s)\w+,?\s\d{4}"

For Each input As String In inputs


Dim match As Match = Regex.Match(input, pattern, RegexOptions.RightToLeft)
If match.Success Then
Console.WriteLine("The date occurs in {0}.", match.Value)
Else
Console.WriteLine("{0} does not match.", input)
End If
Next
End Sub
End Module
' The example displays the following output:
' The date occurs in May 1917.
' June 16, 2003 does not match.

El patrón de expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

(?<=\d{1,2}\s) El inicio de la coincidencia debe estar precedido por uno o


dos dígitos decimales seguidos de un espacio.

\w+ Buscar coincidencias con uno o más caracteres


alfabéticos.

,? Buscar coincidencias con cero o un carácter de coma.

\s Coincide con un carácter de espacio en blanco.

\d{4} Buscar coincidencias con cuatro dígitos decimales.


Comportamiento de búsqueda de coincidencias de ECMAScript
De forma predeterminada, el motor de expresiones regulares usa un comportamiento canónico al buscar
coincidencias entre un patrón de expresiones regulares y el texto de entrada. Sin embargo, se puede
especificar la opción RegexOptions.ECMAScript para hacer que el motor de expresiones regulares use el
comportamiento de búsqueda de coincidencias de ECMAScript.

NOTE
El comportamiento conforme a ECMAScript solo está disponible si se suministra el valor RegexOptions.ECMAScript
al parámetro options de un constructor de clases Regex o de un método estático de coincidencia de patrones.
No está disponible como opción insertada.

La opción RegexOptions.ECMAScript únicamente se puede combinar con las opciones


RegexOptions.IgnoreCase y RegexOptions.Multiline. El uso de cualquier otra opción en una expresión
regular provoca una ArgumentOutOfRangeException.
El comportamiento de las expresiones regulares canónicas y ECMAScript se diferencia en tres aspectos:
sintaxis de la clase de caracteres, grupos de captura con autorreferencia e interpretación de referencia
inversa frente a octal.
Sintaxis de la clase de caracteres. Debido a que las expresiones regulares canónicas admiten
Unicode y ECMAScript no, las clases de caracteres de ECMAScript tienen una sintaxis más limitada y
algunos elementos de lenguaje de clases de caracteres tienen un significado diferente. Por ejemplo,
ECMAScript no admite elementos de lenguaje como la categoría Unicode ni elementos como \p y
\P . De igual modo, el elemento \w , que coincide con un carácter literal, es equivalente a la clase
de caracteres [a-zA-Z_0-9] al usar ECMAScript y a [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Pc}\p{Lm}]
al usar comportamiento canónico. Para más información, consulte Clases de caracteres.
En el ejemplo siguiente se muestra la diferencia entre la búsqueda de coincidencias canónica y la de
ECMAScript. Se define una expresión regular, \b(\w+\s*)+ , que busca coincidencias con palabras
seguidas de caracteres de espacio en blanco. La entrada consta de dos cadenas: una que usa el
conjunto de caracteres latinos y otra que usa el conjunto de caracteres del cirílico. Tal como muestra
el resultado, la llamada al método Regex.IsMatch(String, String, RegexOptions) que usa la búsqueda
de coincidencias de ECMAScript no encuentra coincidencias con las palabras del cirílico, mientras
que la llamada al método que usa la búsqueda de coincidencias canónica sí encuentra coincidencias
con estas palabras.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] values = { "целый мир", "the whole world" };
string pattern = @"\b(\w+\s*)+";
foreach (var value in values)
{
Console.Write("Canonical matching: ");
if (Regex.IsMatch(value, pattern))
Console.WriteLine("'{0}' matches the pattern.", value);
else
Console.WriteLine("{0} does not match the pattern.", value);

Console.Write("ECMAScript matching: ");


if (Regex.IsMatch(value, pattern, RegexOptions.ECMAScript))
Console.WriteLine("'{0}' matches the pattern.", value);
else
Console.WriteLine("{0} does not match the pattern.", value);
Console.WriteLine();
}
}
}
// The example displays the following output:
// Canonical matching: 'целый мир' matches the pattern.
// ECMAScript matching: целый мир does not match the pattern.
//
// Canonical matching: 'the whole world' matches the pattern.
// ECMAScript matching: 'the whole world' matches the pattern.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim values() As String = {"целый мир", "the whole world"}
Dim pattern As String = "\b(\w+\s*)+"
For Each value In values
Console.Write("Canonical matching: ")
If Regex.IsMatch(value, pattern)
Console.WriteLine("'{0}' matches the pattern.", value)
Else
Console.WriteLine("{0} does not match the pattern.", value)
End If

Console.Write("ECMAScript matching: ")


If Regex.IsMatch(value, pattern, RegexOptions.ECMAScript)
Console.WriteLine("'{0}' matches the pattern.", value)
Else
Console.WriteLine("{0} does not match the pattern.", value)
End If
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Canonical matching: 'целый мир' matches the pattern.
' ECMAScript matching: целый мир does not match the pattern.
'
' Canonical matching: 'the whole world' matches the pattern.
' ECMAScript matching: 'the whole world' matches the pattern.

Grupos de captura con autorreferencia. Una clase de captura de expresión regular con una
referencia inversa a sí misma debe actualizarse con cada iteración de captura. Como se muestra en
el ejemplo siguiente, esta característica permite a la expresión regular ((a+)(\1) ?)+ coincidir con
la cadena “ aa aaaa aaaaaa ” si se usa ECMAScript, pero no si se usa la búsqueda de coincidencias
canónica.
using System;
using System.Text.RegularExpressions;

public class Example


{
static string pattern;

public static void Main()


{
string input = "aa aaaa aaaaaa ";
pattern = @"((a+)(\1) ?)+";

// Match input using canonical matching.


AnalyzeMatch(Regex.Match(input, pattern));

// Match input using ECMAScript.


AnalyzeMatch(Regex.Match(input, pattern, RegexOptions.ECMAScript));
}

private static void AnalyzeMatch(Match m)


{
if (m.Success)
{
Console.WriteLine("'{0}' matches {1} at position {2}.",
pattern, m.Value, m.Index);
int grpCtr = 0;
foreach (Group grp in m.Groups)
{
Console.WriteLine(" {0}: '{1}'", grpCtr, grp.Value);
grpCtr++;
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine(" {0}: '{1}'", capCtr, cap.Value);
capCtr++;
}
}
}
else
{
Console.WriteLine("No match found.");
}
Console.WriteLine();
}
}
// The example displays the following output:
// No match found.
//
// '((a+)(\1) ?)+' matches aa aaaa aaaaaa at position 0.
// 0: 'aa aaaa aaaaaa '
// 0: 'aa aaaa aaaaaa '
// 1: 'aaaaaa '
// 0: 'aa '
// 1: 'aaaa '
// 2: 'aaaaaa '
// 2: 'aa'
// 0: 'aa'
// 1: 'aa'
// 2: 'aa'
// 3: 'aaaa '
// 0: ''
// 1: 'aa '
// 2: 'aaaa '
Imports System.Text.RegularExpressions

Module Example
Dim pattern As String

Public Sub Main()


Dim input As String = "aa aaaa aaaaaa "
pattern = "((a+)(\1) ?)+"

' Match input using canonical matching.


AnalyzeMatch(Regex.Match(input, pattern))

' Match input using ECMAScript.


AnalyzeMatch(Regex.Match(input, pattern, RegexOptions.ECMAScript))
End Sub

Private Sub AnalyzeMatch(m As Match)


If m.Success
Console.WriteLine("'{0}' matches {1} at position {2}.", _
pattern, m.Value, m.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In m.Groups
Console.WriteLine(" {0}: '{1}'", grpCtr, grp.Value)
grpCtr += 1
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" {0}: '{1}'", capCtr, cap.Value)
capCtr += 1
Next
Next
Else
Console.WriteLine("No match found.")
End If
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' No match found.
'
' '((a+)(\1) ?)+' matches aa aaaa aaaaaa at position 0.
' 0: 'aa aaaa aaaaaa '
' 0: 'aa aaaa aaaaaa '
' 1: 'aaaaaa '
' 0: 'aa '
' 1: 'aaaa '
' 2: 'aaaaaa '
' 2: 'aa'
' 0: 'aa'
' 1: 'aa'
' 2: 'aa'
' 3: 'aaaa '
' 0: ''
' 1: 'aa '
' 2: 'aaaa '

La expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

(a+) Busca una coincidencia con la letra “a” una o más


veces. Este es el segundo grupo de captura.
M O DELO DESC RIP C IÓ N

(\1) Busca una coincidencia con la subcadena capturada


por el primer grupo de captura. Éste es el tercer
grupo de captura.

? Busca una coincidencia con cero o un carácter de


espacio.

((a+)(\1) ?)+ Busca una coincidencia con el patrón de uno o varios


caracteres “a” seguidos por una cadena que coincide
con el primer grupo de captura seguido por cero o
por un carácter de espacio una o varias veces. Este es
el primer grupo de captura.

Resolución de ambigüedades entre secuencias de escape octales y referencias inversas. En la tabla


siguiente se indican las diferencias en la interpretación de referencia inversa frente a octal por parte
de expresiones regulares canónicas y de ECMAScript.

C O M P O RTA M IEN TO DE
EXP RESIÓ N REGUL A R C O M P O RTA M IEN TO C A N Ó N IC O EC M A SC RIP T

\0 seguido de 0 a 2 dígitos Se interpreta como un octal. Por Mismo comportamiento.


octales ejemplo, \044 se interpreta
siempre como un valor octal y
significa “$”.

\ seguido de un dígito de 1 a 9, Se interpreta como una referencia Si existe un grupo de captura de


seguido de ningún dígito decimal inversa. Por ejemplo, \9 siempre un solo dígito decimal, realice una
adicional significa una referencia inversa de referencia inversa a ese dígito. De
9, incluso si no existe un noveno lo contrario, interprete el valor
grupo de captura. Si el grupo de como literal.
captura no existe, el analizador de
expresiones regulares produce
una ArgumentException.

\ seguido de un dígito de 1 a 9, Interprete los dígitos como un Para interpretarlo como una
seguido de dígitos decimales valor decimal. Si existe ese grupo referencia inversa, convierta todos
adicionales de captura, interprete la expresión los dígitos posibles en un valor
como una referencia inversa. decimal que pueda hacer
referencia a una captura. Si no se
De lo contrario, interprete los puede convertir ningún dígito,
dígitos octales iniciales hasta el interprételo como un octal; para
octal 377; es decir, tenga en ello, use los dígitos octales
cuenta solo los 8 bits inferiores iniciales hasta el octal 377 e
del valor. Interprete el resto de interprete el resto de dígitos
dígitos como literales. Por como literales.
ejemplo, en la expresión \3000 ,
si existe el grupo de captura 300,
interprételo como referencia
inversa de 300; si el grupo de
captura 300 no existe,
interprételo como un octal 300
seguido de 0.

Comparación con la referencia cultural de todos los idiomas


De forma predeterminada, el motor de expresiones regulares, cuando realiza comparaciones sin distinguir
entre mayúsculas y minúsculas, utiliza las convenciones sobre el uso de mayúsculas de la referencia
cultural actual con el fin de determinar los caracteres equivalentes en mayúscula y minúscula.
Sin embargo, este comportamiento no es el deseado en algunos tipos de comparaciones, en particular al
comparar la entrada del usuario con los nombres de recursos del sistema, como pueden ser contraseñas,
archivos o direcciones URL. En el ejemplo siguiente se muestra ese escenario. La finalidad del código es
bloquear el acceso a los recursos cuya dirección URL va precedida de FILE:// . La expresión regular intenta
una búsqueda de coincidencias con la cadena sin distinguir entre mayúsculas y minúsculas y, a tal fin, usa
la expresión $FILE:// . Sin embargo, cuando la referencia cultural actual del sistema es tr-TR (Turco -
Turquía), “I” no es el equivalente en mayúscula de “i”. Como resultado, la llamada al método Regex.IsMatch
devuelve false , con lo que se permite el acceso al archivo.

CultureInfo defaultCulture = Thread.CurrentThread.CurrentCulture;


Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");

string input = "file://c:/Documents.MyReport.doc";


string pattern = "FILE://";

Console.WriteLine("Culture-sensitive matching ({0} culture)...",


Thread.CurrentThread.CurrentCulture.Name);
if (Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("URLs that access files are not allowed.");
else
Console.WriteLine("Access to {0} is allowed.", input);

Thread.CurrentThread.CurrentCulture = defaultCulture;
// The example displays the following output:
// Culture-sensitive matching (tr-TR culture)...
// Access to file://c:/Documents.MyReport.doc is allowed.

Dim defaultCulture As CultureInfo = Thread.CurrentThread.CurrentCulture


Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")

Dim input As String = "file://c:/Documents.MyReport.doc"


Dim pattern As String = "$FILE://"

Console.WriteLine("Culture-sensitive matching ({0} culture)...", _


Thread.CurrentThread.CurrentCulture.Name)
If Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase) Then
Console.WriteLine("URLs that access files are not allowed.")
Else
Console.WriteLine("Access to {0} is allowed.", input)
End If

Thread.CurrentThread.CurrentCulture = defaultCulture
' The example displays the following output:
' Culture-sensitive matching (tr-TR culture)...
' Access to file://c:/Documents.MyReport.doc is allowed.

NOTE
Para obtener más información sobre la comparación de cadenas con distinción entre mayúsculas y minúsculas, y
con la referencia cultural de todos los idiomas, consulte Procedimientos recomendados para el uso de cadenas.

En vez de usar comparaciones sin distinción entre mayúsculas y minúsculas de la referencia cultural
actual, se puede especificar la opción RegexOptions.CultureInvariant para ignorar las diferencias culturales
de idioma y usar las convenciones de la referencia cultural de todos los idiomas.
NOTE
La comparación con la referencia cultural de todos los idiomas solo está disponible si se suministra el valor
RegexOptions.CultureInvariant al parámetro options de un constructor de clases Regex o de un método estático
de coincidencia de patrones. No está disponible como opción insertada.

El ejemplo siguiente es idéntico al anterior, excepto que se llama al método Regex.IsMatch(String, String,
RegexOptions) estático con opciones que incluyen RegexOptions.CultureInvariant. A pesar de que la
referencia cultural actual está establecida en turco (Turquía), el motor de expresiones regulares es capaz de
encontrar coincidencias con “FILE” y “file” y bloquear el acceso al recurso de archivo.

CultureInfo defaultCulture = Thread.CurrentThread.CurrentCulture;


Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");

string input = "file://c:/Documents.MyReport.doc";


string pattern = "FILE://";

Console.WriteLine("Culture-insensitive matching...");
if (Regex.IsMatch(input, pattern,
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
Console.WriteLine("URLs that access files are not allowed.");
else
Console.WriteLine("Access to {0} is allowed.", input);

Thread.CurrentThread.CurrentCulture = defaultCulture;
// The example displays the following output:
// Culture-insensitive matching...
// URLs that access files are not allowed.

Dim defaultCulture As CultureInfo = Thread.CurrentThread.CurrentCulture


Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")

Dim input As String = "file://c:/Documents.MyReport.doc"


Dim pattern As String = "$FILE://"

Console.WriteLine("Culture-insensitive matching...")
If Regex.IsMatch(input, pattern, _
RegexOptions.IgnoreCase Or RegexOptions.CultureInvariant) Then
Console.WriteLine("URLs that access files are not allowed.")
Else
Console.WriteLine("Access to {0} is allowed.", input)
End If
Thread.CurrentThread.CurrentCulture = defaultCulture
' The example displays the following output:
' Culture-insensitive matching...
' URLs that access files are not allowed.

Vea también
Lenguaje de expresiones regulares: referencia rápida
Construcciones misceláneas en expresiones regulares
16/09/2020 • 11 minutes to read • Edit Online

Las expresiones regulares en .NET incluyen tres construcciones de lenguaje misceláneas. Una permite habilitar o
deshabilitar opciones de coincidencia determinadas en medio de un patrón de expresión regular. Las otras dos
permiten incluir comentarios en una expresión regular.

Opciones insertadas
Puede establecer o deshabilitar opciones de coincidencia de patrones específicas para una parte de una expresión
regular mediante la sintaxis
(?imnsx-imnsx)

Indique las opciones que quiere habilitar después del signo de interrogación y las opciones que quiere
deshabilitar después del signo menos. En la siguiente tabla se describe cada una de las opciones. Para obtener
más información sobre cada opción, consulte Opciones de expresiones regulares.

O P C IÓ N DESC RIP C IÓ N

i Coincidencia sin distinción entre mayúsculas y minúsculas.

m Modo multilínea.

n Solo capturas explícitas. (Los paréntesis no actúan como


grupos de capturas).

s Modo de una sola línea.

x Se omite el espacio en blanco sin escape y se permiten los


comentarios en modo X.

Cualquier cambio en las opciones de expresión regular definido mediante la construcción (?imnsx-imnsx)
permanece en vigor hasta el final del grupo envolvente.

NOTE
La construcción de agrupamiento (?imnsx-imnsx: subexpresión ) proporciona una funcionalidad idéntica para una
subexpresión. Para obtener más información, consulte Construcciones de agrupamiento.

En el ejemplo siguiente se usan las opciones i , n y x para habilitar las capturas explícitas y la opción que no
hace distinción entre mayúsculas y minúsculas, y para omitir el espacio en blanco del patrón de expresión regular
en medio de una expresión regular.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern;
string input = "double dare double Double a Drooling dog The Dreaded Deep";

pattern = @"\b(D\w+)\s(d\w+)\b";
// Match pattern using default options.
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine(match.Value);
if (match.Groups.Count > 1)
for (int ctr = 1; ctr < match.Groups.Count; ctr++)
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups[ctr].Value);
}
Console.WriteLine();

// Change regular expression pattern to include options.


pattern = @"\b(D\w+)(?ixn) \s (d\w+) \b";
// Match new pattern with options.
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine(match.Value);
if (match.Groups.Count > 1)
for (int ctr = 1; ctr < match.Groups.Count; ctr++)
Console.WriteLine(" Group {0}: '{1}'", ctr, match.Groups[ctr].Value);
}
}
}
// The example displays the following output:
// Drooling dog
// Group 1: Drooling
// Group 2: dog
//
// Drooling dog
// Group 1: 'Drooling'
// Dreaded Deep
// Group 1: 'Dreaded'
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String
Dim input As String = "double dare double Double a Drooling dog The Dreaded Deep"

pattern = "\b(D\w+)\s(d\w+)\b"
' Match pattern using default options.
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
If match.Groups.Count > 1 Then
For ctr As Integer = 1 To match.Groups.Count - 1
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups(ctr).Value)
Next
End If
Next
Console.WriteLine()

' Change regular expression pattern to include options.


pattern = "\b(D\w+)(?ixn) \s (d\w+) \b"
' Match new pattern with options.
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
If match.Groups.Count > 1 Then
For ctr As Integer = 1 To match.Groups.Count - 1
Console.WriteLine(" Group {0}: '{1}'", ctr, match.Groups(ctr).Value)
Next
End If
Next
End Sub
End Module
' The example displays the following output:
' Drooling dog
' Group 1: Drooling
' Group 2: dog
'
' Drooling dog
' Group 1: 'Drooling'
' Dreaded Deep
' Group 1: 'Dreaded'

En el ejemplo se definen dos expresiones regulares. La primera, \b(D\w+)\s(d\w+)\b , coincide con dos palabras
consecutivas que empiezan con una "D" mayúscula y una "d" minúscula. La segunda expresión regular,
\b(D\w+)(?ixn) \s (d\w+) \b , usa opciones insertadas para modificar este patrón, como se describe en la tabla
siguiente. Una comparación de los resultados confirma los efectos de la construcción (?ixn) .

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

(D\w+) Coincide con una "D" mayúscula seguida de uno o más


caracteres de palabra. Este es el primer grupo de capturas.

(?ixn) A partir de este punto, hace comparaciones sin distinción


entre mayúsculas y minúsculas, solo hace capturas explícitas y
omite el espacio en blanco del patrón de expresión regular.

\s Coincide con un carácter de espacio en blanco.


M O DELO DESC RIP C IÓ N

(d\w+) Coincide con una "d" mayúscula o minúscula seguida de uno


o más caracteres de palabra. Este grupo no se captura
porque se ha habilitado la opción n (captura explícita).

\b Coincide con un límite de palabras.

Comentario alineado
La construcción (?# comment ) permite incluir un comentario alineado en una expresión regular. El motor de
expresiones regulares no usa ninguna parte del comentario en la coincidencia de patrones, aunque el comentario
se incluye en la cadena devuelta por el método Regex.ToString. El comentario termina en el primer paréntesis de
cierre.
En el ejemplo siguiente se repite el primer patrón de expresión regular del ejemplo de la sección anterior. Se
agregan dos comentarios alineados en la expresión regular para indicar si la comparación distingue entre
mayúsculas y minúsculas. El patrón de expresión regular,
\b((?# case-sensitive comparison)D\w+)\s(?ixn)((?#case-insensitive comparison)d\w+)\b , se define como se indica
a continuación.

M O DELO DESC RIP C IÓ N

\b Empieza en un límite de palabras.

(?# case-sensitive comparison) Comentario. No afecta al comportamiento de la coincidencia


de patrones.

(D\w+) Coincide con una "D" mayúscula seguida de uno o más


caracteres de palabra. Este es el primer grupo de captura.

\s Coincide con un carácter de espacio en blanco.

(?ixn) A partir de este punto, hace comparaciones sin distinción


entre mayúsculas y minúsculas, solo hace capturas explícitas y
omite el espacio en blanco del patrón de expresión regular.

(?#case-insensitive comparison) Comentario. No afecta al comportamiento de la coincidencia


de patrones.

(d\w+) Coincide con una "d" mayúscula o minúscula seguida de uno


o más caracteres de palabra. Este es el segundo grupo de
capturas.

\b Coincide con un límite de palabras.


using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b((?# case-sensitive comparison)D\w+)\s(?ixn)((?#case-insensitive
comparison)d\w+)\b";
Regex rgx = new Regex(pattern);
string input = "double dare double Double a Drooling dog The Dreaded Deep";

Console.WriteLine("Pattern: " + pattern.ToString());


// Match pattern using default options.
foreach (Match match in rgx.Matches(input))
{
Console.WriteLine(match.Value);
if (match.Groups.Count > 1)
{
for (int ctr = 1; ctr <match.Groups.Count; ctr++)
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups[ctr].Value);
}
}
}
}
// The example displays the following output:
// Pattern: \b((?# case-sensitive comparison)D\w+)\s(?ixn)((?#case-insensitive comp
// arison)d\w+)\b
// Drooling dog
// Group 1: Drooling
// Dreaded Deep
// Group 1: Dreaded

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b((?# case-sensitive comparison)D\w+)\s(?ixn)((?#case-insensitive
comparison)d\w+)\b"
Dim rgx As New Regex(pattern)
Dim input As String = "double dare double Double a Drooling dog The Dreaded Deep"

Console.WriteLine("Pattern: " + pattern.ToString())


' Match pattern using default options.
For Each match As Match In rgx.Matches(input)
Console.WriteLine(match.Value)
If match.Groups.Count > 1 Then
For ctr As Integer = 1 To match.Groups.Count - 1
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups(ctr).Value)
Next
End If
Next
End Sub
End Module
' The example displays the following output:
' Pattern: \b((?# case-sensitive comparison)D\w+)\s(?ixn)((?#case-insensitive comp
' arison)d\w+)\b
' Drooling dog
' Group 1: Drooling
' Dreaded Deep
' Group 1: Dreaded

Comentario de final de línea


Un signo de número ( # ) marca un comentario en modo X, que empieza en el carácter # sin escape al final del
patrón de expresión regular y continúa hasta el final de la línea. Para usar esta construcción, debe habilitar la
opción x (mediante opciones insertadas) o proporcionar el valor RegexOptions.IgnorePatternWhitespace al
parámetro option al crear una instancia del objeto Regex o al llamar al método Regex estático.
En el ejemplo siguiente se muestra la construcción de comentario de final de línea. Determina si una cadena es
una cadena de formato compuesto que incluye al menos un elemento de formato. En la tabla siguiente se
describe la construcción en el patrón de expresión regular:
\{\d+(,-*\d+)*(\:\w{1,4}?)*\}(?x) # Looks for a composite format item.

M O DELO DESC RIP C IÓ N

\{ Coincide con una llave de apertura.

\d+ Buscar coincidencias con uno o más dígitos decimales.

(,-*\d+)* Coincide con cero o una aparición de una coma seguida de


un signo menos opcional, seguido de uno o más dígitos
decimales.

(\:\w{1,4}?)* Coincide con cero o una aparición de un signo de dos puntos


seguido de uno a cuatro caracteres de espacio en blanco,
pero el menor número posible.

\} Coincide con una llave de cierre.

(?x) Habilita la opción de ignorar el espacio en blanco del patrón


para que se reconozca el comentario de final de línea.

# Looks for a composite format item. Comentario de final de línea.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\{\d+(,-*\d+)*(\:\w{1,4}?)*\}(?x) # Looks for a composite format item.";
string input = "{0,-3:F}";
Console.WriteLine("'{0}':", input);
if (Regex.IsMatch(input, pattern))
Console.WriteLine(" contains a composite format item.");
else
Console.WriteLine(" does not contain a composite format item.");
}
}
// The example displays the following output:
// '{0,-3:F}':
// contains a composite format item.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\{\d+(,-*\d+)*(\:\w{1,4}?)*\}(?x) # Looks for a composite format item."
Dim input As String = "{0,-3:F}"
Console.WriteLine("'{0}':", input)
If Regex.IsMatch(input, pattern) Then
Console.WriteLine(" contains a composite format item.")
Else
Console.WriteLine(" does not contain a composite format item.")
End If
End Sub
End Module
' The example displays the following output:
' '{0,-3:F}':
' contains a composite format item.

Tenga en cuenta que, en lugar de proporcionar la construcción (?x) en la expresión regular, el comentario
también podía haberse reconocido llamando al método Regex.IsMatch(String, String, RegexOptions) y pasando el
valor de enumeración RegexOptions.IgnorePatternWhitespace.

Vea también
Lenguaje de expresiones regulares: referencia rápida
Procedimientos recomendados con expresiones
regulares en .NET
16/09/2020 • 64 minutes to read • Edit Online

El motor de expresiones regulares de .NET es una herramienta eficaz y completa que procesa texto basándose en
coincidencias de patrones en lugar de comparar y buscar coincidencias con texto literal. En la mayoría de los casos,
realiza la coincidencia de modelos de manera rápida y eficaz. Sin embargo, en algunos casos, puede parecer que el
motor de expresiones regulares es muy lento. En casos extremos, incluso puede parecer que deja de responder
mientras procesa una entrada relativamente pequeña a lo largo de las horas o incluso los días.
En este tema se describen algunos de los procedimientos recomendados que los desarrolladores pueden adoptar
para garantizar que sus expresiones regulares alcancen un rendimiento óptimo.

WARNING
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de
expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions y provocar un ataque por
denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions pasan un tiempo de
expiración.

Considerar el origen de entrada


En general, las expresiones regulares pueden aceptar dos tipos de datos de entrada: restringidos o sin restricciones.
La entrada restringida es texto que se origina en una fuente conocida o confiable y sigue un formato predefinido.
La entrada sin restricciones es texto que se origina en un origen no confiable, como un usuario web, y puede no
seguir un formato predefinido o esperado.
Los patrones de expresiones regulares se suelen escribir para buscar coincidencias con entradas válidas. Es decir,
los desarrolladores examinan el texto que desean buscar y escriben un patrón de expresión regular que coincida
con él. A continuación, los desarrolladores determinan si este patrón necesita alguna corrección o algún
procesamiento adicional probándolo con varios elementos de entrada válidos. Cuando el modelo coincide con
todas las entradas válidas previstas, se declara que está listo para producción y se puede incluir en una aplicación
comercial. Esto hace que un patrón de expresión regular sea adecuado para entradas restringidas coincidentes. Sin
embargo, no es adecuado para datos de entrada sin restricciones coincidentes.
Para buscar coincidencias con datos de entrada sin restricciones, una expresión regular debe poder administrar
eficazmente tres clases de texto:
Texto que coincide con el patrón de expresión regular.
Texto que no coincide con el patrón de expresión regular.
Texto que casi coincide con el patrón de expresión regular.
El último tipo de texto es especialmente problemático para una expresión regular que se ha escrito para tratar
datos de entrada restringidos. Si esa expresión regular también usa mucho retroceso, el motor de expresiones
regulares puede dedicar una cantidad de tiempo excesiva (en algunos casos, muchas horas o días) procesando
texto aparentemente inofensivo.
WARNING
En el ejemplo siguiente se utiliza una expresión regular que es propensa a un retroceso excesivo y que es probable que
rechace direcciones de correo electrónico válidas. No debería utilizarse en una rutina de validación de correo electrónico. Si
desea que una expresión regular valide las direcciones de correo electrónico, vea Procedimiento: Comprobación de que las
cadenas están en un formato de correo electrónico válido.

Por ejemplo, considere una expresión regular de uso muy frecuente pero sumamente problemática para validar el
alias de una dirección de correo electrónico. Se escribe la expresión regular ^[0-9A-Z]([-.\w]*[0-9A-Z])*$ para
procesar qué se considera una dirección de correo electrónico válida, que consta de un carácter alfanumérico
seguido de cero o más caracteres que pueden ser alfanuméricos, puntos o guiones. La expresión regular debe
finalizar con un carácter alfanumérico. Sin embargo, como se muestra en el ejemplo siguiente, aunque esta
expresión regular trata la entrada válida fácilmente, su rendimiento es muy ineficaz cuando está procesando datos
de entrada casi válidos.

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
Stopwatch sw;
string[] addresses = { "AAAAAAAAAAA@contoso.com",
"AAAAAAAAAAaaaaaaaaaa!@contoso.com" };
// The following regular expression should not actually be used to
// validate an email address.
string pattern = @"^[0-9A-Z]([-.\w]*[0-9A-Z])*$";
string input;

foreach (var address in addresses) {


string mailBox = address.Substring(0, address.IndexOf("@"));
int index = 0;
for (int ctr = mailBox.Length - 1; ctr >= 0; ctr--) {
index++;

input = mailBox.Substring(ctr, index);


sw = Stopwatch.StartNew();
Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
sw.Stop();
if (m.Success)
Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
index, m.Value, sw.Elapsed);
else
Console.WriteLine("{0,2}. Failed '{1,25}' in {2}",
index, input, sw.Elapsed);
}
Console.WriteLine();
}
}
}

// The example displays output similar to the following:


// 1. Matched ' A' in 00:00:00.0007122
// 2. Matched ' AA' in 00:00:00.0000282
// 3. Matched ' AAA' in 00:00:00.0000042
// 4. Matched ' AAAA' in 00:00:00.0000038
// 5. Matched ' AAAAA' in 00:00:00.0000042
// 6. Matched ' AAAAAA' in 00:00:00.0000042
// 7. Matched ' AAAAAAA' in 00:00:00.0000042
// 8. Matched ' AAAAAAAA' in 00:00:00.0000087
// 9. Matched ' AAAAAAAAA' in 00:00:00.0000045
// 10. Matched ' AAAAAAAAAA' in 00:00:00.0000045
// 11. Matched ' AAAAAAAAAAA' in 00:00:00.0000045
//
// 1. Failed ' !' in 00:00:00.0000447
// 2. Failed ' a!' in 00:00:00.0000071
// 3. Failed ' aa!' in 00:00:00.0000071
// 4. Failed ' aaa!' in 00:00:00.0000061
// 5. Failed ' aaaa!' in 00:00:00.0000081
// 6. Failed ' aaaaa!' in 00:00:00.0000126
// 7. Failed ' aaaaaa!' in 00:00:00.0000359
// 8. Failed ' aaaaaaa!' in 00:00:00.0000414
// 9. Failed ' aaaaaaaa!' in 00:00:00.0000758
// 10. Failed ' aaaaaaaaa!' in 00:00:00.0001462
// 11. Failed ' aaaaaaaaaa!' in 00:00:00.0002885
// 12. Failed ' Aaaaaaaaaaa!' in 00:00:00.0005780
// 13. Failed ' AAaaaaaaaaaa!' in 00:00:00.0011628
// 14. Failed ' AAAaaaaaaaaaa!' in 00:00:00.0022851
// 15. Failed ' AAAAaaaaaaaaaa!' in 00:00:00.0045864
// 16. Failed ' AAAAAaaaaaaaaaa!' in 00:00:00.0093168
// 17. Failed ' AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
// 18. Failed ' AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
// 19. Failed ' AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
// 20. Failed ' AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
// 21. Failed ' AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372

Imports System.Diagnostics
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim sw As Stopwatch
Dim addresses() As String = {"AAAAAAAAAAA@contoso.com",
"AAAAAAAAAAaaaaaaaaaa!@contoso.com"}
' The following regular expression should not actually be used to
' validate an email address.
Dim pattern As String = "^[0-9A-Z]([-.\w]*[0-9A-Z])*$"
Dim input As String

For Each address In addresses


Dim mailBox As String = address.Substring(0, address.IndexOf("@"))
Dim index As Integer = 0
For ctr As Integer = mailBox.Length - 1 To 0 Step -1
index += 1
input = mailBox.Substring(ctr, index)
sw = Stopwatch.StartNew()
Dim m As Match = Regex.Match(input, pattern, RegexOptions.IgnoreCase)
sw.Stop()
if m.Success Then
Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
index, m.Value, sw.Elapsed)
Else
Console.WriteLine("{0,2}. Failed '{1,25}' in {2}",
index, input, sw.Elapsed)
End If
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays output similar to the following:
' 1. Matched ' A' in 00:00:00.0007122
' 2. Matched ' AA' in 00:00:00.0000282
' 3. Matched ' AAA' in 00:00:00.0000042
' 4. Matched ' AAAA' in 00:00:00.0000038
' 5. Matched ' AAAAA' in 00:00:00.0000042
' 6. Matched ' AAAAAA' in 00:00:00.0000042
' 7. Matched ' AAAAAAA' in 00:00:00.0000042
' 8. Matched ' AAAAAAAA' in 00:00:00.0000087
' 8. Matched ' AAAAAAAA' in 00:00:00.0000087
' 9. Matched ' AAAAAAAAA' in 00:00:00.0000045
' 10. Matched ' AAAAAAAAAA' in 00:00:00.0000045
' 11. Matched ' AAAAAAAAAAA' in 00:00:00.0000045
'
' 1. Failed ' !' in 00:00:00.0000447
' 2. Failed ' a!' in 00:00:00.0000071
' 3. Failed ' aa!' in 00:00:00.0000071
' 4. Failed ' aaa!' in 00:00:00.0000061
' 5. Failed ' aaaa!' in 00:00:00.0000081
' 6. Failed ' aaaaa!' in 00:00:00.0000126
' 7. Failed ' aaaaaa!' in 00:00:00.0000359
' 8. Failed ' aaaaaaa!' in 00:00:00.0000414
' 9. Failed ' aaaaaaaa!' in 00:00:00.0000758
' 10. Failed ' aaaaaaaaa!' in 00:00:00.0001462
' 11. Failed ' aaaaaaaaaa!' in 00:00:00.0002885
' 12. Failed ' Aaaaaaaaaaa!' in 00:00:00.0005780
' 13. Failed ' AAaaaaaaaaaa!' in 00:00:00.0011628
' 14. Failed ' AAAaaaaaaaaaa!' in 00:00:00.0022851
' 15. Failed ' AAAAaaaaaaaaaa!' in 00:00:00.0045864
' 16. Failed ' AAAAAaaaaaaaaaa!' in 00:00:00.0093168
' 17. Failed ' AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
' 18. Failed ' AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
' 19. Failed ' AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
' 20. Failed ' AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
' 21. Failed ' AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372

Como muestra el resultado del ejemplo, el motor de expresiones regulares procesa el alias válido de correo
electrónico casi en el mismo intervalo de tiempo independientemente de su longitud. Por otra parte, cuando la
dirección de correo electrónico casi válida tiene más de cinco caracteres, el tiempo de procesamiento se duplica
aproximadamente por cada carácter de la cadena. Esto significa que una cadena casi válida de 28 caracteres
tardaría más de una hora en procesarse y una cadena casi válida de 33 caracteres tardaría casi un día en
procesarse.
Como esta expresión regular se desarrolló teniendo en cuenta solamente el formato de entrada que había que
hacer coincidir, no tiene en cuenta los datos de entrada que no coinciden con el patrón. A su vez, esto puede
permitir que unos datos de entrada sin restricciones que casi coinciden con el patrón de expresión regular reduzcan
considerablemente el rendimiento.
Para resolver este problema, puede hacer lo siguiente:
A la hora de desarrollar un modelo, debe considerar cómo puede afectar el retroceso al rendimiento del
motor de expresiones regulares, especialmente si la expresión regular está diseñada para procesar datos de
entrada sin restricciones. Para obtener más información, consulte la sección Controlar el retroceso.
Probar exhaustivamente la expresión regular usando datos de entrada no válidos y casi válidos, así como
datos de entrada válidos. Para generar de forma aleatoria la entrada para una expresión regular
determinada, puede usar Rex, que es una herramienta de exploración de expresiones regulares de Microsoft
Research.

Controlar la creación de instancias de objeto correctamente


El núcleo del modelo de objetos de expresiones regulares de .NET es la clase System.Text.RegularExpressions.Regex,
que representa el motor de expresiones regulares. A menudo, el mayor factor único que afecta al rendimiento de
las expresiones regulares es la manera en que se emplea el motor de Regex. La definición de una expresión regular
implica acoplar estrechamente el motor de expresiones regulares con un patrón de expresión regular. Ese proceso
de acoplamiento, tanto si consiste en crear una instancia de un objeto Regex pasando a su constructor una
expresión regular como en llamar a un método estático pasándole el patrón de expresión regular junto con la
cadena que se va a analizar, es necesariamente costoso.
NOTE
Para obtener una explicación más detallada de las implicaciones sobre el rendimiento de usar expresiones regulares
interpretadas y compiladas, vea Optimizing Regular Expression Performance, Part II: (Optimización del rendimiento de
expresiones regulares, Parte II: Control del retroceso) en el blog del equipo de BCL.

Puede acoplar el motor de expresiones regulares con un determinado patrón de expresión regular y, a
continuación, usar el motor para buscar coincidencias con texto de varias maneras:
Puede llamar a un método estático de coincidencia de patrones como Regex.Match(String, String). Para ello
no es necesario crear instancias de un objeto de expresión regular.
Puede crear instancias de un objeto Regex y llamar a una instancia de un método de coincidencia de
modelos de una expresión regular interpretada. Este es el método predeterminado para enlazar el motor de
expresiones regulares a un patrón de expresión regular. Se produce cuando se crea una instancia de un
objeto Regex sin un argumento options que incluya la marca Compiled.
Puede crear instancias de un objeto Regex y llamar a una instancia de un método de coincidencia de
modelos de una expresión regular compilada. Los objetos de expresiones regulares representan modelos
compilados cuando se crea una instancia de un objeto Regex con un argumento options que incluye la
marca Compiled.
Puede crear un objeto Regex especial que esté acoplado estrechamente con un determinado patrón de
expresión regular, compilarlo y guardarlo en un ensamblado independiente. Puede hacerlo llamando al
método Regex.CompileToAssembly.
La forma de llamar a los métodos de coincidencia de expresiones regulares puede tener un impacto significativo en
la aplicación. En las próximas secciones se explica cómo usar llamadas a métodos estáticos, expresiones regulares
interpretadas y expresiones regulares compiladas para mejorar el rendimiento de la aplicación.

IMPORTANT
El formato de la llamada al método (estático, interpretado o compilado) afecta al rendimiento si la misma expresión regular se
usa repetidamente en llamadas a métodos o si una aplicación usa muchos objetos de expresiones regulares.

Expresiones regulares estáticas


Se recomienda el uso de métodos de expresiones regulares estáticas como alternativa a crear repetidamente
instancias de un objeto de expresión regular con la misma expresión regular. A diferencia de los patrones de
expresiones regulares usados por los objetos de expresiones regulares, el motor de expresiones regulares
almacena internamente en memoria caché los códigos de operación o el lenguaje intermedio de Microsoft (MSIL)
compilado de los patrones empleados en las llamadas al método estático.
Por ejemplo, un controlador de eventos llama con frecuencia a otro método para validar los datos proporcionados
por el usuario. Esto se refleja en el código siguiente, en el que se usa el evento Button de un control Click para
llamar a un método denominado IsValidCurrency , que comprueba si el usuario ha escrito un símbolo de moneda
seguido al menos de un dígito decimal.
public void OKButton_Click(object sender, EventArgs e)
{
if (! String.IsNullOrEmpty(sourceCurrency.Text))
if (RegexLib.IsValidCurrency(sourceCurrency.Text))
PerformConversion();
else
status.Text = "The source currency value is invalid.";
}

Public Sub OKButton_Click(sender As Object, e As EventArgs) _


Handles OKButton.Click

If Not String.IsNullOrEmpty(sourceCurrency.Text) Then


If RegexLib.IsValidCurrency(sourceCurrency.Text) Then
PerformConversion()
Else
status.Text = "The source currency value is invalid."
End If
End If
End Sub

En el ejemplo siguiente se muestra una implementación muy poco eficaz del método IsValidCurrency . Observe
que cada llamada al método vuelve a crear una instancia de un objeto Regex con el mismo modelo. Esto, a su vez,
significa que el patrón de expresión regular se debe volver a compilar cada vez que se llama al método.

using System;
using System.Text.RegularExpressions;

public class RegexLib


{
public static bool IsValidCurrency(string currencyValue)
{
string pattern = @"\p{Sc}+\s*\d+";
Regex currencyRegex = new Regex(pattern);
return currencyRegex.IsMatch(currencyValue);
}
}

Imports System.Text.RegularExpressions

Public Module RegexLib


Public Function IsValidCurrency(currencyValue As String) As Boolean
Dim pattern As String = "\p{Sc}+\s*\d+"
Dim currencyRegex As New Regex(pattern)
Return currencyRegex.IsMatch(currencyValue)
End Function
End Module

Debe reemplazar este código ineficaz con una llamada al método estático Regex.IsMatch(String, String). Esto
elimina la necesidad de crear instancias de un objeto Regex cada vez que desea llamar a un método de coincidencia
de modelos y permite que el motor de expresiones regulares recupere una versión compilada de la expresión
regular de su memoria caché.
using System;
using System.Text.RegularExpressions;

public class RegexLib


{
public static bool IsValidCurrency(string currencyValue)
{
string pattern = @"\p{Sc}+\s*\d+";
return Regex.IsMatch(currencyValue, pattern);
}
}

Imports System.Text.RegularExpressions

Public Module RegexLib


Public Function IsValidCurrency(currencyValue As String) As Boolean
Dim pattern As String = "\p{Sc}+\s*\d+"
Return Regex.IsMatch(currencyValue, pattern)
End Function
End Module

De forma predeterminada, se almacenan en caché los 15 últimos patrones de expresiones regulares estáticas
usados recientemente. En el caso de las aplicaciones que necesitan un mayor número de expresiones regulares
estáticas almacenadas en caché, el tamaño de la memoria caché se puede ajustar estableciendo la propiedad
Regex.CacheSize.
La expresión regular \p{Sc}+\s*\d+ que se usa en este ejemplo comprueba que la cadena de entrada consta de un
símbolo de moneda y al menos un dígito decimal. El patrón se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\p{Sc}+ Buscar coincidencias con uno o más caracteres de la categoría


Símbolo Unicode, Moneda.

\s* Busca coincidencias con cero o más caracteres de espacio en


blanco.

\d+ Buscar coincidencias con uno o más dígitos decimales.

Expresiones regulares interpretadas y compiladas


Los patrones de expresiones regulares que no están enlazados al motor de expresiones regulares mediante la
especificación de la opción Compiled se interpretan. Cuando se crea una instancia de un objeto de expresión
regular, el motor de expresiones regulares convierte la expresión regular en un conjunto de códigos de operación.
Cuando se llama a un método de instancia, el compilador JIT ejecuta y convierte a MSIL los códigos de operación.
Del mismo modo, cuando se llama a un método estático de expresión regular y la expresión regular no se
encuentra en la memoria caché, el motor de expresiones regulares convierte la expresión regular en un conjunto de
códigos de operación y los almacena en memoria caché. A continuación, convierte estos códigos de operación a
MSIL para que el compilador JIT pueda ejecutarlos. Las expresiones regulares interpretadas reducen el tiempo de
inicio a costa de un tiempo de ejecución más lento. Por eso, son más adecuadas cuando la expresión regular se usa
en un número reducido de llamadas a métodos o si el número exacto de llamadas a métodos de expresión regular
es desconocido pero se espera que sea pequeño. A medida que aumenta el número de llamadas al método, la
mejora de rendimiento del tiempo de inicio reducido queda oscurecida por una velocidad de ejecución más lenta.
Los patrones de expresiones regulares que están enlazados al motor de expresiones regulares mediante la
especificación de la opción Compiled se compilan. Esto significa que, cuando se crea una instancia de un objeto de
expresión regular, o cuando se llama a un método estático de expresión regular y la expresión regular no se
encuentra en la memoria caché, el motor de expresiones regulares convierte la expresión regular a un conjunto
intermedio de códigos de operación que, a continuación, convierte a MSIL. Cuando se llama a un método, el
compilador JIT ejecuta el código MSIL. A diferencia de las expresiones regulares interpretadas, las expresiones
regulares compiladas aumentan el tiempo de inicio pero ejecutan más deprisa los métodos individuales de
coincidencia de patrones. Por tanto, la ventaja de rendimiento resultante de compilar la expresión regular aumenta
en proporción al número de métodos de expresiones regulares llamados.
En resumen, se recomienda usar expresiones regulares interpretadas al llamar a métodos de expresión regular con
una expresión regular concreta con poca frecuencia relativamente. Debe usar expresiones regulares compiladas al
llamar a métodos de expresión regular con una expresión regular concreta con relativa frecuencia. Es difícil
determinar el umbral exacto en el que las velocidades de ejecución más lentas de las expresiones regulares
interpretadas superan las mejoras de su menor tiempo de inicio, o el umbral en el que los tiempos de inicio más
lentos de las expresiones regulares compiladas superan las mejoras de sus velocidades de ejecución más rápidas.
Depende de diversos factores, como la complejidad de la expresión regular y los datos específicos que procesa.
Para determinar si las expresiones regulares interpretadas o compiladas ofrecen el mejor rendimiento para su
escenario de aplicación concreto, puede usar la clase Stopwatch para comparar sus tiempos de ejecución.
En el ejemplo siguiente, se compara el rendimiento de las expresiones regulares compiladas e interpretadas al leer
las diez primeras frases y al leer todas las frases del texto The Financier de Theodore Dreiser. Como muestra el
resultado del ejemplo, cuando solo se realizan diez llamadas a métodos de coincidencia de expresión regular, una
expresión regular interpreta proporciona un rendimiento mejor que una expresión regular compilada. Sin
embargo, una expresión regular compilada ofrece mejor rendimiento cuando se realiza un gran número de
llamadas (en este caso, más de 13000).

using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
Stopwatch sw;
Match match;
int ctr;

StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");


string input = inFile.ReadToEnd();
inFile.Close();

// Read first ten sentences with interpreted regex.


Console.WriteLine("10 Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex int10 = new Regex(pattern, RegexOptions.Singleline);
match = int10.Match(input);
for (ctr = 0; ctr <= 9; ctr++) {
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine(" {0} matches in {1}", ctr, sw.Elapsed);

// Read first ten sentences with compiled regex.


Console.WriteLine("10 Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
Regex comp10 = new Regex(pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
RegexOptions.Singleline | RegexOptions.Compiled);
match = comp10.Match(input);
for (ctr = 0; ctr <= 9; ctr++) {
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine(" {0} matches in {1}", ctr, sw.Elapsed);

// Read all sentences with interpreted regex.


Console.WriteLine("All Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex intAll = new Regex(pattern, RegexOptions.Singleline);
match = intAll.Match(input);
int matches = 0;
while (match.Success) {
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed);

// Read all sentences with compiled regex.


Console.WriteLine("All Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
Regex compAll = new Regex(pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
match = compAll.Match(input);
matches = 0;
while (match.Success) {
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed);
}
}
// The example displays the following output:
// 10 Sentences with Interpreted Regex:
// 10 matches in 00:00:00.0047491
// 10 Sentences with Compiled Regex:
// 10 matches in 00:00:00.0141872
// All Sentences with Interpreted Regex:
// 13,443 matches in 00:00:01.1929928
// All Sentences with Compiled Regex:
// 13,443 matches in 00:00:00.7635869
//
// >compare1
// 10 Sentences with Interpreted Regex:
// 10 matches in 00:00:00.0046914
// 10 Sentences with Compiled Regex:
// 10 matches in 00:00:00.0143727
// All Sentences with Interpreted Regex:
// 13,443 matches in 00:00:01.1514100
// All Sentences with Compiled Regex:
// 13,443 matches in 00:00:00.7432921

Imports System.Diagnostics
Imports System.IO
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]"
Dim sw As Stopwatch
Dim match As Match
Dim ctr As Integer

Dim inFile As New StreamReader(".\Dreiser_TheFinancier.txt")


Dim input As String = inFile.ReadToEnd()
inFile.Close()

' Read first ten sentences with interpreted regex.


Console.WriteLine("10 Sentences with Interpreted Regex:")
sw = Stopwatch.StartNew()
Dim int10 As New Regex(pattern, RegexOptions.SingleLine)
match = int10.Match(input)
For ctr = 0 To 9
If match.Success Then
' Do nothing with the match except get the next match.
match = match.NextMatch()
Else
Exit For
End If
Next
sw.Stop()
Console.WriteLine(" {0} matches in {1}", ctr, sw.Elapsed)

' Read first ten sentences with compiled regex.


Console.WriteLine("10 Sentences with Compiled Regex:")
sw = Stopwatch.StartNew()
Dim comp10 As New Regex(pattern,
RegexOptions.SingleLine Or RegexOptions.Compiled)
match = comp10.Match(input)
For ctr = 0 To 9
If match.Success Then
' Do nothing with the match except get the next match.
match = match.NextMatch()
Else
Exit For
End If
Next
sw.Stop()
Console.WriteLine(" {0} matches in {1}", ctr, sw.Elapsed)

' Read all sentences with interpreted regex.


Console.WriteLine("All Sentences with Interpreted Regex:")
sw = Stopwatch.StartNew()
Dim intAll As New Regex(pattern, RegexOptions.SingleLine)
match = intAll.Match(input)
Dim matches As Integer = 0
Do While match.Success
matches += 1
' Do nothing with the match except get the next match.
match = match.NextMatch()
Loop
sw.Stop()
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed)

' Read all sentences with compiled regex.


Console.WriteLine("All Sentences with Compiled Regex:")
sw = Stopwatch.StartNew()
Dim compAll As New Regex(pattern,
RegexOptions.SingleLine Or RegexOptions.Compiled)
match = compAll.Match(input)
matches = 0
Do While match.Success
matches += 1
' Do nothing with the match except get the next match.
match = match.NextMatch()
Loop
sw.Stop()
sw.Stop()
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed)
End Sub
End Module
' The example displays output like the following:
' 10 Sentences with Interpreted Regex:
' 10 matches in 00:00:00.0047491
' 10 Sentences with Compiled Regex:
' 10 matches in 00:00:00.0141872
' All Sentences with Interpreted Regex:
' 13,443 matches in 00:00:01.1929928
' All Sentences with Compiled Regex:
' 13,443 matches in 00:00:00.7635869
'
' >compare1
' 10 Sentences with Interpreted Regex:
' 10 matches in 00:00:00.0046914
' 10 Sentences with Compiled Regex:
' 10 matches in 00:00:00.0143727
' All Sentences with Interpreted Regex:
' 13,443 matches in 00:00:01.1514100
' All Sentences with Compiled Regex:
' 13,443 matches in 00:00:00.7432921

El patrón de expresión regular usado en el ejemplo, \b(\w+((\r?\n)|,?\s))*\w+[.?:;!] , se define como se muestra


en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

\w+ Buscar coincidencias con uno o más caracteres alfabéticos.

(\r?\n)|,?\s) Buscar una coincidencia con cero o un retorno de carro


seguido de un carácter de nueva línea, o cero o una coma
seguida de un carácter de espacio en blanco.

(\w+((\r?\n)|,?\s))* Buscar coincidencias con cero o más apariciones de uno o más


caracteres alfabéticos que van seguidos de cero o un retorno
de carro y un carácter de nueva línea, o de cero o una coma
seguida de un carácter de espacio en blanco.

\w+ Buscar coincidencias con uno o más caracteres alfabéticos.

[.?:;!] Buscar una coincidencia con un punto, un signo de


interrogación, dos puntos, punto y coma o un signo de
exclamación.

Expresiones regulares: compiladas en un ensamblado


.NET también permite crear un ensamblado que contenga expresiones regulares compiladas. Esto lleva la merma
de rendimiento de la compilación de la expresión regular del tiempo de ejecución al tiempo de diseño. Aunque
también implica cierto trabajo adicional: Hay que definir las expresiones regulares de antemano y compilarlas en
un ensamblado. El compilador puede hacer referencia a este ensamblado al compilar código fuente que usa
expresiones regulares del ensamblado. Cada expresión regular compilada del ensamblado está representada por
una clase que se deriva de Regex.
Para compilar expresiones regulares en un ensamblado, se llama al método
Regex.CompileToAssembly(RegexCompilationInfo[], AssemblyName) y se le pasa una matriz de objetos
RegexCompilationInfo que representan las expresiones regulares que se van a compilar y un objeto
AssemblyName que contiene información sobre el ensamblado que se va a crear.
Se recomienda compilar expresiones regulares en un ensamblado en las situaciones siguientes:
Si es un desarrollador de componentes que desea crear una biblioteca de expresiones regulares reutilizables.
Si espera que los métodos de coincidencia de modelos de la expresión regular se llamen un número
indeterminado de veces, desde una o dos veces hasta miles o decenas de miles de veces. A diferencia de las
expresiones regulares compiladas o interpretadas, las expresiones regulares que se compilan en
ensamblados independientes proporcionan un rendimiento coherente independientemente del número de
llamadas a métodos.
Si emplea expresiones regulares compiladas para optimizar el rendimiento, no debe usar la reflexión para crear el
ensamblado, cargar el motor de expresiones regulares y ejecutar sus métodos de coincidencia con modelos. Para
ello es necesario evitar la creación dinámica de expresiones regulares y especificar cualquier opción de coincidencia
de modelos (como la coincidencia de modelos sin distinción entre mayúsculas y minúsculas) en el momento en
que se crea el ensamblado. También debe separar el código que crea el ensamblado del código que usa la
expresión regular.
En el ejemplo siguiente se muestra cómo crear un ensamblado que contiene una expresión regular compilada. Crea
un ensamblado denominado RegexLib.dll con una única clase de expresión regular, SentencePattern , que
contiene el patrón de expresión regular de coincidencia con frases usado en la sección Expresiones regulares
interpretadas frente a expresiones regulares compiladas.

using System;
using System.Reflection;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
RegexCompilationInfo SentencePattern =
new RegexCompilationInfo(@"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]",
RegexOptions.Multiline,
"SentencePattern",
"Utilities.RegularExpressions",
true);
RegexCompilationInfo[] regexes = { SentencePattern };
AssemblyName assemName = new AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral,
PublicKeyToken=null");
Regex.CompileToAssembly(regexes, assemName);
}
}

Imports System.Reflection
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim SentencePattern As New RegexCompilationInfo("\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]",
RegexOptions.Multiline,
"SentencePattern",
"Utilities.RegularExpressions",
True)
Dim regexes() As RegexCompilationInfo = {SentencePattern}
Dim assemName As New AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral, PublicKeyToken=null")
Regex.CompileToAssembly(regexes, assemName)
End Sub
End Module
Cuando el ejemplo se compila en un ejecutable y se ejecuta, crea un ensamblado denominado RegexLib.dll . La
expresión regular se representa mediante una clase denominada Utilities.RegularExpressions.SentencePattern
que se deriva de Regex. En el ejemplo siguiente, se usa después la expresión regular compilada para extraer las
frases del texto The Financier de Theodore Dreiser.

using System;
using System.IO;
using System.Text.RegularExpressions;
using Utilities.RegularExpressions;

public class Example


{
public static void Main()
{
SentencePattern pattern = new SentencePattern();
StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");
string input = inFile.ReadToEnd();
inFile.Close();

MatchCollection matches = pattern.Matches(input);


Console.WriteLine("Found {0:N0} sentences.", matches.Count);
}
}
// The example displays the following output:
// Found 13,443 sentences.

Imports System.IO
Imports System.Text.RegularExpressions
Imports Utilities.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As New SentencePattern()
Dim inFile As New StreamReader(".\Dreiser_TheFinancier.txt")
Dim input As String = inFile.ReadToEnd()
inFile.Close()

Dim matches As MatchCollection = pattern.Matches(input)


Console.WriteLine("Found {0:N0} sentences.", matches.Count)
End Sub
End Module
' The example displays the following output:
' Found 13,443 sentences.

Controlar el retroceso
Normalmente, el motor de expresiones regulares usa la progresión lineal para desplazarse a través de una cadena
de entrada y compararla con un patrón de expresión regular. Sin embargo, cuando en un patrón de expresión
regular se usan cuantificadores indeterminados como * , + y ? , el motor de expresiones regulares puede
abandonar una parte de las coincidencias parciales correctas y volver a un estado guardado previamente para
buscar una coincidencia correcta de todo el patron. Este proceso se denomina retroceso.

NOTE
Para obtener más información acerca del retroceso, consulte Detalles del comportamiento de expresiones regulares y
Retroceso. Para obtener una explicación detallada del retroceso, vea Optimizing Regular Expression Performance, Part II:
(Optimización del rendimiento de expresiones regulares, Parte II: Control del retroceso) en el blog del equipo de BCL.

La compatibilidad con el retroceso aporta a las expresiones regulares eficacia y flexibilidad. También deja la
responsabilidad de controlar el funcionamiento del motor de expresiones regulares en manos de los
desarrolladores de expresiones regulares. Puesto que los desarrolladores no suelen ser conscientes de esta
responsabilidad, su uso incorrecto del retroceso o su dependencia de un retroceso excesivo suele desempeñar el
rol más significativo en la degradación del rendimiento de las expresiones regulares. En un escenario de caso peor,
el tiempo de ejecución puede duplicarse por cada carácter adicional de la cadena de entrada. De hecho, usando
excesivamente el retroceso, es fácil crear el equivalente en programación de un bucle infinito si la entrada coincide
casi con el patrón de expresiones regulares; el motor de expresiones regulares puede tardar horas o incluso días en
procesar una cadena de entrada relativamente corta.
A menudo, las aplicaciones sufren una reducción del rendimiento por usar el retroceso a pesar de que el retroceso
no es esencial para una coincidencia. Por ejemplo, la expresión regular \b\p{Lu}\w*\b busca una coincidencia con
todas las palabras que comienzan por un carácter en mayúsculas, como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

\p{Lu} Busca una coincidencia con un carácter en mayúsculas.

\w* Buscar una coincidencia con cero o más caracteres alfabéticos.

\b Finalizar la búsqueda de coincidencias en un límite de palabras.

Puesto que un límite de palabra no es igual, o un subconjunto de, que un carácter alfabético, no hay ninguna
posibilidad de que el motor de expresiones regulares cruce un límite de palabra cuando busca coincidencias con
caracteres alfabéticos. Esto significa que para esta expresión regular, el retroceso nunca puede contribuir al éxito
global de cualquier coincidencia; solo puede degradar el rendimiento, ya que se fuerza que el motor de expresiones
regulares guarde su estado para cada coincidencia preliminar correcta de un carácter alfabético.
Si determina que la vuelta atrás (backtracking) no es necesaria, puede deshabilitarla mediante el elemento de
lenguaje (?>subexpression) , conocido como grupo atómico. En el ejemplo siguiente se analiza una cadena de
entrada usando dos expresiones regulares. La primera, \b\p{Lu}\w*\b , se basa en el retroceso. La segunda,
\b\p{Lu}(?>\w*)\b , deshabilita el retroceso. Como muestra el resultado del ejemplo, ambas producen el mismo
resultado.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This this word Sentence name Capital";
string pattern = @"\b\p{Lu}\w*\b";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);

Console.WriteLine();

pattern = @"\b\p{Lu}(?>\w*)\b";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// This
// Sentence
// Capital
//
// This
// Sentence
// Capital

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This this word Sentence name Capital"
Dim pattern As String = "\b\p{Lu}\w*\b"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
Console.WriteLine()

pattern = "\b\p{Lu}(?>\w*)\b"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' This
' Sentence
' Capital
'
' This
' Sentence
' Capital

En muchos casos, el retroceso es esencial para buscar una coincidencia de un patrón de expresión regular con el
texto de entrada. Sin embargo, el retroceso excesivo puede degradar gravemente el rendimiento y dar la impresión
de que una aplicación ha dejado de responder. En concreto, esto ocurre cuando se anidan los cuantificadores y el
texto que coincide con la subexpresión externa es un subconjunto del texto que coincide con la subexpresión
interna.
WARNING
Además de evitar el retroceso excesivo, se debe utilizar la característica de tiempo de espera para garantizar que el retroceso
excesivo no reduzca gravemente el rendimiento de la expresión regular. Para obtener más información, consulte la sección
Usar valores de tiempo de espera.

Por ejemplo, el patrón de expresión regular ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ está diseñado para buscar
coincidencias con un número de pieza que contiene al menos un carácter alfanumérico. Cualquier carácter
adicional puede constar de un carácter alfanumérico, un guión, un carácter de subrayado o un punto, aunque el
último carácter debe ser alfanumérico. El número de pieza termina con un signo de dólar. En algunos casos, este
patrón de expresión regular puede presentar un rendimiento muy deficiente porque los cuantificadores están
anidados y porque la subexpresión [0-9A-Z] es un subconjunto de la subexpresión [-.\w]* .
En estos casos, puede optimizar el rendimiento de la expresión regular quitando los cuantificadores anidados y
reemplazando la subexpresión externa con una aserción de búsqueda anticipada o de búsqueda tardía de ancho
cero. Las aserciones de búsqueda anticipada y de búsqueda tardía son delimitadores; no mueven el puntero en la
cadena de entrada, sino que realizan una búsqueda hacia delante o hacia atrás para comprobar si se cumple una
condición especificada. Por ejemplo, la expresión regular de número de pieza se puede volver a escribir como
^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$ . Este patrón de expresión regular se define como se muestra en la tabla
siguiente.

M O DELO DESC RIP C IÓ N

^ Iniciar la búsqueda de coincidencias con el principio de la


cadena de entrada.

[0-9A-Z] Buscar coincidencias de un carácter alfanumérico. El número


de pieza debe constar al menos de este carácter.

[-.\w]* Buscar coincidencias con cero o más apariciones de cualquier


carácter alfabético, guión o punto.

\$ Buscar coincidencias con un signo de dólar.

(?<=[0-9A-Z]) Realizar una búsqueda anticipada del signo de dólar final para
asegurarse de que el carácter anterior es alfanumérico.

$ Finalizar la búsqueda de coincidencias al final de la cadena de


entrada.

En el ejemplo siguiente se muestra el uso de esta expresión regular para encontrar una matriz que contiene los
números de pieza posibles.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$";
string[] partNos = { "A1C$", "A4", "A4$", "A1603D$", "A1603D#" };

foreach (var input in partNos) {


Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine(match.Value);
else
Console.WriteLine("Match not found.");
}
}
}
// The example displays the following output:
// A1C$
// Match not found.
// A4$
// A1603D$
// Match not found.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$"
Dim partNos() As String = {"A1C$", "A4", "A4$", "A1603D$",
"A1603D#"}

For Each input As String In partNos


Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine(match.Value)
Else
Console.WriteLine("Match not found.")
End If
Next
End Sub
End Module
' The example displays the following output:
' A1C$
' Match not found.
' A4$
' A1603D$
' Match not found.

El lenguaje de expresiones regulares de .NET incluye los elementos del lenguaje siguientes, que puede usar para
eliminar cuantificadores anidados. Para obtener más información, consulte Construcciones de agrupamiento.

EL EM EN TO DEL L EN GUA JE DESC RIP C IÓ N

(?= subexpression ) Búsqueda anticipada positiva de ancho cero. Realizar una


búsqueda anticipada de la posición actual para determinar si
subexpression coincide con la cadena de entrada.
EL EM EN TO DEL L EN GUA JE DESC RIP C IÓ N

(?! subexpression ) Búsqueda anticipada negativa de ancho cero. Realizar una


búsqueda anticipada de la posición actual para determinar si
subexpression no coincide con la cadena de entrada.

(?<= subexpression ) Búsqueda tardía positiva de ancho cero. Realizar una búsqueda
tardía de la posición actual para determinar si
subexpression coincide con la cadena de entrada.

(?<! subexpression ) Búsqueda tardía negativa de ancho cero. Realizar una


búsqueda tardía de la posición actual para determinar si
subexpression no coincide con la cadena de entrada.

Usar valores de tiempo de espera


Si sus expresiones regulares procesan datos de entrada que prácticamente coinciden con el patrón de expresiones
regulares, normalmente se puede usar el retroceso excesivo, que afecta enormemente al rendimiento. Además de
analizar cuidadosamente el uso del retroceso y probar la expresión regular en entradas casi coincidentes, debe
establecer siempre un valor de tiempo de espera para garantizar que el impacto del retroceso excesivo, si aparece,
se reduzca al mínimo.
El intervalo de tiempo de espera de la expresión regular define el período de tiempo que el motor de expresiones
regulares buscará una coincidencia única antes de que el tiempo se agote. El intervalo de tiempo de espera
predeterminado es Regex.InfiniteMatchTimeout, que significa que no se agotará el tiempo de la expresión regular.
Puede invalidar este valor y definir un intervalo de tiempo de espera de la siguiente manera:
Proporcionando un valor de tiempo de espera al crear una instancia del objeto Regex llamando al
constructor Regex(String, RegexOptions, TimeSpan).
Llamando a un método estático de coincidencia de patrones, como Regex.Match(String, String,
RegexOptions, TimeSpan) o Regex.Replace(String, String, String, RegexOptions, TimeSpan), que incluya un
parámetro matchTimeout .
Para las expresiones regulares compiladas que se crean mediante una llamada al método
Regex.CompileToAssembly, llamando al constructor que tiene un parámetro de tipo TimeSpan.
Si ha definido un intervalo de tiempo de espera y no se encuentra ninguna coincidencia al final del intervalo, el
método de expresiones regulares produce una excepción RegexMatchTimeoutException. En el controlador de
excepciones, puede elegir reintentar la búsqueda de coincidencias con un intervalo de tiempo de espera más largo,
abandonar el intento de búsqueda de coincidencias y asumir que no hay ninguna coincidencia o abandonar el
intento de búsqueda de coincidencias y registrar la información de excepción para futuros análisis.
En el ejemplo siguiente se define un método GetWordData que crea una instancia de una expresión regular con un
tiempo de espera de 350 milisegundos para calcular el número de palabras y el promedio de caracteres de una
palabra en un documento de texto. Si se agota el tiempo de espera de la operación de coincidencia, el intervalo de
tiempo de espera aumenta en 350 milisegundos y se vuelve a crear una instancia del objeto Regex. Si el nuevo
intervalo de tiempo de espera es mayor de 1 segundo, el método vuelve a producir la excepción y se la envía al
llamador.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

public class Example


{
{
public static void Main()
{
RegexUtilities util = new RegexUtilities();
string title = "Doyle - The Hound of the Baskervilles.txt";
try {
var info = util.GetWordData(title);
Console.WriteLine("Words: {0:N0}", info.Item1);
Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2);
}
catch (IOException e) {
Console.WriteLine("IOException reading file '{0}'", title);
Console.WriteLine(e.Message);
}
catch (RegexMatchTimeoutException e) {
Console.WriteLine("The operation timed out after {0:N0} milliseconds",
e.MatchTimeout.TotalMilliseconds);
}
}
}

public class RegexUtilities


{
public Tuple<int, double> GetWordData(string filename)
{
const int MAX_TIMEOUT = 1000; // Maximum timeout interval in milliseconds.
const int INCREMENT = 350; // Milliseconds increment of timeout.

List<string> exclusions = new List<string>( new string[] { "a", "an", "the" });
int[] wordLengths = new int[29]; // Allocate an array of more than ample size.
string input = null;
StreamReader sr = null;
try {
sr = new StreamReader(filename);
input = sr.ReadToEnd();
}
catch (FileNotFoundException e) {
string msg = String.Format("Unable to find the file '{0}'", filename);
throw new IOException(msg, e);
}
catch (IOException e) {
throw new IOException(e.Message, e);
}
finally {
if (sr != null) sr.Close();
}

int timeoutInterval = INCREMENT;


bool init = false;
Regex rgx = null;
Match m = null;
int indexPos = 0;
do {
try {
if (! init) {
rgx = new Regex(@"\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval));
m = rgx.Match(input, indexPos);
init = true;
}
else {
m = m.NextMatch();
}
if (m.Success) {
if ( !exclusions.Contains(m.Value.ToLower()))
wordLengths[m.Value.Length]++;

indexPos += m.Length + 1;
}
}
}
catch (RegexMatchTimeoutException e) {
if (e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT) {
timeoutInterval += INCREMENT;
init = false;
}
else {
// Rethrow the exception.
throw;
}
}
} while (m.Success);

// If regex completed successfully, calculate number of words and average length.


int nWords = 0;
long totalLength = 0;

for (int ctr = wordLengths.GetLowerBound(0); ctr <= wordLengths.GetUpperBound(0); ctr++) {


nWords += wordLengths[ctr];
totalLength += ctr * wordLengths[ctr];
}
return new Tuple<int, double>(nWords, totalLength/nWords);
}
}

Imports System.Collections.Generic
Imports System.IO
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim util As New RegexUtilities()
Dim title As String = "Doyle - The Hound of the Baskervilles.txt"
Try
Dim info = util.GetWordData(title)
Console.WriteLine("Words: {0:N0}", info.Item1)
Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2)
Catch e As IOException
Console.WriteLine("IOException reading file '{0}'", title)
Console.WriteLine(e.Message)
Catch e As RegexMatchTimeoutException
Console.WriteLine("The operation timed out after {0:N0} milliseconds",
e.MatchTimeout.TotalMilliseconds)
End Try
End Sub
End Module

Public Class RegexUtilities


Public Function GetWordData(filename As String) As Tuple(Of Integer, Double)
Const MAX_TIMEOUT As Integer = 1000 ' Maximum timeout interval in milliseconds.
Const INCREMENT As Integer = 350 ' Milliseconds increment of timeout.

Dim exclusions As New List(Of String)({"a", "an", "the"})


Dim wordLengths(30) As Integer ' Allocate an array of more than ample size.
Dim input As String = Nothing
Dim sr As StreamReader = Nothing
Try
sr = New StreamReader(filename)
input = sr.ReadToEnd()
Catch e As FileNotFoundException
Dim msg As String = String.Format("Unable to find the file '{0}'", filename)
Throw New IOException(msg, e)
Catch e As IOException
Throw New IOException(e.Message, e)
Finally
If sr IsNot Nothing Then sr.Close()
End Try
Dim timeoutInterval As Integer = INCREMENT
Dim init As Boolean = False
Dim rgx As Regex = Nothing
Dim m As Match = Nothing
Dim indexPos As Integer = 0
Do
Try
If Not init Then
rgx = New Regex("\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval))
m = rgx.Match(input, indexPos)
init = True
Else
m = m.NextMatch()
End If
If m.Success Then
If Not exclusions.Contains(m.Value.ToLower()) Then
wordLengths(m.Value.Length) += 1
End If
indexPos += m.Length + 1
End If
Catch e As RegexMatchTimeoutException
If e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT Then
timeoutInterval += INCREMENT
init = False
Else
' Rethrow the exception.
Throw
End If
End Try
Loop While m.Success

' If regex completed successfully, calculate number of words and average length.
Dim nWords As Integer
Dim totalLength As Long

For ctr As Integer = wordLengths.GetLowerBound(0) To wordLengths.GetUpperBound(0)


nWords += wordLengths(ctr)
totalLength += ctr * wordLengths(ctr)
Next
Return New Tuple(Of Integer, Double)(nWords, totalLength / nWords)
End Function
End Class

Capturar solo cuando sea necesario


Las expresiones regulares de .NET admiten varias construcciones de agrupación, que permiten agrupar un patrón
de expresión regular en una o más subexpresiones. Las construcciones de agrupación que más se usan en el
lenguaje de expresiones regulares de .NET son ( subexpression ) , que define un grupo de captura numerado, y
(?< name > subexpression ) , que define un grupo de captura con nombre. Las construcciones de agrupación son
esenciales para crear referencias inversas y para definir una subexpresión a la que se aplica un cuantificador.
Sin embargo, el uso de estos elementos de lenguaje tiene un costo. Hacen que el objeto GroupCollection devuelto
por la propiedad Match.Groups se rellene con las capturas sin nombre o con nombre más recientes, y si una única
construcción de agrupación ha capturado varias subcadenas en la cadena de entrada, también rellenan el objeto
CaptureCollection devuelto por la propiedad Group.Captures de un grupo de captura determinado con varios
objetos Capture.
A menudo, las construcciones de agrupación se usan en una expresión regular solo para poder aplicarles
cuantificadores y los grupos capturados por estas subexpresiones no se usan posteriormente. Por ejemplo, la
expresión regular \b(\w+[;,]?\s?)+[.?!] está diseñada para capturar una frase completa. En la tabla siguiente se
describen los elementos del lenguaje de este patrón de expresión regular y su efecto sobre las colecciones Match y
Match.Groups del objeto Group.Captures.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

\w+ Buscar coincidencias con uno o más caracteres alfabéticos.

[;,]? Buscar coincidencias con cero o una coma o un punto y coma.

\s? Busca coincidencias con cero o un carácter de espacio en


blanco.

(\w+[;,]?\s?)+ Buscar coincidencias con una o más apariciones de uno o más


caracteres alfabéticos seguidos de una coma o un punto y
coma opcional, seguido de un carácter opcional de espacio en
blanco. Esto define al primer grupo de captura, que es
necesario para que la combinación de varios caracteres
alfabéticos (es decir, una palabra) seguidos de un signo de
puntuación opcional se repita hasta que el motor de
expresiones regulares llegue al final de una frase.

[.?!] Buscar coincidencias con un punto, un signo de interrogación


o un signo de exclamación.

Como se muestra en el ejemplo siguiente, cuando se encuentra una coincidencia, los objetos GroupCollection y
CaptureCollection se rellenan con capturas de la coincidencia. En este caso, el grupo de captura (\w+[;,]?\s?)
existe para que se le pueda aplicar el cuantificador + , que permite que el patrón de expresión regular coincida con
cada palabra de una frase. De lo contrario, coincidiría con la última palabra de una frase.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(\w+[;,]?\s?)+[.?!]";

foreach (Match match in Regex.Matches(input, pattern)) {


Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index);
int grpCtr = 0;
foreach (Group grp in match.Groups) {
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index);
int capCtr = 0;
foreach (Capture cap in grp.Captures) {
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index);
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
// Group 1: 'sentence' at index 12.
// Capture 0: 'This ' at 0.
// Capture 1: 'is ' at 5.
// Capture 2: 'one ' at 8.
// Capture 3: 'sentence' at 12.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
// Group 1: 'another' at index 30.
// Capture 0: 'This ' at 22.
// Capture 1: 'is ' at 27.
// Capture 2: 'another' at 30.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(\w+[;,]?\s?)+[.?!]"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
' Group 1: 'sentence' at index 12.
' Capture 0: 'This ' at 0.
' Capture 1: 'is ' at 5.
' Capture 2: 'one ' at 8.
' Capture 3: 'sentence' at 12.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.
' Group 1: 'another' at index 30.
' Capture 0: 'This ' at 22.
' Capture 1: 'is ' at 27.
' Capture 2: 'another' at 30.

Cuando use subexpresiones solo para aplicarles cuantificadores y no le interese el texto capturado, debe
deshabilitar las capturas de grupo. Por ejemplo, el elemento de lenguaje (?:subexpression) impide al grupo al que
se aplica que capture subcadenas coincidentes. En el ejemplo siguiente, el patrón de expresión regular del ejemplo
anterior se cambia a \b(?:\w+[;,]?\s?)+[.?!] . Como muestra el resultado, evita que el motor de expresiones
regulares rellene las colecciones GroupCollection y CaptureCollection.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(?:\w+[;,]?\s?)+[.?!]";

foreach (Match match in Regex.Matches(input, pattern)) {


Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index);
int grpCtr = 0;
foreach (Group grp in match.Groups) {
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index);
int capCtr = 0;
foreach (Capture cap in grp.Captures) {
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index);
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(?:\w+[;,]?\s?)+[.?!]"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.

Puede deshabilitar las capturas de una de las maneras siguientes:


Use el elemento de lenguaje (?:subexpression) . Este elemento impide la captura de subcadenas
coincidentes en el grupo al que se aplica. No deshabilita las capturas de subcadenas en ningún grupo
anidado.
Use la opción ExplicitCapture. Deshabilita todas las capturas sin nombre o implícitas en el patrón de
expresión regular. Cuando se usa esta opción, solo se pueden capturar las subcadenas que coinciden con
grupos con nombre definidos con el elemento de lenguaje (?<name>subexpression) . La marca ExplicitCapture
se puede pasar al parámetro options de un constructor de clase Regex o al parámetro options de un
método coincidente estático Regex.
Use la opción n del elemento de lenguaje (?imnsx) . Esta opción deshabilita todas las capturas sin nombre
o implícitas desde el punto del patrón de expresión regular en el que aparece el elemento. Las capturas se
deshabilitan hasta el final del modelo o hasta que la opción (-n) habilita las capturas sin nombre o
implícitas. Para obtener más información, consulte Construcciones misceláneas.
Use la opción n del elemento de lenguaje (?imnsx:subexpression) . Esta opción deshabilita todas las
capturas sin nombre o implícitas en subexpression . Las capturas por grupos de captura anidados sin
nombre o implícitos también se deshabilitan.

Temas relacionados
T IT L E DESC RIP C IÓ N

Detalles del comportamiento de expresiones regulares Examina la implementación del motor de expresiones regulares
de .NET. El tema se centra en la flexibilidad de las expresiones
regulares y explica la responsabilidad del desarrollador para
garantizar un funcionamiento eficaz y sólido del motor de
expresiones regulares.

Retroceso Explica qué es el retroceso y cómo afecta al rendimiento de las


expresiones regulares, y examine los elementos del lenguaje
que proporcionan alternativas al retroceso.

Lenguaje de expresiones regulares: referencia rápida Describe los elementos del lenguaje de expresiones regulares
de .NET y proporciona vínculos a documentación detallada
sobre cada elemento del lenguaje.
El modelo de objetos de expresión regular
16/09/2020 • 45 minutes to read • Edit Online

En este tema se describe el modelo de objetos usado para trabajar con expresiones regulares de .NET. Contiene las
siguientes secciones:
Motor de expresiones regulares
Objetos MatchCollection y Match
Colección de grupos
Grupo capturado
Colección de capturas
Captura individual

El motor de expresiones regulares


La clase Regex representa el motor de expresiones regulares de .NET. El motor de expresiones regulares es
responsable de analizar y compilar una expresión regular y de realizar operaciones en las que coinciden el patrón
de expresión regular con una cadena de entrada. El motor es el componente central del modelo de objetos de
expresión regular de .NET.
Puede utilizar el motor de expresiones regulares en una de estas dos formas:
Creando métodos estáticos de la clase Regex. Los parámetros de método incluyen la cadena de entrada y el
patrón de expresión regular. El motor de expresiones regulares almacena en memoria caché las expresiones
regulares que se utilizan en llamadas de métodos estáticos, por lo que las llamadas repetidas a métodos de
expresiones regulares estáticos que usan la misma expresión regular ofrecen un rendimiento relativamente
bueno.
Creando instancias de un objeto Regex, al pasar una expresión regular al constructor de clase. En este caso,
el objeto Regex es inmutable (de solo lectura) y representa un motor de expresiones regulares
estrechamente acoplado a una expresión regular única. Dado que las expresiones regulares utilizadas por
instancias Regex no están almacenadas en memoria caché, no debe crear varias veces instancias de un
objeto Regex con la misma expresión regular.
Puede llamar a los métodos de la clase Regex para realizar las operaciones siguientes:
Determinar si una cadena coincide con un patrón de expresión regular.
Extraer una coincidencia única o la primera coincidencia.
Extraer todas las coincidencias.
Reemplazar una subcadena coincidente.
Dividir una cadena única en una matriz de cadenas.
Estas operaciones se describen en las siguientes secciones.
Buscar coincidencias con un patrón de expresión regular
Este método Regex.IsMatch devuelve true si la cadena coincide con el patrón o false en caso contrario. El
método IsMatch se utiliza a menudo para validar una entrada de cadena. Por ejemplo, el siguiente código
garantiza que una cadena coincide con un número válido de la seguridad social en los Estados Unidos.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] values = { "111-22-3333", "111-2-3333"};
string pattern = @"^\d{3}-\d{2}-\d{4}$";
foreach (string value in values) {
if (Regex.IsMatch(value, pattern))
Console.WriteLine("{0} is a valid SSN.", value);
else
Console.WriteLine("{0}: Invalid", value);
}
}
}
// The example displays the following output:
// 111-22-3333 is a valid SSN.
// 111-2-3333: Invalid

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim values() As String = {"111-22-3333", "111-2-3333"}
Dim pattern As String = "^\d{3}-\d{2}-\d{4}$"
For Each value As String In values
If Regex.IsMatch(value, pattern) Then
Console.WriteLine("{0} is a valid SSN.", value)
Else
Console.WriteLine("{0}: Invalid", value)
End If
Next
End Sub
End Module
' The example displays the following output:
' 111-22-3333 is a valid SSN.
' 111-2-3333: Invalid

El patrón de la expresión regular ^\d{3}-\d{2}-\d{4}$ se interpreta como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

^ Buscar coincidencias con el principio de la cadena de entrada.

\d{3} Coincide con tres dígitos decimales.

- Buscar coincidencias con un guion.

\d{2} Coincide con dos dígitos decimales.

- Buscar coincidencias con un guion.

\d{4} Buscar coincidencias con cuatro dígitos decimales.

$ Coincide con el final de la cadena de entrada.


Extraer una coincidencia única o la primera coincidencia.
El método Regex.Match devuelve un objeto Match que contiene información sobre la primera subcadena que
coincida con un patrón de expresión regular. Si la propiedad Match.Success devuelve true , lo que indica que se
encontró una coincidencia, puede recuperar información sobre las coincidencias subsiguientes llamando al
método Match.NextMatch. Estas llamadas a métodos pueden seguir hasta que la propiedad Match.Success
devuelva false . Por ejemplo, el código siguiente utiliza el método Regex.Match(String, String) para buscar la
primera aparición de una palabra duplicada en una cadena. A continuación, llama al método Match.NextMatch
para encontrar cualquier aparición adicional. El ejemplo examina la propiedad Match.Success después de cada
llamada al método para determinar si la coincidencia actual ha sido correcta y si debe seguir una llamada al
método Match.NextMatch.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is a a farm that that raises dairy cattle.";
string pattern = @"\b(\w+)\W+(\1)\b";
Match match = Regex.Match(input, pattern);
while (match.Success)
{
Console.WriteLine("Duplicate '{0}' found at position {1}.",
match.Groups[1].Value, match.Groups[2].Index);
match = match.NextMatch();
}
}
}
// The example displays the following output:
// Duplicate 'a' found at position 10.
// Duplicate 'that' found at position 22.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is a a farm that that raises dairy cattle."
Dim pattern As String = "\b(\w+)\W+(\1)\b"
Dim match As Match = Regex.Match(input, pattern)
Do While match.Success
Console.WriteLine("Duplicate '{0}' found at position {1}.", _
match.Groups(1).Value, match.Groups(2).Index)
match = match.NextMatch()
Loop
End Sub
End Module
' The example displays the following output:
' Duplicate 'a' found at position 10.
' Duplicate 'that' found at position 22.

El patrón de la expresión regular \b(\w+)\W+(\1)\b se interpreta como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Comienza la búsqueda de coincidencias en un límite de


palabras.
M O DELO DESC RIP C IÓ N

(\w+) Buscar coincidencias con uno o más caracteres alfabéticos.


Este es el primer grupo de captura.

\W+ Coincide con uno o varios caracteres que no se usan para


formar palabras.

(\1) Buscar coincidencias con la primera cadena capturada. Este es


el segundo grupo de captura.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

Extraer todas las coincidencias


El método Regex.Matches devuelve un objeto MatchCollection que contiene información sobre todas las
coincidencias que el motor de expresiones regulares encontró en la cadena de entrada. Por ejemplo, se podría
escribir de nuevo el ejemplo anterior para que llamara al método Matches en lugar de a los métodos Match y
NextMatch.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is a a farm that that raises dairy cattle.";
string pattern = @"\b(\w+)\W+(\1)\b";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Duplicate '{0}' found at position {1}.",
match.Groups[1].Value, match.Groups[2].Index);
}
}
// The example displays the following output:
// Duplicate 'a' found at position 10.
// Duplicate 'that' found at position 22.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is a a farm that that raises dairy cattle."
Dim pattern As String = "\b(\w+)\W+(\1)\b"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Duplicate '{0}' found at position {1}.", _
match.Groups(1).Value, match.Groups(2).Index)
Next
End Sub
End Module
' The example displays the following output:
' Duplicate 'a' found at position 10.
' Duplicate 'that' found at position 22.

Reemplazar una subcadena coincidente


El método Regex.Replace reemplaza cada subcadena que hace coincidir el patrón de expresión regular con una
cadena o patrón de expresión regular especificados, y devuelve la cadena de entrada completa con reemplazos.
Por ejemplo, el código siguiente agrega un símbolo de la divisa de EE. UU. antes de un número decimal en una
cadena.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b\d+\.\d{2}\b";
string replacement = "$$$&";
string input = "Total Cost: 103.64";
Console.WriteLine(Regex.Replace(input, pattern, replacement));
}
}
// The example displays the following output:
// Total Cost: $103.64

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b\d+\.\d{2}\b"
Dim replacement As String = "$$$&"
Dim input As String = "Total Cost: 103.64"
Console.WriteLine(Regex.Replace(input, pattern, replacement))
End Sub
End Module
' The example displays the following output:
' Total Cost: $103.64

El patrón de la expresión regular \b\d+\.\d{2}\b se interpreta como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

\d+ Buscar coincidencias con uno o más dígitos decimales.

\. Coincide con un punto.

\d{2} Coincide con dos dígitos decimales.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

El patrón de reemplazo $$$& se interpreta como se muestra en la siguiente tabla.

M O DELO C A DEN A DE REEM P L A Z O

$$ El carácter del signo de dólar ($).

$& La subcadena coincidente completa.

Dividir una cadena única en una matriz de cadenas


El método Regex.Split divide la cadena de entrada en las posiciones definidas por una coincidencia de expresión
regular. Por ejemplo, el siguiente código coloca los elementos de una lista numerada en una matriz de cadena.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "1. Eggs 2. Bread 3. Milk 4. Coffee 5. Tea";
string pattern = @"\b\d{1,2}\.\s";
foreach (string item in Regex.Split(input, pattern))
{
if (! String.IsNullOrEmpty(item))
Console.WriteLine(item);
}
}
}
// The example displays the following output:
// Eggs
// Bread
// Milk
// Coffee
// Tea

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "1. Eggs 2. Bread 3. Milk 4. Coffee 5. Tea"
Dim pattern As String = "\b\d{1,2}\.\s"
For Each item As String In Regex.Split(input, pattern)
If Not String.IsNullOrEmpty(item) Then
Console.WriteLine(item)
End If
Next
End Sub
End Module
' The example displays the following output:
' Eggs
' Bread
' Milk
' Coffee
' Tea

El patrón de la expresión regular \b\d{1,2}\.\s se interpreta como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

\d{1,2} Buscar coincidencias con uno o dos dígitos decimales.

\. Coincide con un punto.

\s Coincide con un carácter de espacio en blanco.

Objetos MatchCollection y Match


Los métodos Regex devuelven dos objetos que forman parte del modelo de objetos de expresión regular: el
objeto MatchCollection y el objeto Match.
La colección de coincidencias
El método Regex.Matches devuelve un objeto MatchCollection que contiene objetos Match que representan todas
las coincidencias que encontró el motor de expresiones regulares, en el orden en el que aparecen en la cadena de
entrada. Si no hay ninguna coincidencia, el método devuelve un objeto MatchCollection sin miembros. La
propiedad MatchCollection.Item[] proporciona acceso a miembros individuales de la colección por índice, desde
cero hasta uno menos que el valor de la propiedad MatchCollection.Count. Item[] es el indizador de la colección
(en C#) y la propiedad predeterminada (en Visual Basic).
De forma predeterminada, la llamada al método Regex.Matches utiliza la evaluación diferida para rellenar el
objeto MatchCollection. El acceso a propiedades que requieren una colección totalmente rellenada, como las
propiedades MatchCollection.Count y MatchCollection.Item[], puede implicar una reducción del rendimiento. En
consecuencia, recomendamos tener acceso a la colección utilizando el objeto IEnumerator devuelto por el método
MatchCollection.GetEnumerator. Los lenguajes individuales proporcionan construcciones, como For Each en
Visual Basic y foreach en C#, que ajustan la interfaz IEnumerator de la colección.
En el siguiente ejemplo se utiliza el método Regex.Matches(String) para rellenar un objeto MatchCollection con
todas las coincidencias encontradas en una cadena de entrada. El ejemplo enumera la colección, copia las
coincidencias en una matriz de cadena y registra las posiciones de caracteres en una matriz entera.

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
MatchCollection matches;
List<string> results = new List<string>();
List<int> matchposition = new List<int>();

// Create a new Regex object and define the regular expression.


Regex r = new Regex("abc");
// Use the Matches method to find all matches in the input string.
matches = r.Matches("123abc4abcd");
// Enumerate the collection to retrieve all matches and positions.
foreach (Match match in matches)
{
// Add the match string to the string array.
results.Add(match.Value);
// Record the character position where the match was found.
matchposition.Add(match.Index);
}
// List the results.
for (int ctr = 0; ctr < results.Count; ctr++)
Console.WriteLine("'{0}' found at position {1}.",
results[ctr], matchposition[ctr]);
}
}
// The example displays the following output:
// 'abc' found at position 3.
// 'abc' found at position 7.
Imports System.Collections.Generic
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim matches As MatchCollection
Dim results As New List(Of String)
Dim matchposition As New List(Of Integer)

' Create a new Regex object and define the regular expression.
Dim r As New Regex("abc")
' Use the Matches method to find all matches in the input string.
matches = r.Matches("123abc4abcd")
' Enumerate the collection to retrieve all matches and positions.
For Each match As Match In matches
' Add the match string to the string array.
results.Add(match.Value)
' Record the character position where the match was found.
matchposition.Add(match.Index)
Next
' List the results.
For ctr As Integer = 0 To results.Count - 1
Console.WriteLine("'{0}' found at position {1}.", _
results(ctr), matchposition(ctr))
Next
End Sub
End Module
' The example displays the following output:
' 'abc' found at position 3.
' 'abc' found at position 7.

La coincidencia
La clase Match representa el resultado de una coincidencia de expresión regular única. Puede tener acceso a los
objetos Match de dos formas:
Recuperándolos del objeto MatchCollection devuelto por el método Regex.Matches. Para recuperar objetos
Match individuales, itere la colección utilizando una construcción foreach (en C#) o For Each ... Next (en
Visual Basic) o use la propiedad MatchCollection.Item[] para recuperar un determinado objeto Match por
índice o por nombre. También puede recuperar objetos Match individuales de la colección iterando la
colección por índice, desde cero a uno menos que el número de objetos de la colección. Sin embargo, este
método no saca partido de la evaluación diferida, porque tiene acceso a la propiedad
MatchCollection.Count.
En el siguiente ejemplo se recuperan objetos Match individuales de un objeto MatchCollection iterando la
colección mediante la construcción foreach o For Each ... Next . La expresión regular simplemente
coincide con la cadena "abc" de la cadena de entrada.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "abc";
string input = "abc123abc456abc789";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("{0} found at position {1}.",
match.Value, match.Index);
}
}
// The example displays the following output:
// abc found at position 0.
// abc found at position 6.
// abc found at position 12.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "abc"
Dim input As String = "abc123abc456abc789"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("{0} found at position {1}.", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' abc found at position 0.
' abc found at position 6.
' abc found at position 12.

Llamando al método Regex.Match, que devuelve un objeto Match que representa la primera coincidencia
en una cadena o una parte de una cadena. Puede determinar si la coincidencia se ha encontrado
recuperando el valor de la propiedad Match.Success . Para recuperar objetos Match que representan las
coincidencias subsiguientes, llame repetidamente al método Match.NextMatch, hasta que la propiedad
Success del objeto Match devuelto sea false .

En el siguiente ejemplo se utilizan los métodos Regex.Match(String, String) y Match.NextMatch para buscar
coincidencias con la cadena "abc" en la cadena de entrada.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "abc";
string input = "abc123abc456abc789";
Match match = Regex.Match(input, pattern);
while (match.Success)
{
Console.WriteLine("{0} found at position {1}.",
match.Value, match.Index);
match = match.NextMatch();
}
}
}
// The example displays the following output:
// abc found at position 0.
// abc found at position 6.
// abc found at position 12.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "abc"
Dim input As String = "abc123abc456abc789"
Dim match As Match = Regex.Match(input, pattern)
Do While match.Success
Console.WriteLine("{0} found at position {1}.", _
match.Value, match.Index)
match = match.NextMatch()
Loop
End Sub
End Module
' The example displays the following output:
' abc found at position 0.
' abc found at position 6.
' abc found at position 12.

Dos propiedades de la clase Match devuelven objetos de colección:


La propiedad Match.Groups devuelve un objeto GroupCollection que contiene información sobre las
subcadenas que coinciden con grupos de captura en el patrón de expresión regular.
La propiedad Match.Captures devuelve un objeto CaptureCollection que es de uso limitado. La colección no
se rellena para un objeto Match cuya propiedad Success es false . De lo contrario, contiene un único
objeto Capture que tiene la misma información que el objeto Match.
Para obtener más información sobre estos objetos, vea las secciones Colección de grupos y Colección de capturas
más adelante en este tema.
Dos propiedades adicionales de la clase Match proporcionan información sobre la coincidencia. La propiedad
Match.Value devuelve la subcadena en la cadena de entrada que coincide con el patrón de expresión regular. La
propiedad Match.Index devuelve la posición inicial basada en cero de la cadena coincidente en la cadena de
entrada.
La clase Match también tiene dos métodos de coincidencia de patrones:
El método Match.NextMatch encuentra la coincidencia según la coincidencia representada por el objeto
Match actual y devuelve un objeto Match que representa esa coincidencia.
El método Match.Result realiza una operación de reemplazo especificada en la cadena coincidente y
devuelve el resultado.
En el siguiente ejemplo se utiliza el método Match.Result para anteponer un símbolo $ y un espacio antes de cada
número que incluye dos dígitos fraccionarios.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b\d+(,\d{3})*\.\d{2}\b";
string input = "16.32\n194.03\n1,903,672.08";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine(match.Result("$$ $&"));
}
}
// The example displays the following output:
// $ 16.32
// $ 194.03
// $ 1,903,672.08

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b\d+(,\d{3})*\.\d{2}\b"
Dim input As String = "16.32" + vbCrLf + "194.03" + vbCrLf + "1,903,672.08"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine(match.Result("$$ $&"))
Next
End Sub
End Module
' The example displays the following output:
' $ 16.32
' $ 194.03
' $ 1,903,672.08

El patrón de expresión regular \b\d+(,\d{3})*\.\d{2}\b se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

\d+ Buscar coincidencias con uno o más dígitos decimales.

(,\d{3})* Buscar coincidencias con cero o más apariciones de una coma


seguida de tres dígitos decimales.

\. Buscar coincidencias con el carácter de separador decimal.

\d{2} Coincide con dos dígitos decimales.


M O DELO DESC RIP C IÓ N

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

El patrón de reemplazo $$ $& indica que la subcadena debe ser reemplazada por un símbolo de signo de dólar
($) (el patrón $$ ), un espacio y el valor de la coincidencia (el patrón $& ).
Volver al principio

La colección de grupos
La propiedad Match.Groups devuelve un objeto GroupCollection que contiene objetos Group que representan los
grupos capturados en una coincidencia única. El primer objeto Group de la colección (en el índice 0) representa la
coincidencia completa. Cada objeto que sigue representa los resultados de un grupo de captura único.
Puede recuperar objetos Group individuales en la colección utilizando la propiedad GroupCollection.Item[]. Puede
recuperar los grupos sin nombre por su posición ordinal en la colección y recuperar grupos con nombre por
nombre o por posición ordinal. Las capturas sin nombre aparecen primero en la colección y se indizan de
izquierda a derecha en el orden en el que aparecen en el patrón de expresión regular. Las capturas con nombre se
indizan después de las capturas sin nombre, de izquierda a derecha en el orden en el que aparecen en el patrón de
expresión regular. Para determinar qué grupos numerados están disponibles en la colección devuelta para un
determinado método de coincidencia de expresión regular, puede llamar al método Regex.GetGroupNumbers de
instancia. Para determinar qué grupos con nombre están disponibles en la colección, puede llamar al método
Regex.GetGroupNames de instancia. Ambos métodos son especialmente útiles en rutinas de uso general que
analizan las coincidencias encontradas por cualquier expresión regular.
La propiedad GroupCollection.Item[] es el indizador de la colección en C# y la propiedad predeterminada del
objeto de la colección en Visual Basic. Esto significa que se puede tener acceso a los objetos Group individuales
por índice (o por nombre, en el caso de grupos con nombre) del modo siguiente:

Group group = match.Groups[ctr];

Dim group As Group = match.Groups(ctr)

En el siguiente ejemplo se define una expresión regular que usa construcciones de agrupación para capturar el
mes, día y año de una fecha.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\w+)\s(\d{1,2}),\s(\d{4})\b";
string input = "Born: July 28, 1989";
Match match = Regex.Match(input, pattern);
if (match.Success)
for (int ctr = 0; ctr < match.Groups.Count; ctr++)
Console.WriteLine("Group {0}: {1}", ctr, match.Groups[ctr].Value);
}
}
// The example displays the following output:
// Group 0: July 28, 1989
// Group 1: July
// Group 2: 28
// Group 3: 1989

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\w+)\s(\d{1,2}),\s(\d{4})\b"
Dim input As String = "Born: July 28, 1989"
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
For ctr As Integer = 0 To match.Groups.Count - 1
Console.WriteLine("Group {0}: {1}", ctr, match.Groups(ctr).Value)
Next
End If
End Sub
End Module
' The example displays the following output:
' Group 0: July 28, 1989
' Group 1: July
' Group 2: 28
' Group 3: 1989

El patrón de expresión regular \b(\w+)\s(\d{1,2}),\s(\d{4})\b se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

(\w+) Buscar coincidencias con uno o más caracteres alfabéticos.


Este es el primer grupo de captura.

\s Coincide con un carácter de espacio en blanco.

(\d{1,2}) Buscar coincidencias con uno o dos dígitos decimales. Este es


el segundo grupo de captura.

, Coincide con una coma.

\s Coincide con un carácter de espacio en blanco.


M O DELO DESC RIP C IÓ N

(\d{4}) Buscar coincidencias con cuatro dígitos decimales. Éste es el


tercer grupo de captura.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

Volver al principio

El grupo capturado
La clase Group representa el resultado de un único grupo de captura. La propiedad Item[] del objeto
GroupCollection devuelto por la propiedad Match.Groups devuelve objetos de grupo que representan grupos de
captura definidos en una expresión regular. La propiedad Item[] es el indizador (en C#) y la propiedad
predeterminada (en Visual Basic) de la clase Group. También puede recuperar miembros individuales mediante la
iteración de la colección con la construcción foreach o For Each . Para obtener un ejemplo, vea la sección
anterior.
En el siguiente ejemplo se utilizan construcciones de agrupación anidadas para capturar subcadenas en grupos. El
patrón de expresión regular (a(b))c coincide con la cadena "abc". Asigna la subcadena "ab" al primer grupo de
captura y la subcadena "b" al segundo grupo de captura.

var matchposition = new List<int>();


var results = new List<string>();
// Define substrings abc, ab, b.
var r = new Regex("(a(b))c");
Match m = r.Match("abdabc");
for (int i = 0; m.Groups[i].Value != ""; i++)
{
// Add groups to string array.
results.Add(m.Groups[i].Value);
// Record character position.
matchposition.Add(m.Groups[i].Index);
}

// Display the capture groups.


for (int ctr = 0; ctr < results.Count; ctr++)
Console.WriteLine("{0} at position {1}",
results[ctr], matchposition[ctr]);
// The example displays the following output:
// abc at position 3
// ab at position 3
// b at position 4
Dim matchposition As New List(Of Integer)
Dim results As New List(Of String)
' Define substrings abc, ab, b.
Dim r As New Regex("(a(b))c")
Dim m As Match = r.Match("abdabc")
Dim i As Integer = 0
While Not (m.Groups(i).Value = "")
' Add groups to string array.
results.Add(m.Groups(i).Value)
' Record character position.
matchposition.Add(m.Groups(i).Index)
i += 1
End While

' Display the capture groups.


For ctr As Integer = 0 to results.Count - 1
Console.WriteLine("{0} at position {1}", _
results(ctr), matchposition(ctr))
Next
' The example displays the following output:
' abc at position 3
' ab at position 3
' b at position 4

En el siguiente ejemplo se utilizan construcciones de agrupación con nombre para capturar subcadenas de una
cadena que contiene datos en el formato "NOMBREDATOS:VALOR" que la expresión regular divide por el signo de
dos puntos (:).

var r = new Regex(@"^(?<name>\w+):(?<value>\w+)");


Match m = r.Match("Section1:119900");
Console.WriteLine(m.Groups["name"].Value);
Console.WriteLine(m.Groups["value"].Value);
// The example displays the following output:
// Section1
// 119900

Dim r As New Regex("^(?<name>\w+):(?<value>\w+)")


Dim m As Match = r.Match("Section1:119900")
Console.WriteLine(m.Groups("name").Value)
Console.WriteLine(m.Groups("value").Value)
' The example displays the following output:
' Section1
' 119900

El patrón de expresión regular ^(?<name>\w+):(?<value>\w+) se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

^ Iniciar la búsqueda de coincidencias con el principio de la


cadena de entrada.

(?<name>\w+) Buscar coincidencias con uno o más caracteres alfabéticos. El


nombre de este grupo de captura es name .

: Buscar coincidencia con un signo de dos puntos.

(?<value>\w+) Buscar coincidencias con uno o más caracteres alfabéticos. El


nombre de este grupo de captura es value .
Las propiedades de la clase Group proporcionan información sobre el grupo capturado: La propiedad
Group.Value contiene la subcadena capturada, la propiedad Group.Index indica la posición inicial del grupo
capturado en el texto de entrada, la propiedad Group.Length contiene la longitud del texto capturado y la
propiedad Group.Success indica si una subcadena coincidió con el patrón definido por el grupo de captura.
Al aplicar cuantificadores a un grupo (para obtener más información, consulte Cuantificadores), se modifica la
relación de una captura por grupo de captura de dos maneras:
Si el cuantificador * o *? (qué especifica cero o más coincidencias) se aplica a un grupo, es posible que
un grupo de captura no tenga una coincidencia en la cadena de entrada. Cuando no haya texto capturado,
las propiedades del objeto Group se establecen como se muestran en la siguiente tabla.

P RO P IEDA DES DE GRUP O VA LO R

Success false

Value String.Empty

Length 0

Esto se muestra en el ejemplo siguiente. En el patrón de expresión regular aaa(bbb)*ccc , se pueden buscar
coincidencias para el primer grupo de captura (la subcadena "bbb") cero o varias veces. Dado que la cadena
de entrada "aaaccc" coincide con el patrón, el grupo de captura no tiene una coincidencia.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "aaa(bbb)*ccc";
string input = "aaaccc";
Match match = Regex.Match(input, pattern);
Console.WriteLine("Match value: {0}", match.Value);
if (match.Groups[1].Success)
Console.WriteLine("Group 1 value: {0}", match.Groups[1].Value);
else
Console.WriteLine("The first capturing group has no match.");
}
}
// The example displays the following output:
// Match value: aaaccc
// The first capturing group has no match.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "aaa(bbb)*ccc"
Dim input As String = "aaaccc"
Dim match As Match = Regex.Match(input, pattern)
Console.WriteLine("Match value: {0}", match.Value)
If match.Groups(1).Success Then
Console.WriteLine("Group 1 value: {0}", match.Groups(1).Value)
Else
Console.WriteLine("The first capturing group has no match.")
End If
End Sub
End Module
' The example displays the following output:
' Match value: aaaccc
' The first capturing group has no match.

Los cuantificadores pueden coincidir con varias apariciones de un patrón definido por un grupo de captura.
En este caso, las propiedades Value y Length de un objeto Group solo contienen información sobre la
última subcadena capturada. Por ejemplo, la siguiente coincidencia de expresión regular coincide con una
frase única que finaliza con un punto. La expresión utiliza dos construcciones de agrupación: la primera
captura palabras individuales junto con un carácter de espacio en blanco; la segunda captura palabras
individuales. Como lo muestra el resultado del ejemplo, aunque la expresión regular logre capturar una
frase completa, el segundo grupo de captura solo captura la última palabra.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b((\w+)\s?)+\.";
string input = "This is a sentence. This is another sentence.";
Match match = Regex.Match(input, pattern);
if (match.Success)
{
Console.WriteLine("Match: " + match.Value);
Console.WriteLine("Group 2: " + match.Groups[2].Value);
}
}
}
// The example displays the following output:
// Match: This is a sentence.
// Group 2: sentence
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b((\w+)\s?)+\."
Dim input As String = "This is a sentence. This is another sentence."
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine("Match: " + match.Value)
Console.WriteLine("Group 2: " + match.Groups(2).Value)
End If
End Sub
End Module
' The example displays the following output:
' Match: This is a sentence.
' Group 2: sentence

Volver al principio

La colección de capturas
El objeto Group solo contiene información sobre la última captura. Sin embargo, el conjunto completo de capturas
realizado por un grupo de captura sigue aún disponible en el objeto CaptureCollection devuelto por la propiedad
Group.Captures. Cada miembro de la colección es un objeto Capture que representa una captura realizada por
este grupo de captura, en el orden en el que se capturaron (y, por consiguiente, en el orden en el que las cadenas
capturadas coincidían de izquierda a derecha en la cadena de entrada). Puede recuperar objetos Capture
individuales de la colección de estas dos formas:
Mediante la iteración de la colección con el uso de una construcción como foreach (en C#) o For Each (en
Visual Basic).
Utilizando la propiedad CaptureCollection.Item[] para recuperar un objeto específico por índice. La
propiedad Item[] es la propiedad predeterminada del objeto CaptureCollection (en Visual Basic) o el
indizador (en C#).
Si no se aplica un cuantificador a un grupo de captura, el objeto CaptureCollection contiene un objeto Capture
único que es de escaso interés, porque proporciona información sobre la misma coincidencia que su objeto
Group. Si se aplica un cuantificador a un grupo de captura, el objeto CaptureCollection contiene todas las capturas
realizadas por el grupo de captura y el último miembro de la colección representa la misma captura que el objeto
Group.
Si utiliza, por ejemplo, el patrón de expresión regular ((a(b))c)+ (donde el cuantificador + indica una o varias
coincidencias) para capturar coincidencias de la cadena "abcabcabc", el objeto CaptureCollection para cada objeto
Group contiene tres miembros.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "((a(b))c)+";
string input = "abcabcabc";

Match match = Regex.Match(input, pattern);


if (match.Success)
{
Console.WriteLine("Match: '{0}' at position {1}",
match.Value, match.Index);
GroupCollection groups = match.Groups;
for (int ctr = 0; ctr < groups.Count; ctr++) {
Console.WriteLine(" Group {0}: '{1}' at position {2}",
ctr, groups[ctr].Value, groups[ctr].Index);
CaptureCollection captures = groups[ctr].Captures;
for (int ctr2 = 0; ctr2 < captures.Count; ctr2++) {
Console.WriteLine(" Capture {0}: '{1}' at position {2}",
ctr2, captures[ctr2].Value, captures[ctr2].Index);
}
}
}
}
}
// The example displays the following output:
// Match: 'abcabcabc' at position 0
// Group 0: 'abcabcabc' at position 0
// Capture 0: 'abcabcabc' at position 0
// Group 1: 'abc' at position 6
// Capture 0: 'abc' at position 0
// Capture 1: 'abc' at position 3
// Capture 2: 'abc' at position 6
// Group 2: 'ab' at position 6
// Capture 0: 'ab' at position 0
// Capture 1: 'ab' at position 3
// Capture 2: 'ab' at position 6
// Group 3: 'b' at position 7
// Capture 0: 'b' at position 1
// Capture 1: 'b' at position 4
// Capture 2: 'b' at position 7
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "((a(b))c)+"
Dim input As STring = "abcabcabc"

Dim match As Match = Regex.Match(input, pattern)


If match.Success Then
Console.WriteLine("Match: '{0}' at position {1}", _
match.Value, match.Index)
Dim groups As GroupCollection = match.Groups
For ctr As Integer = 0 To groups.Count - 1
Console.WriteLine(" Group {0}: '{1}' at position {2}", _
ctr, groups(ctr).Value, groups(ctr).Index)
Dim captures As CaptureCollection = groups(ctr).Captures
For ctr2 As Integer = 0 To captures.Count - 1
Console.WriteLine(" Capture {0}: '{1}' at position {2}", _
ctr2, captures(ctr2).Value, captures(ctr2).Index)
Next
Next
End If
End Sub
End Module
' The example dosplays the following output:
' Match: 'abcabcabc' at position 0
' Group 0: 'abcabcabc' at position 0
' Capture 0: 'abcabcabc' at position 0
' Group 1: 'abc' at position 6
' Capture 0: 'abc' at position 0
' Capture 1: 'abc' at position 3
' Capture 2: 'abc' at position 6
' Group 2: 'ab' at position 6
' Capture 0: 'ab' at position 0
' Capture 1: 'ab' at position 3
' Capture 2: 'ab' at position 6
' Group 3: 'b' at position 7
' Capture 0: 'b' at position 1
' Capture 1: 'b' at position 4
' Capture 2: 'b' at position 7

En el siguiente ejemplo se utiliza la expresión regular (Abc)+ para buscar una o más ejecuciones consecutivas de
la cadena "Abc" en la cadena "XYZAbcAbcAbcXYZAbcAb". El ejemplo ilustra la forma en que se utiliza la propiedad
Group.Captures para que devuelva varios grupos de subcadenas capturadas.
int counter;
Match m;
CaptureCollection cc;
GroupCollection gc;

// Look for groupings of "Abc".


var r = new Regex("(Abc)+");
// Define the string to search.
m = r.Match("XYZAbcAbcAbcXYZAbcAb");
gc = m.Groups;

// Display the number of groups.


Console.WriteLine("Captured groups = " + gc.Count.ToString());

// Loop through each group.


for (int i = 0; i < gc.Count; i++)
{
cc = gc[i].Captures;
counter = cc.Count;

// Display the number of captures in this group.


Console.WriteLine("Captures count = " + counter.ToString());

// Loop through each capture in the group.


for (int ii = 0; ii < counter; ii++)
{
// Display the capture and its position.
Console.WriteLine(cc[ii] + " Starts at character " +
cc[ii].Index);
}
}
// The example displays the following output:
// Captured groups = 2
// Captures count = 1
// AbcAbcAbc Starts at character 3
// Captures count = 3
// Abc Starts at character 3
// Abc Starts at character 6
// Abc Starts at character 9
Dim counter As Integer
Dim m As Match
Dim cc As CaptureCollection
Dim gc As GroupCollection

' Look for groupings of "Abc".


Dim r As New Regex("(Abc)+")
' Define the string to search.
m = r.Match("XYZAbcAbcAbcXYZAbcAb")
gc = m.Groups

' Display the number of groups.


Console.WriteLine("Captured groups = " & gc.Count.ToString())

' Loop through each group.


Dim i, ii As Integer
For i = 0 To gc.Count - 1
cc = gc(i).Captures
counter = cc.Count

' Display the number of captures in this group.


Console.WriteLine("Captures count = " & counter.ToString())

' Loop through each capture in the group.


For ii = 0 To counter - 1
' Display the capture and its position.
Console.WriteLine(cc(ii).ToString() _
& " Starts at character " & cc(ii).Index.ToString())
Next ii
Next i
' The example displays the following output:
' Captured groups = 2
' Captures count = 1
' AbcAbcAbc Starts at character 3
' Captures count = 3
' Abc Starts at character 3
' Abc Starts at character 6
' Abc Starts at character 9

Volver al principio

La captura individual
La clase Capture contiene el resultado de una única captura de subexpresiones. La propiedad Capture.Value
contiene el texto coincidente y la propiedad Capture.Index indica la posición basada en cero en la cadena de
entrada en la que la comienza la subcadena coincidente.
En el siguiente ejemplo se analiza una cadena de entrada para la temperatura de las ciudades seleccionadas. Se
utiliza una coma (",") para separar una ciudad y su temperatura, y un punto y coma (";"), para separar los datos de
cada ciudad. La cadena de entrada completa representa una coincidencia única. En el patrón de expresión regular
((\w+(\s\w+)*),(\d+);)+ , que se utiliza para analizar la cadena, se asigna el nombre de la ciudad al segundo
grupo de captura y la temperatura al cuarto grupo de captura.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "Miami,78;Chicago,62;New York,67;San Francisco,59;Seattle,58;";
string pattern = @"((\w+(\s\w+)*),(\d+);)+";
Match match = Regex.Match(input, pattern);
if (match.Success)
{
Console.WriteLine("Current temperatures:");
for (int ctr = 0; ctr < match.Groups[2].Captures.Count; ctr++)
Console.WriteLine("{0,-20} {1,3}", match.Groups[2].Captures[ctr].Value,
match.Groups[4].Captures[ctr].Value);
}
}
}
// The example displays the following output:
// Current temperatures:
// Miami 78
// Chicago 62
// New York 67
// San Francisco 59
// Seattle 58

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "Miami,78;Chicago,62;New York,67;San Francisco,59;Seattle,58;"
Dim pattern As String = "((\w+(\s\w+)*),(\d+);)+"
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine("Current temperatures:")
For ctr As Integer = 0 To match.Groups(2).Captures.Count - 1
Console.WriteLine("{0,-20} {1,3}", match.Groups(2).Captures(ctr).Value, _
match.Groups(4).Captures(ctr).Value)
Next
End If
End Sub
End Module
' The example displays the following output:
' Current temperatures:
' Miami 78
' Chicago 62
' New York 67
' San Francisco 59
' Seattle 58

La expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\w+ Buscar coincidencias con uno o más caracteres alfabéticos.

(\s\w+)* Coincide con cero o más apariciones de un carácter de


espacio en blanco seguido de uno o varios caracteres que se
usan para formar palabras. Este patrón busca coincidencias
con nombres de ciudades de múltiples palabras. Éste es el
tercer grupo de captura.
M O DELO DESC RIP C IÓ N

(\w+(\s\w+)*) Coincide con uno o varios caracteres que se usan para formar
palabras seguidos de cero o más apariciones de un carácter
de espacio en blanco seguidos de uno o varios caracteres que
se usan para formar palabras. Este es el segundo grupo de
captura.

, Coincide con una coma.

(\d+) Buscar coincidencias con uno o más dígitos. Este es el cuarto


grupo de captura.

; Buscar coincidencias con un signo de punto y coma.

((\w+(\s\w+)*),(\d+);)+ Buscar coincidencias con el patrón de una palabra seguido de


cualquier palabra adicional seguida de una coma, uno o más
dígitos y un punto y coma, una o más veces. Este es el primer
grupo de captura.

Vea también
System.Text.RegularExpressions
Expresiones regulares de .NET
Lenguaje de expresiones regulares: referencia rápida
Detalles del comportamiento de expresiones
regulares
16/09/2020 • 30 minutes to read • Edit Online

El motor de expresiones regulares de .NET Framework es un buscador de coincidencias de expresiones regulares


con retroceso que incorpora un motor NFA (autómata finito no determinista) tradicional, como el que usa Perl,
Python, Emacs y Tcl. Esto lo distingue de los motores DFA (autómatas finitos deterministas) de expresiones
regulares puras, más rápidos pero más limitados, como los de awk, egrep o lex. Esto también lo distingue de los
NFA POSIX, estandarizados pero más lentos. En la sección siguiente se describen los tres tipos de motores de
expresiones regulares y se explica por qué las expresiones regulares de .NET Framework se implementan
mediante un motor NFA tradicional.

Ventajas del motor NFA


Cuando los motores DFA realizan una búsqueda de coincidencia de patrones, su orden de procesamiento está
controlado por la cadena de entrada. El motor empieza al principio de la cadena de entrada y continúa de forma
secuencial para determinar si el carácter siguiente coincide con el patrón de expresión regular. Pueden garantizar
una coincidencia con la cadena más larga posible. Dado que nunca prueban el mismo carácter dos veces, los
motores de búsqueda DFA no permiten el retroceso. Pero, como los motores de búsqueda DFA solo contienen
estados finitos, no pueden coincidir con un patrón con referencias inversas y, como no crean una expansión
explícita, no pueden capturar subexpresiones.
A diferencia de los motores DFA, cuando los motores NFA tradicionales realizan una búsqueda de coincidencia de
patrones, su orden de procesamiento está controlado por el patrón de expresión regular. Al procesar un elemento
del lenguaje determinado, el motor usa una búsqueda de coincidencia expansiva, es decir, coincide con la mayor
parte posible de la cadena de entrada. Pero también guarda su estado después de encontrar una coincidencia
correcta con una subexpresión. Si finalmente se produce un error en una coincidencia, el motor puede volver a un
estado guardado para buscar otras coincidencias. Este proceso de abandonar una coincidencia de subexpresión
correcta para que los elementos del lenguaje subsiguientes de la expresión regular también puedan coincidir se
conoce como retroceso. Los motores NFA usan el retroceso para probar todas las expansiones posibles de una
expresión regular en un orden específico y aceptan la primera coincidencia. Puesto que los motores NFA
tradicionales construyen una expansión específica de la expresión regular para encontrar una coincidencia
correcta, pueden capturar coincidencias de subexpresiones y referencias inversas coincidentes. Pero el hecho de
que los motores NFA tradicionales puedan retroceder les permite visitar el mismo estado varias veces si llegan al
estado a través de diferentes rutas de acceso. Como resultado, se pueden ejecutar de forma exponencialmente
lenta en el peor de los casos. Dado que los motores NFA tradicionales aceptan la primera coincidencia que
encuentran, puede darse que no descubran otras coincidencias (probablemente más largas).
Los motores NFA POSIX son como los motores NFA tradicionales, salvo que siguen retrocediendo hasta que
puedan garantizar que han encontrado la coincidencia más larga posible. Como resultado, un motor NFA POSIX es
más lento que un motor NFA tradicional, y cuando se usa un motor NFA POSIX, no se puede dar preferencia a una
coincidencia más corta frente a una más larga cambiando el orden de la búsqueda hacia atrás.
Los motores NFA tradicionales son muy populares entre los programadores porque ofrecen mayor control sobre
la coincidencia de cadenas que los motores de búsqueda DFA o NFA POSIX. Aunque, en el peor de los casos, se
pueden ejecutar con lentitud, se les puede dirigir para que busquen coincidencias en tiempo lineal o polinómico
mediante patrones que reduzcan las ambigüedades y limiten el retroceso. En otras palabras, aunque los motores
NFA sacrifican el rendimiento a favor de la eficacia y la flexibilidad, en la mayoría de los casos ofrecen un
rendimiento aceptable si una expresión regular está escrita correctamente y evitan los casos en los que el
retroceso degrada exponencialmente el rendimiento.

NOTE
Para obtener información sobre la reducción del rendimiento que causa un retroceso excesivo y sobre las maneras de crear
una expresión regular para solucionarlo, consulte Retroceso.

Funcionalidades del motor de .NET Framework


Para aprovechar las ventajas de un motor NFA tradicional, el motor de expresiones regulares de .NET Framework
incluye un conjunto completo de construcciones que permiten a los programadores dirigir el motor de retroceso.
Estas construcciones se pueden usar para buscar coincidencias con mayor rapidez o para dar preferencia a
determinadas expansiones frente a otras.
Otras características del motor de expresiones regulares de .NET Framework son las siguientes:
Cuantificadores diferidos: ?? , *? , +? , { n , m }? . Estas construcciones le indican al motor de retroceso
que busque primero el número mínimo de repeticiones. En cambio, los cuantificadores expansivos
normales intentan buscar primero el número máximo de repeticiones. En el siguiente ejemplo se ilustra la
diferencia entre ambos. Una expresión regular coincide con una oración que termina con un número, y hay
un grupo de capturas diseñado para extraer ese número. La expresión regular .+(\d+)\. incluye el
cuantificador expansivo .+ , lo que hace que el motor de expresiones regulares capture solo el último
dígito del número. En cambio, la expresión regular .+?(\d+)\. incluye el cuantificador diferido .+? , lo que
hace que el motor de expresiones regulares capture el número entero.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string greedyPattern = @".+(\d+)\.";
string lazyPattern = @".+?(\d+)\.";
string input = "This sentence ends with the number 107325.";
Match match;

// Match using greedy quantifier .+.


match = Regex.Match(input, greedyPattern);
if (match.Success)
Console.WriteLine("Number at end of sentence (greedy): {0}",
match.Groups[1].Value);
else
Console.WriteLine("{0} finds no match.", greedyPattern);

// Match using lazy quantifier .+?.


match = Regex.Match(input, lazyPattern);
if (match.Success)
Console.WriteLine("Number at end of sentence (lazy): {0}",
match.Groups[1].Value);
else
Console.WriteLine("{0} finds no match.", lazyPattern);
}
}
// The example displays the following output:
// Number at end of sentence (greedy): 5
// Number at end of sentence (lazy): 107325
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim greedyPattern As String = ".+(\d+)\."
Dim lazyPattern As String = ".+?(\d+)\."
Dim input As String = "This sentence ends with the number 107325."
Dim match As Match

' Match using greedy quantifier .+.


match = Regex.Match(input, greedyPattern)
If match.Success Then
Console.WriteLine("Number at end of sentence (greedy): {0}",
match.Groups(1).Value)
Else
Console.WriteLine("{0} finds no match.", greedyPattern)
End If

' Match using lazy quantifier .+?.


match = Regex.Match(input, lazyPattern)
If match.Success Then
Console.WriteLine("Number at end of sentence (lazy): {0}",
match.Groups(1).Value)
Else
Console.WriteLine("{0} finds no match.", lazyPattern)
End If
End Sub
End Module
' The example displays the following output:
' Number at end of sentence (greedy): 5
' Number at end of sentence (lazy): 107325

Las versiones expansiva y diferida de esta expresión regular se definen como se muestra en la tabla
siguiente:

M O DELO DESC RIP C IÓ N

.+ (cuantificador expansivo) Buscar al menos una repetición de cualquier carácter. Esto


hace que el motor de expresiones regulares busque una
coincidencia con la cadena completa y, después, retroceda
según sea necesario para coincidir con el resto del patrón.

.+? (cuantificador diferido) Coincide con al menos una repetición de cualquier


carácter, pero el menor número posible.

(\d+) Coincide con al menos un carácter numérico y lo asigna al


primer grupo de capturas.

\. Coincide con un punto.

Para más información sobre los cuantificadores diferidos, vea Cuantificadores.


Búsqueda anticipada positiva: (?= subexpresión ) . Esta característica permite que el motor de retroceso
vuelva a la misma posición en el texto después de encontrar una coincidencia con una subexpresión. Es útil
para buscar en todo el texto mediante la comprobación de varios patrones que empiezan en la misma
posición. Además, permite al motor comprobar que una subcadena existe al final de la coincidencia sin
incluir la subcadena en el texto coincidente. En el ejemplo siguiente se usa la búsqueda anticipada positiva
para extraer las palabras de una oración que no van seguidas de símbolos de puntuación.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b[A-Z]+\b(?=\P{P})";
string input = "If so, what comes next?";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// If
// what
// comes

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b[A-Z]+\b(?=\P{P})"
Dim input As String = "If so, what comes next?"
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' If
' what
' comes

La expresión regular \b[A-Z]+\b(?=\P{P}) se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de


palabras.

[A-Z]+ Coincide con cualquier carácter alfabético una o más


veces. Como se llama al método Regex.Matches con la
opción RegexOptions.IgnoreCase, la comparación no
distingue mayúsculas de minúsculas.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

(?=\P{P}) Busca hacia delante para determinar si el siguiente


carácter es un signo de puntuación. Si no es así, se
produce la coincidencia.

Para obtener más información sobre las aserciones de búsqueda anticipada positiva, consulte
Construcciones de agrupamiento.
Búsqueda anticipada negativa: (?! subexpresión ) . Esta característica permite coincidir con una expresión
solo si no se produce una coincidencia con una subexpresión. Esto es eficaz a fin de restringir una
búsqueda, ya que a menudo resulta más sencillo proporcionar una expresión para un caso que se debe
eliminar, en lugar de una expresión para los casos que se deben incluir. Por ejemplo, es difícil escribir una
expresión para buscar palabras que no comienzan por "non". En el ejemplo siguiente se usa la búsqueda
anticipada negativa para excluirlas.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(?!non)\w+\b";
string input = "Nonsense is not always non-functional.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// is
// not
// always
// functional

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(?!non)\w+\b"
Dim input As String = "Nonsense is not always non-functional."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' is
' not
' always
' functional

El patrón de expresión regular \b(?!non)\w+\b se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

\b Iniciar la búsqueda de coincidencias en un límite de


palabras.

(?!non) Buscar hacia delante para asegurarse de que la cadena


actual no empieza por "non". Si lo hace, se produce un
error de coincidencia.

(\w+) Buscar coincidencias con uno o más caracteres alfabéticos.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

Para obtener más información sobre las aserciones de búsqueda anticipada negativa, consulte
Construcciones de agrupamiento.
Evaluación condicional: (?( expresión ) sí | no ) y (?( nombre ) sí | no ) , donde expresión es una
subexpresión que debe coincidir, nombre es el nombre de un grupo de capturas, sí es la cadena que debe
coincidir si expresión coincide o nombre es un grupo capturado válido y no vacío, y no es la subexpresión
que debe coincidir si expresión no coincide o si nombre no es un grupo capturado válido y no vacío. Esta
característica permite al motor buscar mediante más de un patrón alternativo, según el resultado de una
búsqueda de coincidencia de subexpresión anterior o el resultado de una aserción de ancho cero. Esto
posibilita una forma más eficaz de referencia inversa que permite, por ejemplo, coincidir con una
subexpresión en función de si se produjo una coincidiencia con una subexpresión anterior. La expresión
regular del ejemplo siguiente coincide con párrafos que están pensados tanto para un uso público como
interno. Los párrafos destinados únicamente al uso interno empiezan con una etiqueta <PRIVATE> . El
patrón de expresión regular ^(?<Pvt>\<PRIVATE\>\s)?(?(Pvt)((\w+\p{P}?\s)+)|((\w+\p{P}?\s)+))\r?$ usa la
evaluación condicional para asignar el contenido de los párrafos pensados para el uso público y para el uso
interno a grupos de capturas independientes. Después, estos párrafos se pueden tratar de forma diferente.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "<PRIVATE> This is not for public consumption." + Environment.NewLine +
"But this is for public consumption." + Environment.NewLine +
"<PRIVATE> Again, this is confidential.\n";
string pattern = @"^(?<Pvt>\<PRIVATE\>\s)?(?(Pvt)((\w+\p{P}?\s)+)|((\w+\p{P}?\s)+))\r?$";
string publicDocument = null, privateDocument = null;

foreach (Match match in Regex.Matches(input, pattern, RegexOptions.Multiline))


{
if (match.Groups[1].Success) {
privateDocument += match.Groups[1].Value + "\n";
}
else {
publicDocument += match.Groups[3].Value + "\n";
privateDocument += match.Groups[3].Value + "\n";
}
}

Console.WriteLine("Private Document:");
Console.WriteLine(privateDocument);
Console.WriteLine("Public Document:");
Console.WriteLine(publicDocument);
}
}
// The example displays the following output:
// Private Document:
// This is not for public consumption.
// But this is for public consumption.
// Again, this is confidential.
//
// Public Document:
// But this is for public consumption.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "<PRIVATE> This is not for public consumption." + vbCrLf + _
"But this is for public consumption." + vbCrLf + _
"<PRIVATE> Again, this is confidential." + vbCrLf
Dim pattern As String = "^(?<Pvt>\<PRIVATE\>\s)?(?(Pvt)((\w+\p{P}?\s)+)|((\w+\p{P}?\s)+))\r?$"
Dim publicDocument As String = Nothing
Dim privateDocument As String = Nothing

For Each match As Match In Regex.Matches(input, pattern, RegexOptions.Multiline)


If match.Groups(1).Success Then
privateDocument += match.Groups(1).Value + vbCrLf
Else
publicDocument += match.Groups(3).Value + vbCrLf
privateDocument += match.Groups(3).Value + vbCrLf
End If
Next

Console.WriteLine("Private Document:")
Console.WriteLine(privateDocument)
Console.WriteLine("Public Document:")
Console.WriteLine(publicDocument)
End Sub
End Module
' The example displays the following output:
' Private Document:
' This is not for public consumption.
' But this is for public consumption.
' Again, this is confidential.
'
' Public Document:
' But this is for public consumption.

El patrón de expresión regular se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

^ Inicia la búsqueda de coincidencias al principio de una


línea.

(?<Pvt>\<PRIVATE\>\s)? Coincide con cero o una repetición de la cadena


<PRIVATE> seguida de un carácter de espacio en blanco.
Asigna la coincidencia a un grupo de capturas
denominado Pvt .

(?(Pvt)((\w+\p{P}?\s)+) Si existe el grupo de capturas Pvt , coincide con una o


más repeticiones de uno o más caracteres de palabra
seguidos de cero o un separador de puntuación, seguido
de un carácter de espacio en blanco. Asigna la subcadena
al primer grupo de capturas.

|((\w+\p{P}?\s)+)) Si no existe el grupo de capturas Pvt , coincide con una o


más repeticiones de uno o más caracteres de palabra
seguidos de cero o un separador de puntuación, seguido
de un carácter de espacio en blanco. Asigna la subcadena
al tercer grupo de capturas.

\r?$ Coincide con el final de una línea o con el final de la


cadena.
Para obtener más información sobre la evaluación condicional, consulte Construcciones de alternancia.
Definiciones de grupos de equilibrio: (?< nombre1 - nombre2 > subexpresión ) . Esta característica
permite al motor de expresiones regulares realizar un seguimiento de construcciones anidadas como
paréntesis o corchetes de apertura y cierre. Para ver un ejemplo, consulte Construcciones de agrupamiento.
Grupos atómicos: (?> subexpresión ) . Esta característica permite al motor de retroceso garantizar que una
subexpresión coincida solo con la primera coincidencia encontrada para dicha subexpresión, como si la
expresión se ejecutara independientemente de la expresión que la contiene. Si no usa esta construcción, el
retroceso en las búsquedas en la expresión más grande puede cambiar el comportamiento de una
subexpresión. Por ejemplo, la expresión regular (a+)\w coincide con uno o varios caracteres "a", junto con
un carácter de palabra que sigue a la secuencia de caracteres "a", y asigna la secuencia de caracteres "a" al
primer grupo de captura. Sin embargo, si el carácter final de la cadena de entrada es también una "a",
coincide con el elemento de lenguaje \w y no se incluye en el grupo capturado.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "aaaaa", "aaaaab" };
string backtrackingPattern = @"(a+)\w";
Match match;

foreach (string input in inputs) {


Console.WriteLine("Input: {0}", input);
match = Regex.Match(input, backtrackingPattern);
Console.WriteLine(" Pattern: {0}", backtrackingPattern);
if (match.Success) {
Console.WriteLine(" Match: {0}", match.Value);
Console.WriteLine(" Group 1: {0}", match.Groups[1].Value);
}
else {
Console.WriteLine(" Match failed.");
}
}
Console.WriteLine();
}
}
// The example displays the following output:
// Input: aaaaa
// Pattern: (a+)\w
// Match: aaaaa
// Group 1: aaaa
// Input: aaaaab
// Pattern: (a+)\w
// Match: aaaaab
// Group 1: aaaaa
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"aaaaa", "aaaaab"}
Dim backtrackingPattern As String = "(a+)\w"
Dim match As Match

For Each input As String In inputs


Console.WriteLine("Input: {0}", input)
match = Regex.Match(input, backtrackingPattern)
Console.WriteLine(" Pattern: {0}", backtrackingPattern)
If match.Success Then
Console.WriteLine(" Match: {0}", match.Value)
Console.WriteLine(" Group 1: {0}", match.Groups(1).Value)
Else
Console.WriteLine(" Match failed.")
End If
Next
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Input: aaaaa
' Pattern: (a+)\w
' Match: aaaaa
' Group 1: aaaa
' Input: aaaaab
' Pattern: (a+)\w
' Match: aaaaab
' Group 1: aaaaa

La expresión regular ((?>a+))\w impide este comportamiento. Dado que todos los caracteres "a"
consecutivos se buscan sin retroceso, el primer grupo de capturas incluye todos los caracteres "a"
consecutivos. Si los caracteres "a" no van seguidos de al menos otro carácter que no sea "a", se produce un
error de coincidencia.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "aaaaa", "aaaaab" };
string nonbacktrackingPattern = @"((?>a+))\w";
Match match;

foreach (string input in inputs) {


Console.WriteLine("Input: {0}", input);
match = Regex.Match(input, nonbacktrackingPattern);
Console.WriteLine(" Pattern: {0}", nonbacktrackingPattern);
if (match.Success) {
Console.WriteLine(" Match: {0}", match.Value);
Console.WriteLine(" Group 1: {0}", match.Groups[1].Value);
}
else {
Console.WriteLine(" Match failed.");
}
}
Console.WriteLine();
}
}
// The example displays the following output:
// Input: aaaaa
// Pattern: ((?>a+))\w
// Match failed.
// Input: aaaaab
// Pattern: ((?>a+))\w
// Match: aaaaab
// Group 1: aaaaa

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"aaaaa", "aaaaab"}
Dim nonbacktrackingPattern As String = "((?>a+))\w"
Dim match As Match

For Each input As String In inputs


Console.WriteLine("Input: {0}", input)
match = Regex.Match(input, nonbacktrackingPattern)
Console.WriteLine(" Pattern: {0}", nonbacktrackingPattern)
If match.Success Then
Console.WriteLine(" Match: {0}", match.Value)
Console.WriteLine(" Group 1: {0}", match.Groups(1).Value)
Else
Console.WriteLine(" Match failed.")
End If
Next
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Input: aaaaa
' Pattern: ((?>a+))\w
' Match failed.
' Input: aaaaab
' Pattern: ((?>a+))\w
' Match: aaaaab
' Group 1: aaaaa
Para más información sobre los grupos atómicos, vea Construcciones de agrupamiento.
La búsqueda de coincidencias de derecha a izquierda se especifica al proporcionar la opción
RegexOptions.RightToLeft a un constructor de clase Regex o a un método coincidente de instancia estática.
Esta característica es útil al realizar búsquedas de derecha a izquierda en lugar de izquierda a derecha, o en
los casos en los que es más eficaz iniciar una búsqueda de coincidencias en la parte derecha del patrón, en
lugar de la izquierda. Como se muestra en el ejemplo siguiente, el uso de la búsqueda de coincidencias de
derecha a izquierda puede cambiar el comportamiento de los cuantificadores expansivos. En el ejemplo se
realizan dos búsquedas de una oración que termina con un número. La búsqueda de izquierda a derecha
que usa el cuantificador expansivo + coincide con uno de los seis dígitos de la oración, mientras que la
búsqueda de derecha a izquierda coincide con los seis dígitos. Para ver una descripción del patrón de
expresión regular, consulte el ejemplo que ilustra los cuantificadores diferidos anteriormente en esta
sección.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string greedyPattern = @".+(\d+)\.";
string input = "This sentence ends with the number 107325.";
Match match;

// Match from left-to-right using lazy quantifier .+?.


match = Regex.Match(input, greedyPattern);
if (match.Success)
Console.WriteLine("Number at end of sentence (left-to-right): {0}",
match.Groups[1].Value);
else
Console.WriteLine("{0} finds no match.", greedyPattern);

// Match from right-to-left using greedy quantifier .+.


match = Regex.Match(input, greedyPattern, RegexOptions.RightToLeft);
if (match.Success)
Console.WriteLine("Number at end of sentence (right-to-left): {0}",
match.Groups[1].Value);
else
Console.WriteLine("{0} finds no match.", greedyPattern);
}
}
// The example displays the following output:
// Number at end of sentence (left-to-right): 5
// Number at end of sentence (right-to-left): 107325
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim greedyPattern As String = ".+(\d+)\."
Dim input As String = "This sentence ends with the number 107325."
Dim match As Match

' Match from left-to-right using lazy quantifier .+?.


match = Regex.Match(input, greedyPattern)
If match.Success Then
Console.WriteLine("Number at end of sentence (left-to-right): {0}",
match.Groups(1).Value)
Else
Console.WriteLine("{0} finds no match.", greedyPattern)
End If

' Match from right-to-left using greedy quantifier .+.


match = Regex.Match(input, greedyPattern, RegexOptions.RightToLeft)
If match.Success Then
Console.WriteLine("Number at end of sentence (right-to-left): {0}",
match.Groups(1).Value)
Else
Console.WriteLine("{0} finds no match.", greedyPattern)
End If
End Sub
End Module
' The example displays the following output:
' Number at end of sentence (left-to-right): 5
' Number at end of sentence (right-to-left): 107325

Para obtener más información sobre la búsqueda de coincidencias de derecha a izquierda, consulte
Opciones de expresiones regulares.
Búsqueda tardía positiva y negativa: (?<= subexpresión ) para la búsqueda tardía positiva y (?<!
subexpresión ) para la búsqueda tardía negativa. Esta característica es parecida a la búsqueda anticipada,
que se describe anteriormente en este tema. Dado que el motor de expresiones regulares permite una
búsqueda de coincidencias completa de derecha a izquierda, las expresiones regulares permiten búsquedas
tardías sin restricciones. La búsqueda tardía positiva y negativa también se puede usar para evitar anidar
los cuantificadores cuando la subexpresión anidada es un superconjunto de una expresión exterior. Las
expresiones regulares con cuantificadores anidados suelen ofrecer un rendimiento bajo. Por ejemplo, en el
ejemplo siguiente se comprueba que una cadena empieza y acaba con un carácter alfanumérico y que
cualquier otro carácter de la cadena es de un subconjunto más grande. Forma parte de la expresión regular
usada para validar direcciones de correo electrónico. Para obtener más información, vea Procedimiento:
Comprobación de que las cadenas están en un formato de correo electrónico válido.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "jack.sprat", "dog#", "dog#1", "me.myself",
"me.myself!" };
string pattern = @"^[A-Z0-9]([-!#$%&'.*+/=?^`{}|~\w])*(?<=[A-Z0-9])$";
foreach (string input in inputs) {
if (Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("{0}: Valid", input);
else
Console.WriteLine("{0}: Invalid", input);
}
}
}
// The example displays the following output:
// jack.sprat: Valid
// dog#: Invalid
// dog#1: Valid
// me.myself: Valid
// me.myself!: Invalid

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"jack.sprat", "dog#", "dog#1", "me.myself",
"me.myself!"}
Dim pattern As String = "^[A-Z0-9]([-!#$%&'.*+/=?^`{}|~\w])*(?<=[A-Z0-9])$"
For Each input As String In inputs
If Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase) Then
Console.WriteLine("{0}: Valid", input)
Else
Console.WriteLine("{0}: Invalid", input)
End If
Next
End Sub
End Module
' The example displays the following output:
' jack.sprat: Valid
' dog#: Invalid
' dog#1: Valid
' me.myself: Valid
' me.myself!: Invalid

La expresión regular ^[A-Z0-9]([-!#$%&'.*+/=?^`{}|~\w])*(?<=[A-Z0-9])$ se define como se muestra en la


tabla siguiente.

M O DELO DESC RIP C IÓ N

^ Empieza la búsqueda de coincidencias en el principio de la


cadena.

[A-Z0-9] Coincide con cualquier carácter numérico o alfanumérico.


(La comparación no distingue mayúsculas de minúsculas).
M O DELO DESC RIP C IÓ N

([-!#$%&'.*+/=?^`{}|~\w])* Coincide con cero o más repeticiones de cualquier carácter


de palabra o de cualquiera de los caracteres siguientes: -,
!, #, $, %, &, ', ., *, +, /, =, ?, ^, `, {, }, | o ~.

(?<=[A-Z0-9]) Realiza una búsqueda tardía en el carácter anterior, que


debe ser numérico o alfanumérico. (La comparación no
distingue mayúsculas de minúsculas).

$ Finalizar la búsqueda al final de la cadena.

Para obtener más información sobre la búsqueda tardía positiva y negativa, consulte Construcciones de
agrupamiento.

Artículos relacionados
T IT L E DESC RIP C IÓ N

Retroceso Proporciona información sobre la manera en que el retroceso


de expresiones regulares se bifurca para buscar coincidencias
alternativas.

Compilar y reutilizar Proporciona información sobre cómo compilar y reutilizar


expresiones regulares para aumentar el rendimiento.

Seguridad para subprocesos Proporciona información sobre la seguridad para subprocesos


de expresiones regulares y explica cuándo se debe sincronizar
el acceso a objetos de expresión regular.

Expresiones regulares de .NET Framework Proporciona información general sobre el aspecto del lenguaje
de programación de expresiones regulares.

Modelo de objetos de expresión regular Proporciona información y ejemplos de código que muestran
cómo usar las clases de expresiones regulares.

Lenguaje de expresiones regulares: referencia rápida Ofrece información sobre el conjunto de caracteres,
operadores y construcciones que se pueden usar para definir
expresiones regulares.

Referencia
System.Text.RegularExpressions
Retroceso en expresiones regulares
16/09/2020 • 37 minutes to read • Edit Online

El retroceso se produce cuando un patrón de expresión regular contiene cuantificadores o construcciones de


alternancia opcionales y el motor de expresiones regulares vuelve a un estado guardado anterior para continuar
la búsqueda de una coincidencia. El retroceso es fundamental para la eficacia de las expresiones regulares;
permite que las expresiones sean eficaces y flexibles, y que coincidan con modelos muy complejos. Al mismo
tiempo, esta eficacia tiene un costo. El retroceso suele ser el factor único más importante que afecta al
rendimiento del motor de expresiones regulares. Afortunadamente, el desarrollador tiene control sobre el
comportamiento del motor de expresiones regulares y cómo usa el retroceso. En este tema se explica cómo
funciona el retroceso y cómo se puede controlar.

NOTE
En general, un motor NFA (autómata finito no determinista), como el motor de expresiones regulares de .NET, se encarga
de crear expresiones regulares eficaces y rápidas para el desarrollador.

Comparación lineal sin retroceso


Si un patrón de expresión regular no tiene ningún cuantificador o construcción de alternancia opcional, el motor
de expresiones regulares se ejecuta en tiempo lineal. Es decir, una vez que el motor de expresiones regulares
hace coincidir el primer elemento de lenguaje del patrón con texto de la cadena de entrada, intenta buscar una
coincidencia del siguiente elemento de lenguaje del patrón con el siguiente carácter o grupo de caracteres de la
cadena de entrada. Este proceso continúa hasta que la coincidencia se realiza correctamente o se produce un
error. En cualquier caso, el motor de expresiones regulares avanza de carácter en carácter por la cadena de
entrada.
Esto se muestra en el ejemplo siguiente. La expresión regular e{2}\w\b busca dos apariciones de la letra "e"
seguida de cualquier carácter alfabético seguido de un límite de palabras.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "needing a reed";
string pattern = @"e{2}\w\b";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("{0} found at position {1}",
match.Value, match.Index);
}
}
// The example displays the following output:
// eed found at position 11
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "needing a reed"
Dim pattern As String = "e{2}\w\b"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("{0} found at position {1}", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' eed found at position 11

Aunque esta expresión regular incluye el cuantificador {2} , se evalúa de manera lineal. El motor de expresiones
regulares no retrocede porque {2} no es un cuantificador opcional, ya que especifica un número exacto y no
un número variable de veces que la subexpresión anterior debe coincidir. Como resultado, el motor de
expresiones regulares intenta hacer coincidir el patrón de expresión regular con la cadena de entrada como se
muestra en la tabla siguiente.

O P ERA C IÓ N P O SIC IÓ N EN EL PAT RÓ N P O SIC IÓ N EN L A C A DEN A RESULTA DO

1 h "needing a reed" (índice 0) Ninguna coincidencia.

2 h "eeding a reed" (índice 1) Posible coincidencia.

3 e{2} "eding a reed" (índice 2) Posible coincidencia.

4 \w "ding a reed" (índice 3) Posible coincidencia.

5 \b "ing a reed" (índice 4) Error en posible


coincidencia.

6 h "eding a reed" (índice 2) Posible coincidencia.

7 e{2} "ding a reed" (índice 3) Error en posible


coincidencia.

8 h "ding a reed" (índice 3) Se produce un error de


coincidencia.

9 h "ing a reed" (índice 4) Ninguna coincidencia.

10 h "ng a reed" (índice 5) Ninguna coincidencia.

11 h "g a reed" (índice 6) Ninguna coincidencia.

12 h " a reed" (índice 7) Ninguna coincidencia.

13 h “a reed” (índice 8) Ninguna coincidencia.

14 h " reed" (índice 9) Ninguna coincidencia.

15 h “reed” (índice 10) Ninguna coincidencia


O P ERA C IÓ N P O SIC IÓ N EN EL PAT RÓ N P O SIC IÓ N EN L A C A DEN A RESULTA DO

16 h "eed" (índice 11) Posible coincidencia.

17 e{2} "ed" (índice 12) Posible coincidencia.

18 \w "d" (índice 13) Posible coincidencia.

19 \b "" (índice 14) Coincidencia.

Si un patrón de expresión regular no incluye ningún cuantificador o construcción de alternancia opcional, el


número máximo de comparaciones necesarias para hacer coincidir el patrón de expresión regular con la cadena
de entrada es aproximadamente equivalente al número de caracteres de la cadena de entrada. En este caso, el
motor de expresiones regulares usa 19 comparaciones para identificar posibles coincidencias en esta cadena de
13 caracteres. Es decir, el motor de expresiones regulares se ejecuta en tiempo prácticamente lineal si no
contiene ningún cuantificador o construcción de alternancia opcional.

Retroceso con cuantificadores o construcciones de alternancia


opcionales
Cuando una expresión regular incluye cuantificadores o construcciones de alternancia opcionales, la evaluación
de la cadena de entrada ya no es lineal. La coincidencia de modelos con un motor NFA está controlada por los
elementos de lenguaje de la expresión regular y no por los caracteres que se hacen coincidir en la cadena de
entrada. Por tanto, el motor de expresiones regulares intenta hacer coincidir totalmente subexpresiones
opcionales o alternativas. Cuando avanza al elemento de lenguaje siguiente de la subexpresión y la coincidencia
no se realiza correctamente, el motor de expresiones regulares puede abandonar una parte de su coincidencia
correcta y volver a un estado guardado anterior para hacer coincidir la expresión regular en su conjunto con la
cadena de entrada. Este proceso de volver a un estado guardado anterior para encontrar una coincidencia se
denomina retroceso.
Por ejemplo, considere el patrón de expresión regular .*(es) , que hace coincidir los caracteres "es" y todos los
caracteres que lo preceden. Como se muestra en el ejemplo siguiente, si la cadena de entrada es "Essential
services are provided by regular expressions.", el patrón coincide con toda la cadena hasta e incluyendo "es" en
"expressions".

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "Essential services are provided by regular expressions.";
string pattern = ".*(es)";
Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
if (m.Success) {
Console.WriteLine("'{0}' found at position {1}",
m.Value, m.Index);
Console.WriteLine("'es' found at position {0}",
m.Groups[1].Index);
}
}
}
// 'Essential services are provided by regular expres' found at position 0
// 'es' found at position 47
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "Essential services are provided by regular expressions."
Dim pattern As String = ".*(es)"
Dim m As Match = Regex.Match(input, pattern, RegexOptions.IgnoreCase)
If m.Success Then
Console.WriteLine("'{0}' found at position {1}", _
m.Value, m.Index)
Console.WriteLine("'es' found at position {0}", _
m.Groups(1).Index)
End If
End Sub
End Module
' 'Essential services are provided by regular expres' found at position 0
' 'es' found at position 47

Para ello, el motor de expresiones regulares usa el retroceso de la manera siguiente:


Coincide con .* (que coincide con cero, una o más apariciones de cualquier carácter) con la cadena de
entrada completa.
Intenta hacer coincidir "e" en el patrón de expresión regular. Sin embargo, la cadena de entrada no tiene
ningún carácter restante disponible para coincidir.
Retrocede hasta su última coincidencia correcta, "Essential services are provided by regular expressions",
e intenta hacer coincidir "e" con el punto del final de la frase. Se produce un error de coincidencia.
Sigue retrocediendo hasta una coincidencia correcta anterior, de carácter en carácter, hasta que la
subcadena coincidente tentativa sea "Essential services are provided by regular expr". A continuación,
compara la "e" del patrón con la segunda "e" de "expressions" y encuentra una coincidencia.
Compara la "s" del patrón con la "s" que sigue al carácter "e" coincidente (la primera "s" de "expressions").
La coincidencia es correcta.
Cuando se usa retroceso, la coincidencia del patrón de expresión regular con la cadena de entrada, que tiene 55
caracteres de longitud, necesita 67 operaciones de comparación. Normalmente, si un patrón de expresión
regular tiene una única construcción de alternancia o un único cuantificador opcional, el número de operaciones
de comparación necesarias para que coincida con el patrón es más del doble del número de caracteres de la
cadena de entrada.

Retroceso con cuantificadores opcionales anidados


El número de operaciones de comparación necesarias para coincidir con un patrón de expresión regular puede
aumentar exponencial si el patrón incluye muchas construcciones de alternancia, si incluye construcciones de
alternancia anidadas o, lo que es más frecuente, si incluye cuantificadores opcionales anidados. Por ejemplo, el
patrón de expresión regular ^(a+)+$ está diseñado para coincidir con una cadena completa que contiene uno o
más caracteres "a". El ejemplo proporciona dos cadenas de entrada de longitud idéntica, pero solo la primera
cadena coincide con el patrón. La clase System.Diagnostics.Stopwatch se usa para determinar cuánto tiempo
tarda la operación de coincidencia.
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "^(a+)+$";
string[] inputs = { "aaaaaa", "aaaaa!" };
Regex rgx = new Regex(pattern);
Stopwatch sw;

foreach (string input in inputs) {


sw = Stopwatch.StartNew();
Match match = rgx.Match(input);
sw.Stop();
if (match.Success)
Console.WriteLine("Matched {0} in {1}", match.Value, sw.Elapsed);
else
Console.WriteLine("No match found in {0}", sw.Elapsed);
}
}
}

Imports System.Diagnostics
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^(a+)+$"
Dim inputs() As String = {"aaaaaa", "aaaaa!"}
Dim rgx As New Regex(pattern)
Dim sw As Stopwatch

For Each input As String In inputs


sw = Stopwatch.StartNew()
Dim match As Match = rgx.Match(input)
sw.Stop()
If match.Success Then
Console.WriteLine("Matched {0} in {1}", match.Value, sw.Elapsed)
Else
Console.WriteLine("No match found in {0}", sw.Elapsed)
End If
Next
End Sub
End Module

Como muestra el resultado del ejemplo, el motor de expresiones regulares tardó en descubrir que una cadena
de entrada no coincidía con el patrón aproximadamente el doble que en identificar una cadena coincidente. Esto
se debe a que una coincidencia infructuosa siempre representa un escenario de caso peor. El motor de
expresiones regulares debe usar la expresión regular para seguir todas las rutas posibles a través de los datos
antes de poder concluir que la coincidencia no es correcta y los paréntesis anidados crean muchas rutas de
acceso adicionales a través de los datos. El motor de expresiones regulares concluye que la segunda cadena no
coincide con el patrón; para ello, hace lo siguiente:
Comprueba que está al principio de la cadena y, a continuación, busca una coincidencia de los cinco
primeros caracteres de la cadena con el patrón a+ . A continuación, determina que no hay ningún grupo
adicional de caracteres "a" en la cadena. Por último, comprueba que está al final de la cadena. Como en la
cadena queda un carácter adicional, la coincidencia produce un error. Esta coincidencia errónea necesita 9
comparaciones. El motor de expresiones regulares también guarda información de estado de las
coincidencias de "a" (que llamaremos coincidencia 1), "aa" (coincidencia 2), "aaa" (coincidencia 3) y "aaaa"
(coincidencia 4).
Vuelve a la coincidencia 4 guardada previamente. Determina que hay un carácter "a" adicional para
asignar a un grupo capturado adicional. Por último, comprueba que está al final de la cadena. Como en la
cadena queda un carácter adicional, la coincidencia produce un error. Esta coincidencia errónea necesita 4
comparaciones. Hasta ahora, se han realizado un total de 13 comparaciones.
Vuelve a la coincidencia 3 guardada previamente. Determina que hay dos caracteres "a" adicionales para
asignar a un grupo capturado adicional. Sin embargo, se produce un error en la prueba de fin de cadena.
Vuelva a la coincidencia 3 e intenta hacer coincidir los dos caracteres "a" adicionales en dos grupos
capturados adicionales. Se sigue produciendo un error en la prueba de fin de cadena. Estas coincidencias
con error necesitan 12 comparaciones. Hasta ahora, se ha realizado un total de 25 comparaciones.
La comparación de la cadena de entrada con la expresión regular continúa de esta manera hasta que el motor
de expresiones regulares ha intentado todas las posibles combinaciones de coincidencias y, a continuación,
concluye que no hay ninguna coincidencia. Debido a los cuantificadores anidados, esta comparación es O(2n ) o
una operación exponencial, donde n es el número de caracteres de la cadena de entrada. Esto significa que, en el
peor de los casos, una cadena de entrada de 30 caracteres necesita aproximadamente 1.073.741.824
comparaciones y una cadena de entrada de 40 caracteres necesita aproximadamente 1.099.511.627.776
comparaciones. Si usa cadenas de estas longitudes o incluso mayores, los métodos de expresión regular pueden
tardar mucho tiempo en completarse cuando procesan datos de entrada que no coinciden con el patrón de
expresión regular.

Controlar el retroceso
El retroceso permite crear expresiones regulares eficaces y flexibles. Sin embargo, como se ha mostrado en la
sección anterior, estas ventajas pueden conllevar un bajo rendimiento inaceptable. Para evitar el retroceso
excesivo, se debe definir un intervalo de tiempo de espera cuando se instancie un objeto Regex o se llame a un
método estático de coincidencia de expresión regular. Esta técnica se analiza en la sección siguiente. Además,
.NET admite tres elementos del lenguaje de expresiones regulares que limitan o suprimen la vuelta atrás
(backtracking) y que admiten expresiones regulares complejas con poca o ninguna reducción del rendimiento:
grupos atómicos, aserciones de búsqueda retrasada (lookbehind) y aserciones de búsqueda anticipada
(lookahead). Para obtener más información sobre cada elemento del lenguaje, vea Construcciones de
agrupamiento.
Definición de un intervalo de tiempo de espera
A partir de .NET Framework 4.5, se puede establecer un valor de tiempo de espera que representa el intervalo
más largo en el que el motor de expresión regular buscará una coincidencia única antes de abandonar el intento
y generar una excepción RegexMatchTimeoutException. El intervalo de tiempo de espera se especifica al
proporcionar un valor TimeSpan al constructor Regex(String, RegexOptions, TimeSpan) para las expresiones
regulares de instancias. Además, cada método estático de coincidencia de patrones tiene una sobrecarga con un
parámetro TimeSpan que permite especificar un valor de tiempo de espera. De forma predeterminada, el
intervalo de tiempo de espera se establece en Regex.InfiniteMatchTimeout y el motor de expresiones regulares
no agota dicho tiempo.

IMPORTANT
Recomendamos que se establezca siempre un intervalo de tiempo de espera si la expresión regular se basa en el
retroceso.

Una excepción RegexMatchTimeoutException indica que el motor de expresiones regulares no pudo encontrar
una coincidencia en el intervalo de tiempo de espera especificado, pero no indica por qué se produjo la
excepción. La razón puede ser un retroceso excesivo, aunque también es posible que el intervalo de tiempo de
espera establecido fuera demasiado bajo, dada la carga del sistema en el momento en que se produjo la
excepción. Cuando se controla la excepción, se puede elegir entre abandonar otras coincidencias con la cadena
de entrada o incrementar el intervalo de tiempo de espera y reintentar la operación de coincidencia.
Por ejemplo, el código siguiente llama al constructor Regex(String, RegexOptions, TimeSpan) para crear
instancias de un objeto Regex con un valor de tiempo de espera de un segundo. El patrón de expresión regular
(a+)+$ , que coincide con una o más secuencias de uno o varios caracteres "a" al final de una línea, está sujeto a
un retroceso excesivo. Si se produce una excepción RegexMatchTimeoutException , el ejemplo incrementa el
valor de tiempo de espera hasta un intervalo máximo de tres segundos. Después, abandona el intento de
coincidir con el patrón.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Security;
using System.Text.RegularExpressions;
using System.Threading;

public class Example


{
const int MaxTimeoutInSeconds = 3;

public static void Main()


{
string pattern = @"(a+)+$"; // DO NOT REUSE THIS PATTERN.
Regex rgx = new Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
Stopwatch sw = null;

string[] inputs= { "aa", "aaaa>",


"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaa>",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>" };

foreach (var inputValue in inputs) {


Console.WriteLine("Processing {0}", inputValue);
bool timedOut = false;
do {
try {
sw = Stopwatch.StartNew();
// Display the result.
if (rgx.IsMatch(inputValue)) {
sw.Stop();
Console.WriteLine(@"Valid: '{0}' ({1:ss\.fffffff} seconds)",
inputValue, sw.Elapsed);
}
else {
sw.Stop();
Console.WriteLine(@"'{0}' is not a valid string. ({1:ss\.fffff} seconds)",
inputValue, sw.Elapsed);
}
}
catch (RegexMatchTimeoutException e) {
sw.Stop();
// Display the elapsed time until the exception.
Console.WriteLine(@"Timeout with '{0}' after {1:ss\.fffff}",
inputValue, sw.Elapsed);
Thread.Sleep(1500); // Pause for 1.5 seconds.

// Increase the timeout interval and retry.


TimeSpan timeout = e.MatchTimeout.Add(TimeSpan.FromSeconds(1));
if (timeout.TotalSeconds > MaxTimeoutInSeconds) {
Console.WriteLine("Maximum timeout interval of {0} seconds exceeded.",
MaxTimeoutInSeconds);
timedOut = false;
}
else {
else {
Console.WriteLine("Changing the timeout interval to {0}",
timeout);
rgx = new Regex(pattern, RegexOptions.IgnoreCase, timeout);
timedOut = true;
}
}
} while (timedOut);
Console.WriteLine();
}
}
}
// The example displays output like the following :
// Processing aa
// Valid: 'aa' (00.0000779 seconds)
//
// Processing aaaa>
// 'aaaa>' is not a valid string. (00.00005 seconds)
//
// Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
// Valid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' (00.0000043 seconds)
//
// Processing aaaaaaaaaaaaaaaaaaaaaa>
// Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 01.00469
// Changing the timeout interval to 00:00:02
// Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 02.01202
// Changing the timeout interval to 00:00:03
// Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 03.01043
// Maximum timeout interval of 3 seconds exceeded.
//
// Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
// Timeout with 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>' after 03.01018
// Maximum timeout interval of 3 seconds exceeded.

Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Security
Imports System.Text.RegularExpressions
Imports System.Threading

Module Example
Const MaxTimeoutInSeconds As Integer = 3

Public Sub Main()


Dim pattern As String = "(a+)+$" ' DO NOT REUSE THIS PATTERN.
Dim rgx As New Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1))
Dim sw As Stopwatch = Nothing

Dim inputs() As String = {"aa", "aaaa>",


"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaa>",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>"}

For Each inputValue In inputs


Console.WriteLine("Processing {0}", inputValue)
Dim timedOut As Boolean = False
Do
Try
sw = Stopwatch.StartNew()
' Display the result.
If rgx.IsMatch(inputValue) Then
sw.Stop()
Console.WriteLine("Valid: '{0}' ({1:ss\.fffffff} seconds)",
inputValue, sw.Elapsed)
Else
sw.Stop()
Console.WriteLine("'{0}' is not a valid string. ({1:ss\.fffff} seconds)",
inputValue, sw.Elapsed)
inputValue, sw.Elapsed)
End If
Catch e As RegexMatchTimeoutException
sw.Stop()
' Display the elapsed time until the exception.
Console.WriteLine("Timeout with '{0}' after {1:ss\.fffff}",
inputValue, sw.Elapsed)
Thread.Sleep(1500) ' Pause for 1.5 seconds.

' Increase the timeout interval and retry.


Dim timeout As TimeSpan = e.MatchTimeout.Add(TimeSpan.FromSeconds(1))
If timeout.TotalSeconds > MaxTimeoutInSeconds Then
Console.WriteLine("Maximum timeout interval of {0} seconds exceeded.",
MaxTimeoutInSeconds)
timedOut = False
Else
Console.WriteLine("Changing the timeout interval to {0}",
timeout)
rgx = New Regex(pattern, RegexOptions.IgnoreCase, timeout)
timedOut = True
End If
End Try
Loop While timedOut
Console.WriteLine()
Next
End Sub
End Module
' The example displays output like the following:
' Processing aa
' Valid: 'aa' (00.0000779 seconds)
'
' Processing aaaa>
' 'aaaa>' is not a valid string. (00.00005 seconds)
'
' Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
' Valid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' (00.0000043 seconds)
'
' Processing aaaaaaaaaaaaaaaaaaaaaa>
' Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 01.00469
' Changing the timeout interval to 00:00:02
' Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 02.01202
' Changing the timeout interval to 00:00:03
' Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 03.01043
' Maximum timeout interval of 3 seconds exceeded.
'
' Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
' Timeout with 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>' after 03.01018
' Maximum timeout interval of 3 seconds exceeded.

Grupos atómicos
El elemento de lenguaje (?> subexpresión ) suprime la vuelta atrás (backtracking) en una subexpresión. Una
vez que coincida correctamente, no abandonará ninguna parte de su coincidencia a la vuelta atrás
(backtracking) posterior. Por ejemplo, en el patrón (?>\w*\d*)1 , si no se puede hacer coincidir 1 , \d* no
abandonará ninguna coincidencia, aunque esto signifique que permita que 1 coincida correctamente. Los
grupos atómicos pueden ayudar a evitar los problemas de rendimiento asociados a las coincidencias con error.
En el ejemplo siguiente se muestra cómo la supresión del retroceso mejora el rendimiento cuando se usan
cuantificadores anidados. Mide el tiempo necesario para que el motor de expresiones regulares determine que
una cadena de entrada no coincide con dos expresiones regulares. La primera expresión regular usa el retroceso
para intentar buscar una coincidencia de una cadena que contiene una o más apariciones de uno o más dígitos
hexadecimales, seguidas de un signo de dos puntos, seguido de uno o más dígitos hexadecimales, seguido de
dos signos de dos puntos. La segunda expresión regular es idéntica a la primera, salvo que deshabilita el
retroceso. Como muestra el resultado del ejemplo, la mejora de rendimiento que supone deshabilitar el
retroceso es significativa.
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "b51:4:1DB:9EE1:5:27d60:f44:D4:cd:E:5:0A5:4a:D24:41Ad:";
bool matched;
Stopwatch sw;

Console.WriteLine("With backtracking:");
string backPattern = "^(([0-9a-fA-F]{1,4}:)*([0-9a-fA-F]{1,4}))*(::)$";
sw = Stopwatch.StartNew();
matched = Regex.IsMatch(input, backPattern);
sw.Stop();
Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, backPattern), sw.Elapsed);
Console.WriteLine();

Console.WriteLine("Without backtracking:");
string noBackPattern = "^((?>[0-9a-fA-F]{1,4}:)*(?>[0-9a-fA-F]{1,4}))*(::)$";
sw = Stopwatch.StartNew();
matched = Regex.IsMatch(input, noBackPattern);
sw.Stop();
Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, noBackPattern), sw.Elapsed);
}
}
// The example displays output like the following:
// With backtracking:
// Match: False in 00:00:27.4282019
//
// Without backtracking:
// Match: False in 00:00:00.0001391
Imports System.Diagnostics
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "b51:4:1DB:9EE1:5:27d60:f44:D4:cd:E:5:0A5:4a:D24:41Ad:"
Dim matched As Boolean
Dim sw As Stopwatch

Console.WriteLine("With backtracking:")
Dim backPattern As String = "^(([0-9a-fA-F]{1,4}:)*([0-9a-fA-F]{1,4}))*(::)$"
sw = Stopwatch.StartNew()
matched = Regex.IsMatch(input, backPattern)
sw.Stop()
Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, backPattern), sw.Elapsed)
Console.WriteLine()

Console.WriteLine("Without backtracking:")
Dim noBackPattern As String = "^((?>[0-9a-fA-F]{1,4}:)*(?>[0-9a-fA-F]{1,4}))*(::)$"
sw = Stopwatch.StartNew()
matched = Regex.IsMatch(input, noBackPattern)
sw.Stop()
Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, noBackPattern), sw.Elapsed)
End Sub
End Module
' The example displays the following output:
' With backtracking:
' Match: False in 00:00:27.4282019
'
' Without backtracking:
' Match: False in 00:00:00.0001391

aserciones de búsqueda tardía


.NET incluye dos elementos de lenguaje, (?<= subexpresión ) y (?<! subexpresión ) , que buscan una
coincidencia con el carácter o los caracteres anteriores de la cadena de entrada. Ambos elementos de lenguaje
son aserciones de ancho cero, es decir, determinan si el carácter o los caracteres que preceden inmediatamente
al carácter actual coinciden con subexpression, sin avanzar o retroceder.
(?<= subexpression ) es una aserción de búsqueda tardía positiva, es decir, el carácter o los caracteres
situados antes de la posición actual deben coincidir con subexpression. (?<! subexpression ) es una aserción
de búsqueda tardía negativa, es decir, el carácter o los caracteres situados antes de la posición actual no deben
coincidir con subexpression. Tanto las aserciones de búsqueda tardía positivas como las negativas son más
útiles cuando subexpression es un subconjunto de la subexpresión anterior.
En el ejemplo siguiente se usan dos patrones de expresiones regulares equivalentes que validan el nombre de
usuario de una dirección de correo electrónico. El primer patrón tiene un rendimiento bajo debido a un
retroceso excesivo. El segundo patrón modifica la primera expresión regular reemplazando un cuantificador
anidado con una aserción de búsqueda tardía positiva. El resultado del ejemplo muestra el tiempo de ejecución
del método Regex.IsMatch .
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
Stopwatch sw;
string input = "test@contoso.com";
bool result;

string pattern = @"^[0-9A-Z]([-.\w]*[0-9A-Z])?@";


sw = Stopwatch.StartNew();
result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
sw.Stop();
Console.WriteLine("Match: {0} in {1}", result, sw.Elapsed);

string behindPattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@";


sw = Stopwatch.StartNew();
result = Regex.IsMatch(input, behindPattern, RegexOptions.IgnoreCase);
sw.Stop();
Console.WriteLine("Match with Lookbehind: {0} in {1}", result, sw.Elapsed);
}
}
// The example displays output similar to the following:
// Match: True in 00:00:00.0017549
// Match with Lookbehind: True in 00:00:00.0000659

Module Example
Public Sub Main()
Dim sw As Stopwatch
Dim input As String = "test@contoso.com"
Dim result As Boolean

Dim pattern As String = "^[0-9A-Z]([-.\w]*[0-9A-Z])?@"


sw = Stopwatch.StartNew()
result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase)
sw.Stop()
Console.WriteLine("Match: {0} in {1}", result, sw.Elapsed)

Dim behindPattern As String = "^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@"


sw = Stopwatch.StartNew()
result = Regex.IsMatch(input, behindPattern, RegexOptions.IgnoreCase)
sw.Stop()
Console.WriteLine("Match with Lookbehind: {0} in {1}", result, sw.Elapsed)
End Sub
End Module
' The example displays output similar to the following:
' Match: True in 00:00:00.0017549
' Match with Lookbehind: True in 00:00:00.0000659

El primer patrón de expresión regular, ^[0-9A-Z]([-.\w]*[0-9A-Z])*@ , se define como se muestra en la tabla


siguiente.

M O DELO DESC RIP C IÓ N

^ Iniciar la búsqueda de coincidencias en el principio de la


cadena.
M O DELO DESC RIP C IÓ N

[0-9A-Z] Buscar coincidencias de un carácter alfanumérico. Esta


comparación no distingue mayúsculas de minúsculas, ya que
se llama al método Regex.IsMatch con la opción
RegexOptions.IgnoreCase .

[-.\w]* Buscar coincidencias con cero, una o más apariciones de un


guión, un punto o un carácter alfabético.

[0-9A-Z] Buscar coincidencias de un carácter alfanumérico.

([-.\w]*[0-9A-Z])* Buscar coincidencias con cero o más apariciones de la


combinación de cero o más guiones, puntos o caracteres
alfabéticos, seguidos de un carácter alfanumérico. Este es el
primer grupo de captura.

@ Buscar coincidencias con un signo ("@").

El segundo patrón de expresión regular, ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@ , emplea una aserción de búsqueda


tardía positiva. Se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

^ Iniciar la búsqueda de coincidencias en el principio de la


cadena.

[0-9A-Z] Buscar coincidencias de un carácter alfanumérico. Esta


comparación no distingue mayúsculas de minúsculas, ya que
se llama al método Regex.IsMatch con la opción
RegexOptions.IgnoreCase .

[-.\w]* Buscar coincidencias con cero o más apariciones de un


guión, un punto o un carácter alfabético.

(?<=[0-9A-Z]) Volver a examinar el último carácter coincidente y continuar


con la coincidencia si es alfanumérica. Tenga en cuenta que
los caracteres alfanuméricos son un subconjunto del
conjunto formado por puntos, guiones y todos los
caracteres alfabéticos.

@ Buscar coincidencias con un signo ("@").

aserciones de búsqueda anticipada


.NET incluye dos elementos de lenguaje, (?= subexpresión ) y (?! subexpresión ) , que buscan una
coincidencia con el carácter o los caracteres siguientes de la cadena de entrada. Ambos elementos de lenguaje
son aserciones de ancho cero, es decir, determinan si el carácter o los caracteres que siguen inmediatamente al
carácter actual coinciden con subexpression, sin avanzar o retroceder.
(?= subexpression ) es una aserción de búsqueda anticipada positiva, es decir, el carácter o los caracteres
situados después de la posición actual deben coincidir con subexpression. (?! subexpression ) es una aserción
de búsqueda anticipada negativa, es decir, el carácter o los caracteres situados después de la posición actual no
deben coincidir con subexpression. Tanto las aserciones de búsqueda anticipada positivas como las negativas
son más útiles cuando subexpression es un subconjunto de la siguiente subexpresión.
En el ejemplo siguiente se usan dos patrones de expresiones regulares que validan un nombre de tipo completo.
El primer patrón tiene un rendimiento bajo debido a un retroceso excesivo. El segundo modifica la primera
expresión regular reemplazando un cuantificador anidado con una aserción de búsqueda anticipada positiva. El
resultado del ejemplo muestra el tiempo de ejecución del método Regex.IsMatch .

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "aaaaaaaaaaaaaaaaaaaaaa.";
bool result;
Stopwatch sw;

string pattern = @"^(([A-Z]\w*)+\.)*[A-Z]\w*$";


sw = Stopwatch.StartNew();
result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
sw.Stop();
Console.WriteLine("{0} in {1}", result, sw.Elapsed);

string aheadPattern = @"^((?=[A-Z])\w+\.)*[A-Z]\w*$";


sw = Stopwatch.StartNew();
result = Regex.IsMatch(input, aheadPattern, RegexOptions.IgnoreCase);
sw.Stop();
Console.WriteLine("{0} in {1}", result, sw.Elapsed);
}
}
// The example displays the following output:
// False in 00:00:03.8003793
// False in 00:00:00.0000866

Imports System.Diagnostics
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "aaaaaaaaaaaaaaaaaaaaaa."
Dim result As Boolean
Dim sw As Stopwatch

Dim pattern As String = "^(([A-Z]\w*)+\.)*[A-Z]\w*$"


sw = Stopwatch.StartNew()
result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase)
sw.Stop()
Console.WriteLine("{0} in {1}", result, sw.Elapsed)

Dim aheadPattern As String = "^((?=[A-Z])\w+\.)*[A-Z]\w*$"


sw = Stopwatch.StartNew()
result = Regex.IsMatch(input, aheadPattern, RegexOptions.IgnoreCase)
sw.Stop()
Console.WriteLine("{0} in {1}", result, sw.Elapsed)
End Sub
End Module
' The example displays the following output:
' False in 00:00:03.8003793
' False in 00:00:00.0000866

El primer patrón de expresión regular, ^(([A-Z]\w*)+\.)*[A-Z]\w*$ , se define como se muestra en la tabla


siguiente.
M O DELO DESC RIP C IÓ N

^ Iniciar la búsqueda de coincidencias en el principio de la


cadena.

([A-Z]\w*)+\. Buscar coincidencias con un carácter alfabético (A-Z) seguido


de cero o más caracteres alfabéticos una o más veces,
seguidas de un punto. Esta comparación no distingue
mayúsculas de minúsculas, ya que se llama al método
Regex.IsMatch con la opción RegexOptions.IgnoreCase .

(([A-Z]\w*)+\.)* Buscar coincidencias con el patrón anterior cero o más veces.

[A-Z]\w* Buscar coincidencias con un carácter alfabético seguido de


cero o más caracteres alfabéticos.

$ Finalizar la búsqueda de coincidencias al final de la cadena de


entrada.

El segundo patrón de expresión regular, ^((?=[A-Z])\w+\.)*[A-Z]\w*$ , emplea una aserción de búsqueda


anticipada positiva. Se define como se muestra en la tabla siguiente.

M O DELO DESC RIP C IÓ N

^ Iniciar la búsqueda de coincidencias en el principio de la


cadena.

(?=[A-Z]) Examinar hacia delante el primer carácter y continuar la


búsqueda de coincidencias si es alfabético (A-Z). Esta
comparación no distingue mayúsculas de minúsculas, ya que
se llama al método Regex.IsMatch con la opción
RegexOptions.IgnoreCase .

\w+\. Buscar coincidencias con uno o más caracteres alfabéticos


seguidos de un punto.

((?=[A-Z])\w+\.)* Buscar coincidencias con el patrón de uno o varios caracteres


alfabéticos seguidos de un punto una o varias veces. El
carácter alfabético inicial debe ser alfabético.

[A-Z]\w* Buscar coincidencias con un carácter alfabético seguido de


cero o más caracteres alfabéticos.

$ Finalizar la búsqueda de coincidencias al final de la cadena de


entrada.

Vea también
Expresiones regulares de .NET
Lenguaje de expresiones regulares: referencia rápida
Cuantificadores
Construcciones de alternancia
Construcciones de agrupamiento
Compilar y volver a utilizar en expresiones regulares
16/09/2020 • 4 minutes to read • Edit Online

Puede optimizar el rendimiento de aplicaciones que usan en gran medida las expresiones regulares al comprender
cómo compila expresiones el motor de expresiones regulares y cómo se almacenan en caché las expresiones
regulares. En este tema, se describen la compilación y el almacenamiento en caché.

Expresiones regulares compiladas


De manera predeterminada, el motor de expresiones regulares compila una expresión regular en una secuencia de
instrucciones internas (son códigos de alto nivel diferentes del Lenguaje Intermedio de Microsoft, o MSIL). Cuando
el motor ejecuta una expresión regular, interpreta los códigos internos.
Si un objeto Regex se construye con la opción RegexOptions.Compiled, compila la expresión regular en código
MSIL explícito en lugar de instrucciones internas de expresiones regulares de alto nivel. De este modo, el
compilador Just-In-Time (JIT) de .NET puede convertir la expresión en código de equipo nativo para un mayor
rendimiento. El costo de construir el objeto Regex puede ser mayor, pero el de realizar coincidencias con él puede
ser mucho más pequeño.
Una alternativa consiste en utilizar expresiones regulares precompiladas. Puede compilar todas las expresiones en
un archivo DLL reutilizable con el método CompileToAssembly. Esto evita la necesidad de compilar en tiempo de
ejecución mientras sigue beneficiándose de la velocidad de las expresiones regulares compiladas.

La caché de expresiones regulares


Para mejorar el rendimiento, el motor de expresiones regulares mantiene una caché de la aplicación de expresiones
regulares compiladas. La caché almacena los patrones de expresiones regulares que se usan solo en las llamadas al
método estático. (Los patrones de expresiones regulares proporcionados a métodos de instancia no se almacenan
en caché). Esto evita la necesidad de volver a analizar una expresión en código de bytes de alto nivel cada vez que
se usa.
El valor de la propiedad static ( Shared en Visual Basic) Regex.CacheSize determina el número máximo de
expresiones regulares almacenadas en caché. De manera predeterminada, el motor de expresiones regulares
almacena en caché hasta 15 expresiones regulares compiladas. Si el número de expresiones regulares compiladas
supera el tamaño de la caché, se descarta la expresión regular usada menos recientemente y se almacena en caché
la nueva expresión regular.
La aplicación puede reutilizar las expresiones regulares en una de las siguientes formas:
Mediante el uso de un método estático del objeto Regex para definir la expresión regular. Si usa un patrón
de expresión regular que ya ha definido otra llamada al método estático, el motor de expresiones regulares
lo recuperará de la caché. Si no está disponible en la caché, el motor compilará la expresión regular y la
agregará a la caché.
Al volver a usar un objeto Regex existente siempre que sea necesario su patrón de expresión regular.
Debido a la sobrecarga de la creación de instancias de objeto y la compilación de expresiones regulares, crear y
destruir rápidamente numerosos objetos Regex es un proceso muy costoso. Para aplicaciones que usan un gran
número de expresiones regulares diferentes, puede optimizar el rendimiento al realizar llamadas a métodos Regex
estáticos y posiblemente al aumentar el tamaño de la caché de expresiones regulares.
Vea también
Expresiones regulares de .NET
Seguridad para subprocesos en expresiones regulares
16/09/2020 • 2 minutes to read • Edit Online

La clase Regex es en sí misma segura para subprocesos e inmutable (de solo lectura). Es decir, se pueden crear
objetos Regex en cualquier subproceso y compartirlos entre varios subprocesos; los métodos de coincidencia
pueden llamarse desde cualquier subproceso y no modifican nunca el estado global.
Pero los objetos de resultado (Match y MatchCollection) que devuelve Regex deben usarse en un único
subproceso. Aunque muchos de estos objetos son lógicamente inmutables, sus implementaciones pueden retrasar
el cálculo de algunos resultados para mejorar el rendimiento y, en consecuencia, los llamadores deben serializar el
acceso a ellos.
Si es necesario compartir objetos de resultado de Regex en varios subprocesos, estos objetos se pueden convertir
en instancias seguras para subprocesos llamando a sus métodos sincronizados. A excepción de los enumeradores,
todas las clases de expresiones regulares son seguras para subprocesos o pueden convertirse en objetos seguros
para subprocesos mediante un método sincronizado.
Los enumeradores son la única excepción. Las aplicaciones debe serializar las llamadas a enumeradores de
colecciones. La regla es que, si una colección puede enumerarse simultáneamente en más de un subproceso, se
deben sincronizar los métodos de enumerador en el objeto raíz de la colección que recorre el enumerador.

Vea también
Expresiones regulares de .NET
Ejemplo de expresiones regulares: Buscar etiquetas
HREF
16/09/2020 • 6 minutes to read • Edit Online

En el ejemplo siguiente se busca una cadena de entrada y se muestran todos los valores href="…" y sus ubicaciones
en la cadena.

WARNING
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de
expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions y provocar un ataque por
denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions pasan un tiempo de
expiración.

El objeto Regex
Dado que el método DumpHRefs puede llamarse varias veces desde el código de usuario, usa el método static (
Shared en Visual Basic) Regex.Match(String, String, RegexOptions). Esto permite que el motor de expresiones
regulares almacene en caché la expresión regular y evita la sobrecarga que se produciría al crear instancias de un
nuevo objeto Regex cada vez que se llamara al método. Después, se usa un objeto Match para iterar todas las
coincidencias de la cadena.

private static void DumpHRefs(string inputString)


{
Match m;
string HRefPattern = @"href\s*=\s*(?:[""'](?<1>[^""']*)[""']|(?<1>\S+))";

try
{
m = Regex.Match(inputString, HRefPattern,
RegexOptions.IgnoreCase | RegexOptions.Compiled,
TimeSpan.FromSeconds(1));
while (m.Success)
{
Console.WriteLine("Found href " + m.Groups[1] + " at "
+ m.Groups[1].Index);
m = m.NextMatch();
}
}
catch (RegexMatchTimeoutException)
{
Console.WriteLine("The matching operation timed out.");
}
}
Private Sub DumpHRefs(inputString As String)
Dim m As Match
Dim HRefPattern As String = "href\s*=\s*(?:[""'](?<1>[^""']*)[""']|(?<1>\S+))"

Try
m = Regex.Match(inputString, HRefPattern, _
RegexOptions.IgnoreCase Or RegexOptions.Compiled,
TimeSpan.FromSeconds(1))
Do While m.Success
Console.WriteLine("Found href {0} at {1}.", _
m.Groups(1), m.Groups(1).Index)
m = m.NextMatch()
Loop
Catch e As RegexMatchTimeoutException
Console.WriteLine("The matching operation timed out.")
End Try
End Sub

En el ejemplo siguiente se muestra la llamada al método DumpHRefs .

public static void Main()


{
string inputString = "My favorite web sites include:</P>" +
"<A HREF=\"http://msdn2.microsoft.com\">" +
"MSDN Home Page</A></P>" +
"<A HREF=\"http://www.microsoft.com\">" +
"Microsoft Corporation Home Page</A></P>" +
"<A HREF=\"http://blogs.msdn.com/bclteam\">" +
".NET Base Class Library blog</A></P>";
DumpHRefs(inputString);
}
// The example displays the following output:
// Found href http://msdn2.microsoft.com at 43
// Found href http://www.microsoft.com at 102
// Found href http://blogs.msdn.com/bclteam at 176

Public Sub Main()


Dim inputString As String = "My favorite web sites include:</P>" & _
"<A HREF=""http://msdn2.microsoft.com"">" & _
"MSDN Home Page</A></P>" & _
"<A HREF=""http://www.microsoft.com"">" & _
"Microsoft Corporation Home Page</A></P>" & _
"<A HREF=""http://blogs.msdn.com/bclteam"">" & _
".NET Base Class Library blog</A></P>"
DumpHRefs(inputString)
End Sub
' The example displays the following output:
' Found href http://msdn2.microsoft.com at 43
' Found href http://www.microsoft.com at 102
' Found href http://blogs.msdn.com/bclteam/) at 176

El patrón de la expresión regular href\s*=\s*(?:["'](?<1>[^"']*)["']|(?<1>\S+)) se interpreta como se muestra en


la tabla siguiente.

M O DELO DESC RIP C IÓ N

href Coincide con la cadena literal "href". La búsqueda no distingue


entre mayúsculas y minúsculas.
M O DELO DESC RIP C IÓ N

\s* Busca coincidencias con cero o más caracteres de espacio en


blanco.

= Coincide con el signo igual.

\s* Busca coincidencias con cero o más caracteres de espacio en


blanco.

(?:\["'\](?<1>\[^"'\]*)["']|(?<1>\S+)) Coincide con uno de los siguientes sin asignar el resultado a


un grupo capturado:
Una comilla o un apóstrofo, seguido de cero o más
apariciones de cualquier carácter que no sea una
comilla o un apóstrofo, seguido por una comilla o
un apóstrofo. El grupo con nombre 1 se incluye
en este patrón.
Uno o varios caracteres que no son espacios en
blanco. El grupo con nombre 1 se incluye en este
patrón.

(?<1>[^"']*) Asigna cero o más apariciones de cualquier carácter que no


sea apóstrofo o comilla al grupo de captura con nombre 1 .

(?<1>\S+) Asigna uno o varios caracteres que no sean un espacio en


blanco al grupo de captura con nombre 1 .

Clase de resultado Match


Los resultados de la búsqueda se almacenan en la clase Match, que proporciona acceso a todas las subcadenas
extraídas por la búsqueda. También recuerda la cadena buscada y la expresión regular que se usa, por lo que puede
llamar al método Match.NextMatch para realizar otra búsqueda desde donde terminó la anterior.

Capturas con nombre explícito


En las expresiones regulares tradicionales, los paréntesis de captura se numeran secuencialmente de forma
automática. Esto origina dos problemas. En primer lugar, si se modifica una expresión regular al insertar o quitar un
conjunto de paréntesis, se debe reescribir todo el código que hace referencia a las capturas numeradas para reflejar
la nueva numeración. En segundo lugar, puesto que a menudo se usan diferentes conjuntos de paréntesis para
proporcionar expresiones alternativas para una coincidencia aceptable, puede resultar difícil determinar cuál de las
dos expresiones devolvió realmente un resultado.
Para abordar estos problemas, la clase Regex admite la sintaxis (?<name>…) para capturar una coincidencia en una
ranura especificada (el nombre dado a la ranura puede ser una cadena o un entero; los enteros se pueden
recuperar con más rapidez). Así, las coincidencias alternativas para la misma cadena se pueden dirigir todas al
mismo lugar. En caso de conflicto, la última coincidencia situada en la ranura es la coincidencia correcta. (Pero está
disponible una lista completa de varias coincidencias para una única ranura. Consulte la colección Group.Captures
para obtener más información).

Vea también
Expresiones regulares de .NET
Ejemplo de expresiones regulares: Cambiar formatos
de fecha
16/09/2020 • 3 minutes to read • Edit Online

En el siguiente ejemplo de código, se usa el método Regex.Replace para reemplazar las fechas con el formato
mm/dd/aa con fechas que tienen el formato dd-mm-aa.

WARNING
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de
expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions y provocar un ataque por
denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions pasan un tiempo de
expiración.

Ejemplo
static string MDYToDMY(string input)
{
try {
return Regex.Replace(input,
@"\b(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2,4})\b",
"${day}-${month}-${year}", RegexOptions.None,
TimeSpan.FromMilliseconds(150));
}
catch (RegexMatchTimeoutException) {
return input;
}
}

Function MDYToDMY(input As String) As String


Try
Return Regex.Replace(input, _
"\b(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2,4})\b", _
"${day}-${month}-${year}", RegexOptions.None,
TimeSpan.FromMilliseconds(150))
Catch e As RegexMatchTimeoutException
Return input
End Try
End Function

El código siguiente muestra cómo se puede llamar al método MDYToDMY en una aplicación.
using System;
using System.Globalization;
using System.Text.RegularExpressions;

public class Class1


{
public static void Main()
{
string dateString = DateTime.Today.ToString("d",
DateTimeFormatInfo.InvariantInfo);
string resultString = MDYToDMY(dateString);
Console.WriteLine("Converted {0} to {1}.", dateString, resultString);
}

static string MDYToDMY(string input)


{
try {
return Regex.Replace(input,
@"\b(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2,4})\b",
"${day}-${month}-${year}", RegexOptions.None,
TimeSpan.FromMilliseconds(150));
}
catch (RegexMatchTimeoutException) {
return input;
}
}
}
// The example displays the following output to the console if run on 8/21/2007:
// Converted 08/21/2007 to 21-08-2007.

Imports System.Globalization
Imports System.Text.RegularExpressions

Module DateFormatReplacement
Public Sub Main()
Dim dateString As String = Date.Today.ToString("d", _
DateTimeFormatInfo.InvariantInfo)
Dim resultString As String = MDYToDMY(dateString)
Console.WriteLine("Converted {0} to {1}.", dateString, resultString)
End Sub

Function MDYToDMY(input As String) As String


Try
Return Regex.Replace(input, _
"\b(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2,4})\b", _
"${day}-${month}-${year}", RegexOptions.None,
TimeSpan.FromMilliseconds(150))
Catch e As RegexMatchTimeoutException
Return input
End Try
End Function
End Module
' The example displays the following output to the console if run on 8/21/2007:
' Converted 08/21/2007 to 21-08-2007.

Comentarios
El patrón de la expresión regular \b(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2,4})\b se interpreta como se
muestra en la tabla siguiente.
M O DELO DESC RIP T IO N

\b Iniciar la búsqueda de coincidencias en un límite de palabras.

(?<month>\d{1,2}) Buscar coincidencias con uno o dos dígitos decimales. Es el


grupo month capturado.

/ Buscar coincidencias con la barra diagonal.

(?<day>\d{1,2}) Buscar coincidencias con uno o dos dígitos decimales. Es el


grupo day capturado.

/ Buscar coincidencias con la barra diagonal.

(?<year>\d{2,4}) Buscar coincidencias de dos a cuatro dígitos decimales. Es el


grupo year capturado.

\b Finalizar la búsqueda de coincidencias en un límite de


palabras.

El patrón ${day}-${month}-${year} define la cadena de reemplazo como se muestra en la siguiente tabla.

M O DELO DESC RIP T IO N

$(day) Agregar la cadena capturada por el grupo de captura day .

- Agregar un guión.

$(month) Agregar la cadena capturada por el grupo de captura month .

- Agregar un guión.

$(year) Agregar la cadena capturada por el grupo de captura year .

Vea también
Expresiones regulares de .NET
Cómo: Extraer un protocolo y un número de puerto
de una dirección URL
16/09/2020 • 3 minutes to read • Edit Online

En los siguientes ejemplos se extrae un protocolo y un número de puerto de una dirección URL.

WARNING
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de
expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions y provocar un ataque por
denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions pasan un tiempo de
expiración.

Ejemplo
El ejemplo usa el método Match.Result para devolver el protocolo seguido de dos puntos y del número de puerto.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string url = "http://www.contoso.com:8080/letters/readme.html";

Regex r = new Regex(@"^(?<proto>\w+)://[^/]+?(?<port>:\d+)?/",


RegexOptions.None, TimeSpan.FromMilliseconds(150));
Match m = r.Match(url);
if (m.Success)
Console.WriteLine(m.Result("${proto}${port}"));
}
}
// The example displays the following output:
// http:8080

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim url As String = "http://www.contoso.com:8080/letters/readme.html"
Dim r As New Regex("^(?<proto>\w+)://[^/]+?(?<port>:\d+)?/",
RegexOptions.None, TimeSpan.FromMilliseconds(150))

Dim m As Match = r.Match(url)


If m.Success Then
Console.WriteLine(m.Result("${proto}${port}"))
End If
End Sub
End Module
' The example displays the following output:
' http:8080

El patrón de la expresión regular ^(?<proto>\w+)://[^/]+?(?<port>:\d+)?/ puede interpretarse como se muestra en


la tabla siguiente.

M O DELO DESC RIP T IO N

^ Comenzar la búsqueda de coincidencia al principio de la


cadena.

(?<proto>\w+) Buscar coincidencias con uno o más caracteres alfabéticos.


Asigne a este grupo el nombre proto .

:// Buscar coincidencias con un signo de dos puntos seguido por


dos barras diagonales.

[^/]+? Buscar coincidencias con una o más apariciones (pero el menor


número posible) de cualquier carácter que no sea una barra
diagonal.

(?<port>:\d+)? Buscar una coincidencia con cero o una aparición de una coma
seguida de uno o más caracteres decimales. Asigne a este
grupo el nombre port .

/ Buscar una coincidencia con una barra diagonal.

El método Match.Result expande la secuencia de reemplazo ${proto}${port} , que concatena el valor de los dos
grupos con nombre capturados en el patrón de expresión regular. Resulta cómodo concatenar explícitamente las
cadenas recuperadas del objeto de la colección devueltas por la propiedad Match.Groups.
El ejemplo usa el método Match.Result con dos sustituciones, ${proto} y ${port} , para incluir los grupos
capturados en la cadena de salida. En su lugar, puede recuperar los grupos capturados del objeto de coincidencia
GroupCollection, como se muestra en el siguiente código.

Console.WriteLine(m.Groups["proto"].Value + m.Groups["port"].Value);

Console.WriteLine(m.Groups("proto").Value + m.Groups("port").Value)

Vea también
Expresiones regulares de .NET
Procedimiento para quitar caracteres no válidos de
una cadena
16/09/2020 • 2 minutes to read • Edit Online

En el ejemplo siguiente se usa el método estático Regex.Replace para quitar caracteres no válidos de una cadena.

WARNING
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de
expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions y provocar un ataque por
denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions pasan un tiempo de
expiración.

Ejemplo
Puede usar el método CleanInput definido en este ejemplo para quitar caracteres potencialmente perjudiciales
que se hayan escrito en un campo de texto que acepta datos del usuario. En este caso, CleanInput elimina todos
los caracteres no alfanuméricos excepto puntos (.), símbolos de arroba (@) y guiones (-), y devuelve la cadena
restante. Pero puede modificar el patrón de expresión regular para que elimine todos los caracteres que no deban
incluirse en una cadena de entrada.

using System;
using System.Text.RegularExpressions;

public class Example


{
static string CleanInput(string strIn)
{
// Replace invalid characters with empty strings.
try {
return Regex.Replace(strIn, @"[^\w\.@-]", "",
RegexOptions.None, TimeSpan.FromSeconds(1.5));
}
// If we timeout when replacing invalid characters,
// we should return Empty.
catch (RegexMatchTimeoutException) {
return String.Empty;
}
}
}
Imports System.Text.RegularExpressions

Module Example
Function CleanInput(strIn As String) As String
' Replace invalid characters with empty strings.
Try
Return Regex.Replace(strIn, "[^\w\.@-]", "")
' If we timeout when replacing invalid characters,
' we should return String.Empty.
Catch e As RegexMatchTimeoutException
Return String.Empty
End Try
End Function
End Module

El patrón de expresión regular [^\w\.@-] coincide con cualquier carácter que no sea un carácter de palabra, un
punto, un símbolo @ o un guion. Un carácter de palabra es cualquier letra, dígito decimal o conector de
puntuación, como un guion bajo. Cualquier carácter que coincida con este patrón se sustituye por String.Empty,
que es la cadena definida por el modelo de reemplazo. Para permitir caracteres adicionales en la entrada de
usuario, agregue esos caracteres a la clase de caracteres en el patrón de la expresión regular. Por ejemplo, el patrón
de expresión regular [^\w\.@-\\%] también permite un símbolo de porcentaje y una barra diagonal inversa en la
cadena de entrada.

Vea también
Expresiones regulares de .NET
Cómo comprobar si las cadenas tienen un formato
de correo electrónico válido
16/09/2020 • 11 minutes to read • Edit Online

En el ejemplo siguiente se usa una expresión regular para comprobar que una cadena tiene un formato de correo
electrónico válido.

WARNING
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de
expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions y provocar un ataque por
denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions pasan un tiempo de
expiración.

Ejemplo
En el ejemplo se define un método IsValidEmail , que devuelve true si la cadena contiene una dirección de
correo electrónico válida y false si no es válida, pero no realiza ninguna otra acción.
Para comprobar que la dirección de correo electrónico es válida, el método IsValidEmail llama al método
Regex.Replace(String, String, MatchEvaluator) con el patrón de expresión regular (@)(.+)$ para separar el
nombre de dominio de la dirección de correo electrónico. El tercer parámetro es un delegado MatchEvaluator que
representa el método que procesa y reemplaza el texto coincidente. El patrón de expresión regular se interpreta
de esta manera:

M O DELO DESC RIP C IÓ N

(@) Buscar el carácter @. Este es el primer grupo de captura.

(.+) Buscar una coincidencia con una o más apariciones de


cualquier carácter. Este es el segundo grupo de captura.

$ Finalizar la búsqueda al final de la cadena.

El nombre de dominio junto con el carácter @ se pasa al método DomainMapper , que usa la clase IdnMapping
para convertir los caracteres Unicode fuera del intervalo de caracteres EE.UU. - ASCII a Punycode. El método
también establece la marca invalid en True si el método IdnMapping.GetAscii detecta cualquier carácter no
válido en el nombre del dominio. El método devuelve el nombre de dominio Punycode precedido por el símbolo
@ al método IsValidEmail .
A continuación, el método IsValidEmail llama al método Regex.IsMatch(String, String) para comprobar que la
dirección se ajusta a un patrón de expresión regular.
Tenga en cuenta que el método IsValidEmail no realiza la autenticación para validar la dirección de correo
electrónico. Se limita a determinar si su formato es válido para una dirección de correo electrónico. Asimismo, el
método IsValidEmail no comprueba que el nombre del dominio de nivel superior sea un nombre válido
enumerado en la base de datos de la zona de la raíz IANA, lo cual requeriría una operación de búsqueda. En su
lugar, la expresión regular comprueba simplemente que el nombre de dominio de nivel superior conste de entre
dos y veinticuatro caracteres ASCII alfanuméricos, que los caracteres primero y último sean alfanuméricos y que
el resto de caracteres sean alfanuméricos o un guión (-).

using System;
using System.Globalization;
using System.Text.RegularExpressions;

public class RegexUtilities


{
public static bool IsValidEmail(string email)
{
if (string.IsNullOrWhiteSpace(email))
return false;

try
{
// Normalize the domain
email = Regex.Replace(email, @"(@)(.+)$", DomainMapper,
RegexOptions.None, TimeSpan.FromMilliseconds(200));

// Examines the domain part of the email and normalizes it.


string DomainMapper(Match match)
{
// Use IdnMapping class to convert Unicode domain names.
var idn = new IdnMapping();

// Pull out and process domain name (throws ArgumentException on invalid)


var domainName = idn.GetAscii(match.Groups[2].Value);

return match.Groups[1].Value + domainName;


}
}
catch (RegexMatchTimeoutException e)
{
return false;
}
catch (ArgumentException e)
{
return false;
}

try
{
return Regex.IsMatch(email,
@"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-
z])@))" +
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}
[a-z0-9]))$",
RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));
}
catch (RegexMatchTimeoutException)
{
return false;
}
}
}
Imports System.Globalization
Imports System.Text.RegularExpressions

Public Class RegexUtilities

Public Shared Function IsValidEmail(email As String) As Boolean

If String.IsNullOrWhiteSpace(email) Then Return False

' Use IdnMapping class to convert Unicode domain names.


Try
'Examines the domain part of the email and normalizes it.
Dim DomainMapper =
Function(match As Match) As String

'Use IdnMapping class to convert Unicode domain names.


Dim idn = New IdnMapping

'Pull out and process domain name (throws ArgumentException on invalid)


Dim domainName As String = idn.GetAscii(match.Groups(2).Value)

Return match.Groups(1).Value & domainName

End Function

'Normalize the domain


email = Regex.Replace(email, "(@)(.+)$", DomainMapper,
RegexOptions.None, TimeSpan.FromMilliseconds(200))

Catch e As RegexMatchTimeoutException
Return False

Catch e As ArgumentException
Return False

End Try

Try
Return Regex.IsMatch(email,
"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\
{\}\|~\w])*)(?<=[0-9a-z])@))" +
"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+[a-z0-9]
[\-a-z0-9]{0,22}[a-z0-9]))$",
RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250))

Catch e As RegexMatchTimeoutException
Return False

End Try

End Function

End Class

En este ejemplo, el patrón de expresión regular


^(?(")(".+?(?<!\\)"@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))(?(\[)(\
[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$
se interpreta como se muestra en la leyenda siguiente. La expresión regular se compila mediante la marca
RegexOptions.IgnoreCase.
Patrón ^ : Comenzar la búsqueda de coincidencia al principio de la cadena.
Patrón (?(") : Determinar si el primer carácter es una comilla. (?(") es el principio de una construcción de
alternancia.
Patrón (?(")(".+?(?<!\\)"@) : Si el primer carácter es un signo de comillas, buscar unas comillas iniciales
seguidas de al menos un carácter cualquiera, seguido a su vez de unas comillas finales. Las comillas finales no
pueden ir precedidas de un carácter de barra diagonal inversa (\). (?<! es el principio de una aserción de
búsqueda anticipada negativa de ancho cero. La cadena debe concluir con una arroba (@).
Patrón |(([0-9a-z] : Si el primer carácter no es un signo de comillas, buscar cualquier carácter alfabético de la a a
la z o de la A a la Z (la comparación distingue entre mayúsculas y minúsculas) o cualquier carácter numérico del 0
al 9.
Patrón (\.(?!\.)) : Si el carácter siguiente es un punto, determinar que coincide. Si no lo es, buscar más adelante
en el siguiente carácter y probar si coincide. (?!\.) es una aserción de búsqueda anticipada negativa de ancho
igual a cero que evita que aparezcan dos puntos consecutivos en la parte local de una dirección de correo
electrónico.
Patrón |[-!#\$%&'\*\+/=\?\^`\{\}\|~\w] : Si el carácter siguiente no es un punto, busque la coincidencia de
cualquier carácter de palabra o uno de los caracteres siguientes: -!#$%&'*+/=?^`{}|~
Patrón ((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])* : Buscar el modelo de alternancia (un punto seguido de algo
que no sea un punto, o uno de varios caracteres) cero o más veces.
Patrón @ : Buscar el carácter @.
Patrón (?<=[0-9a-z]) : Continuar buscando si el carácter que precede al carácter @ es uno de la A a la Z, de la a a
la z o del 0 al 9. Este patrón define una aserción de búsqueda retrasada (lookbehind) positiva de ancho cero.
Patrón (?(\[) : Comprobar si el carácter que va detrás de @ es un corchete de apertura.
Patrón (\[(\d{1,3}\.){3}\d{1,3}\]) : Si lo es, buscar el corchete de apertura, seguido por una dirección IP (cuatro
grupos de uno a tres dígitos, separados por puntos) y por un corchete de cierre.
Patrón |(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+ : Si el carácter que va detrás de @ no es un corchete de apertura,
buscar un carácter alfanumérico con un valor de la A a la Z, de la a a la z o del 0 al 9, seguido de cero o más
apariciones de un guión, seguido de cero o de un carácter alfanumérico con un valor de la A a la Z, de la a a la z o
del 0 al 9, seguido de un punto. Este patrón se puede repetir una o más veces y debe ir seguido del nombre de
dominio de nivel superior.
Patrón [a-z0-9][\-a-z0-9]{0,22}[a-z0-9])) : El nombre de dominio de nivel superior debe empezar y finalizar por
un carácter alfanumérico (a-z, A-Z y 0-9). También puede incluir de cero a 22 caracteres ASCII que sean
alfanuméricos o guiones.
Patrón $ : Finalizar la búsqueda al final de la cadena.

Compilar el código
Los métodos IsValidEmail y DomainMapper pueden estar incluidos en una biblioteca de métodos de la utilidad de
expresiones regulares o pueden incluirse como métodos estáticos o de instancia privados en la clase de
aplicación.
También puede usar el método Regex.CompileToAssembly para incluir esta expresión regular en una biblioteca de
expresiones regulares.
Si se utilizan en una biblioteca de expresiones regulares, puede llamarlos utilizando código como el siguiente:
class Program
{
static void Main(string[] args)
{
string[] emailAddresses = { "david.jones@proseware.com", "d.j@server1.proseware.com",
"jones@ms1.proseware.com", "j.@server1.proseware.com",
"j@proseware.com9", "js#internal@proseware.com",
"j_9@[129.126.118.1]", "j..s@proseware.com",
"js*@proseware.com", "js@proseware..com",
"js@proseware.com9", "j.s@server1.proseware.com",
"\"j\\\"s\\\"\"@proseware.com", "js@contoso.中国" };

foreach (var emailAddress in emailAddresses)


{
if (RegexUtilities.IsValidEmail(emailAddress))
Console.WriteLine($"Valid: {emailAddress}");
else
Console.WriteLine($"Invalid: {emailAddress}");
}

Console.ReadKey();
}
}
// The example displays the following output:
// Valid: david.jones@proseware.com
// Valid: d.j@server1.proseware.com
// Valid: jones@ms1.proseware.com
// Invalid: j.@server1.proseware.com
// Valid: j@proseware.com9
// Valid: js#internal@proseware.com
// Valid: j_9@[129.126.118.1]
// Invalid: j..s@proseware.com
// Invalid: js*@proseware.com
// Invalid: js@proseware..com
// Valid: js@proseware.com9
// Valid: j.s@server1.proseware.com
// Valid: "j\"s\""@proseware.com
// Valid: js@contoso.中国
Public Class Application
Public Shared Sub Main()
Dim emailAddresses() As String = {"david.jones@proseware.com", "d.j@server1.proseware.com",
"jones@ms1.proseware.com", "j.@server1.proseware.com",
"j@proseware.com9", "js#internal@proseware.com",
"j_9@[129.126.118.1]", "j..s@proseware.com",
"js*@proseware.com", "js@proseware..com",
"js@proseware.com9", "j.s@server1.proseware.com",
"""j\""s\""""@proseware.com", "js@contoso.中国"}

For Each emailAddress As String In emailAddresses


If RegexUtilities.IsValidEmail(emailAddress) Then
Console.WriteLine($"Valid: {emailAddress}")
Else
Console.WriteLine($"Invalid: {emailAddress}")
End If
Next
End Sub
End Class
' The example displays the following output:
' Valid: david.jones@proseware.com
' Valid: d.j@server1.proseware.com
' Valid: jones@ms1.proseware.com
' Invalid: j.@server1.proseware.com
' Valid: j@proseware.com9
' Valid: js#internal@proseware.com
' Valid: j_9@[129.126.118.1]
' Invalid: j..s@proseware.com
' Invalid: js*@proseware.com
' Invalid: js@proseware..com
' Valid: js@proseware.com9
' Valid: j.s@server1.proseware.com
' Valid: "j\"s\""@proseware.com
' Valid: js@contoso.中国

Vea también
Expresiones regulares de .NET Framework
Analizar cadenas en .NET
16/09/2020 • 2 minutes to read • Edit Online

Una operación de análisis convierte una cadena que representa un tipo base de .NET en dicho tipo base. Por
ejemplo, se usa una operación de análisis para convertir una cadena en un número de punto flotante o un valor
de fecha y hora. El método que se usa normalmente para realizar una operación de análisis es el método Parse .
Dado que el análisis es la operación inversa del formato (lo que implica convertir un tipo base en su
representación de cadena), se aplican muchas de las mismas reglas y convenciones. Del mismo modo que el
formato usa un objeto que implementa la interfaz IFormatProvider para proporcionar información de formato
según la referencia cultural, el análisis también usa un objeto que implementa la interfaz IFormatProvider para
determinar cómo se interpreta una representación de cadena. Para obtener más información, consulte Aplicar
formato a tipos.

En esta sección
Análisis de cadenas numéricas
Se describe cómo convertir cadenas en tipos numéricos de .NET.
Análisis de cadenas de fecha y hora
Se describe cómo convertir cadenas en tipos DateTime de .NET.
Análisis de otras cadenas
Se describe cómo convertir cadenas en tipos carácter , booleano y enumeración .

Secciones relacionadas
Aplicación de formato a tipos
Se describen los conceptos de formato básicos, como especificadores de formato y proveedores de formato.
Conversión de tipos en .NET
Se describe cómo convertir tipos.
Analizar cadenas numéricas en .NET
16/09/2020 • 14 minutes to read • Edit Online

Todos los tipos numéricos tienen dos métodos de análisis estáticos, Parse y TryParse , que puede usar para
convertir la representación de cadena de un número en un tipo numérico. Estos métodos permiten analizar
cadenas generadas mediante el uso de las cadenas de formato que se documentan en Cadenas con formato
numérico estándar y Cadenas con formato numérico personalizado. De forma predeterminada, los métodos Parse
y TryParse pueden convertir correctamente las cadenas que contienen dígitos decimales enteros solo en valores
enteros. Pueden convertir correctamente las cadenas que contienen dígitos decimales enteros y fraccionarios,
separadores de grupos y un separador decimal en valores de punto flotante. El método Parse produce una
excepción si se produce un error en la operación, mientras que el método TryParse devuelve false .

Análisis y proveedores de formato


Normalmente, las representaciones de cadena de valores numéricos se diferencian en la referencia cultural. Todos
los elementos de las cadenas numéricas, como los símbolos de moneda, los separadores de grupo (o millares) y
los separadores decimales, varían según la referencia cultural. Los métodos de análisis usan implícita o
explícitamente un proveedor de formato que reconoce estas variaciones específicas de la referencia cultural. Si no
se especifica ningún proveedor de formato en una llamada al método Parse o TryParse , se usa el proveedor de
formato asociado a la referencia cultural del subproceso actual (el objeto NumberFormatInfo devuelto por la
propiedad NumberFormatInfo.CurrentInfo).
Un proveedor de formato se representa mediante una implementación IFormatProvider. Esta interfaz tiene un solo
miembro, el método GetFormat, cuyo único parámetro es un objeto Type que representa el tipo al que se va a dar
formato. Este método devuelve el objeto que proporciona información de formato. .NET es compatible con las dos
implementaciones IFormatProvider siguientes para analizar cadenas numéricas:
Un objeto CultureInfo cuyo método CultureInfo.GetFormat devuelve un objeto NumberFormatInfo que
proporciona información de formato específica de la referencia cultural.
Un objeto NumberFormatInfo cuyo método NumberFormatInfo.GetFormat se devuelve a sí mismo.
En el ejemplo siguiente se intenta convertir cada cadena de una matriz en un valor Double. Primero se intenta
analizar la cadena mediante un proveedor de formato que refleja las convenciones de la referencia cultural Inglés
(Estados Unidos). Si esta operación produce una excepción FormatException, se intenta analizar la cadena mediante
un proveedor de formato que refleja las convenciones de la referencia cultural Francés (Francia).
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
string[] values = { "1,304.16", "$1,456.78", "1,094", "152",
"123,45 €", "1 304,16", "Ae9f" };
double number;
CultureInfo culture = null;

foreach (string value in values) {


try {
culture = CultureInfo.CreateSpecificCulture("en-US");
number = Double.Parse(value, culture);
Console.WriteLine("{0}: {1} --> {2}", culture.Name, value, number);
}
catch (FormatException) {
Console.WriteLine("{0}: Unable to parse '{1}'.",
culture.Name, value);
culture = CultureInfo.CreateSpecificCulture("fr-FR");
try {
number = Double.Parse(value, culture);
Console.WriteLine("{0}: {1} --> {2}", culture.Name, value, number);
}
catch (FormatException) {
Console.WriteLine("{0}: Unable to parse '{1}'.",
culture.Name, value);
}
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// en-US: 1,304.16 --> 1304.16
//
// en-US: Unable to parse '$1,456.78'.
// fr-FR: Unable to parse '$1,456.78'.
//
// en-US: 1,094 --> 1094
//
// en-US: 152 --> 152
//
// en-US: Unable to parse '123,45 €'.
// fr-FR: Unable to parse '123,45 €'.
//
// en-US: Unable to parse '1 304,16'.
// fr-FR: 1 304,16 --> 1304.16
//
// en-US: Unable to parse 'Ae9f'.
// fr-FR: Unable to parse 'Ae9f'.
Imports System.Globalization

Module Example
Public Sub Main()
Dim values() As String = {"1,304.16", "$1,456.78", "1,094", "152",
"123,45 €", "1 304,16", "Ae9f"}
Dim number As Double
Dim culture As CultureInfo = Nothing

For Each value As String In values


Try
culture = CultureInfo.CreateSpecificCulture("en-US")
number = Double.Parse(value, culture)
Console.WriteLine("{0}: {1} --> {2}", culture.Name, value, number)
Catch e As FormatException
Console.WriteLine("{0}: Unable to parse '{1}'.",
culture.Name, value)
culture = CultureInfo.CreateSpecificCulture("fr-FR")
Try
number = Double.Parse(value, culture)
Console.WriteLine("{0}: {1} --> {2}", culture.Name, value, number)
Catch ex As FormatException
Console.WriteLine("{0}: Unable to parse '{1}'.",
culture.Name, value)
End Try
End Try
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' en-US: 1,304.16 --> 1304.16
'
' en-US: Unable to parse '$1,456.78'.
' fr-FR: Unable to parse '$1,456.78'.
'
' en-US: 1,094 --> 1094
'
' en-US: 152 --> 152
'
' en-US: Unable to parse '123,45 €'.
' fr-FR: Unable to parse '123,45 €'.
'
' en-US: Unable to parse '1 304,16'.
' fr-FR: 1 304,16 --> 1304.16
'
' en-US: Unable to parse 'Ae9f'.
' fr-FR: Unable to parse 'Ae9f'.

Análisis y valores NumberStyles


Los elementos de estilo (como espacio en blanco, separadores de grupos y separador decimal) que la operación de
análisis puede controlar se definen mediante un valor de enumeración NumberStyles. De forma predeterminada,
las cadenas que representan valores enteros se analizan mediante el valor NumberStyles.Integer, que solo permite
dígitos numéricos, espacio en blanco inicial y final, y un signo inicial. Las cadenas que representan valores de punto
flotante se analizan mediante una combinación de valores NumberStyles.Float y NumberStyles.AllowThousands.
Este estilo compuesto permite dígitos decimales junto con un espacio en blanco inicial y final, un signo inicial, un
separador decimal, separador de grupos y un exponente. Al llamar a una sobrecarga del método Parse o
TryParse que incluya un parámetro de tipo NumberStyles y configurar una o más marcas NumberStyles, puede
controlar los elementos de estilo que pueden estar presentes en la cadena para que la operación de análisis se
realice correctamente.
Por ejemplo, una cadena que contiene un separador de grupos no puede convertirse en un valor Int32 mediante el
método Int32.Parse(String). Pero la conversión se realiza correctamente si usa la marca
NumberStyles.AllowThousands, como se muestra en el ejemplo siguiente.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
string value = "1,304";
int number;
IFormatProvider provider = CultureInfo.CreateSpecificCulture("en-US");
if (Int32.TryParse(value, out number))
Console.WriteLine("{0} --> {1}", value, number);
else
Console.WriteLine("Unable to convert '{0}'", value);

if (Int32.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands,


provider, out number))
Console.WriteLine("{0} --> {1}", value, number);
else
Console.WriteLine("Unable to convert '{0}'", value);
}
}
// The example displays the following output:
// Unable to convert '1,304'
// 1,304 --> 1304

Imports System.Globalization

Module Example
Public Sub Main()
Dim value As String = "1,304"
Dim number As Integer
Dim provider As IFormatProvider = CultureInfo.CreateSpecificCulture("en-US")
If Int32.TryParse(value, number) Then
Console.WriteLine("{0} --> {1}", value, number)
Else
Console.WriteLine("Unable to convert '{0}'", value)
End If

If Int32.TryParse(value, NumberStyles.Integer Or NumberStyles.AllowThousands,


provider, number) Then
Console.WriteLine("{0} --> {1}", value, number)
Else
Console.WriteLine("Unable to convert '{0}'", value)
End If
End Sub
End Module
' The example displays the following output:
' Unable to convert '1,304'
' 1,304 --> 1304

WARNING
La operación de análisis siempre usa las convenciones de formato de una referencia cultural determinada. Si no pasa un
objeto CultureInfo o NumberFormatInfo para especificar una referencia cultural, se usa la referencia cultural asociada al
subproceso actual.
En la tabla siguiente se enumeran los miembros de la enumeración NumberStyles y se describe el efecto que
tienen en la operación de análisis.

VA LO R N UM B ERST Y L ES EF EC TO EN L A C A DEN A Q UE SE VA A A N A L IZ A R

NumberStyles.None Solo se permiten los dígitos numéricos.

NumberStyles.AllowDecimalPoint Se permiten el separador decimal y los dígitos fraccionarios. En


el caso de los valores enteros, solo se permite cero como
dígito fraccionario. La propiedad
NumberFormatInfo.NumberDecimalSeparator o
NumberFormatInfo.CurrencyDecimalSeparator determina los
separadores decimales válidos.

NumberStyles.AllowExponent Se puede usar el carácter "e" o "E" para indicar la notación


exponencial. Vea NumberStyles para obtener información
adicional.

NumberStyles.AllowLeadingWhite Se permite el espacio en blanco inicial.

NumberStyles.AllowTrailingWhite Se permite el espacio en blanco final.

NumberStyles.AllowLeadingSign Un signo positivo o negativo puede preceder a los dígitos


numéricos.

NumberStyles.AllowTrailingSign Un signo positivo o negativo puede seguir a los dígitos


numéricos.

NumberStyles.AllowParentheses Se pueden usar paréntesis para indicar valores negativos.

NumberStyles.AllowThousands Se permite el separador de grupos. El carácter de separador de


grupo viene determinado por la propiedad
NumberFormatInfo.NumberGroupSeparator o
NumberFormatInfo.CurrencyGroupSeparator.

NumberStyles.AllowCurrencySymbol Se permite el símbolo de moneda. El símbolo de moneda se


define mediante la propiedad
NumberFormatInfo.CurrencySymbol.

NumberStyles.AllowHexSpecifier La cadena que se va a analizar se interpreta como un número


hexadecimal. Puede incluir los dígitos hexadecimales 0-9, A-F y
a-f. Esta marca solo se puede usar para analizar valores
enteros.

Además, la enumeración NumberStyles proporciona los siguientes estilos compuestos, que incluyen varias marcas
NumberStyles.

VA LO R N UM B ERST Y L ES C O M P UESTO S M IEM B RO S Q UE IN C L UY E

NumberStyles.Integer Incluye los estilos NumberStyles.AllowLeadingWhite,


NumberStyles.AllowTrailingWhite y
NumberStyles.AllowLeadingSign. Este es el estilo
predeterminado que se usa para analizar valores enteros.
VA LO R N UM B ERST Y L ES C O M P UESTO S M IEM B RO S Q UE IN C L UY E

NumberStyles.Number Incluye los estilos NumberStyles.AllowLeadingWhite,


NumberStyles.AllowTrailingWhite,
NumberStyles.AllowLeadingSign,
NumberStyles.AllowTrailingSign,
NumberStyles.AllowDecimalPoint y
NumberStyles.AllowThousands.

NumberStyles.Float Incluye los estilos NumberStyles.AllowLeadingWhite,


NumberStyles.AllowTrailingWhite,
NumberStyles.AllowLeadingSign,
NumberStyles.AllowDecimalPoint y
NumberStyles.AllowExponent.

NumberStyles.Currency Incluye todos los estilos excepto NumberStyles.AllowExponent


y NumberStyles.AllowHexSpecifier.

NumberStyles.Any Incluye todos los estilos excepto


NumberStyles.AllowHexSpecifier.

NumberStyles.HexNumber Incluye los estilos NumberStyles.AllowLeadingWhite,


NumberStyles.AllowTrailingWhite y
NumberStyles.AllowHexSpecifier.

Análisis y dígitos Unicode


El estándar Unicode define puntos de código para dígitos de diferentes sistemas de escritura. Por ejemplo, los
puntos de código de U+0030 a U+0039 representan los dígitos latinos básicos del 0 al 9, los puntos de código de
U+09E6 a U+09EF representan los dígitos de bengalí del 0 al 9, y los puntos de código de U+FF10 a U+FF19
representan los dígitos de ancho completo del 0 al 9. Pero los únicos dígitos numéricos que reconocen los métodos
de análisis son los dígitos latinos básicos del 0 al 9 con puntos de código de U+0030 a U+0039. Si se pasa a un
método de análisis numérico una cadena que contenga cualquier otro dígito, el método producirá una excepción
FormatException.
En el ejemplo siguiente se usa el método Int32.Parse para analizar las cadenas que se componen de dígitos en
sistemas de escritura diferentes. Como muestra la salida del ejemplo, el intento de analizar los dígitos latinos
básicos se realiza correctamente, pero se produce un error en el intento de analizar los dígitos de ancho completo,
árabe-hindús y de bengalí.
using System;

public class Example


{
public static void Main()
{
string value;
// Define a string of basic Latin digits 1-5.
value = "\u0031\u0032\u0033\u0034\u0035";
ParseDigits(value);

// Define a string of Fullwidth digits 1-5.


value = "\uFF11\uFF12\uFF13\uFF14\uFF15";
ParseDigits(value);

// Define a string of Arabic-Indic digits 1-5.


value = "\u0661\u0662\u0663\u0664\u0665";
ParseDigits(value);

// Define a string of Bangla digits 1-5.


value = "\u09e7\u09e8\u09e9\u09ea\u09eb";
ParseDigits(value);
}

static void ParseDigits(string value)


{
try {
int number = Int32.Parse(value);
Console.WriteLine("'{0}' --> {1}", value, number);
}
catch (FormatException) {
Console.WriteLine("Unable to parse '{0}'.", value);
}
}
}
// The example displays the following output:
// '12345' --> 12345
// Unable to parse '12345'.
// Unable to parse '١٢٣٤٥'.
// Unable to parse ' '.
Module Example
Public Sub Main()
Dim value As String
' Define a string of basic Latin digits 1-5.
value = ChrW(&h31) + ChrW(&h32) + ChrW(&h33) + ChrW(&h34) + ChrW(&h35)
ParseDigits(value)

' Define a string of Fullwidth digits 1-5.


value = ChrW(&hff11) + ChrW(&hff12) + ChrW(&hff13) + ChrW(&hff14) + ChrW(&hff15)
ParseDigits(value)

' Define a string of Arabic-Indic digits 1-5.


value = ChrW(&h661) + ChrW(&h662) + ChrW(&h663) + ChrW(&h664) + ChrW(&h665)
ParseDigits(value)

' Define a string of Bangla digits 1-5.


value = ChrW(&h09e7) + ChrW(&h09e8) + ChrW(&h09e9) + ChrW(&h09ea) + ChrW(&h09eb)
ParseDigits(value)
End Sub

Sub ParseDigits(value As String)


Try
Dim number As Integer = Int32.Parse(value)
Console.WriteLine("'{0}' --> {1}", value, number)
Catch e As FormatException
Console.WriteLine("Unable to parse '{0}'.", value)
End Try
End Sub
End Module
' The example displays the following output:
' '12345' --> 12345
' Unable to parse '12345'.
' Unable to parse '١٢٣٤٥'.
' Unable to parse ' '.

Vea también
NumberStyles
Analizar cadenas
Aplicación de formato a tipos
Análisis de cadenas de fecha y hora en .NET
16/09/2020 • 13 minutes to read • Edit Online

El análisis de cadenas para convertirlas en objetos DateTime requiere que se especifique información sobre cómo
se representan las fechas y horas en forma de texto. Las distintas referencias culturales usan distintos órdenes de
día, mes y año. Algunas representaciones horarias usan el reloj de 24 horas y otras especifican "a. m." y "p. m.".
Algunas aplicaciones solo necesitan la fecha. Otras solo necesitan la hora. Pero otras necesitan especificar la fecha y
la hora. Los métodos que convierten cadenas a objeto DateTime permiten especificar información detallada sobre
los formatos esperados y los elementos de fecha y hora que precisa la aplicación. Hay tres subtareas para convertir
correctamente el texto en un objeto DateTime:
1. Debe especificarse el formato esperado del texto que representa una fecha y hora.
2. Es posible especificar la referencia cultural del formato de fecha y hora.
3. Puede especificarse cómo se establecen los componentes que faltan en la representación de texto en la fecha y
hora.
Los métodos Parse y TryParse convierten varias representaciones comunes de fecha y hora. Los métodos
ParseExact y TryParseExact convierten una representación de cadena que se ajusta al modelo especificado por una
cadena de formato de fecha y hora. (Para obtener más información, vea los artículos sobre cadenas con formato de
fecha y hora estándar y cadenas con formato de fecha y hora personalizado).
El objeto DateTimeFormatInfo actual proporciona más control sobre la forma en que debe interpretarse el texto
como fecha y hora. Las propiedades de un objeto DateTimeFormatInfo describen los separadores de fecha y hora,
los nombres de los meses, días y eras, así como el formato de las designaciones "a. m." y "p. m.". La referencia
cultural del subproceso actual proporciona un objeto DateTimeFormatInfo que representa la referencia cultural
actual. Si quiere una referencia cultural específica o una configuración personalizada, especifique el parámetro
IFormatProvider de un método de análisis. Para el parámetro IFormatProvider, especifique un objeto CultureInfo,
que representa una referencia cultural, o un objeto DateTimeFormatInfo.
Es posible que al texto que representa una fecha y hora le falte alguna información. Por ejemplo, la mayoría de los
usuarios supondría que la fecha "12 de marzo" representa el año actual. Del mismo modo, "Marzo de 2018"
representa el mes de marzo del año 2018. El texto que representa la hora a menudo solo incluye horas, minutos y
una designación "a. m."/"p. m.". Los métodos de análisis controlan esta información que falta mediante valores
predeterminados razonables:
Cuando solo se indica la hora, la parte de fecha utiliza la fecha actual.
Cuando solo se indica la fecha, la parte de hora es la medianoche.
Cuando no se especifica el año en una fecha, se usa el año actual.
Cuando no se especifica el día del mes, se usa el primero del mes.
Si la fecha se indica en la cadena, debe incluir el mes y uno de los dos valores, el día o el año. Si se indica la hora,
debe incluir la hora y los minutos o el designador "a. m."/"p. m.".
Se puede especificar la constante NoCurrentDateDefault para invalidar los valores predeterminados. Cuando se
usa esta constante, las propiedades de año, mes o día se establecen en el valor 1 . El último ejemplo con Parse
muestra este comportamiento.
Además de un componente de fecha y hora, la representación de cadena de una fecha y hora puede incluir una
diferencia horaria que indica cuánto difiere la hora respecto de la hora universal coordinada (UTC). Por ejemplo, la
cadena "14/2/2007 5:32:00 -7:00" define una hora que es siete horas anterior a la hora UTC. Si se omite la
diferencia horaria de la representación de cadena de una hora, el análisis devuelve un objeto DateTime con su
propiedad Kind establecida en DateTimeKind.Unspecified. Si se especifica la diferencia horaria, el análisis devuelve
un objeto DateTime con su propiedad Kind establecida en DateTimeKind.Local y su valor ajustado a la zona horaria
local del equipo. Puede modificar este comportamiento usando un valor DateTimeStyles con el método de análisis.
El proveedor de formato también se usa para interpretar una fecha numérica ambigua. No queda claro qué
componentes de la fecha representada por la cadena "02/03/04" son el mes, el día y el año. Los componentes se
interpretan según el orden de formatos de fecha similares del proveedor de formato.

Parse
En el ejemplo siguiente se muestra el uso del método DateTime.Parse para convertir un objeto string en un valor
DateTime. En este ejemplo se usa la referencia cultural asociada al subproceso actual. Si el objeto CultureInfo
asociado a la referencia cultural actual no puede analizar la cadena de entrada, se produce una excepción
FormatException.

TIP
Todos los ejemplos de C# en este artículo se ejecutan en el explorador. Presione el botón Ejecutar para ver el resultado.
También puede modificarlos para experimentar.

NOTE
Estos ejemplos están disponibles en el repositorio de documentos de GitHub para C# y Visual Basic.

string dateInput = "Jan 1, 2009";


var parsedDate = DateTime.Parse(dateInput);
Console.WriteLine(parsedDate);
// Displays the following output on a system whose culture is en-US:
// 1/1/2009 00:00:00

Dim MyString As String = "Jan 1, 2009"


Dim MyDateTime As DateTime = DateTime.Parse(MyString)
Console.WriteLine(MyDateTime)
' Displays the following output on a system whose culture is en-US:
' 1/1/2009 00:00:00

Además, puede definir explícitamente la referencia cultural cuyas convenciones de formato se utilizan cuando se
analiza una cadena. Especifique uno de los objetos DateTimeFormatInfo estándar devueltos por la propiedad
CultureInfo.DateTimeFormat. En el ejemplo siguiente se usa un proveedor de formato para analizar una cadena en
alemán como valor DateTime. Crea un objeto CultureInfo que representa la referencia cultural de-DE . El objeto
CultureInfo garantiza el análisis correcto de esta cadena concreta. Esto impide cualquier opción que se encuentre
en CurrentCulture de CurrentThread.

var cultureInfo = new CultureInfo("de-DE");


string dateString = "12 Juni 2008";
var dateTime = DateTime.Parse(dateString, cultureInfo);
Console.WriteLine(dateTime);
// The example displays the following output:
// 6/12/2008 00:00:00
Dim MyCultureInfo As New CultureInfo("de-DE")
Dim MyString As String = "12 Juni 2008"
Dim MyDateTime As DateTime = DateTime.Parse(MyString, MyCultureInfo)
Console.WriteLine(MyDateTime)
' The example displays the following output:
' 6/12/2008 00:00:00

Pero, aunque puede usar sobrecargas del método Parse para especificar proveedores de formato personalizados, el
método no admite el análisis de formatos no estándar. Para analizar una fecha y hora expresadas en un formato no
estándar, use en su lugar el método ParseExact.
En el ejemplo siguiente se usa la enumeración DateTimeStyles para especificar que no debe agregarse la
información de fecha y hora actual al valor DateTime en los campos no especificados.

var cultureInfo = new CultureInfo("de-DE");


string dateString = "12 Juni 2008";
var dateTime = DateTime.Parse(dateString, cultureInfo,
DateTimeStyles.NoCurrentDateDefault);
Console.WriteLine(dateTime);
// The example displays the following output if the current culture is en-US:
// 6/12/2008 00:00:00

Dim MyCultureInfo As New CultureInfo("de-DE")


Dim MyString As String = "12 Juni 2008"
Dim MyDateTime As DateTime = DateTime.Parse(MyString, MyCultureInfo,
DateTimeStyles.NoCurrentDateDefault)
Console.WriteLine(MyDateTime)
' The example displays the following output if the current culture is en-US:
' 6/12/2008 00:00:00

ParseExact
Si se ajusta a uno de los patrones de cadena especificados, el método DateTime.ParseExact convierte una cadena en
un objeto DateTime. Cuando se pasa a este método una cadena que no tiene uno de las formas especificadas, se
genera una excepción FormatException. Puede definirse uno de los especificadores de formato de fecha y hora
estándar o una combinación de los especificadores de formato de fecha y hora personalizados. Con el uso de
especificadores de formato personalizados, es posible construir una cadena de reconocimiento personalizada. Para
obtener una explicación de los especificadores, consulte los temas sobre cadenas con formato de fecha y hora
estándar y cadenas con formato de fecha y hora personalizado.
En el ejemplo siguiente, se pasa al método DateTime.ParseExact un objeto de cadena para que lo analice, seguido
de un especificador de formato y luego de un objeto CultureInfo. Este método ParseExact solo puede analizar
cadenas que sigan el patrón de fecha larga en la referencia cultural en-US .
var cultureInfo = new CultureInfo("en-US");
string[] dateStrings = { " Friday, April 10, 2009", "Friday, April 10, 2009" };
foreach (string dateString in dateStrings)
{
try
{
var dateTime = DateTime.ParseExact(dateString, "D", cultureInfo);
Console.WriteLine(dateTime);
}
catch (FormatException)
{
Console.WriteLine("Unable to parse '{0}'", dateString);
}
}
// The example displays the following output:
// Unable to parse ' Friday, April 10, 2009'
// 4/10/2009 00:00:00

Dim MyCultureInfo As New CultureInfo("en-US")


Dim MyString() As String = {" Friday, April 10, 2009", "Friday, April 10, 2009"}
For Each dateString As String In MyString
Try
Dim MyDateTime As DateTime = DateTime.ParseExact(dateString, "D",
MyCultureInfo)
Console.WriteLine(MyDateTime)
Catch e As FormatException
Console.WriteLine("Unable to parse '{0}'", dateString)
End Try
Next
' The example displays the following output:
' Unable to parse ' Friday, April 10, 2009'
' 4/10/2009 00:00:00

Cada sobrecarga de los métodos Parse y ParseExact tiene también un parámetro IFormatProvider que proporciona
información específica de la referencia cultural sobre el formato de la cadena. Este objeto IFormatProvider es un
objeto CultureInfo que representa una referencia cultural estándar o un objeto DateTimeFormatInfo devuelto por la
propiedad CultureInfo.DateTimeFormat. ParseExact usa también una cadena o un argumento de matriz de cadena
adicional que define uno o más formatos de fecha y hora personalizados.

Vea también
Análisis de cadenas
Aplicar formato a tipos
Conversión de tipos en .NET
Cadenas con formato de fecha y hora estándar
Cadenas con formato de fecha y hora personalizado
Analizar otras cadenas en .NET
16/09/2020 • 3 minutes to read • Edit Online

Además de cadenas numéricas y DateTime, puede analizar cadenas que representan los tipos Char, Boolean y
Enum en tipos de datos.

Char
El método de análisis estático asociado con el tipo de datos Char es útil para convertir una cadena que contiene un
único carácter en su valor Unicode. En el ejemplo de código siguiente se analiza una cadena en un carácter
Unicode.

String^ MyString1 = "A";


char MyChar = Char::Parse(MyString1);
// MyChar now contains a Unicode "A" character.

string MyString1 = "A";


char MyChar = Char.Parse(MyString1);
// MyChar now contains a Unicode "A" character.

Dim MyString1 As String = "A"


Dim MyChar As Char = Char.Parse(MyString1)
' MyChar now contains a Unicode "A" character.

Booleano
El tipo de datos Boolean contiene un método Parse que se puede usar para convertir una cadena que representa
un valor Boolean en un tipo Boolean real. Este método no distingue mayúsculas de minúsculas y puede analizar
correctamente una cadena que contenga "True" o "False". El método Parse asociado al tipo Boolean también
puede analizar cadenas que estén rodeadas por espacios en blanco. Si se pasa otra cadena, se produce una
excepción FormatException.
En el ejemplo de código siguiente se usa el método Parse para convertir una cadena en un valor Boolean.

String^ MyString2 = "True";


bool MyBool = bool::Parse(MyString2);
// MyBool now contains a True Boolean value.

string MyString2 = "True";


bool MyBool = bool.Parse(MyString2);
// MyBool now contains a True Boolean value.

Dim MyString2 As String = "True"


Dim MyBool As Boolean = Boolean.Parse(MyString2)
' MyBool now contains a True Boolean value.
Enumeración
Puede usar el método Parse estático para inicializar un tipo de enumeración en el valor de una cadena. Este
método acepta el tipo de enumeración que se está analizando, la cadena que se va a analizar y una marca Boolean
opcional que indica si el análisis distingue mayúsculas de minúsculas. La cadena que se va a analizar puede
contener varios valores separados por comas, que pueden ir precedidos o seguidos de uno o varios espacios
vacíos (también denominados espacios en blanco). Cuando la cadena contiene varios valores, el valor del objeto
devuelto es el valor de todos los valores especificados combinados con una operación OR bit a bit.
En el ejemplo siguiente se usa el método Parse para convertir una representación de cadena en un valor de
enumeración. La enumeración DayOfWeek se inicializa en Thursday desde una cadena.

String^ MyString3 = "Thursday";


DayOfWeek MyDays = (DayOfWeek)Enum::Parse(DayOfWeek::typeid, MyString3);
Console::WriteLine(MyDays);
// The result is Thursday.

string MyString3 = "Thursday";


DayOfWeek MyDays = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), MyString3);
Console.WriteLine(MyDays);
// The result is Thursday.

Dim MyString3 As String = "Thursday"


Dim MyDays As DayOfWeek = CType([Enum].Parse(GetType(DayOfWeek), MyString3), DayOfWeek)
Console.WriteLine("{0:G}", MyDays)
' The result is Thursday.

Vea también
Analizar cadenas
Aplicación de formato a tipos
Conversión de tipos en .NET
Extender metadatos mediante atributos
16/09/2020 • 2 minutes to read • Edit Online

Common Language Runtime permite agregar declaraciones descriptivas a modo de palabras clave, conocidas
como atributos, para anotar elementos de programación como tipos, campos, métodos y propiedades. Cuando
compila el código para runtime, este se convierte al Lenguaje Intermedio de Microsoft (MSIL) y se coloca dentro
de un archivo portable ejecutable (PE) junto con los metadatos generados por el compilador. Los atributos
permiten colocar información descriptiva adicional en los metadatos que se puede extraer usando servicios de
reflexión en tiempo de ejecución. El compilador crea atributos cuando se declaran instancias de clases especiales
que derivan de System.Attribute.
.NET Framework usa atributos por distintos motivos y para tratar diversos problemas. Los atributos describen
cómo serializar los datos, especifican las características que se usan para aplicar la seguridad y limita las
optimizaciones del compilador Just-In-Time (JIT) para que el código siga siendo fácil de depurar. Los atributos
también pueden registrar el nombre de un archivo o el autor del código, o controlar la visibilidad de controles y
los miembros durante el desarrollo de formularios.

Temas relacionados
T IT L E DESC RIP C IÓ N

Aplicar atributos Describe cómo aplicar un atributo a un elemento del código.

Escribir atributos personalizados Describe cómo diseñar clases de atributos personalizados.

Recuperar la información almacenada en atributos Describe cómo recuperar los atributos personalizados del
código que se carga en el contexto de ejecución.

Metadatos y componentes autodescriptivos Proporciona información general de los metadatos y describe


cómo se implementan en un archivo portable ejecutable (PE)
de .NET Framework.

Cómo: Cargar ensamblados en el contexto de solo reflexión Explica cómo recuperar la información de los atributos
personalizados en el contexto de solo reflexión.

Referencia
System.Attribute
Aplicar atributos
16/09/2020 • 5 minutes to read • Edit Online

Para aplicar un atributo a un elemento del código se puede utilizar el proceso siguiente:
1. Definir un atributo nuevo o utilizar uno existente importando su espacio de nombres de .NET Framework.
2. Aplique el atributo al elemento de código colocándolo inmediatamente antes del elemento.
Cada lenguaje tiene su propia sintaxis de atributo. En C++ y C#, el atributo está incluido entre corchetes y
separado del elemento por un espacio en blanco, que puede incluir un salto de línea. En Visual Basic, el
atributo está incluido entre corchetes angulares y debe estar en la misma línea lógica; se puede utilizar el
carácter de continuación de línea si se desea un salto de línea.
3. Especifique parámetros posicionales y parámetros con nombre para el atributo.
Los parámetros posicionales son obligatorios y preceden a cualquier parámetro con nombre; corresponden
a los parámetros de uno de los constructores del atributo. Los parámetros con nombre son opcionales y
corresponden a las propiedades de lectura y escritura del atributo. En C++ y C#, especifique name = value
para cada parámetro opcional, donde name es el nombre de la propiedad. En Visual Basic, especifique name
:= value .
El atributo se emite en metadatos al compilar el código y queda disponible para Common Language Runtime y
cualquier aplicación o herramienta personalizada a través de los servicios de reflexión en tiempo de ejecución.
Por convención, todos los nombres de atributos terminan con la palabra Attribute. Sin embargo, algunos lenguajes
orientados a Common Language Runtime, como Visual Basic y C#, no requieren que se especifique el nombre
completo de los atributos. Por ejemplo, si desea inicializar System.ObsoleteAttribute, solo es necesario hacer
referencia al mismo como Obsolete .

Aplicar un atributo a un método


El siguiente ejemplo de código muestra cómo se declara System.ObsoleteAttribute , que marca código como
obsoleto. La cadena "Will be removed in next version" se pasa al atributo. Este atributo da lugar a una advertencia
del compilador que muestra la cadena transferida cuando se llama al código que describe el atributo.
public ref class Example
{
// Specify attributes between square brackets in C#.
// This attribute is applied only to the Add method.
public:
[Obsolete("Will be removed in next version.")]
static int Add(int a, int b)
{
return (a + b);
}
};

ref class Test


{
public:
static void Main()
{
// This generates a compile-time warning.
int i = Example::Add(2, 2);
}
};

int main()
{
Test::Main();
}

public class Example


{
// Specify attributes between square brackets in C#.
// This attribute is applied only to the Add method.
[Obsolete("Will be removed in next version.")]
public static int Add(int a, int b)
{
return (a + b);
}
}

class Test
{
public static void Main()
{
// This generates a compile-time warning.
int i = Example.Add(2, 2);
}
}

Public Class Example


' Specify attributes between square brackets in C#.
' This attribute is applied only to the Add method.
<Obsolete("Will be removed in next version.")>
Public Shared Function Add(a As Integer, b As Integer) As Integer
Return a + b
End Function
End Class

Class Test
Public Shared Sub Main()
' This generates a compile-time warning.
Dim i As Integer = Example.Add(2, 2)
End Sub
End Class
Aplicar atributos a ensamblados
Si desea aplicar un atributo a un ensamblado, utilice la palabra clave assembly ( Assembly en Visual Basic). El
código siguiente muestra el atributoAssemblyTitleAttribute aplicado al ensamblado.

using namespace System::Reflection;


[assembly:AssemblyTitle("My Assembly")];

using System.Reflection;
[assembly:AssemblyTitle("My Assembly")]

Imports System.Reflection
<Assembly: AssemblyTitle("My Assembly")>

Cuando se aplica este atributo, la cadena "My Assembly" se coloca en el manifiesto del ensamblado, en la parte de
metadatos del archivo. Se puede ver el atributo utilizando el Desensamblador de MSIL (Ildasm.exe) o creando un
programa personalizado que recupere el atributo.

Vea también
Atributos
Retrieving Information Stored in Attributes (Recuperar la información almacenada en atributos)
Conceptos
Atributos (C#)
Información general sobre los atributos (Visual Basic)
Escribir atributos personalizados
16/09/2020 • 15 minutes to read • Edit Online

Para diseñar sus propios atributos personalizados, no necesitará dominar muchos conceptos nuevos. Si está
familiarizado con la programación orientada a objetos y sabe cómo diseñar clases, ya tiene la mayoría de los
conocimientos necesarios. Los atributos personalizados son esencialmente clases tradicionales que se derivan
directa o indirectamente de System.Attribute. Como sucede con las clases tradicionales, los atributos
personalizados contienen métodos que almacenan y recuperan datos.
Los pasos principales para diseñar correctamente clases de atributos personalizados son los siguientes:
Aplicar AttributeUsageAttribute
Declarar la clase de atributo
Declarar constructores
Declarar propiedades
En esta sección se describe cada uno de estos pasos y, para finalizar, se muestra un ejemplo de atributo
personalizado.

Aplicar AttributeUsageAttribute
La declaración de un atributo personalizado empieza con System.AttributeUsageAttribute, que define algunas de
las características clave de la clase de atributo. Por ejemplo, puede especificar si el atributo lo pueden heredar otras
clases o especificar los elementos a los que se puede aplicar el atributo. En el fragmento de código siguiente se
muestra cómo usar AttributeUsageAttribute.

[AttributeUsage(AttributeTargets::All, Inherited = false, AllowMultiple = true)]

[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]

<AttributeUsage(AttributeTargets.All, Inherited:=False, AllowMultiple:=True)>


Public Class SomeClass
Inherits Attribute
'...
End Class

AttributeUsageAttribute tiene tres miembros que son importantes para la creación de atributos personalizados:
AttributeTargets, Inherited y AllowMultiple.
Miembro AttributeTargets
En el ejemplo anterior, se especifica AttributeTargets.All, lo que indica que este atributo se puede aplicar a todos los
elementos de programa. Como alternativa, puede especificar AttributeTargets.Class, que indica que el atributo solo
se puede aplicar a una clase, o AttributeTargets.Method, que indica que el atributo solo se puede aplicar a un
método. De esta manera, un atributo personalizado puede marcar para descripción todos los elementos del
programa.
También puede pasar varios valores AttributeTargets. El fragmento de código siguiente especifica que un atributo
personalizado se puede aplicar a cualquier clase o método.
[AttributeUsage(AttributeTargets::Class | AttributeTargets::Method)]

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]

<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Method)>
Public Class SomeOtherClass
Inherits Attribute
'...
End Class

Propiedad Inherited
La propiedad AttributeUsageAttribute.Inherited indica si el atributo lo pueden heredar clases derivadas de las
clases a las que se aplica el atributo. Esta propiedad acepta una marca true (valor predeterminado) o false . En el
ejemplo siguiente, MyAttribute tiene un valor Inherited predeterminado de true , mientras que YourAttribute
tiene un valor Inherited de false .

// This defaults to Inherited = true.


public ref class MyAttribute : Attribute
{
//...
};

[AttributeUsage(AttributeTargets::Method, Inherited = false)]


public ref class YourAttribute : Attribute
{
//...
};

// This defaults to Inherited = true.


public class MyAttribute : Attribute
{
//...
}

[AttributeUsage(AttributeTargets.Method, Inherited = false)]


public class YourAttribute : Attribute
{
//...
}

' This defaults to Inherited = true.


Public Class MyAttribute
Inherits Attribute
'...
End Class

<AttributeUsage(AttributeTargets.Method, Inherited:=False)>
Public Class YourAttribute
Inherits Attribute
'...
End Class

Los dos atributos se aplican entonces a un método de la clase base MyClass .


public ref class MyClass
{
public:
[MyAttribute]
[YourAttribute]
virtual void MyMethod()
{
//...
}
};

public class MyClass


{
[MyAttribute]
[YourAttribute]
public virtual void MyMethod()
{
//...
}
}

Public Class MeClass


<MyAttribute>
<YourAttribute>
Public Overridable Sub MyMethod()
'...
End Sub
End Class

Por último, la clase YourClass se hereda de la clase base MyClass . El método MyMethod muestra MyAttribute ,
pero no YourAttribute .

public ref class YourClass : MyClass


{
public:
// MyMethod will have MyAttribute but not YourAttribute.
virtual void MyMethod() override
{
//...
}

};

public class YourClass : MyClass


{
// MyMethod will have MyAttribute but not YourAttribute.
public override void MyMethod()
{
//...
}
}
Public Class YourClass
Inherits MeClass
' MyMethod will have MyAttribute but not YourAttribute.
Public Overrides Sub MyMethod()
'...
End Sub

End Class

Propiedad AllowMultiple
La propiedad AttributeUsageAttribute.AllowMultiple indica si pueden existir varias instancias del atributo en un
elemento. Si establece en true , se permiten varias instancias; si se establece en false (el valor predeterminado),
solo se permite una instancia.
En el ejemplo siguiente, MyAttribute tiene un valor AllowMultiple predeterminado de false , mientras que
YourAttribute tiene un valor de true .

//This defaults to AllowMultiple = false.


public ref class MyAttribute : Attribute
{
};

[AttributeUsage(AttributeTargets::Method, AllowMultiple = true)]


public ref class YourAttribute : Attribute
{
};

//This defaults to AllowMultiple = false.


public class MyAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]


public class YourAttribute : Attribute
{
}

' This defaults to AllowMultiple = false.


Public Class MyAttribute
Inherits Attribute
End Class

<AttributeUsage(AttributeTargets.Method, AllowMultiple:=true)>
Public Class YourAttribute
Inherits Attribute
End Class

Si se aplican varias instancias de estos atributos, MyAttribute produce un error del compilador. En el ejemplo de
código siguiente se muestra el uso válido de YourAttribute y el uso no válido de MyAttribute .
public ref class MyClass
{
public:
// This produces an error.
// Duplicates are not allowed.
[MyAttribute]
[MyAttribute]
void MyMethod()
{
//...
}

// This is valid.
[YourAttribute]
[YourAttribute]
void YourMethod()
{
//...
}
};

public class MyClass


{
// This produces an error.
// Duplicates are not allowed.
[MyAttribute]
[MyAttribute]
public void MyMethod()
{
//...
}

// This is valid.
[YourAttribute]
[YourAttribute]
public void YourMethod()
{
//...
}
}

Public Class MyClass


' This produces an error.
' Duplicates are not allowed.
<MyAttribute>
<MyAttribute>
Public Sub MyMethod()
'...
End Sub

' This is valid.


<YourAttribute>
<YourAttribute>
Public Sub YourMethod()
'...
End Sub
End Class

Si tanto la propiedad AllowMultiple como la propiedad Inherited se establecen en true , una clase que se hereda
de otra clase puede heredar un atributo y tiene otra instancia del mismo atributo aplicada en la misma clase
secundaria. Si se establece AllowMultiple en false , las instancias nuevas del mismo atributo de la clase secundaria
sobrescribirán los valores de los atributos de la clase primaria.
Declarar la clase de atributo
Después de aplicar AttributeUsageAttribute, puede empezar a definir los detalles del atributo. La declaración de
una clase de atributo es similar a la declaración de una clase tradicional, como se muestra en el código siguiente.

[AttributeUsage(AttributeTargets::Method)]
public ref class MyAttribute : Attribute
{
// . . .
};

[AttributeUsage(AttributeTargets.Method)]
public class MyAttribute : Attribute
{
// . . .
}

<AttributeUsage(AttributeTargets.Method)>
Public Class MyAttribute
Inherits Attribute
' . . .
End Class

En esta definición de atributo se muestra lo siguiente:


Las clases de atributos se deben declarar como clases públicas.
Por convención, el nombre de la clase de atributo termina con la palabra Attribute . Aunque no es
obligatoria, se recomienda seguir esta convención para mejorar la legibilidad. Cuando se aplica el atributo,
la inclusión de la palabra Attribute es opcional.
Todas las clases de atributo deben heredar directa o indirectamente de System.Attribute.
En Microsoft Visual Basic, todas las clases de atributos personalizados deben tener el atributo
System.AttributeUsageAttribute.

Declarar constructores
Los atributos se inicializan con constructores del mismo modo que las clases tradicionales. En el siguiente
fragmento de código se muestra un constructor de atributos típico. Este constructor público acepta un parámetro y
establece una variable de miembro igual a su valor.

MyAttribute(bool myvalue)
{
this->myvalue = myvalue;
}

public MyAttribute(bool myvalue)


{
this.myvalue = myvalue;
}
Public Sub New(myvalue As Boolean)
Me.myvalue = myvalue
End Sub

Puede sobrecargar el constructor para adaptarse a distintas combinaciones de valores. Si también define una
propiedad para la clase de atributo personalizado, puede utilizar una combinación de parámetros con nombre y de
posición al inicializar el atributo. Normalmente, se definen todos los parámetros necesarios como de posición y
todos parámetros opcionales como con nombre. En este caso, el atributo no se puede inicializar sin el parámetro
obligatorio. Todos los demás parámetros son opcionales. Tenga en cuenta que en Visual Basic, los constructores de
una clase de atributo no deben utilizar un argumento ParamArray.
En el ejemplo de código siguiente, se muestra cómo se puede aplicar un atributo que utiliza el constructor anterior
mediante parámetros obligatorios y opcionales. Se supone que el atributo tiene un valor booleano obligatorio y
una propiedad de cadena opcional.

// One required (positional) and one optional (named) parameter are applied.
[MyAttribute(false, OptionalParameter = "optional data")]
public ref class SomeClass
{
//...
};
// One required (positional) parameter is applied.
[MyAttribute(false)]
public ref class SomeOtherClass
{
//...
};

// One required (positional) and one optional (named) parameter are applied.
[MyAttribute(false, OptionalParameter = "optional data")]
public class SomeClass
{
//...
}
// One required (positional) parameter is applied.
[MyAttribute(false)]
public class SomeOtherClass
{
//...
}

' One required (positional) and one optional (named) parameter are applied.
<MyAttribute(false, OptionalParameter:="optional data")>
Public Class SomeClass
'...
End Class

' One required (positional) parameter is applied.


<MyAttribute(false)>
Public Class SomeOtherClass
'...
End Class

Declarar propiedades
Si quiere definir un parámetro con nombre o proporcionar un método sencillo para devolver los valores
almacenados por el atributo, declare una propiedad. Las propiedades de atributo deben declararse como entidades
públicas con una descripción del tipo de datos que se devolverá. Defina la variable que contendrá el valor de la
propiedad y asóciela con los métodos get y set . En el ejemplo de código siguiente se muestra cómo implementar
una propiedad simple en el atributo.

property bool MyProperty


{
bool get() {return this->myvalue;}
void set(bool value) {this->myvalue = value;}
}

public bool MyProperty


{
get {return this.myvalue;}
set {this.myvalue = value;}
}

Public Property MyProperty As Boolean


Get
Return Me.myvalue
End Get
Set
Me.myvalue = Value
End Set
End Property

ejemplo de atributo personalizado


En esta sección se incorpora la información anterior y se muestra cómo se diseña un atributo simple que
documente información sobre el autor de una sección del código. El atributo de este ejemplo almacena el nombre
y el nivel del programador y si se ha revisado el código. Utiliza tres variables privadas para almacenar los valores
reales que se deben guardar. Cada variable se representa mediante una propiedad pública que obtiene y establece
los valores. Por último, el constructor se define con dos parámetros obligatorios.
[AttributeUsage(AttributeTargets::All)]
public ref class DeveloperAttribute : Attribute
{
// Private fields.
private:
String^ name;
String^ level;
bool reviewed;

public:
// This constructor defines two required parameters: name and level.

DeveloperAttribute(String^ name, String^ level)


{
this->name = name;
this->level = level;
this->reviewed = false;
}

// Define Name property.


// This is a read-only attribute.

virtual property String^ Name


{
String^ get() {return name;}
}

// Define Level property.


// This is a read-only attribute.

virtual property String^ Level


{
String^ get() {return level;}
}

// Define Reviewed property.


// This is a read/write attribute.

virtual property bool Reviewed


{
bool get() {return reviewed;}
void set(bool value) {reviewed = value;}
}
};
[AttributeUsage(AttributeTargets.All)]
public class DeveloperAttribute : Attribute
{
// Private fields.
private string name;
private string level;
private bool reviewed;

// This constructor defines two required parameters: name and level.

public DeveloperAttribute(string name, string level)


{
this.name = name;
this.level = level;
this.reviewed = false;
}

// Define Name property.


// This is a read-only attribute.

public virtual string Name


{
get {return name;}
}

// Define Level property.


// This is a read-only attribute.

public virtual string Level


{
get {return level;}
}

// Define Reviewed property.


// This is a read/write attribute.

public virtual bool Reviewed


{
get {return reviewed;}
set {reviewed = value;}
}
}
<AttributeUsage(AttributeTargets.All)>
Public Class DeveloperAttribute
Inherits Attribute
' Private fields.
Private myname As String
Private mylevel As String
Private myreviewed As Boolean

' This constructor defines two required parameters: name and level.

Public Sub New(name As String, level As String)


Me.myname = name
Me.mylevel = level
Me.myreviewed = False
End Sub

' Define Name property.


' This is a read-only attribute.

Public Overridable ReadOnly Property Name() As String


Get
Return myname
End Get
End Property

' Define Level property.


' This is a read-only attribute.

Public Overridable ReadOnly Property Level() As String


Get
Return mylevel
End Get
End Property

' Define Reviewed property.


' This is a read/write attribute.

Public Overridable Property Reviewed() As Boolean


Get
Return myreviewed
End Get
Set
myreviewed = value
End Set
End Property
End Class

Puede aplicar este atributo con el nombre completo, DeveloperAttribute , o el nombre abreviado, Developer , de
una de las maneras siguientes.

[Developer("Joan Smith", "1")]

-or-

[Developer("Joan Smith", "1", Reviewed = true)]

[Developer("Joan Smith", "1")]

-or-

[Developer("Joan Smith", "1", Reviewed = true)]


<Developer("Joan Smith", "1")>

-or-

<Developer("Joan Smith", "1", Reviewed := true)>

En el primer ejemplo se muestra el atributo aplicado solo con los parámetros con nombre obligatorios, mientras
que en el segundo ejemplo se muestra el atributo aplicado con los parámetros obligatorios y opcionales.

Vea también
System.Attribute
System.AttributeUsageAttribute
Atributos
Recuperar información almacenada en atributos
16/09/2020 • 13 minutes to read • Edit Online

La recuperación de un atributo personalizado es un proceso sencillo. En primer lugar, declare una instancia del
atributo que desea recuperar. A continuación, utilice el método Attribute.GetCustomAttribute para inicializar el
atributo nuevo en el valor del atributo que desea recuperar. Una vez inicializado el nuevo atributo, basta con usar
sus propiedades para obtener los valores.

IMPORTANT
En este tema se describe cómo recuperar los atributos personalizados del código que se carga en el contexto de ejecución.
Para recuperar los atributos del código que se carga en el contexto de solo reflexión, se debe usar la clase
CustomAttributeData, como se muestra en Cómo: Cargar ensamblados en el contexto de solo reflexión.

En esta sección se describen los siguientes métodos para recuperar atributos:


Recuperar una sola instancia de un atributo
Recuperar varias instancias de un atributo aplicadas al mismo ámbito
Recuperar varias instancias de un atributo aplicadas a diferentes ámbitos

Recuperar una sola instancia de un atributo


En el ejemplo siguiente, el atributo DeveloperAttribute (descrito en la sección anterior) se aplica a la clase
MainApp en el nivel de clase. El método GetAttribute usa GetCustomAttribute para recuperar los valores
almacenados en DeveloperAttribute en el nivel de clase antes de mostrarlos en la consola.
using namespace System;
using namespace System::Reflection;
using namespace CustomCodeAttributes;

[Developer("Joan Smith", "42", Reviewed = true)]


ref class MainApp
{
public:
static void Main()
{
// Call function to get and display the attribute.
GetAttribute(MainApp::typeid);
}

static void GetAttribute(Type^ t)


{
// Get instance of the attribute.
DeveloperAttribute^ MyAttribute =
(DeveloperAttribute^) Attribute::GetCustomAttribute(t, DeveloperAttribute::typeid);

if (MyAttribute == nullptr)
{
Console::WriteLine("The attribute was not found.");
}
else
{
// Get the Name value.
Console::WriteLine("The Name Attribute is: {0}." , MyAttribute->Name);
// Get the Level value.
Console::WriteLine("The Level Attribute is: {0}." , MyAttribute->Level);
// Get the Reviewed value.
Console::WriteLine("The Reviewed Attribute is: {0}." , MyAttribute->Reviewed);
}
}
};
using System;
using System.Reflection;
using CustomCodeAttributes;

[Developer("Joan Smith", "42", Reviewed = true)]


class MainApp
{
public static void Main()
{
// Call function to get and display the attribute.
GetAttribute(typeof(MainApp));
}

public static void GetAttribute(Type t)


{
// Get instance of the attribute.
DeveloperAttribute MyAttribute =
(DeveloperAttribute) Attribute.GetCustomAttribute(t, typeof (DeveloperAttribute));

if (MyAttribute == null)
{
Console.WriteLine("The attribute was not found.");
}
else
{
// Get the Name value.
Console.WriteLine("The Name Attribute is: {0}." , MyAttribute.Name);
// Get the Level value.
Console.WriteLine("The Level Attribute is: {0}." , MyAttribute.Level);
// Get the Reviewed value.
Console.WriteLine("The Reviewed Attribute is: {0}." , MyAttribute.Reviewed);
}
}
}

Imports System.Reflection
Imports CustomCodeAttributes

<Developer("Joan Smith", "42", Reviewed:=True)>


Class MainApp
Public Shared Sub Main()
' Call function to get and display the attribute.
GetAttribute(GetType(MainApp))
End Sub

Public Shared Sub GetAttribute(t As Type)


' Get instance of the attribute.
Dim MyAttribute As DeveloperAttribute =
CType(Attribute.GetCustomAttribute(t, GetType(DeveloperAttribute)), DeveloperAttribute)

If MyAttribute Is Nothing Then


Console.WriteLine("The attribute was not found.")
Else
' Get the Name value.
Console.WriteLine("The Name Attribute is: {0}.", MyAttribute.Name)
' Get the Level value.
Console.WriteLine("The Level Attribute is: {0}.", MyAttribute.Level)
' Get the Reviewed value.
Console.WriteLine("The Reviewed Attribute is: {0}.", MyAttribute.Reviewed)
End If
End Sub
End Class

Este programa muestra el texto siguiente cuando se ejecuta.


The Name Attribute is: Joan Smith.
The Level Attribute is: 42.
The Reviewed Attribute is: True.

Si no se encuentra el atributo, el método GetCustomAttribute inicializa MyAttribute con un valor null. En este
ejemplo se comprueba si en MyAttribute se encuentra dicha instancia y notifica al usuario si no se encuentra
ningún atributo. Si DeveloperAttribute no se encuentra en el ámbito de clase, aparece el mensaje siguiente en la
consola.

The attribute was not found.

En este ejemplo se da por supuesto que la definición de atributo está en el espacio de nombres actual. No olvide
importar el espacio de nombres en el que reside la definición de atributo si no está en el espacio de nombres
actual.

Recuperar varias instancias de un atributo aplicadas al mismo ámbito


En el ejemplo anterior, la clase para inspeccionar y el atributo específico para buscar se pasan a
GetCustomAttribute. Ese código funciona bien si solo una instancia de un atributo se aplica en el nivel de clase. Sin
embargo, si se aplican varias instancias de un atributo en el mismo nivel de clase, el método
GetCustomAttribute no recupera toda la información. En casos donde se aplican varias instancias del mismo
atributo al mismo ámbito, puede usar Attribute.GetCustomAttributes para colocar todas las instancias de un
atributo en una matriz. Por ejemplo, si dos instancias de DeveloperAttribute se aplican en el nivel de clase de la
misma clase, el método GetAttribute se puede modificar para mostrar la información encontrada en ambos
atributos. Recuerde que, para aplicar varios atributos en el mismo nivel, el atributo debe definirse con la propiedad
AllowMultiple establecida en true en AttributeUsageAttribute.
En el ejemplo de código siguiente, se muestra cómo utilizar el método GetCustomAttributes para crear una
matriz que haga referencia a todas las instancias de DeveloperAttribute en cualquier clase específica. Los valores
de todos los atributos, a continuación, se muestran en la consola.

public:
static void GetAttribute(Type^ t)
{
array<DeveloperAttribute^>^ MyAttributes =
(array<DeveloperAttribute^>^) Attribute::GetCustomAttributes(t, DeveloperAttribute::typeid);

if (MyAttributes->Length == 0)
{
Console::WriteLine("The attribute was not found.");
}
else
{
for (int i = 0 ; i < MyAttributes->Length; i++)
{
// Get the Name value.
Console::WriteLine("The Name Attribute is: {0}." , MyAttributes[i]->Name);
// Get the Level value.
Console::WriteLine("The Level Attribute is: {0}." , MyAttributes[i]->Level);
// Get the Reviewed value.
Console::WriteLine("The Reviewed Attribute is: {0}.", MyAttributes[i]->Reviewed);
}
}
}
public static void GetAttribute(Type t)
{
DeveloperAttribute[] MyAttributes =
(DeveloperAttribute[]) Attribute.GetCustomAttributes(t, typeof (DeveloperAttribute));

if (MyAttributes.Length == 0)
{
Console.WriteLine("The attribute was not found.");
}
else
{
for (int i = 0 ; i < MyAttributes.Length ; i++)
{
// Get the Name value.
Console.WriteLine("The Name Attribute is: {0}." , MyAttributes[i].Name);
// Get the Level value.
Console.WriteLine("The Level Attribute is: {0}." , MyAttributes[i].Level);
// Get the Reviewed value.
Console.WriteLine("The Reviewed Attribute is: {0}.", MyAttributes[i].Reviewed);
}
}
}

Public Shared Sub GetAttribute(t As Type)


Dim MyAttributes() As DeveloperAttribute =
CType(Attribute.GetCustomAttributes(t, GetType(DeveloperAttribute)), DeveloperAttribute())

If MyAttributes.Length = 0 Then
Console.WriteLine("The attribute was not found.")
Else
For i As Integer = 0 To MyAttributes.Length - 1
' Get the Name value.
Console.WriteLine("The Name Attribute is: {0}.", MyAttributes(i).Name)
' Get the Level value.
Console.WriteLine("The Level Attribute is: {0}.", MyAttributes(i).Level)
' Get the Reviewed value.
Console.WriteLine("The Reviewed Attribute is: {0}.", MyAttributes(i).Reviewed)
Next i
End If
End Sub

Si no se encuentra ningún atributo, este código avisa al usuario. En caso contrario, se muestra la información
contenida en ambas instancias de DeveloperAttribute .

Recuperar varias instancias de un atributo aplicadas a diferentes


ámbitos
Los métodos GetCustomAttributes y GetCustomAttribute no buscan una clase completa y devuelven todas las
instancias de un atributo de esa clase. En su lugar, buscan solo un método especificado o un miembro a la vez. Si
tiene una clase con el mismo atributo aplicado a todos los miembros y desea recuperar los valores de todos los
atributos aplicados a dichos miembros, debe proporcionar cada método o miembro individualmente a
GetCustomAttributes y GetCustomAttribute .
En el ejemplo de código siguiente se toma una clase como parámetro y busca DeveloperAttribute (definido
previamente) en el nivel de clase y en todos los métodos individuales de esa clase.
public:
static void GetAttribute(Type^ t)
{
DeveloperAttribute^ att;

// Get the class-level attributes.

// Put the instance of the attribute on the class level in the att object.
att = (DeveloperAttribute^) Attribute::GetCustomAttribute (t, DeveloperAttribute::typeid);

if (att == nullptr)
{
Console::WriteLine("No attribute in class {0}.\n", t->ToString());
}
else
{
Console::WriteLine("The Name Attribute on the class level is: {0}.", att->Name);
Console::WriteLine("The Level Attribute on the class level is: {0}.", att->Level);
Console::WriteLine("The Reviewed Attribute on the class level is: {0}.\n", att->Reviewed);
}

// Get the method-level attributes.

// Get all methods in this class, and put them


// in an array of System.Reflection.MemberInfo objects.
array<MemberInfo^>^ MyMemberInfo = t->GetMethods();

// Loop through all methods in this class that are in the


// MyMemberInfo array.
for (int i = 0; i < MyMemberInfo->Length; i++)
{
att = (DeveloperAttribute^) Attribute::GetCustomAttribute(MyMemberInfo[i],
DeveloperAttribute::typeid);
if (att == nullptr)
{
Console::WriteLine("No attribute in member function {0}.\n" , MyMemberInfo[i]->ToString());
}
else
{
Console::WriteLine("The Name Attribute for the {0} member is: {1}.",
MyMemberInfo[i]->ToString(), att->Name);
Console::WriteLine("The Level Attribute for the {0} member is: {1}.",
MyMemberInfo[i]->ToString(), att->Level);
Console::WriteLine("The Reviewed Attribute for the {0} member is: {1}.\n",
MyMemberInfo[i]->ToString(), att->Reviewed);
}
}
}
public static void GetAttribute(Type t)
{
DeveloperAttribute att;

// Get the class-level attributes.

// Put the instance of the attribute on the class level in the att object.
att = (DeveloperAttribute) Attribute.GetCustomAttribute (t, typeof (DeveloperAttribute));

if (att == null)
{
Console.WriteLine("No attribute in class {0}.\n", t.ToString());
}
else
{
Console.WriteLine("The Name Attribute on the class level is: {0}.", att.Name);
Console.WriteLine("The Level Attribute on the class level is: {0}.", att.Level);
Console.WriteLine("The Reviewed Attribute on the class level is: {0}.\n", att.Reviewed);
}

// Get the method-level attributes.

// Get all methods in this class, and put them


// in an array of System.Reflection.MemberInfo objects.
MemberInfo[] MyMemberInfo = t.GetMethods();

// Loop through all methods in this class that are in the


// MyMemberInfo array.
for (int i = 0; i < MyMemberInfo.Length; i++)
{
att = (DeveloperAttribute) Attribute.GetCustomAttribute(MyMemberInfo[i], typeof (DeveloperAttribute));
if (att == null)
{
Console.WriteLine("No attribute in member function {0}.\n" , MyMemberInfo[i].ToString());
}
else
{
Console.WriteLine("The Name Attribute for the {0} member is: {1}.",
MyMemberInfo[i].ToString(), att.Name);
Console.WriteLine("The Level Attribute for the {0} member is: {1}.",
MyMemberInfo[i].ToString(), att.Level);
Console.WriteLine("The Reviewed Attribute for the {0} member is: {1}.\n",
MyMemberInfo[i].ToString(), att.Reviewed);
}
}
}
Public Shared Sub GetAttribute(t As Type)
Dim att As DeveloperAttribute

' Get the class-level attributes.

' Put the instance of the attribute on the class level in the att object.
att = CType(Attribute.GetCustomAttribute(t, GetType(DeveloperAttribute)), DeveloperAttribute)

If att Is Nothing
Console.WriteLine("No attribute in class {0}.\n", t.ToString())
Else
Console.WriteLine("The Name Attribute on the class level is: {0}.", att.Name)
Console.WriteLine("The Level Attribute on the class level is: {0}.", att.Level)
Console.WriteLine("The Reviewed Attribute on the class level is: {0}.\n", att.Reviewed)
End If

' Get the method-level attributes.

' Get all methods in this class, and put them


' in an array of System.Reflection.MemberInfo objects.
Dim MyMemberInfo() As MemberInfo = t.GetMethods()

' Loop through all methods in this class that are in the
' MyMemberInfo array.
For i As Integer = 0 To MyMemberInfo.Length - 1
att = CType(Attribute.GetCustomAttribute(MyMemberInfo(i), _
GetType(DeveloperAttribute)), DeveloperAttribute)
If att Is Nothing Then
Console.WriteLine("No attribute in member function {0}.\n", MyMemberInfo(i).ToString())
Else
Console.WriteLine("The Name Attribute for the {0} member is: {1}.",
MyMemberInfo(i).ToString(), att.Name)
Console.WriteLine("The Level Attribute for the {0} member is: {1}.",
MyMemberInfo(i).ToString(), att.Level)
Console.WriteLine("The Reviewed Attribute for the {0} member is: {1}.\n",
MyMemberInfo(i).ToString(), att.Reviewed)
End If
Next
End Sub

Si no se encuentran instancias de DeveloperAttribute en el nivel de método o en el nivel de clase, el método


GetAttribute notifica al usuario que no se encontraron atributos y muestra el nombre del método o de la clase
que no contiene el atributo. Si se encuentra algún atributo, los campos Name , Level y Reviewed se muestran en
la consola.
Puede utilizar los miembros de la clase Type para obtener los miembros y métodos individuales de la clase pasada.
En este ejemplo primero se consulta el objeto Type para obtener información del atributo para el nivel de clase. A
continuación, se utiliza Type.GetMethods para colocar instancias de todos los métodos en una matriz de objetos
System.Reflection.MemberInfo para recuperar información del atributo para el nivel del método. También puede
usar el método Type.GetProperties para comprobar los atributos en el nivel de propiedad o Type.GetConstructors
para comprobar los atributos en el nivel de constructor.

Vea también
System.Type
Attribute.GetCustomAttribute
Attribute.GetCustomAttributes
Atributos
Elección entre tipos de tupla y anónimos
16/09/2020 • 7 minutes to read • Edit Online

A la hora de elegir el tipo adecuado, hay que tener en cuenta su usabilidad, su rendimiento y sus compensaciones
en comparación con otros tipos. Los tipos anónimos están disponibles desde la versión 3.0 de C#, mientras que los
tipos System.Tuple<T1,T2> genéricos se introdujeron con .NET Framework 4.0. Ya que se han incorporado nuevas
opciones con compatibilidad de lenguaje, como System.ValueTuple<T1,T2>, que, como indica su nombre,
proporciona un tipo de valor con la flexibilidad de los tipos anónimos. En este artículo aprenderá cuándo es
pertinente elegir un tipo en lugar de otro.

Usabilidad y funcionalidad
Los tipos anónimos se introdujeron en la versión 3.0 de C# con expresiones de Language Integrated Query (LINQ).
Con LINQ, los desarrolladores suelen proyectar los resultados de consultas a tipos anónimos que contienen
algunas propiedades concretas de los objetos con los que están trabajando. Fíjese en el ejemplo siguiente, en el que
se crea una instancia de una matriz de objetos DateTime y se itera a través de ellos proyectándose en un tipo
anónimo con dos propiedades.

var dates = new[]


{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};

foreach (var anonymous in


dates.Select(
date => new { Formatted = $"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks }))
{
Console.WriteLine($"Ticks: {anonymous.Ticks}, formatted: {anonymous.Formatted}");
}

Se crea una instancia de los tipos anónimos usando el operador new , y los nombres y tipos de propiedades se
infieren a partir de la declaración. Si dos o más inicializadores de objeto anónimo en el mismo ensamblado
especifican una secuencia de propiedades que están en el mismo orden y que tienen los mismos nombres y tipos,
el compilador trata el objeto como instancias del mismo tipo. Comparten la misma información de tipo generada
por el compilador.
El fragmento de C# anterior proyecta un tipo anónimo con dos propiedades, de forma similar a esta clase de C#
generada por el compilador:

internal sealed class f__AnonymousType0


{
public string Formatted { get; }
public long Ticks { get; }

public f__AnonymousType0(string formatted, long ticks)


{
Formatted = formatted;
Ticks = ticks;
}
}
Para obtener más información, consulte los tipos anónimos. Existe la misma funcionalidad con las tuplas al
proyectar en consultas de LINQ; puede seleccionar propiedades en tuplas. Estas tuplas fluyen a través de la
consulta, de la misma forma que los tipos anónimos. Ahora, tenga en cuenta el ejemplo siguiente usando
System.Tuple<string, long> .

var dates = new[]


{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};

foreach (var tuple in


dates.Select(
date => new Tuple<string, long>($"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks)))
{
Console.WriteLine($"Ticks: {tuple.Item2}, formatted: {tuple.Item1}");
}

Con System.Tuple<T1,T2>, la instancia expone propiedades de elementos numeradas, como Item1 y Item2 . Estos
nombres de propiedades pueden hacer que resulte más complicado entender la intención de los valores de
propiedad, ya que el nombre propiedad solo proporciona el ordinal. Además, los tipos System.Tuple son tipos
class de referencia. Sin embargo, System.ValueTuple<T1,T2> es un tipo struct de valor. En el fragmento de C#
siguiente se usa ValueTuple<string, long> para hacer la proyección. Al hacerlo, se asigna mediante una sintaxis
literal.

var dates = new[]


{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};

foreach (var (formatted, ticks) in


dates.Select(
date => (Formatted: $"{date:MMM dd, yyyy at hh:mm zzz}", date.Ticks)))
{
Console.WriteLine($"Ticks: {ticks}, formatted: {formatted}");
}

Para obtener más información sobre las tuplas, consulte Tipos de tupla (referencia de C#) o Tuplas (Visual Basic).
Todos los ejemplos anteriores son equivalentes funcionalmente; sin embargo, hay pequeñas diferencias en cuanto a
su usabilidad y sus implementaciones subyacentes.

Compromisos
Es recomendable que use siempre ValueTuple en lugar de Tuple, además de los tipos anónimos, pero hay
compensaciones que debería tener en cuenta. Los tipos de ValueTuple son mutables, mientras que los de Tuple son
de solo lectura. Los tipos anónimos se pueden usar en árboles de expresión; en cambio, las tuplas no. En la tabla
siguiente se muestra información general sobre algunas de las principales diferencias.
Principales diferencias
C O M PAT IB IL IDA D
N O M B RE DEL C ON LA C O M PAT IB IL IDA D
M O DIF IC A DO R M IEM B RO DEC O N ST RUC C IÓ C O N Á RB O L ES DE
N O M B RE DE A C C ESO T IP O P ERSO N A L IZ A DO N EXP RESIÓ N

Tipos anónimos internal class ️


✔ ❌ ️

Tuple public class ❌ ❌ ️


ValueTuple public struct ️


✔ ️
✔ ❌

Serialización
Un elemento importante que hay que tener en cuenta al elegir un tipo es si se tendrá que serializar o no. La
serialización es el proceso de convertir el estado de un objeto en un formato que se pueda almacenar o transportar.
Para obtener más información, vea el artículo sobre serialización. Cuando la serialización resulta importante, crear
un valor class o struct es preferible a los tipos anónimos o los de tupla.

Rendimiento
El rendimiento entre estos tipos depende de cada escenario. Un mayor impacto supone la compensación entre las
asignaciones y las copias. En la mayoría de los escenarios, el impacto es pequeño. Cuando pudieran surgir impactos
mayores, se tendrían que tomar medidas para tomar una decisión fundamentada.

Conclusión
Al elegir entre los tipos de tupla y los anónimos, como desarrollador es necesario tener en cuenta varios factores.
En general, si no trabaja con árboles de expresión y le parece bien usar la sintaxis de tupla, elija ValueTuple, ya que
proporcionan un tipo de valor con la flexibilidad de poner nombre a las propiedades. Si trabaja con árboles de
expresión y prefiere poner nombre a las propiedades, elija los tipos anónimos. En otros casos, use Tuple.

Vea también
Tipos anónimos (Guía de programación de C#).
Árboles de expresión
Tipos de tupla (referencia de C#)
Tuplas (Visual Basic)
Instrucciones de diseño de tipos
Información general de LINQ
16/09/2020 • 12 minutes to read • Edit Online

Language Integrated Query (LINQ) proporciona funcionalidades de consulta de nivel del lenguaje y una API de
función de orden superior para C# y Visual Basic, que le permiten escribir código expresivo y declarativo.

Sintaxis de consulta de nivel de lenguaje


Esta es la sintaxis de consulta de nivel de lenguaje:

var linqExperts = from p in programmers


where p.IsNewToLINQ
select new LINQExpert(p);

Dim linqExperts = From p in programmers


Where p.IsNewToLINQ
Select New LINQExpert(p)

Este es el mismo ejemplo con la API IEnumerable<T> :

var linqExperts = programmers.Where(p => p.IsNewToLINQ)


.Select(p => new LINQExpert(p));

Dim linqExperts = programmers.Where(Function(p) p.IsNewToLINQ).


Select(Function(p) New LINQExpert(p))

LINQ es expresivo
Imagine que tiene una lista de mascotas, pero desea convertirla en un diccionario en el que pueda tener acceso a
cada mascota directamente por su valor RFID .
Este es código imperativo tradicional:

var petLookup = new Dictionary<int, Pet>();

foreach (var pet in pets)


{
petLookup.Add(pet.RFID, pet);
}

Dim petLookup = New Dictionary(Of Integer, Pet)()

For Each pet in pets


petLookup.Add(pet.RFID, pet)
Next

La intención de este código no es crear un elemento Dictionary<int, Pet> y agregarle elementos por medio de un
bucle, sino convertir una lista existente en un diccionario. LINQ conserva la intención, a diferencia del código
imperativo.
Esta es la expresión LINQ equivalente:

var petLookup = pets.ToDictionary(pet => pet.RFID);

Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)

El código con LINQ tiene la ventaja de poner al mismo nivel la intención y el código cuando se razona como
programador. Otra ventaja es la brevedad de código. Imagínese poder reducir gran parte de un código base en 1/3,
como hemos visto más arriba. No estaría mal, ¿verdad?

Los proveedores LINQ simplifican el acceso a datos


Para una parte importante del software existente, todo tiene que ver con el control de datos de algún origen (bases
de datos, JSON, XML, etc.) A menudo, esto supone aprender una API nueva para cada origen de datos, y esto puede
resultar tedioso. Para simplificar esta tarea, LINQ abstrae los elementos comunes del acceso a datos en una sintaxis
de consulta que no varía, con independencia del origen de datos que elija.
Esto busca todos los elementos XML con un valor de atributo concreto:

public static IEnumerable<XElement> FindAllElementsWithAttribute(XElement documentRoot, string elementName,


string attributeName, string value)
{
return from el in documentRoot.Elements(elementName)
where (string)el.Element(attributeName) == value
select el;
}

Public Shared Function FindAllElementsWithAttribute(documentRoot As XElement, elementName As String,


attributeName As String, value As String) As IEnumerable(Of
XElement)
Return From el In documentRoot.Elements(elementName)
Where el.Element(attributeName).ToString() = value
Select el
End Function

Escribir código a fin de recorrer manualmente el documento XML para realizar esta tarea sería bastante más
complicado.
Interactuar con XML no es lo único que puede hacer con los proveedores LINQ. LINQ to SQL es un asignador
relacional de objetos (ORM) bastante básico para una base de datos del servidor MSSQL. La biblioteca JSON.NET
proporciona una forma eficiente de recorrer documentos JSON mediante LINQ. Además, si no hay una biblioteca
que haga lo que necesita, también puede escribir un proveedor LINQ propio.

Motivos para usar la sintaxis de consulta


¿Por qué usar la sintaxis de consulta? Es una pregunta que surge con frecuencia. Después de todo, el código
siguiente:

var filteredItems = myItems.Where(item => item.Foo);


Dim filteredItems = myItems.Where(Function(item) item.Foo)

es mucho más conciso que esto:

var filteredItems = from item in myItems


where item.Foo
select item;

Dim filteredItems = From item In myItems


Where item.Foo
Select item

¿No es la sintaxis de la API una manera más concisa de la sintaxis de consulta?


No. Con la sintaxis de consulta se puede usar la cláusula let , que permite introducir y enlazar una variable dentro
del ámbito de la expresión para usarla en las partes siguientes de la expresión. Se puede reproducir el mismo
código con la sintaxis de la API, pero probablemente generará código difícil de leer.
Esto nos lleva a la pregunta: ¿debería usar la sintaxis de consulta solamente?
La respuesta a esta pregunta es sí si:
El código base existente ya usa la sintaxis de consulta.
Es necesario establecer el ámbito de las variables en las consultas debido a su complejidad.
Prefiere la sintaxis de consulta y esta no diverge del código base.
La respuesta a esta pregunta es no si...
el código base existente ya usa la sintaxis de la API,
no necesita establecer el ámbito de las variables en las consultas,
prefiere la sintaxis de la API y esta no diverge del código base

LINQ básico
Para obtener una lista realmente completa de ejemplos de LINQ, visite 101 ejemplos de LINQ.
Los ejemplos siguientes son una demostración rápida de algunas de las piezas básicas de LINQ. No pretende ser
exhaustivo, ya que LINQ ofrece más funcionalidad que la que se muestra aquí.
Las herramientas esenciales: Where , Select ,y Aggregate
// Filtering a list.
var germanShepherds = dogs.Where(dog => dog.Breed == DogBreed.GermanShepherd);

// Using the query syntax.


var queryGermanShepherds = from dog in dogs
where dog.Breed == DogBreed.GermanShepherd
select dog;

// Mapping a list from type A to type B.


var cats = dogs.Select(dog => dog.TurnIntoACat());

// Using the query syntax.


var queryCats = from dog in dogs
select dog.TurnIntoACat();

// Summing the lengths of a set of strings.


int seed = 0;
int sumOfStrings = strings.Aggregate(seed, (s1, s2) => s1.Length + s2.Length);

' Filtering a list.


Dim germanShepherds = dogs.Where(Function(dog) dog.Breed = DogBreed.GermanShepherd)

' Using the query syntax.


Dim queryGermanShepherds = From dog In dogs
Where dog.Breed = DogBreed.GermanShepherd
Select dog

' Mapping a list from type A to type B.


Dim cats = dogs.Select(Function(dog) dog.TurnIntoACat())

' Using the query syntax.


Dim queryCats = From dog In dogs
Select dog.TurnIntoACat()

' Summing the lengths of a set of strings.


Dim seed As Integer = 0
Dim sumOfStrings As Integer = strings.Aggregate(seed, Function(s1, s2) s1.Length + s2.Length)

Acoplamiento de una lista de listas

// Transforms the list of kennels into a list of all their dogs.


var allDogsFromKennels = kennels.SelectMany(kennel => kennel.Dogs);

' Transforms the list of kennels into a list of all their dogs.
Dim allDogsFromKennels = kennels.SelectMany(Function(kennel) kennel.Dogs)

Unión entre dos conjuntos (con un comparador personalizado )


public class DogHairLengthComparer : IEqualityComparer<Dog>
{
public bool Equals(Dog a, Dog b)
{
if (a == null && b == null)
{
return true;
}
else if ((a == null && b != null) ||
(a != null && b == null))
{
return false;
}
else
{
return a.HairLengthType == b.HairLengthType;
}
}

public int GetHashCode(Dog d)


{
// Default hashcode is enough here, as these are simple objects.
return d.GetHashCode();
}
}
...

// Gets all the short-haired dogs between two different kennels.


var allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, new DogHairLengthComparer());

Public Class DogHairLengthComparer


Inherits IEqualityComparer(Of Dog)

Public Function Equals(a As Dog,b As Dog) As Boolean


If a Is Nothing AndAlso b Is Nothing Then
Return True
ElseIf (a Is Nothing AndAlso b IsNot Nothing) OrElse (a IsNot Nothing AndAlso b Is Nothing) Then
Return False
Else
Return a.HairLengthType = b.HairLengthType
End If
End Function

Public Function GetHashCode(d As Dog) As Integer


' Default hashcode is enough here, as these are simple objects.
Return d.GetHashCode()
End Function
End Class

...

' Gets all the short-haired dogs between two different kennels.
Dim allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, New DogHairLengthComparer())

Intersección entre dos conjuntos

// Gets the volunteers who spend share time with two humane societies.
var volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
new VolunteerTimeComparer());
' Gets the volunteers who spend share time with two humane societies.
Dim volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
New VolunteerTimeComparer())

Ordenación

// Get driving directions, ordering by if it's toll-free before estimated driving time.
var results = DirectionsProcessor.GetDirections(start, end)
.OrderBy(direction => direction.HasNoTolls)
.ThenBy(direction => direction.EstimatedTime);

' Get driving directions, ordering by if it's toll-free before estimated driving time.
Dim results = DirectionsProcessor.GetDirections(start, end).
OrderBy(Function(direction) direction.HasNoTolls).
ThenBy(Function(direction) direction.EstimatedTime)

Igualdad de las propiedades de instancia


Por último, un ejemplo más avanzado: determinar si los valores de las propiedades de dos instancias del mismo
tipo son iguales (tomado y modificado de esta entrada de StackOverflow):

public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
if (self == null || to == null)
{
return self == to;
}

// Selects the properties which have unequal values into a sequence of those properties.
var unequalProperties = from property in typeof(T).GetProperties(BindingFlags.Public |
BindingFlags.Instance)
where !ignore.Contains(property.Name)
let selfValue = property.GetValue(self, null)
let toValue = property.GetValue(to, null)
where !Equals(selfValue, toValue)
select property;
return !unequalProperties.Any();
}

<System.Runtime.CompilerServices.Extension()>
Public Function PublicInstancePropertiesEqual(Of T As Class)(self As T, [to] As T, ParamArray ignore As
String()) As Boolean
If self Is Nothing OrElse [to] Is Nothing Then
Return self Is [to]
End If

' Selects the properties which have unequal values into a sequence of those properties.
Dim unequalProperties = From [property] In GetType(T).GetProperties(BindingFlags.Public Or
BindingFlags.Instance)
Where Not ignore.Contains([property].Name)
Let selfValue = [property].GetValue(self, Nothing)
Let toValue = [property].GetValue([to], Nothing)
Where Not Equals(selfValue, toValue) Select [property]
Return Not unequalProperties.Any()
End Function

PLINQ
PLINQ, o Parallel LINQ, es un motor de ejecución en paralelo para expresiones de LINQ. En otras palabras, se puede
paralelizar una expresión normal de LINQ de forma trivial en cualquier número de subprocesos. Para hacerlo, se
emplea una llamada a AsParallel() delante de la expresión.
Tenga en cuenta lo siguiente.

public static string GetAllFacebookUserLikesMessage(IEnumerable<FacebookUser> facebookUsers)


{
var seed = default(UInt64);

Func<UInt64, UInt64, UInt64> threadAccumulator = (t1, t2) => t1 + t2;


Func<UInt64, UInt64, UInt64> threadResultAccumulator = (t1, t2) => t1 + t2;
Func<Uint64, string> resultSelector = total => $"Facebook has {total} likes!";

return facebookUsers.AsParallel()
.Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector);
}

Public Shared GetAllFacebookUserLikesMessage(facebookUsers As IEnumerable(Of FacebookUser)) As String


{
Dim seed As UInt64 = 0

Dim threadAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2


Dim threadResultAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
Dim resultSelector As Func(Of Uint64, string) = Function(total) $"Facebook has {total} likes!"

Return facebookUsers.AsParallel().
Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector)
}

Este código repartirá facebookUsers en subprocesos del sistema según sea necesario, sumará el total de "Me
gusta" de cada subproceso en paralelo, sumará los resultados calculados por cada subproceso y devolverá ese
resultado en una bonita cadena.
En forma de diagrama:
Las tareas paralelizables vinculadas a la CPU que se pueden expresar fácilmente con LINQ (es decir, que son
funciones puras y no tienen efectos secundarios) son un candidato excelente para PLINQ. Para tareas que sí tienen
efectos secundarios, considere el uso de la Biblioteca TLP.

Más recursos
Ejemplos de LINQ 101
Linqpad, un entorno de área de juegos y motor de consultas a bases de datos para C#/F#/Visual Basic
EduLinq, un libro electrónico para aprender cómo se implementa LINQ to Objects
Documentos y datos XML
16/09/2020 • 6 minutes to read • Edit Online

.NET Framework proporciona un conjunto de clases completo e integrado que permiten crear, de forma sencilla,
aplicaciones preparadas para XML. Las clases de los espacios de nombres siguientes admiten análisis y escritura
XML, edición de datos XML en memoria, validación de datos y transformación XSLT.
System.Xml
System.Xml.XPath
System.Xml.Xsl
System.Xml.Schema
System.Xml.Linq
Para obtener una lista completa, busque "System.Xml" en el explorador de API de .NET.
Las clases de estos espacios de nombres admiten las recomendaciones del World Wide Web Consortium (W3C).
Por ejemplo:
La clase System.Xml.XmlDocument implementa las recomendaciones de la parte principal del nivel 1 de
Document Object Model (DOM) y de la parte principal del nivel 2 de DOM del W3C.
Las clases System.Xml.XmlReader y System.Xml.XmlWriter admiten las recomendaciones del W3C sobre
XML 1.0 y los espacios de nombres de XML.
Los esquemas de la clase System.Xml.Schema.XmlSchemaSet admiten las recomendaciones del W3C sobre
Esquema XML, parte 1: estructuras y en Esquema XML, parte 2: tipos de datos.
Las clases del espacio de nombres System.Xml.Xsl admiten transformaciones XSLT compatibles con las
recomendaciones del W3C sobre XSLT versión 1.0.
Las clases XML de .NET Framework proporcionan estas ventajas:
Productividad. Gracias a LINQ to XML (C#) y LINQ to XML (Visual Basic) resulta más sencillo programar
con XML y proporciona una experiencia de consulta similar a SQL.
Extensibilidad. Las clases XML en .NET Framework se pueden extender mediante el uso de clases base
abstractas y métodos virtuales. Por ejemplo, puede crear una clase derivada de la clase XmlUrlResolver que
almacene el flujo caché en el disco local.
Arquitectura conectable. .NET Framework proporciona una arquitectura en la que los componentes se
pueden usar unos con otros y se puede hacer streaming de los datos entre componentes. Por ejemplo, un
almacén de datos, como un objeto XPathDocument o XmlDocument, se puede transformar con la clase
XslCompiledTransform y, posteriormente, se pueden hacer streaming de los resultados a otro almacén o
devolverse como flujo desde un servicio web.
Rendimiento. Para obtener un mejor rendimiento de la aplicación, algunas de las clases XML de .NET
Framework admiten un modelo basado en streaming con las características siguientes:
Almacenamiento en caché mínimo para el análisis de modelos de incorporación de cambios solo
hacia delante (XmlReader).
Validación solo hacia delante con (XmlReader).
Navegación al estilo de cursores que reduce la creación de nodos a un único nodo virtual, a la vez
que proporciona acceso aleatorio al documento (XPathNavigator).
Para obtener un mejor rendimiento cuando se requiera un procesamiento XSLT, puede usar la clase
XPathDocument, que es un almacén optimizado de solo lectura para consultas XPath diseñadas para
funcionar, de forma eficiente, con la clase XslCompiledTransform.
Integración con ADO.NET. Las clases XML y ADO.NET están estrechamente integradas para reunir datos
relacionales y XML. La clase DataSet es una caché almacenada en memoria de datos devueltos desde una
base de datos. La clase DataSet puede leer y escribir XML mediante las clases XmlReader y XmlWriter, con el
fin de almacenar su estructura de esquema relacional interna como esquemas XML (XSD) y para deducir la
estructura de esquema de un documento XML.

En esta sección
En Opciones de procesamiento XML se describen las opciones para procesar datos XML.
En Procesamiento de datos XML en memoria se describen los tres modelos para procesar datos XML en memoria:
LINQ to XML (C#) y LINQ to XML (Visual Basic), la clase XmlDocument (basada en Document Object Model del
W3C) y la clase XPathDocument (basada en el modelo de datos XPath).
Transformaciones XSLT
Describe cómo utilizar el procesador XSLT.
Modelo de objetos de esquema XML (SOM)
Describe las clases que se usan para crear y tratar esquemas XML (XSD) mediante una clase XmlSchema que carga
y modifica un esquema.
Integración de XML con datos relacionales y ADO.NET
Describe cómo habilita .NET Framework el acceso sincrónico en tiempo real a las representaciones relacional y
jerárquica de los datos mediante los objetos DataSet y XmlDataDocument.
Administración de espacios de nombres en un documento XML
Describe cómo se usa la clase XmlNamespaceManager para almacenar y mantener la información sobre espacios
de nombres.
Compatibilidad de tipos en las clases System.Xml
Describe cómo se asignan los tipos de datos XML a los tipos CLR, cómo se convierten los tipos de datos XML y
otras características de compatibilidad de tipos de las clases System.Xml.

Secciones relacionadas
ADO.NET
Proporciona información sobre cómo acceder a los datos mediante ADO.NET.
Seguridad
Ofrece información general sobre todo el sistema de seguridad de .NET Framework.
Introducción a Microsoft.Data.Sqlite
16/09/2020 • 2 minutes to read • Edit Online

Microsoft.Data.Sqlite es un proveedor ADO.NET ligero para SQLite. El proveedor Entity Framework Core para
SQLite se basa en esta biblioteca. Sin embargo, también se puede usar de forma independiente o con otras
bibliotecas de acceso a datos.

Instalación
La versión estable más reciente está disponible en NuGet.
CLI de .NET Core
Visual Studio

dotnet add package Microsoft.Data.Sqlite

Uso
Esta biblioteca implementa las abstracciones de ADO.NET comunes para conexiones, comandos, lectores de datos,
etc.

using (var connection = new SqliteConnection("Data Source=hello.db"))


{
connection.Open();

var command = connection.CreateCommand();


command.CommandText =
@"
SELECT name
FROM user
WHERE id = $id
";
command.Parameters.AddWithValue("$id", id);

using (var reader = command.ExecuteReader())


{
while (reader.Read())
{
var name = reader.GetString(0);

Console.WriteLine($"Hello, {name}!");
}
}
}

Vea también
Cadenas de conexión
Referencia de API
Sintaxis de SQL
Procesamiento paralelo, simultaneidad y
programación asincrónica en .NET
16/09/2020 • 2 minutes to read • Edit Online

.NET proporciona varias maneras de escribir código asincrónico para que la aplicación responda mejor al usuario y
escribir código paralelo que usa varios subprocesos de ejecución para optimizar el rendimiento del equipo del
usuario.

En esta sección
Programación asincrónica
Describe los mecanismos para la programación asincrónica que proporciona .NET.
Programación en paralelo
Describe un modelo de programación basado en tareas que simplifica el desarrollo en paralelo, de modo que le
permite escribir código paralelo eficaz, específico y escalable de forma natural sin tener que trabajar directamente
con subprocesos ni el grupo de subprocesos.
Subprocesamiento
Describe los mecanismos de simultaneidad y sincronización básicos que proporciona .NET.
Información general de Async
18/03/2020 • 4 minutes to read • Edit Online

No hace tanto tiempo, las aplicaciones funcionaban más rápido simplemente al comprar un PC o servidor nuevo y
después se detuvo esa tendencia. De hecho, se ha invertido. Aparecieron teléfonos móviles con chips ARM de
núcleo único de 1 GHz y cargas de trabajo de servidor que pasaron a máquinas virtuales. Los usuarios seguían
queriendo una interfaz de usuario dinámica y los propietarios de empresas querían servidores que se ajustaran a
su negocio. La transición al móvil y a la nube y una población conectada a Internet de más de 3 mil millones de
usuarios ha generado un nuevo conjunto de patrones de software.
Se espera que las aplicaciones cliente estén siempre activadas y conectadas, y que respondan adecuadamente a
la interacción del usuario (p. ej., función táctil) con altas calificaciones en la tienda de aplicaciones.
Se espera que los servicios controlen los picos de tráfico al escalar y reducir verticalmente con facilidad.
La programación de Async es una técnica clave que facilita controlar las operaciones simultáneas y de E/S de
bloqueo en varios núcleos. .NET proporciona la funcionalidad de que servicios y aplicaciones sean dinámicos y
elásticos con modelos de programación asincrónicos de nivel de lenguaje y fáciles de usar en C#, Visual Basic y F#.

¿Por qué escribir código asincrónico?


Las aplicaciones modernas usan de forma intensiva la E/S de archivos y redes. Tradicionalmente, las API de E/S
bloquean de manera predeterminada, lo que da lugar a experiencias de usuario y uso del hardware pobres a
menos que quiera aprender y usar patrones exigentes. Las API asincrónicas basadas en tareas y el modelo de
programación asincrónico de nivel de lenguaje invierten este modelo, de modo que establecen la ejecución
asincrónica como la predeterminada con algunos conceptos nuevos que aprender.
El código asincrónico tiene las siguientes características:
Controla más solicitudes del servidor al producir subprocesos que controlan más solicitudes mientras esperan
a que devuelvan las solicitudes de E/S.
Permite que las interfaces de usuario sean más dinámicas al ceder subprocesos a la interacción de la interfaz de
usuario mientras esperan las solicitudes de E/S y al pasar trabajo de ejecución prolongada a otros núcleos de
CPU.
Muchas de las API de .NET más recientes son asincrónicas.
Es fácil escribir código asincrónico en .NET.

Pasos adicionales
Para obtener más información, vea el tema Async en profundidad.
En el tema Modelos para la programación asincrónica, se proporciona una introducción de los tres modelos de
programación asincrónica que se admiten en .NET:
Asynchronous Programming Model (APM) [Modelo de programación asincrónica (APM)] (heredado)
Modelo asincrónico basado en eventos (EAP) (heredado)
Task-based Asynchronous Pattern (TAP) [Modelo asincrónico basado en tareas (TAP)] (recomendado para
nuevos desarrollos)
Para obtener más información sobre el modelo de programación basado en tareas recomendado, consulte el tema
Programación asincrónica basada en tareas.
Async en profundidad
18/03/2020 • 19 minutes to read • Edit Online

La escritura de código asincrónico enlazado a E/S y CPU es sencilla al usar el modelo asincrónico basado en tareas
de .NET. El modelo se expone mediante los tipos Task y Task<T> y las palabras claves async y await en C# y
Visual Basic. (Los recursos específicos del idioma se encuentran en la sección Vea también). En este artículo, se
explica cómo usar Async de .NET y se proporciona información sobre el marco de trabajo de Async usado en
segundo plano.

Task y Task<T>
Las tareas son construcciones que se usan para implementar lo que se conoce como el modelo de promesa de
simultaneidad. En resumen, le ofrecen una "promesa" de que el trabajo se completará en un momento posterior, lo
que le permite coordinarse con la promesa con una API limpia.
Task representa una única operación que no devuelve un valor.
Task<T> representa una única operación que devuelve un valor de tipo T .
Es importante razonar sobre las tareas como abstracciones de trabajo que se producen de forma asincrónica y no
una abstracción sobre subprocesos. De manera predeterminada, las tareas se ejecutan en el trabajo de subproceso
y delegado actual del sistema operativo, según corresponda. De forma opcional, se puede solicitar de forma
explícita que se ejecuten las tareas en un subproceso independiente mediante la API Task.Run .
Las tareas exponen un protocolo de API para supervisar, esperar y acceder al valor del resultado (en el caso de
Task<T> ) de una tarea. La integración de lenguajes, con la palabra clave await , proporciona una abstracción de
alto nivel para usar tareas.
Mediante await , su aplicación o servicio puede realizar trabajo útil mientras se ejecuta una tarea al ceder el
control a su llamador hasta que se realiza la tarea. El código no tiene que depender de las devoluciones de llamada
ni eventos para seguir ejecutándose una vez completada la tarea. La integración de la API de tareas y lenguajes se
encarga de ello. Si está usando Task<T> , la palabra clave await "desencapsulará" también el valor devuelto
cuando se completa la tarea. Más adelante se explican los detalles sobre cómo funciona esto.
Puede obtener más información sobre las tareas y las distintas formas de interactuar con ellas en el tema Modelo
asincrónico basado en tareas (TAP).

Tareas para una operación enlazada a E/S en profundidad


En la siguiente sección, se describe una vista general de lo que sucede con una llamada de E/S asincrónica normal.
Comencemos con un par de ejemplos.
En el primer ejemplo, se llama a un método asincrónico y se devuelve una tarea activa que, probablemente, aún
esté sin completar.

public Task<string> GetHtmlAsync()


{
// Execution is synchronous here
var client = new HttpClient();

return client.GetStringAsync("https://www.dotnetfoundation.org");
}
En el segundo ejemplo, se agrega el uso de las palabras clave async y await para que funcionen en la tarea.

public async Task<string> GetFirstCharactersCountAsync(string url, int count)


{
// Execution is synchronous here
var client = new HttpClient();

// Execution of GetFirstCharactersCountAsync() is yielded to the caller here


// GetStringAsync returns a Task<string>, which is *awaited*
var page = await client.GetStringAsync("https://www.dotnetfoundation.org");

// Execution resumes when the client.GetStringAsync task completes,


// becoming synchronous again.

if (count > page.Length)


{
return page;
}
else
{
return page.Substring(0, count);
}
}

La llamada a GetStringAsync() se realiza a través de bibliotecas de .NET de nivel inferior (es posible que se llame a
otros métodos asincrónicos) hasta que se alcanza una llamada de interoperabilidad de P/Invoke en una biblioteca
de red nativa. La biblioteca nativa puede realizar posteriormente una llamada API del sistema (como write() a un
socket en Linux). Se creará un objeto de tarea en el límite nativo o administrado, posiblemente mediante
TaskCompletionSource. El objeto de tarea se pasará por las capas, funcionará en ellas o se devolverá directamente
y, finalmente, se devuelve al llamador inicial.
En este segundo ejemplo, se devolverá un objeto Task<T> de GetStringAsync . El uso de la palabra clave await
hace que el método devuelva un objeto de tarea recién creado. El control vuelve al llamador de esta ubicación en el
método GetFirstCharactersCountAsync . Los métodos y propiedades del objeto Task<T> permiten que los
llamadores supervisen el progreso de la tarea, que se completará cuando se haya ejecutado el código restante en
GetFirstCharactersCountAsync.
Después de la llamada API del sistema, la solicitud ahora está en el espacio del kernel, que avanza hacia el
subsistema de red del sistema operativo (como /net en Linux Kernel). Aquí, el sistema operativo controlará la
solicitud de red de forma asincrónica. Los detalles pueden ser diferentes según el sistema operativo usado (la
llamada al controlador de dispositivo puede programarse como una señal devuelta al tiempo de ejecución o una
llamada al controlador de dispositivo puede realizarse y después se devuelve una señal), pero, finalmente, el
tiempo de ejecución recibirá la información de que la solicitud de red está en curso. En este momento, el trabajo del
controlador de dispositivo estará programado, en curso o ya estará terminado (la solicitud ya estará "en la
conexión"), pero como esto se produce de forma asincrónica, el controlador de dispositivo es capaz de controlar
otra cosa de forma inmediata.
Por ejemplo, en Windows, un subproceso de sistema operativo realiza una llamada al controlador de dispositivo
de red y le pide que realice la operación de red a través de un paquete de petición de interrupción (IRP) que
representa la operación. El controlador de dispositivo recibe el IRP, realiza la llamada a la red, marca el IRP como
"pendiente" y vuelve al sistema operativo. Ya que el subproceso de sistema operativo ahora sabe que el IRP está
"pendiente", no tiene nada más que hacer en este trabajo y "vuelve", de modo que se puede usar para realizar otro
trabajo.
Cuando se haya realizado la solicitud y regresen los datos a través del controlador de dispositivo, notifica a la CPU
que se han recibido nuevos datos mediante una interrupción. La forma en que se controla esta interrupción varía
según el sistema operativo, pero, finalmente, los datos pasarán a través del sistema operativo hasta que lleguen a
una llamada de interoperabilidad del sistema (por ejemplo, en Linux un controlador de interrupciones programará
la mitad inferior de la IRQ para pasar los datos a través del sistema operativo de forma asincrónica). Tenga en
cuenta que esto también se produce de manera asincrónica. El resultado se pone en cola hasta que el siguiente
subproceso disponible puede ejecutar el método asincrónico y "desencapsular" el resultado de la tarea
completada.
A lo largo de todo este proceso, un punto clave es que ningún subproceso se dedica a ejecutar la tarea .
Aunque el trabajo se ejecuta en algún contexto (es decir, el sistema operativo tiene que pasar datos a un
controlador de dispositivo y responder a una interrupción), no hay ningún subproceso dedicado a esperar a que
vuelvan los datos de la solicitud. Esto permite al sistema controlar un volumen de trabajo mucho mayor en lugar
de esperar a que finalicen algunas llamadas de E/S.
Aunque lo anterior puede parecer mucho trabajo que realizar, al medirlo en términos de tiempo de reloj, es ínfimo
en comparación con el tiempo necesario para realizar el trabajo de E/S real. Aunque no es exacta, una escala de
tiempo posible para una llamada de este estilo tendría este aspecto:
0-1
————————————————————————————————————————————————–2-
3
El tiempo empleado de los puntos 0 a 1 es todo hasta que un método asincrónico cede el control a su
llamador.
El tiempo empleado de los puntos 1 a 2 es el tiempo transcurrido en E/S, sin ningún costo de CPU.
Por último, el tiempo empleado de los puntos 2 a 3 es durante el que se pasa el control (y posiblemente un
valor) de nuevo al método asincrónico, momento en que se vuelve a ejecutar.
¿Qué significa esto en un escenario de servidor?
Este modelo funciona bien con una carga de trabajo de escenario de servidor típica. Ya que no hay ningún
subproceso dedicado al bloqueo de tareas incompletas, el grupo de subprocesos de servidor puede atender a un
mayor volumen de solicitudes web.
Considere dos servidores: uno que ejecute código asincrónico y otro que no lo haga. Para este ejemplo, cada
servidor tiene solo 5 subprocesos disponibles para las solicitudes de servicio. Tenga en cuenta que estos números
son pequeños y sirven solo en un contexto demostrativo.
Suponga que ambos servidores reciben seis solicitudes simultáneas. Cada solicitud realiza una operación de E/S. El
servidor sin código asincrónico tiene que poner en cola la solicitud 6 hasta que uno de los 5 subprocesos haya
finalizado el trabajo enlazado a E/S y escrito una respuesta. En el momento en que entre la solicitud 20, el servidor
puede comenzar a ralentizarse, porque la cola se extiende demasiado.
El servidor con código asincrónico en ejecución también pone en cola la solicitud 6, pero ya que usa async y
await , cada uno de los subprocesos se libera cuando se inicia el trabajo enlazado a E/S, en lugar de cuando
finaliza. Cuando llega la solicitud 20, la cola de solicitudes entrantes será mucho más pequeña (en caso de que haya
algo) y no se ralentiza el servidor.
Aunque se trata de un ejemplo inventado, ocurre de forma muy similar en el mundo real. De hecho, puede esperar
que un servidor pueda controlar más solicitudes mediante async y await que si se dedicaba a un subproceso
para cada solicitud que recibe.
¿Qué significa esto en un escenario de cliente?
El mayor beneficio al usar async y await para una aplicación cliente es un aumento en la capacidad de respuesta.
Aunque puede crear una aplicación dinámica al generar subprocesos de forma manual, el hecho de generar un
subproceso es una operación costosa en comparación a usar solo async y await . Especialmente en el caso de
juegos para móviles, es fundamental afectar lo mínimo posible al subproceso de interfaz de usuario en lo que a
E/S se refiere.
Sobre todo, ya que el trabajo enlazado a E/S no invierte prácticamente ningún tiempo en la CPU, dedicar un
subproceso de CPU completo a realizar prácticamente ningún trabajo útil sería un mal uso de recursos.
Además, es muy sencillo enviar trabajo al subproceso de interfaz de usuario (como actualizar una interfaz de
usuario) con métodos async y no requiere trabajo adicional (como llamar a un delegado seguro para
subprocesos).

Task y Task<T> para una operación enlazada a la CPU en profundidad


El código async enlazado a la CPU es un poco diferente del código async enlazado a E/S. Ya que el trabajo se
realiza en la CPU, no hay ninguna forma de evitar dedicar un subproceso al cálculo. El uso de async y await le
proporciona una manera clara de interactuar con subprocesos en segundo plano y mantener al llamador del
método asincrónico dinámico. Tenga en cuenta que esto no proporciona ninguna protección para datos
compartidos. Si usa datos compartidos, aún tendrá que aplicar una estrategia de sincronización adecuada.
Esta es una vista general de una llamada asincrónica enlazada a la CPU:

public async Task<int> CalculateResult(InputData data)


{
// This queues up the work on the threadpool.
var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data));

// Note that at this point, you can do some other work concurrently,
// as CalculateResult() is still executing!

// Execution of CalculateResult is yielded here!


var result = await expensiveResultTask;

return result;
}

CalculateResult() se ejecuta en el subproceso en que se ha llamado. Cuando llama a Task.Run , pone en cola la
operación costosa enlazada a la CPU, DoExpensiveCalculation() , en el grupo de subprocesos y recibe un
controlador Task<int> . DoExpensiveCalculation() se ejecuta finalmente de forma simultánea en el siguiente
subproceso disponible, probablemente en otro núcleo de CPU. Es posible realizar trabajo simultáneo mientras
DoExpensiveCalculation() está ocupado en otro subproceso, ya que el subproceso que llama a CalculateResult()
aún se está ejecutando.
Una vez se encuentra await , la ejecución de CalculateResult() se cede a su llamador, lo que permite que se
realice otro trabajo con el subproceso actual mientras DoExpensiveCalculation() genera un resultado. Una vez que
haya finalizado, el resultado se pone en la cola para ejecutarse en el subproceso principal. Finalmente, el
subproceso principal volverá a ejecutar CalculateResult() , momento en que tendrá el resultado de
DoExpensiveCalculation() .

¿Por qué ayuda Async en este caso?


async y await son el procedimiento recomendado para administrar el trabajo enlazado a la CPU si necesita
capacidad de respuesta. Hay varios patrones para usar Async con trabajo enlazado a la CPU. Es importante tener
en cuenta que hay un pequeño costo al usar Async y no se recomienda para bucles de pequeñas dimensiones.
Depende de usted determinar cómo escribe el código en torno a esta nueva funcionalidad.

Vea también
Programación asincrónica en C#
Programación asincrónica con async y await (C#)
Programación asincrónica en F#
Programación asincrónica con Async y Await (Visual Basic)
Patrones para la programación asincrónica
16/09/2020 • 3 minutes to read • Edit Online

.NET proporciona tres patrones para realizar las operaciones asincrónicas:


Patrón asincrónico basado en tareas (TAP) , que utiliza un método único para representar el inicio y la
finalización de una operación asincrónica. TAP apareció por primera vez en .NET Framework 4. Es el
enfoque recomendado para la programación asincrónica en. NET. Las palabras clave 2.async y
await en C# y los operadores Async y Await en Visual Basic agregan compatibilidad de lenguaje para TAP.
Para más información, consulte Patrón asincrónico basado en tareas (TAP).
El modelo asincrónico basado en eventos (EAP) , que es el patrón heredado basado en eventos para
proporcionar el comportamiento asincrónico. Requiere un método con el sufijo Async , así como uno o más
eventos, tipos de delegado de controlador de eventos y tipos derivados de EventArg . EAP apareció por
primera vez en .NET Framework 2.0. Ya no se recomienda para nuevo desarrollo. Para más información,
consulte Modelo asincrónico basado en eventos (EAP).
El patrón Modelo de programación asincrónica (APM) (también denominado IAsyncResult), que es el
modelo heredado que usa la interfaz IAsyncResult para ofrecer un comportamiento asincrónico. En este
patrón, las operaciones sincrónicas requieren los métodos Begin y End (por ejemplo, BeginWrite y
EndWrite para implementar una operación de escritura asincrónica). Este patrón ya no se recomienda para
nuevo desarrollo. Para más información, consulte Modelo de programación asincrónica (APM).

Comparación de patrones
Para una comparación rápida de cómo los tres patrones modelan las operaciones asincrónicas, considere un
método Read que lea una determinada cantidad de datos en un búfer proporcionado comenzando en un
desplazamiento especificado:

public class MyClass


{
public int Read(byte [] buffer, int offset, int count);
}

El equivalente TAP de este método expondría el siguiente método ReadAsync único:

public class MyClass


{
public Task<int> ReadAsync(byte [] buffer, int offset, int count);
}

El equivalente EAP expondría el siguiente conjunto de tipos y miembros:

public class MyClass


{
public void ReadAsync(byte [] buffer, int offset, int count);
public event ReadCompletedEventHandler ReadCompleted;
}

El equivalente APM expondría los métodos BeginRead y EndRead :


public class MyClass
{
public IAsyncResult BeginRead(
byte [] buffer, int offset, int count,
AsyncCallback callback, object state);
public int EndRead(IAsyncResult asyncResult);
}

Vea también
Async en profundidad
Programación asincrónica en C#
Programación asincrónica en F#
Programación asincrónica con Async y Await (Visual Basic)
Programación en paralelo en .NET
16/09/2020 • 3 minutes to read • Edit Online

Muchos equipos y estaciones de trabajo personales tienen varios núcleos de CPU que permiten ejecutar
múltiples subprocesos simultáneamente. Para aprovecharse del hardware, puede paralelizar el código para
distribuir el trabajo entre varios procesadores.
En el pasado, la paralelización requería manipulación de bajo nivel de los subprocesos y bloqueos. Visual Studio
y .NET Framework mejoran la compatibilidad para la programación paralela proporcionando un tiempo de
ejecución, tipos de biblioteca de clases y herramientas de diagnóstico. Estas características, que se presentaron
con .NET Framework 4, simplifican el desarrollo en paralelo. Puede escribir código paralelo eficaz, específico y
escalable de forma natural sin tener que trabajar directamente con subprocesos ni el bloque de subprocesos.
La siguiente ilustración proporciona una información general de alto nivel de la arquitectura de programación
paralela en .NET Framework:

Temas relacionados
T EC N O LO GÍA DESC RIP C IÓ N

Biblioteca TPL Proporciona documentación para la clase


System.Threading.Tasks.Parallel, que incluye versiones
paralelas de For y bucles ForEach , y también para la clase
System.Threading.Tasks.Task, que representa la manera
preferida de expresar las operaciones asincrónicas.

Parallel LINQ (PLINQ) Una implementación paralela de LINQ to Objects que


significativamente mejora el rendimiento en muchos casos.

Estructuras de datos para la programación paralela Proporciona vínculos a documentación sobre las clases de
colección seguras para subprocesos, tipos de sincronización
ligeros y tipos para la inicialización diferida.
T EC N O LO GÍA DESC RIP C IÓ N

Herramientas de diagnóstico paralelo Proporciona vínculos a documentación sobre las ventanas


del depurador de Visual Studio para las tareas y pilas
paralelas y para el visualizador de simultaneidad.

Particionadores personalizados para PLINQ y TPL Describe cómo funcionan los particionadores y cómo
configurar particionadores predeterminados o crear nuevos.

Programadores de tareas Describe cómo funcionan los programadores y cómo se


pueden configurar los programadores predeterminados.

Expresiones lambda en PLINQ y TPL Proporciona información general sobre expresiones lambda
en C# y Visual Basic, y presenta cómo se utilizan en PLINQ y
Task Parallel Library.

Información adicional Proporciona vínculos a información adicional y recursos de


ejemplo para programación paralela en .NET.

Vea también
Información general de Async
Subprocesamiento administrado
Biblioteca de procesamiento paralelo basado en
tareas (TPL)
16/09/2020 • 3 minutes to read • Edit Online

La biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) es un conjunto de
API y tipos públicos de los espacios de nombres System.Threading y System.Threading.Tasks. El propósito de la
TPL es aumentar la productividad de los desarrolladores simplificando el proceso de agregar paralelismo y
simultaneidad a las aplicaciones. La TPL escala el grado de simultaneidad de manera dinámica para usar con
mayor eficacia todos los procesadores disponibles. Además, la TPL se encarga de la división del trabajo, la
programación de los subprocesos en ThreadPool, la compatibilidad con la cancelación, la administración de los
estados y otros detalles de bajo nivel. Al utilizar la TPL, el usuario puede optimizar el rendimiento del código
mientras se centra en el trabajo para el que el programa está diseñado.
A partir de .NET Framework 4, la TPL es el modo preferido de escribir código paralelo y multiproceso. Sin
embargo, no todo el código se presta para la paralelización; por ejemplo, si un bucle realiza solo una cantidad
reducida de trabajo en cada iteración o no se ejecuta para un gran número de iteraciones, la sobrecarga de la
paralelización puede dar lugar a una ejecución más lenta del código. Además, al igual que cualquier código
multiproceso, la paralelización hace que la ejecución del programa sea más compleja. Aunque la TPL simplifica
los escenarios de multithreading, recomendamos tener conocimientos básicos sobre conceptos de
subprocesamiento, por ejemplo, bloqueos, interbloqueos y condiciones de carrera, para usar la TPL eficazmente.

Temas relacionados
T IT L E DESC RIP C IÓ N

Paralelismo de datos Describe cómo crear bucles for y foreach paralelos (


For y For Each en Visual Basic).

Programación asincrónica basada en tareas Describe cómo crear y ejecutar tareas implícitamente
mediante Parallel.Invoke o explícitamente usando objetos Task
directamente.

Flujo de datos Describe cómo utilizar los componentes de flujo de datos de


la biblioteca de TPL Dataflow para controlar varias
operaciones que deban comunicarse entre sí o procesar datos
a medida que estén disponibles.

Usar TPL con otros patrones asincrónicos Describe cómo utilizar la TPL con otros modelos asincrónicos
de .NET.

Problemas potenciales en el paralelismo de datos y tareas Describe algunos problemas comunes y cómo evitarlos.

Parallel LINQ (PLINQ) Describe cómo lograr el paralelismo de datos con consultas
LINQ.

Programación en paralelo Nodo de nivel superior de la programación en paralelo de


.NET.

Vea también
Ejemplos de programación en paralelo con .NET Core y .NET Standard
Paralelismo de datos (biblioteca TPL)
16/09/2020 • 6 minutes to read • Edit Online

El paralelismo de datos hace referencia a los escenarios en los que la misma operación se realiza
simultáneamente (es decir, en paralelo) en elementos de una colección o matriz de origen. En las operaciones
paralelas de datos, se crean particiones de la colección de origen para que varios subprocesos puedan funcionar
simultáneamente en segmentos diferentes.
La biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) admite el
paralelismo de datos a través de la clase System.Threading.Tasks.Parallel. Esta clase proporciona las
implementaciones paralelas basadas en método de los bucles for y foreach ( For y For Each en Visual Basic). Se
escribe la lógica del bucle para un bucle Parallel.For o Parallel.ForEach de forma muy similar a como se escribiría
un bucle secuencial. No tiene que crear los subprocesos ni poner en la cola los elementos de trabajo. En bucles
básicos, no es preciso tomar bloqueos. TPL administra todo el trabajo de bajo nivel. Para obtener información
detallada sobre el uso de Parallel.For y Parallel.ForEach, descargue el documento Patterns for Parallel
Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4 (Patrones de
programación en paralelo: descripción y aplicación de los patrones en paralelo con .NET Framework 4). En el
siguiente ejemplo de código se muestra un bucle foreach simple y su equivalente paralelo.

NOTE
En esta documentación, se utilizan expresiones lambda para definir delegados en la TPL. Si no está familiarizado con las
expresiones lambda de C# o Visual Basic, consulte Expresiones lambda en PLINQ y TPL.

// Sequential version
foreach (var item in sourceCollection)
{
Process(item);
}

// Parallel equivalent
Parallel.ForEach(sourceCollection, item => Process(item));

' Sequential version


For Each item In sourceCollection
Process(item)
Next

' Parallel equivalent


Parallel.ForEach(sourceCollection, Sub(item) Process(item))

Cuando un bucle paralelo se ejecuta, la TPL crea particiones del origen de datos para que el bucle pueda
funcionar simultáneamente en varias partes. En segundo plano, el programador de tareas crea particiones de la
tarea según los recursos del sistema y la carga de trabajo. Cuando es posible, el programador redistribuye el
trabajo entre varios subprocesos y procesadores si se desequilibra la carga de trabajo.

NOTE
También puede proporcionar un programador o creador de particiones personalizado. Para más información, consulte
Particionadores personalizados para PLINQ y TPL y Programadores de tareas.
Los métodos Parallel.For y Parallel.ForEach tienen varias sobrecargas que permiten detener o ejecutar la
ejecución de bucles, supervisar el estado del bucle en otros subprocesos, mantener el estado de subprocesos
locales, finalizar los objetos de subprocesos locales, controlar el grado de simultaneidad, etc. Los tipos del
asistente que habilitan esta funcionalidad son ParallelLoopState, ParallelOptions, ParallelLoopResult,
CancellationToken y CancellationTokenSource.
Para obtener más información, vea Patterns for Parallel Programming: Understanding and Applying Parallel
Patterns with the .NET Framework 4 (Patrones de programación en paralelo: descripción y aplicación de los
patrones en paralelo con .NET Framework 4).
PLINQ admite el paralelismo de datos con sintaxis declarativa o de consulta. Para más información, consulte
Parallel LINQ (PLINQ) (LINQ en paralelo [PLINQ]).

Temas relacionados
T IT L E DESC RIP C IÓ N

Cómo: Escribir un bucle Parallel.For sencillo Describe cómo escribir un bucle For en cualquier matriz o
colección de origen IEnumerable<T> indexable.

Cómo: Escribir un bucle Parallel.ForEach sencillo Describe cómo escribir un bucle ForEach en cualquier
colección de origen IEnumerable<T>.

Cómo: Detener o interrumpir un bucle Parallel.For Describe cómo detenerse o salir de un bucle paralelo de
forma que todos los subprocesos se informen de la acción.

Cómo: Escribir un bucle Parallel.For con variables locales de Describe cómo escribir un bucle For en el que cada
subproceso subproceso mantiene una variable privada que no está visible
para cualquier otro subproceso y cómo sincronizar los
resultados de todos los subprocesos cuando el bucle se
completa.

Cómo: Escribir un bucle Parallel.ForEach con variables locales Describe cómo escribir un bucle ForEach en el que cada
de partición subproceso mantiene una variable privada que no está visible
para cualquier otro subproceso y cómo sincronizar los
resultados de todos los subprocesos cuando el bucle se
completa.

Cómo: Cancelar un bucle Parallel.For o ForEach Describe cómo cancelar un bucle paralelo utilizando un
System.Threading.CancellationToken

Cómo: Acelerar cuerpos de bucle pequeños Describe una manera de acelerar la ejecución cuando el
cuerpo de un bucle es muy pequeño.

Biblioteca TPL Proporciona información general sobre la biblioteca TPL.

Programación en paralelo Presenta la programación paralela en .NET Framework.

Vea también
Programación en paralelo
Procedimiento para escribir un bucle Parallel.For
simple
16/09/2020 • 13 minutes to read • Edit Online

Este tema contiene dos ejemplos que ilustran el método Parallel.For. El primer ejemplo usa la sobrecarga del
método Parallel.For(Int64, Int64, Action<Int64>) y el segundo usa la sobrecarga Parallel.For(Int32, Int32,
Action<Int32>), las dos sobrecargas más simples del método Parallel.For. Puede usar estas dos sobrecargas del
método Parallel.For cuando no sea necesario cancelar el bucle, interrumpir las iteraciones del bucle o mantener
cualquier estado local de subproceso.

NOTE
En esta documentación, se utilizan expresiones lambda para definir delegados en la TPL. Si no está familiarizado con las
expresiones lambda de C# o Visual Basic, consulte Expresiones lambda en PLINQ y TPL.

El primer ejemplo calcula el tamaño de los archivos de un solo directorio. El segundo calcula el producto de dos
matrices.

Ejemplo de tamaño de directorio


Este ejemplo es una utilidad de línea de comandos simple que calcula el tamaño total de los archivos de un
directorio. Espera una ruta de directorio única como argumento e informa del número y el tamaño total de los
archivos del directorio. Después de comprobar que el directorio existe, usa el método Parallel.For para enumerar
los archivos del directorio y determinar su tamaño. A continuación, el tamaño de los archivos se agrega a la
variable totalSize . Tenga en cuenta que la adición se realiza llamando a Interlocked.Add para que se lleve a cabo
como una operación atómica. De lo contrario, varias tareas podrían tratar de actualizar la variable totalSize
simultáneamente.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
long totalSize = 0;

String[] args = Environment.GetCommandLineArgs();


if (args.Length == 1) {
Console.WriteLine("There are no command line arguments.");
return;
}
if (! Directory.Exists(args[1])) {
Console.WriteLine("The directory does not exist.");
return;
}

String[] files = Directory.GetFiles(args[1]);


Parallel.For(0, files.Length,
index => { FileInfo fi = new FileInfo(files[index]);
long size = fi.Length;
Interlocked.Add(ref totalSize, size);
} );
Console.WriteLine("Directory '{0}':", args[1]);
Console.WriteLine("{0:N0} files, {1:N0} bytes", files.Length, totalSize);
}
}
// The example displaysoutput like the following:
// Directory 'c:\windows\':
// 32 files, 6,587,222 bytes
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim totalSize As Long = 0

Dim args() As String = Environment.GetCommandLineArgs()


If args.Length = 1 Then
Console.WriteLine("There are no command line arguments.")
Return
End If
If Not Directory.Exists(args(1))
Console.WriteLine("The directory does not exist.")
Return
End If

Dim files() As String = Directory.GetFiles(args(1))


Parallel.For(0, files.Length,
Sub(index As Integer)
Dim fi As New FileInfo(files(index))
Dim size As Long = fi.Length
Interlocked.Add(totalSize, size)
End Sub)
Console.WriteLine("Directory '{0}':", args(1))
Console.WriteLine("{0:N0} files, {1:N0} bytes", files.Length, totalSize)
End Sub
End Module
' The example displays output like the following:
' Directory 'c:\windows\':
' 32 files, 6,587,222 bytes

Ejemplo de matriz y cronómetro


Este ejemplo usa el método Parallel.For para calcular el producto de dos matrices. También muestra cómo usar la
clase System.Diagnostics.Stopwatch para comparar el rendimiento de un bucle paralelo con un bucle no paralelo.
Tenga en cuenta que, como puede generar un gran volumen de salida, el ejemplo permite redirigir la salida a un
archivo.

using System;
using System.Diagnostics;
using System.Threading.Tasks;

class MultiplyMatrices
{
#region Sequential_Loop
static void MultiplyMatricesSequential(double[,] matA, double[,] matB,
double[,] result)
{
int matACols = matA.GetLength(1);
int matBCols = matB.GetLength(1);
int matARows = matA.GetLength(0);

for (int i = 0; i < matARows; i++)


{
for (int j = 0; j < matBCols; j++)
{
double temp = 0;
for (int k = 0; k < matACols; k++)
{
temp += matA[i, k] * matB[k, j];
}
result[i, j] += temp;
}
}
}
#endregion

#region Parallel_Loop
static void MultiplyMatricesParallel(double[,] matA, double[,] matB, double[,] result)
{
int matACols = matA.GetLength(1);
int matBCols = matB.GetLength(1);
int matARows = matA.GetLength(0);

// A basic matrix multiplication.


// Parallelize the outer loop to partition the source array by rows.
Parallel.For(0, matARows, i =>
{
for (int j = 0; j < matBCols; j++)
{
double temp = 0;
for (int k = 0; k < matACols; k++)
{
temp += matA[i, k] * matB[k, j];
}
result[i, j] = temp;
}
}); // Parallel.For
}
#endregion

#region Main
static void Main(string[] args)
{
// Set up matrices. Use small values to better view
// result matrix. Increase the counts to see greater
// speedup in the parallel loop vs. the sequential loop.
int colCount = 180;
int rowCount = 2000;
int colCount2 = 270;
double[,] m1 = InitializeMatrix(rowCount, colCount);
double[,] m2 = InitializeMatrix(colCount, colCount2);
double[,] result = new double[rowCount, colCount2];

// First do the sequential version.


Console.Error.WriteLine("Executing sequential loop...");
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

MultiplyMatricesSequential(m1, m2, result);


stopwatch.Stop();
Console.Error.WriteLine("Sequential loop time in milliseconds: {0}",
stopwatch.ElapsedMilliseconds);

// For the skeptics.


OfferToPrint(rowCount, colCount2, result);

// Reset timer and results matrix.


stopwatch.Reset();
result = new double[rowCount, colCount2];

// Do the parallel loop.


Console.Error.WriteLine("Executing parallel loop...");
stopwatch.Start();
MultiplyMatricesParallel(m1, m2, result);
stopwatch.Stop();
Console.Error.WriteLine("Parallel loop time in milliseconds: {0}",
stopwatch.ElapsedMilliseconds);
OfferToPrint(rowCount, colCount2, result);

// Keep the console window open in debug mode.


// Keep the console window open in debug mode.
Console.Error.WriteLine("Press any key to exit.");
Console.ReadKey();
}
#endregion

#region Helper_Methods
static double[,] InitializeMatrix(int rows, int cols)
{
double[,] matrix = new double[rows, cols];

Random r = new Random();


for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
matrix[i, j] = r.Next(100);
}
}
return matrix;
}

private static void OfferToPrint(int rowCount, int colCount, double[,] matrix)


{
Console.Error.Write("Computation complete. Print results (y/n)? ");
char c = Console.ReadKey(true).KeyChar;
Console.Error.WriteLine(c);
if (Char.ToUpperInvariant(c) == 'Y')
{
if (! Console.IsOutputRedirected) Console.WindowWidth = 180;
Console.WriteLine();
for (int x = 0; x < rowCount; x++)
{
Console.WriteLine("ROW {0}: ", x);
for (int y = 0; y < colCount; y++)
{
Console.Write("{0:#.##} ", matrix[x, y]);
}
Console.WriteLine();
}
}
}
#endregion
}

Imports System.Diagnostics
Imports System.Threading.Tasks

Module MultiplyMatrices
#Region "Sequential_Loop"
Sub MultiplyMatricesSequential(ByVal matA As Double(,), ByVal matB As Double(,), ByVal result As
Double(,))
Dim matACols As Integer = matA.GetLength(1)
Dim matBCols As Integer = matB.GetLength(1)
Dim matARows As Integer = matA.GetLength(0)

For i As Integer = 0 To matARows - 1


For j As Integer = 0 To matBCols - 1
Dim temp As Double = 0
For k As Integer = 0 To matACols - 1
temp += matA(i, k) * matB(k, j)
Next
result(i, j) += temp
Next
Next
End Sub
#End Region
#Region "Parallel_Loop"
Private Sub MultiplyMatricesParallel(ByVal matA As Double(,), ByVal matB As Double(,), ByVal result As
Double(,))
Dim matACols As Integer = matA.GetLength(1)
Dim matBCols As Integer = matB.GetLength(1)
Dim matARows As Integer = matA.GetLength(0)

' A basic matrix multiplication.


' Parallelize the outer loop to partition the source array by rows.
Parallel.For(0, matARows, Sub(i)
For j As Integer = 0 To matBCols - 1
Dim temp As Double = 0
For k As Integer = 0 To matACols - 1
temp += matA(i, k) * matB(k, j)
Next
result(i, j) += temp
Next
End Sub)
End Sub
#End Region

#Region "Main"
Sub Main(ByVal args As String())
' Set up matrices. Use small values to better view
' result matrix. Increase the counts to see greater
' speedup in the parallel loop vs. the sequential loop.
Dim colCount As Integer = 180
Dim rowCount As Integer = 2000
Dim colCount2 As Integer = 270
Dim m1 As Double(,) = InitializeMatrix(rowCount, colCount)
Dim m2 As Double(,) = InitializeMatrix(colCount, colCount2)
Dim result As Double(,) = New Double(rowCount - 1, colCount2 - 1) {}

' First do the sequential version.


Console.Error.WriteLine("Executing sequential loop...")
Dim stopwatch As New Stopwatch()
stopwatch.Start()

MultiplyMatricesSequential(m1, m2, result)


stopwatch.[Stop]()
Console.Error.WriteLine("Sequential loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds)

' For the skeptics.


OfferToPrint(rowCount, colCount2, result)

' Reset timer and results matrix.


stopwatch.Reset()
result = New Double(rowCount - 1, colCount2 - 1) {}

' Do the parallel loop.


Console.Error.WriteLine("Executing parallel loop...")
stopwatch.Start()
MultiplyMatricesParallel(m1, m2, result)
stopwatch.[Stop]()
Console.Error.WriteLine("Parallel loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds)
OfferToPrint(rowCount, colCount2, result)

' Keep the console window open in debug mode.


Console.Error.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
#End Region

#Region "Helper_Methods"
Function InitializeMatrix(ByVal rows As Integer, ByVal cols As Integer) As Double(,)
Dim matrix As Double(,) = New Double(rows - 1, cols - 1) {}

Dim r As New Random()


For i As Integer = 0 To rows - 1
For i As Integer = 0 To rows - 1
For j As Integer = 0 To cols - 1
matrix(i, j) = r.[Next](100)
Next
Next
Return matrix
End Function

Sub OfferToPrint(ByVal rowCount As Integer, ByVal colCount As Integer, ByVal matrix As Double(,))
Console.Error.Write("Computation complete. Display results (y/n)? ")
Dim c As Char = Console.ReadKey(True).KeyChar
Console.Error.WriteLine(c)
If Char.ToUpperInvariant(c) = "Y"c Then
If Not Console.IsOutputRedirected Then Console.WindowWidth = 168
Console.WriteLine()
For x As Integer = 0 To rowCount - 1
Console.WriteLine("ROW {0}: ", x)
For y As Integer = 0 To colCount - 1
Console.Write("{0:#.##} ", matrix(x, y))
Next
Console.WriteLine()
Next
End If
End Sub
#End Region
End Module

Al paralelizar código, incluidos los bucles, un objetivo importante consiste en usar los procesadores tanto como
sea posible, sin paralelizar en exceso hasta el punto de que la sobrecarga del procesamiento en paralelo niegue
cualquier mejora en el rendimiento. En este ejemplo concreto solo se paraleliza el bucle externo, ya que el bucle
interno no realiza mucho trabajo. La combinación de una pequeña cantidad de trabajo y efectos no deseados en la
caché puede producir una degradación del rendimiento en los bucles paralelos anidados. Por consiguiente,
paralelizar el bucle exterior solo es la mejor manera de maximizar las ventajas de simultaneidad en la mayoría de
los sistemas.

Delegado
El tercer parámetro de esta sobrecarga de For es un delegado de tipo Action<int> en C# o Action(Of Integer) en
Visual Basic. Un delegado Action , tanto si tiene uno, dieciséis o ningún parámetro de tipo, siempre devuelve void.
En Visual Basic, el comportamiento de Action se define con Sub . El ejemplo usa una expresión lambda para crear
el delegado, pero puede crear el delegado de otras maneras. Para obtener más información, consulte Expresiones
lambda en PLINQ y TPL.

Valor de iteración
El delegado toma un solo parámetro de entrada cuyo valor es la iteración actual. Este valor de iteración lo
suministra el tiempo de ejecución y su valor inicial es el índice del primer elemento del segmento (partición) del
origen que se está procesando en el subproceso actual.
Si necesita más control sobre el nivel de simultaneidad, use una de las sobrecargas que toma un parámetro de
entrada System.Threading.Tasks.ParallelOptions, como Parallel.For(Int32, Int32, ParallelOptions,
Action<Int32,ParallelLoopState>).

Valor devuelto y control de excepciones


For devuelve un objeto System.Threading.Tasks.ParallelLoopResult cuando se completen todos los subprocesos.
Este valor devuelto es útil cuando se desea detener o interrumpir la iteración de bucle manualmente, ya que
ParallelLoopResult almacena información, como la última iteración que se ejecutó hasta completarse. Si se
producen una o más excepciones en uno de los subprocesos, se inicia System.AggregateException.
En el código de este ejemplo, no se usa el valor devuelto de For.

Análisis y rendimiento
Puede usar el Asistente de rendimiento para ver el uso de CPU en el equipo. Como experimento, aumente el
número de columnas y filas de las matrices. Cuanto mayores sean las matrices, mayor será la diferencia de
rendimiento entre las versiones paralelas y secuenciales del cálculo. Si la matriz es pequeña, la versión secuencial
se ejecutará más rápido debido a la sobrecarga de la configuración del bucle paralelo.
Las llamadas sincrónicas a los recursos compartidos, como la consola o el sistema de archivos, reducirán
considerablemente el rendimiento de un bucle paralelo. Al medir el rendimiento, intente evitar las llamadas como
Console.WriteLine dentro del bucle.

Compilar el código
Copie y pegue este código en un proyecto de Visual Studio.

Vea también
For
ForEach
Paralelismo de datos
Programación en paralelo
Procedimiento Escribir un bucle Parallel.ForEach
sencillo
16/09/2020 • 5 minutes to read • Edit Online

Este ejemplo muestra cómo utilizar un bucle Parallel.ForEach para habilitar el paralelismo de datos sobre cualquier
origen de datos System.Collections.IEnumerable o System.Collections.Generic.IEnumerable<T>.

NOTE
En esta documentación, se utilizan expresiones lambda para definir delegados en PLINQ. Si no está familiarizado con las
expresiones lambda de C# o Visual Basic, vea Expresiones lambda en PLINQ y TPL.

Ejemplo
En este ejemplo se supone que tiene varios archivos .jpg en la carpeta C:\Usuarios\Público\Imágenes\Imágenes de
muestra y crea una subcarpeta con el nombre Modificadas. Al ejecutar el ejemplo, gira cada imagen .jpg de
Imágenes de muestra y la guarda en Modificadas. Puede modificar las dos rutas de acceso según sea necesario.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Drawing;

public class Example


{
public static void Main()
{
// A simple source for demonstration purposes. Modify this path as necessary.
string[] files = Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures", "*.jpg");
string newDir = @"C:\Users\Public\Pictures\Sample Pictures\Modified";
Directory.CreateDirectory(newDir);

// Method signature: Parallel.ForEach(IEnumerable<TSource> source, Action<TSource> body)


Parallel.ForEach(files, (currentFile) =>
{
// The more computational work you do here, the greater
// the speedup compared to a sequential foreach loop.
string filename = Path.GetFileName(currentFile);
var bitmap = new Bitmap(currentFile);

bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
bitmap.Save(Path.Combine(newDir, filename));

// Peek behind the scenes to see how work is parallelized.


// But be aware: Thread contention for the Console slows down parallel
loops!!!

Console.WriteLine($"Processing {filename} on thread


{Thread.CurrentThread.ManagedThreadId}");
//close lambda expression and method invocation
});

// Keep the console window open in debug mode.


Console.WriteLine("Processing complete. Press any key to exit.");
Console.ReadKey();
}
}
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Drawing

Module ForEachDemo

Sub Main()
' A simple source for demonstration purposes. Modify this path as necessary.
Dim files As String() = Directory.GetFiles("C:\Users\Public\Pictures\Sample Pictures", "*.jpg")
Dim newDir As String = "C:\Users\Public\Pictures\Sample Pictures\Modified"
Directory.CreateDirectory(newDir)

Parallel.ForEach(files, Sub(currentFile)
' The more computational work you do here, the greater
' the speedup compared to a sequential foreach loop.
Dim filename As String = Path.GetFileName(currentFile)
Dim bitmap As New Bitmap(currentFile)

bitmap.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone)
bitmap.Save(Path.Combine(newDir, filename))

' Peek behind the scenes to see how work is parallelized.


' But be aware: Thread contention for the Console slows down parallel
loops!!!

Console.WriteLine($"Processing {filename} on thread


{Thread.CurrentThread.ManagedThreadId}")
'close lambda expression and method invocation
End Sub)

' Keep the console window open in debug mode.


Console.WriteLine("Processing complete. Press any key to exit.")
Console.ReadKey()
End Sub
End Module

Un bucle Parallel.ForEach funciona como un bucle Parallel.For. El bucle divide la colección de origen y programa el
trabajo en varios subprocesos en función del entorno del sistema. Mientras más procesadores haya en el sistema,
más rápido se ejecutará el método paralelo. Para algunas colecciones de origen, un bucle secuencial puede ser más
rápido, dependiendo del tamaño del origen y del tipo de trabajo que realiza el bucle. Para más información sobre el
rendimiento, vea Problemas potenciales en el paralelismo de datos y tareas.
Para obtener más información sobre los bucles paralelos, vea Procedimiento para escribir un bucle Parallel.For
sencillo.
Para usar Parallel.ForEach con una colección no genérica, puede usar el método de extensión Enumerable.Cast para
convertir la colección en una colección genérica, como se muestra en el ejemplo siguiente:

Parallel.ForEach(nonGenericCollection.Cast<object>(),
currentElement =>
{
});

Parallel.ForEach(nonGenericCollection.Cast(Of Object), _
Sub(currentElement)
' ... work with currentElement
End Sub)

También puede utilizar Parallel LINQ (PLINQ) para paralelizar el procesamiento de orígenes de datos
IEnumerable<T>. PLINQ permite usar la sintaxis de consulta declarativa para expresar el comportamiento del
bucle. Para más información, consulte Parallel LINQ (PLINQ).

Compilación y ejecución del código


Puede compilar el código como una aplicación de consola de .NET Framework o como una aplicación de consola
de .NET Core.
En Visual Studio, hay plantillas de aplicación de consola de Visual Basic y C# para Windows Desktop y .NET Core.
Desde la línea de comandos, puede usar la CLI de .NET Core y sus comandos (por ejemplo, dotnet new console o
dotnet new console -lang vb ). También puede crear el archivo y usar el compilador de línea de comandos para una
aplicación de .NET Framework.
Para un proyecto de .NET Core, debe hacer referencia al paquete NuGet System.Drawing.Common . En Visual
Studio, use el Administrador de paquetes de NuGet para instalar el paquete. Si lo prefiere, puede agregar una
referencia al paquete en su archivo *.csproj o *.vbproj:

<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="4.5.1" />
</ItemGroup>

Para ejecutar una aplicación de consola .NET Core desde la línea de comandos, use dotnet run desde la carpeta
que contenga la aplicación.
Para ejecutar la aplicación de consola desde Visual Studio, presione F5 .

Vea también
Paralelismo de datos (biblioteca TPL)
Programación en paralelo en .NET
Parallel LINQ (PLINQ)
Procedimiento para escribir un bucle Parallel.For con
variables locales de subproceso
16/09/2020 • 6 minutes to read • Edit Online

En este ejemplo se muestra la forma de usar variables locales para el subproceso para almacenar y recuperar el
estado en cada una de las tareas independientes creadas por un bucle For. Mediante el uso de datos locales de
subproceso, se puede evitar la sobrecarga que supone la sincronización de un gran número de accesos a un
estado compartido. En vez de escribir en un recurso compartido en cada iteración, se calcula y se almacena el
valor hasta que finalizan todas las iteraciones de la tarea. A continuación, se escribe una vez el resultado final en el
recurso compartido o se pasa a otro método.

Ejemplo
En el ejemplo siguiente se llama al método For<TLocal>(Int32, Int32, Func<TLocal>,
Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) para calcular la suma de los valores de una matriz
que contiene un millón de elementos. El valor de cada elemento es igual a su índice.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Test
{
static void Main()
{
int[] nums = Enumerable.Range(0, 1000000).ToArray();
long total = 0;

// Use type parameter to make subtotal a long, not an int


Parallel.For<long>(0, nums.Length, () => 0, (j, loop, subtotal) =>
{
subtotal += nums[j];
return subtotal;
},
(x) => Interlocked.Add(ref total, x)
);

Console.WriteLine("The total is {0:N0}", total);


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
'How to: Write a Parallel.For Loop That Has Thread-Local Variables

Imports System.Threading
Imports System.Threading.Tasks

Module ForWithThreadLocal

Sub Main()
Dim nums As Integer() = Enumerable.Range(0, 1000000).ToArray()
Dim total As Long = 0

' Use type parameter to make subtotal a Long type. Function will overflow otherwise.
Parallel.For(Of Long)(0, nums.Length, Function() 0, Function(j, [loop], subtotal)
subtotal += nums(j)
Return subtotal
End Function, Function(x) Interlocked.Add(total,
x))

Console.WriteLine("The total is {0:N0}", total)


Console.WriteLine("Press any key to exit")
Console.ReadKey()
End Sub

End Module

Los dos primeros parámetros de cada método For especifican los valores de iteración inicial y final. En esta
sobrecarga del método, el tercer parámetro es donde se inicializa el estado local. En este contexto, estado local
significa una variable cuya duración se extiende desde justo antes de la primera iteración del bucle en el
subproceso actual, hasta justo después de la última iteración.
El tipo del tercer parámetro es Func<TResult>, donde TResult es el tipo de la variable que almacenará el estado
local de subproceso. Su tipo se define mediante el argumento de tipo genérico que se proporciona al llamar al
método For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>)
genérico, que en este caso es Int64. El argumento de tipo le indica al compilador el tipo de variable temporal que
se usará para almacenar el estado local de subproceso. En este ejemplo, la expresión () => 0 (o Function() 0 en
Visual Basic) inicializa la variable local de subproceso en cero. Si el argumento de tipo genérico es un tipo de
referencia o un tipo de valor definido por el usuario, la expresión tendrá el aspecto siguiente:

() => new MyClass()

Function() new MyClass()

El cuarto parámetro define la lógica del bucle y debe ser un delegado o una expresión lambda cuya firma sea
Func<int, ParallelLoopState, long, long> en C# o Func(Of Integer, ParallelLoopState, Long, Long) en Visual
Basic. El primer parámetro es el valor del contador de bucle correspondiente a esa iteración del bucle. El segundo
es un objeto ParallelLoopState que se puede usar para desglosar el bucle; la clase Parallel proporciona este objeto
a cada repetición del bucle. El tercer parámetro es la variable local de subproceso. El último parámetro es el tipo de
valor devuelto. En este caso, el tipo es Int64 porque es el que se especificó en el argumento de tipo For. Esa
variable se denomina subtotal y la devuelve la expresión lambda. El valor devuelto se usa para inicializar
subtotal en todas las iteraciones posteriores del bucle. Este último parámetro también se puede considerar como
un valor que se pasa a todas las iteraciones y que, cuando finaliza la última iteración, se pasa al delegado
localFinally .

El quinto parámetro define el método que se llama una vez, después de que se hayan completado todas las
iteraciones de un subproceso en particular. El tipo del argumento de entrada se corresponde de nuevo con el
argumento de tipo del método For<TLocal>(Int32, Int32, Func<TLocal>,
Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) y el tipo devuelto por la expresión lambda del
cuerpo. En este ejemplo, el valor se agrega a una variable en el ámbito de clase de un modo seguro para
subprocesos mediante una llamada al método Interlocked.Add. Gracias a la variable local de subproceso, se ha
evitado el tener que escribir en esta variable de clase en cada iteración del bucle.
Para obtener más información sobre cómo usar las expresiones lambda, vea Expresiones lambda en PLINQ y TPL.

Vea también
Paralelismo de datos
Programación en paralelo
Biblioteca TPL
Expresiones lambda en PLINQ y TPL
Procedimiento para escribir un bucle Parallel.ForEach
con variables locales de partición
16/09/2020 • 6 minutes to read • Edit Online

En el ejemplo siguiente se muestra cómo escribir un método ForEach en el que se usan variables locales de
partición. Cuando un bucle ForEach se ejecuta, divide su colección de origen en varias particiones. Cada partición
tiene su propia copia de la variable local de partición. Una variable local de partición es similar a una variable local
de subproceso, excepto por el hecho de que varias particiones se pueden ejecutar en un único subproceso.
El código y los parámetros de este ejemplo se parecen mucho al método For correspondiente. Para obtener más
información, vea Cómo: Escribir un bucle Parallel.For con variables locales de subproceso.
Para usar una variable local de partición en un bucle ForEach, debe llamar a una de las sobrecargas del método
que toma dos parámetros de tipo. El primer parámetro de tipo, TSource , especifica el tipo del elemento de origen,
mientras que el segundo parámetro de tipo, TLocal , especifica el tipo de la variable local de partición.

Ejemplo
En el ejemplo siguiente se realiza una llamada a la sobrecarga de Parallel.ForEach<TSource,TLocal>
(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) para
calcular la suma de una matriz de un millón de elementos. Esta sobrecarga gráfico tiene cuatro parámetros:
source, que es el origen de datos. Debe implementar IEnumerable<T>. El origen de datos de este ejemplo
es un objeto IEnumerable<Int32> de un millón de miembros que ha sido devuelto por el método
Enumerable.Range.
localInit , o bien la función que inicializa la variable local de partición. Esta función se llama una vez por
cada partición en la que se ejecuta la operación Parallel.ForEach. En el ejemplo se inicializa la variable local
de partición en cero.
body , un Func<T1,T2,T3,TResult> al que invoca el bucle paralelo en cada iteración del mismo bucle. Su
firma es Func\<TSource, ParallelLoopState, TLocal, TLocal> . Se proporciona el código para el delegado y el
bucle pasa los parámetros de entrada, que son:
El elemento actual de IEnumerable<T>.
Una variable ParallelLoopState que se puede usar en el código del delegado a fin de examinar el
estado del bucle.
La variable local de partición.
El delegado devuelve la variable local de partición, y esta se pasa luego a la siguiente iteración del bucle que
se ejecuta en esa partición concreta. Cada una de las particiones del bucle mantiene una instancia
independiente de esta variable.
En el ejemplo, el delegado agrega el valor de cada entero a la variable local de partición, que mantiene un
total acumulado de los valores de los elementos enteros de esa partición.
localFinally , un delegado Action<TLocal> al que invoca Parallel.ForEach cuando se han completado las
operaciones de bucle en las particiones. El método Parallel.ForEach pasa al delegado Action<TLocal> el
valor final de la variable local de partición correspondiente a esta partición del bucle, y usted proporciona el
código que realiza la acción necesaria para combinar el resultado de esta partición con los resultados de las
otras. Varias tareas pueden invocar simultáneamente a este delegado. Debido a esto, en el ejemplo se usa el
método Interlocked.Add(Int32, Int32) se utiliza para sincronizar el acceso a la variable total . Como el tipo
de delegado es Action<T>, no hay valor devuelto.

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Test
{
static void Main()
{
int[] nums = Enumerable.Range(0, 1000000).ToArray();
long total = 0;

// First type parameter is the type of the source elements


// Second type parameter is the type of the thread-local variable (partition subtotal)
Parallel.ForEach<int, long>(nums, // source collection
() => 0, // method to initialize the local variable
(j, loop, subtotal) => // method invoked by the loop on each iteration
{
subtotal += j; //modify local variable
return subtotal; // value to be passed to next iteration
},
// Method to be executed when each partition has completed.
// finalResult is the final value of subtotal for a particular partition.
(finalResult) => Interlocked.Add(ref total, finalResult)
);

Console.WriteLine("The total from Parallel.ForEach is {0:N0}", total);


}
}
// The example displays the following output:
// The total from Parallel.ForEach is 499,999,500,000

' How to: Write a Parallel.ForEach Loop That Has Thread-Local Variables

Imports System.Threading
Imports System.Threading.Tasks

Module ForEachThreadLocal
Sub Main()

Dim nums() As Integer = Enumerable.Range(0, 1000000).ToArray()


Dim total As Long = 0

' First type paramemter is the type of the source elements


' Second type parameter is the type of the thread-local variable (partition subtotal)
Parallel.ForEach(Of Integer, Long)(nums, Function() 0,
Function(elem, loopState, subtotal)
subtotal += elem
Return subtotal
End Function,
Sub(finalResult)
Interlocked.Add(total, finalResult)
End Sub)

Console.WriteLine("The result of Parallel.ForEach is {0:N0}", total)


End Sub
End Module
' The example displays the following output:
' The result of Parallel.ForEach is 499,999,500,000
Vea también
Paralelismo de datos
Cómo: Escribir un bucle Parallel.For con variables locales de subproceso
Expresiones lambda en PLINQ y TPL
Procedimiento para cancelar un bucle Parallel.For o
ForEach
16/09/2020 • 2 minutes to read • Edit Online

Los métodos Parallel.For y Parallel.ForEach admiten la cancelación mediante tokens de cancelación. Para obtener
más información sobre la cancelación, vea Cancelación. En un bucle paralelo, se proporciona CancellationToken al
método en el parámetro ParallelOptions y, a continuación, incluye la llamada paralela en un bloque try-catch.

Ejemplo
En el ejemplo siguiente se muestra cómo cancelar una llamada a Parallel.ForEach. Puede aplicar el mismo enfoque
a una llamada Parallel.For.
namespace CancelParallelLoops
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static void Main()
{
int[] nums = Enumerable.Range(0, 10000000).ToArray();
CancellationTokenSource cts = new CancellationTokenSource();

// Use ParallelOptions instance to store the CancellationToken


ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
Console.WriteLine("Press any key to start. Press 'c' to cancel.");
Console.ReadKey();

// Run a task so that we can cancel from another thread.


Task.Factory.StartNew(() =>
{
if (Console.ReadKey().KeyChar == 'c')
cts.Cancel();
Console.WriteLine("press any key to exit");
});

try
{
Parallel.ForEach(nums, po, (num) =>
{
double d = Math.Sqrt(num);
Console.WriteLine("{0} on {1}", d, Thread.CurrentThread.ManagedThreadId);
po.CancellationToken.ThrowIfCancellationRequested();
});
}
catch (OperationCanceledException e)
{
Console.WriteLine(e.Message);
}
finally
{
cts.Dispose();
}

Console.ReadKey();
}
}
}
' How to: Cancel a Parallel.For or ForEach Loop

Imports System.Threading
Imports System.Threading.Tasks

Module CancelParallelLoops
Sub Main()
Dim nums() As Integer = Enumerable.Range(0, 10000000).ToArray()
Dim cts As New CancellationTokenSource

' Use ParallelOptions instance to store the CancellationToken


Dim po As New ParallelOptions
po.CancellationToken = cts.Token
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount
Console.WriteLine("Press any key to start. Press 'c' to cancel.")
Console.ReadKey()

' Run a task so that we can cancel from another thread.


Dim t As Task = Task.Factory.StartNew(Sub()
If Console.ReadKey().KeyChar = "c"c Then
cts.Cancel()
End If
Console.WriteLine(vbCrLf & "Press any key to exit.")
End Sub)

Try

' The error "Exception is unhandled by user code" will appear if "Just My Code"
' is enabled. This error is benign. You can press F5 to continue, or disable Just My Code.
Parallel.ForEach(nums, po, Sub(num)
Dim d As Double = Math.Sqrt(num)
Console.CursorLeft = 0
Console.Write("{0:##.##} on {1}", d,
Thread.CurrentThread.ManagedThreadId)
po.CancellationToken.ThrowIfCancellationRequested()
End Sub)

Catch e As OperationCanceledException
Console.WriteLine(e.Message)
Finally
cts.Dispose()
End Try

Console.ReadKey()

End Sub
End Module

Si el token que señala la cancelación es el mismo token que se especifica en la instancia ParallelOptions, el bucle
paralelo producirá una sola clase OperationCanceledException en la cancelación. Si algún otro token produce la
cancelación, el bucle producirá una clase AggregateException con una clase OperationCanceledException como una
excepción interna.

Vea también
Paralelismo de datos
Expresiones lambda en PLINQ y TPL
Procedimiento para controlar excepciones en bucles
paralelos
16/09/2020 • 4 minutes to read • Edit Online

Las sobrecargas Parallel.ForParallel.ForEach no tienen ningún mecanismo especial para controlar las excepciones
que puedan iniciarse. En este sentido, se asemejan a bucles for y foreach normales ( For y For Each en Visual
Basic). Una excepción no controlada hace que el bucle termine en cuanto finalicen todas las iteraciones que se estén
ejecutando.
Cuando agregue su propia lógica de control de excepciones a los bucles paralelos, tenga en cuenta la posibilidad de
que se inicien excepciones similares en varios subprocesos al mismo tiempo y la opción de que una excepción
iniciada en un subproceso puede hacer que se inicie otra excepción en otro subproceso. Puede controlar ambos
casos ajustando todas las excepciones del bucle en System.AggregateException. En el ejemplo siguiente, se muestra
un posible enfoque.

NOTE
Cuando está habilitada la opción "Solo mi código", en algunos casos, Visual Studio se interrumpe en la línea que produce la
excepción y muestra el mensaje de error "Excepción no controlada por el código de usuario". Este error es benigno. Puede
presionar F5 para continuar y ver el comportamiento de control de excepciones que se muestra en el siguiente ejemplo. Para
evitar que Visual Studio se interrumpa con el primer error, desactive la casilla "Solo mi código" en Herramientas, Opciones,
Depurar, General.

Ejemplo
En este ejemplo, se detectan todas las excepciones y, a continuación, se ajusta en un System.AggregateException
que se produce. El llamador puede decidir qué excepciones se deben administrar.
class ExceptionDemo2
{
static void Main(string[] args)
{
// Create some random data to process in parallel.
// There is a good probability this data will cause some exceptions to be thrown.
byte[] data = new byte[5000];
Random r = new Random();
r.NextBytes(data);

try
{
ProcessDataInParallel(data);
}
catch (AggregateException ae)
{
var ignoredExceptions = new List<Exception>();
// This is where you can choose which exceptions to handle.
foreach (var ex in ae.Flatten().InnerExceptions)
{
if (ex is ArgumentException)
Console.WriteLine(ex.Message);
else
ignoredExceptions.Add(ex);
}
if (ignoredExceptions.Count > 0) throw new AggregateException(ignoredExceptions);
}

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
}

private static void ProcessDataInParallel(byte[] data)


{
// Use ConcurrentQueue to enable safe enqueueing from multiple threads.
var exceptions = new ConcurrentQueue<Exception>();

// Execute the complete loop and capture all exceptions.


Parallel.ForEach(data, d =>
{
try
{
// Cause a few exceptions, but not too many.
if (d < 3)
throw new ArgumentException($"Value is {d}. Value must be greater than or equal to 3.");
else
Console.Write(d + " ");
}
// Store the exception and continue with the loop.
catch (Exception e)
{
exceptions.Enqueue(e);
}
});
Console.WriteLine();

// Throw the exceptions here after the loop completes.


if (exceptions.Count > 0) throw new AggregateException(exceptions);
}
}
' How to: Handle Exceptions in Parallel Loops

Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Threading.Tasks

Module ExceptionsInLoops

Sub Main()

' Create some random data to process in parallel.


' There is a good probability this data will cause some exceptions to be thrown.
Dim data(1000) As Byte
Dim r As New Random()
r.NextBytes(data)

Try
ProcessDataInParallel(data)
Catch ae As AggregateException
Dim ignoredExceptions As New List(Of Exception)
' This is where you can choose which exceptions to handle.
For Each ex As Exception In ae.Flatten().InnerExceptions
If (TypeOf (ex) Is ArgumentException) Then
Console.WriteLine(ex.Message)
Else
ignoredExceptions.Add(ex)
End If
Next
If ignoredExceptions.Count > 0 Then
Throw New AggregateException(ignoredExceptions)
End If
End Try
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
Sub ProcessDataInParallel(ByVal data As Byte())

' Use ConcurrentQueue to enable safe enqueueing from multiple threads.


Dim exceptions As New ConcurrentQueue(Of Exception)

' Execute the complete loop and capture all exceptions.


Parallel.ForEach(Of Byte)(data, Sub(d)
Try
' Cause a few exceptions, but not too many.
If d < 3 Then
Throw New ArgumentException($"Value is {d}. Value must be
greater than or equal to 3")
Else
Console.Write(d & " ")
End If
Catch ex As Exception
' Store the exception and continue with the loop.
exceptions.Enqueue(ex)
End Try
End Sub)
Console.WriteLine()
' Throw the exceptions here after the loop completes.
If exceptions.Count > 0 Then
Throw New AggregateException(exceptions)
End If
End Sub
End Module

Vea también
Paralelismo de datos
Expresiones lambda en PLINQ y TPL
Cómo: Acelerar cuerpos de bucle pequeños
16/09/2020 • 2 minutes to read • Edit Online

Si un bucle Parallel.For tiene un cuerpo pequeño, su rendimiento puede ser menor que el del bucle secuencial
equivalente, como el bucle for de C# y el bucle For de Visual Basic. El menor rendimiento se debe a la sobrecarga
implicada en la partición de los datos y al costo de invocar un delegado en cada iteración del bucle. Para abordar
estos escenarios, la clase Partitioner proporciona el método Partitioner.Create, que permite proporcionar un bucle
secuencial para el cuerpo del delegado de manera que el delegado solo se invoque una sola vez por partición, en
lugar de una vez por cada iteración. Para más información, consulte Custom Partitioners for PLINQ and TPL
(Particionadores personalizados para PLINQ y TPL).

Ejemplo
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static void Main()
{

// Source must be array or IList.


var source = Enumerable.Range(0, 100000).ToArray();

// Partition the entire source array.


var rangePartitioner = Partitioner.Create(0, source.Length);

double[] results = new double[source.Length];

// Loop over the partitions in parallel.


Parallel.ForEach(rangePartitioner, (range, loopState) =>
{
// Loop over each range element without a delegate invocation.
for (int i = range.Item1; i < range.Item2; i++)
{
results[i] = source[i] * Math.PI;
}
});

Console.WriteLine("Operation complete. Print results? y/n");


char input = Console.ReadKey().KeyChar;
if (input == 'y' || input == 'Y')
{
foreach(double d in results)
{
Console.Write("{0} ", d);
}
}
}
}
Imports System.Threading.Tasks
Imports System.Collections.Concurrent

Module PartitionDemo

Sub Main()
' Source must be array or IList.
Dim source = Enumerable.Range(0, 100000).ToArray()

' Partition the entire source array.


' Let the partitioner size the ranges.
Dim rangePartitioner = Partitioner.Create(0, source.Length)

Dim results(source.Length - 1) As Double

' Loop over the partitions in parallel. The Sub is invoked


' once per partition.
Parallel.ForEach(rangePartitioner, Sub(range, loopState)

' Loop over each range element without a delegate invocation.


For i As Integer = range.Item1 To range.Item2 - 1
results(i) = source(i) * Math.PI
Next
End Sub)
Console.WriteLine("Operation complete. Print results? y/n")
Dim input As Char = Console.ReadKey().KeyChar
If input = "y"c Or input = "Y"c Then
For Each d As Double In results
Console.Write("{0} ", d)
Next
End If

End Sub
End Module

El enfoque que se muestra en este ejemplo resulta útil cuando el bucle realiza una cantidad mínima de trabajo. A
medida que el trabajo requiere más procesamiento, probablemente obtendrá un rendimiento igual o mejor usando
un bucle For o ForEach con el particionador predeterminado.

Vea también
Data Parallelism (Paralelismo de datos)
Particionadores personalizados para PLINQ y TPL
Iteradores (C#)
Iteradores (Visual Basic)
Expresiones lambda en PLINQ y TPL
Cómo: Recorrer en iteración directorios con la clase
paralela
16/09/2020 • 7 minutes to read • Edit Online

En muchos casos, la iteración de archivo es una operación que se puede paralelizar fácilmente. El tema Cómo:
Recorrer en iteración directorios con PLINQ muestra la manera más fácil de realizar esta tarea para muchos
escenarios. Sin embargo, pueden surgir complicaciones cuando el código tiene que tratar con los muchos tipos de
excepciones que pueden surgir al obtener acceso al sistema de archivos. En el ejemplo siguiente se muestra un
enfoque para el problema. Usa una iteración basada en la pila para recorrer todos los archivos y carpetas en un
directorio especificado y habilita el código para detectar y controlar diversas excepciones. Por supuesto, la forma de
controlar las excepciones depende de usted.

Ejemplo
En el ejemplo siguiente se recorren los directorios secuencialmente, pero se procesan los archivos en paralelo. Este
enfoque es probablemente el mejor cuando hay una tasa alta de directorios y archivos. También es posible ejecutar
la iteración de directorio y obtener acceso a cada archivo secuencialmente. Probablemente no es eficaz paralelizar
ambos bucles a menos que esté dirigido específicamente a un equipo con un gran número de procesadores. Sin
embargo, como en todos los casos, se debe probar exhaustivamente la aplicación para determinar el mejor
enfoque.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static void Main()
{
try {
TraverseTreeParallelForEach(@"C:\Program Files", (f) =>
{
// Exceptions are no-ops.
try {
// Do nothing with the data except read it.
byte[] data = File.ReadAllBytes(f);
}
catch (FileNotFoundException) {}
catch (IOException) {}
catch (UnauthorizedAccessException) {}
catch (SecurityException) {}
// Display the filename.
Console.WriteLine(f);
});
}
catch (ArgumentException) {
Console.WriteLine(@"The directory 'C:\Program Files' does not exist.");
}

// Keep the console window open.


Console.ReadKey();
}
public static void TraverseTreeParallelForEach(string root, Action<string> action)
{
//Count of files traversed and timer for diagnostic output
int fileCount = 0;
var sw = Stopwatch.StartNew();

// Determine whether to parallelize file processing on each folder based on processor count.
int procCount = System.Environment.ProcessorCount;

// Data structure to hold names of subfolders to be examined for files.


Stack<string> dirs = new Stack<string>();

if (!Directory.Exists(root)) {
throw new ArgumentException();
}
dirs.Push(root);

while (dirs.Count > 0) {


string currentDir = dirs.Pop();
string[] subDirs = {};
string[] files = {};

try {
subDirs = Directory.GetDirectories(currentDir);
}
// Thrown if we do not have discovery permission on the directory.
catch (UnauthorizedAccessException e) {
Console.WriteLine(e.Message);
continue;
}
// Thrown if another process has deleted the directory after we retrieved its name.
catch (DirectoryNotFoundException e) {
Console.WriteLine(e.Message);
continue;
}

try {
files = Directory.GetFiles(currentDir);
}
catch (UnauthorizedAccessException e) {
Console.WriteLine(e.Message);
continue;
}
catch (DirectoryNotFoundException e) {
Console.WriteLine(e.Message);
continue;
}
catch (IOException e) {
Console.WriteLine(e.Message);
continue;
}

// Execute in parallel if there are enough files in the directory.


// Otherwise, execute sequentially.Files are opened and processed
// synchronously but this could be modified to perform async I/O.
try {
if (files.Length < procCount) {
foreach (var file in files) {
action(file);
fileCount++;
}
}
else {
Parallel.ForEach(files, () => 0, (file, loopState, localCount) =>
{ action(file);
return (int) ++localCount;
},
(c) => {
Interlocked.Add(ref fileCount, c);
Interlocked.Add(ref fileCount, c);
});
}
}
catch (AggregateException ae) {
ae.Handle((ex) => {
if (ex is UnauthorizedAccessException) {
// Here we just output a message and go on.
Console.WriteLine(ex.Message);
return true;
}
// Handle other exceptions here if necessary...

return false;
});
}

// Push the subdirectories onto the stack for traversal.


// This could also be done before handing the files.
foreach (string str in subDirs)
dirs.Push(str);
}

// For diagnostic purposes.


Console.WriteLine("Processed {0} files in {1} milliseconds", fileCount, sw.ElapsedMilliseconds);
}
}

Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.IO
Imports System.Security
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Sub Main()
Try
TraverseTreeParallelForEach("C:\Program Files",
Sub(f)
' Exceptions are No-ops.
Try
' Do nothing with the data except read it.
Dim data() As Byte = File.ReadAllBytes(f)
' In the event the file has been deleted.
Catch e As FileNotFoundException

' General I/O exception, especially if the file is in use.


Catch e As IOException

' Lack of adequate permissions.


Catch e As UnauthorizedAccessException

' Lack of adequate permissions.


Catch e As SecurityException

End Try
' Display the filename.
Console.WriteLine(f)
End Sub)
Catch e As ArgumentException
Console.WriteLine("The directory 'C:\Program Files' does not exist.")
End Try
' Keep the console window open.
Console.ReadKey()
End Sub

Public Sub TraverseTreeParallelForEach(ByVal root As String, ByVal action As Action(Of String))


Public Sub TraverseTreeParallelForEach(ByVal root As String, ByVal action As Action(Of String))
'Count of files traversed and timer for diagnostic output
Dim fileCount As Integer = 0
Dim sw As Stopwatch = Stopwatch.StartNew()

' Determine whether to parallelize file processing on each folder based on processor count.
Dim procCount As Integer = System.Environment.ProcessorCount

' Data structure to hold names of subfolders to be examined for files.


Dim dirs As New Stack(Of String)

If Not Directory.Exists(root) Then Throw New ArgumentException()

dirs.Push(root)

While (dirs.Count > 0)


Dim currentDir As String = dirs.Pop()
Dim subDirs() As String = Nothing
Dim files() As String = Nothing

Try
subDirs = Directory.GetDirectories(currentDir)
' Thrown if we do not have discovery permission on the directory.
Catch e As UnauthorizedAccessException
Console.WriteLine(e.Message)
Continue While
' Thrown if another process has deleted the directory after we retrieved its name.
Catch e As DirectoryNotFoundException
Console.WriteLine(e.Message)
Continue While
End Try

Try
files = Directory.GetFiles(currentDir)
Catch e As UnauthorizedAccessException
Console.WriteLine(e.Message)
Continue While
Catch e As DirectoryNotFoundException
Console.WriteLine(e.Message)
Continue While
Catch e As IOException
Console.WriteLine(e.Message)
Continue While
End Try

' Execute in parallel if there are enough files in the directory.


' Otherwise, execute sequentially.Files are opened and processed
' synchronously but this could be modified to perform async I/O.
Try
If files.Length < procCount Then
For Each file In files
action(file)
fileCount += 1
Next
Else
Parallel.ForEach(files, Function() 0, Function(file, loopState, localCount)
action(file)
localCount = localCount + 1
Return localCount
End Function,
Sub(c)
Interlocked.Add(fileCount, c)
End Sub)
End If
Catch ae As AggregateException
ae.Handle(Function(ex)

If TypeOf (ex) Is UnauthorizedAccessException Then


' Here we just output a message and go on.
Console.WriteLine(ex.Message)
Return True
End If
' Handle other exceptions here if necessary...

Return False
End Function)
End Try
' Push the subdirectories onto the stack for traversal.
' This could also be done before handing the files.
For Each str As String In subDirs
dirs.Push(str)
Next

' For diagnostic purposes.


Console.WriteLine("Processed {0} files in {1} milliseconds", fileCount, sw.ElapsedMilliseconds)
End While
End Sub
End Module

En este ejemplo, la E/S de archivo se realiza de forma sincrónica. Al trabajar con archivos grandes o conexiones de
red lentas, puede ser preferible obtener acceso a los archivos de forma asincrónica. Puede combinar las técnicas de
E/S asincrónica con la iteración paralela. Para más información, consulte TPL and Traditional .NET Framework
Asynchronous Programming (TPL y la programación asincrónica tradicional de .NET).
El ejemplo utiliza la variable local fileCount para mantener un número total de archivos procesados. Dado que
varias tareas podrían tener acceso simultáneamente a la variable, el acceso a la misma se sincroniza llamando al
método Interlocked.Add.
Tenga en cuenta que si se produce una excepción en el subproceso principal, los subprocesos que inicia el método
ForEach podrían seguir ejecutándose. Para detener estos subprocesos, puede establecer una variable booleana en
los controladores de excepciones y comprobar su valor en cada iteración del bucle paralelo. Si el valor indica que
se ha iniciado una excepción, use la variable ParallelLoopState para detener o interrumpir el bucle. Para más
información, vea Cómo: Detener o interrumpir un bucle Parallel.For.

Vea también
Data Parallelism (Paralelismo de datos)
Programación asincrónica basada en tareas
16/09/2020 • 60 minutes to read • Edit Online

La biblioteca TPL se basa en el concepto de tarea, que representa una operación asincrónica. De cierta forma, una
tarea recuerda a un subproceso o elemento de trabajo ThreadPool, pero en un nivel más alto de abstracción. El
término paralelismo de tareas hace referencia a la ejecución simultánea de una o varias tareas independientes.
Las tareas proporcionan dos ventajas fundamentales:
Un uso más eficaz y más escalable de los recursos del sistema.
En segundo plano, las tareas se ponen en la cola del elemento ThreadPool, que se ha mejorado con
algoritmos que determinan y ajustan el número de subprocesos y que ofrecen el equilibrio de carga para
maximizar el rendimiento. Esto hace que las tareas resulten relativamente ligeras y que, por tanto, pueda
crearse un gran número de ellas para habilitar un paralelismo pormenorizado.
Un mayor control mediante programación del que se puede conseguir con un subproceso o un elemento
de trabajo.
Las tareas y el marco que se crea en torno a ellas proporcionan un amplio conjunto de API que admiten el
uso de esperas, cancelaciones, continuaciones, control robusto de excepciones, estado detallado,
programación personalizada, y más.
Por estos dos motivos, en .NET Framework, TPL es la API preferida para escribir código multiproceso, asincrónico
y paralelo.

Crear y ejecutar tareas implícitamente


El método Parallel.Invoke proporciona una manera conveniente de ejecutar cualquier número de instrucciones
arbitrarias simultáneamente. Pase un delegado Action por cada elemento de trabajo. La manera más fácil de crear
estos delegados es con expresiones lambda. La expresión lambda puede llamar a un método con nombre o
proporcionar el código alineado. En el siguiente ejemplo se muestra una llamada a Invoke básica que crea e inicia
dos tareas que se ejecutan a la vez. La primera tarea se representa mediante una expresión lambda que llama a un
método denominado DoSomeWork y la segunda tarea se representa mediante una expresión lambda que llama a
un método denominado DoSomeOtherWork .

NOTE
En esta documentación, se utilizan expresiones lambda para definir delegados en la TPL. Si no está familiarizado con las
expresiones lambda de C# o Visual Basic, consulte Expresiones lambda en PLINQ y TPL.

Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());

Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())

NOTE
El número de instancias de Task que Invoke crea en segundo plano no es necesariamente igual al número de delegados que
se proporcionan. La TPL puede emplear varias optimizaciones, sobre todo con grandes números de delegados.
Para obtener más información, vea Cómo: Usar Parallel.Invoke para ejecutar operaciones en paralelo.
Para tener un mayor control de la ejecución de tareas o para devolver un valor de la tarea, debe trabajar con
objetos Task más explícitamente.

Crear y ejecutar tareas explícitamente


Una tarea que no devuelve un valor se representa mediante la clase System.Threading.Tasks.Task . Una tarea que
devuelve un valor se representa mediante la clase System.Threading.Tasks.Task<TResult>, que se hereda de Task.
El objeto de tarea controla los detalles de la infraestructura y proporciona métodos y propiedades a los que se
puede obtener acceso desde el subproceso que realiza la llamada a lo largo de la duración de la tarea. Por
ejemplo, se puede tener acceso a la propiedad Status de una tarea en cualquier momento para determinar si ha
empezado a ejecutarse, si se ha ejecutado hasta su finalización, si se ha cancelado o si se ha producido una
excepción. El estado se representa mediante la enumeración TaskStatus.
Cuando se crea una tarea, se proporciona un delegado de usuario que encapsula el código que la tarea va a
ejecutar. El delegado se puede expresar como un delegado con nombre, un método anónimo o una expresión
lambda. Las expresiones lambda pueden contener una llamada a un método con nombre, tal y como se muestra
en el siguiente ejemplo. Observe que el ejemplo incluye una llamada al método Task.Wait para garantizar que la
ejecución de la tarea se complete antes de que finalice la aplicación de modo de consola.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
Thread.CurrentThread.Name = "Main";

// Create a task and supply a user delegate by using a lambda expression.


Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
// Start the task.
taskA.Start();

// Output a message from the calling thread.


Console.WriteLine("Hello from thread '{0}'.",
Thread.CurrentThread.Name);
taskA.Wait();
}
}
// The example displays output like the following:
// Hello from thread 'Main'.
// Hello from taskA.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Thread.CurrentThread.Name = "Main"

' Create a task and supply a user delegate by using a lambda expression.
Dim taskA = New Task(Sub() Console.WriteLine("Hello from taskA."))
' Start the task.
taskA.Start()

' Output a message from the calling thread.


Console.WriteLine("Hello from thread '{0}'.",
Thread.CurrentThread.Name)
taskA.Wait()
End Sub
End Module
' The example displays output like the following:
' Hello from thread 'Main'.
' Hello from taskA.

También se pueden usar los métodos Task.Run para crear e iniciar una tarea en una sola operación. Para
administrar la tarea, los métodos Run usan el programador de tareas predeterminado, independientemente qué
programador de tareas esté asociado al subproceso actual. Los métodos Run son el modo preferido para crear e
iniciar tareas cuando no se necesita más control sobre la creación y la programación de la tarea.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
Thread.CurrentThread.Name = "Main";

// Define and run the task.


Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));

// Output a message from the calling thread.


Console.WriteLine("Hello from thread '{0}'.",
Thread.CurrentThread.Name);
taskA.Wait();
}
}
// The example displays output like the following:
// Hello from thread 'Main'.
// Hello from taskA.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Thread.CurrentThread.Name = "Main"

Dim taskA As Task = Task.Run(Sub() Console.WriteLine("Hello from taskA."))

' Output a message from the calling thread.


Console.WriteLine("Hello from thread '{0}'.",
Thread.CurrentThread.Name)
taskA.Wait()
End Sub
End Module
' The example displays output like the following:
' Hello from thread 'Main'.
' Hello from taskA.

También se puede usar el método TaskFactory.StartNew para crear e iniciar una tarea en una sola operación.
Utilice este método cuando la creación y la programación no tengan que ser independientes y necesite opciones
de creación de tareas adicionales o el uso de un programador concreto, o cuando necesita pasar información de
estado adicional en la tarea que se puede recuperar mediante su propiedad Task.AsyncState, como se muestra en
el ejemplo siguiente.
using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}

public class Example


{
public static void Main()
{
Task[] taskArray = new Task[10];
for (int i = 0; i < taskArray.Length; i++) {
taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
CustomData data = obj as CustomData;
if (data == null)
return;

data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
},
new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks}
);
}
Task.WaitAll(taskArray);
foreach (var task in taskArray) {
var data = task.AsyncState as CustomData;
if (data != null)
Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum);
}
}
}
// The example displays output like the following:
// Task #0 created at 635116412924597583 on thread #3.
// Task #1 created at 635116412924607584 on thread #4.
// Task #3 created at 635116412924607584 on thread #4.
// Task #4 created at 635116412924607584 on thread #4.
// Task #2 created at 635116412924607584 on thread #3.
// Task #6 created at 635116412924607584 on thread #3.
// Task #5 created at 635116412924607584 on thread #4.
// Task #8 created at 635116412924607584 on thread #4.
// Task #7 created at 635116412924607584 on thread #3.
// Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class

Module Example
Public Sub Main()
Dim taskArray(9) As Task
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
Dim data As CustomData = TryCast(obj, CustomData)
If data Is Nothing Then Return

data.ThreadNum = Thread.CurrentThread.ManagedThreadId
End Sub,
New CustomData With {.Name = i, .CreationTime =
DateTime.Now.Ticks})
Next
Task.WaitAll(taskArray)

For Each task In taskArray


Dim data = TryCast(task.AsyncState, CustomData)
If data IsNot Nothing Then
Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum)
End If
Next
End Sub
End Module
' The example displays output like the following:
' Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
' Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
' Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

Task y Task<TResult> exponen en cada caso una propiedad Factory estática que devuelve una instancia
predeterminada de TaskFactory, por lo que se puede llamar al método como Task.Factory.StartNew() . Asimismo,
en el siguiente ejemplo, dado que las tareas son de tipo System.Threading.Tasks.Task<TResult>, cada una tiene
una propiedad Task<TResult>.Result pública que contiene el resultado del cálculo. Las tareas se ejecutan de forma
asincrónica y pueden completarse en cualquier orden. Si se obtiene acceso a la propiedad Result antes de que el
cálculo se complete, la propiedad bloqueará el subproceso hasta que el valor esté disponible.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
Task<Double>[] taskArray = { Task<Double>.Factory.StartNew(() => DoComputation(1.0)),
Task<Double>.Factory.StartNew(() => DoComputation(100.0)),
Task<Double>.Factory.StartNew(() => DoComputation(1000.0)) };

var results = new Double[taskArray.Length];


Double sum = 0;

for (int i = 0; i < taskArray.Length; i++) {


results[i] = taskArray[i].Result;
Console.Write("{0:N1} {1}", results[i],
i == taskArray.Length - 1 ? "= " : "+ ");
sum += results[i];
}
Console.WriteLine("{0:N1}", sum);
}

private static Double DoComputation(Double start)


{
Double sum = 0;
for (var value = start; value <= start + 10; value += .1)
sum += value;

return sum;
}
}
// The example displays the following output:
// 606.0 + 10,605.0 + 100,495.0 = 111,706.0

Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation(1.0)),
Task(Of Double).Factory.StartNew(Function() DoComputation(100.0)),
Task(Of Double).Factory.StartNew(Function() DoComputation(1000.0))}

Dim results(taskArray.Length - 1) As Double


Dim sum As Double

For i As Integer = 0 To taskArray.Length - 1


results(i) = taskArray(i).Result
Console.Write("{0:N1} {1}", results(i),
If(i = taskArray.Length - 1, "= ", "+ "))
sum += results(i)
Next
Console.WriteLine("{0:N1}", sum)
End Sub

Private Function DoComputation(start As Double) As Double


Dim sum As Double
For value As Double = start To start + 10 Step .1
sum += value
Next
Return sum
End Function
End Module
' The example displays the following output:
' 606.0 + 10,605.0 + 100,495.0 = 111,706.0
Para obtener más información, vea Cómo: Devolver un valor a partir de una tarea.
Cuando se usa una expresión lambda para crear un delegado, se obtiene acceso a todas las variables que están
visibles en ese momento en el código fuente. Sin embargo, en algunos casos, sobre todo en los bucles, una
expresión lambda no captura la variable como cabría esperar. Captura solo el valor final, no el valor tal y como se
transforma después de cada iteración. En el siguiente ejemplo se ilustra el problema. Pasa un contador de bucle a
una expresión lambda que crea instancias de un objeto CustomData y usa el contador de bucle como identificador
del objeto. Como muestra la salida del ejemplo, cada objeto CustomData tiene un identificador idéntico.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}

public class Example


{
public static void Main()
{
// Create the task object by using an Action(Of Object) to pass in the loop
// counter. This produces an unexpected result.
Task[] taskArray = new Task[10];
for (int i = 0; i < taskArray.Length; i++) {
taskArray[i] = Task.Factory.StartNew( (Object obj) => {
var data = new CustomData() {Name = i, CreationTime =
DateTime.Now.Ticks};
data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Task #{0} created at {1} on thread #
{2}.",
data.Name, data.CreationTime,
data.ThreadNum);
},
i );
}
Task.WaitAll(taskArray);
}
}
// The example displays output like the following:
// Task #10 created at 635116418427727841 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427727841 on thread #3.
// Task #10 created at 635116418427747843 on thread #3.
// Task #10 created at 635116418427747843 on thread #3.
// Task #10 created at 635116418427737842 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class

Module Example
Public Sub Main()
' Create the task object by using an Action(Of Object) to pass in the loop
' counter. This produces an unexpected result.
Dim taskArray(9) As Task
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
Dim data As New CustomData With {.Name = i,
.CreationTime = DateTime.Now.Ticks}
data.ThreadNum = Thread.CurrentThread.ManagedThreadId
Console.WriteLine("Task #{0} created at {1} on thread #
{2}.",
data.Name, data.CreationTime,
data.ThreadNum)
End Sub,
i)
Next
Task.WaitAll(taskArray)
End Sub
End Module
' The example displays output like the following:
' Task #10 created at 635116418427727841 on thread #4.
' Task #10 created at 635116418427737842 on thread #4.
' Task #10 created at 635116418427737842 on thread #4.
' Task #10 created at 635116418427737842 on thread #4.
' Task #10 created at 635116418427737842 on thread #4.
' Task #10 created at 635116418427737842 on thread #4.
' Task #10 created at 635116418427727841 on thread #3.
' Task #10 created at 635116418427747843 on thread #3.
' Task #10 created at 635116418427747843 on thread #3.
' Task #10 created at 635116418427737842 on thread #4.

Puede obtener acceso al valor en cada iteración si proporciona un objeto de estado a una tarea a través de su
constructor. En el ejemplo siguiente se modifica el ejemplo anterior utilizando el contador de bucle al crear el
objeto CustomData que, a su vez, se pasa a la expresión lambda. Como muestra el resultado del ejemplo, cada
objeto CustomData tiene ahora un identificador único basado en el valor del contador de bucle cuando se creó la
instancia del objeto.
using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}

public class Example


{
public static void Main()
{
// Create the task object by using an Action(Of Object) to pass in custom data
// to the Task constructor. This is useful when you need to capture outer variables
// from within a loop.
Task[] taskArray = new Task[10];
for (int i = 0; i < taskArray.Length; i++) {
taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
CustomData data = obj as CustomData;
if (data == null)
return;

data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Task #{0} created at {1} on thread #
{2}.",
data.Name, data.CreationTime,
data.ThreadNum);
},
new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks}
);
}
Task.WaitAll(taskArray);
}
}
// The example displays output like the following:
// Task #0 created at 635116412924597583 on thread #3.
// Task #1 created at 635116412924607584 on thread #4.
// Task #3 created at 635116412924607584 on thread #4.
// Task #4 created at 635116412924607584 on thread #4.
// Task #2 created at 635116412924607584 on thread #3.
// Task #6 created at 635116412924607584 on thread #3.
// Task #5 created at 635116412924607584 on thread #4.
// Task #8 created at 635116412924607584 on thread #4.
// Task #7 created at 635116412924607584 on thread #3.
// Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class

Module Example
Public Sub Main()
' Create the task object by using an Action(Of Object) to pass in custom data
' to the Task constructor. This is useful when you need to capture outer variables
' from within a loop.
Dim taskArray(9) As Task
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
Dim data As CustomData = TryCast(obj, CustomData)
If data Is Nothing Then Return

data.ThreadNum = Thread.CurrentThread.ManagedThreadId
Console.WriteLine("Task #{0} created at {1} on thread #
{2}.",
data.Name, data.CreationTime,
data.ThreadNum)
End Sub,
New CustomData With {.Name = i, .CreationTime =
DateTime.Now.Ticks})
Next
Task.WaitAll(taskArray)
End Sub
End Module
' The example displays output like the following:
' Task #0 created at 635116412924597583 on thread #3.
' Task #1 created at 635116412924607584 on thread #4.
' Task #3 created at 635116412924607584 on thread #4.
' Task #4 created at 635116412924607584 on thread #4.
' Task #2 created at 635116412924607584 on thread #3.
' Task #6 created at 635116412924607584 on thread #3.
' Task #5 created at 635116412924607584 on thread #4.
' Task #8 created at 635116412924607584 on thread #4.
' Task #7 created at 635116412924607584 on thread #3.
' Task #9 created at 635116412924607584 on thread #4.

Este estado se pasa como argumento al delegado de la tarea y permite tener acceso desde el objeto de tarea
mediante la propiedad Task.AsyncState. El ejemplo siguiente es una variación del ejemplo anterior. Utiliza la
propiedad AsyncState para mostrar información sobre los objetos CustomData pasados a la expresión lambda.
using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}

public class Example


{
public static void Main()
{
Task[] taskArray = new Task[10];
for (int i = 0; i < taskArray.Length; i++) {
taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
CustomData data = obj as CustomData;
if (data == null)
return;

data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
},
new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks}
);
}
Task.WaitAll(taskArray);
foreach (var task in taskArray) {
var data = task.AsyncState as CustomData;
if (data != null)
Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum);
}
}
}
// The example displays output like the following:
// Task #0 created at 635116412924597583 on thread #3.
// Task #1 created at 635116412924607584 on thread #4.
// Task #3 created at 635116412924607584 on thread #4.
// Task #4 created at 635116412924607584 on thread #4.
// Task #2 created at 635116412924607584 on thread #3.
// Task #6 created at 635116412924607584 on thread #3.
// Task #5 created at 635116412924607584 on thread #4.
// Task #8 created at 635116412924607584 on thread #4.
// Task #7 created at 635116412924607584 on thread #3.
// Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class

Module Example
Public Sub Main()
Dim taskArray(9) As Task
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
Dim data As CustomData = TryCast(obj, CustomData)
If data Is Nothing Then Return

data.ThreadNum = Thread.CurrentThread.ManagedThreadId
End Sub,
New CustomData With {.Name = i, .CreationTime =
DateTime.Now.Ticks})
Next
Task.WaitAll(taskArray)

For Each task In taskArray


Dim data = TryCast(task.AsyncState, CustomData)
If data IsNot Nothing Then
Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum)
End If
Next
End Sub
End Module
' The example displays output like the following:
' Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
' Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
' Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

Identificador de tarea
Cada tarea recibe un identificador entero que la identifica de manera inequívoca en un dominio de aplicación y al
que se puede obtener acceso mediante la propiedad Task.Id. El identificador resulta útil para ver información
sobre la tarea en las ventanas Pilas paralelas y Tareas del depurador de Visual Studio. El identificador se crea de
forma diferida, lo que significa que no se crea hasta que se solicita; por tanto, una tarea podrá tener un
identificador diferente cada vez que se ejecute el programa. Para más información sobre cómo ver los
identificadores de tarea en el depurador, consulte Usar la ventana Tareas y Usar la ventana Pilas paralelas.

Opciones de creación de tareas


La mayoría de las API que crean tareas proporcionan sobrecargas que aceptan un parámetro
TaskCreationOptions. Al especificar una de estas opciones, se le está indicando al programador cómo se programa
la tarea en el grupo de subprocesos. En la tabla siguiente se muestran las diversas opciones de creación de tareas.
TA SKC REAT IO N O P T IO N S VA LO R DE PA RÁ M ET RO DESC RIP C IÓ N

None Es la opción predeterminada si no se especifica ninguna


opción. El programador usa su heurística predeterminada
para programar la tarea.

PreferFairness Especifica que la tarea debe programarse de modo que las


tareas creadas anteriormente tengan más posibilidades de
ejecutarse antes y que las tareas posteriormente tengan más
posibilidades de ejecutarse después.

LongRunning Especifica que la tarea representa una operación de ejecución


prolongada.

AttachedToParent Especifica que una tarea debe crearse como un elemento


secundario asociado de la tarea actual, si existe. Para más
información, consulte Tareas secundarias asociadas y
desasociadas.

DenyChildAttach Especifica que, si una tarea interna especifica la opción


AttachedToParent , esa tarea no se convertirá en una tarea
secundaria asociada.

HideScheduler Especifica que el programador de tareas para tareas creadas


llamando a métodos como TaskFactory.StartNew o
Task<TResult>.ContinueWith desde dentro de una tarea
determinada es el programador predeterminado en lugar del
programador en el que se ejecuta esta tarea.

Las opciones pueden combinarse con una operación OR . En el ejemplo siguiente se muestra una tarea que tiene
las opciones LongRunning y PreferFairness.

var task3 = new Task(() => MyLongRunningMethod(),


TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Dim task3 = New Task(Sub() MyLongRunningMethod(),


TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()

Tareas, subprocesos y referencia cultural


Cada subproceso tiene asociada una referencia cultural asociada y una interfaz de usuario de referencia cultural,
que está definida por las propiedades Thread.CurrentCulture y Thread.CurrentUICulture, respectivamente. La
referencia cultural de un subproceso se usa en operaciones como dar formato, analizar, ordenar y comparar
cadenas. La referencia cultural de la interfaz de usuario de un subproceso se usa en la búsqueda de recursos. Por
lo general, a menos que especifique una referencia cultural predeterminada para todos los subprocesos de un
dominio de aplicación usando las propiedades CultureInfo.DefaultThreadCurrentCulture y
CultureInfo.DefaultThreadCurrentUICulture, la referencia cultural del sistema define la referencia cultural
predeterminada y la referencia cultural de la interfaz de usuario de un subproceso. Si establece explícitamente la
referencia cultural de un subproceso e inicia un nuevo subproceso, el nuevo subproceso no hereda la referencia
cultural del subproceso que realiza la llamada; en su lugar, su referencia cultural es la referencia cultural
predeterminada del sistema. El modelo de programación basado en tareas para las aplicaciones destinadas a
versiones de .NET Framework anteriores a .NET Framework 4.6 siguen este procedimiento.

IMPORTANT
Tenga en cuenta que la referencia cultural del subproceso que realiza la llamada como parte del contexto de una tarea se
aplica a las aplicaciones destinadas a .NET Framework 4.6, no a las que se ejecutan en .NET Framework 4.6. Puede elegir
como destino una versión determinada de .NET Framework al crear el proyecto en Visual Studio seleccionado esta versión
en la lista desplegable situada en la parte superior del cuadro de diálogo Nuevo proyecto o fuera de Visual Studio puede
usar el atributo TargetFrameworkAttribute. Para las aplicaciones destinadas a versiones de .NET Framework anteriores a .NET
Framework 4.6 o que no están destinadas a una versión específica de .NET Framework, la referencia cultural del subproceso
en el que se ejecuta sigue determinando la referencia cultural de la tarea.

A partir de las aplicaciones destinadas a .NET Framework 4.6, cada tarea hereda la referencia cultural del
subproceso que realiza la llamada, aunque la tarea se ejecute de forma asincrónica en un subproceso del grupo
de subprocesos.
Esto se muestra en el ejemplo siguiente. Usa el atributo TargetFrameworkAttribute para elegir .NET
Framework 4.6 como destino y cambia la referencia cultural actual de la aplicación a francés (Francia) o, si francés
(Francia) es la referencia cultural actual, a inglés (Estados Unidos). Después, invoca un delegado llamado
formatDelegate que devuelve algunos números con el formato de valores de moneda de la nueva referencia
cultural. Tenga en cuenta si el delegado se ejecuta como una tarea tanto de forma sincrónica como asincrónica,
devuelve el resultado esperado porque la tarea asincrónica hereda la referencia cultural del subproceso que
realiza la llamada.

using System;
using System.Globalization;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;

[assembly:TargetFramework(".NETFramework,Version=v4.6")]

public class Example


{

public static void Main()


{
decimal[] values = { 163025412.32m, 18905365.59m };
string formatString = "C2";
Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture
on thread {1}.\n",
CultureInfo.CurrentCulture.Name,

Thread.CurrentThread.ManagedThreadId);
foreach (var value in values)
output += String.Format("{0} ",
value.ToString(formatString));

output += Environment.NewLine;
return output;
};

Console.WriteLine("The example is running on thread {0}",


Thread.CurrentThread.ManagedThreadId);
// Make the current culture different from the system culture.
Console.WriteLine("The current culture is {0}",
CultureInfo.CurrentCulture.Name);
if (CultureInfo.CurrentCulture.Name == "fr-FR")
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
else
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");
Console.WriteLine("Changed the current culture to {0}.\n",
CultureInfo.CurrentCulture.Name);

// Execute the delegate synchronously.


Console.WriteLine("Executing the delegate synchronously:");
Console.WriteLine(formatDelegate());

// Call an async delegate to format the values using one format string.
Console.WriteLine("Executing a task asynchronously:");
var t1 = Task.Run(formatDelegate);
Console.WriteLine(t1.Result);

Console.WriteLine("Executing a task synchronously:");


var t2 = new Task<String>(formatDelegate);
t2.RunSynchronously();
Console.WriteLine(t2.Result);
}
}
// The example displays the following output:
// The example is running on thread 1
// The current culture is en-US
// Changed the current culture to fr-FR.
//
// Executing the delegate synchronously:
// Formatting using the fr-FR culture on thread 1.
// 163 025 412,32 € 18 905 365,59 €
//
// Executing a task asynchronously:
// Formatting using the fr-FR culture on thread 3.
// 163 025 412,32 € 18 905 365,59 €
//
// Executing a task synchronously:
// Formatting using the fr-FR culture on thread 1.
// 163 025 412,32 € 18 905 365,59 €
// If the TargetFrameworkAttribute statement is removed, the example
// displays the following output:
// The example is running on thread 1
// The current culture is en-US
// Changed the current culture to fr-FR.
//
// Executing the delegate synchronously:
// Formatting using the fr-FR culture on thread 1.
// 163 025 412,32 € 18 905 365,59 €
//
// Executing a task asynchronously:
// Formatting using the en-US culture on thread 3.
// $163,025,412.32 $18,905,365.59
//
// Executing a task synchronously:
// Formatting using the fr-FR culture on thread 1.
// 163 025 412,32 € 18 905 365,59 €

Imports System.Globalization
Imports System.Runtime.Versioning
Imports System.Threading
Imports System.Threading.Tasks

<Assembly: TargetFramework(".NETFramework,Version=v4.6")>

Module Example
Public Sub Main()
Dim values() As Decimal = {163025412.32d, 18905365.59d}
Dim formatString As String = "C2"
Dim formatDelegate As Func(Of String) = Function()
Dim output As String = String.Format("Formatting using
the {0} culture on thread {1}.",
CultureInfo.CurrentCulture.Name,

Thread.CurrentThread.ManagedThreadId)
output += Environment.NewLine
For Each value In values
output += String.Format("{0} ",
value.ToString(formatString))
Next
output += Environment.NewLine
Return output
End Function

Console.WriteLine("The example is running on thread {0}",


Thread.CurrentThread.ManagedThreadId)
' Make the current culture different from the system culture.
Console.WriteLine("The current culture is {0}",
CultureInfo.CurrentCulture.Name)
If CultureInfo.CurrentCulture.Name = "fr-FR" Then
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US")
Else
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR")
End If
Console.WriteLine("Changed the current culture to {0}.",
CultureInfo.CurrentCulture.Name)
Console.WriteLine()

' Execute the delegate synchronously.


Console.WriteLine("Executing the delegate synchronously:")
Console.WriteLine(formatDelegate())

' Call an async delegate to format the values using one format string.
Console.WriteLine("Executing a task asynchronously:")
Dim t1 = Task.Run(formatDelegate)
Console.WriteLine(t1.Result)

Console.WriteLine("Executing a task synchronously:")


Dim t2 = New Task(Of String)(formatDelegate)
t2.RunSynchronously()
Console.WriteLine(t2.Result)
End Sub
End Module
' The example displays the following output:
' The example is running on thread 1
' The current culture is en-US
' Changed the current culture to fr-FR.
'
' Executing the delegate synchronously:
' Formatting Imports the fr-FR culture on thread 1.
' 163 025 412,32 € 18 905 365,59 €
'
' Executing a task asynchronously:
' Formatting Imports the fr-FR culture on thread 3.
' 163 025 412,32 € 18 905 365,59 €
'
' Executing a task synchronously:
' Formatting Imports the fr-FR culture on thread 1.
' 163 025 412,32 € 18 905 365,59 €
' If the TargetFrameworkAttribute statement is removed, the example
' displays the following output:
' The example is running on thread 1
' The current culture is en-US
' Changed the current culture to fr-FR.
'
' Executing the delegate synchronously:
' Formatting using the fr-FR culture on thread 1.
' 163 025 412,32 € 18 905 365,59 €
'
' Executing a task asynchronously:
' Formatting using the en-US culture on thread 3.
' Formatting using the en-US culture on thread 3.
' $163,025,412.32 $18,905,365.59
'
' Executing a task synchronously:
' Formatting using the fr-FR culture on thread 1.
' 163 025 412,32 € 18 905 365,59 €

Si utiliza Visual Studio, puede omitir el atributo TargetFrameworkAttribute y seleccionar en su lugar .NET
Framework 4.6 como destino al crear el proyecto en el cuadro de diálogo Nuevo proyecto .
Para que la salida refleje el comportamiento de las aplicaciones destinadas a versiones de .NET Framework
anteriores a .NET Framework 4.6, quite el atributo TargetFrameworkAttribute del código fuente. La salida reflejará
las convenciones de formato de la referencia cultural predeterminada del sistema, no la referencia cultural del
subproceso que realiza la llamada.
Para obtener más información sobre las tareas asincrónicas y la referencia cultural, consulte la sección "Referencia
cultural y operaciones asincrónicas basadas en tareas" en el tema CultureInfo.

Crear continuaciones de tareas


Los métodos Task.ContinueWith y Task<TResult>.ContinueWith permiten especificar una tarea que se iniciará
cuando finalice la tarea anterior. Al delegado de la tarea de continuación se le pasa una referencia a la tarea
antecedente para que pueda examinar el estado de dicha tarea y, al recuperar el valor de la propiedad
Task<TResult>.Result, puede usar la salida del antecedente como entrada de la continuación.
En el ejemplo siguiente, la tarea getData se inicia llamando al método TaskFactory.StartNew<TResult>
(Func<TResult>). La tarea processData se inicia automáticamente cuando finaliza getData y displayData se
inicia cuando finaliza processData . getData genera una matriz de enteros, accesible a la tarea processData
mediante la propiedad getData de la tarea Task<TResult>.Result. La tarea processData procesa dicha matriz y
devuelve un resultado cuyo tipo se deduce del tipo de valor devuelto de la expresión lambda pasada al método
Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>). La tarea displayData se ejecuta
automáticamente cuando finaliza processData y el objeto Tuple<T1,T2,T3> devuelto por la expresión lambda
processData es accesible para la tarea displayData mediante la propiedad processData de la tarea
Task<TResult>.Result. La tarea displayData toma el resultado de la tarea processData y genera un resultado cuyo
tipo se deduce de forma similar y que se pone a disposición del programa en la propiedad Result.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var getData = Task.Factory.StartNew(() => {
Random rnd = new Random();
int[] values = new int[100];
for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
values[ctr] = rnd.Next();

return values;
} );
var processData = getData.ContinueWith((x) => {
int n = x.Result.Length;
long sum = 0;
double mean;

for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)


sum += x.Result[ctr];

mean = sum / (double) n;


return Tuple.Create(n, sum, mean);
} );
var displayData = processData.ContinueWith((x) => {
return String.Format("N={0:N0}, Total = {1:N0}, Mean =
{2:N2}",
x.Result.Item1, x.Result.Item2,
x.Result.Item3);
} );
Console.WriteLine(displayData.Result);
}
}
// The example displays output similar to the following:
// N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim getData = Task.Factory.StartNew(Function()
Dim rnd As New Random()
Dim values(99) As Integer
For ctr = 0 To values.GetUpperBound(0)
values(ctr) = rnd.Next()
Next
Return values
End Function)
Dim processData = getData.ContinueWith(Function(x)
Dim n As Integer = x.Result.Length
Dim sum As Long
Dim mean As Double

For ctr = 0 To x.Result.GetUpperBound(0)


sum += x.Result(ctr)
Next
mean = sum / n
Return Tuple.Create(n, sum, mean)
End Function)
Dim displayData = processData.ContinueWith(Function(x)
Return String.Format("N={0:N0}, Total = {1:N0}, Mean =
{2:N2}",
x.Result.Item1, x.Result.Item2,
x.Result.Item3)
End Function)
Console.WriteLine(displayData.Result)
End Sub
End Module
' The example displays output like the following:
' N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Dado que Task.ContinueWith es un método de instancia, puede encadenar llamadas a métodos en lugar de crear
instancias de un objeto Task<TResult> para cada tarea antecedente. El ejemplo siguiente es funcionalmente
idéntico al anterior, con la diferencia de que encadena llamadas al método Task.ContinueWith. Observe que el
objeto Task<TResult> devuelto por la cadena de llamadas al método es la tarea final de continuación.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var displayData = Task.Factory.StartNew(() => {
Random rnd = new Random();
int[] values = new int[100];
for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
values[ctr] = rnd.Next();

return values;
} ).
ContinueWith((x) => {
int n = x.Result.Length;
long sum = 0;
double mean;

for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)


sum += x.Result[ctr];

mean = sum / (double) n;


return Tuple.Create(n, sum, mean);
} ).
ContinueWith((x) => {
return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
x.Result.Item1, x.Result.Item2,
x.Result.Item3);
} );
Console.WriteLine(displayData.Result);
}
}
// The example displays output similar to the following:
// N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim displayData = Task.Factory.StartNew(Function()
Dim rnd As New Random()
Dim values(99) As Integer
For ctr = 0 To values.GetUpperBound(0)
values(ctr) = rnd.Next()
Next
Return values
End Function). _
ContinueWith(Function(x)
Dim n As Integer = x.Result.Length
Dim sum As Long
Dim mean As Double

For ctr = 0 To x.Result.GetUpperBound(0)


sum += x.Result(ctr)
Next
mean = sum / n
Return Tuple.Create(n, sum, mean)
End Function). _
ContinueWith(Function(x)
Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
x.Result.Item1, x.Result.Item2,
x.Result.Item3)
End Function)
Console.WriteLine(displayData.Result)
End Sub
End Module
' The example displays output like the following:
' N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Los métodos ContinueWhenAll y ContinueWhenAny permiten continuar a partir de varias tareas.


Para más información, consulte Encadenar tareas mediante tareas de continuación.

Crear tareas secundarias desasociadas


Cuando el código de usuario que se está ejecutando en una tarea crea una nueva tarea y no especifica la opción
AttachedToParent, la nueva tarea no se sincroniza con la tarea principal de ninguna manera especial. Este tipo de
tarea no sincronizada se denomina tarea anidada desasociada o tarea secundaria desasociada. En el siguiente
ejemplo se muestra una tarea que crea una tarea secundaria desasociada.

var outer = Task.Factory.StartNew(() =>


{
Console.WriteLine("Outer task beginning.");

var child = Task.Factory.StartNew(() =>


{
Thread.SpinWait(5000000);
Console.WriteLine("Detached task completed.");
});
});

outer.Wait();
Console.WriteLine("Outer task completed.");
// The example displays the following output:
// Outer task beginning.
// Outer task completed.
// Detached task completed.
Dim outer = Task.Factory.StartNew(Sub()
Console.WriteLine("Outer task beginning.")
Dim child = Task.Factory.StartNew(Sub()
Thread.SpinWait(5000000)
Console.WriteLine("Detached task
completed.")
End Sub)
End Sub)
outer.Wait()
Console.WriteLine("Outer task completed.")
' The example displays the following output:
' Outer task beginning.
' Outer task completed.
' Detached child completed.

Observe que la tarea primaria no espera a que la tarea secundaria desasociada se complete.

Crear tareas secundarias


Cuando el código de usuario que se está ejecutando en una tarea crea una tarea con la opción AttachedToParent,
la nueva tarea se conoce como una tarea secundaria asociada de la tarea principal. Puede usar la opción
AttachedToParent para expresar el paralelismo de tareas estructurado, ya que la tarea primaria espera
implícitamente a que todas las tareas secundarias asociadas finalicen. En el ejemplo siguiente se muestra una
tarea principal que crea diez tareas secundarias adjuntas. Observe que aunque en el ejemplo se llame al método
Task.Wait para esperar a que la tarea primaria finalice, no tiene que esperar explícitamente a que se completen las
tareas secundarias asociadas.
using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Parent task beginning.");
for (int ctr = 0; ctr < 10; ctr++) {
int taskNo = ctr;
Task.Factory.StartNew((x) => {
Thread.SpinWait(5000000);
Console.WriteLine("Attached child #{0} completed.",
x);
},
taskNo, TaskCreationOptions.AttachedToParent);
}
});

parent.Wait();
Console.WriteLine("Parent task completed.");
}
}
// The example displays output like the following:
// Parent task beginning.
// Attached child #9 completed.
// Attached child #0 completed.
// Attached child #8 completed.
// Attached child #1 completed.
// Attached child #7 completed.
// Attached child #2 completed.
// Attached child #6 completed.
// Attached child #3 completed.
// Attached child #5 completed.
// Attached child #4 completed.
// Parent task completed.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Parent task beginning.")
For ctr As Integer = 0 To 9
Dim taskNo As Integer = ctr
Task.Factory.StartNew(Sub(x)
Thread.SpinWait(5000000)
Console.WriteLine("Attached
child #{0} completed.",
x)
End Sub,
taskNo,
TaskCreationOptions.AttachedToParent)
Next
End Sub)
parent.Wait()
Console.WriteLine("Parent task completed.")
End Sub
End Module
' The example displays output like the following:
' Parent task beginning.
' Attached child #9 completed.
' Attached child #0 completed.
' Attached child #8 completed.
' Attached child #1 completed.
' Attached child #7 completed.
' Attached child #2 completed.
' Attached child #6 completed.
' Attached child #3 completed.
' Attached child #5 completed.
' Attached child #4 completed.
' Parent task completed.

Una tarea principal puede usar la opción TaskCreationOptions.DenyChildAttach para evitar que otras tareas se
asocien a la tarea primaria. Para más información, consulte Tareas secundarias asociadas y desasociadas.

Esperar hasta que las tareas finalicen


Los tipos System.Threading.Tasks.Task y System.Threading.Tasks.Task<TResult> proporcionan varias sobrecargas
de los métodos Task.Wait que permiten esperar a que finalice una tarea. Además, las sobrecargas del método
Task.WaitAll estático y del método Task.WaitAny permiten esperar a que finalicen alguna o todas las tareas de una
matriz de tareas.
Normalmente, una tarea se espera por una de estas razones:
El subproceso principal depende del resultado final que se calcula mediante una tarea.
Hay que controlar las excepciones que pueden producirse en la tarea.
La aplicación puede finalizar antes de que todas las tareas hayan terminado de ejecutarse. Por ejemplo, las
aplicaciones de consola finalizarán en cuanto se ejecute todo el código sincrónico en Main (el punto de
entrada de la aplicación).
En el siguiente ejemplo se muestra el modelo básico donde el control de excepciones no está implicado.
Task[] tasks = new Task[3]
{
Task.Factory.StartNew(() => MethodA()),
Task.Factory.StartNew(() => MethodB()),
Task.Factory.StartNew(() => MethodC())
};

//Block until all tasks complete.


Task.WaitAll(tasks);

// Continue on this thread...

Dim tasks() =
{
Task.Factory.StartNew(Sub() MethodA()),
Task.Factory.StartNew(Sub() MethodB()),
Task.Factory.StartNew(Sub() MethodC())
}

' Block until all tasks complete.


Task.WaitAll(tasks)

' Continue on this thread...

Para un ejemplo que muestra el control de excepciones, consulte Control de excepciones.


Algunas sobrecargas permiten especificar un tiempo de espera, mientras que otras toman un objeto
CancellationToken adicional como parámetro de entrada, de modo que la espera puede cancelarse mediante
programación o en respuesta a los datos proporcionados por el usuario.
Cuando se espera a una tarea, se espera implícitamente a todos los elementos secundarios de esa tarea que se
crearon con la opción TaskCreationOptions.AttachedToParent. Task.Wait devuelve un valor inmediatamente si la
tarea ya se ha completado. Un método Task.Wait producirá las tareas generadas por una tarea incluso si se llama
a este método Task.Wait una vez completada la tarea.

Componer tareas
Las clases Task y Task<TResult> proporcionan varios métodos que pueden ayudar a componer varias tareas para
implementar patrones comunes y para mejorar el uso de las características de lenguaje asincrónicas
proporcionadas por C#, Visual Basic y F#. En esta sección se describen los métodos WhenAll, WhenAny, Delay y
FromResult.
Task.WhenAll
El método Task.WhenAll espera de forma asincrónica a que finalicen varios Task objetos Task<TResult>.
Proporciona versiones sobrecargadas que permiten esperar a conjuntos no uniformes de tareas. Por ejemplo,
puede esperar a que varios objetos Task y Task<TResult> se completen de una llamada al método.
Task.WhenAny
El método Task.WhenAny espera de forma asincrónica a que finalice uno de varios Task objetos Task<TResult>.
Como en el método Task.WhenAll, este método proporciona versiones sobrecargadas que permiten esperar a
conjuntos no uniformes de tareas. El método WhenAny es especialmente útil en los siguientes escenarios.
Operaciones redundantes. Considere un algoritmo o una operación que pueda realizarse de muchas
maneras. Puede usar el método WhenAny para seleccionar la operación que finaliza primero y luego
cancelar las operaciones restantes.
Operaciones intercaladas. Puede iniciar varias operaciones, todas las cuales deben finalizar y utilizar el
método WhenAny para procesar los resultados cuando finalice cada operación. Finalizada una operación,
puede iniciar una o más tareas adicionales.
Operaciones limitadas. Puede usar el método WhenAny para extender el escenario anterior limitando el
número de operaciones simultáneas.
Operaciones que han expirado. Puede usar el método WhenAny para seleccionar entre una o más tareas y
una tarea que termina después de un tiempo concreto, como una tarea devuelta por el método Delay. El
método Delay se describe en la sección siguiente.
Task.Delay
El método Task.Delay produce un objeto Task que finaliza tras el tiempo especificado. Puede usar este método
para crear bucles que sondeen ocasionalmente en busca de datos, introduzcan finales de tiempo de espera,
retrasen el control de los datos proporcionados por el usuario durante un tiempo predeterminado, etc.
Task(T ).FromResult
Mediante el método Task.FromResult, puede crear un objeto Task<TResult> que contenga un resultado
previamente calculado. Este método es útil cuando se realiza una operación asincrónica que devuelve un objeto
Task<TResult> y el resultado de ese objeto Task<TResult> ya se ha calculado. Para obtener un ejemplo en el que
se usa FromResult para recuperar los resultados de las operaciones de descarga asincrónica que se retienen en
caché, vea Procedimiento para crear tareas precalculadas.

Controlar excepciones en tareas


Cuando una tarea produce una o más excepciones, las excepciones se encapsulan en una excepción
AggregateException. Esa excepción se propaga de nuevo al subproceso que se combina con la tarea, que
normalmente es el subproceso que está esperando a que la tarea termine o al subproceso que tiene acceso a la
propiedad Result. Este comportamiento sirve para aplicar la directiva de .NET Framework por la que, de manera
predeterminada, todas las excepciones no controladas deben terminar el proceso. El código de llamada puede
controlar las excepciones con cualquiera de los siguientes elementos del bloque try / catch :
El método Wait
El método WaitAll
El método WaitAny
La propiedad Result
El subproceso de unión también puede controlar excepciones; para ello, obtiene acceso a la propiedad Exception
antes de que la tarea se recolecte como elemento no utilizado. Al obtener acceso a esta propiedad, impide que la
excepción no controlada desencadene el comportamiento de propagación de la excepción que termina el proceso
cuando el objeto ha finalizado.
Para más información sobre las excepciones y las tareas, consulte Control de excepciones.

Cancelar tareas
La clase Task admite la cancelación cooperativa y está completamente integrada con las clases
System.Threading.CancellationTokenSource y System.Threading.CancellationToken, que se presentaron en .NET
Framework versión 4. Muchos de los constructores de la clase System.Threading.Tasks.Task toman un objeto
CancellationToken como parámetro de entrada. Muchas de las sobrecargas de StartNew y Run incluyen también
un parámetro CancellationToken.
Puede crear el token y emitir la solicitud de cancelación posteriormente usando la clase CancellationTokenSource.
A continuación, debe pasar el token a Task como argumento y hacer referencia al mismo token también en el
delegado de usuario, que se encarga de responder a una solicitud de cancelación.
Para más información, vea Cancelación de tareas y Cómo: Cancelar una tarea y sus elementos secundarios.

Clase TaskFactory
La clase TaskFactory proporciona métodos estáticos que encapsulan algunos modelos comunes de creación e
inicio de tareas y tareas de continuación.
El modelo más común es StartNew, que crea e inicia una tarea en una sola instrucción.
Cuando cree tareas de continuación a partir de varios antecedentes, use el método ContinueWhenAll o el
métodoContinueWhenAny o sus equivalentes en la clase Task<TResult>. Para más información, consulte
Encadenar tareas mediante tareas de continuación.
Para encapsular los métodos BeginX y EndX del modelo de programación asincrónica en una instancia de
Task o Task<TResult>, use los métodos FromAsync. Para más información, consulte TPL y la programación
asincrónica tradicional de .NET Framework.
Al objeto TaskFactory predeterminado se puede tener acceso como propiedad estática de la clase Task o de la
clase Task<TResult>. También pueden crearse directamente instancias de TaskFactory y especificar varias opciones
entre las que se incluyan las opciones CancellationToken, TaskCreationOptions, TaskContinuationOptions o
TaskScheduler. Cualquier opción que se especifique al crear el generador de tareas se aplicará a todas las tareas
que este generador cree, a menos que Task se cree usando la enumeración TaskCreationOptions, en cuyo caso las
opciones de la tarea reemplazarán a las del generador de tareas.

Tareas sin delegados


En algunos casos, es posible que desee usar un objeto Task para encapsular alguna operación asincrónica
ejecutada por un componente externo en lugar de su propio usuario delegado. Si la operación se basa en el
patrón Begin/End del modelo de programación asincrónica, puede usar los métodos FromAsync. Si no es este el
caso, puede usar el objeto TaskCompletionSource<TResult> para encapsular la operación en una tarea y, de este
modo, aprovechar algunas de las ventajas de programación de Task, como por ejemplo, su compatibilidad con la
propagación de excepciones y el uso de continuaciones. Para obtener más información, vea
TaskCompletionSource<TResult>.

Programadores personalizados
La mayoría de los desarrolladores de aplicaciones o bibliotecas no prestan atención al procesador en el que se
ejecuta la tarea, al modo en que la tarea sincroniza su trabajo con otras tareas o al modo en que se programa la
tarea en el objeto System.Threading.ThreadPool. Solo necesitan que la ejecución en el equipo host sea lo más
eficaz posible. Si necesita tener un control más minucioso sobre los detalles de programación, la biblioteca TPL
permite configurar algunos valores del programador de tareas predeterminado e incluso permite proporcionar
un programador personalizado. Para obtener más información, vea TaskScheduler.

Estructuras de datos relacionados


TPL tiene varios tipos públicos nuevos que resultan útiles tanto en escenarios en paralelo como en escenarios
secuenciales. Entre ellos, se incluyen diversas clases de colecciones multiproceso rápidas y escalables del espacio
de nombres System.Collections.Concurrent y varios tipos nuevos de sincronización, como
System.Threading.Semaphore y System.Threading.ManualResetEventSlim, que resultan más eficaces que sus
predecesores en tipos concretos de cargas de trabajo. Otros tipos nuevos de .NET Framework versión 4, como
System.Threading.Barrier y System.Threading.SpinLock, proporcionan una funcionalidad que no estaba disponible
en versiones anteriores. Para más información, consulte Estructuras de datos para la programación paralela.

Tipos de tarea personalizados


Se recomienda no heredar de System.Threading.Tasks.Task ni de System.Threading.Tasks.Task<TResult>. En su
lugar, se recomienda usar la propiedad AsyncState para asociar los datos adicionales o el estado a un objeto Task
o Task<TResult>. También puede usar métodos de extensión para extender la funcionalidad de las clases Task y
Task<TResult>. Para más información sobre los métodos de extensión, consulte Métodos de extensión (C#) y
Métodos de extensión (Visual Basic).
Si debe heredar de Task o Task<TResult>, no puede usar Run, o las clases System.Threading.Tasks.TaskFactory,
System.Threading.Tasks.TaskFactory<TResult> o System.Threading.Tasks.TaskCompletionSource<TResult>, para
crear instancias del tipo de tarea personalizada porque estos mecanismos solo crean objetos Task y objetos
Task<TResult>. Además, no puede usar los mecanismos de continuación de tarea proporcionados por Task,
Task<TResult>, TaskFactory y TaskFactory<TResult> para crear instancias del tipo de tarea personalizada porque
también estos mecanismos crean solo objetos Task y Task<TResult>.

Temas relacionados
T IT L E DESC RIP C IÓ N

Encadenar tareas mediante tareas de continuación Describe el funcionamiento de las continuaciones.

Attached and Detached Child Tasks (Tareas secundarias Describe la diferencia entre las tareas secundarias asociadas y
asociadas y desasociadas) desasociadas.

Cancelación de tareas Describe la compatibilidad con la cancelación que está


integrada en el objeto Task.

Control de excepciones Describe cómo se controlan excepciones en subprocesos


simultáneos.

Cómo: Usar Parallel.Invoke para ejecutar operaciones en Describe cómo usar Invoke.
paralelo

Cómo: Devolver un valor a partir de una tarea Describe cómo devolver valores de tareas.

Cómo: Cancelar una tarea y sus elementos secundarios Describe cómo cancelar tareas.

Cómo: Crear tareas precalculadas Describe cómo utilizar el método Task.FromResult para
recuperar los resultados de las operaciones asincrónicas de
descarga que se retienen en una memoria caché.

Cómo: Recorrer un árbol binario con tareas en paralelo Describe cómo utilizar tareas para atravesar un árbol binario.

Cómo: Desencapsular una tarea anidada Demuestra cómo utilizar el método de extensión Unwrap.

Paralelismo de datos Describe cómo usar For y ForEach para crear bucles paralelos
sobre los datos.

Programación en paralelo Nodo de nivel superior de la programación en paralelo de


.NET Framework.

Vea también
Programación en paralelo
Ejemplos de programación en paralelo con .NET Core y .NET Standard
Encadenamiento de tareas mediante tareas de
continuación
16/09/2020 • 39 minutes to read • Edit Online

En la programación asincrónica, es habitual que una operación asincrónica, al finalizar, invoque una segunda
operación. Las continuaciones permiten a las operaciones descendentes usar los resultados de la primera
operación. Tradicionalmente, las continuaciones se han realizado mediante métodos de devolución de llamada. En
la biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas), se proporciona la
misma funcionalidad mediante tareas de continuación. Una tarea de continuación (también conocida
simplemente como una continuación) es una tarea asincrónica invocada por otra tarea, conocida como
antecedente, cuando esta finaliza.
A pesar de que las continuaciones son relativamente fáciles de usar, resultan eficaces y flexibles. Por ejemplo, se
puede:
Pasar datos del antecedente a la continuación.
Especificar las condiciones precisas en las que se invoca o no se invoca la continuación.
Cancelar una continuación antes de que se inicie o de forma cooperativa mientras se ejecuta.
Proporcionar sugerencias sobre cómo debería programarse la continuación.
Invocar varias continuaciones desde el mismo antecedente.
Invocar una continuación cuando todos o alguno de los antecedentes finalicen.
Encadenar las continuaciones una tras otra hasta cualquier longitud arbitraria.
Usar una continuación para controlar las excepciones producidas por el antecedente.

Sobre las continuaciones


Una continuación es una tarea que se crea en el estado WaitingForActivation y que se activa automáticamente
cuando su tarea (o tareas) antecedente finaliza. Llamar a Task.Start en una continuación en código de usuario
produce una excepción System.InvalidOperationException .
Una continuación es en sí misma un Task y no bloquea el subproceso en el que se inicia. Para un bloqueo, llame al
método Task.Wait hasta que finalice la tarea de continuación.

Creación de una continuación para un solo antecedente


Se puede crear una continuación que se ejecute una vez completado su antecedente mediante una llamada al
método Task.ContinueWith . En el ejemplo siguiente se muestra el patrón básico (para mayor claridad, se omite el
control de excepciones). En él se ejecuta una tarea antecedente, taskA , que devuelve un objeto DayOfWeek que
indica el nombre del día actual de la semana. Cuando finaliza el antecedente, este se pasa a la tarea de
continuación, continuation , y se muestra una cadena que incluye su resultado.

NOTE
En los ejemplos de C# de este artículo se usa el modificador async en el método Main . Esa característica está disponible
en C# 7.1 y versiones posteriores. En las versiones anteriores se genera CS5001 al compilar este código de ejemplo. Tendrá
que establecer la versión del lenguaje en C# 7.1 o posterior. Puede obtener información sobre cómo configurar la versión
del lenguaje en el artículo sobre configuración de la versión del lenguaje.
using System;
using System.Threading.Tasks;

public class SimpleExample


{
public static async Task Main()
{
// Declare, assign, and start the antecedent task.
Task<DayOfWeek> taskA = Task.Run(() => DateTime.Today.DayOfWeek);

// Execute the continuation when the antecedent finishes.


await taskA.ContinueWith(antecedent => Console.WriteLine($"Today is {antecedent.Result}."));
}
}
// The example displays the following output:
// Today is Monday.

Imports System.Threading.Tasks

Module Example
Public Sub Main()
' Execute the antecedent.
Dim taskA As Task(Of DayOfWeek) = Task.Run(Function() DateTime.Today.DayOfWeek)

' Execute the continuation when the antecedent finishes.


Dim continuation As Task = taskA.ContinueWith(Sub(antecedent)
Console.WriteLine("Today is {0}.",
antecedent.Result)
End Sub)
continuation.Wait()
End Sub
End Module
' The example displays output like the following output:
' Today is Monday.

Creación de una continuación para varios antecedentes


También puede crear una continuación que se ejecutará cuando se haya completado una tarea o un grupo de
tareas. Para ejecutar una continuación cuando se hayan completado todas las tareas antecedentes, llame al
método estático Shared ( Task.WhenAll en Visual Basic) o al método de instancia TaskFactory.ContinueWhenAll .
Para ejecutar una continuación cuando cualquiera de las tareas antecedentes se haya completado, llame al
método estático Shared ( Task.WhenAny en Visual Basic) o al método de instancia TaskFactory.ContinueWhenAny
.
Las llamadas a las sobrecargas Task.WhenAll y Task.WhenAny no bloquean el subproceso que realiza la llamada.
Sin embargo, se suele llamar a todos menos a los métodos Task.WhenAll(IEnumerable<Task>) y
Task.WhenAll(Task[]) para recuperar la propiedad Task<TResult>.Result devuelta, que bloquea el subproceso que
realiza la llamada.
En el ejemplo siguiente se llama al método Task.WhenAll(IEnumerable<Task>) para crear una tarea de
continuación que refleje los resultados de sus diez tareas antecedentes. Cada tarea antecedente eleva al cuadrado
un valor de índice que varía entre uno y diez. Si los antecedentes se completan correctamente (su propiedad
Task.Status es TaskStatus.RanToCompletion), la propiedad Task<TResult>.Result de la continuación es una matriz
de los valores Task<TResult>.Result devueltos por cada antecedente. En el ejemplo se suman para calcular el total
de los cuadrados de todos los números entre uno y diez.
using System.Collections.Generic;
using System;
using System.Threading.Tasks;

public class WhenAllExample


{
public static async Task Main()
{
var tasks = new List<Task<int>>();
for (int ctr = 1; ctr <= 10; ctr++)
{
int baseValue = ctr;
tasks.Add(Task.Factory.StartNew(b => (int)b * (int)b, baseValue));
}

var results = await Task.WhenAll(tasks);

int sum = 0;
for (int ctr = 0; ctr <= results.Length - 1; ctr++)
{
var result = results[ctr];
Console.Write($"{result} {((ctr == results.Length - 1) ? "=" : "+")} ");
sum += result;
}

Console.WriteLine(sum);
}
}
// The example displays the similar output:
// 1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385

Imports System.Collections.Generic
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim tasks As New List(Of Task(Of Integer))()
For ctr As Integer = 1 To 10
Dim baseValue As Integer = ctr
tasks.Add(Task.Factory.StartNew(Function(b)
Dim i As Integer = CInt(b)
Return i * i
End Function, baseValue))
Next
Dim continuation = Task.WhenAll(tasks)

Dim sum As Long = 0


For ctr As Integer = 0 To continuation.Result.Length - 1
Console.Write("{0} {1} ", continuation.Result(ctr),
If(ctr = continuation.Result.Length - 1, "=", "+"))
sum += continuation.Result(ctr)
Next
Console.WriteLine(sum)
End Sub
End Module
' The example displays the following output:
' 1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385

Opciones de continuación
Cuando se crea una continuación de tarea única, se puede usar una sobrecarga ContinueWith que toma un valor
de enumeración System.Threading.Tasks.TaskContinuationOptions para especificar las condiciones en las que se
inicia la continuación. Por ejemplo, se puede especificar que la continuación únicamente se ejecute si el
antecedente se completa correctamente, o solo si se completa en un estado de error. Si la condición no es true
cuando el antecedente está listo para invocar la continuación, la continuación realiza la transición directamente al
estado TaskStatus.Canceled y, por tanto, no se puede iniciar.
Hay distintos métodos de continuación de varias tareas, como las sobrecargas del método
TaskFactory.ContinueWhenAll , que también incluyen un parámetro
System.Threading.Tasks.TaskContinuationOptions . Sin embargo, solo es válido un subconjunto de todos los
miembros de enumeración de System.Threading.Tasks.TaskContinuationOptions . Se pueden especificar valores
System.Threading.Tasks.TaskContinuationOptions que tengan equivalentes en la enumeración
System.Threading.Tasks.TaskCreationOptions , como TaskContinuationOptions.AttachedToParent,
TaskContinuationOptions.LongRunningy TaskContinuationOptions.PreferFairness. Si se especifica cualquiera de
las opciones NotOn o OnlyOn con una continuación de varias tareas, se producirá una excepción
ArgumentOutOfRangeException en tiempo de ejecución.
Para obtener más información sobre las opciones de continuación de tarea, consulte el tema
TaskContinuationOptions .

Paso de datos a una continuación


El método Task.ContinueWith pasa una referencia al antecedente al delegado de usuario de la continuación como
argumento. Si el antecedente es un objeto System.Threading.Tasks.Task<TResult> y la tarea se ejecutó hasta que
se completó, la continuación puede luego tener acceso a la propiedad Task<TResult>.Result de la tarea.
La propiedad Task<TResult>.Result se bloquea hasta que se completa la tarea. Sin embargo, si la tarea se canceló
o produjo errores y se intenta tener acceso a la propiedad Result , se producirá una excepción
AggregateException . Puede evitar este problema con la opción OnlyOnRanToCompletion , tal como se muestra
en el ejemplo siguiente.

using System;
using System.Threading.Tasks;

public class ResultExample


{
public static async Task Main()
{
var task = Task.Run(
() =>
{
DateTime date = DateTime.Now;
return date.Hour > 17
? "evening"
: date.Hour > 12
? "afternoon"
: "morning";
});

await task.ContinueWith(
antecedent =>
{
Console.WriteLine($"Good {antecedent.Result}!");
Console.WriteLine($"And how are you this fine {antecedent.Result}?");
}, TaskContinuationOptions.OnlyOnRanToCompletion);
}
}
// The example displays the similar output:
// Good afternoon!
// And how are you this fine afternoon?
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim t = Task.Run(Function()
Dim dat As DateTime = DateTime.Now
If dat = DateTime.MinValue Then
Throw New ArgumentException("The clock is not working.")
End If

If dat.Hour > 17 Then


Return "evening"
Else If dat.Hour > 12 Then
Return "afternoon"
Else
Return "morning"
End If
End Function)
Dim c = t.ContinueWith(Sub(antecedent)
Console.WriteLine("Good {0}!",
antecedent.Result)
Console.WriteLine("And how are you this fine {0}?",
antecedent.Result)
End Sub, TaskContinuationOptions.OnlyOnRanToCompletion)
c.Wait()
End Sub
End Module
' The example displays output like the following:
' Good afternoon!
' And how are you this fine afternoon?

Si desea que la continuación se ejecute aunque el antecedente no finalice correctamente, debe protegerse de la
excepción. Un modo de hacerlo es probar la propiedad Task.Status del antecedente y solo intentar el acceso a la
propiedad Result si el estado no es Faulted ni Canceled. También puede examinar la propiedad Exception del
antecedente. Para más información, consulte Control de excepciones. En el ejemplo siguiente se modifica el
ejemplo anterior para tener acceso a la propiedad Task<TResult>.Result del antecedente, pero solo si su estado es
TaskStatus.RanToCompletion.
using System;
using System.Threading.Tasks;

public class ResultTwoExample


{
public static async Task Main() =>
await Task.Run(
() =>
{
DateTime date = DateTime.Now;
return date.Hour > 17
? "evening"
: date.Hour > 12
? "afternoon"
: "morning";
})
.ContinueWith(
antecedent =>
{
if (antecedent.Status == TaskStatus.RanToCompletion)
{
Console.WriteLine($"Good {antecedent.Result}!");
Console.WriteLine($"And how are you this fine {antecedent.Result}?");
}
else if (antecedent.Status == TaskStatus.Faulted)
{
Console.WriteLine(antecedent.Exception.GetBaseException().Message);
}
});
}
// The example displays output like the following:
// Good afternoon!
// And how are you this fine afternoon?
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim t = Task.Run(Function()
Dim dat As DateTime = DateTime.Now
If dat = DateTime.MinValue Then
Throw New ArgumentException("The clock is not working.")
End If

If dat.Hour > 17 Then


Return "evening"
Else If dat.Hour > 12 Then
Return "afternoon"
Else
Return "morning"
End If
End Function)
Dim c = t.ContinueWith(Sub(antecedent)
If t.Status = TaskStatus.RanToCompletion Then
Console.WriteLine("Good {0}!",
antecedent.Result)
Console.WriteLine("And how are you this fine {0}?",
antecedent.Result)
Else If t.Status = TaskStatus.Faulted Then
Console.WriteLine(t.Exception.GetBaseException().Message)
End If
End Sub)
End Sub
End Module
' The example displays output like the following:
' Good afternoon!
' And how are you this fine afternoon?

Cancelación de una continuación


La propiedad Task.Status de una continuación se establece en TaskStatus.Canceled en las situaciones siguientes:
Se produce un excepción OperationCanceledException en respuesta a una solicitud de cancelación. Como
sucede con cualquier tarea, si la excepción contiene el mismo token que se ha pasado a la continuación, se
trata como una confirmación de cancelación cooperativa.
A la continuación se le pasa un System.Threading.CancellationToken cuya propiedad IsCancellationRequested
es true . En este caso, la continuación no se inicia y realiza la transición al estado TaskStatus.Canceled .
La continuación nunca se ejecuta porque la condición establecida por su argumento TaskContinuationOptions
no se cumplió. Por ejemplo, si un antecedente entra en un estado TaskStatus.Faulted , su continuación —a la
que se le pasó la opción TaskContinuationOptions.NotOnFaulted — no se ejecutará, pero realizará la transición
al estado Canceled .
Si una tarea y su continuación representan dos partes de la misma operación lógica, se puede pasar el mismo
token de cancelación a ambas tareas, tal como se muestra en el ejemplo siguiente. Consta de un antecedente que
genera una lista de enteros divisibles por 33 y que pasa a la continuación. La continuación a su vez muestra la
lista. El antecedente y la continuación se ponen en pausa periódicamente durante intervalos aleatorios. Además,
un objeto System.Threading.Timer se usa para ejecutar el método Elapsed después de un intervalo de tiempo de
espera de cinco segundos. Este ejemplo llama al método CancellationTokenSource.Cancel, que hace que la tarea
que se ejecuta actualmente llame al método CancellationToken.ThrowIfCancellationRequested. Que el método
CancellationTokenSource.Cancel se invoque cuando se está ejecutando el antecedente o su continuación depende
de la duración de las pausas generadas de forma aleatoria. Si se cancela el antecedente, la continuación no se
iniciará. Si no se cancela el antecedente, el token aún puede usarse para cancelar la continuación.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

public class CancellationExample


{
static readonly Random s_random = new Random((int)DateTime.Now.Ticks);

public static async Task Main()


{
using var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
var timer = new Timer(Elapsed, cts, 5000, Timeout.Infinite);

var task = Task.Run(


async () =>
{
var product33 = new List<int>();
for (int index = 1; index < short.MaxValue; index++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("\nCancellation requested in antecedent...\n");
token.ThrowIfCancellationRequested();
}
if (index % 2000 == 0)
{
int delay = s_random.Next(16, 501);
await Task.Delay(delay);
}
if (index % 33 == 0)
{
product33.Add(index);
}
}

return product33.ToArray();
}, token);

Task<double> continuation = task.ContinueWith(


async antecedent =>
{
Console.WriteLine("Multiples of 33:\n");
int[] array = antecedent.Result;
for (int index = 0; index < array.Length; index++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("\nCancellation requested in continuation...\n");
token.ThrowIfCancellationRequested();
}
if (index % 100 == 0)
{
int delay = s_random.Next(16, 251);
await Task.Delay(delay);
}

Console.Write($"{array[index]:N0}{(index != array.Length - 1 ? ", " : "")}");

if (Console.CursorLeft >= 74)


{
Console.WriteLine();
}
}
Console.WriteLine();
return array.Average();
}, token).Unwrap();
}, token).Unwrap();

try
{
await task;
double result = await continuation;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

Console.WriteLine("\nAntecedent Status: {0}", task.Status);


Console.WriteLine("Continuation Status: {0}", continuation.Status);
}

static void Elapsed(object state)


{
if (state is CancellationTokenSource cts)
{
cts.Cancel();
Console.WriteLine("\nCancellation request issued...\n");
}
}
}
// The example displays the similar output:
// Multiples of 33:
//
// 33, 66, 99, 132, 165, 198, 231, 264, 297, 330, 363, 396, 429, 462, 495, 528,
// 561, 594, 627, 660, 693, 726, 759, 792, 825, 858, 891, 924, 957, 990, 1,023,
// 1,056, 1,089, 1,122, 1,155, 1,188, 1,221, 1,254, 1,287, 1,320, 1,353, 1,386,
// 1,419, 1,452, 1,485, 1,518, 1,551, 1,584, 1,617, 1,650, 1,683, 1,716, 1,749,
// 1,782, 1,815, 1,848, 1,881, 1,914, 1,947, 1,980, 2,013, 2,046, 2,079, 2,112,
// 2,145, 2,178, 2,211, 2,244, 2,277, 2,310, 2,343, 2,376, 2,409, 2,442, 2,475,
// 2,508, 2,541, 2,574, 2,607, 2,640, 2,673, 2,706, 2,739, 2,772, 2,805, 2,838,
// 2,871, 2,904, 2,937, 2,970, 3,003, 3,036, 3,069, 3,102, 3,135, 3,168, 3,201,
// 3,234, 3,267, 3,300, 3,333, 3,366, 3,399, 3,432, 3,465, 3,498, 3,531, 3,564,
// 3,597, 3,630, 3,663, 3,696, 3,729, 3,762, 3,795, 3,828, 3,861, 3,894, 3,927,
// 3,960, 3,993, 4,026, 4,059, 4,092, 4,125, 4,158, 4,191, 4,224, 4,257, 4,290,
// 4,323, 4,356, 4,389, 4,422, 4,455, 4,488, 4,521, 4,554, 4,587, 4,620, 4,653,
// 4,686, 4,719, 4,752, 4,785, 4,818, 4,851, 4,884, 4,917, 4,950, 4,983, 5,016,
// 5,049, 5,082, 5,115, 5,148, 5,181, 5,214, 5,247, 5,280, 5,313, 5,346, 5,379,
// 5,412, 5,445, 5,478, 5,511, 5,544, 5,577, 5,610, 5,643, 5,676, 5,709, 5,742,
// Cancellation request issued...
//
// 5,775,
// Cancellation requested in continuation...
//
// The operation was canceled.
//
// Antecedent Status: RanToCompletion
// Continuation Status: Canceled

Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim rnd As New Random()
Dim lockObj As New Object()
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
Dim timer As New Timer(AddressOf Elapsed, cts, 5000, Timeout.Infinite)

Dim t = Task.Run(Function()
Dim product33 As New List(Of Integer)()
For ctr As Integer = 1 To Int16.MaxValue
' Check for cancellation.
If token.IsCancellationRequested Then
Console.WriteLine("\nCancellation requested in antecedent...\n")
token.ThrowIfCancellationRequested()
End If
' Introduce a delay.
If ctr Mod 2000 = 0 Then
Dim delay As Integer
SyncLock lockObj
delay = rnd.Next(16, 501)
End SyncLock
Thread.Sleep(delay)
End If

' Determine if this is a multiple of 33.


If ctr Mod 33 = 0 Then product33.Add(ctr)
Next
Return product33.ToArray()
End Function, token)

Dim continuation = t.ContinueWith(Sub(antecedent)


Console.WriteLine("Multiples of 33:" + vbCrLf)
Dim arr = antecedent.Result
For ctr As Integer = 0 To arr.Length - 1
If token.IsCancellationRequested Then
Console.WriteLine("{0}Cancellation requested in
continuation...{0}",
vbCrLf)
token.ThrowIfCancellationRequested()
End If

If ctr Mod 100 = 0 Then


Dim delay As Integer
SyncLock lockObj
delay = rnd.Next(16, 251)
End SyncLock
Thread.Sleep(delay)
End If
Console.Write("{0:N0}{1}", arr(ctr),
If(ctr <> arr.Length - 1, ", ", ""))
If Console.CursorLeft >= 74 Then Console.WriteLine()
Next
Console.WriteLine()
End Sub, token)

Try
continuation.Wait()
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name,
ie.Message)
Next
Finally
cts.Dispose()
End Try

Console.WriteLine(vbCrLf + "Antecedent Status: {0}", t.Status)


Console.WriteLine("Continuation Status: {0}", continuation.Status)
End Sub

Private Sub Elapsed(state As Object)


Dim cts As CancellationTokenSource = TryCast(state, CancellationTokenSource)
If cts Is Nothing Then return

cts.Cancel()
Console.WriteLine("{0}Cancellation request issued...{0}", vbCrLf)
End Sub
End Module
' The example displays output like the following:
' The example displays output like the following:
' Multiples of 33:
'
' 33, 66, 99, 132, 165, 198, 231, 264, 297, 330, 363, 396, 429, 462, 495, 528,
' 561, 594, 627, 660, 693, 726, 759, 792, 825, 858, 891, 924, 957, 990, 1,023,
' 1,056, 1,089, 1,122, 1,155, 1,188, 1,221, 1,254, 1,287, 1,320, 1,353, 1,386,
' 1,419, 1,452, 1,485, 1,518, 1,551, 1,584, 1,617, 1,650, 1,683, 1,716, 1,749,
' 1,782, 1,815, 1,848, 1,881, 1,914, 1,947, 1,980, 2,013, 2,046, 2,079, 2,112,
' 2,145, 2,178, 2,211, 2,244, 2,277, 2,310, 2,343, 2,376, 2,409, 2,442, 2,475,
' 2,508, 2,541, 2,574, 2,607, 2,640, 2,673, 2,706, 2,739, 2,772, 2,805, 2,838,
' 2,871, 2,904, 2,937, 2,970, 3,003, 3,036, 3,069, 3,102, 3,135, 3,168, 3,201,
' 3,234, 3,267, 3,300, 3,333, 3,366, 3,399, 3,432, 3,465, 3,498, 3,531, 3,564,
' 3,597, 3,630, 3,663, 3,696, 3,729, 3,762, 3,795, 3,828, 3,861, 3,894, 3,927,
' 3,960, 3,993, 4,026, 4,059, 4,092, 4,125, 4,158, 4,191, 4,224, 4,257, 4,290,
' 4,323, 4,356, 4,389, 4,422, 4,455, 4,488, 4,521, 4,554, 4,587, 4,620, 4,653,
' 4,686, 4,719, 4,752, 4,785, 4,818, 4,851, 4,884, 4,917, 4,950, 4,983, 5,016,
' 5,049, 5,082, 5,115, 5,148, 5,181, 5,214, 5,247, 5,280, 5,313, 5,346, 5,379,
' 5,412, 5,445, 5,478, 5,511, 5,544, 5,577, 5,610, 5,643, 5,676, 5,709, 5,742,
' 5,775, 5,808, 5,841, 5,874, 5,907, 5,940, 5,973, 6,006, 6,039, 6,072, 6,105,
' 6,138, 6,171, 6,204, 6,237, 6,270, 6,303, 6,336, 6,369, 6,402, 6,435, 6,468,
' 6,501, 6,534, 6,567, 6,600, 6,633, 6,666, 6,699, 6,732, 6,765, 6,798, 6,831,
' 6,864, 6,897, 6,930, 6,963, 6,996, 7,029, 7,062, 7,095, 7,128, 7,161, 7,194,
' 7,227, 7,260, 7,293, 7,326, 7,359, 7,392, 7,425, 7,458, 7,491, 7,524, 7,557,
' 7,590, 7,623, 7,656, 7,689, 7,722, 7,755, 7,788, 7,821, 7,854, 7,887, 7,920,
' 7,953, 7,986, 8,019, 8,052, 8,085, 8,118, 8,151, 8,184, 8,217, 8,250, 8,283,
' 8,316, 8,349, 8,382, 8,415, 8,448, 8,481, 8,514, 8,547, 8,580, 8,613, 8,646,
' 8,679, 8,712, 8,745, 8,778, 8,811, 8,844, 8,877, 8,910, 8,943, 8,976, 9,009,
' 9,042, 9,075, 9,108, 9,141, 9,174, 9,207, 9,240, 9,273, 9,306, 9,339, 9,372,
' 9,405, 9,438, 9,471, 9,504, 9,537, 9,570, 9,603, 9,636, 9,669, 9,702, 9,735,
' 9,768, 9,801, 9,834, 9,867, 9,900, 9,933, 9,966, 9,999, 10,032, 10,065, 10,098,
' 10,131, 10,164, 10,197, 10,230, 10,263, 10,296, 10,329, 10,362, 10,395, 10,428,
' 10,461, 10,494, 10,527, 10,560, 10,593, 10,626, 10,659, 10,692, 10,725, 10,758,
' 10,791, 10,824, 10,857, 10,890, 10,923, 10,956, 10,989, 11,022, 11,055, 11,088,
' 11,121, 11,154, 11,187, 11,220, 11,253, 11,286, 11,319, 11,352, 11,385, 11,418,
' 11,451, 11,484, 11,517, 11,550, 11,583, 11,616, 11,649, 11,682, 11,715, 11,748,
' 11,781, 11,814, 11,847, 11,880, 11,913, 11,946, 11,979, 12,012, 12,045, 12,078,
' 12,111, 12,144, 12,177, 12,210, 12,243, 12,276, 12,309, 12,342, 12,375, 12,408,
' 12,441, 12,474, 12,507, 12,540, 12,573, 12,606, 12,639, 12,672, 12,705, 12,738,
' 12,771, 12,804, 12,837, 12,870, 12,903, 12,936, 12,969, 13,002, 13,035, 13,068,
' 13,101, 13,134, 13,167, 13,200, 13,233, 13,266,
' Cancellation requested in continuation...
'
'
' Cancellation request issued...
'
' TaskCanceledException: A task was canceled.
'
' Antecedent Status: RanToCompletion
' Continuation Status: Canceled

También se puede evitar que una continuación se ejecute si su antecedente se cancela sin proporcionar a la
continuación un token de cancelación. Para ello, debe especificarse la opción
TaskContinuationOptions.NotOnCanceled al crear la continuación. Este es un ejemplo sencillo.
using System;
using System.Threading;
using System.Threading.Tasks;

public class CancellationTwoExample


{
public static async Task Main()
{
using var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.Cancel();

var task = Task.FromCanceled(token);


Task continuation =
task.ContinueWith(
antecedent => Console.WriteLine("The continuation is running."),
TaskContinuationOptions.NotOnCanceled);

try
{
await task;
}
catch (Exception ex)
{
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
Console.WriteLine();
}

Console.WriteLine($"Task {task.Id}: {task.Status:G}");


Console.WriteLine($"Task {continuation.Id}: {continuation.Status:G}");
}
}
// The example displays the similar output:
// TaskCanceledException: A task was canceled.
//
// Task 1: Canceled
// Task 2: Canceled
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
cts.Cancel()

Dim t As Task = Task.FromCanceled(token)


Dim continuation As Task = t.ContinueWith(Sub(antecedent)
Console.WriteLine("The continuation is running.")
End Sub, TaskContinuationOptions.NotOnCanceled)
Try
t.Wait()
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
Next
Console.WriteLine()
Finally
cts.Dispose()
End Try

Console.WriteLine("Task {0}: {1:G}", t.Id, t.Status)


Console.WriteLine("Task {0}: {1:G}", continuation.Id,
continuation.Status)
End Sub
End Module
' The example displays the following output:
' TaskCanceledException: A task was canceled.
'
' Task 1: Canceled
' Task 2: Canceled

Cuando una continuación entra en el estado Canceled puede afectar a las continuaciones posteriores, en función
de los valores de TaskContinuationOptions que se especificaron para esas continuaciones.
Las continuaciones eliminadas no se iniciarán.

Continuaciones y tareas secundarias


Una continuación no se ejecuta hasta que el antecedente y todas sus tareas secundarias asociadas no se
completen. La continuación no espera a que las tareas secundarias desasociadas finalicen. En los dos ejemplos
siguientes se ilustran tareas secundarias que se asocian y desasocian de un antecedente que crea una
continuación. En el ejemplo siguiente la continuación solo se ejecuta después de que se completan todas las
tareas secundarias. Si se ejecuta el ejemplo varias veces, se genera una salida idéntica cada vez. En el ejemplo se
inicia el antecedente mediante una llamada al método TaskFactory.StartNew, ya que de forma predeterminada el
método Task.Run crea una tarea primaria cuya opción de creación de tarea predeterminada es
TaskCreationOptions.DenyChildAttach.
using System;
using System.Threading.Tasks;

public class AttachedExample


{
public static async Task Main()
{
await Task.Factory
.StartNew(
() =>
{
Console.WriteLine($"Running antecedent task {Task.CurrentId}...");
Console.WriteLine("Launching attached child tasks...");
for (int ctr = 1; ctr <= 5; ctr++)
{
int index = ctr;
Task.Factory.StartNew(async value =>
{
Console.WriteLine($" Attached child task #{value} running");
await Task.Delay(1000);
}, index, TaskCreationOptions.AttachedToParent);
}
Console.WriteLine("Finished launching attached child tasks...");
}).ContinueWith(
antecedent =>
Console.WriteLine($"Executing continuation of Task {antecedent.Id}"));
}
}
// The example displays the similar output:
// Running antecedent task 1...
// Launching attached child tasks...
// Finished launching attached child tasks...
// Attached child task #1 running
// Attached child task #5 running
// Attached child task #3 running
// Attached child task #2 running
// Attached child task #4 running
// Executing continuation of Task 1
Imports System.Threading
Imports System.Threading.Tasks

Public Module Example


Public Sub Main()
Dim t = Task.Factory.StartNew(Sub()
Console.WriteLine("Running antecedent task {0}...",
Task.CurrentId)
Console.WriteLine("Launching attached child tasks...")
For ctr As Integer = 1 To 5
Dim index As Integer = ctr
Task.Factory.StartNew(Sub(value)
Console.WriteLine(" Attached child
task #{0} running",
value)
Thread.Sleep(1000)
End Sub, index,
TaskCreationOptions.AttachedToParent)
Next
Console.WriteLine("Finished launching attached child tasks...")
End Sub)
Dim continuation = t.ContinueWith(Sub(antecedent)
Console.WriteLine("Executing continuation of Task {0}",
antecedent.Id)
End Sub)
continuation.Wait()
End Sub
End Module
' The example displays the following output:
' Running antecedent task 1...
' Launching attached child tasks...
' Finished launching attached child tasks...
' Attached child task #5 running
' Attached child task #1 running
' Attached child task #2 running
' Attached child task #3 running
' Attached child task #4 running
' Executing continuation of Task 1

No obstante, si las tareas secundarias se desasocian del antecedente, la continuación se ejecuta en cuanto finaliza
el antecedente y con independencia del estado de las tareas secundarias. Como resultado, varias ejecuciones del
ejemplo siguiente pueden tener una salida distinta que depende de cómo el programador de tareas controla cada
tarea secundaria.
using System;
using System.Threading.Tasks;

public class DetachedExample


{
public static async Task Main()
{
Task task =
Task.Factory.StartNew(
() =>
{
Console.WriteLine($"Running antecedent task {Task.CurrentId}...");
Console.WriteLine("Launching attached child tasks...");
for (int ctr = 1; ctr <= 5; ctr++)
{
int index = ctr;
Task.Factory.StartNew(
async value =>
{
Console.WriteLine($" Attached child task #{value} running");
await Task.Delay(1000);
}, index);
}
Console.WriteLine("Finished launching detached child tasks...");
}, TaskCreationOptions.DenyChildAttach);

Task continuation =
task.ContinueWith(
antecedent =>
Console.WriteLine($"Executing continuation of Task {antecedent.Id}"));

await continuation;

Console.ReadLine();
}
}
// The example displays the similar output:
// Running antecedent task 1...
// Launching attached child tasks...
// Finished launching detached child tasks...
// Attached child task #2 running
// Attached child task #3 running
// Attached child task #1 running
// Attached child task #4 running
// Attached child task #5 running
// Executing continuation of Task 1
Imports System.Threading
Imports System.Threading.Tasks

Public Module Example


Public Sub Main()
Dim t = Task.Factory.StartNew(Sub()
Console.WriteLine("Running antecedent task {0}...",
Task.CurrentId)
Console.WriteLine("Launching attached child tasks...")
For ctr As Integer = 1 To 5
Dim index As Integer = ctr
Task.Factory.StartNew(Sub(value)
Console.WriteLine(" Attached child
task #{0} running",
value)
Thread.Sleep(1000)
End Sub, index)
Next
Console.WriteLine("Finished launching detached child tasks...")
End Sub, TaskCreationOptions.DenyChildAttach)
Dim continuation = t.ContinueWith(Sub(antecedent)
Console.WriteLine("Executing continuation of Task {0}",
antecedent.Id)
End Sub)
continuation.Wait()
End Sub
End Module
' The example displays output like the following:
' Running antecedent task 1...
' Launching attached child tasks...
' Finished launching detached child tasks...
' Attached child task #1 running
' Attached child task #2 running
' Attached child task #5 running
' Attached child task #3 running
' Executing continuation of Task 1
' Attached child task #4 running

El estado final de la tarea antecedente depende del estado final de las tareas secundarias asociadas. El estado de
las tareas secundarias desasociadas no afecta al elemento primario. Para más información, consulte Tareas
secundarias asociadas y desasociadas.

Asociación de estado con continuaciones


Un estado arbitrario se puede asociar con una continuación de tarea. El método ContinueWith proporciona
versiones sobrecargadas, y cada una de ellas toma un valor Object que representa el estado de la continuación.
Más adelante se puede tener acceso a este objeto de estado mediante la propiedad Task.AsyncState . Si no se
proporciona un valor, este objeto de estado es null .
El estado de continuación es útil al convertir un código existente que use el modelo de programación asincrónica
(APM) para utilizar la TPL. En el APM, normalmente se proporciona el estado del objeto en el método
Begin Método y el acceso posterior a ese estado mediante la propiedad IAsyncResult.AsyncState. Si se usa el
método ContinueWith , se puede conservar este estado al convertir código que usa el APM para usar la TPL.
El estado de continuación también puede ser útil cuando se trabaja con objetos Task en el depurador de Visual
Studio. Por ejemplo, en la ventana Tareas paralelas , la columna Tarea muestra la representación de cadena del
objeto de estado de cada tarea. Para más información sobre la ventana Tareas paralelas , consulte el artículo
Usar la ventana Tareas.
En el ejemplo siguiente se muestra cómo usar el estado de continuación. En él se crea una cadena de tareas de
continuación. Cada tarea proporciona la hora actual —un objeto DateTime — para el parámetro state del
método ContinueWith . Cada objeto DateTime representa la hora en que se creó la tarea de continuación. Cada
tarea produce como resultado un segundo objeto DateTime que representa la hora en que finaliza la tarea. Una
vez que finalizan todas las tareas, se muestran la hora de creación y la hora de finalización de cada tarea de
continuación.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

class ContinuationStateExample
{
static DateTime DoWork()
{
Thread.Sleep(2000);

return DateTime.Now;
}

static async Task Main()


{
Task<DateTime> task = Task.Run(() => DoWork());

var continuations = new List<Task<DateTime>>();


for (int i = 0; i < 5; i++)
{
task = task.ContinueWith((antecedent, _) => DoWork(), DateTime.Now);
continuations.Add(task);
}

await task;

foreach (Task<DateTime> continuation in continuations)


{
DateTime start = (DateTime)continuation.AsyncState;
DateTime end = continuation.Result;

Console.WriteLine($"Task was created at {start.TimeOfDay} and finished at {end.TimeOfDay}.");


}

Console.ReadLine();
}
}
// The example displays the similar output:
// Task was created at 10:56:21.1561762 and finished at 10:56:25.1672062.
// Task was created at 10:56:21.1610677 and finished at 10:56:27.1707646.
// Task was created at 10:56:21.1610677 and finished at 10:56:29.1743230.
// Task was created at 10:56:21.1610677 and finished at 10:56:31.1779883.
// Task was created at 10:56:21.1610677 and finished at 10:56:33.1837083.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

' Demonstrates how to associate state with task continuations.


Public Module ContinuationState
' Simluates a lengthy operation and returns the time at which
' the operation completed.
Public Function DoWork() As Date
' Simulate work by suspending the current thread
' for two seconds.
Thread.Sleep(2000)

' Return the current time.


Return Date.Now
End Function

Public Sub Main()


' Start a root task that performs work.
Dim t As Task(Of Date) = Task(Of Date).Run(Function() DoWork())

' Create a chain of continuation tasks, where each task is


' followed by another task that performs work.
Dim continuations As New List(Of Task(Of DateTime))()
For i As Integer = 0 To 4
' Provide the current time as the state of the continuation.
t = t.ContinueWith(Function(antecedent, state) DoWork(), DateTime.Now)
continuations.Add(t)
Next

' Wait for the last task in the chain to complete.


t.Wait()

' Display the creation time of each continuation (the state object)
' and the completion time (the result of that task) to the console.
For Each continuation In continuations
Dim start As DateTime = CDate(continuation.AsyncState)
Dim [end] As DateTime = continuation.Result

Console.WriteLine("Task was created at {0} and finished at {1}.",


start.TimeOfDay, [end].TimeOfDay)
Next
End Sub
End Module
' The example displays output like the following:
' Task was created at 10:56:21.1561762 and finished at 10:56:25.1672062.
' Task was created at 10:56:21.1610677 and finished at 10:56:27.1707646.
' Task was created at 10:56:21.1610677 and finished at 10:56:29.1743230.
' Task was created at 10:56:21.1610677 and finished at 10:56:31.1779883.
' Task was created at 10:56:21.1610677 and finished at 10:56:33.1837083.

Continuaciones que devuelven tipos de tareas


En ocasiones, puede que deba encadenar una continuación que devuelve un tipo Task. Estas se conocen como
tareas anidadas y son comunes. Por ejemplo, puede ocurrir que una tarea primaria llame a
Task<TResult>.ContinueWith y proporcione un elemento continuationFunction que es una tarea que devuelve
una llamada a Unwrap para crear una tarea proxy que representa la operación asincrónica de <Task<Task<T>>> o
Task(Of Task(Of T)) (Visual Basic).

En el ejemplo siguiente se muestra cómo usar continuaciones que encapsulan funciones adicionales que
devuelven tareas. Cada continuación se puede desencapsular, de modo que se expone la tarea interna que se
encapsuló.
using System;
using System.Threading;
using System.Threading.Tasks;

public class UnwrapExample


{
public static async Task Main()
{
Task<int> taskOne = RemoteIncrement(0);
Console.WriteLine("Started RemoteIncrement(0)");

Task<int> taskTwo = RemoteIncrement(4)


.ContinueWith(t => RemoteIncrement(t.Result))
.Unwrap().ContinueWith(t => RemoteIncrement(t.Result))
.Unwrap().ContinueWith(t => RemoteIncrement(t.Result))
.Unwrap();

Console.WriteLine("Started RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)");

try
{
await taskOne;
Console.WriteLine("Finished RemoteIncrement(0)");

await taskTwo;
Console.WriteLine("Finished RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)");
}
catch (Exception e)
{
Console.WriteLine($"A task has thrown the following (unexpected) exception:\n{e}");
}
}

static Task<int> RemoteIncrement(int number) =>


Task<int>.Factory.StartNew(
obj =>
{
Thread.Sleep(1000);

int x = (int)obj;
Console.WriteLine("Thread={0}, Next={1}", Thread.CurrentThread.ManagedThreadId, ++x);
return x;
},
number);
}

// The example displays the similar output:


// Started RemoteIncrement(0)
// Started RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)
// Thread=4, Next=1
// Finished RemoteIncrement(0)
// Thread=5, Next=5
// Thread=6, Next=6
// Thread=6, Next=7
// Thread=6, Next=8
// Finished RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)
Imports System.Threading

Module UnwrapExample
Sub Main()
Dim taskOne As Task(Of Integer) = RemoteIncrement(0)
Console.WriteLine("Started RemoteIncrement(0)")

Dim taskTwo As Task(Of Integer) = RemoteIncrement(4).


ContinueWith(Function(t) RemoteIncrement(t.Result)).
Unwrap().ContinueWith(Function(t) RemoteIncrement(t.Result)).
Unwrap().ContinueWith(Function(t) RemoteIncrement(t.Result)).
Unwrap()

Console.WriteLine("Started RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)")

Try
taskOne.Wait()
Console.WriteLine("Finished RemoteIncrement(0)")

taskTwo.Wait()
Console.WriteLine("Finished RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)")
Catch e As AggregateException
Console.WriteLine($"A task has thrown the following (unexpected) exception:{vbLf}{e}")
End Try
End Sub

Function RemoteIncrement(ByVal number As Integer) As Task(Of Integer)


Return Task(Of Integer).Factory.StartNew(
Function(obj)
Thread.Sleep(1000)

Dim x As Integer = CInt(obj)


Console.WriteLine("Thread={0}, Next={1}", Thread.CurrentThread.ManagedThreadId,
Interlocked.Increment(x))
Return x
End Function, number)
End Function
End Module

' The example displays the similar output:


' Started RemoteIncrement(0)
' Started RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)
' Thread=4, Next=1
' Finished RemoteIncrement(0)
' Thread=5, Next=5
' Thread=6, Next=6
' Thread=6, Next=7
' Thread=6, Next=8
' Finished RemoteIncrement(...(RemoteIncrement(RemoteIncrement(4))...)

Para más información sobre el uso de Unwrap, consulte Cómo: Desencapsular una tarea anidada.

Control de excepciones iniciadas por continuaciones


Una relación antecedente-continuación no es una relación entre elementos primarios y secundarios. Las
excepciones producidas por las continuaciones no se propagan al antecedente. Por lo tanto, las excepciones
producidas por las continuaciones se deben controlar como en cualquier otra tarea, es decir:
Puede usar el método Wait, WaitAllo WaitAny , o su homólogo genérico, para esperar en la continuación.
Puede esperar un antecedente y sus continuaciones en la misma instrucción try , tal como se muestra en el
ejemplo siguiente.
using System;
using System.Threading.Tasks;

public class ExceptionExample


{
public static async Task Main()
{
Task<int> task = Task.Run(
() =>
{
Console.WriteLine($"Executing task {Task.CurrentId}");
return 54;
});

var continuation = task.ContinueWith(


antecedent =>
{
Console.WriteLine($"Executing continuation task {Task.CurrentId}");
Console.WriteLine($"Value from antecedent: {antecedent.Result}");

throw new InvalidOperationException();


});

try
{
await task;
await continuation;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
// The example displays the similar output:
// Executing task 1
// Executing continuation task 2
// Value from antecedent: 54
// Operation is not valid due to the current state of the object.
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task(Of Integer).Run(Function()
Console.WriteLine("Executing task {0}",
Task.CurrentId)
Return 54
End Function)
Dim continuation = task1.ContinueWith(Sub(antecedent)
Console.WriteLine("Executing continuation task {0}",
Task.CurrentId)
Console.WriteLine("Value from antecedent: {0}",
antecedent.Result)
Throw New InvalidOperationException()
End Sub)

Try
task1.Wait()
continuation.Wait()
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
Console.WriteLine(ex.Message)
Next
End Try
End Sub
End Module
' The example displays the following output:
' Executing task 1
' Executing continuation task 2
' Value from antecedent: 54
' Operation is not valid due to the current state of the object.

Puede usar una segunda continuación para observar la propiedad Exception de la primera continuación. En el
siguiente ejemplo, una tarea intenta leer en un archivo inexistente. La continuación muestra entonces
información sobre la excepción en la tarea antecedente.
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

public class ExceptionTwoExample


{
public static async Task Main()
{
var task = Task.Run(
() =>
{
string fileText = File.ReadAllText(@"C:\NonexistentFile.txt");
return fileText;
});

Task continuation = task.ContinueWith(


antecedent =>
{
var fileNotFound =
antecedent.Exception
?.InnerExceptions
?.FirstOrDefault(e => e is FileNotFoundException) as FileNotFoundException;

if (fileNotFound != null)
{
Console.WriteLine(fileNotFound.Message);
}
}, TaskContinuationOptions.OnlyOnFaulted);

await continuation;

Console.ReadLine();
}
}
// The example displays the following output:
// Could not find file 'C:\NonexistentFile.txt'.

Imports System.IO
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim t = Task.Run(Function()
Dim s As String = File.ReadAllText("C:\NonexistentFile.txt")
Return s
End Function)

Dim c = t.ContinueWith(Sub(antecedent)
' Get the antecedent's exception information.
For Each ex In antecedent.Exception.InnerExceptions
If TypeOf ex Is FileNotFoundException
Console.WriteLine(ex.Message)
End If
Next
End Sub, TaskContinuationOptions.OnlyOnFaulted)

c.Wait()
End Sub
End Module
' The example displays the following output:
' Could not find file 'C:\NonexistentFile.txt'.

Dado que se ejecutó con la opción TaskContinuationOptions.OnlyOnFaulted , la continuación solo se ejecuta si se


produce una excepción en el antecedente y, por lo tanto, puede asumir que la propiedad Exception del
antecedente no es null . Si la continuación se ejecuta tanto si se produce una excepción en el antecedente como
si no, habría que comprobar si la propiedad Exception del antecedente no es null antes de intentar controlar la
excepción, como se muestra en el fragmento de código siguiente.

var fileNotFound =
antecedent.Exception
?.InnerExceptions
?.FirstOrDefault(e => e is FileNotFoundException) as FileNotFoundException;

if (fileNotFound != null)
{
Console.WriteLine(fileNotFound.Message);
}

' Determine whether an exception occurred.


If antecedent.Exception IsNot Nothing Then
' Get the antecedent's exception information.
For Each ex In antecedent.Exception.InnerExceptions
If TypeOf ex Is FileNotFoundException
Console.WriteLine(ex.Message)
End If
Next
End If

Para más información, consulte Control de excepciones.


Si la continuación es una tarea secundaria asociada que se creó mediante la opción
TaskContinuationOptions.AttachedToParent , sus excepciones serán propagadas por el elemento primario
hacia el subproceso de llamada, como sucede con cualquier otro elemento secundario asociado. Para más
información, consulte Tareas secundarias asociadas y desasociadas.

Vea también
Biblioteca TPL
Tareas secundarias asociadas y desasociadas
16/09/2020 • 15 minutes to read • Edit Online

Una tarea secundaria o tarea anidada es una instancia de System.Threading.Tasks.Task que se crea en el delegado
de usuario de otra tarea, conocida como tarea primaria. Una tarea secundaria puede estar desasociada o
asociada. Una tarea secundaria desasociada es una tarea que se ejecuta independientemente de su elemento
principal. Una tarea secundaria asociada es una tarea anidada que se crea con la opción
TaskCreationOptions.AttachedToParent y cuyo elemento primario no le prohíbe asociarse de forma explícita o
predeterminada. Una tarea puede crear cualquier número de tareas secundarias asociadas y desasociadas, con la
única limitación de los recursos del sistema.
En la tabla siguiente se muestran las diferencias básicas entre los dos tipos de tareas secundarias.

C AT EGO RÍA TA REA S SEC UN DA RIA S DESA SO C IA DA S TA REA S SEC UN DA RIA S A SO C IA DA S

La tarea primaria espera a que se No Sí


completen las tareas secundarias.

La tarea primaria propaga las No Sí


excepciones que producen las tareas
secundarias.

El estado de la tarea primaria depende No Sí


del estado de la tarea secundaria.

En la mayoría de los casos, se recomienda usar tareas secundarias desasociadas porque las relaciones con otras
tareas son menos complejas. Esta es la razón por la que las tareas que se crean dentro de tareas primarias están
desasociadas de forma predeterminada y es necesario especificar explícitamente la opción
TaskCreationOptions.AttachedToParent para crear una tarea secundaria asociada.

Tareas secundarias desasociadas


Aunque una tarea secundaria se crea mediante una tarea primaria, de forma predeterminada es independiente
de la tarea primaria. En el ejemplo siguiente se muestra una tarea primaria que crea una tarea secundaria simple.
Si se ejecuta varias veces el código de ejemplo, se observa que el resultado del ejemplo se diferencia del que se
muestra, y también que el resultado puede cambiar cada vez que se ejecuta el código. Esto ocurre porque la
tarea primaria y las tareas secundarias se ejecutan de forma independiente; la tarea secundaria es una tarea
independiente. El ejemplo únicamente espera que se complete la tarea primaria; la tarea secundaria puede no
ejecutarse o completarse antes de que finalice la aplicación de consola.
using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Outer task executing.");

var child = Task.Factory.StartNew(() => {


Console.WriteLine("Nested task starting.");
Thread.SpinWait(500000);
Console.WriteLine("Nested task completing.");
});
});

parent.Wait();
Console.WriteLine("Outer has completed.");
}
}
// The example produces output like the following:
// Outer task executing.
// Nested task starting.
// Outer has completed.
// Nested task completing.

Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Outer task executing.")
Dim child = Task.Factory.StartNew(Sub()

Console.WriteLine("Nested task starting.")


Thread.SpinWait(500000)

Console.WriteLine("Nested task completing.")


End Sub)
End Sub)
parent.Wait()
Console.WriteLine("Outer task has completed.")
End Sub
End Module
' The example produces output like the following:
' Outer task executing.
' Nested task starting.
' Outer task has completed.
' Nested task completing.

Si la tarea secundaria se representa mediante un objeto Task<TResult> en lugar de un objeto Task, puede
garantizarse que la tarea primaria esperará a que la tarea secundaria se complete teniendo acceso a la propiedad
Task<TResult>.Result de la tarea secundaria, aunque sea una tarea secundaria desasociada. La propiedad Result
se bloquea hasta que se completa su tarea, como se muestra en el ejemplo siguiente.
using System;
using System.Threading;
using System.Threading.Tasks;

class Example
{
static void Main()
{
var outer = Task<int>.Factory.StartNew(() => {
Console.WriteLine("Outer task executing.");

var nested = Task<int>.Factory.StartNew(() => {


Console.WriteLine("Nested task starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Nested task completing.");
return 42;
});

// Parent will wait for this detached child.


return nested.Result;
});

Console.WriteLine("Outer has returned {0}.", outer.Result);


}
}
// The example displays the following output:
// Outer task executing.
// Nested task starting.
// Nested task completing.
// Outer has returned 42.

Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim parent = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Outer task executing.")
Dim child = Task(Of
Integer).Factory.StartNew(Function()

Console.WriteLine("Nested task starting.")

Thread.SpinWait(5000000)

Console.WriteLine("Nested task completing.")

Return 42
End
Function)
Return child.Result

End Function)
Console.WriteLine("Outer has returned {0}", parent.Result)
End Sub
End Module
' The example displays the following output:
' Outer task executing.
' Nested task starting.
' Detached task completing.
' Outer has returned 42
Tareas secundarias asociadas
A diferencia de las tareas secundarias desasociadas, las tareas secundarias asociadas se sincronizan
estrechamente con la tarea primaria. En el ejemplo anterior, se puede cambiar la tarea secundaria desasociada a
una tarea secundaria asociada mediante la opción TaskCreationOptions.AttachedToParent en la instrucción de
creación de tareas, como se muestra en el ejemplo siguiente. En este código, la tarea secundaria asociada se
completa antes que su tarea primaria. Como resultado, el resultado del ejemplo es igual cada vez que se ejecuta
el código.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Parent task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent has completed.");
}
}
// The example displays the following output:
// Parent task executing.
// Attached child starting.
// Attached child completing.
// Parent has completed.

Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Parent task executing")
Dim child = Task.Factory.StartNew(Sub()

Console.WriteLine("Attached child starting.")

Thread.SpinWait(5000000)

Console.WriteLine("Attached child completing.")


End Sub,
TaskCreationOptions.AttachedToParent)
End Sub)
parent.Wait()
Console.WriteLine("Parent has completed.")
End Sub
End Module
' The example displays the following output:
' Parent task executing.
' Attached child starting.
' Attached child completing.
' Parent has completed.

Puede usar tareas secundarias asociadas para crear gráficos con una estrecha sincronización de operaciones
asincrónicas.
Sin embargo, una tarea secundaria se puede asociar a su elemento primario solo si su elemento primario no
prohíbe las tareas secundarias asociadas. Las tareas primarias pueden evitar explícitamente que las tareas
secundarias se asocien a ellas especificando la opción TaskCreationOptions.DenyChildAttach del constructor de
clase de la tarea primaria o el método TaskFactory.StartNew. Las tareas primarias impiden implícitamente que las
tareas secundarias se asocien a ellas si se crean mediante una llamada al método Task.Run. Esto se ilustra en el
siguiente ejemplo: Es idéntico al ejemplo anterior, excepto en que la tarea primaria se crea mediante una llamada
al método Task.Run(Action) en lugar de al método TaskFactory.StartNew(Action). Como la tarea secundaria no se
puede asociar a su elemento primario, el resultado del ejemplo es impredecible. Como las opciones de creación
de tareas predeterminadas para las sobrecargas de Task.Run incluyen TaskCreationOptions.DenyChildAttach,
este ejemplo es funcionalmente equivalente al primer ejemplo de la sección "Tareas secundarias desasociadas".

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var parent = Task.Run(() => {
Console.WriteLine("Parent task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent has completed.");
}
}
// The example displays output like the following:
// Parent task executing.
// Parent has completed.
// Attached child starting.

Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim parent = Task.Run(Sub()
Console.WriteLine("Parent task executing.")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Attached child
starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Attached child
completing.")
End Sub,
TaskCreationOptions.AttachedToParent)
End Sub)
parent.Wait()
Console.WriteLine("Parent has completed.")
End Sub
End Module
' The example displays output like the following:
' Parent task executing.
' Parent has completed.
' Attached child starting.
Excepciones en tareas secundarias
Si una tarea secundaria desasociadas inicia una excepción, esa excepción debe observarse o controlarse
directamente en la tarea primaria como si se tratara de una tarea no anidada. Si una tarea secundaria asociada
inicia una excepción, la excepción se propaga automáticamente a la tarea primaria y de nuevo al subproceso, que
espera o intenta obtener acceso a la propiedad Task<TResult>.Result de la tarea. Por tanto, si se usan tareas
secundarias asociadas, se pueden controlar todas las excepciones en un solo punto en la llamada a Task.Wait del
subproceso que realiza la llamada. Para más información, consulte Control de excepciones.

Cancelación y tareas secundarias


La cancelación de tareas es cooperativa. Es decir, para que se pueda cancelar, cada tarea secundaria asociada o
desasociada debe supervisar el estado del token de cancelación. Si desea cancelar un elemento primario y todos
sus elementos secundarios utilizando una solicitud de cancelación, debe pasar el mismo token como argumento
a todas las tareas y proporcionar en cada tarea la lógica de respuesta a la solicitud en cada tarea. Para más
información, consulte Task Cancellation (Cancelación de tareas) y How to: Cancel a Task and Its Children
(Cancelar una tarea y sus elementos secundarios).
Cuando la tarea primaria se cancela
Si una tarea primaria se cancela antes de que se inicie su tarea secundaria, la tarea secundaria nunca se inicia. Si
una tarea primaria se cancela después de que se ha iniciado su tarea secundaria, la tarea secundaria se ejecutará
hasta completarse a menos que tenga su propia lógica de cancelación. Para más información, vea Task
Cancellation.
Cuando una tarea secundaria desasociada se cancela
Si una tarea secundaria desasociada se cancela usando el mismo token que se pasó a la tarea primaria, y la tarea
primaria no espera a la tarea secundaria, no se propagará ninguna excepción puesto que la excepción se trata
cono una cancelación de cooperación benigna. Este comportamiento es igual que el de cualquier tarea de nivel
superior.
Cuando se cancela una tarea secundaria asociada
Cuando una tarea secundaria asociada se cancela usando el mismo token que se pasó a su tarea primaria, se
propaga una excepción TaskCanceledException al subproceso de unión dentro de AggregateException. Se debe
esperar a la tarea primaria para poder controlar todas las excepciones benignas además de todas las
excepciones de error que se propagan de manera ascendente a través de un gráfico de tareas secundarias
asociadas.
Para más información, consulte Control de excepciones.

Impedir que una tarea secundaria se adjunte a su tarea primara


Una excepción no controlada producida por una tarea secundaria se propaga a la tarea primaria. Puede usar este
comportamiento para observar todas las excepciones de tareas secundarias desde una tarea raíz en lugar de
recorrer un árbol de tareas. Sin embargo, la propagación de excepciones puede dar problemas cuando una tarea
primaria no cuenta con datos adjuntos de otro código. Por ejemplo, piense en una aplicación que llama a un
componente de la biblioteca de terceros de un objeto Task. Si el componente de la biblioteca de terceros también
crea un objeto Task y especifica TaskCreationOptions.AttachedToParent para asociarlo a la tarea primaria, las
excepciones no controladas que aparecen en la tarea secundaria se propagan a la tarea primaria. Esto podría dar
lugar a un comportamiento inesperado en la aplicación principal.
Para evitar que una tarea secundaria se adjunte a su tarea primaria, especifique la opción
TaskCreationOptions.DenyChildAttach cuando cree el objeto primario Task o Task<TResult>. Cuando una tarea
intenta asociarse a su elemento primario y el elemento primario especifica la opción
TaskCreationOptions.DenyChildAttach, la tarea secundaria no podrá asociarse a un elemento primario y se
ejecutará como si no se hubiera especificado la opción TaskCreationOptions.AttachedToParent.
Puede que también desee evitar que una tarea secundaria se adjunte a su tarea primaria cuando la tarea
secundaria no finaliza a tiempo. Dado que una tarea primaria no finaliza hasta que finalizan todas las tareas
secundarias, una tarea secundaria que se ejecute durante mucho tiempo puede provocar que el rendimiento
general de la aplicación sea mediocre. Para obtener un ejemplo que muestra cómo mejorar el rendimiento de la
aplicación impidiendo que una tarea se asocie a su tarea primaria, consulte Cómo: Evitar que una tarea
secundaria se asocie a su elemento primario.

Vea también
Programación en paralelo
Data Parallelism (Paralelismo de datos)
Cancelación de tareas
16/09/2020 • 5 minutes to read • Edit Online

Las clases System.Threading.Tasks.Task y System.Threading.Tasks.Task<TResult> admiten la cancelación a través


del uso de tokens de cancelación en .NET Framework. Para más información, consulte el tema sobre la
cancelación en subprocesos administrados. En las clases Task, la cancelación implica la cooperación entre el
delegado de usuario, que representa una operación que se puede cancelar y el código que solicitó la cancelación.
Una cancelación correcta significa que el código que la solicita llama al método CancellationTokenSource.Cancel
y que el delegado de usuario finaliza la operación a tiempo. Puede finalizar la operación a través de una de estas
opciones:
Devolver simplemente un valor del delegado. En muchos escenarios esto es suficiente; sin embargo, una
instancia de tarea cancelada de esta manera cambia al estado TaskStatus.RanToCompletion , no al estado
TaskStatus.Canceled .
Producir una excepción OperationCanceledException y pasarle el token en el que se solicitó la
cancelación. En este caso, se prefiere usar el método ThrowIfCancellationRequested . Una tarea cancelada
de esta manera cambia al estado Canceled, que sirve al código que realiza la llamada para comprobar
que la tarea respondió a su solicitud de cancelación.
En el siguiente ejemplo se muestra el modelo básico para la opción de cancelación de tareas que produce la
excepción. Observe que el token se pasa al delegado de usuario y a la propia instancia de la tarea.
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
var tokenSource2 = new CancellationTokenSource();
CancellationToken ct = tokenSource2.Token;

var task = Task.Run(() =>


{
// Were we already canceled?
ct.ThrowIfCancellationRequested();

bool moreToDo = true;


while (moreToDo)
{
// Poll on this property if you have to do
// other cleanup before throwing.
if (ct.IsCancellationRequested)
{
// Clean up here, then...
ct.ThrowIfCancellationRequested();
}
}
}, tokenSource2.Token); // Pass same token to Task.Run.

tokenSource2.Cancel();

// Just continue on this thread, or await with try-catch:


try
{
await task;
}
catch (OperationCanceledException e)
{
Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {e.Message}");
}
finally
{
tokenSource2.Dispose();
}

Console.ReadKey();
}
}
Imports System.Threading
Imports System.Threading.Tasks

Module Test
Sub Main()
Dim tokenSource2 As New CancellationTokenSource()
Dim ct As CancellationToken = tokenSource2.Token

Dim t2 = Task.Factory.StartNew(Sub()
' Were we already canceled?
ct.ThrowIfCancellationRequested()

Dim moreToDo As Boolean = True


While moreToDo = True
' Poll on this property if you have to do
' other cleanup before throwing.
If ct.IsCancellationRequested Then

' Clean up here, then...


ct.ThrowIfCancellationRequested()
End If

End While
End Sub _
, tokenSource2.Token) ' Pass same token to StartNew.

' Cancel the task.


tokenSource2.Cancel()

' Just continue on this thread, or Wait/WaitAll with try-catch:


Try
t2.Wait()

Catch e As AggregateException

For Each item In e.InnerExceptions


Console.WriteLine(e.Message & " " & item.Message)
Next
Finally
tokenSource2.Dispose()
End Try

Console.ReadKey()
End Sub
End Module

Para obtener un ejemplo más completo, vea Cómo: Cancelar una tarea y sus elementos secundarios.
Cuando una instancia de tarea observa una excepción OperationCanceledException iniciada desde el código de
usuario, compara el token de la excepción con su token asociado (el que se pasó a la API que creó la tarea). Si
son iguales y la propiedad IsCancellationRequested del token devuelve true, la tarea lo interpreta como una
confirmación de cancelación y pasa al estado Canceled. Si no se usa un método Wait o WaitAll para esperar a la
tarea, esta simplemente establece su estado en Canceled.
Si espera en un elemento Task que cambia al estado “Canceled”, se crea y se inicia una excepción
System.Threading.Tasks.TaskCanceledException (encapsulada en la excepción AggregateException ). Observe que
esta excepción indica la cancelación correcta en lugar de una situación de error. Por consiguiente, la propiedad
Exception del elemento Task devuelve null .
Si la propiedad IsCancellationRequested del token devuelve False o si el token de la excepción no coincide con el
token de la tarea, OperationCanceledException se trata como una excepción normal, por lo que la tarea cambia
al estado Faulted. Observe también que la presencia de otras excepciones también hará que la tarea pase al
estado Faulted. Puede obtener el estado de la tarea completada en la propiedad Status .
Es posible que una tarea continúe procesando algunos elementos una vez solicitada la cancelación.

Vea también
Cancelación en subprocesos administrados
Cómo: Cancelar una tarea y sus elementos secundarios
Control de excepciones (biblioteca TPL)
16/09/2020 • 20 minutes to read • Edit Online

Las excepciones no controladas que se inician mediante código de usuario que se ejecuta dentro de una tarea
se propagan de vuelta al subproceso que hace la llamada, excepto en determinados escenarios que se
describen posteriormente en este tema. Las excepciones se propagan cuando se usa uno de los métodos
estáticos o de instancia Task.Wait, los cuales se pueden controlar si se incluye la llamada en una instrucción
try / catch . Si una tarea es la tarea primaria de tareas secundarias asociadas o si se esperan varias tareas,
pueden producirse varias excepciones.
Para propagar todas las excepciones de nuevo al subproceso que realiza la llamada, la infraestructura de la
tarea las encapsula en una instancia de AggregateException . La excepción AggregateException tiene una
propiedad InnerExceptions que se puede enumerar para examinar todas las excepciones originales que se
produjeron y controlar (o no) cada una de ellas de forma individual. También puede controlar las excepciones
originales mediante el método AggregateException.Handle .
Incluso aunque solo se produzca una excepción, se encapsulará en una excepción AggregateException , como
se muestra en el ejemplo siguiente.

using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

try
{
task1.Wait();
}
catch (AggregateException ae)
{
foreach (var e in ae.InnerExceptions) {
// Handle the custom exception.
if (e is CustomException) {
Console.WriteLine(e.Message);
}
// Rethrow any other exception.
else {
throw;
}
}
}
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays the following output:
// This exception is expected!
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
' Handle the custom exception.
If TypeOf ex Is CustomException Then
Console.WriteLine(ex.Message)
' Rethrow any other exception.
Else
Throw
End If
Next
End Try
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' This exception is expected!

Para evitar una excepción no controlada, basta con detectar el objeto AggregateException y omitir las
excepciones internas. Sin embargo, esta operación no resulta recomendable porque es igual que detectar el
tipo Exception base en escenarios no paralelos. Si desea detectar una excepción sin realizar acciones concretas
que la resuelvan, puede dejar al programa en un estado indeterminado.
Si no desea llamar al método Task.Wait para esperar a la finalización de una tarea, también puede recuperar la
excepción AggregateException de la propiedad Exception de la tarea, como se muestra en el ejemplo siguiente.
Para más información, consulte la sección Observar excepciones mediante la propiedad Task.Exception de este
tema.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

while(! task1.IsCompleted) {}

if (task1.Status == TaskStatus.Faulted) {
foreach (var e in task1.Exception.InnerExceptions) {
// Handle the custom exception.
if (e is CustomException) {
Console.WriteLine(e.Message);
}
// Rethrow any other exception.
else {
throw e;
}
}
}
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays the following output:
// This exception is expected!

Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

While Not task1.IsCompleted


End While

If task1.Status = TaskStatus.Faulted Then


For Each ex In task1.Exception.InnerExceptions
' Handle the custom exception.
If TypeOf ex Is CustomException Then
Console.WriteLine(ex.Message)
' Rethrow any other exception.
Else
Throw ex
End If
Next
End If
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' This exception is expected!
Si no espera a una tarea que propague la excepción ni accede a su propiedad Exception , la excepción se
escalará conforme a la directiva de excepciones de .NET cuando la tarea se recopile como elemento no
utilizado.
Cuando las excepciones pueden propagarse de vuelta al subproceso de unión, es posible que una tarea
continúe procesando algunos elementos después de que se haya producido la excepción.

NOTE
Cuando está habilitada la opción "Solo mi código", en algunos casos, Visual Studio se interrumpe en la línea que produce
la excepción y muestra el mensaje de error "Excepción no controlada por el código de usuario". Este error es benigno.
Puede presionar F5 para continuar y ver el comportamiento de control de excepciones que se muestra en estos
ejemplos. Para evitar que Visual Studio se interrumpa con el primer error, desactive la casilla Habilitar Solo mi código
bajo Herramientas, Opciones, Depuración, General.

Tareas secundarias asociadas y objetos AggregateException anidados


Si una tarea tiene una tarea secundaria adjunta que inicia una excepción, esa excepción se encapsula en un
objeto AggregateException antes de que se propague a la tarea primaria, que encapsula esa excepción en su
propio objeto AggregateException antes de propagarla de nuevo al subproceso que realiza la llamada. En casos
como este, la propiedad InnerExceptions de la excepción AggregateException que se detecta en los métodos
Task.Wait, WaitAny o WaitAll contiene una o varias instancias de AggregateException, pero no las excepciones
originales que produjeron el error. Para evitar tener que iterar sobre excepciones AggregateException, puede
usar el método Flatten para quitar todas las excepciones AggregateException anidadas, de forma que la
propiedad AggregateException.InnerExceptions contenga las excepciones originales. En el ejemplo siguiente, las
instancias anidadas de AggregateException se reducen y se controlan en un solo bucle.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Factory.StartNew(() => {
var child1 = Task.Factory.StartNew(() => {
var child2 = Task.Factory.StartNew(() => {
// This exception is nested inside three AggregateExceptions.
throw new CustomException("Attached child2 faulted.");
}, TaskCreationOptions.AttachedToParent);

// This exception is nested inside two AggregateExceptions.


throw new CustomException("Attached child1 faulted.");
}, TaskCreationOptions.AttachedToParent);
});

try {
task1.Wait();
}
catch (AggregateException ae) {
foreach (var e in ae.Flatten().InnerExceptions) {
if (e is CustomException) {
Console.WriteLine(e.Message);
}
else {
throw;
}
}
}
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays the following output:
// Attached child1 faulted.
// Attached child2 faulted.
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Factory.StartNew(Sub()
Dim child1 = Task.Factory.StartNew(Sub()
Dim child2 =
Task.Factory.StartNew(Sub()

Throw New CustomException("Attached child2 faulted.")

End Sub,

TaskCreationOptions.AttachedToParent)
Throw New
CustomException("Attached child1 faulted.")
End Sub,

TaskCreationOptions.AttachedToParent)
End Sub)

Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.Flatten().InnerExceptions
If TypeOf ex Is CustomException Then
Console.WriteLine(ex.Message)
Else
Throw
End If
Next
End Try
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' Attached child1 faulted.
' Attached child2 faulted.

También puede utilizar el método AggregateException.Flatten para volver a generar las excepciones internas de
varias instancias de AggregateException iniciadas por varias tareas en una sola instancia de
AggregateException, como se muestra en el ejemplo siguiente.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
try {
ExecuteTasks();
}
catch (AggregateException ae) {
foreach (var e in ae.InnerExceptions) {
Console.WriteLine("{0}:\n {1}", e.GetType().Name, e.Message);
}
}
}

static void ExecuteTasks()


{
// Assume this is a user-entered String.
String path = @"C:\";
List<Task> tasks = new List<Task>();

tasks.Add(Task.Run(() => {
// This should throw an UnauthorizedAccessException.
return Directory.GetFiles(path, "*.txt",
SearchOption.AllDirectories);
}));

tasks.Add(Task.Run(() => {
if (path == @"C:\")
throw new ArgumentException("The system root is not a valid path.");
return new String[] { ".txt", ".dll", ".exe", ".bin", ".dat" };
}));

tasks.Add(Task.Run(() => {
throw new NotImplementedException("This operation has not been
implemented.");
}));

try {
Task.WaitAll(tasks.ToArray());
}
catch (AggregateException ae) {
throw ae.Flatten();
}
}
}
// The example displays the following output:
// UnauthorizedAccessException:
// Access to the path 'C:\Documents and Settings' is denied.
// ArgumentException:
// The system root is not a valid path.
// NotImplementedException:
// This operation has not been implemented.
Imports System.Collections.Generic
Imports System.IO
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Try
ExecuteTasks()
Catch ae As AggregateException
For Each e In ae.InnerExceptions
Console.WriteLine("{0}:{2} {1}", e.GetType().Name, e.Message,
vbCrLf)
Next
End Try
End Sub

Sub ExecuteTasks()
' Assume this is a user-entered String.
Dim path = "C:\"
Dim tasks As New List(Of Task)

tasks.Add(Task.Run(Function()
' This should throw an UnauthorizedAccessException.
Return Directory.GetFiles(path, "*.txt",
SearchOption.AllDirectories)
End Function))

tasks.Add(Task.Run(Function()
If path = "C:\" Then
Throw New ArgumentException("The system root is not a valid path.")
End If
Return {".txt", ".dll", ".exe", ".bin", ".dat"}
End Function))

tasks.Add(Task.Run(Sub()
Throw New NotImplementedException("This operation has not been
implemented.")
End Sub))

Try
Task.WaitAll(tasks.ToArray)
Catch ae As AggregateException
Throw ae.Flatten()
End Try
End Sub
End Module
' The example displays the following output:
' UnauthorizedAccessException:
' Access to the path 'C:\Documents and Settings' is denied.
' ArgumentException:
' The system root is not a valid path.
' NotImplementedException:
' This operation has not been implemented.

Excepciones de tareas secundarias desasociadas


De forma predeterminada, las tareas secundarias están desasociadas cuando se crean. Las excepciones
producidas por tareas desasociadas deben controlarse o reiniciarse en la tarea primaria inmediata; no se
propagan de nuevo al subproceso que realiza la llamada del mismo modo que las tareas secundarias
asociadas. La tarea primaria superior puede reiniciar manualmente una excepción de una tarea secundaria
desasociada para que se encapsule en un objeto AggregateException y propagarla de vuelta al subproceso de
unión.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Run(() => {
var nested1 = Task.Run(() => {
throw new CustomException("Detached child task faulted.");
});

// Here the exception will be escalated back to the calling thread.


// We could use try/catch here to prevent that.
nested1.Wait();
});

try {
task1.Wait();
}
catch (AggregateException ae) {
foreach (var e in ae.Flatten().InnerExceptions) {
if (e is CustomException) {
Console.WriteLine(e.Message);
}
}
}
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays the following output:
// Detached child task faulted.
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub()
Dim nestedTask1 = Task.Run(Sub()
Throw New CustomException("Detached child
task faulted.")
End Sub)
' Here the exception will be escalated back to joining thread.
' We could use try/catch here to prevent that.
nestedTask1.Wait()
End Sub)

Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.Flatten().InnerExceptions
If TypeOf ex Is CustomException Then
' Recover from the exception. Here we just
' print the message for demonstration purposes.
Console.WriteLine(ex.Message)
End If
Next
End Try
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' Detached child task faulted.

Aunque se use una tarea de continuación para observar una excepción en una tarea secundaria, la tarea
primaria debe seguir observando la excepción.

Excepciones que indican la cancelación cooperativa


Cuando el código de usuario de una tarea responde a una solicitud de cancelación, el procedimiento correcto
es producir una excepción OperationCanceledException que se pasa en el token de cancelación con el que se
comunicó la solicitud. Antes de intentar propagar la excepción, la instancia de la tarea compara el token de la
excepción con el que recibió durante su creación. Si son iguales, la tarea propaga una excepción
TaskCanceledException encapsulada en un elemento AggregateExceptiony puede verse cuando se examinan las
excepciones internas. Sin embargo, si el subproceso que hace la llamada no está esperando la tarea, no se
propagará esa excepción concreta. Para más información, vea Cancelación de tareas.
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

var task1 = Task.Factory.StartNew(() =>


{
CancellationToken ct = token;
while (someCondition)
{
// Do some work...
Thread.SpinWait(50000);
ct.ThrowIfCancellationRequested();
}
},
token);

// No waiting required.
tokenSource.Dispose();

Dim someCondition As Boolean = True


Dim tokenSource = New CancellationTokenSource()
Dim token = tokenSource.Token

Dim task1 = Task.Factory.StartNew(Sub()


Dim ct As CancellationToken = token
While someCondition = True
' Do some work...
Thread.SpinWait(500000)
ct.ThrowIfCancellationRequested()
End While
End Sub,
token)

Usar el método Handle para filtrar excepciones internas


El método AggregateException.Handle puede usarse para filtrar excepciones que pueden tratarse como
"controladas" sin necesidad de usar ninguna otra lógica. En el delegado de usuario que se facilita al método
AggregateException.Handle(Func<Exception,Boolean>) , se puede examinar el tipo de excepción, su propiedad
Message o cualquier otra información sobre ella que permita determinar si no supone un riesgo. Las
excepciones en las que el delegado devuelve false se reinician inmediatamente en una nueva instancia de
AggregateException después de que el método AggregateException.Handle devuelva un valor.
El ejemplo siguiente es funcionalmente equivalente al primer ejemplo de este tema, que examina cada
excepción de la colección AggregateException.InnerExceptions. En su lugar, este controlador de excepciones
llama al objeto del método AggregateException.Handle por cada excepción y solo vuelve a generar las
excepciones que no son instancias de CustomException .
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

try {
task1.Wait();
}
catch (AggregateException ae)
{
// Call the Handle method to handle the custom exception,
// otherwise rethrow the exception.
ae.Handle(ex => { if (ex is CustomException)
Console.WriteLine(ex.Message);
return ex is CustomException;
});
}
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays the following output:
// This exception is expected!

Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

Try
task1.Wait()
Catch ae As AggregateException
' Call the Handle method to handle the custom exception,
' otherwise rethrow the exception.
ae.Handle(Function(e)
If TypeOf e Is CustomException Then
Console.WriteLine(e.Message)
End If
Return TypeOf e Is CustomException
End Function)
End Try
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' This exception is expected!

A continuación, se muestra un ejemplo más completo que usa el método AggregateException.Handle para
ofrecer un control especial para una excepción UnauthorizedAccessException al enumerar los archivos.
using System;
using System.IO;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
// This should throw an UnauthorizedAccessException.
try {
var files = GetAllFiles(@"C:\");
if (files != null)
foreach (var file in files)
Console.WriteLine(file);
}
catch (AggregateException ae) {
foreach (var ex in ae.InnerExceptions)
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
}
Console.WriteLine();

// This should throw an ArgumentException.


try {
foreach (var s in GetAllFiles(""))
Console.WriteLine(s);
}
catch (AggregateException ae) {
foreach (var ex in ae.InnerExceptions)
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
}
}

static string[] GetAllFiles(string path)


{
var task1 = Task.Run( () => Directory.GetFiles(path, "*.txt",
SearchOption.AllDirectories));

try {
return task1.Result;
}
catch (AggregateException ae) {
ae.Handle( x => { // Handle an UnauthorizedAccessException
if (x is UnauthorizedAccessException) {
Console.WriteLine("You do not have permission to access all folders in this
path.");
Console.WriteLine("See your network administrator or try another path.");
}
return x is UnauthorizedAccessException;
});
return Array.Empty<String>();
}
}
}
// The example displays the following output:
// You do not have permission to access all folders in this path.
// See your network administrator or try another path.
//
// ArgumentException: The path is not of a legal form.
Imports System.IO
Imports System.Threading.Tasks

Module Example
Public Sub Main()
' This should throw an UnauthorizedAccessException.
Try
Dim files = GetAllFiles("C:\")
If files IsNot Nothing Then
For Each file In files
Console.WriteLine(file)
Next
End If
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
Next
End Try
Console.WriteLine()

' This should throw an ArgumentException.


Try
For Each s In GetAllFiles("")
Console.WriteLine(s)
Next
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
Next
End Try
Console.WriteLine()
End Sub

Function GetAllFiles(ByVal path As String) As String()


Dim task1 = Task.Run(Function()
Return Directory.GetFiles(path, "*.txt",
SearchOption.AllDirectories)
End Function)
Try
Return task1.Result
Catch ae As AggregateException
ae.Handle(Function(x)
' Handle an UnauthorizedAccessException
If TypeOf x Is UnauthorizedAccessException Then
Console.WriteLine("You do not have permission to access all folders in this
path.")
Console.WriteLine("See your network administrator or try another path.")
End If
Return TypeOf x Is UnauthorizedAccessException
End Function)
End Try
Return Array.Empty(Of String)()
End Function
End Module
' The example displays the following output:
' You do not have permission to access all folders in this path.
' See your network administrator or try another path.
'
' ArgumentException: The path is not of a legal form.

Observar excepciones mediante la propiedad Task.Exception


Si una tarea se completa con el estado TaskStatus.Faulted , se puede examinar su propiedad Exception para
detectar qué excepción concreta produjo el error. Un mecanismo adecuado para observar la propiedad
Exception es usar una continuación que se ejecute solo si se produce un error en la tarea anterior, tal y como se
muestra en el siguiente ejemplo.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Run(() =>
{ throw new CustomException("task1 faulted.");
}).ContinueWith( t => { Console.WriteLine("{0}: {1}",
t.Exception.InnerException.GetType().Name,
t.Exception.InnerException.Message);
}, TaskContinuationOptions.OnlyOnFaulted);
Thread.Sleep(500);
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays output like the following:
// CustomException: task1 faulted.

Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Factory.StartNew(Sub()
Throw New CustomException("task1 faulted.")
End Sub).
ContinueWith(Sub(t)
Console.WriteLine("{0}: {1}",
t.Exception.InnerException.GetType().Name,
t.Exception.InnerException.Message)
End Sub, TaskContinuationOptions.OnlyOnFaulted)

Thread.Sleep(500)
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays output like the following:
' CustomException: task1 faulted.

En una aplicación significativa, el delegado de continuación podría registrar información detallada sobre la
excepción y posiblemente generar nuevas tareas para recuperarse de la excepción. Si se produce un error en
una tarea, las siguientes expresiones inician la excepción:
await task
task.Wait()
task.Result
task.GetAwaiter().GetResult()
Use una instrucción try-catch para controlar y observar las excepciones producidas. Como alternativa, acceda
a la propiedad Task.Exception para observar la excepción.

Evento UnobservedTaskException
En algunos escenarios (por ejemplo, cuando se hospedan complementos que no son de confianza), es posible
que se produzcan numerosas excepciones benignas y que resulte demasiado difícil observarlas todas
manualmente. En estos casos, se puede proceder a controlar el evento TaskScheduler.UnobservedTaskException
. La instancia de System.Threading.Tasks.UnobservedTaskExceptionEventArgs que se pasa al controlador se
puede utilizar para evitar que la excepción no observada se propague de nuevo al subproceso de unión.

Vea también
Biblioteca TPL
Procedimiento para usar Parallel.Invoke para ejecutar
operaciones en paralelo
16/09/2020 • 5 minutes to read • Edit Online

En este ejemplo se muestra cómo paralelizar operaciones usando Invoke en Task Parallel Library. Se realizan tres
operaciones en un origen de datos compartido. Las operaciones se pueden ejecutar en paralelo de manera
sencilla, ya que ninguna de ellas modifica el origen.

NOTE
En esta documentación, se utilizan expresiones lambda para definir delegados en la TPL. Si no está familiarizado con las
expresiones lambda de C# o Visual Basic, vea Expresiones lambda en PLINQ y TPL.

Ejemplo
namespace ParallelTasks
{
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net;

class ParallelInvoke
{
static void Main()
{
// Retrieve Goncharov's "Oblomov" from Gutenberg.org.
string[] words = CreateWordArray(@"http://www.gutenberg.org/files/54700/54700-0.txt");

#region ParallelTasks
// Perform three tasks in parallel on the source array
Parallel.Invoke(() =>
{
Console.WriteLine("Begin first task...");
GetLongestWord(words);
}, // close first Action

() =>
{
Console.WriteLine("Begin second task...");
GetMostCommonWords(words);
}, //close second Action

() =>
{
Console.WriteLine("Begin third task...");
GetCountForWord(words, "sleep");
} //close third Action
); //close parallel.invoke

Console.WriteLine("Returned from Parallel.Invoke");


#endregion

Console.WriteLine("Press any key to exit");


Console.ReadKey();
}

#region HelperMethods
private static void GetCountForWord(string[] words, string term)
{
var findWord = from word in words
where word.ToUpper().Contains(term.ToUpper())
select word;

Console.WriteLine($@"Task 3 -- The word ""{term}"" occurs {findWord.Count()} times.");


}

private static void GetMostCommonWords(string[] words)


{
var frequencyOrder = from word in words
where word.Length > 6
group word by word into g
orderby g.Count() descending
select g.Key;

var commonWords = frequencyOrder.Take(10);

StringBuilder sb = new StringBuilder();


sb.AppendLine("Task 2 -- The most common words are:");
foreach (var v in commonWords)
{
sb.AppendLine(" " + v);
}
Console.WriteLine(sb.ToString());
}

private static string GetLongestWord(string[] words)


{
var longestWord = (from w in words
orderby w.Length descending
select w).First();

Console.WriteLine($"Task 1 -- The longest word is {longestWord}.");


return longestWord;
}

// An http request performed synchronously for simplicity.


static string[] CreateWordArray(string uri)
{
Console.WriteLine($"Retrieving from {uri}");

// Download a web page the easy way.


string s = new WebClient().DownloadString(uri);

// Separate string into an array of words, removing some common punctuation.


return s.Split(
new char[] { ' ', '\u000A', ',', '.', ';', ':', '-', '_', '/' },
StringSplitOptions.RemoveEmptyEntries);
}
#endregion
}
}
// The example displays output like the following:
// Retrieving from http://www.gutenberg.org/files/54700/54700-0.txt
// Begin first task...
// Begin second task...
// Begin third task...
// Task 2 -- The most common words are:
// Oblomov
// himself
// Schtoltz
// Gutenberg
// Project
// another
// thought
// Oblomov's
// nothing
// replied
//
// Task 1 -- The longest word is incomprehensible.
// Task 3 -- The word "sleep" occurs 57 times.
// Returned from Parallel.Invoke
// Press any key to exit

Imports System.Net
Imports System.Threading.Tasks

Module ParallelTasks
Sub Main()
' Retrieve Goncharov's "Oblomov" from Gutenberg.org.
Dim words As String() = CreateWordArray("http://www.gutenberg.org/files/54700/54700-0.txt")

'#Region "ParallelTasks"
' Perform three tasks in parallel on the source array
Parallel.Invoke(Sub()
Console.WriteLine("Begin first task...")
GetLongestWord(words)
' close first Action
End Sub,
Sub()
Console.WriteLine("Begin second task...")
GetMostCommonWords(words)
'close second Action
End Sub,
Sub()
Console.WriteLine("Begin third task...")
GetCountForWord(words, "sleep")
'close third Action
End Sub)
'close parallel.invoke
Console.WriteLine("Returned from Parallel.Invoke")
'#End Region

Console.WriteLine("Press any key to exit")


Console.ReadKey()
End Sub

#Region "HelperMethods"
Sub GetCountForWord(ByVal words As String(), ByVal term As String)
Dim findWord = From word In words
Where word.ToUpper().Contains(term.ToUpper())
Select word

Console.WriteLine($"Task 3 -- The word ""{term}"" occurs {findWord.Count()} times.")


End Sub

Sub GetMostCommonWords(ByVal words As String())


Dim frequencyOrder = From word In words
Where word.Length > 6
Group By word
Into wordGroup = Group, Count()
Order By wordGroup.Count() Descending
Select wordGroup

Dim commonWords = From grp In frequencyOrder


Select grp
Take (10)

Dim s As String
s = "Task 2 -- The most common words are:" & vbCrLf
For Each v In commonWords
s = s & v(0) & vbCrLf
Next
Console.WriteLine(s)
End Sub

Function GetLongestWord(ByVal words As String()) As String


Dim longestWord = (From w In words
Order By w.Length Descending
Select w).First()

Console.WriteLine($"Task 1 -- The longest word is {longestWord}.")


Return longestWord
End Function

' An http request performed synchronously for simplicity.


Function CreateWordArray(ByVal uri As String) As String()
Console.WriteLine($"Retrieving from {uri}")

' Download a web page the easy way.


Dim s As String = New WebClient().DownloadString(uri)

' Separate string into an array of words, removing some common punctuation.
Return s.Split(New Char() {" "c, ControlChars.Lf, ","c, "."c, ";"c, ":"c,
"-"c, "_"c, "/"c}, StringSplitOptions.RemoveEmptyEntries)
End Function
#End Region
End Module
' The exmaple displays output like the following:
' Retrieving from http://www.gutenberg.org/files/54700/54700-0.txt
' Begin first task...
' Begin second task...
' Begin third task...
' Task 2 -- The most common words are:
' Oblomov
' himself
' Schtoltz
' Gutenberg
' Project
' another
' thought
' Oblomov's
' nothing
' replied
'
' Task 1 -- The longest word is incomprehensible.
' Task 3 -- The word "sleep" occurs 57 times.
' Returned from Parallel.Invoke
' Press any key to exit

Con Invoke, solo tiene que expresar qué acciones quiere ejecutar simultáneamente y el tiempo de ejecución
controla todos los detalles de programación de los subprocesos, incluido el ajuste automático del número de
núcleos en el equipo host.
En este ejemplo se paralelizan las operaciones, no los datos. Como método alternativo, puede paralelizar los
consultas de LINQ mediante PLINQ y ejecutar las consultas de forma secuencial. También puede paralelizar los
datos con PLINQ. Otra opción consiste en paralelizar las consultas y las tareas. Aunque la sobrecarga resultante
podría reducir el rendimiento en equipos host con relativamente pocos procesadores, se ajusta mejor en equipos
con varios procesadores.

Compilar el código
Copie y pegue el ejemplo completo en un proyecto de Microsoft Visual Studio y presione F5 .
Vea también
Programación en paralelo
Cómo: Cancelar una tarea y sus elementos secundarios
Parallel LINQ (PLINQ)
Procedimiento para devolver un valor a partir de una
tarea
16/09/2020 • 2 minutes to read • Edit Online

En este ejemplo se muestra cómo se usa el tipo System.Threading.Tasks.Task<TResult> para devolver un valor de
la propiedad Result. Requiere que exista el directorio de C:\Usuarios\Pública\Imágenes\Imágenes de muestra\ y
que contenga archivos.

Ejemplo
using System;
using System.Linq;
using System.Threading.Tasks;

class Program
{
static void Main()
{
// Return a value type with a lambda expression
Task<int> task1 = Task<int>.Factory.StartNew(() => 1);
int i = task1.Result;

// Return a named reference type with a multi-line statement lambda.


Task<Test> task2 = Task<Test>.Factory.StartNew(() =>
{
string s = ".NET";
double d = 4.0;
return new Test { Name = s, Number = d };
});
Test test = task2.Result;

// Return an array produced by a PLINQ query


Task<string[]> task3 = Task<string[]>.Factory.StartNew(() =>
{
string path = @"C:\Users\Public\Pictures\Sample Pictures\";
string[] files = System.IO.Directory.GetFiles(path);

var result = (from file in files.AsParallel()


let info = new System.IO.FileInfo(file)
where info.Extension == ".jpg"
select file).ToArray();

return result;
});

foreach (var name in task3.Result)


Console.WriteLine(name);
}
class Test
{
public string Name { get; set; }
public double Number { get; set; }
}
}
Imports System.Threading.Tasks

Module Module1

Sub Main()
ReturnAValue()

Console.WriteLine("Press any key to exit.")


Console.ReadKey()

End Sub

Sub ReturnAValue()

' Return a value type with a lambda expression


Dim task1 = Task(Of Integer).Factory.StartNew(Function() 1)
Dim i As Integer = task1.Result

' Return a named reference type with a multi-line statement lambda.


Dim task2 As Task(Of Test) = Task.Factory.StartNew(Function()
Dim s As String = ".NET"
Dim d As Integer = 4
Return New Test With {.Name = s, .Number = d}
End Function)

Dim myTest As Test = task2.Result


Console.WriteLine(myTest.Name & ": " & myTest.Number)

' Return an array produced by a PLINQ query.


Dim task3 As Task(Of String()) = Task(Of String()).Factory.StartNew(Function()

Dim path =
"C:\Users\Public\Pictures\Sample Pictures\"
Dim files =
System.IO.Directory.GetFiles(path)

Dim result = (From file In


files.AsParallel()
Let info = New
System.IO.FileInfo(file)
Where
info.Extension = ".jpg"
Select
file).ToArray()
Return result
End Function)

For Each name As String In task3.Result


Console.WriteLine(name)
Next
End Sub

Class Test
Public Name As String
Public Number As Double
End Class
End Module

La propiedad Result bloquea el subproceso que realiza la llamada hasta que la tarea finaliza.
Para ver cómo se pasa el resultado de System.Threading.Tasks.Task<TResult> a una tarea de continuación, consulte
Encadenar tareas mediante tareas de continuación.

Vea también
Programación asincrónica basada en tareas
Expresiones lambda en PLINQ y TPL
Procedimiento para cancelar una tarea y sus
elementos secundarios
16/09/2020 • 9 minutes to read • Edit Online

En estos ejemplos se muestra cómo realizar las tareas siguientes:


1. Crear e iniciar una tarea cancelable.
2. Pasar un token de cancelación a un delegado de usuario y, opcionalmente, a la instancia de la tarea.
3. Observar y responder a la solicitud de cancelación en el delegado de usuario.
4. Opcionalmente, observar en el subproceso que realiza la llamada que la tarea se canceló.
El subproceso que realiza la llamada no finaliza la tarea forzosamente, sino que solo señala que se solicita la
cancelación. Si la tarea ya se está ejecutando, es el delegado de usuario el que debe observar la solicitud y
responder según corresponda. Si la cancelación se solicita antes de ejecutarse la tarea, el delegado de usuario
nunca se ejecuta y el objeto de tarea pasa al estado Cancelado.

Ejemplo
En este ejemplo se muestra cómo finalizar un objeto Task y sus elementos secundarios en respuesta a una
solicitud de cancelación. También se muestra que, cuando un delegado de usuario finaliza con una excepción
TaskCanceledException, el subproceso que realiza la llamada puede usar opcionalmente el método Wait o el
método WaitAll para esperar a que las tareas finalicen. En este caso, se debe usar un bloque try/catch para
controlar las excepciones en el subproceso que realiza la llamada.

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static async Task Main()
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

// Store references to the tasks so that we can wait on them and


// observe their status after cancellation.
Task t;
var tasks = new ConcurrentBag<Task>();

Console.WriteLine("Press any key to begin tasks...");


Console.ReadKey(true);
Console.WriteLine("To terminate the example, press 'c' to cancel and exit...");
Console.WriteLine();

// Request cancellation of a single task when the token source is canceled.


// Pass the token to the user delegate, and also to the task so it can
// handle the exception correctly.
t = Task.Run(() => DoSomeWork(1, token), token);
Console.WriteLine("Task {0} executing", t.Id);
tasks.Add(t);

// Request cancellation of a task and its children. Note the token is passed
// to (1) the user delegate and (2) as the second argument to Task.Run, so
// to (1) the user delegate and (2) as the second argument to Task.Run, so
// that the task instance can correctly handle the OperationCanceledException.
t = Task.Run(() =>
{
// Create some cancelable child tasks.
Task tc;
for (int i = 3; i <= 10; i++)
{
// For each child task, pass the same token
// to each user delegate and to Task.Run.
tc = Task.Run(() => DoSomeWork(i, token), token);
Console.WriteLine("Task {0} executing", tc.Id);
tasks.Add(tc);
// Pass the same token again to do work on the parent task.
// All will be signaled by the call to tokenSource.Cancel below.
DoSomeWork(2, token);
}
}, token);

Console.WriteLine("Task {0} executing", t.Id);


tasks.Add(t);

// Request cancellation from the UI thread.


char ch = Console.ReadKey().KeyChar;
if (ch == 'c' || ch == 'C')
{
tokenSource.Cancel();
Console.WriteLine("\nTask cancellation requested.");

// Optional: Observe the change in the Status property on the task.


// It is not necessary to wait on tasks that have canceled. However,
// if you do wait, you must enclose the call in a try-catch block to
// catch the TaskCanceledExceptions that are thrown. If you do
// not wait, no exception is thrown if the token that was passed to the
// Task.Run method is the same token that requested the cancellation.
}

try
{
await Task.WhenAll(tasks.ToArray());
}
catch (OperationCanceledException)
{
Console.WriteLine($"\n{nameof(OperationCanceledException)} thrown\n");
}
finally
{
tokenSource.Dispose();
}

// Display status of all tasks.


foreach (var task in tasks)
Console.WriteLine("Task {0} status is now {1}", task.Id, task.Status);
}

static void DoSomeWork(int taskNum, CancellationToken ct)


{
// Was cancellation already requested?
if (ct.IsCancellationRequested)
{
Console.WriteLine("Task {0} was cancelled before it got started.",
taskNum);
ct.ThrowIfCancellationRequested();
}

int maxIterations = 100;

// NOTE!!! A "TaskCanceledException was unhandled


// by user code" error will be raised here if "Just My Code"
// is enabled on your computer. On Express editions JMC is
// enabled and cannot be disabled. The exception is benign.
// Just press F5 to continue executing your code.
for (int i = 0; i <= maxIterations; i++)
{
// Do a bit of work. Not too much.
var sw = new SpinWait();
for (int j = 0; j <= 100; j++)
sw.SpinOnce();

if (ct.IsCancellationRequested)
{
Console.WriteLine("Task {0} cancelled", taskNum);
ct.ThrowIfCancellationRequested();
}
}
}
}
// The example displays output like the following:
// Press any key to begin tasks...
// To terminate the example, press 'c' to cancel and exit...
//
// Task 1 executing
// Task 2 executing
// Task 3 executing
// Task 4 executing
// Task 5 executing
// Task 6 executing
// Task 7 executing
// Task 8 executing
// c
// Task cancellation requested.
// Task 2 cancelled
// Task 7 cancelled
//
// OperationCanceledException thrown
//
// Task 2 status is now Canceled
// Task 1 status is now RanToCompletion
// Task 8 status is now Canceled
// Task 7 status is now Canceled
// Task 6 status is now RanToCompletion
// Task 5 status is now RanToCompletion
// Task 4 status is now RanToCompletion
// Task 3 status is now RanToCompletion

Imports System.Collections.Concurrent
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Sub Main()
Dim tokenSource As New CancellationTokenSource()
Dim token As CancellationToken = tokenSource.Token

' Store references to the tasks so that we can wait on them and
' observe their status after cancellation.
Dim t As Task
Dim tasks As New ConcurrentBag(Of Task)()

Console.WriteLine("Press any key to begin tasks...")


Console.ReadKey(True)
Console.WriteLine("To terminate the example, press 'c' to cancel and exit...")
Console.WriteLine()

' Request cancellation of a single task when the token source is canceled.
' Pass the token to the user delegate, and also to the task so it can
' handle the exception correctly.
t = Task.Factory.StartNew(Sub() DoSomeWork(1, token), token)
Console.WriteLine("Task {0} executing", t.Id)
tasks.Add(t)

' Request cancellation of a task and its children. Note the token is passed
' to (1) the user delegate and (2) as the second argument to StartNew, so
' that the task instance can correctly handle the OperationCanceledException.
t = Task.Factory.StartNew(Sub()
' Create some cancelable child tasks.
Dim tc As Task
For i As Integer = 3 To 10
' For each child task, pass the same token
' to each user delegate and to StartNew.
tc = Task.Factory.StartNew(Sub(iteration) DoSomeWork(iteration,
token), i, token)
Console.WriteLine("Task {0} executing", tc.Id)
tasks.Add(tc)
' Pass the same token again to do work on the parent task.
' All will be signaled by the call to tokenSource.Cancel below.
DoSomeWork(2, token)
Next
End Sub,
token)

Console.WriteLine("Task {0} executing", t.Id)


tasks.Add(t)

' Request cancellation from the UI thread.


Dim ch As Char = Console.ReadKey().KeyChar
If ch = "c"c Or ch = "C"c Then
tokenSource.Cancel()
Console.WriteLine(vbCrLf + "Task cancellation requested.")

' Optional: Observe the change in the Status property on the task.
' It is not necessary to wait on tasks that have canceled. However,
' if you do wait, you must enclose the call in a try-catch block to
' catch the TaskCanceledExceptions that are thrown. If you do
' not wait, no exception is thrown if the token that was passed to the
' StartNew method is the same token that requested the cancellation.
End If

Try
Task.WaitAll(tasks.ToArray())
Catch e As AggregateException
Console.WriteLine()
Console.WriteLine("AggregateException thrown with the following inner exceptions:")
' Display information about each exception.
For Each v In e.InnerExceptions
If TypeOf v Is TaskCanceledException
Console.WriteLine(" TaskCanceledException: Task {0}",
DirectCast(v, TaskCanceledException).Task.Id)
Else
Console.WriteLine(" Exception: {0}", v.GetType().Name)
End If
Next
Console.WriteLine()
Finally
tokenSource.Dispose()
End Try

' Display status of all tasks.


For Each t In tasks
Console.WriteLine("Task {0} status is now {1}", t.Id, t.Status)
Next
End Sub

Sub DoSomeWork(ByVal taskNum As Integer, ByVal ct As CancellationToken)


' Was cancellation already requested?
If ct.IsCancellationRequested = True Then
Console.WriteLine("Task {0} was cancelled before it got started.",
taskNum)
ct.ThrowIfCancellationRequested()
End If

Dim maxIterations As Integer = 100

' NOTE!!! A "TaskCanceledException was unhandled


' by user code" error will be raised here if "Just My Code"
' is enabled on your computer. On Express editions JMC is
' enabled and cannot be disabled. The exception is benign.
' Just press F5 to continue executing your code.
For i As Integer = 0 To maxIterations
' Do a bit of work. Not too much.
Dim sw As New SpinWait()
For j As Integer = 0 To 100
sw.SpinOnce()
Next
If ct.IsCancellationRequested Then
Console.WriteLine("Task {0} cancelled", taskNum)
ct.ThrowIfCancellationRequested()
End If
Next
End Sub
End Module
' The example displays output like the following:
' Press any key to begin tasks...
' To terminate the example, press 'c' to cancel and exit...
'
' Task 1 executing
' Task 2 executing
' Task 3 executing
' Task 4 executing
' Task 5 executing
' Task 6 executing
' Task 7 executing
' Task 8 executing
' c
' Task cancellation requested.
' Task 2 cancelled
' Task 7 cancelled
'
' AggregateException thrown with the following inner exceptions:
' TaskCanceledException: Task 2
' TaskCanceledException: Task 8
' TaskCanceledException: Task 7
'
' Task 2 status is now Canceled
' Task 1 status is now RanToCompletion
' Task 8 status is now Canceled
' Task 7 status is now Canceled
' Task 6 status is now RanToCompletion
' Task 5 status is now RanToCompletion
' Task 4 status is now RanToCompletion
' Task 3 status is now RanToCompletion

La clase System.Threading.Tasks.Task está totalmente integrada con el modelo de cancelación basado en los tipos
System.Threading.CancellationTokenSource y System.Threading.CancellationToken. Para más información,
consulte el tema sobre la cancelación en subprocesos administrados y la cancelación de tareas.

Vea también
System.Threading.CancellationTokenSource
System.Threading.CancellationToken
System.Threading.Tasks.Task
System.Threading.Tasks.Task<TResult>
Programación asincrónica basada en tareas
Tareas secundarias asociadas y desasociadas
Expresiones lambda en PLINQ y TPL
Cómo: Crear tareas precalculadas
16/09/2020 • 4 minutes to read • Edit Online

En este documento se describe cómo utilizar el método Task.FromResult para recuperar los resultados de las
operaciones asincrónicas de descarga que se retienen en una memoria caché. El método FromResult devuelve un
objeto Task<TResult> terminado que contiene el valor proporcionado como su propiedad Result. Este método es
útil cuando se realiza una operación asincrónica que devuelve un objeto Task<TResult> y el resultado de ese
objeto Task<TResult> ya se ha calculado.

Ejemplo
En el ejemplo siguiente se descargan cadenas desde la Web. Define el método DownloadStringAsync . Este método
descarga cadenas de la Web de forma asincrónica. En este ejemplo también se usa un objeto
ConcurrentDictionary<TKey,TValue> para almacenar en caché los resultados de las operaciones anteriores. Si la
dirección de entrada se mantiene en esta memoria caché, DownloadStringAsync utiliza el método FromResult para
generar un objeto Task<TResult> que incluye el contenido en esa dirección. En caso contrario,
DownloadStringAsync descarga el archivo desde la Web y agrega el resultado a la memoria caché.

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

// Demonstrates how to use Task<TResult>.FromResult to create a task


// that holds a pre-computed result.
class CachedDownloads
{
// Holds the results of download operations.
static ConcurrentDictionary<string, string> cachedDownloads =
new ConcurrentDictionary<string, string>();

// Asynchronously downloads the requested resource as a string.


public static Task<string> DownloadStringAsync(string address)
{
// First try to retrieve the content from cache.
string content;
if (cachedDownloads.TryGetValue(address, out content))
{
return Task.FromResult<string>(content);
}

// If the result was not in the cache, download the


// string and add it to the cache.
return Task.Run(async () =>
{
content = await new WebClient().DownloadStringTaskAsync(address);
cachedDownloads.TryAdd(address, content);
return content;
});
}

static void Main(string[] args)


{
// The URLs to download.
string[] urls = new string[]
{
"http://msdn.microsoft.com",
"http://msdn.microsoft.com",
"http://www.contoso.com",
"http://www.microsoft.com"
};

// Used to time download operations.


Stopwatch stopwatch = new Stopwatch();

// Compute the time required to download the URLs.


stopwatch.Start();
var downloads = from url in urls
select DownloadStringAsync(url);
Task.WhenAll(downloads).ContinueWith(results =>
{
stopwatch.Stop();

// Print the number of characters download and the elapsed time.


Console.WriteLine("Retrieved {0} characters. Elapsed time was {1} ms.",
results.Result.Sum(result => result.Length),
stopwatch.ElapsedMilliseconds);
})
.Wait();

// Perform the same operation a second time. The time required


// should be shorter because the results are held in the cache.
stopwatch.Restart();
downloads = from url in urls
select DownloadStringAsync(url);
Task.WhenAll(downloads).ContinueWith(results =>
{
stopwatch.Stop();

// Print the number of characters download and the elapsed time.


Console.WriteLine("Retrieved {0} characters. Elapsed time was {1} ms.",
results.Result.Sum(result => result.Length),
stopwatch.ElapsedMilliseconds);
})
.Wait();
}
}

/* Sample output:
Retrieved 27798 characters. Elapsed time was 1045 ms.
Retrieved 27798 characters. Elapsed time was 0 ms.
*/

Imports System.Collections.Concurrent
Imports System.Diagnostics
Imports System.Linq
Imports System.Net
Imports System.Threading.Tasks

' Demonstrates how to use Task<TResult>.FromResult to create a task


' that holds a pre-computed result.
Friend Class CachedDownloads
' Holds the results of download operations.
Private Shared cachedDownloads As New ConcurrentDictionary(Of String, String)()

' Asynchronously downloads the requested resource as a string.


Public Shared Function DownloadStringAsync(ByVal address As String) As Task(Of String)
' First try to retrieve the content from cache.
Dim content As String
If cachedDownloads.TryGetValue(address, content) Then
Return Task.FromResult(Of String)(content)
End If

' If the result was not in the cache, download the


' string and add it to the cache.
' string and add it to the cache.
Return Task.Run(async Function()
content = await New WebClient().DownloadStringTaskAsync(address)
cachedDownloads.TryAdd(address, content)
Return content
End Function)
End Function

Shared Sub Main(ByVal args() As String)


' The URLs to download.
Dim urls() As String = {"http://msdn.microsoft.com", "http://www.contoso.com",
"http://www.microsoft.com"}

' Used to time download operations.


Dim stopwatch As New Stopwatch()

' Compute the time required to download the URLs.


stopwatch.Start()
Dim downloads = From url In urls _
Select DownloadStringAsync(url)
Task.WhenAll(downloads).ContinueWith(Sub(results)
' Print the number of characters download and the elapsed
time.
stopwatch.Stop()
Console.WriteLine("Retrieved {0} characters. Elapsed time was
{1} ms.", results.Result.Sum(Function(result) result.Length), stopwatch.ElapsedMilliseconds)
End Sub).Wait()

' Perform the same operation a second time. The time required
' should be shorter because the results are held in the cache.
stopwatch.Restart()
downloads = From url In urls _
Select DownloadStringAsync(url)
Task.WhenAll(downloads).ContinueWith(Sub(results)
' Print the number of characters download and the elapsed
time.
stopwatch.Stop()
Console.WriteLine("Retrieved {0} characters. Elapsed time was
{1} ms.", results.Result.Sum(Function(result) result.Length), stopwatch.ElapsedMilliseconds)
End Sub).Wait()
End Sub
End Class

' Sample output:


'Retrieved 27798 characters. Elapsed time was 1045 ms.
'Retrieved 27798 characters. Elapsed time was 0 ms.
'

Este ejemplo calcula el tiempo necesario para descargar varias cadenas dos veces. El segundo conjunto de
operaciones de descarga debe tardar menos tiempo que el primer conjunto porque los resultados se mantienen
en la memoria caché. El método FromResult habilita el método DownloadStringAsync para crear objetos
Task<TResult> que contienen estos resultados precalculados.

Vea también
Programación asincrónica basada en tareas
Cómo: Recorrer un árbol binario con tareas paralelas
16/09/2020 • 2 minutes to read • Edit Online

En el ejemplo siguiente se muestran dos formas de usar las tareas paralelas para recorrer una estructura de datos
en árbol. La creación del árbol se deja como un ejercicio.

Ejemplo
public class TreeWalk
{
static void Main()
{
Tree<MyClass> tree = new Tree<MyClass>();

// ...populate tree (left as an exercise)

// Define the Action to perform on each node.


Action<MyClass> myAction = x => Console.WriteLine("{0} : {1}", x.Name, x.Number);

// Traverse the tree with parallel tasks.


DoTree(tree, myAction);
}

public class MyClass


{
public string Name { get; set; }
public int Number { get; set; }
}
public class Tree<T>
{
public Tree<T> Left;
public Tree<T> Right;
public T Data;
}

// By using tasks explcitly.


public static void DoTree<T>(Tree<T> tree, Action<T> action)
{
if (tree == null) return;
var left = Task.Factory.StartNew(() => DoTree(tree.Left, action));
var right = Task.Factory.StartNew(() => DoTree(tree.Right, action));
action(tree.Data);

try
{
Task.WaitAll(left, right);
}
catch (AggregateException )
{
//handle exceptions here
}
}

// By using Parallel.Invoke
public static void DoTree2<T>(Tree<T> tree, Action<T> action)
{
if (tree == null) return;
Parallel.Invoke(
() => DoTree2(tree.Left, action),
() => DoTree2(tree.Right, action),
() => action(tree.Data)
);
}
}
Imports System.Threading.Tasks

Public Class TreeWalk

Shared Sub Main()

Dim tree As Tree(Of Person) = New Tree(Of Person)()

' ...populate tree (left as an exercise)

' Define the Action to perform on each node.


Dim myAction As Action(Of Person) = New Action(Of Person)(Sub(x)
Console.WriteLine("{0} : {1} ", x.Name,
x.Number)
End Sub)

' Traverse the tree with parallel tasks.


DoTree(tree, myAction)
End Sub

Public Class Person


Public Name As String
Public Number As Integer
End Class

Public Class Tree(Of T)


Public Left As Tree(Of T)
Public Right As Tree(Of T)
Public Data As T
End Class

' By using tasks explicitly.


Public Shared Sub DoTree(Of T)(ByVal myTree As Tree(Of T), ByVal a As Action(Of T))
If myTree Is Nothing Then
Return
End If
Dim left = Task.Factory.StartNew(Sub() DoTree(myTree.Left, a))
Dim right = Task.Factory.StartNew(Sub() DoTree(myTree.Right, a))
a(myTree.Data)

Try
Task.WaitAll(left, right)
Catch ae As AggregateException
'handle exceptions here
End Try
End Sub

' By using Parallel.Invoke


Public Shared Sub DoTree2(Of T)(ByVal myTree As Tree(Of T), ByVal myAct As Action(Of T))
If myTree Is Nothing Then
Return
End If
Parallel.Invoke(
Sub() DoTree2(myTree.Left, myAct),
Sub() DoTree2(myTree.Right, myAct),
Sub() myAct(myTree.Data)
)
End Sub
End Class

Los dos métodos que se muestran son funcionalmente equivalentes. Con el uso del método StartNew para crear y
ejecutar las tareas, estas devuelven un identificador que puede usarse para esperar las tareas y controlar
excepciones.

Vea también
Biblioteca TPL
Cómo: Desencapsular una tarea anidada
16/09/2020 • 5 minutes to read • Edit Online

Puede devolver una tarea de un método y, a continuación, esperar o continuar a partir de dicha tarea, tal como se
muestra en el ejemplo siguiente:

static Task<string> DoWorkAsync()


{
return Task<String>.Factory.StartNew(() =>
{
//...
return "Work completed.";
});
}

static void StartTask()


{
Task<String> t = DoWorkAsync();
t.Wait();
Console.WriteLine(t.Result);
}

Shared Function DoWorkAsync() As Task(Of String)

Return Task(Of String).Run(Function()


'...
Return "Work completed."
End Function)
End Function

Shared Sub StartTask()

Dim t As Task(Of String) = DoWorkAsync()


t.Wait()
Console.WriteLine(t.Result)
End Sub

En el ejemplo anterior, la propiedad Result es de tipo string ( String en Visual Basic).


Sin embargo, en algunos escenarios, puede crear una tarea dentro de otra tarea y, a continuación, devolver la tarea
anidada. En este caso, el delegado TResult de la tarea indicada es una tarea. En el ejemplo siguiente, la propiedad
Result es un delegado Task<Task<string>> en C# o Task(Of Task(Of String)) en Visual Basic.

// Note the type of t and t2.


Task<Task<string>> t = Task.Factory.StartNew(() => DoWorkAsync());
Task<Task<string>> t2 = DoWorkAsync().ContinueWith((s) => DoMoreWorkAsync());

// Outputs: System.Threading.Tasks.Task`1[System.String]
Console.WriteLine(t.Result);
' Note the type of t and t2.
Dim t As Task(Of Task(Of String)) = Task.Run(Function() DoWorkAsync())
Dim t2 As Task(Of Task(Of String)) = DoWorkAsync().ContinueWith(Function(s) DoMoreWorkAsync())

' Outputs: System.Threading.Tasks.Task`1[System.String]


Console.WriteLine(t.Result)

Aunque es posible escribir código para desajustar la tarea exterior y recuperar la tarea original y su propiedad
Result, este código no es fácil de escribir porque se deben controlar las excepciones y también las solicitudes de
cancelación. En esta situación, se recomienda que use uno de los métodos de extensión Unwrap, como se muestra
en el ejemplo siguiente.

// Unwrap the inner task.


Task<string> t3 = DoWorkAsync().ContinueWith((s) => DoMoreWorkAsync()).Unwrap();

// Outputs "More work completed."


Console.WriteLine(t.Result);

' Unwrap the inner task.


Dim t3 As Task(Of String) = DoWorkAsync().ContinueWith(Function(s) DoMoreWorkAsync()).Unwrap()

' Outputs "More work completed."


Console.WriteLine(t.Result)

Los métodos Unwrap se pueden usar para transformar cualquier delegado Task<Task> o Task<Task<TResult>> (
Task(Of Task) o Task(Of Task(Of TResult)) en Visual Basic) en un delegado Task o Task<TResult> (
Task(Of TResult) en Visual Basic). La nueva tarea representa totalmente la tarea anidada interna e incluye el
estado de cancelación y todas las excepciones.

Ejemplo
En el ejemplo siguiente se muestra cómo usar los métodos de extensión Unwrap.

namespace Unwrap
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
// A program whose only use is to demonstrate Unwrap.
class Program
{
static void Main()
{
// An arbitrary threshold value.
byte threshold = 0x40;

// data is a Task<byte[]>
var data = Task<byte[]>.Factory.StartNew(() =>
{
return GetData();
});

// We want to return a task so that we can


// continue from it later in the program.
// Without Unwrap: stepTwo is a Task<Task<byte[]>>
// With Unwrap: stepTwo is a Task<byte[]>
// With Unwrap: stepTwo is a Task<byte[]>
var stepTwo = data.ContinueWith((antecedent) =>
{
return Task<byte>.Factory.StartNew( () => Compute(antecedent.Result));
})
.Unwrap();

// Without Unwrap: antecedent.Result = Task<byte>


// and the following method will not compile.
// With Unwrap: antecedent.Result = byte and
// we can work directly with the result of the Compute method.
var lastStep = stepTwo.ContinueWith( (antecedant) =>
{
if (antecedant.Result >= threshold)
{
return Task.Factory.StartNew( () => Console.WriteLine("Program complete. Final =
0x{0:x} threshold = 0x{1:x}", stepTwo.Result, threshold));
}
else
{
return DoSomeOtherAsyncronousWork(stepTwo.Result, threshold);
}
});

lastStep.Wait();
Console.WriteLine("Press any key");
Console.ReadKey();
}

#region Dummy_Methods
private static byte[] GetData()
{
Random rand = new Random();
byte[] bytes = new byte[64];
rand.NextBytes(bytes);
return bytes;
}

static Task DoSomeOtherAsyncronousWork(int i, byte b2)


{
return Task.Factory.StartNew(() =>
{
Thread.SpinWait(500000);
Console.WriteLine("Doing more work. Value was <= threshold");
});
}
static byte Compute(byte[] data)
{

byte final = 0;
foreach (byte item in data)
{
final ^= item;
Console.WriteLine("{0:x}", final);
}
Console.WriteLine("Done computing");
return final;
}
#endregion
}
}

'How to: Unwrap a Task


Imports System.Threading
Imports System.Threading.Tasks

Module UnwrapATask2
Sub Main()
' An arbitrary threshold value.
Dim threshold As Byte = &H40

' myData is a Task(Of Byte())

Dim myData As Task(Of Byte()) = Task.Factory.StartNew(Function()


Return GetData()
End Function)
' We want to return a task so that we can
' continue from it later in the program.
' Without Unwrap: stepTwo is a Task(Of Task(Of Byte))
' With Unwrap: stepTwo is a Task(Of Byte)

Dim stepTwo = myData.ContinueWith(Function(antecedent)


Return Task.Factory.StartNew(Function()
Return
Compute(antecedent.Result)
End Function)
End Function).Unwrap()

Dim lastStep = stepTwo.ContinueWith(Function(antecedent)


Console.WriteLine("Result = {0}", antecedent.Result)
If antecedent.Result >= threshold Then
Return Task.Factory.StartNew(Sub()

Console.WriteLine("Program complete. Final = &H{1:x} threshold = &H{1:x}",

stepTwo.Result, threshold)
End Sub)
Else
Return DoSomeOtherAsynchronousWork(stepTwo.Result,
threshold)
End If
End Function)
Try
lastStep.Wait()
Catch ae As AggregateException
For Each ex As Exception In ae.InnerExceptions
Console.WriteLine(ex.Message & ex.StackTrace & ex.GetBaseException.ToString())
Next
End Try

Console.WriteLine("Press any key")


Console.ReadKey()
End Sub

#Region "Dummy_Methods"
Function GetData() As Byte()
Dim rand As Random = New Random()
Dim bytes(64) As Byte
rand.NextBytes(bytes)
Return bytes
End Function

Function DoSomeOtherAsynchronousWork(ByVal i As Integer, ByVal b2 As Byte) As Task


Return Task.Factory.StartNew(Sub()
Thread.SpinWait(500000)
Console.WriteLine("Doing more work. Value was <= threshold.")
End Sub)
End Function

Function Compute(ByVal d As Byte()) As Byte


Dim final As Byte = 0
For Each item As Byte In d
final = final Xor item
Console.WriteLine("{0:x}", final)
Next
Console.WriteLine("Done computing")
Console.WriteLine("Done computing")
Return final
End Function
#End Region
End Module

Vea también
System.Threading.Tasks.TaskExtensions
Programación asincrónica basada en tareas
Cómo: Evitar que una tarea secundaria se adjunte a
su elemento primario
16/09/2020 • 6 minutes to read • Edit Online

En este documento se explica cómo evitar que una tarea secundaria se adjunte a la tarea principal. Impedir que una
tarea secundaria se adjunte a su elemento principal es útil cuando se llama a un componente que está escrito por
un tercero y que también usa las tareas. Por ejemplo, un componente de terceros que utiliza la opción
TaskCreationOptions.AttachedToParent para crear un objeto Task o Task<TResult> puede causar problemas en el
código si es de larga duración o produce una excepción no controlada.

Ejemplo
En el ejemplo siguiente se comparan los efectos del uso de las opciones predeterminadas con los efectos de
impedir que una tarea secundaria se adjunte al elemento principal. En el ejemplo se crea un objeto Task que llama
a una biblioteca de terceros que también usa un objeto Task. La biblioteca de otro fabricante utiliza la opción
AttachedToParent para crear el objeto Task. La aplicación utiliza la opción TaskCreationOptions.DenyChildAttach
para crear la tarea principal. Esta opción indica el tiempo de ejecución para quitar la especificación
AttachedToParent de las tareas secundarias.

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

// Defines functionality that is provided by a third-party.


// In a real-world scenario, this would likely be provided
// in a separate code file or assembly.
namespace Contoso
{
public class Widget
{
public Task Run()
{
// Create a long-running task that is attached to the
// parent in the task hierarchy.
return Task.Factory.StartNew(() =>
{
// Simulate a lengthy operation.
Thread.Sleep(5000);
}, TaskCreationOptions.AttachedToParent);
}
}
}

// Demonstrates how to prevent a child task from attaching to the parent.


class DenyChildAttach
{
static void RunWidget(Contoso.Widget widget,
TaskCreationOptions parentTaskOptions)
{
// Record the time required to run the parent
// and child tasks.
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

Console.WriteLine("Starting widget as a background task...");


// Run the widget task in the background.
Task<Task> runWidget = Task.Factory.StartNew(() =>
{
Task widgetTask = widget.Run();

// Perform other work while the task runs...


Thread.Sleep(1000);

return widgetTask;
}, parentTaskOptions);

// Wait for the parent task to finish.


Console.WriteLine("Waiting for parent task to finish...");
runWidget.Wait();
Console.WriteLine("Parent task has finished. Elapsed time is {0} ms.",
stopwatch.ElapsedMilliseconds);

// Perform more work...


Console.WriteLine("Performing more work on the main thread...");
Thread.Sleep(2000);
Console.WriteLine("Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds);

// Wait for the child task to finish.


Console.WriteLine("Waiting for child task to finish...");
runWidget.Result.Wait();
Console.WriteLine("Child task has finished. Elapsed time is {0} ms.",
stopwatch.ElapsedMilliseconds);
}

static void Main(string[] args)


{
Contoso.Widget w = new Contoso.Widget();

// Perform the same operation two times. The first time, the operation
// is performed by using the default task creation options. The second
// time, the operation is performed by using the DenyChildAttach option
// in the parent task.

Console.WriteLine("Demonstrating parent/child tasks with default options...");


RunWidget(w, TaskCreationOptions.None);

Console.WriteLine();

Console.WriteLine("Demonstrating parent/child tasks with the DenyChildAttach option...");


RunWidget(w, TaskCreationOptions.DenyChildAttach);
}
}

/* Sample output:
Demonstrating parent/child tasks with default options...
Starting widget as a background task...
Waiting for parent task to finish...
Parent task has finished. Elapsed time is 5014 ms.
Performing more work on the main thread...
Elapsed time is 7019 ms.
Waiting for child task to finish...
Child task has finished. Elapsed time is 7019 ms.

Demonstrating parent/child tasks with the DenyChildAttach option...


Starting widget as a background task...
Waiting for parent task to finish...
Parent task has finished. Elapsed time is 1007 ms.
Performing more work on the main thread...
Elapsed time is 3015 ms.
Waiting for child task to finish...
Child task has finished. Elapsed time is 5015 ms.
*/
Imports System.Diagnostics
Imports System.Threading
Imports System.Threading.Tasks

' Defines functionality that is provided by a third-party.


' In a real-world scenario, this would likely be provided
' in a separate code file or assembly.
Namespace Contoso
Public Class Widget
Public Function Run() As Task
' Create a long-running task that is attached to the
' parent in the task hierarchy.
Return Task.Factory.StartNew(Sub() Thread.Sleep(5000), TaskCreationOptions.AttachedToParent)
' Simulate a lengthy operation.
End Function
End Class
End Namespace

' Demonstrates how to prevent a child task from attaching to the parent.
Friend Class DenyChildAttach
Private Shared Sub RunWidget(ByVal widget As Contoso.Widget, ByVal parentTaskOptions As
TaskCreationOptions)
' Record the time required to run the parent
' and child tasks.
Dim stopwatch As New Stopwatch()
stopwatch.Start()

Console.WriteLine("Starting widget as a background task...")

' Run the widget task in the background.


Dim runWidget As Task(Of Task) = Task.Factory.StartNew(Function()
' Perform other work while the task runs...
Dim widgetTask As Task = widget.Run()
Thread.Sleep(1000)
Return widgetTask
End Function, parentTaskOptions)

' Wait for the parent task to finish.


Console.WriteLine("Waiting for parent task to finish...")
runWidget.Wait()
Console.WriteLine("Parent task has finished. Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds)

' Perform more work...


Console.WriteLine("Performing more work on the main thread...")
Thread.Sleep(2000)
Console.WriteLine("Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds)

' Wait for the child task to finish.


Console.WriteLine("Waiting for child task to finish...")
runWidget.Result.Wait()
Console.WriteLine("Child task has finished. Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds)
End Sub

Shared Sub Main(ByVal args() As String)


Dim w As New Contoso.Widget()

' Perform the same operation two times. The first time, the operation
' is performed by using the default task creation options. The second
' time, the operation is performed by using the DenyChildAttach option
' in the parent task.

Console.WriteLine("Demonstrating parent/child tasks with default options...")


RunWidget(w, TaskCreationOptions.None)

Console.WriteLine()

Console.WriteLine("Demonstrating parent/child tasks with the DenyChildAttach option...")


RunWidget(w, TaskCreationOptions.DenyChildAttach)
End Sub
End Class

' Sample output:


'Demonstrating parent/child tasks with default options...
'Starting widget as a background task...
'Waiting for parent task to finish...
'Parent task has finished. Elapsed time is 5014 ms.
'Performing more work on the main thread...
'Elapsed time is 7019 ms.
'Waiting for child task to finish...
'Child task has finished. Elapsed time is 7019 ms.
'
'Demonstrating parent/child tasks with the DenyChildAttach option...
'Starting widget as a background task...
'Waiting for parent task to finish...
'Parent task has finished. Elapsed time is 1007 ms.
'Performing more work on the main thread...
'Elapsed time is 3015 ms.
'Waiting for child task to finish...
'Child task has finished. Elapsed time is 5015 ms.
'

Dado que una tarea primaria no finaliza hasta que finalizan todas las tareas secundarias, una tarea secundaria que
se ejecute durante mucho tiempo puede provocar que el rendimiento general de la aplicación sea bajo. En este
ejemplo, cuando la aplicación usa las opciones predeterminadas para crear la tarea principal, la tarea secundaria
debe finalizar antes de que finalice la principal. Cuando la aplicación utiliza la opción
TaskCreationOptions.DenyChildAttach, el elemento secundario no está asociado al principal. Por lo tanto, la
aplicación puede realizar un trabajo adicional después de que finalice la tarea principal y antes debe esperar a que
finalice la tarea secundaria.

Vea también
Programación asincrónica basada en tareas
Flujo de datos (biblioteca TPL)
16/09/2020 • 68 minutes to read • Edit Online

La biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) proporciona
componentes de flujo de datos que ayudan a aumentar la solidez de aplicaciones habilitadas para
simultaneidad. Se conoce colectivamente a estos componentes de flujo de datos como biblioteca TDF (biblioteca
de TPL Dataflow) pero nos referiremos descriptivamente a ella como "biblioteca de flujos de datos TPL". Este
modelo de flujo de datos promueve una programación basada en actores mediante el paso de mensajes en
proceso para tareas de canalización y de flujo de datos de grano grueso. Los componentes de flujo de datos se
basan en los tipos y la infraestructura de programación de la biblioteca TPL y se integran con la compatibilidad
de los lenguajes C#, Visual Basic y F# para proporcionar programación asincrónica. Estos componentes de flujo
de datos son útiles cuando se tienen varias operaciones que deben comunicarse entre sí de forma asincrónica, o
cuando se desea procesar datos a medida que estén disponibles. Por ejemplo, piense en una aplicación que
procesa datos de imagen de una cámara web. Con el modelo de flujo de datos, la aplicación puede procesar
fotogramas de imagen a medida que estén disponibles. Si la aplicación mejora fotogramas de imagen, por
ejemplo, corrigiendo la luz o reduciendo ojos rojos, puede crear una canalización de los componentes de flujo
de datos. Cada fase de la canalización puede utilizar más funcionalidad de paralelismo de grano grueso, como la
funcionalidad proporcionada por la biblioteca TPL, para transformar la imagen.
En este documento se proporciona información general sobre la biblioteca de flujos de datos TPL. Se describe el
modelo de programación, los tipos de bloques de flujo de datos predefinidos y cómo configurar bloques de
flujo de datos para satisfacer las necesidades específicas de las aplicaciones.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET.
Para instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione
Administrar paquetes NuGet en el menú Proyecto y busque en línea el paquete
System.Threading.Tasks.Dataflow . Como alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Modelo de programación
La biblioteca de flujos de datos TPL proporciona una base para el paso de mensajes y para paralelizar
aplicaciones que consumen mucha CPU, así como aplicaciones intensivas de entrada y salida con alto
rendimiento y latencia baja. También ofrece el control explícito sobre cómo almacenar los datos en búfer y
desplazarlos alrededor del sistema. Para entender mejor el modelo de programación de flujo de datos, piense
en una aplicación que de forma asincrónica carga imágenes desde el disco y crea un compuesto de esas
imágenes. Los modelos de programación tradicionales suelen usar devoluciones de llamada y objetos de
sincronización, como bloqueos, para coordinar tareas y tener acceso a datos compartidos. Con el modelo de
programación de flujo de datos, puede crear objetos de flujo de datos que procesan las imágenes como se leen
desde el disco. Bajo el modelo de flujo de datos, se declara cómo se controlan los datos cuando están
disponibles, así como las dependencias entre datos. Dado que el runtime administra las dependencias entre
datos, se puede evitar a menudo la necesidad de sincronizar el acceso a los datos compartidos. Además, dado
que el runtime programa el trabajo según la llegada asincrónica de datos, el flujo de datos puede mejorar la
capacidad de respuesta y el nivel de rendimiento administrando de forma eficaz los subprocesos subyacentes.
Para ver un ejemplo en donde se usa el modelo de programación de flujo de datos para implementar
procesamiento de imágenes en una aplicación de Windows Forms, vea Tutorial: Uso de flujos de datos en
aplicaciones de Windows Forms.
Orígenes y destinos
La biblioteca de flujos de datos TPL consta de bloques de flujo de datos, que son estructuras de datos que
almacenan datos en búfer y procesan datos. La biblioteca TPL define tres tipos de bloques de flujos de datos:
bloques de origen, bloques de destino y bloques propagadores. Un bloque de origen actúa como un origen de
datos y se puede leer desde él. Un bloque de destino actúa como un receptor de datos y se puede escribir en él.
Un bloque propagador actúa como un bloque de origen y un bloque de destino, y se puede leer desde él y
escribir en él. La biblioteca TPL define la interfaz System.Threading.Tasks.Dataflow.ISourceBlock<TOutput> para
representar orígenes, System.Threading.Tasks.Dataflow.ITargetBlock<TInput> para representar destinos y
System.Threading.Tasks.Dataflow.IPropagatorBlock<TInput,TOutput> para representar propagadores.
IPropagatorBlock<TInput,TOutput> hereda de ISourceBlock<TOutput> y de ITargetBlock<TInput>.
La biblioteca de flujos de datos TPL proporciona varios tipos de bloques de flujo de datos predefinidos que
implementan las interfaces ISourceBlock<TOutput>, ITargetBlock<TInput> y
IPropagatorBlock<TInput,TOutput>. Estos tipos de bloques de flujo de datos se describen en este documento en
la sección Tipos de bloques de flujo de datos predefinidos.
Conectar bloques
Puede conectar los bloques de flujo de datos para establecer canalizaciones, que son secuencias lineales de
bloques de flujo de datos, o redes, que son gráficos de bloques de flujo de datos. Una canalización es una forma
de red. En una canalización o red, los orígenes propagan datos de forma asincrónica en destinos a medida que
esos datos están disponibles. El método ISourceBlock<TOutput>.LinkTo vincula un bloque de flujo de datos de
origen a un bloque de destino. Un origen puede vincularse a cero o más destinos; los destinos se pueden
vincular a partir de cero o más orígenes. Puede agregar o quitar bloques de flujo de datos hacia o desde una
canalización o red simultáneamente. Los tipos de bloques de flujo de datos predefinidos controlan todos los
aspectos de la seguridad para subprocesos de vinculación y desvinculación.
Para ver un ejemplo en donde se conectan bloques de flujo de datos para formar una canalización básica, vea
Tutorial: Creación de una canalización de flujos de datos. Para ver un ejemplo en donde se conectan bloques de
flujo de datos para formar una red más compleja, vea Tutorial: Uso de flujos de datos en aplicaciones de
Windows Forms. Para obtener un ejemplo en donde un destino se desvincula de un origen después de que el
origen le ofrezca un mensaje, vea Procedimiento: Desvinculación de bloques de flujo de datos.
Filtrado
Cuando se llama al método ISourceBlock<TOutput>.LinkTo para vincular un origen a un destino, puede
proporcionar un delegado que determina si el bloque de destino acepta o rechaza un mensaje basado en el
valor de ese mensaje. Este mecanismo de filtrado resulta útil para garantizar que un bloque de flujo de datos
recibe solo ciertos valores. Para la mayoría de los tipos de bloques de flujo de datos predefinidos, si un bloque
de origen está conectado a varios bloques de destino, cuando un bloque de destino rechaza un mensaje, el
origen proporciona ese mensaje al destino siguiente. El orden en el que un origen proporciona mensajes a los
destinos se define mediante el origen y puede variar según el tipo de origen. La mayoría de los tipos de bloques
de origen dejan de proporcionar un mensaje después de que un destino acepta ese mensaje. Una excepción a
esta regla es la clase BroadcastBlock<T>, que proporciona cada mensaje a todos los destinos, aunque algunos
destinos rechacen el mensaje. Para ver un ejemplo en donde se usa el filtrado para procesar únicamente
determinados mensajes, consulte Tutorial: Uso de flujos de datos en aplicaciones de Windows Forms.

IMPORTANT
Dado que cada tipo de bloque de flujo de datos de origen predefinido garantiza que los mensajes se propaguen en el
orden en que se reciben, se debe leer cada mensaje desde el bloque de origen antes de que el bloque de origen pueda
procesar el mensaje siguiente. Por consiguiente, si usa el filtrado para conectarse varios destinos a un origen, asegúrese
de que al menos un bloque de destino recibe cada mensaje. De lo contrario, la aplicación podría generar un interbloqueo.

Paso de mensajes
El modelo de programación basado en el flujo de datos está relacionado con el concepto paso de mensajes,
donde los componentes independientes de un programa comunican entre sí enviándose mensajes. Una manera
de propagar mensajes entre los componentes de la aplicación es llamar a los métodos Post y
DataflowBlock.SendAsync para enviar mensajes a los bloques de flujo de datos de destino (Post actúa de forma
sincrónica; SendAsync actúa de forma asincrónica) y a los métodos Receive, ReceiveAsync y TryReceive para
recibir los mensajes desde los bloques de origen. Puede combinar estos métodos con las canalizaciones o redes
de flujo de datos enviando datos de entrada al nodo principal (un bloque de destino) y recibiendo datos de
salida del nodo terminal de la canalización o de los nodos terminales de la red (uno o varios bloques de origen).
También puede utilizar el método Choose para leer desde el primero de los orígenes proporcionados siempre
que tenga datos disponibles y realice acciones sobre esos datos.
Los bloques de origen proporcionan datos a los bloques de destino llamando al método
ITargetBlock<TInput>.OfferMessage. El bloque de destino responde a un mensaje proporcionado de una de
estas tres maneras: puede aceptar el mensaje, rechazar el mensaje o posponer el mensaje. Cuando el destino
acepta el mensaje, el método OfferMessage devuelve Accepted. Cuando el destino rechaza el mensaje, el
método OfferMessage devuelve Declined. Cuando el destino requiere que ya no recibe ningún mensaje del
origen, OfferMessage devuelve DecliningPermanently. Los tipos de bloques de origen predefinidos no
proporcionan mensajes a los destinos vinculados después de recibir este valor devuelto, y automáticamente se
desvinculan de estos destinos.
Cuando un bloque de destino pospone el mensaje para su uso posterior, el método OfferMessage devuelve
Postponed. Un bloque de destino que pospone un mensaje puede llamar posteriormente al método
ISourceBlock<TOutput>.ReserveMessage para tratar de reservar el mensaje proporcionado. En este punto, el
mensaje todavía permanece disponible y lo puede usar el bloque de destino, o puede que otro destino haya
tomado el mensaje. Cuando el bloque de destino requiere el mensaje posteriormente o cuando ya no necesita
el mensaje, llama al método ISourceBlock<TOutput>.ConsumeMessage o ReleaseReservation, respectivamente.
La reserva de mensajes la utilizan normalmente los tipos de bloques de flujo de datos que trabajan en modo no
expansivo. El modo no expansivo se explica más adelante en este documento. En lugar de reservar un mensaje
pospuesto, un bloque de destino puede utilizar el método ISourceBlock<TOutput>.ConsumeMessage para
intentar utilizar directamente el mensaje pospuesto.
Finalización del bloque de flujo de datos
Los bloques de flujo de datos también admiten el concepto de finalización. Un bloque de flujo de datos que está
en estado completado no realiza ningún trabajo posterior. Cada bloque de flujo de datos tiene un objeto
System.Threading.Tasks.Task asociado, conocido como tarea de finalización, que representa el estado de
finalización del bloque. Dado que para finalizar se puede esperar un objeto Task, mediante tareas de finalización,
para finalizar se pueden esperar uno o más nodos terminales de una red de flujo de datos. La interfaz
IDataflowBlock define el método Complete, que informa al bloque de flujo de datos de una solicitud para que se
complete, y la propiedad Completion, que devuelve la tarea de finalización para el bloque de flujo de datos.
Tanto ISourceBlock<TOutput> como ITargetBlock<TInput> heredan de la interfaz IDataflowBlock.
Hay dos maneras de determinar si un bloque de flujo de datos se completó sin error, encontró uno o más
errores, o se canceló. La primera manera consiste en llamar al método Task.Wait en la tarea de finalización en un
bloque try - catch ( Try - Catch en Visual Basic). En el siguiente ejemplo se crea un objeto
ActionBlock<TInput> que produce ArgumentOutOfRangeException si su valor de entrada es menor que cero.
AggregateException se produce cuando este ejemplo llama a Wait en la tarea de finalización. Se obtiene acceso
a ArgumentOutOfRangeException mediante la propiedad InnerExceptions del objeto AggregateException.
// Create an ActionBlock<int> object that prints its input
// and throws ArgumentOutOfRangeException if the input
// is less than zero.
var throwIfNegative = new ActionBlock<int>(n =>
{
Console.WriteLine("n = {0}", n);
if (n < 0)
{
throw new ArgumentOutOfRangeException();
}
});

// Post values to the block.


throwIfNegative.Post(0);
throwIfNegative.Post(-1);
throwIfNegative.Post(1);
throwIfNegative.Post(-2);
throwIfNegative.Complete();

// Wait for completion in a try/catch block.


try
{
throwIfNegative.Completion.Wait();
}
catch (AggregateException ae)
{
// If an unhandled exception occurs during dataflow processing, all
// exceptions are propagated through an AggregateException object.
ae.Handle(e =>
{
Console.WriteLine("Encountered {0}: {1}",
e.GetType().Name, e.Message);
return true;
});
}

/* Output:
n = 0
n = -1
Encountered ArgumentOutOfRangeException: Specified argument was out of the range
of valid values.
*/
' Create an ActionBlock<int> object that prints its input
' and throws ArgumentOutOfRangeException if the input
' is less than zero.
Dim throwIfNegative = New ActionBlock(Of Integer)(Sub(n)
Console.WriteLine("n = {0}", n)
If n < 0 Then
Throw New ArgumentOutOfRangeException()
End If
End Sub)

' Post values to the block.


throwIfNegative.Post(0)
throwIfNegative.Post(-1)
throwIfNegative.Post(1)
throwIfNegative.Post(-2)
throwIfNegative.Complete()

' Wait for completion in a try/catch block.


Try
throwIfNegative.Completion.Wait()
Catch ae As AggregateException
' If an unhandled exception occurs during dataflow processing, all
' exceptions are propagated through an AggregateException object.
ae.Handle(Function(e)
Console.WriteLine("Encountered {0}: {1}", e.GetType().Name, e.Message)
Return True
End Function)
End Try

' Output:
' n = 0
' n = -1
' Encountered ArgumentOutOfRangeException: Specified argument was out of the range
' of valid values.
'

En este ejemplo se muestra el caso en el que una excepción no está controlada en el delegado de un bloque de
flujo de datos de ejecución. Se recomienda controlar las excepciones en los cuerpos de estos bloques. Sin
embargo, si no puede hacerlo, el bloque se comporta como si estuviera cancelado y no procesa los mensajes
entrantes.
Cuando un bloque de flujo de datos se cancela explícitamente, el objeto AggregateException contiene
OperationCanceledException en la propiedad InnerExceptions. Para obtener más información sobre la
cancelación del flujo de datos, vea la sección Habilitar la cancelación.
La segunda manera de determinar el estado de finalización de un bloque de flujo de datos es usar una
continuación de la tarea de finalización o utilizar las características de lenguaje asincrónicas de C# y Visual Basic
para esperar a la tarea de finalización de forma asincrónica. El delegado que se proporciona al método
Task.ContinueWith toma un objeto Task que representa la tarea anterior. En el caso de la propiedad Completion,
el delegado de continuación toma la propia tarea de finalización. El siguiente ejemplo se parece el anterior, con
la salvedad de que también utiliza el método ContinueWith para crear una tarea de continuación que imprime
el estado de la operación total de flujo de datos.
// Create an ActionBlock<int> object that prints its input
// and throws ArgumentOutOfRangeException if the input
// is less than zero.
var throwIfNegative = new ActionBlock<int>(n =>
{
Console.WriteLine("n = {0}", n);
if (n < 0)
{
throw new ArgumentOutOfRangeException();
}
});

// Create a continuation task that prints the overall


// task status to the console when the block finishes.
throwIfNegative.Completion.ContinueWith(task =>
{
Console.WriteLine("The status of the completion task is '{0}'.",
task.Status);
});

// Post values to the block.


throwIfNegative.Post(0);
throwIfNegative.Post(-1);
throwIfNegative.Post(1);
throwIfNegative.Post(-2);
throwIfNegative.Complete();

// Wait for completion in a try/catch block.


try
{
throwIfNegative.Completion.Wait();
}
catch (AggregateException ae)
{
// If an unhandled exception occurs during dataflow processing, all
// exceptions are propagated through an AggregateException object.
ae.Handle(e =>
{
Console.WriteLine("Encountered {0}: {1}",
e.GetType().Name, e.Message);
return true;
});
}

/* Output:
n = 0
n = -1
The status of the completion task is 'Faulted'.
Encountered ArgumentOutOfRangeException: Specified argument was out of the range
of valid values.
*/
' Create an ActionBlock<int> object that prints its input
' and throws ArgumentOutOfRangeException if the input
' is less than zero.
Dim throwIfNegative = New ActionBlock(Of Integer)(Sub(n)
Console.WriteLine("n = {0}", n)
If n < 0 Then
Throw New ArgumentOutOfRangeException()
End If
End Sub)

' Create a continuation task that prints the overall


' task status to the console when the block finishes.
throwIfNegative.Completion.ContinueWith(Sub(task) Console.WriteLine("The status of the completion task is
'{0}'.", task.Status))

' Post values to the block.


throwIfNegative.Post(0)
throwIfNegative.Post(-1)
throwIfNegative.Post(1)
throwIfNegative.Post(-2)
throwIfNegative.Complete()

' Wait for completion in a try/catch block.


Try
throwIfNegative.Completion.Wait()
Catch ae As AggregateException
' If an unhandled exception occurs during dataflow processing, all
' exceptions are propagated through an AggregateException object.
ae.Handle(Function(e)
Console.WriteLine("Encountered {0}: {1}", e.GetType().Name, e.Message)
Return True
End Function)
End Try

' Output:
' n = 0
' n = -1
' The status of the completion task is 'Faulted'.
' Encountered ArgumentOutOfRangeException: Specified argument was out of the range
' of valid values.
'

También puede usar propiedades como IsCanceled en el cuerpo de la tarea de continuación para determinar
información adicional sobre el estado de finalización de un bloque de flujo de datos. Para obtener más
información sobre las tareas de continuación y cómo se relacionan con la cancelación y el control de errores,
vea Encadenar tareas mediante tareas de continuación, Cancelación de tareas y Control de excepciones.

Tipos de bloques de flujo de datos predefinidos


La biblioteca de flujos de datos TPL proporciona varios tipos de bloques de flujo de datos predefinidos. Estos
tipos se dividen en tres categorías: bloques de almacenamiento en búfer, bloques de ejecución y bloques de
agrupación. En las secciones siguientes se describen los tipos de bloques que componen estas categorías.
Bloques de almacenamiento en búfer
Los bloques de almacenamiento en búfer contiene datos para su uso por los consumidores de datos. La
biblioteca de flujos de datos TPL proporciona tres tipos de bloques de almacenamiento en búfer:
System.Threading.Tasks.Dataflow.BufferBlock<T>, System.Threading.Tasks.Dataflow.BroadcastBlock<T> y
System.Threading.Tasks.Dataflow.WriteOnceBlock<T>.
BufferBlock(T)
La clase BufferBlock<T> representa una estructura de mensajería asincrónica de uso general. Esta clase
almacena una cola FIFO (primero en entrar, primero en salir) de mensajes donde varios orígenes pueden
escribir o de los que varios destinos pueden leer. Cuando un destino recibe un mensaje de un objeto
BufferBlock<T>, ese mensaje se quita de la cola de mensajes. Por tanto, aunque un objeto BufferBlock<T>
puede tener varios destinos, solo uno recibirá cada mensaje. La clase BufferBlock<T> resulta útil si desea pasar
varios mensajes a otro componente, y ese componente debe recibir cada mensaje.
En el siguiente ejemplo básico se exponen varios valores Int32 a un objeto BufferBlock<T> y después se leen
esos valores desde ese objeto.

// Create a BufferBlock<int> object.


var bufferBlock = new BufferBlock<int>();

// Post several messages to the block.


for (int i = 0; i < 3; i++)
{
bufferBlock.Post(i);
}

// Receive the messages back from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(bufferBlock.Receive());
}

/* Output:
0
1
2
*/

' Create a BufferBlock<int> object.


Dim bufferBlock = New BufferBlock(Of Integer)()

' Post several messages to the block.


For i As Integer = 0 To 2
bufferBlock.Post(i)
Next i

' Receive the messages back from the block.


For i As Integer = 0 To 2
Console.WriteLine(bufferBlock.Receive())
Next i

' Output:
' 0
' 1
' 2
'

Para obtener un ejemplo completo en donde se muestra cómo escribir y leer mensajes desde un
objeto BufferBlock<T>, vea Procedimiento: Escritura y lectura de mensajes en un bloque de flujo de datos.
BroadcastBlock(T)
La clase BroadcastBlock<T> resulta útil si debe pasar varios mensajes a otro componente, pero este
componente solo necesita el valor más reciente. Esta clase también resulta útil si desea difundir un mensaje a
varios componentes.
En el siguiente ejemplo básico se publica un valor Double a un objeto BroadcastBlock<T> y después se lee ese
valor desde el objeto varias veces. Dado que los valores no se quitan de los objetos BroadcastBlock<T> después
de leerlos, el mismo valor está disponible cada vez.
// Create a BroadcastBlock<double> object.
var broadcastBlock = new BroadcastBlock<double>(null);

// Post a message to the block.


broadcastBlock.Post(Math.PI);

// Receive the messages back from the block several times.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(broadcastBlock.Receive());
}

/* Output:
3.14159265358979
3.14159265358979
3.14159265358979
*/

' Create a BroadcastBlock<double> object.


Dim broadcastBlock = New BroadcastBlock(Of Double)(Nothing)

' Post a message to the block.


broadcastBlock.Post(Math.PI)

' Receive the messages back from the block several times.
For i As Integer = 0 To 2
Console.WriteLine(broadcastBlock.Receive())
Next i

' Output:
' 3.14159265358979
' 3.14159265358979
' 3.14159265358979
'

Para obtener un ejemplo completo en donde se muestra cómo utilizar BroadcastBlock<T> para difundir un
mensaje a varios bloques de destino, vea Procedimiento: Especificación de un Programador de tareas en un
bloque de flujo de datos.
WriteOnceBlock(T)
La clase WriteOnceBlock<T> se asemeja a la clase BroadcastBlock<T>, salvo que un objeto WriteOnceBlock<T>
se puede escribir una sola una vez. Puede pensar que WriteOnceBlock<T> es similar a la palabra clave de C#
readonly (Readonly en Visual Basic), salvo que un objeto WriteOnceBlock<T> se vuelve inalterable después de
recibir un valor en lugar de una construcción. Al igual que la clase BroadcastBlock<T>, cuando un destino recibe
un mensaje de un objeto WriteOnceBlock<T>, el mensaje no se quita de dicho objeto. Por tanto, varios destinos
reciben una copia del mensaje. La clase WriteOnceBlock<T> es útil si desea difundir solamente el primero de
varios de mensajes.
En el siguiente ejemplo básico se exponen varios valores String a un objeto WriteOnceBlock<T> y después se
lee ese valor desde el objeto. Dado que un objeto WriteOnceBlock<T> solo se puede escribir una vez, a partir
de que un objeto WriteOnceBlock<T> recibe un mensaje, ese objeto descarta los mensajes subsiguientes.
// Create a WriteOnceBlock<string> object.
var writeOnceBlock = new WriteOnceBlock<string>(null);

// Post several messages to the block in parallel. The first


// message to be received is written to the block.
// Subsequent messages are discarded.
Parallel.Invoke(
() => writeOnceBlock.Post("Message 1"),
() => writeOnceBlock.Post("Message 2"),
() => writeOnceBlock.Post("Message 3"));

// Receive the message from the block.


Console.WriteLine(writeOnceBlock.Receive());

/* Sample output:
Message 2
*/

' Create a WriteOnceBlock<string> object.


Dim writeOnceBlock = New WriteOnceBlock(Of String)(Nothing)

' Post several messages to the block in parallel. The first


' message to be received is written to the block.
' Subsequent messages are discarded.
Parallel.Invoke(Function() writeOnceBlock.Post("Message 1"), Function() writeOnceBlock.Post("Message 2"),
Function() writeOnceBlock.Post("Message 3"))

' Receive the message from the block.


Console.WriteLine(writeOnceBlock.Receive())

' Sample output:


' Message 2
'

Para ver un ejemplo completo en donde se muestra cómo utilizar WriteOnceBlock<T> para recibir el valor de la
primera operación que finaliza, consulte Procedimiento: Desvinculación de bloques de flujo de datos.
Bloques de ejecución
Los bloques de ejecución llaman a un delegado proporcionado por el usuario para cada fragmento de datos
recibidos. La biblioteca de flujos de datos TPL proporciona tres tipos de bloques de ejecución:
ActionBlock<TInput>, System.Threading.Tasks.Dataflow.TransformBlock<TInput,TOutput> y
System.Threading.Tasks.Dataflow.TransformManyBlock<TInput,TOutput>.
ActionBlock(T)
La clase ActionBlock<TInput> es un bloque de destino que llama a un delegado cuando recibe datos. Piense en
un objeto ActionBlock<TInput> como un delegado que se ejecuta de forma asincrónica cuando los datos están
disponibles. El delegado que se proporciona a un objeto ActionBlock<TInput> puede ser de tipo Action<T> o
tipo System.Func<TInput, Task> . Si se utiliza un objeto ActionBlock<TInput> con Action<T>, se considera que el
procesamiento de cada elemento de entrada se ha completado cuando devuelve el delegado. Si se utiliza un
objeto ActionBlock<TInput> con System.Func<TInput, Task> , se considera que el procesamiento de cada
elemento de entrada se ha completado solamente cuando el objeto devuelto Task está completo. Mediante
estos dos mecanismos, se puede utilizar ActionBlock<TInput> para el procesamiento sincrónico y asincrónico
de cada elemento de entrada.
En el siguiente ejemplo básico se exponen varios valores Int32 a un objeto ActionBlock<TInput>. El objeto
ActionBlock<TInput> imprime esos valores en la consola. Después, en este ejemplo se establece el bloque en
estado completado y se espera hasta que finalicen todas las tareas de flujo de datos.
// Create an ActionBlock<int> object that prints values
// to the console.
var actionBlock = new ActionBlock<int>(n => Console.WriteLine(n));

// Post several messages to the block.


for (int i = 0; i < 3; i++)
{
actionBlock.Post(i * 10);
}

// Set the block to the completed state and wait for all
// tasks to finish.
actionBlock.Complete();
actionBlock.Completion.Wait();

/* Output:
0
10
20
*/

' Create an ActionBlock<int> object that prints values


' to the console.
Dim actionBlock = New ActionBlock(Of Integer)(Function(n) WriteLine(n))

' Post several messages to the block.


For i As Integer = 0 To 2
actionBlock.Post(i * 10)
Next i

' Set the block to the completed state and wait for all
' tasks to finish.
actionBlock.Complete()
actionBlock.Completion.Wait()

' Output:
' 0
' 10
' 20
'

Para obtener ejemplos completos en donde se muestra cómo usar delegados con la clase ActionBlock<TInput>,
vea Procedimiento: Ejecución de una acción cuando un bloque de flujo de datos recibe datos.
TransformBlock(TInput, TOutput)
La clase TransformBlock<TInput,TOutput> es similar a la clase ActionBlock<TInput>, salvo que actúa como
origen y como destino. El delegado que pasa a un objeto TransformBlock<TInput,TOutput> devuelve un valor
de tipo TOutput . El delegado que se proporciona a un objeto TransformBlock<TInput,TOutput> puede ser de
tipo System.Func<TInput, TOutput> o tipo System.Func<TInput, Task<TOutput>> . Si se utiliza un objeto
TransformBlock<TInput,TOutput> con System.Func<TInput, TOutput> , se considera que el procesamiento de
cada elemento de entrada se ha completado cuando devuelve el delegado. Si se utiliza un objeto
TransformBlock<TInput,TOutput> que se usa con System.Func<TInput, Task<TOutput>> , se considera que el
procesamiento de cada elemento de entrada se ha completado solamente cuando el objeto devuelto
Task<TResult> está completo. Al igual que sucede con ActionBlock<TInput>, mediante estos dos mecanismos,
se puede utilizar TransformBlock<TInput,TOutput> para el procesamiento sincrónico y asincrónico de cada
elemento de entrada.
En el siguiente ejemplo básico se crea un objeto TransformBlock<TInput,TOutput> que calcula la raíz cuadrada
de la entrada. El objeto TransformBlock<TInput,TOutput> toma valores Int32 como entrada y genera valores
Double como salida.
// Create a TransformBlock<int, double> object that
// computes the square root of its input.
var transformBlock = new TransformBlock<int, double>(n => Math.Sqrt(n));

// Post several messages to the block.


transformBlock.Post(10);
transformBlock.Post(20);
transformBlock.Post(30);

// Read the output messages from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(transformBlock.Receive());
}

/* Output:
3.16227766016838
4.47213595499958
5.47722557505166
*/

' Create a TransformBlock<int, double> object that


' computes the square root of its input.
Dim transformBlock = New TransformBlock(Of Integer, Double)(Function(n) Math.Sqrt(n))

' Post several messages to the block.


transformBlock.Post(10)
transformBlock.Post(20)
transformBlock.Post(30)

' Read the output messages from the block.


For i As Integer = 0 To 2
Console.WriteLine(transformBlock.Receive())
Next i

' Output:
' 3.16227766016838
' 4.47213595499958
' 5.47722557505166
'

Para obtener ejemplos completos que utilizan TransformBlock<TInput,TOutput> en una red de bloques de flujo
de datos que realiza procesamiento de imágenes en una aplicación de Windows Forms, vea Tutorial: Uso de
flujos de datos en aplicaciones de Windows Forms.
TransformManyBlock(TInput, TOutput)
La clase TransformManyBlock<TInput,TOutput> es similar a la clase TransformBlock<TInput,TOutput>, salvo
que TransformManyBlock<TInput,TOutput> genere cero o más valores de salida por cada valor de entrada, en
lugar de generar un solo valor de salida por cada valor de entrada. El delegado que se proporciona a un objeto
TransformManyBlock<TInput,TOutput> puede ser de tipo System.Func<TInput, IEnumerable<TOutput>> o tipo
System.Func<TInput, Task<IEnumerable<TOutput>>> . Si se utiliza un objeto TransformManyBlock<TInput,TOutput>
con System.Func<TInput, IEnumerable<TOutput>> , se considera que el procesamiento de cada elemento de
entrada se ha completado cuando devuelve el delegado. Si se utiliza un objeto
TransformManyBlock<TInput,TOutput> con System.Func<TInput, Task<IEnumerable<TOutput>>> , se considera que
el procesamiento de cada elemento de entrada se ha completado solo cuando el objeto devuelto
System.Threading.Tasks.Task<IEnumerable<TOutput>> está completo.

En el siguiente ejemplo básico se crea un objeto TransformManyBlock<TInput,TOutput> que divide las cadenas
en sus secuencias de caracteres individuales. El objeto TransformManyBlock<TInput,TOutput> toma valores
String como entrada y genera valores Char como salida.
// Create a TransformManyBlock<string, char> object that splits
// a string into its individual characters.
var transformManyBlock = new TransformManyBlock<string, char>(
s => s.ToCharArray());

// Post two messages to the first block.


transformManyBlock.Post("Hello");
transformManyBlock.Post("World");

// Receive all output values from the block.


for (int i = 0; i < ("Hello" + "World").Length; i++)
{
Console.WriteLine(transformManyBlock.Receive());
}

/* Output:
H
e
l
l
o
W
o
r
l
d
*/

' Create a TransformManyBlock<string, char> object that splits


' a string into its individual characters.
Dim transformManyBlock = New TransformManyBlock(Of String, Char)(Function(s) s.ToCharArray())

' Post two messages to the first block.


transformManyBlock.Post("Hello")
transformManyBlock.Post("World")

' Receive all output values from the block.


For i As Integer = 0 To ("Hello" & "World").Length - 1
Console.WriteLine(transformManyBlock.Receive())
Next i

' Output:
' H
' e
' l
' l
' o
' W
' o
' r
' l
' d
'

Para obtener ejemplos completos que usan TransformManyBlock<TInput,TOutput> para generar varios
resultados independientes para cada entrada en una canalización de flujo de datos, vea Tutorial: Creación de una
canalización de flujos de datos.
Grado de paralelismo
Cada uno de los objetos ActionBlock<TInput>, TransformBlock<TInput,TOutput> y
TransformManyBlock<TInput,TOutput> almacena en búfer los mensajes de entrada hasta que el bloque está
listo para procesarlos. De forma predeterminada, estas clases procesan los mensajes en el orden en el que se
recibieron, un mensaje cada vez. También puede especificar el grado de paralelismo para permitir que los
objetos ActionBlock<TInput>, TransformBlock<TInput,TOutput> y TransformManyBlock<TInput,TOutput>
puedan procesar varios mensajes simultáneamente. Para obtener más información sobre la ejecución
simultánea, vea la sección Especificar el grado de paralelismo más adelante en este documento. Para obtener un
ejemplo en donde se establece el grado de paralelismo que permite que un bloque de flujo de datos de
ejecución pueda procesar varios mensajes al mismo tiempo, vea Procedimiento: Especificación del grado de
paralelismo en un bloque de flujo de datos.
Resumen de tipos de delegado
En la tabla siguiente se resumen los tipos de delegado que puede proporcionar a los objetos
ActionBlock<TInput>, TransformBlock<TInput,TOutput> y TransformManyBlock<TInput,TOutput>. En esta tabla
también se especifica si el tipo de delegado funciona de forma sincrónica o asincrónica.

T IP O T IP O DE DEL EGA DO SIN C RÓ N IC O T IP O DE DEL EGA DO A SIN C RÓ N IC O

ActionBlock<TInput> System.Action System.Func<TInput, Task>

TransformBlock<TInput,TOutput> System.Func<TInput, TOutput> System.Func<TInput,


Task<TOutput>>

TransformManyBlock<TInput,TOutput System.Func<TInput, System.Func<TInput,


> IEnumerable<TOutput>> Task<IEnumerable<TOutput>>>

También puede utilizar expresiones lambda cuando trabaja con tipos de bloque de ejecución. Para obtener un
ejemplo en donde se muestra cómo usar una expresión lambda con un bloque de ejecución, vea Procedimiento:
Ejecución de una acción cuando un bloque de flujo de datos recibe datos.
Bloques de agrupación
Los bloques de agrupación combinan datos de uno o más orígenes y con distintas restricciones. La biblioteca de
flujos de datos TPL proporciona tres tipos de bloques de combinación: BatchBlock<T>, JoinBlock<T1,T2> y
BatchedJoinBlock<T1,T2>.
BatchBlock(T)
La clase BatchBlock<T> combina conjuntos de datos de entrada, que se conocen como lotes, en las matrices de
datos de salida. Especifique el tamaño de cada lote cuando crea un objeto BatchBlock<T>. Cuando el objeto
BatchBlock<T> recibe el número especificado de elementos de entrada, propaga de forma asincrónica una
matriz que contiene esos elementos. Si un objeto BatchBlock<T> se establece en el estado completado pero no
contiene elementos suficientes para formar un lote, propaga una matriz final que contiene los elementos de
entrada restantes.
La clase BatchBlock<T> funciona en modo expansivo o no expansivo. En modo expansivo, que es el valor
predeterminado, un objeto BatchBlock<T> acepta cada mensaje que se proporciona y propaga una matriz
después de recibir el número especificado de elementos. En modo no expansivo, un objeto BatchBlock<T>
pospone todos los mensajes entrantes hasta que haya suficientes orígenes que proporcionen mensajes al
bloque para formar un lote. Normalmente, el modo expansivo se comporta mejor que el modo no expansivo
porque requiere menos sobrecarga de procesamiento. Sin embargo, se puede usar el modo no expansivo
cuando se debe coordinar el consumo de varios orígenes en modo atómico. Especifique el modo no expansivo
estableciendo Greedy en False en el parámetro dataflowBlockOptions del constructor BatchBlock<T>.
En el siguiente ejemplo básico se exponen varios valores Int32 a un objeto BatchBlock<T> que contiene diez
elementos en un lote. Para garantizar que todos los valores se propagan fuera de BatchBlock<T>, este ejemplo
llama al método Complete. El método Complete establece el objeto BatchBlock<T> en el estado completado y,
por consiguiente, el objeto BatchBlock<T> propaga cualquier elemento restante como un lote final.
// Create a BatchBlock<int> object that holds ten
// elements per batch.
var batchBlock = new BatchBlock<int>(10);

// Post several values to the block.


for (int i = 0; i < 13; i++)
{
batchBlock.Post(i);
}
// Set the block to the completed state. This causes
// the block to propagate out any any remaining
// values as a final batch.
batchBlock.Complete();

// Print the sum of both batches.

Console.WriteLine("The sum of the elements in batch 1 is {0}.",


batchBlock.Receive().Sum());

Console.WriteLine("The sum of the elements in batch 2 is {0}.",


batchBlock.Receive().Sum());

/* Output:
The sum of the elements in batch 1 is 45.
The sum of the elements in batch 2 is 33.
*/

' Create a BatchBlock<int> object that holds ten


' elements per batch.
Dim batchBlock = New BatchBlock(Of Integer)(10)

' Post several values to the block.


For i As Integer = 0 To 12
batchBlock.Post(i)
Next i
' Set the block to the completed state. This causes
' the block to propagate out any any remaining
' values as a final batch.
batchBlock.Complete()

' Print the sum of both batches.

Console.WriteLine("The sum of the elements in batch 1 is {0}.", batchBlock.Receive().Sum())

Console.WriteLine("The sum of the elements in batch 2 is {0}.", batchBlock.Receive().Sum())

' Output:
' The sum of the elements in batch 1 is 45.
' The sum of the elements in batch 2 is 33.
'

Para obtener un ejemplo completo que usa BatchBlock<T> para mejorar la eficacia de las operaciones de
inserción de la base de datos, vea Tutorial: Uso de BatchBlock y BatchedJoinBlock para mejorar la eficacia.
JoinBlock(T1, T2, ...)
Las clases JoinBlock<T1,T2> y JoinBlock<T1,T2,T3> obtienen elementos de entrada y propagan objetos
System.Tuple<T1,T2> o System.Tuple<T1,T2,T3> que contienen esos elementos. Las clases JoinBlock<T1,T2> y
JoinBlock<T1,T2,T3> no heredan de ITargetBlock<TInput>. En su lugar, proporcionan propiedades, Target1,
Target2 y Target3, que implementan ITargetBlock<TInput>.
Al igual que BatchBlock<T>, JoinBlock<T1,T2> y JoinBlock<T1,T2,T3> funcionan en modo expansivo o no
expansivo. En modo expansivo, que es el valor predeterminado, un objeto JoinBlock<T1,T2> o
JoinBlock<T1,T2,T3> acepta cada mensaje que se proporciona y propaga una tupla después de que cada uno de
sus destinos reciba por lo menos un mensaje. En modo no expansivo, un objeto JoinBlock<T1,T2> o
JoinBlock<T1,T2,T3> pospone todos los mensajes entrantes hasta que todos los destinos han proporcionado
los datos necesarios para crear una tupla. En este punto, el bloque se involucra en un protocolo de confirmación
en dos fases para recuperar atómicamente todos los elementos necesarios de los orígenes. Este aplazamiento
permite que, mientras tanto, otra entidad consuma datos, para permitir que el sistema global progrese.
En el siguiente ejemplo básico se muestra un caso en el que un objeto JoinBlock<T1,T2,T3> requiere varios
datos para calcular un valor. En este ejemplo se crea un objeto JoinBlock<T1,T2,T3> que requiere dos valores
Int32 y un valor Char para realizar una operación aritmética.

// Create a JoinBlock<int, int, char> object that requires


// two numbers and an operator.
var joinBlock = new JoinBlock<int, int, char>();

// Post two values to each target of the join.

joinBlock.Target1.Post(3);
joinBlock.Target1.Post(6);

joinBlock.Target2.Post(5);
joinBlock.Target2.Post(4);

joinBlock.Target3.Post('+');
joinBlock.Target3.Post('-');

// Receive each group of values and apply the operator part


// to the number parts.

for (int i = 0; i < 2; i++)


{
var data = joinBlock.Receive();
switch (data.Item3)
{
case '+':
Console.WriteLine("{0} + {1} = {2}",
data.Item1, data.Item2, data.Item1 + data.Item2);
break;
case '-':
Console.WriteLine("{0} - {1} = {2}",
data.Item1, data.Item2, data.Item1 - data.Item2);
break;
default:
Console.WriteLine("Unknown operator '{0}'.", data.Item3);
break;
}
}

/* Output:
3 + 5 = 8
6 - 4 = 2
*/
' Create a JoinBlock<int, int, char> object that requires
' two numbers and an operator.
Dim joinBlock = New JoinBlock(Of Integer, Integer, Char)()

' Post two values to each target of the join.

joinBlock.Target1.Post(3)
joinBlock.Target1.Post(6)

joinBlock.Target2.Post(5)
joinBlock.Target2.Post(4)

joinBlock.Target3.Post("+"c)
joinBlock.Target3.Post("-"c)

' Receive each group of values and apply the operator part
' to the number parts.

For i As Integer = 0 To 1
Dim data = joinBlock.Receive()
Select Case data.Item3
Case "+"c
Console.WriteLine("{0} + {1} = {2}", data.Item1, data.Item2, data.Item1 + data.Item2)
Case "-"c
Console.WriteLine("{0} - {1} = {2}", data.Item1, data.Item2, data.Item1 - data.Item2)
Case Else
Console.WriteLine("Unknown operator '{0}'.", data.Item3)
End Select
Next i

' Output:
' 3 + 5 = 8
' 6 - 4 = 2
'

Para obtener un ejemplo completo en donde se usan objetos JoinBlock<T1,T2> en modo no expansivo para
compartir conjuntamente un recurso, vea Procedimiento: Uso de JoinBlock para leer datos de varios orígenes.
BatchedJoinBlock(T1, T2, ...)
Las clases BatchedJoinBlock<T1,T2> y BatchedJoinBlock<T1,T2,T3> obtienen lotes de elementos de entrada y
propagan objetos System.Tuple(IList(T1), IList(T2)) o System.Tuple(IList(T1), IList(T2), IList(T3)) que
contienen esos elementos. Piense en BatchedJoinBlock<T1,T2> como una combinación de BatchBlock<T> y
JoinBlock<T1,T2>. Especifique el tamaño de cada lote cuando crea un objeto BatchedJoinBlock<T1,T2>.
BatchedJoinBlock<T1,T2> también proporciona propiedades, Target1 y Target2, que implementan
ITargetBlock<TInput>. Cuando el número especificado de elementos de entrada se recibe a través de todos los
destinos, el objeto BatchedJoinBlock<T1,T2> propaga de forma asincrónica un objeto
System.Tuple(IList(T1), IList(T2)) que contiene esos elementos.

En el siguiente ejemplo básico se crea un objeto BatchedJoinBlock<T1,T2> que contiene resultados, valores
Int32 y errores que son objetos Exception. En este ejemplo se realizan varias operaciones y se escriben los
resultados en la propiedad Target1, y los errores en la propiedad Target2, del objeto BatchedJoinBlock<T1,T2>.
Dado que el número de operaciones correctas y las que dieron error no se conoce de antemano, los objetos
IList<T> permiten que cada destino reciba cero o más valores.
// For demonstration, create a Func<int, int> that
// returns its argument, or throws ArgumentOutOfRangeException
// if the argument is less than zero.
Func<int, int> DoWork = n =>
{
if (n < 0)
throw new ArgumentOutOfRangeException();
return n;
};

// Create a BatchedJoinBlock<int, Exception> object that holds


// seven elements per batch.
var batchedJoinBlock = new BatchedJoinBlock<int, Exception>(7);

// Post several items to the block.


foreach (int i in new int[] { 5, 6, -7, -22, 13, 55, 0 })
{
try
{
// Post the result of the worker to the
// first target of the block.
batchedJoinBlock.Target1.Post(DoWork(i));
}
catch (ArgumentOutOfRangeException e)
{
// If an error occurred, post the Exception to the
// second target of the block.
batchedJoinBlock.Target2.Post(e);
}
}

// Read the results from the block.


var results = batchedJoinBlock.Receive();

// Print the results to the console.

// Print the results.


foreach (int n in results.Item1)
{
Console.WriteLine(n);
}
// Print failures.
foreach (Exception e in results.Item2)
{
Console.WriteLine(e.Message);
}

/* Output:
5
6
13
55
0
Specified argument was out of the range of valid values.
Specified argument was out of the range of valid values.
*/
' For demonstration, create a Func<int, int> that
' returns its argument, or throws ArgumentOutOfRangeException
' if the argument is less than zero.
Dim DoWork As Func(Of Integer, Integer) = Function(n)
If n < 0 Then
Throw New ArgumentOutOfRangeException()
End If
Return n
End Function

' Create a BatchedJoinBlock<int, Exception> object that holds


' seven elements per batch.
Dim batchedJoinBlock = New BatchedJoinBlock(Of Integer, Exception)(7)

' Post several items to the block.


For Each i As Integer In New Integer() {5, 6, -7, -22, 13, 55, 0}
Try
' Post the result of the worker to the
' first target of the block.
batchedJoinBlock.Target1.Post(DoWork(i))
Catch e As ArgumentOutOfRangeException
' If an error occurred, post the Exception to the
' second target of the block.
batchedJoinBlock.Target2.Post(e)
End Try
Next i

' Read the results from the block.


Dim results = batchedJoinBlock.Receive()

' Print the results to the console.

' Print the results.


For Each n As Integer In results.Item1
Console.WriteLine(n)
Next n
' Print failures.
For Each e As Exception In results.Item2
Console.WriteLine(e.Message)
Next e

' Output:
' 5
' 6
' 13
' 55
' 0
' Specified argument was out of the range of valid values.
' Specified argument was out of the range of valid values.
'

Para obtener un ejemplo completo que usa BatchedJoinBlock<T1,T2> para capturar resultados y cualquier
excepción que se produzca mientras el programa lee de una base de datos, vea Tutorial: Uso de BatchBlock y
BatchedJoinBlock para mejorar la eficacia.

Configurar el comportamiento del bloque de flujo de datos


Puede habilitar opciones adicionales si proporciona un objeto
System.Threading.Tasks.Dataflow.DataflowBlockOptions al constructor de los tipos de bloques de flujo de datos.
Estas opciones controlan el comportamiento, como el del programador que administra la tarea subyacente, y el
grado de paralelismo. El objeto DataflowBlockOptions también tiene tipos derivados que especifican el
comportamiento específico de ciertos tipos de bloques de flujo de datos. En la tabla siguiente se resumen los
tipo de opciones que se asocian a cada tipo de bloques de flujo de datos.
T IP O DE B LO Q UES DE F L UJO DE DATO S T IP O DE DATA F LO W B LO C KO P T IO N S

BufferBlock<T> DataflowBlockOptions

BroadcastBlock<T> DataflowBlockOptions

WriteOnceBlock<T> DataflowBlockOptions

ActionBlock<TInput> ExecutionDataflowBlockOptions

TransformBlock<TInput,TOutput> ExecutionDataflowBlockOptions

TransformManyBlock<TInput,TOutput> ExecutionDataflowBlockOptions

BatchBlock<T> GroupingDataflowBlockOptions

JoinBlock<T1,T2> GroupingDataflowBlockOptions

BatchedJoinBlock<T1,T2> GroupingDataflowBlockOptions

Las secciones siguientes proporcionan información adicional sobre las clases importantes de opciones de
bloques de flujo de datos que están disponibles a través de las clases
System.Threading.Tasks.Dataflow.DataflowBlockOptions,
System.Threading.Tasks.Dataflow.ExecutionDataflowBlockOptions y
System.Threading.Tasks.Dataflow.GroupingDataflowBlockOptions.
Especificar el programador de tareas
Cada bloque de flujo de datos predefinido utiliza el mecanismo de programación de tareas de la biblioteca TPL
para realizar actividades como propagar datos a un destino, recibir datos de un origen y ejecutar delegados
definido por el usuario si los datos están disponibles. TaskScheduler es una clase abstracta que representa un
programador de tareas que pone en cola las tareas en los subprocesos. El programador de tareas
predeterminado, Default, utiliza la clase ThreadPool para poner en cola y ejecutar el trabajo. Puede reemplazar
el programador de tareas predeterminado estableciendo la propiedad TaskScheduler al crear un objeto de
bloque de flujo de datos.
Cuando el mismo programador de tareas administra varios bloques de flujo de datos, puede aplicar directivas
entre ellos. Por ejemplo, si cada uno de los bloques de flujo de datos se configuran como destino del
programador exclusivo del mismo objeto ConcurrentExclusiveSchedulerPair, todo el trabajo que se ejecuta a
través de estos bloques se serializa. De igual forma, si estos bloques se configuran como destino del
programador simultáneo del mismo objeto ConcurrentExclusiveSchedulerPair, y el programador se configura
para tener un nivel de simultaneidad máximo, todo el trabajo de estos bloques se limita a ese número de
operaciones simultáneas. Para obtener un ejemplo en donde se usa la clase ConcurrentExclusiveSchedulerPair
para permitir que las operaciones de lectura se produzcan en paralelo, pero las operaciones de escritura sean
exclusivas del resto de operaciones, vea Procedimiento: Especificación de un Programador de tareas en un
bloque de flujo de datos. Para obtener más información sobre los programadores de tareas en la biblioteca TPL,
consulte el tema sobre la clase TaskScheduler.
Especificar el grado de Paralelismo
De forma predeterminada, los tres tipos de bloques de ejecución que la biblioteca de flujos de datos TPL
proporciona, ActionBlock<TInput>, TransformBlock<TInput,TOutput> y TransformManyBlock<TInput,TOutput>,
procesan un mensaje al mismo tiempo. Estos tipos de bloques de flujo de datos también procesan mensajes en
el orden en que se reciben. Para permitir que estos bloques de flujo de datos procesen mensajes
simultáneamente, establezca la propiedad ExecutionDataflowBlockOptions.MaxDegreeOfParallelism cuando
construya el objeto de bloques de flujo de datos.
El valor predeterminado de MaxDegreeOfParallelism es 1, que garantiza que el bloque de flujo de datos procesa
un mensaje al mismo tiempo. Al establecer esta propiedad en un valor mayor de 1 se permite que el bloque de
flujo de datos procese varios mensajes simultáneamente. Al establecer esta propiedad en
DataflowBlockOptions.Unbounded se permite que el programador de tareas subyacente administre el grado
máximo de simultaneidad.

IMPORTANT
Cuando se especifica un grado máximo de paralelismo mayor que 1, varios mensajes se procesan simultáneamente y, por
tanto, los mensajes no se pueden procesar en el orden en que se reciben. Pero los mensajes salen del bloque en el mismo
orden en que se reciben.

Dado que la propiedad MaxDegreeOfParallelism representa el grado máximo de paralelismo, el bloque de flujo
de datos puede ejecutarse con un menor grado de paralelismo que el especificado. El bloque de flujo de datos
puede utilizar un menor grado de paralelismo para cumplir los requisitos funcionales o porque hay una falta de
recursos del sistema disponibles. Un flujo de datos bloqueado nunca elige más paralelismo que el especificado.
El valor de la propiedad MaxDegreeOfParallelism es exclusivo para cada objeto de bloque de flujo de datos. Por
ejemplo, si cuatro objetos de bloques de flujo de datos especifican 1 como el grado máximo de paralelismo, los
cuatro objetos de bloques de flujo de datos podrían ejecutarse en paralelo.
Para obtener un ejemplo en donde se establece el grado máximo de paralelismo que permite que se produzcan
operaciones largas en paralelo, vea Procedimiento: Especificación del grado de paralelismo en un bloque de
flujo de datos.
Especificar el número de mensajes por tarea
Los tipos predefinidos de bloques de flujo de datos utilizan tareas para procesar varios elementos de entrada.
Esto ayuda a minimizar el número de objetos de tarea necesarios para procesar datos, lo que permite que las
aplicaciones se ejecuten más eficazmente. Sin embargo, cuando las tareas de un conjunto de bloques de flujo de
datos están procesando datos, es posible que las tareas de otros bloques de flujo de datos tengan que esperar
el tiempo de procesamiento en la cola mensajes. Para permitir una mejor equidad entre tareas de flujo de datos,
establezca la propiedad MaxMessagesPerTask. Cuando MaxMessagesPerTask se establece en
DataflowBlockOptions.Unbounded, que es el valor predeterminado, la tarea utilizada por un bloque de flujo de
datos procesa tantos mensajes como están disponibles. Cuando MaxMessagesPerTask se establece en un valor
distinto de Unbounded, el bloque de flujo de datos procesa como máximo este número de mensajes por objeto
Task. Aunque al establecer la propiedad MaxMessagesPerTask se puede aumentar la equidad entre tareas, puede
provocar que el sistema cree más tareas que las necesarias, lo que puede reducir el rendimiento.
Habilitar la cancelación
La biblioteca TPL proporciona un mecanismo que habilita las tareas para coordinar la cancelación de manera
cooperativa. Para permitir que los bloques de flujo de datos puedan participar en este mecanismo de
cancelación, establezca la propiedad CancellationToken. Cuando este objeto CancellationToken se establece en el
estado cancelado, todos los bloques de flujo de datos que controlan este token finalizan la ejecución de su
elemento actual pero no comienzan a procesar los elementos siguientes. Estos bloques de flujo de datos
también borran los mensajes almacenados en búfer, conexiones de inicio para los bloques de origen y de
destino, y la transición al estado cancelado. Al realizar la transición al estado cancelado, la propiedad
Completion tiene la propiedad Status establecida en Canceled, a menos que se produzca una excepción durante
el procesamiento. En ese caso, Status se establece en Faulted.
Para obtener un ejemplo en donde se muestra cómo usar la cancelación en una aplicación de Windows Forms,
vea Procedimiento: Cómo: Cancelar un bloque de flujos de datos. Para más información sobre la cancelación en
la biblioteca TPL, consulte Task Cancellation (Cancelación de tareas).
Especificar el comportamiento expansivo frente al no expansivo
Varios tipos de bloques de flujo de datos de agrupación pueden trabajar en modo expansivo o no expansivo. De
forma predeterminada, los tipos de bloques de flujo de datos predefinidos funcionan en modo expansivo.
Para los tipos de bloques de combinación como JoinBlock<T1,T2>, el modo expansivo significa que el bloque
acepta datos inmediatamente aunque los correspondientes datos con los que se combinará aún no estén
disponibles. El modo no expansivo significa que el bloque pospone todos los mensajes entrantes hasta que uno
esté disponible para cada uno de sus destinos para completar la combinación. Si los mensajes pospuestos ya no
están disponibles, el bloque de combinación libera todos los mensajes pospuestos y reinicia el proceso. Para la
clase BatchBlock<T>, el comportamiento expansivo y no expansivo es similar, salvo que en modo no expansivo,
un objeto BatchBlock<T> pospone todos los mensajes entrantes hasta que haya suficientes mensajes
disponibles de orígenes distintos para completar un lote.
Para especificar el modo no expansivo para un bloque de flujo de datos, establezca Greedy en False . Para
obtener un ejemplo en donde se muestra cómo usar el modo no expansivo para permitir que varios bloques de
combinación compartan un origen de datos con mayor eficacia, vea Procedimiento: Uso de JoinBlock para leer
datos de varios orígenes.

Bloques de flujo de datos personalizados


Aunque la biblioteca de flujos de datos TPL proporciona muchos tipos de bloques predefinidos, puede crear
tipos de bloques adicionales que tengan un comportamiento personalizado. Implemente las interfaces
ISourceBlock<TOutput> o ITargetBlock<TInput> directamente, o utilice el método Encapsulate para compilar un
bloque complejo que encapsule el comportamiento de los tipos de bloques existentes. Para obtener ejemplos
que muestran cómo implementar funcionalidad en bloques de flujo de datos personalizados, vea Tutorial:
Creación de tipos de bloques de flujo de datos personalizados.

Temas relacionados
T IT L E DESC RIP C IÓ N

Cómo: Escritura y lectura de mensajes en un bloque de flujo Muestra cómo escribir y leer los mensajes de un objeto
de datos BufferBlock<T>.

Cómo: Implementación de un modelo de flujo de datos Describe cómo utilizar el modelo de flujo de datos para
productor-consumidor implementar un patrón consumidor-productor, cuando el
productor envía mensajes a un bloque de flujo de datos y el
consumidor lee mensajes de ese bloque.

Cómo: Ejecución de una acción cuando un bloque de flujo de Describe cómo proporcionar delegados a los tipos de
datos recibe datos bloques de flujo de datos de ejecución,
ActionBlock<TInput>, TransformBlock<TInput,TOutput> y
TransformManyBlock<TInput,TOutput>.

Tutorial: Creación de una canalización de flujos de datos Describe cómo crear una canalización de flujo de datos que
descarga texto desde Internet y realiza operaciones en ese
texto.

Cómo: Desvinculación de bloques de flujo de datos Muestra cómo utilizar el método LinkTo para desvincular un
bloque de destino de su origen después de que el origen
proporciona un mensaje al destino.

Tutorial: Uso de flujos de datos en aplicaciones de Muestra cómo crear una red de bloques de flujo de datos
Windows Forms que realizan procesamiento de imágenes en una aplicación
de Windows Forms.
T IT L E DESC RIP C IÓ N

Cómo: Cancelación de un bloque de flujo de datos Muestra cómo se usa la cancelación en una aplicación de
Windows Forms.

Cómo: Uso de JoinBlock para leer datos de diferentes Explica cómo utilizar la clase JoinBlock<T1,T2> para realizar
orígenes una operación cuando los datos están disponibles a partir
de varios orígenes, y cómo utilizar el modo no expansivo
para permitir que varios bloques de combinación puedan
compartir un origen de datos más eficazmente.

Cómo: Especificación del grado de paralelismo en un bloque Describe cómo establecer la propiedad
de flujo de datos MaxDegreeOfParallelism para permitir que un bloque de
flujo de datos de ejecución pueda procesar varios mensajes
al mismo tiempo.

Cómo: Especificación de un Programador de tareas en un Muestra cómo asociar un programador de tareas específico
bloque de flujo de datos cuando se usa flujo de datos en la aplicación.

Tutorial: Uso de BatchBlock y BatchedJoinBlock para mejorar Describe cómo utilizar la clase BatchBlock<T> para mejorar
la eficacia la eficacia de las operaciones de inserción de la base de
datos y cómo utilizar la clase BatchedJoinBlock<T1,T2> para
capturar los resultados y cualquier excepción que se
produzca mientras el programa lee de una base de datos.

Tutorial: Creación de tipos de bloques de flujo de datos Muestra dos maneras de crear un tipo de bloque de flujo de
personalizados datos que implementa un comportamiento personalizado.

Biblioteca TPL Presenta la biblioteca TPL, una biblioteca que simplifica la


programación paralela y simultánea en aplicaciones de .NET
Framework.
Cómo: Escribir y leer mensajes en un bloque de flujo
de datos
16/09/2020 • 9 minutes to read • Edit Online

En este documento se describe cómo usar la biblioteca de flujos de datos TPL para escribir y leer mensajes en un
bloque de flujo de datos. La biblioteca de flujos de datos TPL proporciona métodos sincrónicos y asincrónicos para
escribir y leer mensajes en un bloque de flujo de datos. Este documento usa la clase
System.Threading.Tasks.Dataflow.BufferBlock<T>. La clase BufferBlock<T> almacena mensajes en búfer y se
comporta como origen y destino de los mismos.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET. Para
instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar
paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow . Como
alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Escribir y leer en un bloque de flujo de datos de forma sincrónica


En el ejemplo siguiente se usa el método Post para escribir en un bloque de flujo de datos BufferBlock<T> y el
método Receive para leer desde el mismo objeto.

var bufferBlock = new BufferBlock<int>();

// Post several messages to the block.


for (int i = 0; i < 3; i++)
{
bufferBlock.Post(i);
}

// Receive the messages back from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(bufferBlock.Receive());
}

// Output:
// 0
// 1
// 2
Dim bufferBlock = New BufferBlock(Of Integer)()

' Post several messages to the block.


For i As Integer = 0 To 2
bufferBlock.Post(i)
Next i

' Receive the messages back from the block.


For i As Integer = 0 To 2
Console.WriteLine(bufferBlock.Receive())
Next i

' Output:
' 0
' 1
' 2

También puede usar el método TryReceive para leer desde un bloque de flujo de datos, como se muestra en el
ejemplo siguiente. El método TryReceive no bloquea el subproceso actual y es útil cuando se realizan sondeos
ocasionales de los datos.

// Post more messages to the block.


for (int i = 0; i < 3; i++)
{
bufferBlock.Post(i);
}

// Receive the messages back from the block.


while (bufferBlock.TryReceive(out int value))
{
Console.WriteLine(value);
}

// Output:
// 0
// 1
// 2

' Post more messages to the block.


For i As Integer = 0 To 2
bufferBlock.Post(i)
Next i

' Receive the messages back from the block.


Dim value As Integer
Do While bufferBlock.TryReceive(value)
Console.WriteLine(value)
Loop

' Output:
' 0
' 1
' 2

Dado que el método Post actúa de forma sincrónica, el objeto BufferBlock<T> de los ejemplos anteriores recibe
todos los datos antes de que el segundo bucle los lea. En el siguiente ejemplo se amplía el primer ejemplo
mediante Invoke para leer y escribir en el bloque de mensajes simultáneamente. Dado que Invoke realiza acciones
al mismo tiempo, los valores no se escriben en el objeto BufferBlock<T> en un orden específico.
// Write to and read from the message block concurrently.
var post01 = Task.Run(() =>
{
bufferBlock.Post(0);
bufferBlock.Post(1);
});
var receive = Task.Run(() =>
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine(bufferBlock.Receive());
}
});
var post2 = Task.Run(() =>
{
bufferBlock.Post(2);
});

await Task.WhenAll(post01, receive, post2);

// Output:
// 0
// 1
// 2

' Write to and read from the message block concurrently.


Dim post01 = Task.Run(Sub()
bufferBlock.Post(0)
bufferBlock.Post(1)
End Sub)
Dim receive = Task.Run(Sub()
For i As Integer = 0 To 2
Console.WriteLine(bufferBlock.Receive())
Next i
End Sub)
Dim post2 = Task.Run(Sub() bufferBlock.Post(2))
Task.WaitAll(post01, receive, post2)

' Output:
' 0
' 1
' 2

Escribir y leer en un bloque de flujo de datos de forma asincrónica


En el ejemplo siguiente se usa el método SendAsync para escribir de forma asincrónica en un objeto
BufferBlock<T> y el método ReceiveAsync para leer de forma asincrónica desde el mismo objeto. En este ejemplo
se usan los operadores async y await (Async y Await en Visual Basic) para, asincrónicamente, enviar datos al
bloque de destino y leerlos en él. El método SendAsync resulta útil si debe habilitar un bloque de flujo de datos
para posponer mensajes. El método ReceiveAsync resulta útil cuando desea actuar en los datos cuando dichos
datos están disponibles. Para más información sobre la propagación de los mensajes entre los bloques de
mensajes, consulte la sección Paso de mensajes en Flujo de datos.
// Post more messages to the block asynchronously.
for (int i = 0; i < 3; i++)
{
await bufferBlock.SendAsync(i);
}

// Asynchronously receive the messages back from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(await bufferBlock.ReceiveAsync());
}

// Output:
// 0
// 1
// 2

' Post more messages to the block asynchronously.


For i As Integer = 0 To 2
await bufferBlock.SendAsync(i)
Next i

' Asynchronously receive the messages back from the block.


For i As Integer = 0 To 2
Console.WriteLine(await bufferBlock.ReceiveAsync())
Next i

' Output:
' 0
' 1
' 2

Un ejemplo completo
En el siguiente ejemplo se muestra el código completo de este documento.

using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

// Demonstrates a how to write to and read from a dataflow block.


class DataflowReadWrite
{
// Demonstrates asynchronous dataflow operations.
static async Task AsyncSendReceive(BufferBlock<int> bufferBlock)
{
// Post more messages to the block asynchronously.
for (int i = 0; i < 3; i++)
{
await bufferBlock.SendAsync(i);
}

// Asynchronously receive the messages back from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(await bufferBlock.ReceiveAsync());
}

// Output:
// 0
// 1
// 2
}
static async Task Main()
{
var bufferBlock = new BufferBlock<int>();

// Post several messages to the block.


for (int i = 0; i < 3; i++)
{
bufferBlock.Post(i);
}

// Receive the messages back from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(bufferBlock.Receive());
}

// Output:
// 0
// 1
// 2

// Post more messages to the block.


for (int i = 0; i < 3; i++)
{
bufferBlock.Post(i);
}

// Receive the messages back from the block.


while (bufferBlock.TryReceive(out int value))
{
Console.WriteLine(value);
}

// Output:
// 0
// 1
// 2

// Write to and read from the message block concurrently.


var post01 = Task.Run(() =>
{
bufferBlock.Post(0);
bufferBlock.Post(1);
});
var receive = Task.Run(() =>
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine(bufferBlock.Receive());
}
});
var post2 = Task.Run(() =>
{
bufferBlock.Post(2);
});

await Task.WhenAll(post01, receive, post2);

// Output:
// 0
// 1
// 2

// Demonstrate asynchronous dataflow operations.


await AsyncSendReceive(bufferBlock);
}
}
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

' Demonstrates a how to write to and read from a dataflow block.


Friend Class DataflowReadWrite
' Demonstrates asynchronous dataflow operations.
Private Shared async Function AsyncSendReceive(ByVal bufferBlock As BufferBlock(Of Integer)) As Task
' Post more messages to the block asynchronously.
For i As Integer = 0 To 2
await bufferBlock.SendAsync(i)
Next i

' Asynchronously receive the messages back from the block.


For i As Integer = 0 To 2
Console.WriteLine(await bufferBlock.ReceiveAsync())
Next i

' Output:
' 0
' 1
' 2
End Function

Shared Sub Main(ByVal args() As String)


Dim bufferBlock = New BufferBlock(Of Integer)()

' Post several messages to the block.


For i As Integer = 0 To 2
bufferBlock.Post(i)
Next i

' Receive the messages back from the block.


For i As Integer = 0 To 2
Console.WriteLine(bufferBlock.Receive())
Next i

' Output:
' 0
' 1
' 2

' Post more messages to the block.


For i As Integer = 0 To 2
bufferBlock.Post(i)
Next i

' Receive the messages back from the block.


Dim value As Integer
Do While bufferBlock.TryReceive(value)
Console.WriteLine(value)
Loop

' Output:
' 0
' 1
' 2

' Write to and read from the message block concurrently.


Dim post01 = Task.Run(Sub()
bufferBlock.Post(0)
bufferBlock.Post(1)
End Sub)
Dim receive = Task.Run(Sub()
For i As Integer = 0 To 2
Console.WriteLine(bufferBlock.Receive())
Next i
End Sub)
Dim post2 = Task.Run(Sub() bufferBlock.Post(2))
Dim post2 = Task.Run(Sub() bufferBlock.Post(2))
Task.WaitAll(post01, receive, post2)

' Output:
' 0
' 1
' 2

' Demonstrate asynchronous dataflow operations.


AsyncSendReceive(bufferBlock).Wait()
End Sub

End Class

Pasos siguientes
En este ejemplo se muestra cómo leer y escribir directamente en un bloque de mensajes. También puede conectar
los bloques de flujo de datos para establecer canalizaciones, que son secuencias lineales de bloques de flujo de
datos, o redes, que son gráficos de bloques de flujo de datos. En una canalización o red, los orígenes propagan
datos de forma asincrónica en destinos a medida que esos datos están disponibles. Para obtener un ejemplo en el
que se cree una canalización de flujo de datos básica, consulte Walkthrough: Creating a Dataflow Pipeline (Tutorial:
Crear una canalización de flujo de datos). Para obtener un ejemplo en el que se cree una red de flujo de datos más
compleja, consulte Walkthrough: Using Dataflow in a Windows Forms Application (Tutorial: Usar el flujo de datos
en una aplicación de Windows Forms).

Vea también
Flujo de datos
Procedimiento Implementación de un modelo de
flujo de datos productor-consumidor
16/09/2020 • 7 minutes to read • Edit Online

Este documento describe cómo utilizar la biblioteca de flujos de datos TPL para implementar un modelo productor-
consumidor. En este modelo, el productor envía mensajes a un bloque de mensajes y el consumidor lee los
mensajes de este bloque.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET. Para
instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar
paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow . Como
alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Ejemplo
En el ejemplo siguiente se muestra un modelo productor-consumidor básico que usa el flujo de datos. El método
Produce escribe matrices que contienen bytes de datos aleatorios en un objeto
System.Threading.Tasks.Dataflow.ITargetBlock<TInput> y el método Consume lee los bytes de un objeto
System.Threading.Tasks.Dataflow.ISourceBlock<TOutput>. Al actuar en las interfaces ISourceBlock<TOutput> y
ITargetBlock<TInput>, en lugar de en sus tipos derivados, puede escribir código reutilizable que puede actuar en
una variedad de tipos de bloques de flujo de datos. Este ejemplo utiliza la clase BufferBlock<T>. Puesto que la clase
BufferBlock<T> actúa como origen y como un bloque de origen y destino, el productor y el consumidor pueden
utilizar un objeto compartido para transferir datos.
El método Produce llama al método Post en un bucle para escribir datos de forma sincrónica en el bloque de
destino. Después de que el método Produce escriba todos los datos en el bloque de destino, llama al método
Complete para indicar que el bloque nunca tendrá datos adicionales disponibles. El método Consume usa los
operadores async y await (Async y Await en Visual Basic) para calcular de forma asincrónica el número total de
bytes recibidos del objeto ISourceBlock<TOutput>. Para que actúe de forma asincrónica, el método Consume llama
al método OutputAvailableAsync para recibir una notificación cuando el bloque de origen tiene datos disponibles y
cuando el bloque de origen nunca va a tener datos adicionales disponibles.

using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

// Demonstrates a basic producer and consumer pattern that uses dataflow.


class DataflowProducerConsumer
{
// Demonstrates the production end of the producer and consumer pattern.
static void Produce(ITargetBlock<byte[]> target)
{
// Create a Random object to generate random data.
Random rand = new Random();

// In a loop, fill a buffer with random data and


// post the buffer to the target block.
for (int i = 0; i < 100; i++)
{
{
// Create an array to hold random byte data.
byte[] buffer = new byte[1024];

// Fill the buffer with random bytes.


rand.NextBytes(buffer);

// Post the result to the message block.


target.Post(buffer);
}

// Set the target to the completed state to signal to the consumer


// that no more data will be available.
target.Complete();
}

// Demonstrates the consumption end of the producer and consumer pattern.


static async Task<int> ConsumeAsync(ISourceBlock<byte[]> source)
{
// Initialize a counter to track the number of bytes that are processed.
int bytesProcessed = 0;

// Read from the source buffer until the source buffer has no
// available output data.
while (await source.OutputAvailableAsync())
{
byte[] data = source.Receive();

// Increment the count of bytes received.


bytesProcessed += data.Length;
}

return bytesProcessed;
}

static void Main(string[] args)


{
// Create a BufferBlock<byte[]> object. This object serves as the
// target block for the producer and the source block for the consumer.
var buffer = new BufferBlock<byte[]>();

// Start the consumer. The Consume method runs asynchronously.


var consumer = ConsumeAsync(buffer);

// Post source data to the dataflow block.


Produce(buffer);

// Wait for the consumer to process all data.


consumer.Wait();

// Print the count of bytes processed to the console.


Console.WriteLine("Processed {0} bytes.", consumer.Result);
}
}

/* Output:
Processed 102400 bytes.
*/

Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

' Demonstrates a basic producer and consumer pattern that uses dataflow.
Friend Class DataflowProducerConsumer
' Demonstrates the production end of the producer and consumer pattern.
Private Shared Sub Produce(ByVal target As ITargetBlock(Of Byte()))
' Create a Random object to generate random data.
Dim rand As New Random()
' In a loop, fill a buffer with random data and
' post the buffer to the target block.
For i As Integer = 0 To 99
' Create an array to hold random byte data.
Dim buffer(1023) As Byte

' Fill the buffer with random bytes.


rand.NextBytes(buffer)

' Post the result to the message block.


target.Post(buffer)
Next i

' Set the target to the completed state to signal to the consumer
' that no more data will be available.
target.Complete()
End Sub

' Demonstrates the consumption end of the producer and consumer pattern.
Private Shared async Function ConsumeAsync(ByVal source As ISourceBlock(Of Byte())) As Task(Of Integer)
' Initialize a counter to track the number of bytes that are processed.
Dim bytesProcessed As Integer = 0

' Read from the source buffer until the source buffer has no
' available output data.
Do While await source.OutputAvailableAsync()
Dim data() As Byte = source.Receive()

' Increment the count of bytes received.


bytesProcessed += data.Length
Loop

Return bytesProcessed
End Function

Shared Sub Main(ByVal args() As String)


' Create a BufferBlock<byte[]> object. This object serves as the
' target block for the producer and the source block for the consumer.
Dim buffer = New BufferBlock(Of Byte())()

' Start the consumer. The Consume method runs asynchronously.


Dim consumer = ConsumeAsync(buffer)

' Post source data to the dataflow block.


Produce(buffer)

' Wait for the consumer to process all data.


consumer.Wait()

' Print the count of bytes processed to the console.


Console.WriteLine("Processed {0} bytes.", consumer.Result)
End Sub
End Class

' Output:
'Processed 102400 bytes.
'

Programación sólida
En el ejemplo anterior se usa un solo consumidor para procesar los datos de origen. Si tiene varios consumidores
en la aplicación, use el método TryReceive para leer datos desde el bloque de origen, como se muestra en el
siguiente ejemplo.
// Demonstrates the consumption end of the producer and consumer pattern.
static async Task<int> ConsumeAsync(IReceivableSourceBlock<byte[]> source)
{
// Initialize a counter to track the number of bytes that are processed.
int bytesProcessed = 0;

// Read from the source buffer until the source buffer has no
// available output data.
while (await source.OutputAvailableAsync())
{
byte[] data;
while (source.TryReceive(out data))
{
// Increment the count of bytes received.
bytesProcessed += data.Length;
}
}

return bytesProcessed;
}

' Demonstrates the consumption end of the producer and consumer pattern.
Private Shared async Function ConsumeAsync(ByVal source As IReceivableSourceBlock(Of Byte())) As Task(Of
Integer)
' Initialize a counter to track the number of bytes that are processed.
Dim bytesProcessed As Integer = 0

' Read from the source buffer until the source buffer has no
' available output data.
Do While await source.OutputAvailableAsync()
Dim data() As Byte
Do While source.TryReceive(data)
' Increment the count of bytes received.
bytesProcessed += data.Length
Loop
Loop

Return bytesProcessed
End Function

El método TryReceive devuelve False cuando no hay datos disponibles. Cuando varios consumidores deben tener
acceso simultáneamente al bloque de origen, este mecanismo garantiza que los datos están disponibles después
de la llamada a OutputAvailableAsync.

Vea también
Flujo de datos
Cómo: Realizar una acción cuando un bloque de
flujos de datos recibe datos
16/09/2020 • 9 minutes to read • Edit Online

Los tipos Bloque de flujo de datos de ejecución llaman a un delegado proporcionado por el usuario cuando
reciben datos. Las clases System.Threading.Tasks.Dataflow.ActionBlock<TInput>,
System.Threading.Tasks.Dataflow.TransformBlock<TInput,TOutput> y
System.Threading.Tasks.Dataflow.TransformManyBlock<TInput,TOutput> son tipos de bloques de flujo de datos de
ejecución. Puede usar la palabra clave delegate ( Sub en Visual Basic), Action<T>, Func<T,TResult> o una
expresión lambda cuando proporcione una función de trabajo a un bloque de flujo de datos de ejecución. Este
documento describe cómo usar Func<T,TResult> y expresiones lambda para realizar la acción en bloques de
ejecución.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET. Para
instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar
paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow . Como
alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Ejemplo
En el ejemplo siguiente se usa el flujo de datos para leer un archivo del disco y se calcula el número de bytes en el
archivo que son iguales a cero. Usa TransformBlock<TInput,TOutput> para leer el archivo y calcular el número de
cero bytes, y ActionBlock<TInput> para imprimir el número de cero bytes en la consola. El objeto
TransformBlock<TInput,TOutput> especifica un objeto Func<T,TResult> para realizar el trabajo cuando los
bloques reciben datos. El objeto ActionBlock<TInput> usa una expresión lambda para imprimir en la consola el
número de cero bytes que se lee.

using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to provide delegates to exectution dataflow blocks.


class DataflowExecutionBlocks
{
// Computes the number of zero bytes that the provided file
// contains.
static int CountBytes(string path)
{
byte[] buffer = new byte[1024];
int totalZeroBytesRead = 0;
using (var fileStream = File.OpenRead(path))
{
int bytesRead = 0;
do
{
bytesRead = fileStream.Read(buffer, 0, buffer.Length);
totalZeroBytesRead += buffer.Count(b => b == 0);
} while (bytesRead > 0);
}

return totalZeroBytesRead;
}

static void Main(string[] args)


{
// Create a temporary file on disk.
string tempFile = Path.GetTempFileName();

// Write random data to the temporary file.


using (var fileStream = File.OpenWrite(tempFile))
{
Random rand = new Random();
byte[] buffer = new byte[1024];
for (int i = 0; i < 512; i++)
{
rand.NextBytes(buffer);
fileStream.Write(buffer, 0, buffer.Length);
}
}

// Create an ActionBlock<int> object that prints to the console


// the number of bytes read.
var printResult = new ActionBlock<int>(zeroBytesRead =>
{
Console.WriteLine("{0} contains {1} zero bytes.",
Path.GetFileName(tempFile), zeroBytesRead);
});

// Create a TransformBlock<string, int> object that calls the


// CountBytes function and returns its result.
var countBytes = new TransformBlock<string, int>(
new Func<string, int>(CountBytes));

// Link the TransformBlock<string, int> object to the


// ActionBlock<int> object.
countBytes.LinkTo(printResult);

// Create a continuation task that completes the ActionBlock<int>


// object when the TransformBlock<string, int> finishes.
countBytes.Completion.ContinueWith(delegate { printResult.Complete(); });

// Post the path to the temporary file to the


// TransformBlock<string, int> object.
countBytes.Post(tempFile);

// Requests completion of the TransformBlock<string, int> object.


countBytes.Complete();

// Wait for the ActionBlock<int> object to print the message.


printResult.Completion.Wait();

// Delete the temporary file.


File.Delete(tempFile);
}
}

/* Sample output:
tmp4FBE.tmp contains 2081 zero bytes.
*/

Imports System.IO
Imports System.Linq
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to provide delegates to exectution dataflow blocks.


' Demonstrates how to provide delegates to exectution dataflow blocks.
Friend Class DataflowExecutionBlocks
' Computes the number of zero bytes that the provided file
' contains.
Private Shared Function CountBytes(ByVal path As String) As Integer
Dim buffer(1023) As Byte
Dim totalZeroBytesRead As Integer = 0
Using fileStream = File.OpenRead(path)
Dim bytesRead As Integer = 0
Do
bytesRead = fileStream.Read(buffer, 0, buffer.Length)
totalZeroBytesRead += buffer.Count(Function(b) b = 0)
Loop While bytesRead > 0
End Using

Return totalZeroBytesRead
End Function

Shared Sub Main(ByVal args() As String)


' Create a temporary file on disk.
Dim tempFile As String = Path.GetTempFileName()

' Write random data to the temporary file.


Using fileStream = File.OpenWrite(tempFile)
Dim rand As New Random()
Dim buffer(1023) As Byte
For i As Integer = 0 To 511
rand.NextBytes(buffer)
fileStream.Write(buffer, 0, buffer.Length)
Next i
End Using

' Create an ActionBlock<int> object that prints to the console


' the number of bytes read.
Dim printResult = New ActionBlock(Of Integer)(Sub(zeroBytesRead) Console.WriteLine("{0} contains {1}
zero bytes.", Path.GetFileName(tempFile), zeroBytesRead))

' Create a TransformBlock<string, int> object that calls the


' CountBytes function and returns its result.
Dim countBytes = New TransformBlock(Of String, Integer)(New Func(Of String, Integer)(AddressOf
DataflowExecutionBlocks.CountBytes))

' Link the TransformBlock<string, int> object to the


' ActionBlock<int> object.
countBytes.LinkTo(printResult)

' Create a continuation task that completes the ActionBlock<int>


' object when the TransformBlock<string, int> finishes.
countBytes.Completion.ContinueWith(Sub() printResult.Complete())

' Post the path to the temporary file to the


' TransformBlock<string, int> object.
countBytes.Post(tempFile)

' Requests completion of the TransformBlock<string, int> object.


countBytes.Complete()

' Wait for the ActionBlock<int> object to print the message.


printResult.Completion.Wait()

' Delete the temporary file.


File.Delete(tempFile)
End Sub
End Class

' Sample output:


'tmp4FBE.tmp contains 2081 zero bytes.
'
Aunque puede proporcionar una expresión lambda para un objeto TransformBlock<TInput,TOutput>, este
ejemplo utiliza Func<T,TResult> para habilitar otro código para usar el método CountBytes . El objeto
ActionBlock<TInput> utiliza una expresión lambda porque el trabajo que se debe realizar es específico de esta
tarea y es probable que no sea útil desde otro código. Para información sobre cómo funcionan las expresiones
lambda en la biblioteca TPL, vea Expresiones lambda en PLINQ y TPL.
En la sección Resumen de tipos de delegado del documento Flujo de datos se resumen los tipos de delegado que
puede proporcionar a los objetos ActionBlock<TInput>, TransformBlock<TInput,TOutput> y
TransformManyBlock<TInput,TOutput>. La tabla también especifica si el tipo de delegado funciona de forma
sincrónica o asincrónica.

Programación sólida
En este ejemplo se proporcionada un delegado de tipo Func<T,TResult> para el objeto
TransformBlock<TInput,TOutput>, con el fin de realizar la tarea del bloque de flujo de datos de forma sincrónica.
Para que el bloque de flujo de datos se comporte de forma asincrónica, proporcione un delegado de tipo
Func<TResult> al bloque de flujo de datos. Cuando un bloque de flujo de datos se comporta de forma
asincrónica, la tarea del bloque de flujo de datos se completa solo cuando el objeto Task<TResult> devuelto
finaliza. En el ejemplo siguiente se modifica el método CountBytes y se usan los operadores async y await (Async
y Await en Visual Basic) para calcular de forma asincrónica el número total de bytes que son cero en el archivo
proporcionado. El método ReadAsync realiza las operaciones de lectura de archivo de forma asincrónica.

// Asynchronously computes the number of zero bytes that the provided file
// contains.
static async Task<int> CountBytesAsync(string path)
{
byte[] buffer = new byte[1024];
int totalZeroBytesRead = 0;
using (var fileStream = new FileStream(
path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x1000, true))
{
int bytesRead = 0;
do
{
// Asynchronously read from the file stream.
bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length);
totalZeroBytesRead += buffer.Count(b => b == 0);
} while (bytesRead > 0);
}

return totalZeroBytesRead;
}

' Asynchronously computes the number of zero bytes that the provided file
' contains.
Private Shared async Function CountBytesAsync(ByVal path As String) As Task(Of Integer)
Dim buffer(1023) As Byte
Dim totalZeroBytesRead As Integer = 0
Using fileStream = New FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, &H1000, True)
Dim bytesRead As Integer = 0
Do
' Asynchronously read from the file stream.
bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)
totalZeroBytesRead += buffer.Count(Function(b) b = 0)
Loop While bytesRead > 0
End Using

Return totalZeroBytesRead
End Function
También puede utilizar expresiones lambda asincrónicas para realizar la acción en un bloque de flujo de datos de
ejecución. En el ejemplo siguiente se modifica el objeto TransformBlock<TInput,TOutput> que se utiliza en el
ejemplo anterior para que utilice una expresión lambda para realizar el trabajo de forma asincrónica.

// Create a TransformBlock<string, int> object that calls the


// CountBytes function and returns its result.
var countBytesAsync = new TransformBlock<string, int>(async path =>
{
byte[] buffer = new byte[1024];
int totalZeroBytesRead = 0;
using (var fileStream = new FileStream(
path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x1000, true))
{
int bytesRead = 0;
do
{
// Asynchronously read from the file stream.
bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length);
totalZeroBytesRead += buffer.Count(b => b == 0);
} while (bytesRead > 0);
}

return totalZeroBytesRead;
});

' Create a TransformBlock<string, int> object that calls the


' CountBytes function and returns its result.
Dim countBytesAsync = New TransformBlock(Of String, Integer)(async Function(path)
' Asynchronously read from the file stream.
Dim buffer(1023) As Byte
Dim totalZeroBytesRead As Integer = 0
Using fileStream = New FileStream(path,
FileMode.Open, FileAccess.Read, FileShare.Read, &H1000, True)
Dim bytesRead As Integer = 0
Do
bytesRead = await
fileStream.ReadAsync(buffer, 0, buffer.Length)
totalZeroBytesRead +=
buffer.Count(Function(b) b = 0)
Loop While bytesRead > 0
End Using
Return totalZeroBytesRead
End Function)

Vea también
Flujo de datos
Tutorial: Creación de una canalización de flujos de
datos
16/09/2020 • 19 minutes to read • Edit Online

Aunque puede usar los métodos DataflowBlock.Receive, DataflowBlock.ReceiveAsync y DataflowBlock.TryReceive


para recibir mensajes de los bloques de origen, también puede conectar los bloques de mensajes para formar
una canalización de flujo de datos. Una canalización de flujo datos es una serie de componentes, o bloques de
flujo de datos, de los que cada uno realiza una tarea concreta que contribuye a lograr un objetivo mayor. Cada
bloque de flujo de datos de una canalización de flujo de datos realiza un determinado trabajo cuando recibe un
mensaje de otro bloque de flujo de datos. Se podría establecer una analogía de esto con una cadena de montaje
en la fabricación de automóviles. Mientras cada vehículo pasa a través de la línea de montaje, una estación monta
el bastidor, la siguiente instala el motor y así sucesivamente. Dado que una cadena de montaje permite montar
varios vehículos al mismo tiempo, proporciona un mejor rendimiento que montar de uno en uno los vehículos
completos.
En este documento se muestra una canalización de flujo de datos que descarga el libro La Ilíada de Homero
desde un sitio web y explora el texto para encontrar coincidencias entre palabras individuales y palabras que
revierten el orden de los caracteres de la primera palabra. La formación de la canalización de flujo de datos en
este documento se compone de los siguientes pasos:
1. Crear los bloques de flujo de datos que participan en la canalización.
2. Conectar cada bloque de flujo de datos con el siguiente bloque de la canalización. Cada bloque recibe
como entrada la salida del bloque anterior de la canalización.
3. Para cada bloque de flujo de datos, crear una tarea de continuación que establezca el siguiente bloque en
estado completado después de que finalice el bloque anterior.
4. Publicar datos en el encabezado de la canalización.
5. Marcar el encabezado de la canalización como completado.
6. Esperar a que la canalización complete todo el trabajo.

Requisitos previos
Lea Flujo de datos antes de empezar este tutorial.

Crear una aplicación de consola


En Visual Studio, cree un proyecto Aplicación de consola de Visual C# o Visual Basic. Instale el paquete NuGet
System.Threading.Tasks.Dataflow.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET. Para
instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar
paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow . Como
alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .
Agregue el código siguiente a su proyecto para crear la aplicación básica.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to create a basic dataflow pipeline.


// This program downloads the book "The Iliad of Homer" by Homer from the Web
// and finds all reversed words that appear in that book.
static class Program
{
static void Main()
{
}
}

Imports System.Net.Http
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to create a basic dataflow pipeline.


' This program downloads the book "The Iliad of Homer" by Homer from the Web
' and finds all reversed words that appear in that book.
Module DataflowReversedWords

Sub Main()
End Sub

End Module

Creación de los bloques de flujo de datos


Agregue el código siguiente al método Main para crear los bloques de flujo de datos que participan en la
canalización. En la tabla siguiente se resume el rol de cada miembro de la canalización.
//
// Create the members of the pipeline.
//

// Downloads the requested resource as a string.


var downloadString = new TransformBlock<string, string>(async uri =>
{
Console.WriteLine("Downloading '{0}'...", uri);

return await new HttpClient(new HttpClientHandler{ AutomaticDecompression =


System.Net.DecompressionMethods.GZip }).GetStringAsync(uri);
});

// Separates the specified text into an array of words.


var createWordList = new TransformBlock<string, string[]>(text =>
{
Console.WriteLine("Creating word list...");

// Remove common punctuation by replacing all non-letter characters


// with a space character.
char[] tokens = text.Select(c => char.IsLetter(c) ? c : ' ').ToArray();
text = new string(tokens);

// Separate the text into an array of words.


return text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
});

// Removes short words and duplicates.


var filterWordList = new TransformBlock<string[], string[]>(words =>
{
Console.WriteLine("Filtering word list...");

return words
.Where(word => word.Length > 3)
.Distinct()
.ToArray();
});

// Finds all words in the specified collection whose reverse also


// exists in the collection.
var findReversedWords = new TransformManyBlock<string[], string>(words =>
{
Console.WriteLine("Finding reversed words...");

var wordsSet = new HashSet<string>(words);

return from word in words.AsParallel()


let reverse = new string(word.Reverse().ToArray())
where word != reverse && wordsSet.Contains(reverse)
select word;
});

// Prints the provided reversed words to the console.


var printReversedWords = new ActionBlock<string>(reversedWord =>
{
Console.WriteLine("Found reversed words {0}/{1}",
reversedWord, new string(reversedWord.Reverse().ToArray()));
});
'
' Create the members of the pipeline.
'

' Downloads the requested resource as a string.


Dim downloadString = New TransformBlock(Of String, String)(
Async Function(uri)
Console.WriteLine("Downloading '{0}'...", uri)

Return Await New HttpClient().GetStringAsync(uri)


End Function)

' Separates the specified text into an array of words.


Dim createWordList = New TransformBlock(Of String, String())(
Function(text)
Console.WriteLine("Creating word list...")

' Remove common punctuation by replacing all non-letter characters


' with a space character.
Dim tokens() As Char = text.Select(Function(c) If(Char.IsLetter(c), c, " "c)).ToArray()
text = New String(tokens)

' Separate the text into an array of words.


Return text.Split(New Char() {" "c}, StringSplitOptions.RemoveEmptyEntries)
End Function)

' Removes short words and duplicates.


Dim filterWordList = New TransformBlock(Of String(), String())(
Function(words)
Console.WriteLine("Filtering word list...")

Return words.Where(Function(word) word.Length > 3).Distinct().ToArray()


End Function)

' Finds all words in the specified collection whose reverse also
' exists in the collection.
Dim findReversedWords = New TransformManyBlock(Of String(), String)(
Function(words)

Dim wordsSet = New HashSet(Of String)(words)

Return From word In words.AsParallel()


Let reverse = New String(word.Reverse().ToArray())
Where word <> reverse AndAlso wordsSet.Contains(reverse)
Select word
End Function)

' Prints the provided reversed words to the console.


Dim printReversedWords = New ActionBlock(Of String)(
Sub(reversedWord)
Console.WriteLine("Found reversed words {0}/{1}", reversedWord, New
String(reversedWord.Reverse().ToArray()))
End Sub)

M IEM B RO T IP O DESC RIP C IÓ N

downloadString TransformBlock<TInput,TOutput> Descarga el texto del libro desde la


Web.

createWordList TransformBlock<TInput,TOutput> Separa el texto del libro en una matriz


de palabras.
M IEM B RO T IP O DESC RIP C IÓ N

filterWordList TransformBlock<TInput,TOutput> Quita palabras cortas y duplicados de


la matriz de palabras.

findReversedWords TransformManyBlock<TInput,TOutput> Busca todas las palabras de la colección


de matriz de palabras filtradas cuya
palabra inversa también se encuentra
en la matriz de palabras.

printReversedWords ActionBlock<TInput> Muestra las palabras y sus


correspondientes palabras inversas en
la consola.

Aunque se podrían combinar varios pasos de la canalización de flujo de datos de este ejemplo en un solo paso,
en el ejemplo se muestra el concepto de componer varias tareas de flujo de datos independientes para realizar
una tarea mayor. En el ejemplo se usa TransformBlock<TInput,TOutput> para permitir que cada miembro de la
canalización pueda realizar una operación en sus datos de entrada y enviar los resultados al siguiente paso de la
canalización. El miembro findReversedWords de la canalización es un objeto
TransformManyBlock<TInput,TOutput> porque genera varias salidas independientes para cada entrada. El final
de la canalización, printReversedWords , es un objeto ActionBlock<TInput> porque realiza una acción en su
entrada y no genera una salida.

Formación de la canalización
Agregue el código siguiente para conectar cada bloque con el bloque siguiente en la canalización.
Cuando se llama al método LinkTo para conectar un bloque de flujo de datos de origen a un bloque de flujo de
datos de destino, el bloque de origen propaga los datos al bloque de destino a medida que los datos se
encuentran disponibles. Si también proporciona DataflowLinkOptions con PropagateCompletion establecido en
true, la finalización correcta o incorrecta de un bloque de la canalización conllevará la finalización del siguiente
bloque de la canalización.

//
// Connect the dataflow blocks to form a pipeline.
//

var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };

downloadString.LinkTo(createWordList, linkOptions);
createWordList.LinkTo(filterWordList, linkOptions);
filterWordList.LinkTo(findReversedWords, linkOptions);
findReversedWords.LinkTo(printReversedWords, linkOptions);

'
' Connect the dataflow blocks to form a pipeline.
'

Dim linkOptions = New DataflowLinkOptions With {.PropagateCompletion = True}

downloadString.LinkTo(createWordList, linkOptions)
createWordList.LinkTo(filterWordList, linkOptions)
filterWordList.LinkTo(findReversedWords, linkOptions)
findReversedWords.LinkTo(printReversedWords, linkOptions)

Envío de datos a la canalización


Agregue el código siguiente para publicar la dirección URL del libro La Ilíada de Homero en el encabezado de la
canalización de flujo de datos.

// Process "The Iliad of Homer" by Homer.


downloadString.Post("http://www.gutenberg.org/cache/epub/16452/pg16452.txt");

' Process "The Iliad of Homer" by Homer.


downloadString.Post("http://www.gutenberg.org/cache/epub/16452/pg16452.txt")

En este ejemplo se usa DataflowBlock.Post para enviar datos de forma sincrónica al encabezado de la
canalización. Use el método DataflowBlock.SendAsync cuando deba enviar datos de forma asincrónica a un nodo
de flujo de datos.

Finalización de la actividad de canalización


Agregue el código siguiente para marcar el encabezado de la canalización como completado. El encabezado de la
canalización propaga su finalización después de procesar todos los mensajes almacenados en búfer.

// Mark the head of the pipeline as complete.


downloadString.Complete();

' Mark the head of the pipeline as complete.


downloadString.Complete()

En este ejemplo se envía una dirección URL a través de la canalización de flujo de datos para que se procese. Si
envía más de una entrada a través de una canalización, llame al método IDataflowBlock.Complete después de
enviar toda la entrada. Puede omitir este paso si la aplicación no tiene ningún punto bien definido en el que los
datos ya no están disponibles o la aplicación no tiene que esperar a que finalice la canalización.

Espera para la finalización de la canalización


Agregue el código siguiente para esperar a que finalice la canalización. La operación global termina cuando
finaliza la cola de la canalización.

// Wait for the last block in the pipeline to process all messages.
printReversedWords.Completion.Wait();

' Wait for the last block in the pipeline to process all messages.
printReversedWords.Completion.Wait()

Puede esperar la finalización del flujo de datos desde cualquier subproceso o desde varios subprocesos al mismo
tiempo.

Ejemplo completo
En el ejemplo siguiente se muestra el código completo de este tutorial.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks.Dataflow;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to create a basic dataflow pipeline.


// This program downloads the book "The Iliad of Homer" by Homer from the Web
// and finds all reversed words that appear in that book.
static class DataflowReversedWords
{
static void Main()
{
//
// Create the members of the pipeline.
//

// Downloads the requested resource as a string.


var downloadString = new TransformBlock<string, string>(async uri =>
{
Console.WriteLine("Downloading '{0}'...", uri);

return await new HttpClient(new HttpClientHandler{ AutomaticDecompression =


System.Net.DecompressionMethods.GZip }).GetStringAsync(uri);
});

// Separates the specified text into an array of words.


var createWordList = new TransformBlock<string, string[]>(text =>
{
Console.WriteLine("Creating word list...");

// Remove common punctuation by replacing all non-letter characters


// with a space character.
char[] tokens = text.Select(c => char.IsLetter(c) ? c : ' ').ToArray();
text = new string(tokens);

// Separate the text into an array of words.


return text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
});

// Removes short words and duplicates.


var filterWordList = new TransformBlock<string[], string[]>(words =>
{
Console.WriteLine("Filtering word list...");

return words
.Where(word => word.Length > 3)
.Distinct()
.ToArray();
});

// Finds all words in the specified collection whose reverse also


// exists in the collection.
var findReversedWords = new TransformManyBlock<string[], string>(words =>
{
Console.WriteLine("Finding reversed words...");

var wordsSet = new HashSet<string>(words);

return from word in words.AsParallel()


let reverse = new string(word.Reverse().ToArray())
where word != reverse && wordsSet.Contains(reverse)
select word;
});

// Prints the provided reversed words to the console.


var printReversedWords = new ActionBlock<string>(reversedWord =>
{
Console.WriteLine("Found reversed words {0}/{1}",
reversedWord, new string(reversedWord.Reverse().ToArray()));
});

//
// Connect the dataflow blocks to form a pipeline.
// Connect the dataflow blocks to form a pipeline.
//

var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };

downloadString.LinkTo(createWordList, linkOptions);
createWordList.LinkTo(filterWordList, linkOptions);
filterWordList.LinkTo(findReversedWords, linkOptions);
findReversedWords.LinkTo(printReversedWords, linkOptions);

// Process "The Iliad of Homer" by Homer.


downloadString.Post("http://www.gutenberg.org/cache/epub/16452/pg16452.txt");

// Mark the head of the pipeline as complete.


downloadString.Complete();

// Wait for the last block in the pipeline to process all messages.
printReversedWords.Completion.Wait();
}
}
/* Sample output:
Downloading 'http://www.gutenberg.org/cache/epub/16452/pg16452.txt'...
Creating word list...
Filtering word list...
Finding reversed words...
Found reversed words doom/mood
Found reversed words draw/ward
Found reversed words aera/area
Found reversed words seat/taes
Found reversed words live/evil
Found reversed words port/trop
Found reversed words sleek/keels
Found reversed words area/aera
Found reversed words tops/spot
Found reversed words evil/live
Found reversed words mood/doom
Found reversed words speed/deeps
Found reversed words moor/room
Found reversed words trop/port
Found reversed words spot/tops
Found reversed words spots/stops
Found reversed words stops/spots
Found reversed words reed/deer
Found reversed words keels/sleek
Found reversed words deeps/speed
Found reversed words deer/reed
Found reversed words taes/seat
Found reversed words room/moor
Found reversed words ward/draw
*/

Imports System.Net.Http
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to create a basic dataflow pipeline.


' This program downloads the book "The Iliad of Homer" by Homer from the Web
' and finds all reversed words that appear in that book.
Module DataflowReversedWords

Sub Main()
'
' Create the members of the pipeline.
'

' Downloads the requested resource as a string.


Dim downloadString = New TransformBlock(Of String, String)(
Async Function(uri)
Console.WriteLine("Downloading '{0}'...", uri)
Console.WriteLine("Downloading '{0}'...", uri)

Return Await New HttpClient().GetStringAsync(uri)


End Function)

' Separates the specified text into an array of words.


Dim createWordList = New TransformBlock(Of String, String())(
Function(text)
Console.WriteLine("Creating word list...")

' Remove common punctuation by replacing all non-letter characters


' with a space character.
Dim tokens() As Char = text.Select(Function(c) If(Char.IsLetter(c), c, " "c)).ToArray()
text = New String(tokens)

' Separate the text into an array of words.


Return text.Split(New Char() {" "c}, StringSplitOptions.RemoveEmptyEntries)
End Function)

' Removes short words and duplicates.


Dim filterWordList = New TransformBlock(Of String(), String())(
Function(words)
Console.WriteLine("Filtering word list...")

Return words.Where(Function(word) word.Length > 3).Distinct().ToArray()


End Function)

' Finds all words in the specified collection whose reverse also
' exists in the collection.
Dim findReversedWords = New TransformManyBlock(Of String(), String)(
Function(words)

Dim wordsSet = New HashSet(Of String)(words)

Return From word In words.AsParallel()


Let reverse = New String(word.Reverse().ToArray())
Where word <> reverse AndAlso wordsSet.Contains(reverse)
Select word
End Function)

' Prints the provided reversed words to the console.


Dim printReversedWords = New ActionBlock(Of String)(
Sub(reversedWord)
Console.WriteLine("Found reversed words {0}/{1}", reversedWord, New
String(reversedWord.Reverse().ToArray()))
End Sub)

'
' Connect the dataflow blocks to form a pipeline.
'

Dim linkOptions = New DataflowLinkOptions With {.PropagateCompletion = True}

downloadString.LinkTo(createWordList, linkOptions)
createWordList.LinkTo(filterWordList, linkOptions)
filterWordList.LinkTo(findReversedWords, linkOptions)
findReversedWords.LinkTo(printReversedWords, linkOptions)

' Process "The Iliad of Homer" by Homer.


downloadString.Post("http://www.gutenberg.org/cache/epub/16452/pg16452.txt")

' Mark the head of the pipeline as complete.


downloadString.Complete()

' Wait for the last block in the pipeline to process all messages.
printReversedWords.Completion.Wait()
End Sub

End Module
' Sample output:
'Downloading 'http://www.gutenberg.org/cache/epub/16452/pg16452.txt'...
'Creating word list...
'Filtering word list...
'Finding reversed words...
'Found reversed words aera/area
'Found reversed words doom/mood
'Found reversed words draw/ward
'Found reversed words live/evil
'Found reversed words seat/taes
'Found reversed words area/aera
'Found reversed words port/trop
'Found reversed words sleek/keels
'Found reversed words tops/spot
'Found reversed words evil/live
'Found reversed words speed/deeps
'Found reversed words mood/doom
'Found reversed words moor/room
'Found reversed words spot/tops
'Found reversed words spots/stops
'Found reversed words trop/port
'Found reversed words stops/spots
'Found reversed words reed/deer
'Found reversed words deeps/speed
'Found reversed words deer/reed
'Found reversed words taes/seat
'Found reversed words keels/sleek
'Found reversed words room/moor
'Found reversed words ward/draw

Pasos siguientes
En este ejemplo se envía una dirección URL para procesarla a través de la canalización de flujo de datos. Si envía
más de un valor de entrada a través de una canalización, puede introducir un formulario de paralelismo en la
aplicación que se parezca a cómo se moverían las piezas en una fábrica de automóviles. Cuando el primer
miembro de la canalización envía su resultado al segundo miembro, puede procesar otro elemento en paralelo
mientras el segundo miembro procesa el primer resultado.
El paralelismo que se logra mediante el uso de canalizaciones de flujo de datos se conoce como paralelismo
general porque normalmente consta de menos tareas y más grandes. También puede usar un paralelismo
específico de tareas más pequeñas y breves en una canalización de flujo de datos. En este ejemplo, el miembro
findReversedWords de la canalización usa PLINQ para procesar en paralelo varios elementos de la lista de trabajo.
El uso de paralelismo de grano fino en una canalización de grano grueso puede mejorar el rendimiento global.
También puede conectar un bloque de flujo de datos de origen a varios bloques de destino para crear una red de
flujo de datos. La versión sobrecargada del método LinkTo toma un objeto Predicate<T> que define si el bloque
de destino acepta cada mensaje según su valor. La mayoría de los tipos de bloques de flujo de datos que actúan
como orígenes ofrecen mensajes a todos los bloques de destino conectados, siguiendo el orden en que se
conectaron, hasta que uno de los bloques acepta ese mensaje. Mediante este mecanismo de filtrado, puede crear
sistemas de bloques de flujo de datos conectados que dirigen determinados datos a través de una ruta de acceso
y otros datos a través de otra ruta de acceso. Para ver un ejemplo que usa el filtrado para crear una red de flujo
de datos, consulte Tutorial: Uso de flujos de datos en aplicaciones de Windows Forms.

Vea también
Flujo de datos
Cómo: Desvincular bloques de flujos de datos
16/09/2020 • 6 minutes to read • Edit Online

En este documento se describe cómo desvincular un bloque de flujo de datos de destino de su origen.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET. Para
instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar
paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow . Como
alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Ejemplo
En el ejemplo siguiente se crean tres objetos TransformBlock<TInput,TOutput>, y cada uno de ellos llama al
método TrySolution para calcular un valor. Este ejemplo requiere solo el resultado de la primera llamada a
TrySolution para finalizar.

using System;
using System.Threading;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to unlink dataflow blocks.


class DataflowReceiveAny
{
// Receives the value from the first provided source that has
// a message.
public static T ReceiveFromAny<T>(params ISourceBlock<T>[] sources)
{
// Create a WriteOnceBlock<T> object and link it to each source block.
var writeOnceBlock = new WriteOnceBlock<T>(e => e);
foreach (var source in sources)
{
// Setting MaxMessages to one instructs
// the source block to unlink from the WriteOnceBlock<T> object
// after offering the WriteOnceBlock<T> object one message.
source.LinkTo(writeOnceBlock, new DataflowLinkOptions { MaxMessages = 1 });
}
// Return the first value that is offered to the WriteOnceBlock object.
return writeOnceBlock.Receive();
}

// Demonstrates a function that takes several seconds to produce a result.


static int TrySolution(int n, CancellationToken ct)
{
// Simulate a lengthy operation that completes within three seconds
// or when the provided CancellationToken object is cancelled.
SpinWait.SpinUntil(() => ct.IsCancellationRequested,
new Random().Next(3000));

// Return a value.
return n + 42;
}

static void Main(string[] args)


{
// Create a shared CancellationTokenSource object to enable the
// Create a shared CancellationTokenSource object to enable the
// TrySolution method to be cancelled.
var cts = new CancellationTokenSource();

// Create three TransformBlock<int, int> objects.


// Each TransformBlock<int, int> object calls the TrySolution method.
Func<int, int> action = n => TrySolution(n, cts.Token);
var trySolution1 = new TransformBlock<int, int>(action);
var trySolution2 = new TransformBlock<int, int>(action);
var trySolution3 = new TransformBlock<int, int>(action);

// Post data to each TransformBlock<int, int> object.


trySolution1.Post(11);
trySolution2.Post(21);
trySolution3.Post(31);

// Call the ReceiveFromAny<T> method to receive the result from the


// first TransformBlock<int, int> object to finish.
int result = ReceiveFromAny(trySolution1, trySolution2, trySolution3);

// Cancel all calls to TrySolution that are still active.


cts.Cancel();

// Print the result to the console.


Console.WriteLine("The solution is {0}.", result);

cts.Dispose();
}
}

/* Sample output:
The solution is 53.
*/
Imports System.Threading
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to unlink dataflow blocks.


Friend Class DataflowReceiveAny
' Receives the value from the first provided source that has
' a message.
Public Shared Function ReceiveFromAny(Of T)(ParamArray ByVal sources() As ISourceBlock(Of T)) As T
' Create a WriteOnceBlock<T> object and link it to each source block.
Dim writeOnceBlock = New WriteOnceBlock(Of T)(Function(e) e)
For Each source In sources
' Setting MaxMessages to one instructs
' the source block to unlink from the WriteOnceBlock<T> object
' after offering the WriteOnceBlock<T> object one message.
source.LinkTo(writeOnceBlock, New DataflowLinkOptions With {.MaxMessages = 1})
Next source
' Return the first value that is offered to the WriteOnceBlock object.
Return writeOnceBlock.Receive()
End Function

' Demonstrates a function that takes several seconds to produce a result.


Private Shared Function TrySolution(ByVal n As Integer, ByVal ct As CancellationToken) As Integer
' Simulate a lengthy operation that completes within three seconds
' or when the provided CancellationToken object is cancelled.
SpinWait.SpinUntil(Function() ct.IsCancellationRequested, New Random().Next(3000))

' Return a value.


Return n + 42
End Function

Shared Sub Main(ByVal args() As String)


' Create a shared CancellationTokenSource object to enable the
' TrySolution method to be cancelled.
Dim cts = New CancellationTokenSource()

' Create three TransformBlock<int, int> objects.


' Each TransformBlock<int, int> object calls the TrySolution method.
Dim action As Func(Of Integer, Integer) = Function(n) TrySolution(n, cts.Token)
Dim trySolution1 = New TransformBlock(Of Integer, Integer)(action)
Dim trySolution2 = New TransformBlock(Of Integer, Integer)(action)
Dim trySolution3 = New TransformBlock(Of Integer, Integer)(action)

' Post data to each TransformBlock<int, int> object.


trySolution1.Post(11)
trySolution2.Post(21)
trySolution3.Post(31)

' Call the ReceiveFromAny<T> method to receive the result from the
' first TransformBlock<int, int> object to finish.
Dim result As Integer = ReceiveFromAny(trySolution1, trySolution2, trySolution3)

' Cancel all calls to TrySolution that are still active.


cts.Cancel()

' Print the result to the console.


Console.WriteLine("The solution is {0}.", result)

cts.Dispose()
End Sub
End Class

' Sample output:


'The solution is 53.
'

Para recibir el valor del primer objeto TransformBlock<TInput,TOutput> que termina, este ejemplo define el
método ReceiveFromAny(T) . El método ReceiveFromAny(T) acepta una matriz de objetos ISourceBlock<TOutput> y
vincula cada uno de estos objetos a un objeto WriteOnceBlock<T>. Cuando se usa el método LinkTo para vincular
un bloque de flujo de datos de origen a un bloque de destino, el origen propaga mensajes al destino a medida
que los datos están disponibles. Dado que la clase WriteOnceBlock<T> acepta solo el primer mensaje que se
ofrece, el método ReceiveFromAny(T) genera su resultado mediante una llamada al método Receive. Esto produce
el primer mensaje que se ofrece al objeto WriteOnceBlock<T>. El método LinkTo tiene una versión sobrecargada
que adopta un objeto DataflowLinkOptions con una propiedad MaxMessages que, cuando se establece en 1 ,
indica al bloque de origen que se desvincule del destino después de que el destino reciba un mensaje del origen.
Es importante que el objeto WriteOnceBlock<T> se desvincule de sus orígenes porque la relación entre la matriz
de orígenes y el objeto WriteOnceBlock<T> ya no es necesaria una vez que el objeto WriteOnceBlock<T> recibe
un mensaje.
Para habilitar las llamadas restantes a TrySolution para terminar una vez que una de ellas calcula un valor, el
método TrySolution adopta un objeto CancellationToken que se cancela después de llamar a las devoluciones
ReceiveFromAny(T) . El método SpinUntil realiza la devolución cuando este objeto CancellationToken se cancela.

Vea también
Flujo de datos
Tutorial: Usar flujos de datos en aplicaciones de
Windows Forms
16/09/2020 • 22 minutes to read • Edit Online

Este documento muestra cómo crear una red de bloques de flujo de datos que realizan el procesamiento de
imágenes en una aplicación de Windows Forms.
En este ejemplo se cargan archivos de imagen de la carpeta especificada, se crea una imagen compuesta y se
muestra el resultado. En el ejemplo se utiliza el modelo de flujo de datos para distribuir las imágenes por la red.
En el modelo de flujo de datos, los componentes independientes de un programa se comunican entre sí
mediante mensajes. Cuando un componente recibe un mensaje, realiza alguna acción y pasa el resultado a otro
componente. Compare esto con el modelo de flujo de control, en el que una aplicación utiliza estructuras de
control, como por ejemplo, instrucciones condicionales, bucles, etc., para controlar el orden de las operaciones
en un programa.

Requisitos previos
Lea Flujo de datos antes de empezar este tutorial.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET.
Para instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione
Administrar paquetes NuGet en el menú Proyecto y busque en línea el paquete
System.Threading.Tasks.Dataflow . Como alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Secciones
Este tutorial contiene las siguientes secciones:
Crear una aplicación de Windows Forms
Crear la red del flujo de datos
Conectar la red del flujo de datos a la interfaz de usuario
Ejemplo completo

Crear una aplicación de Windows Forms


En esta sección se describe cómo crear la aplicación básica de Windows Forms y agregar controles al
formulario principal.
Para crear la aplicación de Windows Forms
1. En Visual Studio, cree un proyecto Aplicación de Windows Forms de Visual C# o Visual Basic. En este
documento, el proyecto se denomina CompositeImages .
2. En el diseñador de formularios del formulario principal, Form1.cs (Form1.vb para Visual Basic), agregue
un control ToolStrip.
3. Agregue un control ToolStripButton al control ToolStrip. Establezca la propiedad DisplayStyle en Text y la
propiedad Text en Elegir carpeta .
4. Agregue un segundo control ToolStripButton al control ToolStrip. Establezca la propiedad DisplayStyle en
Text, la propiedad Text en Cancelar y la propiedad Enabled en False .
5. Agregue un objeto PictureBox al formulario principal. Establezca la propiedad Dock en Fill.

Crear la red del flujo de datos


En esta sección se describe cómo crear la red del flujo de datos que lleva a cabo el procesamiento de imágenes.
Para crear la red del flujo de datos
1. Agregue a su proyecto una referencia a System.Threading.Tasks.Dataflow.dll.
2. Asegúrese de que Form1.cs (Form1.vb para Visual Basic) contenga las siguientes instrucciones using (
Using en Visual Basic):

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;

3. Agregue a la clase Form1 los miembros de datos siguientes:

// The head of the dataflow network.


ITargetBlock<string> headBlock = null;

// Enables the user interface to signal cancellation to the network.


CancellationTokenSource cancellationTokenSource;

4. Agregue el método siguiente, CreateImageProcessingNetwork , a la clase Form1 . Este método crea la red
de procesamiento de imágenes.

// Creates the image processing dataflow network and returns the


// head node of the network.
ITargetBlock<string> CreateImageProcessingNetwork()
{
//
// Create the dataflow blocks that form the network.
//

// Create a dataflow block that takes a folder path as input


// and returns a collection of Bitmap objects.
var loadBitmaps = new TransformBlock<string, IEnumerable<Bitmap>>(path =>
{
try
{
return LoadBitmaps(path);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing the empty collection
// to the next stage of the network.
return Enumerable.Empty<Bitmap>();
return Enumerable.Empty<Bitmap>();
}
});

// Create a dataflow block that takes a collection of Bitmap objects


// and returns a single composite bitmap.
var createCompositeBitmap = new TransformBlock<IEnumerable<Bitmap>, Bitmap>(bitmaps =>
{
try
{
return CreateCompositeBitmap(bitmaps);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing null to the next stage
// of the network.
return null;
}
});

// Create a dataflow block that displays the provided bitmap on the form.
var displayCompositeBitmap = new ActionBlock<Bitmap>(bitmap =>
{
// Display the bitmap.
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Image = bitmap;

// Enable the user to select another folder.


toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

// Create a dataflow block that responds to a cancellation request by


// displaying an image to indicate that the operation is cancelled and
// enables the user to select another folder.
var operationCancelled = new ActionBlock<object>(delegate
{
// Display the error image to indicate that the operation
// was cancelled.
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Image = pictureBox1.ErrorImage;

// Enable the user to select another folder.


toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

//
// Connect the network.
//

// Link loadBitmaps to createCompositeBitmap.


// The provided predicate ensures that createCompositeBitmap accepts the
// collection of bitmaps only if that collection has at least one member.
loadBitmaps.LinkTo(createCompositeBitmap, bitmaps => bitmaps.Count() > 0);
// Also link loadBitmaps to operationCancelled.
// When createCompositeBitmap rejects the message, loadBitmaps
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
loadBitmaps.LinkTo(operationCancelled);

// Link createCompositeBitmap to displayCompositeBitmap.


// The provided predicate ensures that displayCompositeBitmap accepts the
// bitmap only if it is non-null.
createCompositeBitmap.LinkTo(displayCompositeBitmap, bitmap => bitmap != null);

// Also link createCompositeBitmap to operationCancelled.


// When displayCompositeBitmap rejects the message, createCompositeBitmap
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
createCompositeBitmap.LinkTo(operationCancelled);

// Return the head of the network.


return loadBitmaps;
}

5. Implemente el método LoadBitmaps .

// Loads all bitmap files that exist at the provided path.


IEnumerable<Bitmap> LoadBitmaps(string path)
{
List<Bitmap> bitmaps = new List<Bitmap>();

// Load a variety of image types.


foreach (string bitmapType in
new string[] { "*.bmp", "*.gif", "*.jpg", "*.png", "*.tif" })
{
// Load each bitmap for the current extension.
foreach (string fileName in Directory.GetFiles(path, bitmapType))
{
// Throw OperationCanceledException if cancellation is requested.
cancellationTokenSource.Token.ThrowIfCancellationRequested();

try
{
// Add the Bitmap object to the collection.
bitmaps.Add(new Bitmap(fileName));
}
catch (Exception)
{
// TODO: A complete application might handle the error.
}
}
}
return bitmaps;
}

6. Implemente el método CreateCompositeBitmap .

// Creates a composite bitmap from the provided collection of Bitmap objects.


// This method computes the average color of each pixel among all bitmaps
// to create the composite image.
Bitmap CreateCompositeBitmap(IEnumerable<Bitmap> bitmaps)
{
Bitmap[] bitmapArray = bitmaps.ToArray();

// Compute the maximum width and height components of all


// bitmaps in the collection.
// bitmaps in the collection.
Rectangle largest = new Rectangle();
foreach (var bitmap in bitmapArray)
{
if (bitmap.Width > largest.Width)
largest.Width = bitmap.Width;
if (bitmap.Height > largest.Height)
largest.Height = bitmap.Height;
}

// Create a 32-bit Bitmap object with the greatest dimensions.


Bitmap result = new Bitmap(largest.Width, largest.Height,
PixelFormat.Format32bppArgb);

// Lock the result Bitmap.


var resultBitmapData = result.LockBits(
new Rectangle(new Point(), result.Size), ImageLockMode.WriteOnly,
result.PixelFormat);

// Lock each source bitmap to create a parallel list of BitmapData objects.


var bitmapDataList = (from bitmap in bitmapArray
select bitmap.LockBits(
new Rectangle(new Point(), bitmap.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
.ToList();

// Compute each column in parallel.


Parallel.For(0, largest.Width, new ParallelOptions
{
CancellationToken = cancellationTokenSource.Token
},
i =>
{
// Compute each row.
for (int j = 0; j < largest.Height; j++)
{
// Counts the number of bitmaps whose dimensions
// contain the current location.
int count = 0;

// The sum of all alpha, red, green, and blue components.


int a = 0, r = 0, g = 0, b = 0;

// For each bitmap, compute the sum of all color components.


foreach (var bitmapData in bitmapDataList)
{
// Ensure that we stay within the bounds of the image.
if (bitmapData.Width > i && bitmapData.Height > j)
{
unsafe
{
byte* row = (byte*)(bitmapData.Scan0 + (j * bitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
a += *pix; pix++;
r += *pix; pix++;
g += *pix; pix++;
b += *pix;
}
count++;
}
}

//prevent divide by zero in bottom right pixelless corner


if (count == 0)
break;

unsafe
{
// Compute the average of each color component.
a /= count;
a /= count;
r /= count;
g /= count;
b /= count;

// Set the result pixel.


byte* row = (byte*)(resultBitmapData.Scan0 + (j * resultBitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
*pix = (byte)a; pix++;
*pix = (byte)r; pix++;
*pix = (byte)g; pix++;
*pix = (byte)b;
}
}
});

// Unlock the source bitmaps.


for (int i = 0; i < bitmapArray.Length; i++)
{
bitmapArray[i].UnlockBits(bitmapDataList[i]);
}

// Unlock the result bitmap.


result.UnlockBits(resultBitmapData);

// Return the result.


return result;
}

NOTE
La versión de C# del método CreateCompositeBitmap utiliza punteros para permitir un procesamiento eficaz de
los objetos System.Drawing.Bitmap. Por lo tanto, debe habilitar la opción Permitir código no seguro en el
proyecto para utilizar la palabra clave unsafe. Para obtener más información sobre cómo habilitar el código no
seguro en un proyecto de Visual C#, vea Compilar (Página, Diseñador de proyectos) (C#).

En la tabla siguiente se describen los miembros de la red.

M EM B ER T IP O DESC RIP T IO N

loadBitmaps TransformBlock<TInput,TOutput> Acepta una ruta de carpeta como


entrada y genera una colección de
objetos Bitmap como salida.

createCompositeBitmap TransformBlock<TInput,TOutput> Acepta una colección de objetos


Bitmap como entrada y produce un
mapa de bits compuesto como salida.

displayCompositeBitmap ActionBlock<TInput> Muestra el mapa de bits compuesto


en el formulario.

operationCancelled ActionBlock<TInput> Muestra una imagen para indicar que


la operación se cancela y permite al
usuario seleccionar otra carpeta.

Para conectar los bloques de flujo de datos para formar una red, este ejemplo usa el método LinkTo. El método
LinkTo contiene una versión sobrecargada que adopta un objeto Predicate<T> que determina si el bloque de
destino acepta o rechaza un mensaje. Este mecanismo de filtrado permite que los bloques de mensajes reciban
solo ciertos valores. En este ejemplo, la red puede dividirse en ramas de una de dos maneras. La rama principal
carga las imágenes desde el disco, crea la imagen compuesta y la muestra en el formulario. La rama alternativa
cancela la operación actual. Los objetos Predicate<T> permiten que los bloques de flujo de datos en la rama
principal cambien a la rama alternativa al rechazar determinados mensajes. Por ejemplo, si el usuario cancela la
operación, el bloque de flujo de datos createCompositeBitmap produce null ( Nothing en Visual Basic) como
salida. El bloque de flujo de datos displayCompositeBitmap rechaza valores de entrada null y, por lo tanto, el
mensaje se ofrece a operationCancelled . El bloque de flujo de datos operationCancelled acepta todos los
mensajes y, por lo tanto, muestra una imagen para indicar que se ha cancelado la operación.
En la ilustración siguiente se muestra la red de procesamiento de imágenes:

Dado que los bloques de flujo de datos displayCompositeBitmap y operationCancelled actúan sobre la interfaz
de usuario, es importante que esta acción se produzca en el subproceso de interfaz de usuario. Para lograrlo,
durante la construcción, cada uno de estos objetos proporciona un objeto ExecutionDataflowBlockOptions que
tiene la propiedad TaskScheduler establecida como TaskScheduler.FromCurrentSynchronizationContext. El
método TaskScheduler.FromCurrentSynchronizationContext crea un objeto TaskScheduler que funciona en el
contexto de sincronización actual. Como se llama al método CreateImageProcessingNetwork desde el controlador
del botón Elegir carpeta , que se ejecuta en el subproceso de la interfaz de usuario, las acciones para los
bloques de flujo de datos displayCompositeBitmap y operationCancelled también se ejecutan en el subproceso
de la interfaz de usuario.
Este ejemplo usa un token de cancelación compartido en lugar de establecer la propiedad CancellationToken,
porque la propiedad CancellationToken cancela permanentemente la ejecución del bloque de flujo de datos. En
este ejemplo, un token de cancelación permite reutilizar la misma red del flujo de datos varias veces, incluso
cuando el usuario cancela una o varias operaciones. Para obtener un ejemplo que usa CancellationToken para
cancelar la ejecución de un bloque de flujo de datos de modo permanente, vea Cómo: Cancelar un bloque de
flujos de datos.

Conectar la red del flujo de datos a la interfaz de usuario


En esta sección se describe cómo conectar la red del flujo de datos a la interfaz de usuario. La creación de la
imagen compuesta y la cancelación de la operación se inician desde los botones Elegir carpeta y Cancelar .
Cuando el usuario elige cualquiera de ellos, se inicia la acción correspondiente de forma asincrónica.
Para conectar la red del flujo de datos a la interfaz de usuario
1. En el diseñador de formularios del formulario principal, cree un controlador de eventos para el evento
Click del botón Elegir carpeta .
2. Implemente el evento Click del botón Elegir carpeta .
// Event handler for the Choose Folder button.
private void toolStripButton1_Click(object sender, EventArgs e)
{
// Create a FolderBrowserDialog object to enable the user to
// select a folder.
FolderBrowserDialog dlg = new FolderBrowserDialog
{
ShowNewFolderButton = false
};

// Set the selected path to the common Sample Pictures folder


// if it exists.
string initialDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures),
"Sample Pictures");
if (Directory.Exists(initialDirectory))
{
dlg.SelectedPath = initialDirectory;
}

// Show the dialog and process the dataflow network.


if (dlg.ShowDialog() == DialogResult.OK)
{
// Create a new CancellationTokenSource object to enable
// cancellation.
cancellationTokenSource = new CancellationTokenSource();

// Create the image processing network if needed.


headBlock ??= CreateImageProcessingNetwork();

// Post the selected path to the network.


headBlock.Post(dlg.SelectedPath);

// Enable the Cancel button and disable the Choose Folder button.
toolStripButton1.Enabled = false;
toolStripButton2.Enabled = true;

// Show a wait cursor.


Cursor = Cursors.WaitCursor;
}
}

3. En el diseñador de formularios del formulario principal, cree un controlador de eventos para el evento
Click del botón Cancelar .
4. Implemente el evento Click del botón Cancelar .

// Event handler for the Cancel button.


private void toolStripButton2_Click(object sender, EventArgs e)
{
// Signal the request for cancellation. The current component of
// the dataflow network will respond to the cancellation request.
cancellationTokenSource.Cancel();
}

Ejemplo completo
En el ejemplo siguiente se muestra el código completo de este tutorial.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;

namespace CompositeImages
{
public partial class Form1 : Form
{
// The head of the dataflow network.
ITargetBlock<string> headBlock = null;

// Enables the user interface to signal cancellation to the network.


CancellationTokenSource cancellationTokenSource;

public Form1()
{
InitializeComponent();
}

// Creates the image processing dataflow network and returns the


// head node of the network.
ITargetBlock<string> CreateImageProcessingNetwork()
{
//
// Create the dataflow blocks that form the network.
//

// Create a dataflow block that takes a folder path as input


// and returns a collection of Bitmap objects.
var loadBitmaps = new TransformBlock<string, IEnumerable<Bitmap>>(path =>
{
try
{
return LoadBitmaps(path);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing the empty collection
// to the next stage of the network.
return Enumerable.Empty<Bitmap>();
}
});

// Create a dataflow block that takes a collection of Bitmap objects


// and returns a single composite bitmap.
var createCompositeBitmap = new TransformBlock<IEnumerable<Bitmap>, Bitmap>(bitmaps =>
{
try
{
return CreateCompositeBitmap(bitmaps);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing null to the next stage
// of the network.
return null;
}
});

// Create a dataflow block that displays the provided bitmap on the form.
var displayCompositeBitmap = new ActionBlock<Bitmap>(bitmap =>
{
// Display the bitmap.
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Image = bitmap;
// Enable the user to select another folder.
toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

// Create a dataflow block that responds to a cancellation request by


// displaying an image to indicate that the operation is cancelled and
// enables the user to select another folder.
var operationCancelled = new ActionBlock<object>(delegate
{
// Display the error image to indicate that the operation
// was cancelled.
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Image = pictureBox1.ErrorImage;

// Enable the user to select another folder.


toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

//
// Connect the network.
//

// Link loadBitmaps to createCompositeBitmap.


// The provided predicate ensures that createCompositeBitmap accepts the
// collection of bitmaps only if that collection has at least one member.
loadBitmaps.LinkTo(createCompositeBitmap, bitmaps => bitmaps.Count() > 0);

// Also link loadBitmaps to operationCancelled.


// When createCompositeBitmap rejects the message, loadBitmaps
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
loadBitmaps.LinkTo(operationCancelled);

// Link createCompositeBitmap to displayCompositeBitmap.


// The provided predicate ensures that displayCompositeBitmap accepts the
// bitmap only if it is non-null.
createCompositeBitmap.LinkTo(displayCompositeBitmap, bitmap => bitmap != null);

// Also link createCompositeBitmap to operationCancelled.


// When displayCompositeBitmap rejects the message, createCompositeBitmap
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
createCompositeBitmap.LinkTo(operationCancelled);

// Return the head of the network.


return loadBitmaps;
}

// Loads all bitmap files that exist at the provided path.


IEnumerable<Bitmap> LoadBitmaps(string path)
{
{
List<Bitmap> bitmaps = new List<Bitmap>();

// Load a variety of image types.


foreach (string bitmapType in
new string[] { "*.bmp", "*.gif", "*.jpg", "*.png", "*.tif" })
{
// Load each bitmap for the current extension.
foreach (string fileName in Directory.GetFiles(path, bitmapType))
{
// Throw OperationCanceledException if cancellation is requested.
cancellationTokenSource.Token.ThrowIfCancellationRequested();

try
{
// Add the Bitmap object to the collection.
bitmaps.Add(new Bitmap(fileName));
}
catch (Exception)
{
// TODO: A complete application might handle the error.
}
}
}
return bitmaps;
}

// Creates a composite bitmap from the provided collection of Bitmap objects.


// This method computes the average color of each pixel among all bitmaps
// to create the composite image.
Bitmap CreateCompositeBitmap(IEnumerable<Bitmap> bitmaps)
{
Bitmap[] bitmapArray = bitmaps.ToArray();

// Compute the maximum width and height components of all


// bitmaps in the collection.
Rectangle largest = new Rectangle();
foreach (var bitmap in bitmapArray)
{
if (bitmap.Width > largest.Width)
largest.Width = bitmap.Width;
if (bitmap.Height > largest.Height)
largest.Height = bitmap.Height;
}

// Create a 32-bit Bitmap object with the greatest dimensions.


Bitmap result = new Bitmap(largest.Width, largest.Height,
PixelFormat.Format32bppArgb);

// Lock the result Bitmap.


var resultBitmapData = result.LockBits(
new Rectangle(new Point(), result.Size), ImageLockMode.WriteOnly,
result.PixelFormat);

// Lock each source bitmap to create a parallel list of BitmapData objects.


var bitmapDataList = (from bitmap in bitmapArray
select bitmap.LockBits(
new Rectangle(new Point(), bitmap.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
.ToList();

// Compute each column in parallel.


Parallel.For(0, largest.Width, new ParallelOptions
{
CancellationToken = cancellationTokenSource.Token
},
i =>
{
// Compute each row.
for (int j = 0; j < largest.Height; j++)
for (int j = 0; j < largest.Height; j++)
{
// Counts the number of bitmaps whose dimensions
// contain the current location.
int count = 0;

// The sum of all alpha, red, green, and blue components.


int a = 0, r = 0, g = 0, b = 0;

// For each bitmap, compute the sum of all color components.


foreach (var bitmapData in bitmapDataList)
{
// Ensure that we stay within the bounds of the image.
if (bitmapData.Width > i && bitmapData.Height > j)
{
unsafe
{
byte* row = (byte*)(bitmapData.Scan0 + (j * bitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
a += *pix; pix++;
r += *pix; pix++;
g += *pix; pix++;
b += *pix;
}
count++;
}
}

//prevent divide by zero in bottom right pixelless corner


if (count == 0)
break;

unsafe
{
// Compute the average of each color component.
a /= count;
r /= count;
g /= count;
b /= count;

// Set the result pixel.


byte* row = (byte*)(resultBitmapData.Scan0 + (j * resultBitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
*pix = (byte)a; pix++;
*pix = (byte)r; pix++;
*pix = (byte)g; pix++;
*pix = (byte)b;
}
}
});

// Unlock the source bitmaps.


for (int i = 0; i < bitmapArray.Length; i++)
{
bitmapArray[i].UnlockBits(bitmapDataList[i]);
}

// Unlock the result bitmap.


result.UnlockBits(resultBitmapData);

// Return the result.


return result;
}

// Event handler for the Choose Folder button.


private void toolStripButton1_Click(object sender, EventArgs e)
{
// Create a FolderBrowserDialog object to enable the user to
// select a folder.
FolderBrowserDialog dlg = new FolderBrowserDialog
FolderBrowserDialog dlg = new FolderBrowserDialog
{
ShowNewFolderButton = false
};

// Set the selected path to the common Sample Pictures folder


// if it exists.
string initialDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures),
"Sample Pictures");
if (Directory.Exists(initialDirectory))
{
dlg.SelectedPath = initialDirectory;
}

// Show the dialog and process the dataflow network.


if (dlg.ShowDialog() == DialogResult.OK)
{
// Create a new CancellationTokenSource object to enable
// cancellation.
cancellationTokenSource = new CancellationTokenSource();

// Create the image processing network if needed.


headBlock ??= CreateImageProcessingNetwork();

// Post the selected path to the network.


headBlock.Post(dlg.SelectedPath);

// Enable the Cancel button and disable the Choose Folder button.
toolStripButton1.Enabled = false;
toolStripButton2.Enabled = true;

// Show a wait cursor.


Cursor = Cursors.WaitCursor;
}
}

// Event handler for the Cancel button.


private void toolStripButton2_Click(object sender, EventArgs e)
{
// Signal the request for cancellation. The current component of
// the dataflow network will respond to the cancellation request.
cancellationTokenSource.Cancel();
}

~Form1()
{
cancellationTokenSource.Dispose();
}
}
}

En la ilustración siguiente se muestra la salida típica de la carpeta \Sample Pictures\ común.


Vea también
Flujo de datos
Cómo: Cancelar un bloque de flujos de datos
16/09/2020 • 18 minutes to read • Edit Online

En este ejemplo se explica cómo habilitar la cancelación en la aplicación. Este ejemplo usa Windows Forms para
mostrar dónde están activos los elementos de trabajo en una canalización de flujo de datos y también los efectos
de la canalización.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET. Para
instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar
paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow . Como
alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Para crear la aplicación de Windows Forms


1. Cree un proyecto de Aplicación de Windows Forms de Visual Basic o C#. En los pasos siguientes, el
proyecto se denomina CancellationWinForms .
2. En el diseñador de formularios del formulario principal, Form1.cs (Form1.vb para Visual Basic), agregue un
control ToolStrip.
3. Agregue un control ToolStripButton al control ToolStrip. Establezca la propiedad DisplayStyle en Text y la
propiedad Text en Agregar elementos de trabajo .
4. Agregue un segundo control ToolStripButton al control ToolStrip. Establezca la propiedad DisplayStyle en
Text, la propiedad Text en Cancelar y la propiedad Enabled en False .
5. Agregue cuatro objetos ToolStripProgressBar al control ToolStrip.

Creación de la canalización de flujo de datos


En esta sección se describe cómo crear la canalización de flujo de datos que procesa los elementos de trabajo y
actualiza las barras de progreso.
Para crear la canalización de flujo de datos
1. En el proyecto, agregue una referencia a System.Threading.Tasks.Dataflow.dll.
2. Asegúrese de que Form1.cs (Form1.vb para Visual Basic) contenga las siguientes instrucciones using (
Imports en Visual Basic).

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

3. Agregue la clase WorkItem como un tipo interno de la clase Form1 .

// A placeholder type that performs work.


class WorkItem
{
// Performs work for the provided number of milliseconds.
public void DoWork(int milliseconds)
{
// For demonstration, suspend the current thread.
Thread.Sleep(milliseconds);
}
}

' A placeholder type that performs work.


Private Class WorkItem
' Performs work for the provided number of milliseconds.
Public Sub DoWork(ByVal milliseconds As Integer)
' For demonstration, suspend the current thread.
Thread.Sleep(milliseconds)
End Sub
End Class

4. Agregue a la clase Form1 los miembros de datos siguientes:

// Enables the user interface to signal cancellation.


CancellationTokenSource cancellationSource;

// The first node in the dataflow pipeline.


TransformBlock<WorkItem, WorkItem> startWork;

// The second, and final, node in the dataflow pipeline.


ActionBlock<WorkItem> completeWork;

// Increments the value of the provided progress bar.


ActionBlock<ToolStripProgressBar> incrementProgress;

// Decrements the value of the provided progress bar.


ActionBlock<ToolStripProgressBar> decrementProgress;

// Enables progress bar actions to run on the UI thread.


TaskScheduler uiTaskScheduler;
' Enables the user interface to signal cancellation.
Private cancellationSource As CancellationTokenSource

' The first node in the dataflow pipeline.


Private startWork As TransformBlock(Of WorkItem, WorkItem)

' The second, and final, node in the dataflow pipeline.


Private completeWork As ActionBlock(Of WorkItem)

' Increments the value of the provided progress bar.


Private incrementProgress As ActionBlock(Of ToolStripProgressBar)

' Decrements the value of the provided progress bar.


Private decrementProgress As ActionBlock(Of ToolStripProgressBar)

' Enables progress bar actions to run on the UI thread.


Private uiTaskScheduler As TaskScheduler

5. Agregue el método siguiente, CreatePipeline , a la clase Form1 .

// Creates the blocks that participate in the dataflow pipeline.


private void CreatePipeline()
{
// Create the cancellation source.
cancellationSource = new CancellationTokenSource();

// Create the first node in the pipeline.


startWork = new TransformBlock<WorkItem, WorkItem>(workItem =>
{
// Perform some work.
workItem.DoWork(250);

// Decrement the progress bar that tracks the count of


// active work items in this stage of the pipeline.
decrementProgress.Post(toolStripProgressBar1);

// Increment the progress bar that tracks the count of


// active work items in the next stage of the pipeline.
incrementProgress.Post(toolStripProgressBar2);

// Send the work item to the next stage of the pipeline.


return workItem;
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token
});

// Create the second, and final, node in the pipeline.


completeWork = new ActionBlock<WorkItem>(workItem =>
{
// Perform some work.
workItem.DoWork(1000);

// Decrement the progress bar that tracks the count of


// active work items in this stage of the pipeline.
decrementProgress.Post(toolStripProgressBar2);

// Increment the progress bar that tracks the overall


// count of completed work items.
incrementProgress.Post(toolStripProgressBar3);
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
MaxDegreeOfParallelism = 2
});

// Connect the two nodes of the pipeline. When the first node completes,
// set the second node also to the completed state.
startWork.LinkTo(
completeWork, new DataflowLinkOptions { PropagateCompletion = true });

// Create the dataflow action blocks that increment and decrement


// progress bars.
// These blocks use the task scheduler that is associated with
// the UI thread.

incrementProgress = new ActionBlock<ToolStripProgressBar>(


progressBar => progressBar.Value++,
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
TaskScheduler = uiTaskScheduler
});

decrementProgress = new ActionBlock<ToolStripProgressBar>(


progressBar => progressBar.Value--,
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
TaskScheduler = uiTaskScheduler
});
}
' Creates the blocks that participate in the dataflow pipeline.
Private Sub CreatePipeline()
' Create the cancellation source.
cancellationSource = New CancellationTokenSource()

' Create the first node in the pipeline.


startWork = New TransformBlock(Of WorkItem, WorkItem)(Function(workItem)
' Perform some work.
' Decrement the progress bar that tracks
the count of
' active work items in this stage of the
pipeline.
' Increment the progress bar that tracks
the count of
' active work items in the next stage of
the pipeline.
' Send the work item to the next stage of
the pipeline.
workItem.DoWork(250)

decrementProgress.Post(toolStripProgressBar1)

incrementProgress.Post(toolStripProgressBar2)
Return workItem
End Function,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token})

' Create the second, and final, node in the pipeline.


completeWork = New ActionBlock(Of WorkItem)(Sub(workItem)
' Perform some work.
' Decrement the progress bar that tracks the count
of
' active work items in this stage of the pipeline.
' Increment the progress bar that tracks the
overall
' count of completed work items.
workItem.DoWork(1000)
decrementProgress.Post(toolStripProgressBar2)
incrementProgress.Post(toolStripProgressBar3)
End Sub,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.MaxDegreeOfParallelism = 2})

' Connect the two nodes of the pipeline. When the first node completes,
' set the second node also to the completed state.
startWork.LinkTo(
completeWork, New DataflowLinkOptions With {.PropagateCompletion = true})

' Create the dataflow action blocks that increment and decrement
' progress bars.
' These blocks use the task scheduler that is associated with
' the UI thread.

incrementProgress = New ActionBlock(Of ToolStripProgressBar)(


Sub(progressBar) progressBar.Value += 1,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.TaskScheduler = uiTaskScheduler})

decrementProgress = New ActionBlock(Of ToolStripProgressBar)(


Sub(progressBar) progressBar.Value -= 1,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.TaskScheduler = uiTaskScheduler})

End Sub
Dado que los bloques de flujo de datos incrementProgress y decrementProgress actúan sobre la interfaz de
usuario, es importante que esta acción se produzca en el subproceso de interfaz de usuario. Para lograrlo, durante
la construcción, cada uno de estos objetos proporciona un objeto ExecutionDataflowBlockOptions que tiene la
propiedad TaskScheduler establecida como TaskScheduler.FromCurrentSynchronizationContext. El método
TaskScheduler.FromCurrentSynchronizationContext crea un objeto TaskScheduler que funciona en el contexto de
sincronización actual. Dado que al constructor Form1 se le llama desde el subproceso de interfaz de usuario, las
acciones de los bloques de flujo de datos incrementProgress y decrementProgress se ejecutan también en el
subproceso de interfaz de usuario.
Este ejemplo establece la propiedad CancellationToken cuando construye los miembros de la canalización. Dado
que la propiedad CancellationToken cancela de forma permanente la ejecución del bloque de flujo de datos, se
debe volver a crear la canalización completa después de que el usuario cancela la operación, en caso de que
después desee agregar más elementos de trabajo a la canalización. Para consultar un ejemplo en el que se
muestre una forma alternativa de cancelar un bloque de flujo de datos, a fin de que se pueda realizar otro trabajo
después de cancelar una operación, vea Tutorial: uso de flujo de datos en una Aplicación de Windows Forms.

Conexión de la canalización de flujo de datos a la interfaz de usuario


En esta sección se describe cómo conectar la canalización de flujo de datos a la interfaz de usuario. Tanto la
creación de la canalización como la adición de elementos de trabajo a la canalización se controlan mediante el
controlador de eventos para el botón Agregar elementos de trabajo . La cancelación se inicia con el botón
Cancelar . Cuando el usuario hace clic en cualquiera de estos botones, se inicia la acción correspondiente de
forma asincrónica.
Para conectar la canalización de flujo de datos a la interfaz de usuario
1. En el diseñador de formularios del formulario principal, cree un controlador de eventos para el evento Click
del botón Agregar elementos de trabajo .
2. Implemente el evento Click para el botón Agregar elementos de trabajo .

// Event handler for the Add Work Items button.


private void toolStripButton1_Click(object sender, EventArgs e)
{
// The Cancel button is disabled when the pipeline is not active.
// Therefore, create the pipeline and enable the Cancel button
// if the Cancel button is disabled.
if (!toolStripButton2.Enabled)
{
CreatePipeline();

// Enable the Cancel button.


toolStripButton2.Enabled = true;
}

// Post several work items to the head of the pipeline.


for (int i = 0; i < 5; i++)
{
toolStripProgressBar1.Value++;
startWork.Post(new WorkItem());
}
}
' Event handler for the Add Work Items button.
Private Sub toolStripButton1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles
toolStripButton1.Click
' The Cancel button is disabled when the pipeline is not active.
' Therefore, create the pipeline and enable the Cancel button
' if the Cancel button is disabled.
If Not toolStripButton2.Enabled Then
CreatePipeline()

' Enable the Cancel button.


toolStripButton2.Enabled = True
End If

' Post several work items to the head of the pipeline.


For i As Integer = 0 To 4
toolStripProgressBar1.Value += 1
startWork.Post(New WorkItem())
Next i
End Sub

3. En el diseñador de formularios del formulario principal, cree un controlador de eventos Click para el botón
Cancelar .
4. Implemente el controlador de eventos Click para el botón Cancelar .

// Event handler for the Cancel button.


private async void toolStripButton2_Click(object sender, EventArgs e)
{
// Disable both buttons.
toolStripButton1.Enabled = false;
toolStripButton2.Enabled = false;

// Trigger cancellation.
cancellationSource.Cancel();

try
{
// Asynchronously wait for the pipeline to complete processing and for
// the progress bars to update.
await Task.WhenAll(
completeWork.Completion,
incrementProgress.Completion,
decrementProgress.Completion);
}
catch (OperationCanceledException)
{
}

// Increment the progress bar that tracks the number of cancelled


// work items by the number of active work items.
toolStripProgressBar4.Value += toolStripProgressBar1.Value;
toolStripProgressBar4.Value += toolStripProgressBar2.Value;

// Reset the progress bars that track the number of active work items.
toolStripProgressBar1.Value = 0;
toolStripProgressBar2.Value = 0;

// Enable the Add Work Items button.


toolStripButton1.Enabled = true;
}
' Event handler for the Cancel button.
Private Async Sub toolStripButton2_Click(ByVal sender As Object, ByVal e As EventArgs) Handles
toolStripButton2.Click
' Disable both buttons.
toolStripButton1.Enabled = False
toolStripButton2.Enabled = False

' Trigger cancellation.


cancellationSource.Cancel()

Try
' Asynchronously wait for the pipeline to complete processing and for
' the progress bars to update.
Await Task.WhenAll(completeWork.Completion, incrementProgress.Completion,
decrementProgress.Completion)
Catch e1 As OperationCanceledException
End Try

' Increment the progress bar that tracks the number of cancelled
' work items by the number of active work items.
toolStripProgressBar4.Value += toolStripProgressBar1.Value
toolStripProgressBar4.Value += toolStripProgressBar2.Value

' Reset the progress bars that track the number of active work items.
toolStripProgressBar1.Value = 0
toolStripProgressBar2.Value = 0

' Enable the Add Work Items button.


toolStripButton1.Enabled = True
End Sub

Ejemplo
En el siguiente ejemplo se muestra el código completo de Form1.cs (Form1.vb para Visual Basic).

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;

namespace CancellationWinForms
{
public partial class Form1 : Form
{
// A placeholder type that performs work.
class WorkItem
{
// Performs work for the provided number of milliseconds.
public void DoWork(int milliseconds)
{
// For demonstration, suspend the current thread.
Thread.Sleep(milliseconds);
}
}

// Enables the user interface to signal cancellation.


CancellationTokenSource cancellationSource;

// The first node in the dataflow pipeline.


TransformBlock<WorkItem, WorkItem> startWork;

// The second, and final, node in the dataflow pipeline.


ActionBlock<WorkItem> completeWork;
// Increments the value of the provided progress bar.
ActionBlock<ToolStripProgressBar> incrementProgress;

// Decrements the value of the provided progress bar.


ActionBlock<ToolStripProgressBar> decrementProgress;

// Enables progress bar actions to run on the UI thread.


TaskScheduler uiTaskScheduler;

public Form1()
{
InitializeComponent();

// Create the UI task scheduler from the current sychronization


// context.
uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}

// Creates the blocks that participate in the dataflow pipeline.


private void CreatePipeline()
{
// Create the cancellation source.
cancellationSource = new CancellationTokenSource();

// Create the first node in the pipeline.


startWork = new TransformBlock<WorkItem, WorkItem>(workItem =>
{
// Perform some work.
workItem.DoWork(250);

// Decrement the progress bar that tracks the count of


// active work items in this stage of the pipeline.
decrementProgress.Post(toolStripProgressBar1);

// Increment the progress bar that tracks the count of


// active work items in the next stage of the pipeline.
incrementProgress.Post(toolStripProgressBar2);

// Send the work item to the next stage of the pipeline.


return workItem;
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token
});

// Create the second, and final, node in the pipeline.


completeWork = new ActionBlock<WorkItem>(workItem =>
{
// Perform some work.
workItem.DoWork(1000);

// Decrement the progress bar that tracks the count of


// active work items in this stage of the pipeline.
decrementProgress.Post(toolStripProgressBar2);

// Increment the progress bar that tracks the overall


// count of completed work items.
incrementProgress.Post(toolStripProgressBar3);
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
MaxDegreeOfParallelism = 2
});

// Connect the two nodes of the pipeline. When the first node completes,
// set the second node also to the completed state.
startWork.LinkTo(
startWork.LinkTo(
completeWork, new DataflowLinkOptions { PropagateCompletion = true });

// Create the dataflow action blocks that increment and decrement


// progress bars.
// These blocks use the task scheduler that is associated with
// the UI thread.

incrementProgress = new ActionBlock<ToolStripProgressBar>(


progressBar => progressBar.Value++,
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
TaskScheduler = uiTaskScheduler
});

decrementProgress = new ActionBlock<ToolStripProgressBar>(


progressBar => progressBar.Value--,
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
TaskScheduler = uiTaskScheduler
});
}

// Event handler for the Add Work Items button.


private void toolStripButton1_Click(object sender, EventArgs e)
{
// The Cancel button is disabled when the pipeline is not active.
// Therefore, create the pipeline and enable the Cancel button
// if the Cancel button is disabled.
if (!toolStripButton2.Enabled)
{
CreatePipeline();

// Enable the Cancel button.


toolStripButton2.Enabled = true;
}

// Post several work items to the head of the pipeline.


for (int i = 0; i < 5; i++)
{
toolStripProgressBar1.Value++;
startWork.Post(new WorkItem());
}
}

// Event handler for the Cancel button.


private async void toolStripButton2_Click(object sender, EventArgs e)
{
// Disable both buttons.
toolStripButton1.Enabled = false;
toolStripButton2.Enabled = false;

// Trigger cancellation.
cancellationSource.Cancel();

try
{
// Asynchronously wait for the pipeline to complete processing and for
// the progress bars to update.
await Task.WhenAll(
completeWork.Completion,
incrementProgress.Completion,
decrementProgress.Completion);
}
catch (OperationCanceledException)
{
}
// Increment the progress bar that tracks the number of cancelled
// work items by the number of active work items.
toolStripProgressBar4.Value += toolStripProgressBar1.Value;
toolStripProgressBar4.Value += toolStripProgressBar2.Value;

// Reset the progress bars that track the number of active work items.
toolStripProgressBar1.Value = 0;
toolStripProgressBar2.Value = 0;

// Enable the Add Work Items button.


toolStripButton1.Enabled = true;
}

~Form1()
{
cancellationSource.Dispose();
}
}
}

Imports System.Threading
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

Namespace CancellationWinForms
Partial Public Class Form1
Inherits Form
' A placeholder type that performs work.
Private Class WorkItem
' Performs work for the provided number of milliseconds.
Public Sub DoWork(ByVal milliseconds As Integer)
' For demonstration, suspend the current thread.
Thread.Sleep(milliseconds)
End Sub
End Class

' Enables the user interface to signal cancellation.


Private cancellationSource As CancellationTokenSource

' The first node in the dataflow pipeline.


Private startWork As TransformBlock(Of WorkItem, WorkItem)

' The second, and final, node in the dataflow pipeline.


Private completeWork As ActionBlock(Of WorkItem)

' Increments the value of the provided progress bar.


Private incrementProgress As ActionBlock(Of ToolStripProgressBar)

' Decrements the value of the provided progress bar.


Private decrementProgress As ActionBlock(Of ToolStripProgressBar)

' Enables progress bar actions to run on the UI thread.


Private uiTaskScheduler As TaskScheduler

Public Sub New()


InitializeComponent()

' Create the UI task scheduler from the current sychronization


' context.
uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
End Sub

' Creates the blocks that participate in the dataflow pipeline.


Private Sub CreatePipeline()
' Create the cancellation source.
cancellationSource = New CancellationTokenSource()
cancellationSource = New CancellationTokenSource()

' Create the first node in the pipeline.


startWork = New TransformBlock(Of WorkItem, WorkItem)(Function(workItem)
' Perform some work.
' Decrement the progress bar that
tracks the count of
' active work items in this stage of
the pipeline.
' Increment the progress bar that
tracks the count of
' active work items in the next stage
of the pipeline.
' Send the work item to the next stage
of the pipeline.
workItem.DoWork(250)

decrementProgress.Post(toolStripProgressBar1)

incrementProgress.Post(toolStripProgressBar2)
Return workItem
End Function,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token})

' Create the second, and final, node in the pipeline.


completeWork = New ActionBlock(Of WorkItem)(Sub(workItem)
' Perform some work.
' Decrement the progress bar that tracks the
count of
' active work items in this stage of the
pipeline.
' Increment the progress bar that tracks the
overall
' count of completed work items.
workItem.DoWork(1000)
decrementProgress.Post(toolStripProgressBar2)
incrementProgress.Post(toolStripProgressBar3)
End Sub,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.MaxDegreeOfParallelism = 2})

' Connect the two nodes of the pipeline. When the first node completes,
' set the second node also to the completed state.
startWork.LinkTo(
completeWork, New DataflowLinkOptions With {.PropagateCompletion = true})

' Create the dataflow action blocks that increment and decrement
' progress bars.
' These blocks use the task scheduler that is associated with
' the UI thread.

incrementProgress = New ActionBlock(Of ToolStripProgressBar)(


Sub(progressBar) progressBar.Value += 1,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.TaskScheduler = uiTaskScheduler})

decrementProgress = New ActionBlock(Of ToolStripProgressBar)(


Sub(progressBar) progressBar.Value -= 1,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.TaskScheduler = uiTaskScheduler})

End Sub

' Event handler for the Add Work Items button.


Private Sub toolStripButton1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles
toolStripButton1.Click
' The Cancel button is disabled when the pipeline is not active.
' Therefore, create the pipeline and enable the Cancel button
' if the Cancel button is disabled.
If Not toolStripButton2.Enabled Then
If Not toolStripButton2.Enabled Then
CreatePipeline()

' Enable the Cancel button.


toolStripButton2.Enabled = True
End If

' Post several work items to the head of the pipeline.


For i As Integer = 0 To 4
toolStripProgressBar1.Value += 1
startWork.Post(New WorkItem())
Next i
End Sub

' Event handler for the Cancel button.


Private Async Sub toolStripButton2_Click(ByVal sender As Object, ByVal e As EventArgs) Handles
toolStripButton2.Click
' Disable both buttons.
toolStripButton1.Enabled = False
toolStripButton2.Enabled = False

' Trigger cancellation.


cancellationSource.Cancel()

Try
' Asynchronously wait for the pipeline to complete processing and for
' the progress bars to update.
Await Task.WhenAll(completeWork.Completion, incrementProgress.Completion,
decrementProgress.Completion)
Catch e1 As OperationCanceledException
End Try

' Increment the progress bar that tracks the number of cancelled
' work items by the number of active work items.
toolStripProgressBar4.Value += toolStripProgressBar1.Value
toolStripProgressBar4.Value += toolStripProgressBar2.Value

' Reset the progress bars that track the number of active work items.
toolStripProgressBar1.Value = 0
toolStripProgressBar2.Value = 0

' Enable the Add Work Items button.


toolStripButton1.Enabled = True
End Sub

Protected Overrides Sub Finalize()


cancellationSource.Dispose()
MyBase.Finalize()
End Sub
End Class
End Namespace

En la ilustración siguiente se muestra la aplicación en ejecución.

Vea también
Flujo de datos
Tutorial: Crear tipos de bloques de flujos de datos
personalizados
16/09/2020 • 23 minutes to read • Edit Online

Aunque la biblioteca de flujo de datos TPL proporciona varios tipos de bloques de flujo de datos que permiten una
variedad de funciones, también puede crear tipos de bloques personalizados. En este documento se describe cómo
crear un tipo de bloque de flujo de datos que implementa un comportamiento personalizado.

Requisitos previos
Lea Flujo de datos antes de leer este documento.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET. Para
instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar
paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow . Como
alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Definición de un bloque de flujo de datos de ventana deslizante


Considere la posibilidad de que una aplicación de flujo de datos requiera que los valores de entrada se almacenen
en búfer y que el resultado se muestre en un tipo de ventana deslizante. Por ejemplo, para los valores de entrada
{0, 1, 2, 3, 4, 5} y un tamaño de ventana de tres, un bloque de flujo de datos de ventana deslizante produce las
matrices de salida {0, 1, 2}, {1, 2, 3}, {2, 3, 4} y {3, 4, 5}. En las secciones siguientes se describen dos maneras de
crear un tipo de bloque de flujo de datos que implementa este comportamiento personalizado. La primera técnica
utiliza el método Encapsulate para combinar la funcionalidad de un objeto ISourceBlock<TOutput> y un objeto
ITargetBlock<TInput> en un bloque propagador. La segunda técnica define una clase que deriva de
IPropagatorBlock<TInput,TOutput> y combina la funcionalidad existente para llevar a cabo un comportamiento
personalizado.

Uso del método encapsulador para definir el bloque de flujo de datos


de ventana deslizante
En el ejemplo siguiente se usa el método Encapsulate para crear un bloque propagador desde un origen y un
destino. Un bloque propagador permite que un bloque de origen y un bloque de destino actúen como un
destinatario y remitente de datos.
Esta técnica es útil cuando necesita la funcionalidad de flujo de datos personalizada, pero no necesita un tipo que
proporcione métodos, propiedades o campos adicionales.
// Creates a IPropagatorBlock<T, T[]> object propagates data in a
// sliding window fashion.
public static IPropagatorBlock<T, T[]> CreateSlidingWindow<T>(int windowSize)
{
// Create a queue to hold messages.
var queue = new Queue<T>();

// The source part of the propagator holds arrays of size windowSize


// and propagates data out to any connected targets.
var source = new BufferBlock<T[]>();

// The target part receives data and adds them to the queue.
var target = new ActionBlock<T>(item =>
{
// Add the item to the queue.
queue.Enqueue(item);
// Remove the oldest item when the queue size exceeds the window size.
if (queue.Count > windowSize)
queue.Dequeue();
// Post the data in the queue to the source block when the queue size
// equals the window size.
if (queue.Count == windowSize)
source.Post(queue.ToArray());
});

// When the target is set to the completed state, propagate out any
// remaining data and set the source to the completed state.
target.Completion.ContinueWith(delegate
{
if (queue.Count > 0 && queue.Count < windowSize)
source.Post(queue.ToArray());
source.Complete();
});

// Return a IPropagatorBlock<T, T[]> object that encapsulates the


// target and source blocks.
return DataflowBlock.Encapsulate(target, source);
}
' Creates a IPropagatorBlock<T, T[]> object propagates data in a
' sliding window fashion.
Public Shared Function CreateSlidingWindow(Of T)(ByVal windowSize As Integer) As IPropagatorBlock(Of T, T())
' Create a queue to hold messages.
Dim queue = New Queue(Of T)()

' The source part of the propagator holds arrays of size windowSize
' and propagates data out to any connected targets.
Dim source = New BufferBlock(Of T())()

' The target part receives data and adds them to the queue.
Dim target = New ActionBlock(Of T)(Sub(item)
' Add the item to the queue.
' Remove the oldest item when the queue size exceeds the window
size.
' Post the data in the queue to the source block when the queue
size
' equals the window size.
queue.Enqueue(item)
If queue.Count > windowSize Then
queue.Dequeue()
End If
If queue.Count = windowSize Then
source.Post(queue.ToArray())
End If
End Sub)

' When the target is set to the completed state, propagate out any
' remaining data and set the source to the completed state.
target.Completion.ContinueWith(Sub()
If queue.Count > 0 AndAlso queue.Count < windowSize Then
source.Post(queue.ToArray())
End If
source.Complete()
End Sub)

' Return a IPropagatorBlock<T, T[]> object that encapsulates the


' target and source blocks.
Return DataflowBlock.Encapsulate(target, source)
End Function

Derivación de IPropagatorBlock para definir el bloque de flujo de datos


de ventana deslizante
En el ejemplo siguiente se muestra la clase SlidingWindowBlock . Esta clase se deriva de
IPropagatorBlock<TInput,TOutput> para que pueda actuar como un origen y un destino de datos. Como se
muestra en el ejemplo anterior, la clase SlidingWindowBlock se basa en los tipos de bloques de flujo de datos
existentes. Sin embargo, la clase SlidingWindowBlock también implementa los métodos necesarios para las
interfaces ISourceBlock<TOutput>, ITargetBlock<TInput> y IDataflowBlock. Todos estos métodos reenvían el
trabajo a los miembros del tipo de bloque de flujo de datos predefinido. Por ejemplo, el método Post aplaza el
trabajo para el miembro de datos m_target , que también es un objeto ITargetBlock<TInput>.
Esta técnica es útil cuando necesita la funcionalidad de flujo de datos personalizada y también necesita un tipo que
proporcione métodos, propiedades o campos adicionales. Por ejemplo, la clase SlidingWindowBlock también
deriva de IReceivableSourceBlock<TOutput>, para que pueda proporcionar los métodos TryReceive y
TryReceiveAll. La clase SlidingWindowBlock también muestra la extensibilidad al proporcionar la propiedad
WindowSize , que recupera el número de elementos en la ventana deslizante.

// Propagates data in a sliding window fashion.


public class SlidingWindowBlock<T> : IPropagatorBlock<T, T[]>,
IReceivableSourceBlock<T[]>
IReceivableSourceBlock<T[]>
{
// The size of the window.
private readonly int m_windowSize;
// The target part of the block.
private readonly ITargetBlock<T> m_target;
// The source part of the block.
private readonly IReceivableSourceBlock<T[]> m_source;

// Constructs a SlidingWindowBlock object.


public SlidingWindowBlock(int windowSize)
{
// Create a queue to hold messages.
var queue = new Queue<T>();

// The source part of the propagator holds arrays of size windowSize


// and propagates data out to any connected targets.
var source = new BufferBlock<T[]>();

// The target part receives data and adds them to the queue.
var target = new ActionBlock<T>(item =>
{
// Add the item to the queue.
queue.Enqueue(item);
// Remove the oldest item when the queue size exceeds the window size.
if (queue.Count > windowSize)
queue.Dequeue();
// Post the data in the queue to the source block when the queue size
// equals the window size.
if (queue.Count == windowSize)
source.Post(queue.ToArray());
});

// When the target is set to the completed state, propagate out any
// remaining data and set the source to the completed state.
target.Completion.ContinueWith(delegate
{
if (queue.Count > 0 && queue.Count < windowSize)
source.Post(queue.ToArray());
source.Complete();
});

m_windowSize = windowSize;
m_target = target;
m_source = source;
}

// Retrieves the size of the window.


public int WindowSize { get { return m_windowSize; } }

#region IReceivableSourceBlock<TOutput> members

// Attempts to synchronously receive an item from the source.


public bool TryReceive(Predicate<T[]> filter, out T[] item)
{
return m_source.TryReceive(filter, out item);
}

// Attempts to remove all available elements from the source into a new
// array that is returned.
public bool TryReceiveAll(out IList<T[]> items)
{
return m_source.TryReceiveAll(out items);
}

#endregion

#region ISourceBlock<TOutput> members

// Links this dataflow block to the provided target.


// Links this dataflow block to the provided target.
public IDisposable LinkTo(ITargetBlock<T[]> target, DataflowLinkOptions linkOptions)
{
return m_source.LinkTo(target, linkOptions);
}

// Called by a target to reserve a message previously offered by a source


// but not yet consumed by this target.
bool ISourceBlock<T[]>.ReserveMessage(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target)
{
return m_source.ReserveMessage(messageHeader, target);
}

// Called by a target to consume a previously offered message from a source.


T[] ISourceBlock<T[]>.ConsumeMessage(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target, out bool messageConsumed)
{
return m_source.ConsumeMessage(messageHeader,
target, out messageConsumed);
}

// Called by a target to release a previously reserved message from a source.


void ISourceBlock<T[]>.ReleaseReservation(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target)
{
m_source.ReleaseReservation(messageHeader, target);
}

#endregion

#region ITargetBlock<TInput> members

// Asynchronously passes a message to the target block, giving the target the
// opportunity to consume the message.
DataflowMessageStatus ITargetBlock<T>.OfferMessage(DataflowMessageHeader messageHeader,
T messageValue, ISourceBlock<T> source, bool consumeToAccept)
{
return m_target.OfferMessage(messageHeader,
messageValue, source, consumeToAccept);
}

#endregion

#region IDataflowBlock members

// Gets a Task that represents the completion of this dataflow block.


public Task Completion { get { return m_source.Completion; } }

// Signals to this target block that it should not accept any more messages,
// nor consume postponed messages.
public void Complete()
{
m_target.Complete();
}

public void Fault(Exception error)


{
m_target.Fault(error);
}

#endregion
}

' Propagates data in a sliding window fashion.


Public Class SlidingWindowBlock(Of T)
Implements IPropagatorBlock(Of T, T()), IReceivableSourceBlock(Of T())
' The size of the window.
' The size of the window.
Private ReadOnly m_windowSize As Integer
' The target part of the block.
Private ReadOnly m_target As ITargetBlock(Of T)
' The source part of the block.
Private ReadOnly m_source As IReceivableSourceBlock(Of T())

' Constructs a SlidingWindowBlock object.


Public Sub New(ByVal windowSize As Integer)
' Create a queue to hold messages.
Dim queue = New Queue(Of T)()

' The source part of the propagator holds arrays of size windowSize
' and propagates data out to any connected targets.
Dim source = New BufferBlock(Of T())()

' The target part receives data and adds them to the queue.
Dim target = New ActionBlock(Of T)(Sub(item)
' Add the item to the queue.
' Remove the oldest item when the queue size exceeds the
window size.
' Post the data in the queue to the source block when the
queue size
' equals the window size.
queue.Enqueue(item)
If queue.Count > windowSize Then
queue.Dequeue()
End If
If queue.Count = windowSize Then
source.Post(queue.ToArray())
End If
End Sub)

' When the target is set to the completed state, propagate out any
' remaining data and set the source to the completed state.
target.Completion.ContinueWith(Sub()
If queue.Count > 0 AndAlso queue.Count < windowSize Then
source.Post(queue.ToArray())
End If
source.Complete()
End Sub)

m_windowSize = windowSize
m_target = target
m_source = source
End Sub

' Retrieves the size of the window.


Public ReadOnly Property WindowSize() As Integer
Get
Return m_windowSize
End Get
End Property

'#Region "IReceivableSourceBlock<TOutput> members"

' Attempts to synchronously receive an item from the source.


Public Function TryReceive(ByVal filter As Predicate(Of T()), <System.Runtime.InteropServices.Out()>
ByRef item() As T) As Boolean Implements IReceivableSourceBlock(Of T()).TryReceive
Return m_source.TryReceive(filter, item)
End Function

' Attempts to remove all available elements from the source into a new
' array that is returned.
Public Function TryReceiveAll(<System.Runtime.InteropServices.Out()> ByRef items As IList(Of T())) As
Boolean Implements IReceivableSourceBlock(Of T()).TryReceiveAll
Return m_source.TryReceiveAll(items)
End Function

'#End Region
'#End Region

#Region "ISourceBlock<TOutput> members"

' Links this dataflow block to the provided target.


Public Function LinkTo(ByVal target As ITargetBlock(Of T()), ByVal linkOptions As DataflowLinkOptions)
As IDisposable Implements ISourceBlock(Of T()).LinkTo
Return m_source.LinkTo(target, linkOptions)
End Function

' Called by a target to reserve a message previously offered by a source


' but not yet consumed by this target.
Private Function ReserveMessage(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T())) As Boolean Implements ISourceBlock(Of T()).ReserveMessage
Return m_source.ReserveMessage(messageHeader, target)
End Function

' Called by a target to consume a previously offered message from a source.


Private Function ConsumeMessage(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T()), ByRef messageConsumed As Boolean) As T() Implements ISourceBlock(Of T()).ConsumeMessage
Return m_source.ConsumeMessage(messageHeader, target, messageConsumed)
End Function

' Called by a target to release a previously reserved message from a source.


Private Sub ReleaseReservation(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T())) Implements ISourceBlock(Of T()).ReleaseReservation
m_source.ReleaseReservation(messageHeader, target)
End Sub

#End Region

#Region "ITargetBlock<TInput> members"

' Asynchronously passes a message to the target block, giving the target the
' opportunity to consume the message.
Private Function OfferMessage(ByVal messageHeader As DataflowMessageHeader, ByVal messageValue As T,
ByVal source As ISourceBlock(Of T), ByVal consumeToAccept As Boolean) As DataflowMessageStatus Implements
ITargetBlock(Of T).OfferMessage
Return m_target.OfferMessage(messageHeader, messageValue, source, consumeToAccept)
End Function

#End Region

#Region "IDataflowBlock members"

' Gets a Task that represents the completion of this dataflow block.
Public ReadOnly Property Completion() As Task Implements IDataflowBlock.Completion
Get
Return m_source.Completion
End Get
End Property

' Signals to this target block that it should not accept any more messages,
' nor consume postponed messages.
Public Sub Complete() Implements IDataflowBlock.Complete
m_target.Complete()
End Sub

Public Sub Fault(ByVal [error] As Exception) Implements IDataflowBlock.Fault


m_target.Fault([error])
End Sub

#End Region
End Class

Ejemplo completo
En el ejemplo siguiente se muestra el código completo de este tutorial. También se muestra cómo usar los dos
bloques de ventana deslizante en un método que escribe en el bloque, lee en él e imprime los resultados en la
consola.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to create a custom dataflow block type.


class Program
{
// Creates a IPropagatorBlock<T, T[]> object propagates data in a
// sliding window fashion.
public static IPropagatorBlock<T, T[]> CreateSlidingWindow<T>(int windowSize)
{
// Create a queue to hold messages.
var queue = new Queue<T>();

// The source part of the propagator holds arrays of size windowSize


// and propagates data out to any connected targets.
var source = new BufferBlock<T[]>();

// The target part receives data and adds them to the queue.
var target = new ActionBlock<T>(item =>
{
// Add the item to the queue.
queue.Enqueue(item);
// Remove the oldest item when the queue size exceeds the window size.
if (queue.Count > windowSize)
queue.Dequeue();
// Post the data in the queue to the source block when the queue size
// equals the window size.
if (queue.Count == windowSize)
source.Post(queue.ToArray());
});

// When the target is set to the completed state, propagate out any
// remaining data and set the source to the completed state.
target.Completion.ContinueWith(delegate
{
if (queue.Count > 0 && queue.Count < windowSize)
source.Post(queue.ToArray());
source.Complete();
});

// Return a IPropagatorBlock<T, T[]> object that encapsulates the


// target and source blocks.
return DataflowBlock.Encapsulate(target, source);
}

// Propagates data in a sliding window fashion.


public class SlidingWindowBlock<T> : IPropagatorBlock<T, T[]>,
IReceivableSourceBlock<T[]>
{
// The size of the window.
private readonly int m_windowSize;
// The target part of the block.
private readonly ITargetBlock<T> m_target;
// The source part of the block.
private readonly IReceivableSourceBlock<T[]> m_source;

// Constructs a SlidingWindowBlock object.


public SlidingWindowBlock(int windowSize)
{
// Create a queue to hold messages.
var queue = new Queue<T>();

// The source part of the propagator holds arrays of size windowSize


// and propagates data out to any connected targets.
var source = new BufferBlock<T[]>();

// The target part receives data and adds them to the queue.
var target = new ActionBlock<T>(item =>
{
// Add the item to the queue.
queue.Enqueue(item);
// Remove the oldest item when the queue size exceeds the window size.
if (queue.Count > windowSize)
queue.Dequeue();
// Post the data in the queue to the source block when the queue size
// equals the window size.
if (queue.Count == windowSize)
source.Post(queue.ToArray());
});

// When the target is set to the completed state, propagate out any
// remaining data and set the source to the completed state.
target.Completion.ContinueWith(delegate
{
if (queue.Count > 0 && queue.Count < windowSize)
source.Post(queue.ToArray());
source.Complete();
});

m_windowSize = windowSize;
m_target = target;
m_source = source;
}

// Retrieves the size of the window.


public int WindowSize { get { return m_windowSize; } }

#region IReceivableSourceBlock<TOutput> members

// Attempts to synchronously receive an item from the source.


public bool TryReceive(Predicate<T[]> filter, out T[] item)
{
return m_source.TryReceive(filter, out item);
}

// Attempts to remove all available elements from the source into a new
// array that is returned.
public bool TryReceiveAll(out IList<T[]> items)
{
return m_source.TryReceiveAll(out items);
}

#endregion

#region ISourceBlock<TOutput> members

// Links this dataflow block to the provided target.


public IDisposable LinkTo(ITargetBlock<T[]> target, DataflowLinkOptions linkOptions)
{
return m_source.LinkTo(target, linkOptions);
}

// Called by a target to reserve a message previously offered by a source


// but not yet consumed by this target.
bool ISourceBlock<T[]>.ReserveMessage(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target)
{
return m_source.ReserveMessage(messageHeader, target);
}
// Called by a target to consume a previously offered message from a source.
T[] ISourceBlock<T[]>.ConsumeMessage(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target, out bool messageConsumed)
{
return m_source.ConsumeMessage(messageHeader,
target, out messageConsumed);
}

// Called by a target to release a previously reserved message from a source.


void ISourceBlock<T[]>.ReleaseReservation(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target)
{
m_source.ReleaseReservation(messageHeader, target);
}

#endregion

#region ITargetBlock<TInput> members

// Asynchronously passes a message to the target block, giving the target the
// opportunity to consume the message.
DataflowMessageStatus ITargetBlock<T>.OfferMessage(DataflowMessageHeader messageHeader,
T messageValue, ISourceBlock<T> source, bool consumeToAccept)
{
return m_target.OfferMessage(messageHeader,
messageValue, source, consumeToAccept);
}

#endregion

#region IDataflowBlock members

// Gets a Task that represents the completion of this dataflow block.


public Task Completion { get { return m_source.Completion; } }

// Signals to this target block that it should not accept any more messages,
// nor consume postponed messages.
public void Complete()
{
m_target.Complete();
}

public void Fault(Exception error)


{
m_target.Fault(error);
}

#endregion
}

// Demonstrates usage of the sliding window block by sending the provided


// values to the provided propagator block and printing the output of
// that block to the console.
static void DemonstrateSlidingWindow<T>(IPropagatorBlock<T, T[]> slidingWindow,
IEnumerable<T> values)
{
// Create an action block that prints arrays of data to the console.
string windowComma = string.Empty;
var printWindow = new ActionBlock<T[]>(window =>
{
Console.Write(windowComma);
Console.Write("{");

string comma = string.Empty;


foreach (T item in window)
{
Console.Write(comma);
Console.Write(item);
Console.Write(item);
comma = ",";
}
Console.Write("}");

windowComma = ", ";


});

// Link the printer block to the sliding window block.


slidingWindow.LinkTo(printWindow);

// Set the printer block to the completed state when the sliding window
// block completes.
slidingWindow.Completion.ContinueWith(delegate { printWindow.Complete(); });

// Print an additional newline to the console when the printer block completes.
var completion = printWindow.Completion.ContinueWith(delegate { Console.WriteLine(); });

// Post the provided values to the sliding window block and then wait
// for the sliding window block to complete.
foreach (T value in values)
{
slidingWindow.Post(value);
}
slidingWindow.Complete();

// Wait for the printer to complete and perform its final action.
completion.Wait();
}

static void Main(string[] args)


{

Console.Write("Using the DataflowBlockExtensions.Encapsulate method ");


Console.WriteLine("(T=int, windowSize=3):");
DemonstrateSlidingWindow(CreateSlidingWindow<int>(3), Enumerable.Range(0, 10));

Console.WriteLine();

var slidingWindow = new SlidingWindowBlock<char>(4);

Console.Write("Using SlidingWindowBlock<T> ");


Console.WriteLine("(T=char, windowSize={0}):", slidingWindow.WindowSize);
DemonstrateSlidingWindow(slidingWindow, from n in Enumerable.Range(65, 10)
select (char)n);
}
}

/* Output:
Using the DataflowBlockExtensions.Encapsulate method (T=int, windowSize=3):
{0,1,2}, {1,2,3}, {2,3,4}, {3,4,5}, {4,5,6}, {5,6,7}, {6,7,8}, {7,8,9}

Using SlidingWindowBlock<T> (T=char, windowSize=4):


{A,B,C,D}, {B,C,D,E}, {C,D,E,F}, {D,E,F,G}, {E,F,G,H}, {F,G,H,I}, {G,H,I,J}
*/

Imports System.Collections.Generic
Imports System.Linq
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to create a custom dataflow block type.


Friend Class Program
' Creates a IPropagatorBlock<T, T[]> object propagates data in a
' sliding window fashion.
Public Shared Function CreateSlidingWindow(Of T)(ByVal windowSize As Integer) As IPropagatorBlock(Of T,
T())
' Create a queue to hold messages.
Dim queue = New Queue(Of T)()

' The source part of the propagator holds arrays of size windowSize
' and propagates data out to any connected targets.
Dim source = New BufferBlock(Of T())()

' The target part receives data and adds them to the queue.
Dim target = New ActionBlock(Of T)(Sub(item)
' Add the item to the queue.
' Remove the oldest item when the queue size exceeds the window
size.
' Post the data in the queue to the source block when the queue
size
' equals the window size.
queue.Enqueue(item)
If queue.Count > windowSize Then
queue.Dequeue()
End If
If queue.Count = windowSize Then
source.Post(queue.ToArray())
End If
End Sub)

' When the target is set to the completed state, propagate out any
' remaining data and set the source to the completed state.
target.Completion.ContinueWith(Sub()
If queue.Count > 0 AndAlso queue.Count < windowSize Then
source.Post(queue.ToArray())
End If
source.Complete()
End Sub)

' Return a IPropagatorBlock<T, T[]> object that encapsulates the


' target and source blocks.
Return DataflowBlock.Encapsulate(target, source)
End Function

' Propagates data in a sliding window fashion.


Public Class SlidingWindowBlock(Of T)
Implements IPropagatorBlock(Of T, T()), IReceivableSourceBlock(Of T())
' The size of the window.
Private ReadOnly m_windowSize As Integer
' The target part of the block.
Private ReadOnly m_target As ITargetBlock(Of T)
' The source part of the block.
Private ReadOnly m_source As IReceivableSourceBlock(Of T())

' Constructs a SlidingWindowBlock object.


Public Sub New(ByVal windowSize As Integer)
' Create a queue to hold messages.
Dim queue = New Queue(Of T)()

' The source part of the propagator holds arrays of size windowSize
' and propagates data out to any connected targets.
Dim source = New BufferBlock(Of T())()

' The target part receives data and adds them to the queue.
Dim target = New ActionBlock(Of T)(Sub(item)
' Add the item to the queue.
' Remove the oldest item when the queue size exceeds the
window size.
' Post the data in the queue to the source block when the
queue size
' equals the window size.
queue.Enqueue(item)
If queue.Count > windowSize Then
queue.Dequeue()
End If
If queue.Count = windowSize Then
If queue.Count = windowSize Then
source.Post(queue.ToArray())
End If
End Sub)

' When the target is set to the completed state, propagate out any
' remaining data and set the source to the completed state.
target.Completion.ContinueWith(Sub()
If queue.Count > 0 AndAlso queue.Count < windowSize Then
source.Post(queue.ToArray())
End If
source.Complete()
End Sub)

m_windowSize = windowSize
m_target = target
m_source = source
End Sub

' Retrieves the size of the window.


Public ReadOnly Property WindowSize() As Integer
Get
Return m_windowSize
End Get
End Property

'#Region "IReceivableSourceBlock<TOutput> members"

' Attempts to synchronously receive an item from the source.


Public Function TryReceive(ByVal filter As Predicate(Of T()), <System.Runtime.InteropServices.Out()>
ByRef item() As T) As Boolean Implements IReceivableSourceBlock(Of T()).TryReceive
Return m_source.TryReceive(filter, item)
End Function

' Attempts to remove all available elements from the source into a new
' array that is returned.
Public Function TryReceiveAll(<System.Runtime.InteropServices.Out()> ByRef items As IList(Of T())) As
Boolean Implements IReceivableSourceBlock(Of T()).TryReceiveAll
Return m_source.TryReceiveAll(items)
End Function

'#End Region

#Region "ISourceBlock<TOutput> members"

' Links this dataflow block to the provided target.


Public Function LinkTo(ByVal target As ITargetBlock(Of T()), ByVal linkOptions As DataflowLinkOptions)
As IDisposable Implements ISourceBlock(Of T()).LinkTo
Return m_source.LinkTo(target, linkOptions)
End Function

' Called by a target to reserve a message previously offered by a source


' but not yet consumed by this target.
Private Function ReserveMessage(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T())) As Boolean Implements ISourceBlock(Of T()).ReserveMessage
Return m_source.ReserveMessage(messageHeader, target)
End Function

' Called by a target to consume a previously offered message from a source.


Private Function ConsumeMessage(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T()), ByRef messageConsumed As Boolean) As T() Implements ISourceBlock(Of T()).ConsumeMessage
Return m_source.ConsumeMessage(messageHeader, target, messageConsumed)
End Function

' Called by a target to release a previously reserved message from a source.


Private Sub ReleaseReservation(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T())) Implements ISourceBlock(Of T()).ReleaseReservation
m_source.ReleaseReservation(messageHeader, target)
End Sub
#End Region

#Region "ITargetBlock<TInput> members"

' Asynchronously passes a message to the target block, giving the target the
' opportunity to consume the message.
Private Function OfferMessage(ByVal messageHeader As DataflowMessageHeader, ByVal messageValue As T,
ByVal source As ISourceBlock(Of T), ByVal consumeToAccept As Boolean) As DataflowMessageStatus Implements
ITargetBlock(Of T).OfferMessage
Return m_target.OfferMessage(messageHeader, messageValue, source, consumeToAccept)
End Function

#End Region

#Region "IDataflowBlock members"

' Gets a Task that represents the completion of this dataflow block.
Public ReadOnly Property Completion() As Task Implements IDataflowBlock.Completion
Get
Return m_source.Completion
End Get
End Property

' Signals to this target block that it should not accept any more messages,
' nor consume postponed messages.
Public Sub Complete() Implements IDataflowBlock.Complete
m_target.Complete()
End Sub

Public Sub Fault(ByVal [error] As Exception) Implements IDataflowBlock.Fault


m_target.Fault([error])
End Sub

#End Region
End Class

' Demonstrates usage of the sliding window block by sending the provided
' values to the provided propagator block and printing the output of
' that block to the console.
Private Shared Sub DemonstrateSlidingWindow(Of T)(ByVal slidingWindow As IPropagatorBlock(Of T, T()),
ByVal values As IEnumerable(Of T))
' Create an action block that prints arrays of data to the console.
Dim windowComma As String = String.Empty
Dim printWindow = New ActionBlock(Of T())(Sub(window)
Console.Write(windowComma)
Console.Write("{")
Dim comma As String = String.Empty
For Each item As T In window
Console.Write(comma)
Console.Write(item)
comma = ","
Next item
Console.Write("}")
windowComma = ", "
End Sub)

' Link the printer block to the sliding window block.


slidingWindow.LinkTo(printWindow)

' Set the printer block to the completed state when the sliding window
' block completes.
slidingWindow.Completion.ContinueWith(Sub() printWindow.Complete())

' Print an additional newline to the console when the printer block completes.
Dim completion = printWindow.Completion.ContinueWith(Sub() Console.WriteLine())

' Post the provided values to the sliding window block and then wait
' for the sliding window block to complete.
For Each value As T In values
For Each value As T In values
slidingWindow.Post(value)
Next value
slidingWindow.Complete()

' Wait for the printer to complete and perform its final action.
completion.Wait()
End Sub

Shared Sub Main(ByVal args() As String)

Console.Write("Using the DataflowBlockExtensions.Encapsulate method ")


Console.WriteLine("(T=int, windowSize=3):")
DemonstrateSlidingWindow(CreateSlidingWindow(Of Integer)(3), Enumerable.Range(0, 10))

Console.WriteLine()

Dim slidingWindow = New SlidingWindowBlock(Of Char)(4)

Console.Write("Using SlidingWindowBlock<T> ")


Console.WriteLine("(T=char, windowSize={0}):", slidingWindow.WindowSize)
DemonstrateSlidingWindow(slidingWindow, _
From n In Enumerable.Range(65, 10) _
Select ChrW(n))
End Sub
End Class

' Output:
'Using the DataflowBlockExtensions.Encapsulate method (T=int, windowSize=3):
'{0,1,2}, {1,2,3}, {2,3,4}, {3,4,5}, {4,5,6}, {5,6,7}, {6,7,8}, {7,8,9}
'
'Using SlidingWindowBlock<T> (T=char, windowSize=4):
'{A,B,C,D}, {B,C,D,E}, {C,D,E,F}, {D,E,F,G}, {E,F,G,H}, {F,G,H,I}, {G,H,I,J}
'

Vea también
Flujo de datos
Cómo: Usar JoinBlock para leer datos de diferentes
orígenes
16/09/2020 • 10 minutes to read • Edit Online

En este documento se explica cómo utilizar la clase JoinBlock<T1,T2> para realizar una operación cuando los
datos están disponibles en varios orígenes. También se demuestra cómo usar el modo no expansivo para habilitar
varios bloques de combinación para compartir un origen de datos más eficazmente.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET. Para
instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar
paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow . Como
alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Ejemplo
El siguiente ejemplo define tres tipos de recursos, NetworkResource , FileResource y MemoryResource , y realiza las
operaciones cuando los recursos están disponibles. Este ejemplo requiere un par NetworkResource y
MemoryResource para realizar la primera operación y un par FileResource y MemoryResource para realizar la
segunda operación. Para permitir que estas operaciones se produzcan cuando todos los recursos necesarios están
disponibles, este ejemplo usa la clase JoinBlock<T1,T2>. Cuando un objeto JoinBlock<T1,T2> recibe datos de
todos los orígenes, los propaga en su destino, que en este ejemplo es un objeto ActionBlock<TInput>. Ambos
objetos JoinBlock<T1,T2> leen de un grupo compartido de objetos MemoryResource .

using System;
using System.Threading;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to use non-greedy join blocks to distribute


// resources among a dataflow network.
class Program
{
// Represents a resource. A derived class might represent
// a limited resource such as a memory, network, or I/O
// device.
abstract class Resource
{
}

// Represents a memory resource. For brevity, the details of


// this class are omitted.
class MemoryResource : Resource
{
}

// Represents a network resource. For brevity, the details of


// this class are omitted.
class NetworkResource : Resource
{
}

// Represents a file resource. For brevity, the details of


// this class are omitted.
// this class are omitted.
class FileResource : Resource
{
}

static void Main(string[] args)


{
// Create three BufferBlock<T> objects. Each object holds a different
// type of resource.
var networkResources = new BufferBlock<NetworkResource>();
var fileResources = new BufferBlock<FileResource>();
var memoryResources = new BufferBlock<MemoryResource>();

// Create two non-greedy JoinBlock<T1, T2> objects.


// The first join works with network and memory resources;
// the second pool works with file and memory resources.

var joinNetworkAndMemoryResources =
new JoinBlock<NetworkResource, MemoryResource>(
new GroupingDataflowBlockOptions
{
Greedy = false
});

var joinFileAndMemoryResources =
new JoinBlock<FileResource, MemoryResource>(
new GroupingDataflowBlockOptions
{
Greedy = false
});

// Create two ActionBlock<T> objects.


// The first block acts on a network resource and a memory resource.
// The second block acts on a file resource and a memory resource.

var networkMemoryAction =
new ActionBlock<Tuple<NetworkResource, MemoryResource>>(
data =>
{
// Perform some action on the resources.

// Print a message.
Console.WriteLine("Network worker: using resources...");

// Simulate a lengthy operation that uses the resources.


Thread.Sleep(new Random().Next(500, 2000));

// Print a message.
Console.WriteLine("Network worker: finished using resources...");

// Release the resources back to their respective pools.


networkResources.Post(data.Item1);
memoryResources.Post(data.Item2);
});

var fileMemoryAction =
new ActionBlock<Tuple<FileResource, MemoryResource>>(
data =>
{
// Perform some action on the resources.

// Print a message.
Console.WriteLine("File worker: using resources...");

// Simulate a lengthy operation that uses the resources.


Thread.Sleep(new Random().Next(500, 2000));

// Print a message.
Console.WriteLine("File worker: finished using resources...");
// Release the resources back to their respective pools.
fileResources.Post(data.Item1);
memoryResources.Post(data.Item2);
});

// Link the resource pools to the JoinBlock<T1, T2> objects.


// Because these join blocks operate in non-greedy mode, they do not
// take the resource from a pool until all resources are available from
// all pools.

networkResources.LinkTo(joinNetworkAndMemoryResources.Target1);
memoryResources.LinkTo(joinNetworkAndMemoryResources.Target2);

fileResources.LinkTo(joinFileAndMemoryResources.Target1);
memoryResources.LinkTo(joinFileAndMemoryResources.Target2);

// Link the JoinBlock<T1, T2> objects to the ActionBlock<T> objects.

joinNetworkAndMemoryResources.LinkTo(networkMemoryAction);
joinFileAndMemoryResources.LinkTo(fileMemoryAction);

// Populate the resource pools. In this example, network and


// file resources are more abundant than memory resources.

networkResources.Post(new NetworkResource());
networkResources.Post(new NetworkResource());
networkResources.Post(new NetworkResource());

memoryResources.Post(new MemoryResource());

fileResources.Post(new FileResource());
fileResources.Post(new FileResource());
fileResources.Post(new FileResource());

// Allow data to flow through the network for several seconds.


Thread.Sleep(10000);
}
}

/* Sample output:
File worker: using resources...
File worker: finished using resources...
Network worker: using resources...
Network worker: finished using resources...
File worker: using resources...
File worker: finished using resources...
Network worker: using resources...
Network worker: finished using resources...
File worker: using resources...
File worker: finished using resources...
File worker: using resources...
File worker: finished using resources...
Network worker: using resources...
Network worker: finished using resources...
Network worker: using resources...
Network worker: finished using resources...
File worker: using resources...
*/

Imports System.Threading
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to use non-greedy join blocks to distribute


' resources among a dataflow network.
Friend Class Program
' Represents a resource. A derived class might represent
' a limited resource such as a memory, network, or I/O
' a limited resource such as a memory, network, or I/O
' device.
Private MustInherit Class Resource
End Class

' Represents a memory resource. For brevity, the details of


' this class are omitted.
Private Class MemoryResource
Inherits Resource
End Class

' Represents a network resource. For brevity, the details of


' this class are omitted.
Private Class NetworkResource
Inherits Resource
End Class

' Represents a file resource. For brevity, the details of


' this class are omitted.
Private Class FileResource
Inherits Resource
End Class

Shared Sub Main(ByVal args() As String)


' Create three BufferBlock<T> objects. Each object holds a different
' type of resource.
Dim networkResources = New BufferBlock(Of NetworkResource)()
Dim fileResources = New BufferBlock(Of FileResource)()
Dim memoryResources = New BufferBlock(Of MemoryResource)()

' Create two non-greedy JoinBlock<T1, T2> objects.


' The first join works with network and memory resources;
' the second pool works with file and memory resources.

Dim joinNetworkAndMemoryResources = New JoinBlock(Of NetworkResource, MemoryResource)(New


GroupingDataflowBlockOptions With {.Greedy = False})

Dim joinFileAndMemoryResources = New JoinBlock(Of FileResource, MemoryResource)(New


GroupingDataflowBlockOptions With {.Greedy = False})

' Create two ActionBlock<T> objects.


' The first block acts on a network resource and a memory resource.
' The second block acts on a file resource and a memory resource.

Dim networkMemoryAction = New ActionBlock(Of Tuple(Of NetworkResource, MemoryResource))(Sub(data)


' Perform
some action on the resources.
' Print a
message.
'
Simulate a lengthy operation that uses the resources.
' Print a
message.
' Release
the resources back to their respective pools.

Console.WriteLine("Network worker: using resources...")

Thread.Sleep(New Random().Next(500, 2000))

Console.WriteLine("Network worker: finished using resources...")

networkResources.Post(data.Item1)

memoryResources.Post(data.Item2)
End Sub)

Dim fileMemoryAction = New ActionBlock(Of Tuple(Of FileResource, MemoryResource))(Sub(data)


' Perform some
action on the resources.
' Print a
message.
' Simulate a
lengthy operation that uses the resources.
' Print a
message.
' Release the
resources back to their respective pools.

Console.WriteLine("File worker: using resources...")

Thread.Sleep(New Random().Next(500, 2000))

Console.WriteLine("File worker: finished using resources...")

fileResources.Post(data.Item1)

memoryResources.Post(data.Item2)
End Sub)

' Link the resource pools to the JoinBlock<T1, T2> objects.


' Because these join blocks operate in non-greedy mode, they do not
' take the resource from a pool until all resources are available from
' all pools.

networkResources.LinkTo(joinNetworkAndMemoryResources.Target1)
memoryResources.LinkTo(joinNetworkAndMemoryResources.Target2)

fileResources.LinkTo(joinFileAndMemoryResources.Target1)
memoryResources.LinkTo(joinFileAndMemoryResources.Target2)

' Link the JoinBlock<T1, T2> objects to the ActionBlock<T> objects.

joinNetworkAndMemoryResources.LinkTo(networkMemoryAction)
joinFileAndMemoryResources.LinkTo(fileMemoryAction)

' Populate the resource pools. In this example, network and


' file resources are more abundant than memory resources.

networkResources.Post(New NetworkResource())
networkResources.Post(New NetworkResource())
networkResources.Post(New NetworkResource())

memoryResources.Post(New MemoryResource())

fileResources.Post(New FileResource())
fileResources.Post(New FileResource())
fileResources.Post(New FileResource())

' Allow data to flow through the network for several seconds.
Thread.Sleep(10000)

End Sub

End Class

' Sample output:


'File worker: using resources...
'File worker: finished using resources...
'Network worker: using resources...
'Network worker: finished using resources...
'File worker: using resources...
'File worker: finished using resources...
'Network worker: using resources...
'Network worker: finished using resources...
'File worker: using resources...
'File worker: finished using resources...
'File worker: using resources...
'File worker: using resources...
'File worker: finished using resources...
'Network worker: using resources...
'Network worker: finished using resources...
'Network worker: using resources...
'Network worker: finished using resources...
'File worker: using resources...
'

Para habilitar el uso eficaz del grupo compartido de objetos MemoryResource , este ejemplo especifica un objeto
GroupingDataflowBlockOptions que tiene la propiedad Greedy establecida en False para crear objetos
JoinBlock<T1,T2> que actúan de modo no expansivo. Un bloque de combinación no expansivo pospone todos los
mensajes entrantes hasta que uno esté disponible de cada origen. Si otro bloque aceptó cualquiera de los
mensajes pospuestos, el bloque de combinación reinicia el proceso. El modo no expansivo permite que los
bloques de combinación compartan uno o varios bloques de origen para avanzar mientras otros bloques esperan
datos. En este ejemplo, si un objeto MemoryResource se agrega al grupo memoryResources , el primer bloque de
combinación que recibe su segundo origen de datos puede progresar. Si en este ejemplo se usara el modo
expansivo, que es el predeterminado, un bloque de combinación puede adoptar el objeto MemoryResource y
esperar a que el segundo recurso esté disponible. Sin embargo, si el otro bloque de combinación tiene su
segundo origen de datos disponible, no puede avanzar porque otro bloque de combinación ha adoptado el objeto
MemoryResource .

Programación sólida
El uso de las combinaciones no expansivas también puede ayudar a evitar el interbloqueo en la aplicación. En una
aplicación de software, el interbloqueo se produce cuando dos o más procesos mantienen un recurso y esperan
mutuamente a que el otro proceso libere algún otro recurso. Considere una aplicación que define dos objetos
JoinBlock<T1,T2>. Ambos objetos leen datos de dos bloques de origen compartidos. En modo expansivo, si un
bloque de combinación lee desde el primer origen y el segundo bloque de combinación lee desde el segundo
origen, se podría producir un interbloqueo de la aplicación porque los dos bloques de combinación esperan a que
el otro libere su recurso. En el modo no expansivo, cada bloque de combinación lee de sus orígenes solo cuando
todos los datos están disponibles y, por tanto, se elimina el riesgo del interbloqueo.

Vea también
Flujo de datos
Cómo: Especificar el grado de paralelismo en un
bloque de flujos de datos
16/09/2020 • 7 minutes to read • Edit Online

En este documento se describe cómo establecer la propiedad


ExecutionDataflowBlockOptions.MaxDegreeOfParallelism para que un bloque de flujo de datos de ejecución
pueda procesar más de un mensaje al mismo tiempo. Esto resulta útil cuando tiene un bloque de flujo de datos
que realiza un cálculo de ejecución prolongada y se puede beneficiar del procesamiento de mensajes en paralelo.
En el ejemplo se usa la clase System.Threading.Tasks.Dataflow.ActionBlock<TInput> para llevar a cabo varias
operaciones de flujo de datos simultáneamente; sin embargo, puede especificar el grado máximo de paralelismo
en cualquiera de los tipos de bloque de ejecución predefinidos que la biblioteca de flujo de datos TPL proporciona,
ActionBlock<TInput>, System.Threading.Tasks.Dataflow.TransformBlock<TInput,TOutput> y
System.Threading.Tasks.Dataflow.TransformManyBlock<TInput,TOutput>.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET. Para
instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar
paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow . Como
alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Ejemplo
En el ejemplo siguiente se realizan dos cálculos de flujo de datos y se imprime el tiempo transcurrido que se
necesita para cada cálculo. El primer cálculo especifica un grado máximo de paralelismo de 1, que es el valor
predeterminado. Un grado máximo de paralelismo de 1 hace que el bloque de flujo de datos procese los mensajes
en serie. El segundo cálculo es similar al primero, excepto que especifica un grado máximo de paralelismo igual al
número de procesadores disponibles. Esto permite que el bloque de flujo de datos realice varias operaciones en
paralelo.

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to specify the maximum degree of parallelism


// when using dataflow.
class Program
{
// Performs several computations by using dataflow and returns the elapsed
// time required to perform the computations.
static TimeSpan TimeDataflowComputations(int maxDegreeOfParallelism,
int messageCount)
{
// Create an ActionBlock<int> that performs some work.
var workerBlock = new ActionBlock<int>(
// Simulate work by suspending the current thread.
millisecondsTimeout => Thread.Sleep(millisecondsTimeout),
// Specify a maximum degree of parallelism.
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = maxDegreeOfParallelism
MaxDegreeOfParallelism = maxDegreeOfParallelism
});

// Compute the time that it takes for several messages to


// flow through the dataflow block.

Stopwatch stopwatch = new Stopwatch();


stopwatch.Start();

for (int i = 0; i < messageCount; i++)


{
workerBlock.Post(1000);
}
workerBlock.Complete();

// Wait for all messages to propagate through the network.


workerBlock.Completion.Wait();

// Stop the timer and return the elapsed number of milliseconds.


stopwatch.Stop();
return stopwatch.Elapsed;
}
static void Main(string[] args)
{
int processorCount = Environment.ProcessorCount;
int messageCount = processorCount;

// Print the number of processors on this computer.


Console.WriteLine("Processor count = {0}.", processorCount);

TimeSpan elapsed;

// Perform two dataflow computations and print the elapsed


// time required for each.

// This call specifies a maximum degree of parallelism of 1.


// This causes the dataflow block to process messages serially.
elapsed = TimeDataflowComputations(1, messageCount);
Console.WriteLine("Degree of parallelism = {0}; message count = {1}; " +
"elapsed time = {2}ms.", 1, messageCount, (int)elapsed.TotalMilliseconds);

// Perform the computations again. This time, specify the number of


// processors as the maximum degree of parallelism. This causes
// multiple messages to be processed in parallel.
elapsed = TimeDataflowComputations(processorCount, messageCount);
Console.WriteLine("Degree of parallelism = {0}; message count = {1}; " +
"elapsed time = {2}ms.", processorCount, messageCount, (int)elapsed.TotalMilliseconds);
}
}

/* Sample output:
Processor count = 4.
Degree of parallelism = 1; message count = 4; elapsed time = 4032ms.
Degree of parallelism = 4; message count = 4; elapsed time = 1001ms.
*/

Imports System.Diagnostics
Imports System.Threading
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to specify the maximum degree of parallelism


' when using dataflow.
Friend Class Program
' Performs several computations by using dataflow and returns the elapsed
' time required to perform the computations.
Private Shared Function TimeDataflowComputations(ByVal maxDegreeOfParallelism As Integer, ByVal
messageCount As Integer) As TimeSpan
' Create an ActionBlock<int> that performs some work.
Dim workerBlock = New ActionBlock(Of Integer)(Function(millisecondsTimeout)
Pause(millisecondsTimeout), New ExecutionDataflowBlockOptions() With {.MaxDegreeOfParallelism =
maxDegreeOfParallelism})
' Simulate work by suspending the current thread.
' Specify a maximum degree of parallelism.

' Compute the time that it takes for several messages to


' flow through the dataflow block.

Dim stopwatch As New Stopwatch()


stopwatch.Start()

For i As Integer = 0 To messageCount - 1


workerBlock.Post(1000)
Next i
workerBlock.Complete()

' Wait for all messages to propagate through the network.


workerBlock.Completion.Wait()

' Stop the timer and return the elapsed number of milliseconds.
stopwatch.Stop()
Return stopwatch.Elapsed
End Function

Private Shared Function Pause(ByVal obj As Object)


Thread.Sleep(obj)
Return Nothing
End Function
Shared Sub Main(ByVal args() As String)
Dim processorCount As Integer = Environment.ProcessorCount
Dim messageCount As Integer = processorCount

' Print the number of processors on this computer.


Console.WriteLine("Processor count = {0}.", processorCount)

Dim elapsed As TimeSpan

' Perform two dataflow computations and print the elapsed


' time required for each.

' This call specifies a maximum degree of parallelism of 1.


' This causes the dataflow block to process messages serially.
elapsed = TimeDataflowComputations(1, messageCount)
Console.WriteLine("Degree of parallelism = {0}; message count = {1}; " & "elapsed time = {2}ms.", 1,
messageCount, CInt(Fix(elapsed.TotalMilliseconds)))

' Perform the computations again. This time, specify the number of
' processors as the maximum degree of parallelism. This causes
' multiple messages to be processed in parallel.
elapsed = TimeDataflowComputations(processorCount, messageCount)
Console.WriteLine("Degree of parallelism = {0}; message count = {1}; " & "elapsed time = {2}ms.",
processorCount, messageCount, CInt(Fix(elapsed.TotalMilliseconds)))
End Sub
End Class

' Sample output:


'Processor count = 4.
'Degree of parallelism = 1; message count = 4; elapsed time = 4032ms.
'Degree of parallelism = 4; message count = 4; elapsed time = 1001ms.
'

Programación sólida
De forma predeterminada, cada bloque de flujo de datos predefinido propaga los mensajes en el orden con que
se reciben. Aunque cuando se especifica un grado máximo de paralelismo mayor que 1 se procesan
simultáneamente varios mensajes, se siguen propagando en el orden con que se reciben.
Dado que la propiedad MaxDegreeOfParallelism representa el grado máximo de paralelismo, el bloque de flujo de
datos puede ejecutarse con un menor grado de paralelismo que el especificado. El bloque de flujo de datos puede
usar un grado de paralelismo menor para cumplir los requisitos funcionales o porque hay una falta de recursos
del sistema. Un bloque de flujo de datos nunca elige un grado de paralelismo mayor que el especificado.

Vea también
Flujo de datos
Cómo: Especificar un programador de tareas en un
bloque de flujos de datos
16/09/2020 • 14 minutes to read • Edit Online

Este documento muestra cómo asociar un programador de tareas específico al usar un flujo de datos en la
aplicación. En el ejemplo, se usa la clase System.Threading.Tasks.ConcurrentExclusiveSchedulerPair en una
aplicación de Windows Forms para mostrar cuándo están activas las tareas de lectura y cuándo está activa una
tarea de escritura. También se usa el método TaskScheduler.FromCurrentSynchronizationContext para permitir
que un bloque de flujo de datos se ejecute en el subproceso de la interfaz de usuario.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET. Para
instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar
paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow . Como
alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Para crear la aplicación de Windows Forms


1. Cree un proyecto Aplicación de Windows Forms de Visual C# o Visual Basic. En los pasos siguientes, el
proyecto se denomina WriterReadersWinForms .
2. En el diseñador de formularios del formulario principal, Form1.cs (Form1.vb para Visual Basic), agregue
cuatro controles CheckBox. Establezca la propiedad Text como Lector 1 para checkBox1 , Lector 2 para
checkBox2 , Lector 3 para checkBox3 y Escritor para checkBox4 . Establezca la propiedad Enabled de cada
control como False .
3. Agregue un control Timer al formulario. Establezca la propiedad Interval en 2500 .

Agregar funcionalidad de flujo de datos


En esta sección, se describe cómo crear los bloques de flujo de datos que participan en la aplicación y cómo
asociar cada uno a un programador de tareas.
Para agregar funcionalidad de flujo de datos a la aplicación
1. En el proyecto, agregue una referencia a System.Threading.Tasks.Dataflow.dll.
2. Asegúrese de que Form1.cs (Form1.vb para Visual Basic) contenga las siguientes instrucciones using (
Imports en Visual Basic).

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

3. Agregue un miembro de datos BroadcastBlock<T> a la clase Form1 .

// Broadcasts values to an ActionBlock<int> object that is associated


// with each check box.
BroadcastBlock<int> broadcaster = new BroadcastBlock<int>(null);

' Broadcasts values to an ActionBlock<int> object that is associated


' with each check box.
Private broadcaster As New BroadcastBlock(Of Integer)(Nothing)

4. En el constructor Form1 , después de llamar a InitializeComponent , cree un objeto ActionBlock<TInput>


que alterne el estado de los objetos CheckBox.

// Create an ActionBlock<CheckBox> object that toggles the state


// of CheckBox objects.
// Specifying the current synchronization context enables the
// action to run on the user-interface thread.
var toggleCheckBox = new ActionBlock<CheckBox>(checkBox =>
{
checkBox.Checked = !checkBox.Checked;
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

' Create an ActionBlock<CheckBox> object that toggles the state


' of CheckBox objects.
' Specifying the current synchronization context enables the
' action to run on the user-interface thread.
Dim toggleCheckBox = New ActionBlock(Of CheckBox)(Sub(checkBox) checkBox.Checked = Not
checkBox.Checked, New ExecutionDataflowBlockOptions With {.TaskScheduler =
TaskScheduler.FromCurrentSynchronizationContext()})

5. En el constructor Form1 , cree un objeto ConcurrentExclusiveSchedulerPair y cuatro objetos


ActionBlock<TInput>, un objeto ActionBlock<TInput> para cada objeto CheckBox. Para cada objeto
ActionBlock<TInput>, especifique un objeto ExecutionDataflowBlockOptions que tenga la propiedad
TaskScheduler establecida como la propiedad ConcurrentScheduler para los lectores, y como la propiedad
ExclusiveScheduler para el escritor.
// Create a ConcurrentExclusiveSchedulerPair object.
// Readers will run on the concurrent part of the scheduler pair.
// The writer will run on the exclusive part of the scheduler pair.
var taskSchedulerPair = new ConcurrentExclusiveSchedulerPair();

// Create an ActionBlock<int> object for each reader CheckBox object.


// Each ActionBlock<int> object represents an action that can read
// from a resource in parallel to other readers.
// Specifying the concurrent part of the scheduler pair enables the
// reader to run in parallel to other actions that are managed by
// that scheduler.
var readerActions =
from checkBox in new CheckBox[] {checkBox1, checkBox2, checkBox3}
select new ActionBlock<int>(milliseconds =>
{
// Toggle the check box to the checked state.
toggleCheckBox.Post(checkBox);

// Perform the read action. For demonstration, suspend the current


// thread to simulate a lengthy read operation.
Thread.Sleep(milliseconds);

// Toggle the check box to the unchecked state.


toggleCheckBox.Post(checkBox);
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = taskSchedulerPair.ConcurrentScheduler
});

// Create an ActionBlock<int> object for the writer CheckBox object.


// This ActionBlock<int> object represents an action that writes to
// a resource, but cannot run in parallel to readers.
// Specifying the exclusive part of the scheduler pair enables the
// writer to run in exclusively with respect to other actions that are
// managed by the scheduler pair.
var writerAction = new ActionBlock<int>(milliseconds =>
{
// Toggle the check box to the checked state.
toggleCheckBox.Post(checkBox4);

// Perform the write action. For demonstration, suspend the current


// thread to simulate a lengthy write operation.
Thread.Sleep(milliseconds);

// Toggle the check box to the unchecked state.


toggleCheckBox.Post(checkBox4);
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = taskSchedulerPair.ExclusiveScheduler
});

// Link the broadcaster to each reader and writer block.


// The BroadcastBlock<T> class propagates values that it
// receives to all connected targets.
foreach (var readerAction in readerActions)
{
broadcaster.LinkTo(readerAction);
}
broadcaster.LinkTo(writerAction);
' Create a ConcurrentExclusiveSchedulerPair object.
' Readers will run on the concurrent part of the scheduler pair.
' The writer will run on the exclusive part of the scheduler pair.
Dim taskSchedulerPair = New ConcurrentExclusiveSchedulerPair()

' Create an ActionBlock<int> object for each reader CheckBox object.


' Each ActionBlock<int> object represents an action that can read
' from a resource in parallel to other readers.
' Specifying the concurrent part of the scheduler pair enables the
' reader to run in parallel to other actions that are managed by
' that scheduler.
Dim readerActions = From checkBox In New CheckBox() {checkBox1, checkBox2, checkBox3} _
Select New ActionBlock(Of Integer)(Sub(milliseconds)
' Toggle the check box to the checked state.
' Perform the read action. For demonstration, suspend the
current
' thread to simulate a lengthy read operation.
' Toggle the check box to the unchecked state.
toggleCheckBox.Post(checkBox)
Thread.Sleep(milliseconds)
toggleCheckBox.Post(checkBox)
End Sub, New ExecutionDataflowBlockOptions With
{.TaskScheduler = taskSchedulerPair.ConcurrentScheduler})

' Create an ActionBlock<int> object for the writer CheckBox object.


' This ActionBlock<int> object represents an action that writes to
' a resource, but cannot run in parallel to readers.
' Specifying the exclusive part of the scheduler pair enables the
' writer to run in exclusively with respect to other actions that are
' managed by the scheduler pair.
Dim writerAction = New ActionBlock(Of Integer)(Sub(milliseconds)
' Toggle the check box to the checked state.
' Perform the write action. For demonstration,
suspend the current
' thread to simulate a lengthy write operation.
' Toggle the check box to the unchecked state.
toggleCheckBox.Post(checkBox4)
Thread.Sleep(milliseconds)
toggleCheckBox.Post(checkBox4)
End Sub, New ExecutionDataflowBlockOptions With
{.TaskScheduler = taskSchedulerPair.ExclusiveScheduler})

' Link the broadcaster to each reader and writer block.


' The BroadcastBlock<T> class propagates values that it
' receives to all connected targets.
For Each readerAction In readerActions
broadcaster.LinkTo(readerAction)
Next readerAction
broadcaster.LinkTo(writerAction)

6. En el constructor Form1 , inicie el objeto Timer.

// Start the timer.


timer1.Start();

' Start the timer.


timer1.Start()

7. En el diseñador de formularios del formulario principal, cree un controlador de eventos para el evento Tick
del temporizador.
8. Implemente el evento Tick del temporizador.
// Event handler for the timer.
private void timer1_Tick(object sender, EventArgs e)
{
// Post a value to the broadcaster. The broadcaster
// sends this message to each target.
broadcaster.Post(1000);
}

' Event handler for the timer.


Private Sub timer1_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles timer1.Tick
' Post a value to the broadcaster. The broadcaster
' sends this message to each target.
broadcaster.Post(1000)
End Sub

Dado que el bloque de flujo de datos toggleCheckBox actúa en la interfaz de usuario, es importante que esta
acción se produzca en el subproceso de la interfaz de usuario. Para lograrlo, durante la construcción, este objeto
proporciona un objeto ExecutionDataflowBlockOptions que tiene la propiedad TaskScheduler establecida como
TaskScheduler.FromCurrentSynchronizationContext. El método FromCurrentSynchronizationContext crea un
objeto TaskScheduler que funciona en el contexto de sincronización actual. Dado que al constructor Form1 se le
llama desde el subproceso de la interfaz de usuario, la acción del bloque de flujo de datos toggleCheckBox se
ejecuta también en el subproceso de la interfaz de usuario.
En este ejemplo, también se usa la clase ConcurrentExclusiveSchedulerPair para permitir que algunos bloques de
flujo de datos actúen de forma simultánea y que otro bloque de flujo de datos actúe de forma exclusiva con
respecto a todos los demás bloques de flujo de datos que se ejecutan en el mismo objeto
ConcurrentExclusiveSchedulerPair. Esta técnica es útil cuando varios bloques de flujo de datos comparten un
recurso y algunos requieren acceso exclusivo a ese recurso, ya que evita la necesidad de sincronizar manualmente
el acceso a ese recurso. Al eliminar la sincronización manual, se puede hacer que el código sea más eficiente.

Ejemplo
En el siguiente ejemplo se muestra el código completo de Form1.cs (Form1.vb para Visual Basic).

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;

namespace WriterReadersWinForms
{
public partial class Form1 : Form
{
// Broadcasts values to an ActionBlock<int> object that is associated
// with each check box.
BroadcastBlock<int> broadcaster = new BroadcastBlock<int>(null);

public Form1()
{
InitializeComponent();

// Create an ActionBlock<CheckBox> object that toggles the state


// of CheckBox objects.
// Specifying the current synchronization context enables the
// action to run on the user-interface thread.
var toggleCheckBox = new ActionBlock<CheckBox>(checkBox =>
{
checkBox.Checked = !checkBox.Checked;
checkBox.Checked = !checkBox.Checked;
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

// Create a ConcurrentExclusiveSchedulerPair object.


// Readers will run on the concurrent part of the scheduler pair.
// The writer will run on the exclusive part of the scheduler pair.
var taskSchedulerPair = new ConcurrentExclusiveSchedulerPair();

// Create an ActionBlock<int> object for each reader CheckBox object.


// Each ActionBlock<int> object represents an action that can read
// from a resource in parallel to other readers.
// Specifying the concurrent part of the scheduler pair enables the
// reader to run in parallel to other actions that are managed by
// that scheduler.
var readerActions =
from checkBox in new CheckBox[] {checkBox1, checkBox2, checkBox3}
select new ActionBlock<int>(milliseconds =>
{
// Toggle the check box to the checked state.
toggleCheckBox.Post(checkBox);

// Perform the read action. For demonstration, suspend the current


// thread to simulate a lengthy read operation.
Thread.Sleep(milliseconds);

// Toggle the check box to the unchecked state.


toggleCheckBox.Post(checkBox);
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = taskSchedulerPair.ConcurrentScheduler
});

// Create an ActionBlock<int> object for the writer CheckBox object.


// This ActionBlock<int> object represents an action that writes to
// a resource, but cannot run in parallel to readers.
// Specifying the exclusive part of the scheduler pair enables the
// writer to run in exclusively with respect to other actions that are
// managed by the scheduler pair.
var writerAction = new ActionBlock<int>(milliseconds =>
{
// Toggle the check box to the checked state.
toggleCheckBox.Post(checkBox4);

// Perform the write action. For demonstration, suspend the current


// thread to simulate a lengthy write operation.
Thread.Sleep(milliseconds);

// Toggle the check box to the unchecked state.


toggleCheckBox.Post(checkBox4);
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = taskSchedulerPair.ExclusiveScheduler
});

// Link the broadcaster to each reader and writer block.


// The BroadcastBlock<T> class propagates values that it
// receives to all connected targets.
foreach (var readerAction in readerActions)
{
broadcaster.LinkTo(readerAction);
}
broadcaster.LinkTo(writerAction);

// Start the timer.


// Start the timer.
timer1.Start();
}

// Event handler for the timer.


private void timer1_Tick(object sender, EventArgs e)
{
// Post a value to the broadcaster. The broadcaster
// sends this message to each target.
broadcaster.Post(1000);
}
}
}

Imports System.Threading
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

Namespace WriterReadersWinForms
Partial Public Class Form1
Inherits Form
' Broadcasts values to an ActionBlock<int> object that is associated
' with each check box.
Private broadcaster As New BroadcastBlock(Of Integer)(Nothing)

Public Sub New()


InitializeComponent()

' Create an ActionBlock<CheckBox> object that toggles the state


' of CheckBox objects.
' Specifying the current synchronization context enables the
' action to run on the user-interface thread.
Dim toggleCheckBox = New ActionBlock(Of CheckBox)(Sub(checkBox) checkBox.Checked = Not
checkBox.Checked, New ExecutionDataflowBlockOptions With {.TaskScheduler =
TaskScheduler.FromCurrentSynchronizationContext()})

' Create a ConcurrentExclusiveSchedulerPair object.


' Readers will run on the concurrent part of the scheduler pair.
' The writer will run on the exclusive part of the scheduler pair.
Dim taskSchedulerPair = New ConcurrentExclusiveSchedulerPair()

' Create an ActionBlock<int> object for each reader CheckBox object.


' Each ActionBlock<int> object represents an action that can read
' from a resource in parallel to other readers.
' Specifying the concurrent part of the scheduler pair enables the
' reader to run in parallel to other actions that are managed by
' that scheduler.
Dim readerActions = From checkBox In New CheckBox() {checkBox1, checkBox2, checkBox3} _
Select New ActionBlock(Of Integer)(Sub(milliseconds)
' Toggle the check box to the checked state.
' Perform the read action. For demonstration, suspend
the current
' thread to simulate a lengthy read operation.
' Toggle the check box to the unchecked state.
toggleCheckBox.Post(checkBox)
Thread.Sleep(milliseconds)
toggleCheckBox.Post(checkBox)
End Sub, New ExecutionDataflowBlockOptions
With {.TaskScheduler = taskSchedulerPair.ConcurrentScheduler})

' Create an ActionBlock<int> object for the writer CheckBox object.


' This ActionBlock<int> object represents an action that writes to
' a resource, but cannot run in parallel to readers.
' Specifying the exclusive part of the scheduler pair enables the
' writer to run in exclusively with respect to other actions that are
' managed by the scheduler pair.
Dim writerAction = New ActionBlock(Of Integer)(Sub(milliseconds)
Dim writerAction = New ActionBlock(Of Integer)(Sub(milliseconds)
' Toggle the check box to the checked state.
' Perform the write action. For demonstration,
suspend the current
' thread to simulate a lengthy write
operation.
' Toggle the check box to the unchecked state.
toggleCheckBox.Post(checkBox4)
Thread.Sleep(milliseconds)
toggleCheckBox.Post(checkBox4)
End Sub, New ExecutionDataflowBlockOptions With
{.TaskScheduler = taskSchedulerPair.ExclusiveScheduler})

' Link the broadcaster to each reader and writer block.


' The BroadcastBlock<T> class propagates values that it
' receives to all connected targets.
For Each readerAction In readerActions
broadcaster.LinkTo(readerAction)
Next readerAction
broadcaster.LinkTo(writerAction)

' Start the timer.


timer1.Start()
End Sub

' Event handler for the timer.


Private Sub timer1_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles timer1.Tick
' Post a value to the broadcaster. The broadcaster
' sends this message to each target.
broadcaster.Post(1000)
End Sub
End Class
End Namespace

Vea también
Flujo de datos
Tutorial: Usar BatchBlock y BatchedJoinBlock para
mejorar la eficacia
16/09/2020 • 34 minutes to read • Edit Online

La biblioteca de flujos de datos TPL proporciona las clases System.Threading.Tasks.Dataflow.BatchBlock<T> y


System.Threading.Tasks.Dataflow.BatchedJoinBlock<T1,T2> para poder recibir y almacenar en búfer datos de uno
o más orígenes y después propagar esos datos almacenados en búfer como una colección. Este mecanismo por
lotes es útil cuando se recopilan datos de uno o más orígenes y después se procesan varios elementos de datos
como un lote. Por ejemplo, piense en una aplicación que usa flujo de datos para insertar registros en una base de
datos. Esta operación puede ser más eficaz si varios elementos se insertan al mismo tiempo en lugar de insertar
de uno en uno de forma secuencial. En este documento se describe cómo utilizar la clase BatchBlock<T> para
mejorar la eficacia de las operaciones de inserción de la base de datos. También se describe cómo utilizar la clase
BatchedJoinBlock<T1,T2> para capturar los resultados y cualquier excepción que se produce cuando el programa
lee de una base de datos.

NOTE
La biblioteca de flujos de datos TPL (el espacio de nombres System.Threading.Tasks.Dataflow) no se distribuye con .NET. Para
instalar el espacio de nombres System.Threading.Tasks.Dataflow en Visual Studio, abra el proyecto, seleccione Administrar
paquetes NuGet en el menú Proyecto y busque en línea el paquete System.Threading.Tasks.Dataflow . Como
alternativa, para realizar la instalación con la CLI de .Net Core, ejecute
dotnet add package System.Threading.Tasks.Dataflow .

Requisitos previos
1. Lea la sección sobre bloques de combinación en el documento Flujo de datos antes de iniciar este tutorial.
2. Asegúrese de que tiene una copia de la base de datos Northwind, Northwind.sdf, disponible en el equipo.
Este archivo se encuentra normalmente en la carpeta %Archivos de programa%\Microsoft SQL Server
Compact Edition\v3.5\Ejemplos\.

IMPORTANT
En algunas versiones de Windows, no se puede conectar a Northwind.sdf si Visual Studio se ejecuta en modo que no
sea de administrador. Para conectarse a Northwind.sdf, inicie Visual Studio o Símbolo del sistema para
desarrolladores de Visual Studio en modo Ejecutar como administrador .

Este tutorial contiene las siguientes secciones:


Crear la aplicación de consola
Definir la clase Employee
Definir las operaciones de la base de datos de empleados
Agregar datos de empleados a la base de datos sin usar el almacenamiento en búfer
Usar el almacenamiento en búfer para agregar datos de empleados en la base de datos
Usar una combinación almacenada en búfer para leer datos de empleados de la base de datos
Ejemplo completo

Crear la aplicación de consola


1. En Visual Studio, cree un proyecto Aplicación de consola de Visual C# o Visual Basic. En este documento,
el proyecto se denomina DataflowBatchDatabase .
2. En el proyecto, agregue una referencia a System.Data.SqlServerCe.dll y una referencia a
System.Threading.Tasks.Dataflow.dll.
3. Asegúrese de que Form1.cs (Form1.vb para Visual Basic) contenga las siguientes instrucciones using (
Imports en Visual Basic).

using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks.Dataflow;

Imports System.Collections.Generic
Imports System.Data.SqlServerCe
Imports System.Diagnostics
Imports System.IO
Imports System.Threading.Tasks.Dataflow

4. Agregue a la clase Program los miembros de datos siguientes:

// The number of employees to add to the database.


// TODO: Change this value to experiment with different numbers of
// employees to insert into the database.
static readonly int insertCount = 256;

// The size of a single batch of employees to add to the database.


// TODO: Change this value to experiment with different batch sizes.
static readonly int insertBatchSize = 96;

// The source database file.


// TODO: Change this value if Northwind.sdf is at a different location
// on your computer.
static readonly string sourceDatabase =
@"C:\Program Files\Microsoft SQL Server Compact Edition\v3.5\Samples\Northwind.sdf";

// TODO: Change this value if you require a different temporary location.


static readonly string scratchDatabase =
@"C:\Temp\Northwind.sdf";
' The number of employees to add to the database.
' TODO: Change this value to experiment with different numbers of
' employees to insert into the database.
Private Shared ReadOnly insertCount As Integer = 256

' The size of a single batch of employees to add to the database.


' TODO: Change this value to experiment with different batch sizes.
Private Shared ReadOnly insertBatchSize As Integer = 96

' The source database file.


' TODO: Change this value if Northwind.sdf is at a different location
' on your computer.
Private Shared ReadOnly sourceDatabase As String = "C:\Program Files\Microsoft SQL Server Compact
Edition\v3.5\Samples\Northwind.sdf"

' TODO: Change this value if you require a different temporary location.
Private Shared ReadOnly scratchDatabase As String = "C:\Temp\Northwind.sdf"

Definir la clase Employee


Agregue la clase Program a la clase Employee .

// Describes an employee. Each property maps to a


// column in the Employees table in the Northwind database.
// For brevity, the Employee class does not contain
// all columns from the Employees table.
class Employee
{
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }

// A random number generator that helps tp generate


// Employee property values.
static Random rand = new Random(42);

// Possible random first names.


static readonly string[] firstNames = { "Tom", "Mike", "Ruth", "Bob", "John" };
// Possible random last names.
static readonly string[] lastNames = { "Jones", "Smith", "Johnson", "Walker" };

// Creates an Employee object that contains random


// property values.
public static Employee Random()
{
return new Employee
{
EmployeeID = -1,
LastName = lastNames[rand.Next() % lastNames.Length],
FirstName = firstNames[rand.Next() % firstNames.Length]
};
}
}
' Describes an employee. Each property maps to a
' column in the Employees table in the Northwind database.
' For brevity, the Employee class does not contain
' all columns from the Employees table.
Private Class Employee
Public Property EmployeeID() As Integer
Public Property LastName() As String
Public Property FirstName() As String

' A random number generator that helps tp generate


' Employee property values.
Private Shared rand As New Random(42)

' Possible random first names.


Private Shared ReadOnly firstNames() As String = {"Tom", "Mike", "Ruth", "Bob", "John"}
' Possible random last names.
Private Shared ReadOnly lastNames() As String = {"Jones", "Smith", "Johnson", "Walker"}

' Creates an Employee object that contains random


' property values.
Public Shared Function Random() As Employee
Return New Employee With {.EmployeeID = -1, .LastName = lastNames(rand.Next() Mod lastNames.Length),
.FirstName = firstNames(rand.Next() Mod firstNames.Length)}
End Function
End Class

La clase Employee contiene tres propiedades EmployeeID , LastName y FirstName . Estas propiedades
corresponden a las columnas Employee ID , Last Name y First Name en la tabla Employees de la base de datos
Northwind. Para esta demostración, la clase Employee también define el método Random , que crea un objeto
Employee con valores aleatorios para sus propiedades.

Definir las operaciones de la base de datos de empleados


Agregue a la clase Program los métodos InsertEmployees , GetEmployeeCount y GetEmployeeID .

// Adds new employee records to the database.


static void InsertEmployees(Employee[] employees, string connectionString)
{
using (SqlCeConnection connection =
new SqlCeConnection(connectionString))
{
try
{
// Create the SQL command.
SqlCeCommand command = new SqlCeCommand(
"INSERT INTO Employees ([Last Name], [First Name])" +
"VALUES (@lastName, @firstName)",
connection);

connection.Open();
for (int i = 0; i < employees.Length; i++)
{
// Set parameters.
command.Parameters.Clear();
command.Parameters.Add("@lastName", employees[i].LastName);
command.Parameters.Add("@firstName", employees[i].FirstName);

// Execute the command.


command.ExecuteNonQuery();
}
}
finally
{
connection.Close();
connection.Close();
}
}
}

// Retrieves the number of entries in the Employees table in


// the Northwind database.
static int GetEmployeeCount(string connectionString)
{
int result = 0;
using (SqlCeConnection sqlConnection =
new SqlCeConnection(connectionString))
{
SqlCeCommand sqlCommand = new SqlCeCommand(
"SELECT COUNT(*) FROM Employees", sqlConnection);

sqlConnection.Open();
try
{
result = (int)sqlCommand.ExecuteScalar();
}
finally
{
sqlConnection.Close();
}
}
return result;
}

// Retrieves the ID of the first employee that has the provided name.
static int GetEmployeeID(string lastName, string firstName,
string connectionString)
{
using (SqlCeConnection connection =
new SqlCeConnection(connectionString))
{
SqlCeCommand command = new SqlCeCommand(
string.Format(
"SELECT [Employee ID] FROM Employees " +
"WHERE [Last Name] = '{0}' AND [First Name] = '{1}'",
lastName, firstName),
connection);

connection.Open();
try
{
return (int)command.ExecuteScalar();
}
finally
{
connection.Close();
}
}
}
' Adds new employee records to the database.
Private Shared Sub InsertEmployees(ByVal employees() As Employee, ByVal connectionString As String)
Using connection As New SqlCeConnection(connectionString)
Try
' Create the SQL command.
Dim command As New SqlCeCommand("INSERT INTO Employees ([Last Name], [First Name])" & "VALUES
(@lastName, @firstName)", connection)

connection.Open()
For i As Integer = 0 To employees.Length - 1
' Set parameters.
command.Parameters.Clear()
command.Parameters.Add("@lastName", employees(i).LastName)
command.Parameters.Add("@firstName", employees(i).FirstName)

' Execute the command.


command.ExecuteNonQuery()
Next i
Finally
connection.Close()
End Try
End Using
End Sub

' Retrieves the number of entries in the Employees table in


' the Northwind database.
Private Shared Function GetEmployeeCount(ByVal connectionString As String) As Integer
Dim result As Integer = 0
Using sqlConnection As New SqlCeConnection(connectionString)
Dim sqlCommand As New SqlCeCommand("SELECT COUNT(*) FROM Employees", sqlConnection)

sqlConnection.Open()
Try
result = CInt(Fix(sqlCommand.ExecuteScalar()))
Finally
sqlConnection.Close()
End Try
End Using
Return result
End Function

' Retrieves the ID of the first employee that has the provided name.
Private Shared Function GetEmployeeID(ByVal lastName As String, ByVal firstName As String, ByVal
connectionString As String) As Integer
Using connection As New SqlCeConnection(connectionString)
Dim command As New SqlCeCommand(String.Format("SELECT [Employee ID] FROM Employees " & "WHERE [Last
Name] = '{0}' AND [First Name] = '{1}'", lastName, firstName), connection)

connection.Open()
Try
Return CInt(Fix(command.ExecuteScalar()))
Finally
connection.Close()
End Try
End Using
End Function

El método InsertEmployees agrega nuevos registros de empleados en la base de datos. El método


GetEmployeeCount recupera el número de entradas de la tabla Employees . El método GetEmployeeID recupera el
identificador del primer empleado con el nombre proporcionado. Cada uno de estos métodos lleva una cadena de
conexión la base de datos Northwind y utiliza la funcionalidad del espacio de nombres System.Data.SqlServerCe
para comunicarse con la base de datos.

Agregar datos de empleados a la base de datos sin usar el


almacenamiento en búfer
Agregue a la clase Program los métodos AddEmployees y PostRandomEmployees .

// Posts random Employee data to the provided target block.


static void PostRandomEmployees(ITargetBlock<Employee> target, int count)
{
Console.WriteLine("Adding {0} entries to Employee table...", count);

for (int i = 0; i < count; i++)


{
target.Post(Employee.Random());
}
}

// Adds random employee data to the database by using dataflow.


static void AddEmployees(string connectionString, int count)
{
// Create an ActionBlock<Employee> object that adds a single
// employee entry to the database.
var insertEmployee = new ActionBlock<Employee>(e =>
InsertEmployees(new Employee[] { e }, connectionString));

// Post several random Employee objects to the dataflow block.


PostRandomEmployees(insertEmployee, count);

// Set the dataflow block to the completed state and wait for
// all insert operations to complete.
insertEmployee.Complete();
insertEmployee.Completion.Wait();
}

' Posts random Employee data to the provided target block.


Private Shared Sub PostRandomEmployees(ByVal target As ITargetBlock(Of Employee), ByVal count As Integer)
Console.WriteLine("Adding {0} entries to Employee table...", count)

For i As Integer = 0 To count - 1


target.Post(Employee.Random())
Next i
End Sub

' Adds random employee data to the database by using dataflow.


Private Shared Sub AddEmployees(ByVal connectionString As String, ByVal count As Integer)
' Create an ActionBlock<Employee> object that adds a single
' employee entry to the database.
Dim insertEmployee = New ActionBlock(Of Employee)(Sub(e) InsertEmployees(New Employee() {e},
connectionString))

' Post several random Employee objects to the dataflow block.


PostRandomEmployees(insertEmployee, count)

' Set the dataflow block to the completed state and wait for
' all insert operations to complete.
insertEmployee.Complete()
insertEmployee.Completion.Wait()
End Sub

El método AddEmployees agrega datos del empleado en la base de datos mediante el flujo de datos. También crea
un objeto ActionBlock<TInput> que llama al método InsertEmployees para agregar una entrada de empleado en
la base de datos. A continuación, el método AddEmployees llama al método PostRandomEmployees para enviar
varios objetos Employee al objeto ActionBlock<TInput>. El método AddEmployees espera que todas las
operaciones de inserción finalicen.
Usar el almacenamiento en búfer para agregar datos de empleados en
la base de datos
Agregue a la clase Program el método AddEmployeesBatched .

// Adds random employee data to the database by using dataflow.


// This method is similar to AddEmployees except that it uses batching
// to add multiple employees to the database at a time.
static void AddEmployeesBatched(string connectionString, int batchSize,
int count)
{
// Create a BatchBlock<Employee> that holds several Employee objects and
// then propagates them out as an array.
var batchEmployees = new BatchBlock<Employee>(batchSize);

// Create an ActionBlock<Employee[]> object that adds multiple


// employee entries to the database.
var insertEmployees = new ActionBlock<Employee[]>(a =>
InsertEmployees(a, connectionString));

// Link the batch block to the action block.


batchEmployees.LinkTo(insertEmployees);

// When the batch block completes, set the action block also to complete.
batchEmployees.Completion.ContinueWith(delegate { insertEmployees.Complete(); });

// Post several random Employee objects to the batch block.


PostRandomEmployees(batchEmployees, count);

// Set the batch block to the completed state and wait for
// all insert operations to complete.
batchEmployees.Complete();
insertEmployees.Completion.Wait();
}

' Adds random employee data to the database by using dataflow.


' This method is similar to AddEmployees except that it uses batching
' to add multiple employees to the database at a time.
Private Shared Sub AddEmployeesBatched(ByVal connectionString As String, ByVal batchSize As Integer, ByVal
count As Integer)
' Create a BatchBlock<Employee> that holds several Employee objects and
' then propagates them out as an array.
Dim batchEmployees = New BatchBlock(Of Employee)(batchSize)

' Create an ActionBlock<Employee[]> object that adds multiple


' employee entries to the database.
Dim insertEmployees = New ActionBlock(Of Employee())(Sub(a) Program.InsertEmployees(a, connectionString))

' Link the batch block to the action block.


batchEmployees.LinkTo(insertEmployees)

' When the batch block completes, set the action block also to complete.
batchEmployees.Completion.ContinueWith(Sub() insertEmployees.Complete())

' Post several random Employee objects to the batch block.


PostRandomEmployees(batchEmployees, count)

' Set the batch block to the completed state and wait for
' all insert operations to complete.
batchEmployees.Complete()
insertEmployees.Completion.Wait()
End Sub
Este método es similar a AddEmployees , salvo que también utiliza la clase BatchBlock<T> para almacenar en búfer
varios objetos Employee antes de enviar esos objetos al objeto ActionBlock<TInput>. Dado que la clase
BatchBlock<T> propaga varios elementos como una colección, el objeto ActionBlock<TInput> se modifica para
actuar sobre una matriz de objetos Employee . Como en el método AddEmployees , AddEmployeesBatched llama al
método PostRandomEmployees para enviar varios objetos Employee ; sin embargo, AddEmployeesBatched envía estos
objetos al objeto BatchBlock<T>. El método AddEmployeesBatched también espera a que todas las operaciones de
inserción finalicen.

Usar una combinación almacenada en búfer para leer datos de


empleados de la base de datos
Agregue a la clase Program el método GetRandomEmployees .

// Displays information about several random employees to the console.


static void GetRandomEmployees(string connectionString, int batchSize,
int count)
{
// Create a BatchedJoinBlock<Employee, Exception> object that holds
// both employee and exception data.
var selectEmployees = new BatchedJoinBlock<Employee, Exception>(batchSize);

// Holds the total number of exceptions that occurred.


int totalErrors = 0;

// Create an action block that prints employee and error information


// to the console.
var printEmployees =
new ActionBlock<Tuple<IList<Employee>, IList<Exception>>>(data =>
{
// Print information about the employees in this batch.
Console.WriteLine("Received a batch...");
foreach (Employee e in data.Item1)
{
Console.WriteLine("Last={0} First={1} ID={2}",
e.LastName, e.FirstName, e.EmployeeID);
}

// Print the error count for this batch.


Console.WriteLine("There were {0} errors in this batch...",
data.Item2.Count);

// Update total error count.


totalErrors += data.Item2.Count;
});

// Link the batched join block to the action block.


selectEmployees.LinkTo(printEmployees);

// When the batched join block completes, set the action block also to complete.
selectEmployees.Completion.ContinueWith(delegate { printEmployees.Complete(); });

// Try to retrieve the ID for several random employees.


Console.WriteLine("Selecting random entries from Employees table...");
for (int i = 0; i < count; i++)
{
try
{
// Create a random employee.
Employee e = Employee.Random();

// Try to retrieve the ID for the employee from the database.


e.EmployeeID = GetEmployeeID(e.LastName, e.FirstName, connectionString);

// Post the Employee object to the Employee target of


// Post the Employee object to the Employee target of
// the batched join block.
selectEmployees.Target1.Post(e);
}
catch (NullReferenceException e)
{
// GetEmployeeID throws NullReferenceException when there is
// no such employee with the given name. When this happens,
// post the Exception object to the Exception target of
// the batched join block.
selectEmployees.Target2.Post(e);
}
}

// Set the batched join block to the completed state and wait for
// all retrieval operations to complete.
selectEmployees.Complete();
printEmployees.Completion.Wait();

// Print the total error count.


Console.WriteLine("Finished. There were {0} total errors.", totalErrors);
}

' Displays information about several random employees to the console.


Private Shared Sub GetRandomEmployees(ByVal connectionString As String, ByVal batchSize As Integer, ByVal
count As Integer)
' Create a BatchedJoinBlock<Employee, Exception> object that holds
' both employee and exception data.
Dim selectEmployees = New BatchedJoinBlock(Of Employee, Exception)(batchSize)

' Holds the total number of exceptions that occurred.


Dim totalErrors As Integer = 0

' Create an action block that prints employee and error information
' to the console.
Dim printEmployees = New ActionBlock(Of Tuple(Of IList(Of Employee), IList(Of Exception)))(Sub(data)
' Print
information about the employees in this batch.
' Print
the error count for this batch.
' Update
total error count.

Console.WriteLine("Received a batch...")
For Each e
As Employee In data.Item1

Console.WriteLine("Last={0} First={1} ID={2}", e.LastName, e.FirstName, e.EmployeeID)


Next e

Console.WriteLine("There were {0} errors in this batch...", data.Item2.Count)

totalErrors += data.Item2.Count
End Sub)

' Link the batched join block to the action block.


selectEmployees.LinkTo(printEmployees)

' When the batched join block completes, set the action block also to complete.
selectEmployees.Completion.ContinueWith(Sub() printEmployees.Complete())

' Try to retrieve the ID for several random employees.


Console.WriteLine("Selecting random entries from Employees table...")
For i As Integer = 0 To count - 1
Try
' Create a random employee.
Dim e As Employee = Employee.Random()
' Try to retrieve the ID for the employee from the database.
e.EmployeeID = GetEmployeeID(e.LastName, e.FirstName, connectionString)

' Post the Employee object to the Employee target of


' the batched join block.
selectEmployees.Target1.Post(e)
Catch e As NullReferenceException
' GetEmployeeID throws NullReferenceException when there is
' no such employee with the given name. When this happens,
' post the Exception object to the Exception target of
' the batched join block.
selectEmployees.Target2.Post(e)
End Try
Next i

' Set the batched join block to the completed state and wait for
' all retrieval operations to complete.
selectEmployees.Complete()
printEmployees.Completion.Wait()

' Print the total error count.


Console.WriteLine("Finished. There were {0} total errors.", totalErrors)
End Sub

Este método imprime información sobre empleados aleatorios en la consola. Crea varios objetos Employee
aleatorios y llama al método GetEmployeeID para recuperar el identificador único para cada objeto. Debido a que
el método GetEmployeeID produce una excepción si no hay ningún empleado coincidente con los nombres y
apellidos dados, el método GetRandomEmployees utiliza la clase BatchedJoinBlock<T1,T2> para almacenar objetos
Employee para llamadas correctas a GetEmployeeID y objetos System.Exception para llamadas que dan error. El
objeto ActionBlock<TInput> en este ejemplo actúa sobre un objeto Tuple<T1,T2> que contiene una lista de
objetos Employee y una lista de objetos Exception. El objeto BatchedJoinBlock<T1,T2> propaga estos datos
cuando la suma del objeto Employee y Exception recibido es igual que el tamaño del lote.

Ejemplo completo
El ejemplo siguiente muestra el código completo: El método Main compara el tiempo necesario para realizar
inserciones por lotes de la base de datos frente al tiempo necesario para realizar inserciones sin lotes de la base
de datos. También muestra el uso de una combinación almacenada en búfer para leer datos de empleados de la
base de datos, así como notificar errores.

using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to use batched dataflow blocks to improve


// the performance of database operations.
namespace DataflowBatchDatabase
{
class Program
{
// The number of employees to add to the database.
// TODO: Change this value to experiment with different numbers of
// employees to insert into the database.
static readonly int insertCount = 256;

// The size of a single batch of employees to add to the database.


// TODO: Change this value to experiment with different batch sizes.
static readonly int insertBatchSize = 96;

// The source database file.


// The source database file.
// TODO: Change this value if Northwind.sdf is at a different location
// on your computer.
static readonly string sourceDatabase =
@"C:\Program Files\Microsoft SQL Server Compact Edition\v3.5\Samples\Northwind.sdf";

// TODO: Change this value if you require a different temporary location.


static readonly string scratchDatabase =
@"C:\Temp\Northwind.sdf";

// Describes an employee. Each property maps to a


// column in the Employees table in the Northwind database.
// For brevity, the Employee class does not contain
// all columns from the Employees table.
class Employee
{
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }

// A random number generator that helps tp generate


// Employee property values.
static Random rand = new Random(42);

// Possible random first names.


static readonly string[] firstNames = { "Tom", "Mike", "Ruth", "Bob", "John" };
// Possible random last names.
static readonly string[] lastNames = { "Jones", "Smith", "Johnson", "Walker" };

// Creates an Employee object that contains random


// property values.
public static Employee Random()
{
return new Employee
{
EmployeeID = -1,
LastName = lastNames[rand.Next() % lastNames.Length],
FirstName = firstNames[rand.Next() % firstNames.Length]
};
}
}

// Adds new employee records to the database.


static void InsertEmployees(Employee[] employees, string connectionString)
{
using (SqlCeConnection connection =
new SqlCeConnection(connectionString))
{
try
{
// Create the SQL command.
SqlCeCommand command = new SqlCeCommand(
"INSERT INTO Employees ([Last Name], [First Name])" +
"VALUES (@lastName, @firstName)",
connection);

connection.Open();
for (int i = 0; i < employees.Length; i++)
{
// Set parameters.
command.Parameters.Clear();
command.Parameters.Add("@lastName", employees[i].LastName);
command.Parameters.Add("@firstName", employees[i].FirstName);

// Execute the command.


command.ExecuteNonQuery();
}
}
finally
{
{
connection.Close();
}
}
}

// Retrieves the number of entries in the Employees table in


// the Northwind database.
static int GetEmployeeCount(string connectionString)
{
int result = 0;
using (SqlCeConnection sqlConnection =
new SqlCeConnection(connectionString))
{
SqlCeCommand sqlCommand = new SqlCeCommand(
"SELECT COUNT(*) FROM Employees", sqlConnection);

sqlConnection.Open();
try
{
result = (int)sqlCommand.ExecuteScalar();
}
finally
{
sqlConnection.Close();
}
}
return result;
}

// Retrieves the ID of the first employee that has the provided name.
static int GetEmployeeID(string lastName, string firstName,
string connectionString)
{
using (SqlCeConnection connection =
new SqlCeConnection(connectionString))
{
SqlCeCommand command = new SqlCeCommand(
string.Format(
"SELECT [Employee ID] FROM Employees " +
"WHERE [Last Name] = '{0}' AND [First Name] = '{1}'",
lastName, firstName),
connection);

connection.Open();
try
{
return (int)command.ExecuteScalar();
}
finally
{
connection.Close();
}
}
}

// Posts random Employee data to the provided target block.


static void PostRandomEmployees(ITargetBlock<Employee> target, int count)
{
Console.WriteLine("Adding {0} entries to Employee table...", count);

for (int i = 0; i < count; i++)


{
target.Post(Employee.Random());
}
}

// Adds random employee data to the database by using dataflow.


static void AddEmployees(string connectionString, int count)
{
// Create an ActionBlock<Employee> object that adds a single
// employee entry to the database.
var insertEmployee = new ActionBlock<Employee>(e =>
InsertEmployees(new Employee[] { e }, connectionString));

// Post several random Employee objects to the dataflow block.


PostRandomEmployees(insertEmployee, count);

// Set the dataflow block to the completed state and wait for
// all insert operations to complete.
insertEmployee.Complete();
insertEmployee.Completion.Wait();
}

// Adds random employee data to the database by using dataflow.


// This method is similar to AddEmployees except that it uses batching
// to add multiple employees to the database at a time.
static void AddEmployeesBatched(string connectionString, int batchSize,
int count)
{
// Create a BatchBlock<Employee> that holds several Employee objects and
// then propagates them out as an array.
var batchEmployees = new BatchBlock<Employee>(batchSize);

// Create an ActionBlock<Employee[]> object that adds multiple


// employee entries to the database.
var insertEmployees = new ActionBlock<Employee[]>(a =>
InsertEmployees(a, connectionString));

// Link the batch block to the action block.


batchEmployees.LinkTo(insertEmployees);

// When the batch block completes, set the action block also to complete.
batchEmployees.Completion.ContinueWith(delegate { insertEmployees.Complete(); });

// Post several random Employee objects to the batch block.


PostRandomEmployees(batchEmployees, count);

// Set the batch block to the completed state and wait for
// all insert operations to complete.
batchEmployees.Complete();
insertEmployees.Completion.Wait();
}

// Displays information about several random employees to the console.


static void GetRandomEmployees(string connectionString, int batchSize,
int count)
{
// Create a BatchedJoinBlock<Employee, Exception> object that holds
// both employee and exception data.
var selectEmployees = new BatchedJoinBlock<Employee, Exception>(batchSize);

// Holds the total number of exceptions that occurred.


int totalErrors = 0;

// Create an action block that prints employee and error information


// to the console.
var printEmployees =
new ActionBlock<Tuple<IList<Employee>, IList<Exception>>>(data =>
{
// Print information about the employees in this batch.
Console.WriteLine("Received a batch...");
foreach (Employee e in data.Item1)
{
Console.WriteLine("Last={0} First={1} ID={2}",
e.LastName, e.FirstName, e.EmployeeID);
}
// Print the error count for this batch.
Console.WriteLine("There were {0} errors in this batch...",
data.Item2.Count);

// Update total error count.


totalErrors += data.Item2.Count;
});

// Link the batched join block to the action block.


selectEmployees.LinkTo(printEmployees);

// When the batched join block completes, set the action block also to complete.
selectEmployees.Completion.ContinueWith(delegate { printEmployees.Complete(); });

// Try to retrieve the ID for several random employees.


Console.WriteLine("Selecting random entries from Employees table...");
for (int i = 0; i < count; i++)
{
try
{
// Create a random employee.
Employee e = Employee.Random();

// Try to retrieve the ID for the employee from the database.


e.EmployeeID = GetEmployeeID(e.LastName, e.FirstName, connectionString);

// Post the Employee object to the Employee target of


// the batched join block.
selectEmployees.Target1.Post(e);
}
catch (NullReferenceException e)
{
// GetEmployeeID throws NullReferenceException when there is
// no such employee with the given name. When this happens,
// post the Exception object to the Exception target of
// the batched join block.
selectEmployees.Target2.Post(e);
}
}

// Set the batched join block to the completed state and wait for
// all retrieval operations to complete.
selectEmployees.Complete();
printEmployees.Completion.Wait();

// Print the total error count.


Console.WriteLine("Finished. There were {0} total errors.", totalErrors);
}

static void Main(string[] args)


{
// Create a connection string for accessing the database.
// The connection string refers to the temporary database location.
string connectionString = string.Format(@"Data Source={0}",
scratchDatabase);

// Create a Stopwatch object to time database insert operations.


Stopwatch stopwatch = new Stopwatch();

// Start with a clean database file by copying the source database to


// the temporary location.
File.Copy(sourceDatabase, scratchDatabase, true);

// Demonstrate multiple insert operations without batching.


Console.WriteLine("Demonstrating non-batched database insert operations...");
Console.WriteLine("Original size of Employee table: {0}.",
GetEmployeeCount(connectionString));
stopwatch.Start();
AddEmployees(connectionString, insertCount);
AddEmployees(connectionString, insertCount);
stopwatch.Stop();
Console.WriteLine("New size of Employee table: {0}; elapsed insert time: {1} ms.",
GetEmployeeCount(connectionString), stopwatch.ElapsedMilliseconds);

Console.WriteLine();

// Start again with a clean database file.


File.Copy(sourceDatabase, scratchDatabase, true);

// Demonstrate multiple insert operations, this time with batching.


Console.WriteLine("Demonstrating batched database insert operations...");
Console.WriteLine("Original size of Employee table: {0}.",
GetEmployeeCount(connectionString));
stopwatch.Restart();
AddEmployeesBatched(connectionString, insertBatchSize, insertCount);
stopwatch.Stop();
Console.WriteLine("New size of Employee table: {0}; elapsed insert time: {1} ms.",
GetEmployeeCount(connectionString), stopwatch.ElapsedMilliseconds);

Console.WriteLine();

// Start again with a clean database file.


File.Copy(sourceDatabase, scratchDatabase, true);

// Demonstrate multiple retrieval operations with error reporting.


Console.WriteLine("Demonstrating batched join database select operations...");
// Add a small number of employees to the database.
AddEmployeesBatched(connectionString, insertBatchSize, 16);
// Query for random employees.
GetRandomEmployees(connectionString, insertBatchSize, 10);
}
}
}
/* Sample output:
Demonstrating non-batched database insert operations...
Original size of Employee table: 15.
Adding 256 entries to Employee table...
New size of Employee table: 271; elapsed insert time: 11035 ms.

Demonstrating batched database insert operations...


Original size of Employee table: 15.
Adding 256 entries to Employee table...
New size of Employee table: 271; elapsed insert time: 197 ms.

Demonstrating batched join database insert operations...


Adding 16 entries to Employee table...
Selecting items from Employee table...
Received a batch...
Last=Jones First=Tom ID=21
Last=Jones First=John ID=24
Last=Smith First=Tom ID=26
Last=Jones First=Tom ID=21
There were 4 errors in this batch...
Received a batch...
Last=Smith First=Tom ID=26
Last=Jones First=Mike ID=28
There were 0 errors in this batch...
Finished. There were 4 total errors.
*/

Imports System.Collections.Generic
Imports System.Data.SqlServerCe
Imports System.Diagnostics
Imports System.IO
Imports System.Threading.Tasks.Dataflow
' Demonstrates how to use batched dataflow blocks to improve
' the performance of database operations.
Namespace DataflowBatchDatabase
Friend Class Program
' The number of employees to add to the database.
' TODO: Change this value to experiment with different numbers of
' employees to insert into the database.
Private Shared ReadOnly insertCount As Integer = 256

' The size of a single batch of employees to add to the database.


' TODO: Change this value to experiment with different batch sizes.
Private Shared ReadOnly insertBatchSize As Integer = 96

' The source database file.


' TODO: Change this value if Northwind.sdf is at a different location
' on your computer.
Private Shared ReadOnly sourceDatabase As String = "C:\Program Files\Microsoft SQL Server Compact
Edition\v3.5\Samples\Northwind.sdf"

' TODO: Change this value if you require a different temporary location.
Private Shared ReadOnly scratchDatabase As String = "C:\Temp\Northwind.sdf"

' Describes an employee. Each property maps to a


' column in the Employees table in the Northwind database.
' For brevity, the Employee class does not contain
' all columns from the Employees table.
Private Class Employee
Public Property EmployeeID() As Integer
Public Property LastName() As String
Public Property FirstName() As String

' A random number generator that helps tp generate


' Employee property values.
Private Shared rand As New Random(42)

' Possible random first names.


Private Shared ReadOnly firstNames() As String = {"Tom", "Mike", "Ruth", "Bob", "John"}
' Possible random last names.
Private Shared ReadOnly lastNames() As String = {"Jones", "Smith", "Johnson", "Walker"}

' Creates an Employee object that contains random


' property values.
Public Shared Function Random() As Employee
Return New Employee With {.EmployeeID = -1, .LastName = lastNames(rand.Next() Mod
lastNames.Length), .FirstName = firstNames(rand.Next() Mod firstNames.Length)}
End Function
End Class

' Adds new employee records to the database.


Private Shared Sub InsertEmployees(ByVal employees() As Employee, ByVal connectionString As String)
Using connection As New SqlCeConnection(connectionString)
Try
' Create the SQL command.
Dim command As New SqlCeCommand("INSERT INTO Employees ([Last Name], [First Name])" &
"VALUES (@lastName, @firstName)", connection)

connection.Open()
For i As Integer = 0 To employees.Length - 1
' Set parameters.
command.Parameters.Clear()
command.Parameters.Add("@lastName", employees(i).LastName)
command.Parameters.Add("@firstName", employees(i).FirstName)

' Execute the command.


command.ExecuteNonQuery()
Next i
Finally
connection.Close()
End Try
End Try
End Using
End Sub

' Retrieves the number of entries in the Employees table in


' the Northwind database.
Private Shared Function GetEmployeeCount(ByVal connectionString As String) As Integer
Dim result As Integer = 0
Using sqlConnection As New SqlCeConnection(connectionString)
Dim sqlCommand As New SqlCeCommand("SELECT COUNT(*) FROM Employees", sqlConnection)

sqlConnection.Open()
Try
result = CInt(Fix(sqlCommand.ExecuteScalar()))
Finally
sqlConnection.Close()
End Try
End Using
Return result
End Function

' Retrieves the ID of the first employee that has the provided name.
Private Shared Function GetEmployeeID(ByVal lastName As String, ByVal firstName As String, ByVal
connectionString As String) As Integer
Using connection As New SqlCeConnection(connectionString)
Dim command As New SqlCeCommand(String.Format("SELECT [Employee ID] FROM Employees " & "WHERE
[Last Name] = '{0}' AND [First Name] = '{1}'", lastName, firstName), connection)

connection.Open()
Try
Return CInt(Fix(command.ExecuteScalar()))
Finally
connection.Close()
End Try
End Using
End Function

' Posts random Employee data to the provided target block.


Private Shared Sub PostRandomEmployees(ByVal target As ITargetBlock(Of Employee), ByVal count As
Integer)
Console.WriteLine("Adding {0} entries to Employee table...", count)

For i As Integer = 0 To count - 1


target.Post(Employee.Random())
Next i
End Sub

' Adds random employee data to the database by using dataflow.


Private Shared Sub AddEmployees(ByVal connectionString As String, ByVal count As Integer)
' Create an ActionBlock<Employee> object that adds a single
' employee entry to the database.
Dim insertEmployee = New ActionBlock(Of Employee)(Sub(e) InsertEmployees(New Employee() {e},
connectionString))

' Post several random Employee objects to the dataflow block.


PostRandomEmployees(insertEmployee, count)

' Set the dataflow block to the completed state and wait for
' all insert operations to complete.
insertEmployee.Complete()
insertEmployee.Completion.Wait()
End Sub

' Adds random employee data to the database by using dataflow.


' This method is similar to AddEmployees except that it uses batching
' to add multiple employees to the database at a time.
Private Shared Sub AddEmployeesBatched(ByVal connectionString As String, ByVal batchSize As Integer,
ByVal count As Integer)
' Create a BatchBlock<Employee> that holds several Employee objects and
' then propagates them out as an array.
' then propagates them out as an array.
Dim batchEmployees = New BatchBlock(Of Employee)(batchSize)

' Create an ActionBlock<Employee[]> object that adds multiple


' employee entries to the database.
Dim insertEmployees = New ActionBlock(Of Employee())(Sub(a) Program.InsertEmployees(a,
connectionString))

' Link the batch block to the action block.


batchEmployees.LinkTo(insertEmployees)

' When the batch block completes, set the action block also to complete.
batchEmployees.Completion.ContinueWith(Sub() insertEmployees.Complete())

' Post several random Employee objects to the batch block.


PostRandomEmployees(batchEmployees, count)

' Set the batch block to the completed state and wait for
' all insert operations to complete.
batchEmployees.Complete()
insertEmployees.Completion.Wait()
End Sub

' Displays information about several random employees to the console.


Private Shared Sub GetRandomEmployees(ByVal connectionString As String, ByVal batchSize As Integer,
ByVal count As Integer)
' Create a BatchedJoinBlock<Employee, Exception> object that holds
' both employee and exception data.
Dim selectEmployees = New BatchedJoinBlock(Of Employee, Exception)(batchSize)

' Holds the total number of exceptions that occurred.


Dim totalErrors As Integer = 0

' Create an action block that prints employee and error information
' to the console.
Dim printEmployees = New ActionBlock(Of Tuple(Of IList(Of Employee), IList(Of Exception)))
(Sub(data)
'
Print information about the employees in this batch.
'
Print the error count for this batch.
'
Update total error count.

Console.WriteLine("Received a batch...")

For Each e As Employee In data.Item1

Console.WriteLine("Last={0} First={1} ID={2}", e.LastName, e.FirstName, e.EmployeeID)

Next e

Console.WriteLine("There were {0} errors in this batch...", data.Item2.Count)

totalErrors += data.Item2.Count
End
Sub)

' Link the batched join block to the action block.


selectEmployees.LinkTo(printEmployees)

' When the batched join block completes, set the action block also to complete.
selectEmployees.Completion.ContinueWith(Sub() printEmployees.Complete())

' Try to retrieve the ID for several random employees.


Console.WriteLine("Selecting random entries from Employees table...")
For i As Integer = 0 To count - 1
Try
' Create a random employee.
Dim e As Employee = Employee.Random()
Dim e As Employee = Employee.Random()

' Try to retrieve the ID for the employee from the database.
e.EmployeeID = GetEmployeeID(e.LastName, e.FirstName, connectionString)

' Post the Employee object to the Employee target of


' the batched join block.
selectEmployees.Target1.Post(e)
Catch e As NullReferenceException
' GetEmployeeID throws NullReferenceException when there is
' no such employee with the given name. When this happens,
' post the Exception object to the Exception target of
' the batched join block.
selectEmployees.Target2.Post(e)
End Try
Next i

' Set the batched join block to the completed state and wait for
' all retrieval operations to complete.
selectEmployees.Complete()
printEmployees.Completion.Wait()

' Print the total error count.


Console.WriteLine("Finished. There were {0} total errors.", totalErrors)
End Sub

Shared Sub Main(ByVal args() As String)


' Create a connection string for accessing the database.
' The connection string refers to the temporary database location.
Dim connectionString As String = String.Format("Data Source={0}", scratchDatabase)

' Create a Stopwatch object to time database insert operations.


Dim stopwatch As New Stopwatch()

' Start with a clean database file by copying the source database to
' the temporary location.
File.Copy(sourceDatabase, scratchDatabase, True)

' Demonstrate multiple insert operations without batching.


Console.WriteLine("Demonstrating non-batched database insert operations...")
Console.WriteLine("Original size of Employee table: {0}.", GetEmployeeCount(connectionString))
stopwatch.Start()
AddEmployees(connectionString, insertCount)
stopwatch.Stop()
Console.WriteLine("New size of Employee table: {0}; elapsed insert time: {1} ms.",
GetEmployeeCount(connectionString), stopwatch.ElapsedMilliseconds)

Console.WriteLine()

' Start again with a clean database file.


File.Copy(sourceDatabase, scratchDatabase, True)

' Demonstrate multiple insert operations, this time with batching.


Console.WriteLine("Demonstrating batched database insert operations...")
Console.WriteLine("Original size of Employee table: {0}.", GetEmployeeCount(connectionString))
stopwatch.Restart()
AddEmployeesBatched(connectionString, insertBatchSize, insertCount)
stopwatch.Stop()
Console.WriteLine("New size of Employee table: {0}; elapsed insert time: {1} ms.",
GetEmployeeCount(connectionString), stopwatch.ElapsedMilliseconds)

Console.WriteLine()

' Start again with a clean database file.


File.Copy(sourceDatabase, scratchDatabase, True)

' Demonstrate multiple retrieval operations with error reporting.


Console.WriteLine("Demonstrating batched join database select operations...")
' Add a small number of employees to the database.
AddEmployeesBatched(connectionString, insertBatchSize, 16)
AddEmployeesBatched(connectionString, insertBatchSize, 16)
' Query for random employees.
GetRandomEmployees(connectionString, insertBatchSize, 10)
End Sub
End Class
End Namespace
' Sample output:
'Demonstrating non-batched database insert operations...
'Original size of Employee table: 15.
'Adding 256 entries to Employee table...
'New size of Employee table: 271; elapsed insert time: 11035 ms.
'
'Demonstrating batched database insert operations...
'Original size of Employee table: 15.
'Adding 256 entries to Employee table...
'New size of Employee table: 271; elapsed insert time: 197 ms.
'
'Demonstrating batched join database insert operations...
'Adding 16 entries to Employee table...
'Selecting items from Employee table...
'Received a batch...
'Last=Jones First=Tom ID=21
'Last=Jones First=John ID=24
'Last=Smith First=Tom ID=26
'Last=Jones First=Tom ID=21
'There were 4 errors in this batch...
'Received a batch...
'Last=Smith First=Tom ID=26
'Last=Jones First=Mike ID=28
'There were 0 errors in this batch...
'Finished. There were 4 total errors.
'

Vea también
Flujo de datos
Usar TPL con otros patrones asincrónicos
16/09/2020 • 2 minutes to read • Edit Online

La biblioteca TPL puede utilizarse con patrones de programación asincrónica de .NET Framework tradicionales de
varias formas.

En esta sección
TPL y la programación asincrónica tradicional de .NET Framework
Se describe cómo se pueden usar los objetos Task junto con el modelo de programación asincrónica (APM) y el
modelo asincrónico basado en eventos (EAP).
Encapsular modelos de EAP en una tarea
Se explica cómo utilizar objetos Task para encapsular modelos de EAP.

Vea también
Biblioteca TPL
TPL y la programación asincrónica tradicional de
.NET Framework
16/09/2020 • 23 minutes to read • Edit Online

.NET Framework proporciona los siguientes dos modelos estándar para realizar las operaciones asincrónicas
enlazadas a E/S y enlazadas a cálculos:
Modelo de programación asincrónica (APM), en el que las operaciones asincrónicas se representan
mediante un par de métodos Begin/End como FileStream.BeginRead y Stream.EndRead.
Modelo asincrónico basado en eventos (EAP) en el que las operaciones asincrónicas se representan
mediante un par método-evento que se denomina OperationNameAsync y OperationNameCompleted;
por ejemplo, WebClient.DownloadStringAsync y WebClient.DownloadStringCompleted. (EAP apareció por
primera vez en .NET Framework versión 2.0).
La biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) se puede usar de
varias maneras junto con cualquiera de los modelos asincrónicos. Puede exponer las operaciones de APM y EAP
como tareas a los consumidores de la biblioteca o puede exponer los modelos de APM, pero usar objetos de tarea
para implementarlos internamente. En ambos escenarios, al usar los objetos de tarea, puede simplificar el código
y aprovechar la siguiente funcionalidad útil:
Registre las devoluciones de llamada, en el formulario de continuaciones de la tarea, en cualquier
momento después de que se haya iniciado la tarea.
Coordine varias operaciones que se ejecutan en respuesta a un método Begin_ , mediante los métodos
ContinueWhenAll, ContinueWhenAny, WaitAll o WaitAny.
Encapsule las operaciones asincrónicas enlazadas a E/S y enlazadas a cálculos en el mismo objeto de tarea.
Supervise el estado del objeto de tarea.
Serialice las referencias del estado una operación para un objeto de tarea mediante
TaskCompletionSource<TResult>.

Ajustar las operaciones de APM en una tarea


Las clases System.Threading.Tasks.TaskFactory y System.Threading.Tasks.TaskFactory<TResult> proporcionan
varias sobrecargas de los métodosTaskFactory.FromAsync yTaskFactory<TResult>.FromAsyncque permiten
encapsular un par de métodos Begin/End en una instancia Task o Task<TResult>. Las diversas sobrecargas
hospedan cualquier par de métodos de Begin/End que tenga entre cero y tres parámetros de entrada.
Para los pares que tienen métodos End que devuelven un valor ( Function en Visual Basic), use los métodos de
TaskFactory<TResult> que crean un objeto Task<TResult>. Para los métodos End que devuelven un valor void (
Sub en Visual Basic), use los métodos de TaskFactory que crean un objeto Task.

En los pocos casos en los que el método Begin tiene más de tres parámetros o contiene parámetros ref o out ,
se proporcionan las sobrecargas FromAsync adicionales que encapsulan sólo el método End .
En el ejemplo siguiente, se muestra la signatura para la sobrecarga FromAsync que coincide con los métodos
FileStream.BeginRead y FileStream.EndRead. Esta sobrecarga toma los tres parámetros de entrada siguientes.
public Task<TResult> FromAsync<TArg1, TArg2, TArg3>(
Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, //BeginRead
Func<IAsyncResult, TResult> endMethod, //EndRead
TArg1 arg1, // the byte[] buffer
TArg2 arg2, // the offset in arg1 at which to start writing data
TArg3 arg3, // the maximum number of bytes to read
object state // optional state information
)

Public Function FromAsync(Of TArg1, TArg2, TArg3)(


ByVal beginMethod As Func(Of TArg1, TArg2, TArg3, AsyncCallback, Object, IAsyncResult),
ByVal endMethod As Func(Of IAsyncResult, TResult),
ByVal dataBuffer As TArg1,
ByVal byteOffsetToStartAt As TArg2,
ByVal maxBytesToRead As TArg3,
ByVal stateInfo As Object)

El primer parámetro es un delegado Func<T1,T2,T3,T4,T5,TResult> que coincide con la signatura del método
FileStream.BeginRead. El segundo parámetro es un delegado Func<T,TResult> que toma una interfaz IAsyncResult
y devuelve TResult . Dado que EndRead devuelve un entero, el compilador deduce el tipo de TResult como
Int32 y el tipo de la tarea como Task. Los últimos cuatro parámetros son idénticos a los del método
FileStream.BeginRead:
Búfer donde se van a almacenar los datos de archivo.
Desplazamiento en el búfer donde deben comenzar a escribirse los datos.
Cantidad máxima de datos que se van a leer del archivo.
Un objeto opcional que almacena los datos de estado definidos por el usuario que se van a pasar a la
devolución de llamada.
Usar ContinueWith para la funcionalidad de devolución de llamada
Si necesita obtener acceso a los datos del archivo, en contraposición a solo el número de bytes, el método
FromAsync no es suficiente. En su ligar, use Task, cuya propiedad Result contiene los datos de archivo. Puede
hacer si agrega una continuación a la tarea original. La continuación realiza el trabajo que normalmente realizaría
el delegado AsyncCallback. Se invoca cuando se completa el antecedente y se ha rellenado el búfer de datos. (El
objeto FileStream se debería cerrar antes de devolver un valor).
En el siguiente ejemplo se muestra cómo devolver un objeto Task que encapsula el par BeginRead/EndRead de la
clase FileStream.
const int MAX_FILE_SIZE = 14000000;
public static Task<string> GetFileStringAsync(string path)
{
FileInfo fi = new FileInfo(path);
byte[] data = null;
data = new byte[fi.Length];

FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length, true);

//Task<int> returns the number of bytes read


Task<int> task = Task<int>.Factory.FromAsync(
fs.BeginRead, fs.EndRead, data, 0, data.Length, null);

// It is possible to do other work here while waiting


// for the antecedent task to complete.
// ...

// Add the continuation, which returns a Task<string>.


return task.ContinueWith((antecedent) =>
{
fs.Close();

// Result = "number of bytes read" (if we need it.)


if (antecedent.Result < 100)
{
return "Data is too small to bother with.";
}
else
{
// If we did not receive the entire file, the end of the
// data buffer will contain garbage.
if (antecedent.Result < data.Length)
Array.Resize(ref data, antecedent.Result);

// Will be returned in the Result property of the Task<string>


// at some future point after the asynchronous file I/O operation completes.
return new UTF8Encoding().GetString(data);
}
});
}
Const MAX_FILE_SIZE As Integer = 14000000
Shared Function GetFileStringAsync(ByVal path As String) As Task(Of String)
Dim fi As New FileInfo(path)
Dim data(fi.Length - 1) As Byte

Dim fs As FileStream = New FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length,


True)

' Task(Of Integer) returns the number of bytes read


Dim myTask As Task(Of Integer) = Task(Of Integer).Factory.FromAsync(
AddressOf fs.BeginRead, AddressOf fs.EndRead, data, 0, data.Length, Nothing)

' It is possible to do other work here while waiting


' for the antecedent task to complete.
' ...

' Add the continuation, which returns a Task<string>.


Return myTask.ContinueWith(Function(antecedent)
fs.Close()
If (antecedent.Result < 100) Then
Return "Data is too small to bother with."
End If
' If we did not receive the entire file, the end of the
' data buffer will contain garbage.
If (antecedent.Result < data.Length) Then
Array.Resize(data, antecedent.Result)
End If

' Will be returned in the Result property of the Task<string>


' at some future point after the asynchronous file I/O operation
completes.
Return New UTF8Encoding().GetString(data)
End Function)

End Function

A continuación, se puede llamar al método de la forma siguiente.

Task<string> t = GetFileStringAsync(path);

// Do some other work:


// ...

try
{
Console.WriteLine(t.Result.Substring(0, 500));
}
catch (AggregateException ae)
{
Console.WriteLine(ae.InnerException.Message);
}

Dim myTask As Task(Of String) = GetFileStringAsync(path)

' Do some other work


' ...

Try
Console.WriteLine(myTask.Result.Substring(0, 500))
Catch ex As AggregateException
Console.WriteLine(ex.InnerException.Message)
End Try
Proporcionar los datos de estado personalizados
En las operaciones IAsyncResult típicas, si el delegado AsyncCallback requiere algún dato de estado
personalizado, tiene que pasarlo a través del último parámetro Begin para que los datos se puedan empaquetar
en el objeto IAsyncResult que se pasará finalmente al método de devolución de llamada. Normalmente no se
requiere esto cuando se usan los métodos FromAsync . Si los datos personalizados son conocidos para la
continuación, se pueden capturar directamente en el delegado de continuación. El siguiente ejemplo se parece el
ejemplo anterior, pero en lugar de examinar la propiedad Result del antecedente, la continuación examina los
datos de estado personalizados que son directamente accesibles al delegado de usuario de la continuación.

public Task<string> GetFileStringAsync2(string path)


{
FileInfo fi = new FileInfo(path);
byte[] data = new byte[fi.Length];
MyCustomState state = GetCustomState();
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length, true);
// We still pass null for the last parameter because
// the state variable is visible to the continuation delegate.
Task<int> task = Task<int>.Factory.FromAsync(
fs.BeginRead, fs.EndRead, data, 0, data.Length, null);

return task.ContinueWith((antecedent) =>


{
// It is safe to close the filestream now.
fs.Close();

// Capture custom state data directly in the user delegate.


// No need to pass it through the FromAsync method.
if (state.StateData.Contains("New York, New York"))
{
return "Start spreading the news!";
}
else
{
// If we did not receive the entire file, the end of the
// data buffer will contain garbage.
if (antecedent.Result < data.Length)
Array.Resize(ref data, antecedent.Result);

// Will be returned in the Result property of the Task<string>


// at some future point after the asynchronous file I/O operation completes.
return new UTF8Encoding().GetString(data);
}
});
}
Public Function GetFileStringAsync2(ByVal path As String) As Task(Of String)
Dim fi = New FileInfo(path)
Dim data(fi.Length - 1) As Byte
Dim state As New MyCustomState()

Dim fs As New FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length, True)


' We still pass null for the last parameter because
' the state variable is visible to the continuation delegate.
Dim myTask As Task(Of Integer) = Task(Of Integer).Factory.FromAsync(
AddressOf fs.BeginRead, AddressOf fs.EndRead, data, 0, data.Length, Nothing)

Return myTask.ContinueWith(Function(antecedent)
fs.Close()
' Capture custom state data directly in the user delegate.
' No need to pass it through the FromAsync method.
If (state.StateData.Contains("New York, New York")) Then
Return "Start spreading the news!"
End If

' If we did not receive the entire file, the end of the
' data buffer will contain garbage.
If (antecedent.Result < data.Length) Then
Array.Resize(data, antecedent.Result)
End If
'/ Will be returned in the Result property of the Task<string>
'/ at some future point after the asynchronous file I/O operation
completes.
Return New UTF8Encoding().GetString(data)
End Function)

End Function

Sincronizar varias tareas FromAsync


Los métodos estáticos ContinueWhenAll y ContinueWhenAny proporcionan flexibilidad adicional cuando se usan
junto con los métodos FromAsync . El siguiente ejemplo muestra cómo iniciar varias operaciones asincrónicas de
E/S y, a continuación, espera a que todos ellas se completen antes de ejecutar la continuación.
public Task<string> GetMultiFileData(string[] filesToRead)
{
FileStream fs;
Task<string>[] tasks = new Task<string>[filesToRead.Length];
byte[] fileData = null;
for (int i = 0; i < filesToRead.Length; i++)
{
fileData = new byte[0x1000];
fs = new FileStream(filesToRead[i], FileMode.Open, FileAccess.Read, FileShare.Read, fileData.Length,
true);

// By adding the continuation here, the


// Result of each task will be a string.
tasks[i] = Task<int>.Factory.FromAsync(
fs.BeginRead, fs.EndRead, fileData, 0, fileData.Length, null)
.ContinueWith((antecedent) =>
{
fs.Close();

// If we did not receive the entire file, the end of the


// data buffer will contain garbage.
if (antecedent.Result < fileData.Length)
Array.Resize(ref fileData, antecedent.Result);

// Will be returned in the Result property of the Task<string>


// at some future point after the asynchronous file I/O operation completes.
return new UTF8Encoding().GetString(fileData);
});
}

// Wait for all tasks to complete.


return Task<string>.Factory.ContinueWhenAll(tasks, (data) =>
{
// Propagate all exceptions and mark all faulted tasks as observed.
Task.WaitAll(data);

// Combine the results from all tasks.


StringBuilder sb = new StringBuilder();
foreach (var t in data)
{
sb.Append(t.Result);
}
// Final result to be returned eventually on the calling thread.
return sb.ToString();
});
}
Public Function GetMultiFileData(ByVal filesToRead As String()) As Task(Of String)
Dim fs As FileStream
Dim tasks(filesToRead.Length - 1) As Task(Of String)
Dim fileData() As Byte = Nothing
For i As Integer = 0 To filesToRead.Length
fileData(&H1000) = New Byte()
fs = New FileStream(filesToRead(i), FileMode.Open, FileAccess.Read, FileShare.Read, fileData.Length,
True)

' By adding the continuation here, the


' Result of each task will be a string.
tasks(i) = Task(Of Integer).Factory.FromAsync(AddressOf fs.BeginRead,
AddressOf fs.EndRead,
fileData,
0,
fileData.Length,
Nothing).
ContinueWith(Function(antecedent)
fs.Close()
'If we did not receive the entire file,
the end of the
' data buffer will contain garbage.
If (antecedent.Result < fileData.Length)
Then
ReDim Preserve
fileData(antecedent.Result)
End If

'Will be returned in the Result property


of the Task<string>
' at some future point after the
asynchronous file I/O operation completes.
Return New
UTF8Encoding().GetString(fileData)
End Function)
Next

Return Task(Of String).Factory.ContinueWhenAll(tasks, Function(data)

' Propagate all exceptions and mark all faulted


tasks as observed.
Task.WaitAll(data)

' Combine the results from all tasks.


Dim sb As New StringBuilder()
For Each t As Task(Of String) In data
sb.Append(t.Result)
Next
' Final result to be returned eventually on the
calling thread.
Return sb.ToString()
End Function)
End Function

Tareas FromAsync solo para el método End


En los pocos casos en los que el método Begin requiere más de tres parámetros de entrada o tiene parámetros
ref o out , puede usar las sobrecargas FromAsync , por ejemplo, TaskFactory<TResult>.FromAsync(IAsyncResult,
Func<IAsyncResult,TResult>), que representa sólo el método End . Estos métodos también se pueden usar en
cualquier escenario en el que se pasa IAsyncResult y desea encapsularlo en una tarea.
static Task<String> ReturnTaskFromAsyncResult()
{
IAsyncResult ar = DoSomethingAsynchronously();
Task<String> t = Task<string>.Factory.FromAsync(ar, _ =>
{
return (string)ar.AsyncState;
});

return t;
}

Shared Function ReturnTaskFromAsyncResult() As Task(Of String)


Dim ar As IAsyncResult = DoSomethingAsynchronously()
Dim t As Task(Of String) = Task(Of String).Factory.FromAsync(ar, Function(res) CStr(res.AsyncState))
Return t
End Function

Iniciar y cancelar las tareas FromAsync


La tarea devuelta por un método FromAsync tiene un estado de WaitingForActivation y la iniciará el sistema en
algún momento una vez creada la tarea. Si intenta llamar a Start en este tipo de tarea, se producirá una excepción.
No puede cancelar una tarea FromAsync , porque las API subyacentes de .NET Framework admiten actualmente la
cancelación en curso del la E/S de archivo o red. Puede agregar la funcionalidad de cancelación a un método que
encapsula una llamada FromAsync , pero sólo puede responder a la cancelación antes de que se llame a
FromAsync o después de completar (por ejemplo, en una tarea de continuación).

Algunas clases que admiten EAP, por ejemplo, WebClient, admiten la cancelación y esa funcionalidad de
cancelación nativa se puede integrar mediante los tokens de cancelación.

Exponer las operaciones de EAP complejas como tareas


La TPL no proporciona ningún método diseñado específicamente para encapsular una operación asincrónica
basada en eventos del mismo modo que la familia de métodos FromAsync ajusta el modelo IAsyncResult. Sin
embargo, TPL proporciona la clase System.Threading.Tasks.TaskCompletionSource<TResult>, que se puede usar
para representar cualquier conjunto arbitrario de operaciones como Task<TResult>. Las operaciones pueden ser
sincrónicas o asincrónicas y pueden ser enlazadas a E/S o enlazadas a cálculo, o ambos.
En el siguiente ejemplo se muestra cómo usar TaskCompletionSource<TResult> para exponer un conjunto de
operaciones WebClient asincrónicas al código de cliente como un objeto Task<TResult> básico. El método
permite escribir una matriz de direcciones URL de web y un término o nombre que se va a buscar y, a
continuación, devuelve el número de veces que aparece el término de búsqueda en cada sitio.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

public class SimpleWebExample


{
public Task<string[]> GetWordCountsSimplified(string[] urls, string name,
CancellationToken token)
{
TaskCompletionSource<string[]> tcs = new TaskCompletionSource<string[]>();
WebClient[] webClients = new WebClient[urls.Length];
object m_lock = new object();
int count = 0;
List<string> results = new List<string>();
List<string> results = new List<string>();

// If the user cancels the CancellationToken, then we can use the


// WebClient's ability to cancel its own async operations.
token.Register(() =>
{
foreach (var wc in webClients)
{
if (wc != null)
wc.CancelAsync();
}
});

for (int i = 0; i < urls.Length; i++)


{
webClients[i] = new WebClient();

#region callback
// Specify the callback for the DownloadStringCompleted
// event that will be raised by this WebClient instance.
webClients[i].DownloadStringCompleted += (obj, args) =>
{

// Argument validation and exception handling omitted for brevity.

// Split the string into an array of words,


// then count the number of elements that match
// the search term.
string[] words = args.Result.Split(' ');
string NAME = name.ToUpper();
int nameCount = (from word in words.AsParallel()
where word.ToUpper().Contains(NAME)
select word)
.Count();

// Associate the results with the url, and add new string to the array that
// the underlying Task object will return in its Result property.
lock (m_lock)
{
results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount, name));

// If this is the last async operation to complete,


// then set the Result property on the underlying Task.
count++;
if (count == urls.Length)
{
tcs.TrySetResult(results.ToArray());
}
}
};
#endregion

// Call DownloadStringAsync for each URL.


Uri address = null;
address = new Uri(urls[i]);
webClients[i].DownloadStringAsync(address, address);
} // end for

// Return the underlying Task. The client code


// waits on the Result property, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}
}

Imports System.Collections.Generic
Imports System.Net
Imports System.Threading
Imports System.Threading
Imports System.Threading.Tasks

Public Class SimpleWebExample


Dim tcs As New TaskCompletionSource(Of String())
Dim token As CancellationToken
Dim results As New List(Of String)
Dim m_lock As New Object()
Dim count As Integer
Dim addresses() As String
Dim nameToSearch As String

Public Function GetWordCountsSimplified(ByVal urls() As String, ByVal str As String,


ByVal token As CancellationToken) As Task(Of String())
addresses = urls
nameToSearch = str

Dim webClients(urls.Length - 1) As WebClient

' If the user cancels the CancellationToken, then we can use the
' WebClient's ability to cancel its own async operations.
token.Register(Sub()
For Each wc As WebClient In webClients
If wc IsNot Nothing Then
wc.CancelAsync()
End If
Next
End Sub)

For i As Integer = 0 To urls.Length - 1


webClients(i) = New WebClient()

' Specify the callback for the DownloadStringCompleted


' event that will be raised by this WebClient instance.
AddHandler webClients(i).DownloadStringCompleted, AddressOf WebEventHandler

Dim address As New Uri(urls(i))


' Pass the address, and also use it for the userToken
' to identify the page when the delegate is invoked.
webClients(i).DownloadStringAsync(address, address)
Next

' Return the underlying Task. The client code


' waits on the Result property, and handles exceptions
' in the try-catch block there.
Return tcs.Task
End Function

Public Sub WebEventHandler(ByVal sender As Object, ByVal args As DownloadStringCompletedEventArgs)

If args.Cancelled = True Then


tcs.TrySetCanceled()
Return
ElseIf args.Error IsNot Nothing Then
tcs.TrySetException(args.Error)
Return
Else
' Split the string into an array of words,
' then count the number of elements that match
' the search term.
Dim words() As String = args.Result.Split(" "c)

Dim name As String = nameToSearch.ToUpper()


Dim nameCount = (From word In words.AsParallel()
Where word.ToUpper().Contains(name)
Select word).Count()

' Associate the results with the url, and add new string to the array that
' the underlying Task object will return in its Result property.
SyncLock (m_lock)
SyncLock (m_lock)
results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount,
nameToSearch))
count = count + 1
If (count = addresses.Length) Then
tcs.TrySetResult(results.ToArray())
End If
End SyncLock
End If
End Sub
End Class

Para obtener un ejemplo más completo, en donde se incluye control de excepciones adicional y se muestra cómo
llamar al método desde código de cliente, vea Procedimiento: Encapsulado de patrones de EAP en una tarea.
Recuerde que TaskCompletionSource iniciará cualquier tarea creada por TaskCompletionSource<TResult> y, por
consiguiente, el código de usuario no debería llamar al método Start en esa tarea.

Implementar el modelo de APM usando las tareas


En algunos escenarios, puede ser deseable exponer directamente el modelo IAsyncResult mediante pares de
métodos Begin/End en una API. Por ejemplo, quizás desee mantener la coherencia con las API existentes o puede
haber automatizado herramientas que requieren este modelo. En tales casos, puede usar las tareas para
simplificar la forma en que se implementa internamente el modelo de APM.
En el siguiente ejemplo se muestra cómo usar las tareas para implementar un par de métodos Begin/End de APM
para un método enlazado a cálculo de ejecución prolongada.
class Calculator
{
public IAsyncResult BeginCalculate(int decimalPlaces, AsyncCallback ac, object state)
{
Console.WriteLine("Calling BeginCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId);
Task<string> f = Task<string>.Factory.StartNew(_ => Compute(decimalPlaces), state);
if (ac != null) f.ContinueWith((res) => ac(f));
return f;
}

public string Compute(int numPlaces)


{
Console.WriteLine("Calling compute on thread {0}", Thread.CurrentThread.ManagedThreadId);

// Simulating some heavy work.


Thread.SpinWait(500000000);

// Actual implemenation left as exercise for the reader.


// Several examples are available on the Web.
return "3.14159265358979323846264338327950288";
}

public string EndCalculate(IAsyncResult ar)


{
Console.WriteLine("Calling EndCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId);
return ((Task<string>)ar).Result;
}
}

public class CalculatorClient


{
static int decimalPlaces = 12;
public static void Main()
{
Calculator calc = new Calculator();
int places = 35;

AsyncCallback callBack = new AsyncCallback(PrintResult);


IAsyncResult ar = calc.BeginCalculate(places, callBack, calc);

// Do some work on this thread while the calulator is busy.


Console.WriteLine("Working...");
Thread.SpinWait(500000);
Console.ReadLine();
}

public static void PrintResult(IAsyncResult result)


{
Calculator c = (Calculator)result.AsyncState;
string piString = c.EndCalculate(result);
Console.WriteLine("Calling PrintResult on thread {0}; result = {1}",
Thread.CurrentThread.ManagedThreadId, piString);
}
}
Class Calculator
Public Function BeginCalculate(ByVal decimalPlaces As Integer, ByVal ac As AsyncCallback, ByVal state As
Object) As IAsyncResult
Console.WriteLine("Calling BeginCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId)
Dim myTask = Task(Of String).Factory.StartNew(Function(obj) Compute(decimalPlaces), state)
myTask.ContinueWith(Sub(antedecent) ac(myTask))

End Function
Private Function Compute(ByVal decimalPlaces As Integer)
Console.WriteLine("Calling compute on thread {0}", Thread.CurrentThread.ManagedThreadId)

' Simulating some heavy work.


Thread.SpinWait(500000000)

' Actual implemenation left as exercise for the reader.


' Several examples are available on the Web.
Return "3.14159265358979323846264338327950288"
End Function

Public Function EndCalculate(ByVal ar As IAsyncResult) As String


Console.WriteLine("Calling EndCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId)
Return CType(ar, Task(Of String)).Result
End Function
End Class

Class CalculatorClient
Shared decimalPlaces As Integer
Shared Sub Main()
Dim calc As New Calculator
Dim places As Integer = 35
Dim callback As New AsyncCallback(AddressOf PrintResult)
Dim ar As IAsyncResult = calc.BeginCalculate(places, callback, calc)

' Do some work on this thread while the calulator is busy.


Console.WriteLine("Working...")
Thread.SpinWait(500000)
Console.ReadLine()
End Sub

Public Shared Sub PrintResult(ByVal result As IAsyncResult)


Dim c As Calculator = CType(result.AsyncState, Calculator)
Dim piString As String = c.EndCalculate(result)
Console.WriteLine("Calling PrintResult on thread {0}; result = {1}",
Thread.CurrentThread.ManagedThreadId, piString)
End Sub

End Class

Usar el código de ejemplo de StreamExtensions


El archivo StreamExtensions.cs, en el repositorio Extensiones adicionales en paralelo de .NET Standard, contiene
varias implementaciones de la referencia que usan objetos de Task para la E/S asincrónica de archivo y red.

Vea también
Biblioteca TPL
Cómo: Encapsular modelos de EAP en una tarea
16/09/2020 • 5 minutes to read • Edit Online

En el ejemplo siguiente se muestra cómo exponer una secuencia arbitraria de operaciones de modelo asincrónico
basado en eventos (EAP) como una tarea mediante el uso de una clase TaskCompletionSource<TResult>. En el
ejemplo también se muestra cómo usar una clase CancellationToken para invocar los métodos de cancelación
integrados en los objetos WebClient.

Ejemplo
class WebDataDownloader
{

static void Main()


{
WebDataDownloader downloader = new WebDataDownloader();
string[] addresses = { "http://www.msnbc.com", "http://www.yahoo.com",
"http://www.nytimes.com", "http://www.washingtonpost.com",
"http://www.latimes.com", "http://www.newsday.com" };
CancellationTokenSource cts = new CancellationTokenSource();

// Create a UI thread from which to cancel the entire operation


Task.Factory.StartNew(() =>
{
Console.WriteLine("Press c to cancel");
if (Console.ReadKey().KeyChar == 'c')
cts.Cancel();
});

// Using a neutral search term that is sure to get some hits.


Task<string[]> webTask = downloader.GetWordCounts(addresses, "the", cts.Token);

// Do some other work here unless the method has already completed.
if (!webTask.IsCompleted)
{
// Simulate some work.
Thread.SpinWait(5000000);
}

string[] results = null;


try
{
results = webTask.Result;
}
catch (AggregateException e)
{
foreach (var ex in e.InnerExceptions)
{
OperationCanceledException oce = ex as OperationCanceledException;
if (oce != null)
{
if (oce.CancellationToken == cts.Token)
{
Console.WriteLine("Operation canceled by user.");
}
}
else
{
Console.WriteLine(ex.Message);
}
}
}
}
finally
{
cts.Dispose();
}
if (results != null)
{
foreach (var item in results)
Console.WriteLine(item);
}
Console.ReadKey();
}

Task<string[]> GetWordCounts(string[] urls, string name, CancellationToken token)


{
TaskCompletionSource<string[]> tcs = new TaskCompletionSource<string[]>();
WebClient[] webClients = new WebClient[urls.Length];

// If the user cancels the CancellationToken, then we can use the


// WebClient's ability to cancel its own async operations.
token.Register(() =>
{
foreach (var wc in webClients)
{
if (wc != null)
wc.CancelAsync();
}
});

object m_lock = new object();


int count = 0;
List<string> results = new List<string>();
for (int i = 0; i < urls.Length; i++)
{
webClients[i] = new WebClient();

#region callback
// Specify the callback for the DownloadStringCompleted
// event that will be raised by this WebClient instance.
webClients[i].DownloadStringCompleted += (obj, args) =>
{
if (args.Cancelled == true)
{
tcs.TrySetCanceled();
return;
}
else if (args.Error != null)
{
// Pass through to the underlying Task
// any exceptions thrown by the WebClient
// during the asynchronous operation.
tcs.TrySetException(args.Error);
return;
}
else
{
// Split the string into an array of words,
// then count the number of elements that match
// the search term.
string[] words = null;
words = args.Result.Split(' ');
string NAME = name.ToUpper();
int nameCount = (from word in words.AsParallel()
where word.ToUpper().Contains(NAME)
select word)
.Count();

// Associate the results with the url, and add new string to the array that
// the underlying Task object will return in its Result property.
results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount,
name));
}

// If this is the last async operation to complete,


// then set the Result property on the underlying Task.
lock (m_lock)
{
count++;
if (count == urls.Length)
{
tcs.TrySetResult(results.ToArray());
}
}
};
#endregion

// Call DownloadStringAsync for each URL.


Uri address = null;
try
{
address = new Uri(urls[i]);
// Pass the address, and also use it for the userToken
// to identify the page when the delegate is invoked.
webClients[i].DownloadStringAsync(address, address);
}

catch (UriFormatException ex)


{
// Abandon the entire operation if one url is malformed.
// Other actions are possible here.
tcs.TrySetException(ex);
return tcs.Task;
}
}

// Return the underlying Task. The client code


// waits on the Result property, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}

Class WebDataDownLoader

Dim tcs As New TaskCompletionSource(Of String())


Dim nameToSearch As String
Dim token As CancellationToken
Dim results As New List(Of String)
Dim m_lock As Object
Dim count As Integer
Dim addresses() As String

Shared Sub Main()

Dim downloader As New WebDataDownLoader()


downloader.addresses = {"http://www.msnbc.com", "http://www.yahoo.com", _
"http://www.nytimes.com", "http://www.washingtonpost.com", _
"http://www.latimes.com", "http://www.newsday.com"}
Dim cts As New CancellationTokenSource()

' Create a UI thread from which to cancel the entire operation


Task.Factory.StartNew(Sub()
Console.WriteLine("Press c to cancel")
If Console.ReadKey().KeyChar = "c"c Then
cts.Cancel()
End If
End Sub)
End Sub)

' Using a neutral search term that is sure to get some hits on English web sites.
' Please substitute your favorite search term.
downloader.nameToSearch = "the"
Dim webTask As Task(Of String()) = downloader.GetWordCounts(downloader.addresses,
downloader.nameToSearch, cts.Token)

' Do some other work here unless the method has already completed.
If (webTask.IsCompleted = False) Then
' Simulate some work
Thread.SpinWait(5000000)
End If

Dim results As String() = Nothing


Try
results = webTask.Result
Catch ae As AggregateException
For Each ex As Exception In ae.InnerExceptions
If (TypeOf (ex) Is OperationCanceledException) Then
Dim oce As OperationCanceledException = CType(ex, OperationCanceledException)
If oce.CancellationToken = cts.Token Then
Console.WriteLine("Operation canceled by user.")
End If
Else
Console.WriteLine(ex.Message)
End If

Next
Finally
cts.Dispose()
End Try

If (Not results Is Nothing) Then


For Each item As String In results
Console.WriteLine(item)
Next
End If

Console.WriteLine("Press any key to exit")


Console.ReadKey()
End Sub

Public Function GetWordCounts(ByVal urls() As String, ByVal str As String, ByVal token As
CancellationToken) As Task(Of String())

Dim webClients() As WebClient


ReDim webClients(urls.Length)
m_lock = New Object()

' If the user cancels the CancellationToken, then we can use the
' WebClient's ability to cancel its own async operations.
token.Register(Sub()
For Each wc As WebClient In webClients
If Not wc Is Nothing Then
wc.CancelAsync()
End If
Next
End Sub)

For i As Integer = 0 To urls.Length - 1


webClients(i) = New WebClient()

' Specify the callback for the DownloadStringCompleted


' event that will be raised by this WebClient instance.
AddHandler webClients(i).DownloadStringCompleted, AddressOf WebEventHandler

Dim address As Uri = Nothing


Try
address = New Uri(urls(i))
' Pass the address, and also use it for the userToken
' to identify the page when the delegate is invoked.
webClients(i).DownloadStringAsync(address, address)
Catch ex As UriFormatException
tcs.TrySetException(ex)
Return tcs.Task
End Try

Next

' Return the underlying Task. The client code


' waits on the Result property, and handles exceptions
' in the try-catch block there.
Return tcs.Task
End Function

Public Sub WebEventHandler(ByVal sender As Object, ByVal args As DownloadStringCompletedEventArgs)

If args.Cancelled = True Then


tcs.TrySetCanceled()
Return
ElseIf Not args.Error Is Nothing Then
tcs.TrySetException(args.Error)
Return
Else
' Split the string into an array of words,
' then count the number of elements that match
' the search term.
Dim words() As String = args.Result.Split(" "c)
Dim NAME As String = nameToSearch.ToUpper()
Dim nameCount = (From word In words.AsParallel()
Where word.ToUpper().Contains(NAME)
Select word).Count()

' Associate the results with the url, and add new string to the array that
' the underlying Task object will return in its Result property.
results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount,
nameToSearch))
End If

SyncLock (m_lock)
count = count + 1
If (count = addresses.Length) Then
tcs.TrySetResult(results.ToArray())
End If
End SyncLock
End Sub

Vea también
TPL y la programación asincrónica tradicional de .NET Framework
Problemas potenciales en el paralelismo de datos y
tareas
16/09/2020 • 15 minutes to read • Edit Online

En muchos casos, Parallel.For y Parallel.ForEach pueden proporcionar importantes mejoras de rendimiento con
respecto a los bucles secuenciales normales. Sin embargo, el trabajo de paralelizar el bucle aporta una
complejidad que puede conducir a problemas que, en código secuencial, no son tan comunes o no se producen en
ningún caso. En este tema se indican algunas prácticas que se deben evitar al escribir bucles paralelos.

No suponer que la ejecución en paralelo siempre es más rápida


En ciertos casos, un bucle paralelo podría ejecutarse más lentamente que su equivalente secuencial. La regla
básica es que es poco probable que los bucles en paralelo que tienen pocas iteraciones y delegados de usuario
rápidos aumenten la velocidad en gran medida. Sin embargo, dado que hay muchos factores que afectan al
rendimiento, recomendamos medir siempre los resultados reales.

Evitar la escritura en ubicaciones de memoria compartida


En código secuencial, no es raro leer o escribir en variables estáticas o campos de clase. Sin embargo, cada vez que
varios subprocesos tienen acceso simultáneamente a estas variables, hay grandes posibilidades de que se
produzcan condiciones de carrera. Aunque se pueden usar bloqueos para sincronizar el acceso a la variable, el
costo de la sincronización puede afectar negativamente al rendimiento. Por tanto, se recomienda evitar, o al menos
limitar, el acceso al estado compartido en un bucle en paralelo en la medida de lo posible. La mejor manera de
hacerlo es utilizar las sobrecargas de Parallel.For y Parallel.ForEach que usan una variable
System.Threading.ThreadLocal<T> para almacenar el estado local de subproceso durante la ejecución del bucle.
Para obtener más información, vea Cómo: Escribir un bucle Parallel.For con variables locales de subproceso y
Cómo: Escribir un bucle Parallel.ForEach con variables locales de partición.

Evitar la paralelización excesiva


Si usa bucles en paralelo, incurrirá en costos de sobrecarga al crear particiones de la colección de origen y
sincronizar los subprocesos de trabajo. El número de procesadores del equipo reduce también las ventajas de la
paralelización. Si se ejecutan varios subprocesos enlazados a cálculos en un único procesador, no se gana en
velocidad. Por tanto, debe tener cuidado para no paralelizar en exceso un bucle.
El escenario más común en el que se puede producir un exceso de paralelización son los bucles anidados. En la
mayoría de los casos, es mejor paralelizar únicamente el bucle exterior, a menos que se cumplan una o varias de
las siguientes condiciones:
Se sabe que el bucle interno es muy largo.
Se realiza un cálculo costoso en cada pedido (la operación que se muestra en el ejemplo no es costosa).
Se sabe que el sistema de destino tiene suficientes procesadores como para controlar el número de
subprocesos que se producirán al paralelizar la consulta de cust.Orders .
En todos los casos, la mejor manera de determinar la forma óptima de la consulta es mediante la prueba y la
medición.

Evitar llamadas a métodos que no son seguros para subprocesos


La escritura en métodos de instancia que no son seguros para subprocesos de un bucle en paralelo puede
producir daños en los datos, que pueden pasar o no inadvertidos para el programa. También puede dar lugar a
excepciones. En el siguiente ejemplo, varios subprocesos estarían intentando llamar simultáneamente al método
FileStream.WriteByte, lo que no se admite en la clase.

FileStream fs = File.OpenWrite(path);
byte[] bytes = new Byte[10000000];
// ...
Parallel.For(0, bytes.Length, (i) => fs.WriteByte(bytes[i]));

Dim fs As FileStream = File.OpenWrite(filepath)


Dim bytes() As Byte
ReDim bytes(1000000)
' ...init byte array
Parallel.For(0, bytes.Length, Sub(n) fs.WriteByte(bytes(n)))

Limitar las llamadas a métodos seguros para subprocesos


La mayoría de los métodos estáticos de .NET Framework son seguros para subprocesos y se les puede llamar
simultáneamente desde varios subprocesos. Sin embargo, incluso en estos casos, la sincronización que esto
supone puede conducir a una ralentización importante en la consulta.

NOTE
Puede comprobarlo si inserta algunas llamadas a WriteLine en las consultas. Aunque este método se usa en los ejemplos de
la documentación para fines de demostración, no debe usarlo en bucles paralelos a menos que sea necesario.

Tener en cuenta los problemas de afinidad de los subprocesos


Algunas tecnologías, como la interoperabilidad COM para componentes de contenedor uniproceso (STA),
Windows Forms y Windows Presentation Foundation (WPF), imponen restricciones de afinidad de subprocesos
que exigen que el código se ejecute en un subproceso determinado. Por ejemplo, tanto en Windows Forms como
en WPF, solo se puede tener acceso a un control en el subproceso donde se creó. Por ejemplo, esto significa que no
puede actualizar un control de lista desde un bucle paralelo a menos que configure el programador del
subproceso para que programe trabajo solo en el subproceso de la interfaz de usuario. Para obtener más
información, vea cómo especificar un contexto de sincronización.

Tener precaución cuando se espera en delegados a los que llama


Parallel.Invoke
En determinadas circunstancias, la biblioteca TPL incluirá una tarea, lo que significa que se ejecuta en la tarea del
subproceso que se está ejecutando actualmente. (Para más información, consulte Clase TaskScheduler). Esta
optimización de rendimiento puede provocar un interbloqueo en ciertos casos. Por ejemplo, dos tareas podrían
ejecutar el mismo código de delegado, que señala cuándo se genera un evento, y después esperar a que la otra
tarea señale. Si la segunda tarea está alineada en el mismo subproceso que la primera y la primera entra en un
estado de espera, la segunda tarea nunca podrá señalar su evento. Para evitar que esto suceda, puede especificar
un tiempo de espera en la operación de espera o utilizar constructores de subproceso explícitos para ayudar a
garantizar que una tarea no pueda bloquear a la otra.

No suponer que las iteraciones de ForEach, For y ForAll siempre se


ejecutan en paralelo
Es importante tener en cuenta que las iteraciones individuales de un bucle For, ForEach o ForAll pueden ejecutarse
en paralelo, pero no tiene que ser así necesariamente. Por consiguiente, se debe evitar escribir código cuya
exactitud dependa de la ejecución en paralelo de las iteraciones o de la ejecución de las iteraciones en algún orden
concreto. Por ejemplo, es probable que este código lleve a un interbloqueo:

ManualResetEventSlim mre = new ManualResetEventSlim();


Enumerable.Range(0, Environment.ProcessorCount * 100)
.AsParallel()
.ForAll((j) =>
{
if (j == Environment.ProcessorCount)
{
Console.WriteLine("Set on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j);
mre.Set();
}
else
{
Console.WriteLine("Waiting on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j);
mre.Wait();
}
}); //deadlocks

Dim mres = New ManualResetEventSlim()


Enumerable.Range(0, Environment.ProcessorCount * 100) _
.AsParallel() _
.ForAll(Sub(j)

If j = Environment.ProcessorCount Then
Console.WriteLine("Set on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j)
mres.Set()
Else
Console.WriteLine("Waiting on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j)
mres.Wait()
End If
End Sub) ' deadlocks

En este ejemplo, una iteración establece un evento y el resto de las iteraciones esperan el evento. Ninguna de las
iteraciones que esperan puede completarse hasta que se haya completado la iteración del valor de evento. Sin
embargo, es posible que las iteraciones que esperan bloqueen todos los subprocesos que se utilizan para ejecutar
el bucle paralelo antes de que la iteración del valor de evento haya tenido oportunidad de ejecutarse. Esto produce
un interbloqueo: la iteración del valor de evento nunca se ejecutará y las iteraciones que esperan nunca se
activarán.
En concreto, una iteración de un bucle paralelo no debe esperar nunca otra iteración del bucle para progresar. Si el
bucle paralelo decide programar las iteraciones secuencialmente pero en el orden contrario, se producirá un
interbloqueo.

Evitar la ejecución de bucles en paralelo en el subproceso de la interfaz


de usuario
Es importante que la interfaz de usuario (IU) de la aplicación siga respondiendo. Si una operación contiene
bastante trabajo para garantizar la paralelización, no se debería ejecutar en el subproceso de la interfaz de usuario.
Conviene descargarla para que se ejecute en un subproceso en segundo plano. Por ejemplo, si desea utilizar un
bucle en paralelo para calcular datos que después se presentarán en un control de IU, considere ejecutar el bucle
dentro de una instancia de la tarea en lugar de directamente en un controlador de eventos de IU. Solo si el cálculo
básico se ha completado se debería serializar la actualización de nuevo en el subproceso de la interfaz de usuario.
Si ejecuta bucles en paralelo en el subproceso de la interfaz de usuario, tenga cuidado de evitar la actualización de
los controles de la interfaz de usuario desde el interior del bucle. Si se intenta actualizar los controles de la interfaz
de usuario desde dentro de un bucle en paralelo que se está ejecutando en el subproceso de la interfaz de usuario,
se pueden provocar daños en el estado, excepciones, retrasos en las actualizaciones e incluso interbloqueos,
dependiendo de cómo se invoque la actualización de la interfaz de usuario. En el siguiente ejemplo, el bucle en
paralelo bloquea el subproceso de la interfaz de usuario en el que se está ejecutando hasta que todas las
iteraciones se completan. Sin embargo, si se está ejecutando una iteración del bucle en un subproceso en segundo
plano (como puede hacer For), la llamada a Invoke produce que se envíe un mensaje al subproceso de la interfaz
de usuario, que se bloquea mientras espera a que ese mensaje se procese. Puesto que se bloquea el subproceso
de la interfaz de usuario cuando se ejecuta For, el mensaje puede no procesarse nunca y el subproceso de la
interfaz de usuario se interbloquea.

private void button1_Click(object sender, EventArgs e)


{
Parallel.For(0, N, i =>
{
// do work for i
button1.Invoke((Action)delegate { DisplayProgress(i); });
});
}

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

Dim iterations As Integer = 20


Parallel.For(0, iterations, Sub(x)
Button1.Invoke(Sub()
DisplayProgress(x)
End Sub)
End Sub)
End Sub

En el siguiente ejemplo se muestra cómo evitar el interbloqueo mediante la ejecución del bucle dentro de una
instancia de la tarea. El bucle no bloquea el subproceso de la interfaz de usuario y se puede procesar el mensaje.

private void button1_Click(object sender, EventArgs e)


{
Task.Factory.StartNew(() =>
Parallel.For(0, N, i =>
{
// do work for i
button1.Invoke((Action)delegate { DisplayProgress(i); });
})
);
}

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

Dim iterations As Integer = 20


Task.Factory.StartNew(Sub() Parallel.For(0, iterations, Sub(x)
Button1.Invoke(Sub()
DisplayProgress(x)
End Sub)
End Sub))
End Sub
Vea también
Programación en paralelo
Posibles problemas con PLINQ
Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4
(Patrones para la programación paralela: comprender y aplicar patrones paralelos con .NET Framework 4)
Introducción a PLINQ
16/09/2020 • 22 minutes to read • Edit Online

Parallel LINQ (PLINQ) es una implementación en paralelo del patrón Language-Integrated Query (LINQ).
PLINQ implementa el conjunto completo de operadores de consulta estándar de LINQ como métodos de
extensión para el espacio de nombres System.Linq y tiene operadores adicionales para las operaciones en
paralelo. PLINQ combina la simplicidad y legibilidad de la sintaxis de LINQ con la eficacia de la
programación en paralelo.

TIP
Si no está familiarizado con LINQ, ofrece un modelo unificado para consultar cualquier origen de datos enumerable
en un modo con seguridad de tipos. LINQ to Objects es el nombre de las consultas LINQ que se ejecutan en
colecciones en memoria, como List<T> y matrices. En este artículo, se da por echo que posee un conocimiento
básico de LINQ. Para obtener más información, vea Language Integrated Query (LINQ).

¿Qué es una consulta en paralelo?


En muchos sentidos, una consulta PLINQ se parece a una consulta LINQ to Objects no en paralelo. Las
consultas PLINQ, al igual que las consultas LINQ secuenciales, funcionan en cualquier origen de datos
IEnumerable o IEnumerable<T> en memoria y tienen ejecución aplazada, lo que significa que no se
empiezan a ejecutar hasta que se enumera la consulta. La principal diferencia es que PLINQ intenta usar
completamente todos los procesadores del sistema. Para ello, crea particiones del origen de datos en
segmentos y, luego, ejecuta la consulta en cada segmento en subprocesos de trabajo independientes en
paralelo en varios procesadores. En muchos casos, la ejecución en paralelo significa que la consulta se
ejecuta considerablemente más rápido.
A través de la ejecución en paralelo, PLINQ puede alcanzar importantes mejoras de rendimiento con
respecto al código heredado en ciertos tipos de consultas, a menudo solo con agregar la operación de
consulta AsParallel al origen de datos. Sin embargo, el paralelismo puede presentar sus propias
complejidades y no todas las operaciones de consulta se ejecutan más rápido en PLINQ. De hecho, la
paralelización en realidad ralentiza ciertas consultas. Por lo tanto, debe entender cómo ciertos problemas,
como la ordenación, afectan a las consultas en paralelo. Para más información, consulte Introducción a la
velocidad en PLINQ.

NOTE
En esta documentación, se utilizan expresiones lambda para definir delegados en PLINQ. Si no está familiarizado con
las expresiones lambda de C# o Visual Basic, consulte Expresiones lambda en PLINQ y TPL.

En el resto del artículo se ofrece información general de las principales clases PLINQ y se analiza cómo crear
consultas PLINQ. Cada sección incluye vínculos a información más detallada y ejemplos de código.

La clase ParallelEnumerable
La clase System.Linq.ParallelEnumerable expone casi toda la funcionalidad de PLINQ. Junto con el resto de
los tipos de espacios de nombres System.Linq, se compilan en el ensamblado System.Core.dll. Los
proyectos C# y Visual Basic predeterminados en Visual Studio hacen referencia al ensamblado e importan el
espacio de nombres.
ParallelEnumerable incluye implementaciones de todos los operadores de consulta estándar que LINQ to
Objects admite, a pesar de que no intenta ejecutar en paralelo cada uno de ellos. Si no está familiarizado
con LINK, consulte Introducción a LINQ (C#) e Introducción a LINQ (Visual Basic).
Además de los operadores de consulta estándar, la clase ParallelEnumerable contiene un conjunto de
métodos que permiten comportamientos específicos de la ejecución en paralelo. Estos métodos específicos
de PLINQ se muestran en la tabla siguiente.

O P ERA DO R PA RA L L EL EN UM ERA B L E DESC RIP C IÓ N

AsParallel Punto de entrada de PLINQ. Especifica que el resto de la


consulta se debe ejecutar en paralelo, si es posible.

AsSequential Especifica que el resto de la consulta se debe ejecutar en


secuencia, como una consulta LINQ no en paralelo.

AsOrdered Especifica que PLINQ debe conservar la ordenación de la


secuencia de origen para el resto de la consulta o hasta
que cambie la ordenación, por ejemplo, mediante el uso
de una cláusula orderby (Order By en Visual Basic).

AsUnordered Especifica que PLINQ no necesita conservar la ordenación


de la secuencia de origen para el resto de la consulta.

WithCancellation Especifica que PLINQ debe supervisar periódicamente el


estado del token de cancelación que se proporciona y
cancelar la ejecución si se solicita.

WithDegreeOfParallelism Especifica la cantidad máxima de procesadores que PLINQ


debe usar para ejecutar la consulta en paralelo.

WithMergeOptions Proporciona una sugerencia sobre cómo PLINQ debería, si


es posible, combinar los resultados en paralelo en una sola
secuencia en el subproceso utilizado.

WithExecutionMode Especifica si PLINQ debe ejecutar la consulta en paralelo


incluso si el comportamiento predeterminado indica que
se debería ejecutar en secuencia.

ForAll Método de enumeración multiproceso que, a diferencia de


la iteración sobre los resultados de la consulta, permite
procesar los resultados en paralelo sin tener que
combinarlos primero en el subproceso del consumidor.

Aggregate overload Sobrecarga única para PLINQ que permite la agregación


inmediata sobre particiones locales de subprocesos,
además de una función de agregación local para combinar
los resultados de todas las particiones.

El modelo de participación
Cuando escribe una consulta, participa en PLINQ al invocar el método de extensión
ParallelEnumerable.AsParallel en el origen de datos, tal como se muestra en el ejemplo siguiente.
var source = Enumerable.Range(1, 10000);

// Opt in to PLINQ with AsParallel.


var evenNums = from num in source.AsParallel()
where num % 2 == 0
select num;
Console.WriteLine("{0} even numbers out of {1} total",
evenNums.Count(), source.Count());
// The example displays the following output:
// 5000 even numbers out of 10000 total

Dim source = Enumerable.Range(1, 10000)

' Opt in to PLINQ with AsParallel


Dim evenNums = From num In source.AsParallel()
Where num Mod 2 = 0
Select num
Console.WriteLine("{0} even numbers out of {1} total",
evenNums.Count(), source.Count())
' The example displays the following output:
' 5000 even numbers out of 10000 total

El método de extensión AsParallel enlaza los operadores de consulta subsiguientes, en este caso, where y
select , a las implementaciones System.Linq.ParallelEnumerable.

Modos de ejecución
De manera predeterminada, PLINQ es conservador. En tiempo de ejecución, la infraestructura de PLINQ
analiza la estructura general de la consulta. Si existe la probabilidad de que la consulta genere aumentos de
velocidad a través de la paralelización, PLINQ particiona la secuencia de origen en tareas que se pueden
ejecutar en simultáneo. Si ejecutar una consulta en paralelo no es seguro, PLINQ simplemente la ejecuta en
secuencia. Si PLINQ puede elegir entre un algoritmo en paralelo posiblemente costoso y un algoritmo
secuencial económico, de manera predeterminada elegirá el algoritmo secuencial. Puede usar el método
WithExecutionMode y la enumeración System.Linq.ParallelExecutionMode para indicar a PLINQ que
seleccione el algoritmo paralelo. Esto resulta útil cuando, a través de pruebas y mediciones, sabe que una
consulta determinada se ejecuta más rápido en paralelo. Para obtener más información, vea Cómo:
Especificar el modo de ejecución en PLINQ.

Grado de paralelismo
De manera predeterminada, PLINQ usa todos los procesadores del equipo host. Puede indicar a PLINQ que
use más de un número especificado de procesadores mediante el método WithDegreeOfParallelism. Esto
resulta útil cuando desea asegurarse de que otros procesos que estén en ejecución en el equipo reciban
cierta cantidad de tiempo de CPU. El siguiente fragmento de código limita la consulta para que solo use un
máximo de dos procesadores.

var query = from item in source.AsParallel().WithDegreeOfParallelism(2)


where Compute(item) > 42
select item;

Dim query = From item In source.AsParallel().WithDegreeOfParallelism(2)


Where Compute(item) > 42
Select item

En casos en los que una consulta realiza una cantidad considerable de trabajo no enlazado a proceso, como
E/S de archivo, puede se recomendable especificar un grado de paralelismo mayor que el número de
núcleos de la máquina.

Comparación entre consultas en paralelo ordenadas y no


ordenadas
En algunas consultas, un operador de consulta debe generar resultados que conserven la ordenación de la
secuencia de origen. PLINQ proporciona el operador AsOrdered para este propósito. AsOrdered es distinto
de AsSequential. Una secuencia AsOrdered de todos modos se procesa en paralelo, pero sus resultados se
almacenan en búfer y se ordenan. Dado que conservar el orden implica normalmente trabajo adicional, una
secuencia AsOrdered se podría procesar más despacio que la secuencia AsUnordered predeterminada. El
hecho que una operación en paralelo ordenada determinada sea más rápida que una versión secuencial de
la operación depende de muchos factores.
En el ejemplo de código siguiente se muestra cómo participar en conservar el orden.

var evenNums = from num in numbers.AsParallel().AsOrdered()


where num % 2 == 0
select num;

Dim evenNums = From num In numbers.AsParallel().AsOrdered()


Where num Mod 2 = 0
Select num

Para más información, consulte cómo conservar el orden en PLINQ.

Comparación entre consultas en paralelo y consultas secuenciales


Algunas operaciones requieren que los datos de origen se entreguen en secuencia. Los operadores de
consulta ParallelEnumerable vuelven automáticamente al modo secuencial cuando es necesario. Para los
operadores de consulta definidos por el usuario y los delegados de usuario que requieren la ejecución
secuencial, PLINQ proporciona el método AsSequential. Cuando se usa AsSequential, todos los operadores
subsiguientes de la consulta se ejecutan secuencialmente hasta que se vuelve a llamar a AsParallel. Para
obtener más información, vea Cómo: Combinar consultas LINQ paralelas y secuenciales.

Opciones para combinar resultados de consulta


Cuando una consulta PLINQ se ejecuta en paralelo, sus resultados a partir de cada subproceso de trabajo se
deben volver a combinar en el subproceso principal para que los use un bucle foreach ( For Each en Visual
Basic) o para su inserción en una lista o matriz. En algunos casos, podría ser recomendable especificar un
tipo determinado de operación Merge, por ejemplo, para comenzar a generar resultados más rápidamente.
Para este propósito, PLINQ admite el método WithMergeOptions y la enumeración ParallelMergeOptions.
Para más información, consulte las opciones de combinación en PLINQ.

El operador ForAll
En las consultas LINK secuenciales, la ejecución se aplaza hasta que la consulta se enumera en un bucle
foreach ( For Each en Visual Basic) o mediante la invocación de un método como ToList, ToArray o
ToDictionary. En PLINQ, también puede usar foreach para ejecutar la consulta e iterar a través de los
resultados. Sin embargo, foreach no se ejecuta en paralelo y, por lo tanto, requiere que el resultado de
todas las tareas en paralelo se combinen nuevamente en el subproceso en el que se ejecuta el bucle. En
PLINQ, puede usar foreach cuando deba conservar la ordenación final de los resultados de la consulta y
también cada vez que procese los resultados de forma serial; por ejemplo, cuando llama a
Console.WriteLine para cada elemento. Para lograr un ejecución más rápida de las consultas cuando no es
necesario conservar el orden y cuando el procesamiento mismo de los resultados se puede ejecutar en
paralelo, use el método ForAll para ejecutar una consulta PLINQ. ForAll no lleva a cabo este paso de
combinación final. En el ejemplo de código siguiente, se muestra cómo se utiliza el método ForAll. Aquí se
usa System.Collections.Concurrent.ConcurrentBag<T> porque está optimizado para varios subprocesos, lo
que agrega simultaneidad sin intentar quitar ningún elemento.

var nums = Enumerable.Range(10, 10000);


var query = from num in nums.AsParallel()
where num % 10 == 0
select num;

// Process the results as each thread completes


// and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
// which can safely accept concurrent add operations
query.ForAll(e => concurrentBag.Add(Compute(e)));

Dim nums = Enumerable.Range(10, 10000)


Dim query = From num In nums.AsParallel()
Where num Mod 10 = 0
Select num

' Process the results as each thread completes


' and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
' which can safely accept concurrent add operations
query.ForAll(Sub(e) concurrentBag.Add(Compute(e)))

En la ilustración siguiente se muestra la diferencia entre foreach y ForAll con respecto a la ejecución de la
consulta.

Cancelación
PLINQ se integra con los tipos de cancelación en .NET Framework 4. (Para más información, consulte el
tema sobre la cancelación en subprocesos administrados). Por lo tanto, a diferencia de las consultas LINQ to
Objects secuenciales, las consultas PLINQ se pueden cancelar. Para crear una consulta PLINQ cancelable, use
el operador WithCancellation en la consulta y proporcione una instancia CancellationToken como
argumento. Cuando la propiedad IsCancellationRequested del token se establece en true, PLINQ lo tendrá
en cuenta, detendrá el procesamiento de todos los subprocesos e iniciará OperationCanceledException.
Es posible que una consulta PLINQ siga procesando algunos elementos una vez establecido el token de
cancelación.
Para una mayor capacidad de respuesta, también puede responder a las solicitudes de cancelación en
delegados de usuario de ejecución prolongada. Para obtener más información, vea Cómo: Cancelar una
consulta PLINQ.

Excepciones
Cuando se ejecuta una consulta PLINQ, distintos subprocesos pueden generar varias excepciones de forma
simultánea. Además, el código para controlar la excepción puede estar en un subproceso distinto al del
código que generó la excepción. PLINQ usa el tipo AggregateException para encapsular todas las
excepciones que generó una consulta y calcular las referencias de esas excepciones nuevamente en el
subproceso que realiza la llamada. En el subproceso que realiza la llamada, solo se requiere un bloque try-
catch. Sin embargo, puede iterar a través de todas las excepciones que están encapsuladas en
AggregateException y capturar cualquiera desde la que pueda realizar una recuperación de forma segura.
En raras ocasiones, se pueden generar algunas excepciones que no se ajustan en AggregateException, y
ThreadAbortException tampoco se ajusta.
Cuando las excepciones pueden propagarse de nuevo al subproceso de unión, es posible que una consulta
continúe procesando algunos elementos después de que se haya producido la excepción.
Para obtener más información, vea Cómo: Controlar excepciones en una consulta PLINQ.

Particionadores personalizados
En algunos casos, puede mejorar el rendimiento de las consultas si escribe un particionador personalizado
que aproveche algunas características de los datos de origen. En la consulta, el mismo particionador
personalizado es el objeto enumerable que se consulta.

int[] arr = new int[9999];


Partitioner<int> partitioner = new MyArrayPartitioner<int>(arr);
var query = partitioner.AsParallel().Select(x => SomeFunction(x));

Dim arr(10000) As Integer


Dim partitioner As Partitioner(Of Integer) = New MyArrayPartitioner(Of Integer)(arr)
Dim query = partitioner.AsParallel().Select(Function(x) SomeFunction(x))

PLINQ admite una cantidad fija de particiones (a pesar de que los datos se pueden reasignar de forma
dinámica a esas particiones durante el tiempo de ejecución para el equilibrio de carga). For y ForEach
admiten solo la partición dinámica, lo que significa que el número de particiones cambia en tiempo de
ejecución. Para más información, consulte Custom Partitioners for PLINQ and TPL (Particionadores
personalizados para PLINQ y TPL).

Medición del rendimiento de PLINQ


En muchos casos, una consulta se puede ejecutar en paralelo, pero la sobrecarga que implica configurar la
consulta en paralelo supera el beneficio obtenido en el rendimiento. Si una consulta no realiza mucho
cálculo o si el origen de datos es pequeño, una consulta PLINQ podría ser más lenta que una consulta LINQ
to Objects secuencial. Puede usar el analizador de rendimiento en paralelo en Visual Studio Team Server
para comparar el rendimiento de diversas consultas, ubicar cuellos de botella en el procesamiento y
determinar si la consulta se ejecuta en paralelo o secuencialmente. Para más información, vea Visualizador
de simultaneidad y Cómo: Medir el rendimiento de consultas PLINQ.

Vea también
Parallel LINQ (PLINQ)
Introducción a la velocidad en PLINQ
Introducción a la velocidad en PLINQ
16/09/2020 • 12 minutes to read • Edit Online

El objetivo principal de PLINQ es acelerar la ejecución de consultas LINQ to Objects mediante la ejecución de los
delegados de consulta en paralelo en equipos de varios núcleos. PLINQ funciona mejor cuando el
procesamiento de cada elemento de una colección de origen es independiente, sin ningún estado compartido
implicado entre los delegados individuales. Estas operaciones son comunes en LINQ to Objects y PLINQ y a
menudo se denominan "perfectamente paralelas" porque se prestan fácilmente a la programación en varios
subprocesos. Sin embargo, no todas las consultas constan de operaciones paralelas perfectas; en la mayoría de
los casos, una consulta incluye algunos operadores que no se pueden paralelizar o que ralentizan la ejecución
en paralelo. E incluso con las consultas que son perfectamente paralelas, PLINQ debe crear particiones del
origen de datos y programar el trabajo en los subprocesos y generalmente tiene que combinar los resultados
cuando finaliza la consulta. Todas estas operaciones aumentan el costo computacional de la paralelización; a
estos costos derivados de agregar la paralelización se les denomina sobrecarga. Para lograr un rendimiento
óptimo de una consulta PLINQ, el objetivo es maximizar las partes que son perfectamente paralelas y minimizar
las partes que requieren sobrecarga. Este artículo proporciona información que le ayudará a escribir consultas
PLINQ tan eficaces como sea posible mientras se siguen produciendo resultados correctos.

Factores que afectan al rendimiento de las consultas PLINQ


En las siguientes secciones se enumeran algunos de los factores más importantes que afectan al rendimiento de
las consultas paralelas. Estas son las instrucciones generales que por sí mismas no son suficientes para predecir
el rendimiento de las consultas en todos los casos. Como siempre, es importante medir el rendimiento real de
consultas específicas en equipos con una variedad de cargas y configuraciones representativas.
1. Costo computacional del trabajo total.
Para conseguir velocidad, una consulta PLINQ debe tener suficiente trabajo perfectamente paralelo para
compensar la sobrecarga. El trabajo se puede expresar como el costo computacional de cada delegado
multiplicado por el número de elementos de la colección de origen. Suponiendo que una operación se
puede paralelizar, mientras más costoso resulte desde el punto de vista computacional, mayor será la
posibilidad de aumentar la velocidad. Por ejemplo, si una función tarda un milisegundo en ejecutarse,
una consulta secuencial de más de 1000 elementos tardará un segundo en realizar esa operación,
mientras que una consulta paralela en un equipo con cuatro núcleos puede tardar solo 250
milisegundos. Esto da como resultado una velocidad de 750 milisegundos. Si la función requiere un
segundo para ejecutar cada elemento, la velocidad sería de 750 segundos. Si el delegado es muy caro,
PLINQ podría proporcionar un aumento significativo de la velocidad con solo unos pocos elementos de
la colección de origen. Por el contrario, las colecciones de origen pequeñas con delegados triviales
generalmente no son buenas candidatas para PLINQ.
En el ejemplo siguiente, queryA es probablemente una buena candidata para PLINQ, suponiendo que su
función Select implica mucho trabajo. queryB probablemente no es una buena candidata porque no hay
suficiente trabajo en la instrucción Select, y la sobrecarga de la paralelización compensará la mayoría o la
totalidad de la velocidad.
Dim queryA = From num In numberList.AsParallel()
Select ExpensiveFunction(num); 'good for PLINQ

Dim queryB = From num In numberList.AsParallel()


Where num Mod 2 > 0
Select num; 'not as good for PLINQ

var queryA = from num in numberList.AsParallel()


select ExpensiveFunction(num); //good for PLINQ

var queryB = from num in numberList.AsParallel()


where num % 2 > 0
select num; //not as good for PLINQ

2. El número de núcleos lógicos del sistema (grado de paralelismo).


Este punto es un corolario obvio de la sección anterior, las consultas que son perfectamente paralelas se
ejecutan más rápido en equipos con varios núcleos porque se puede dividir el trabajo entre más
subprocesos simultáneos. La cantidad total de velocidad depende de qué porcentaje del trabajo total de
la consulta se puede paralelizar. Sin embargo, no se da por supuesto que todas las consultas se
ejecutarán dos veces más rápido en un equipo de ocho núcleos que un equipo de cuatro núcleos. Al
optimizar las consultas para un rendimiento óptimo, es importante medir los resultados reales en
equipos con varios números de núcleos. Este punto se relaciona con el punto 1: los conjuntos de datos
más grandes deben aprovechar la ventaja de una cantidad mayor de recursos informáticos.
3. El número y tipo de operaciones.
PLINQ proporciona el operador AsOrdered para situaciones en las que es necesario mantener el orden
de los elementos de la secuencia de origen. Hay un costo asociado con la ordenación, pero suele ser
moderado. Las operaciones GroupBy y Join también incurren en sobrecarga. PLINQ funciona mejor si se
permite procesar elementos en la colección de origen en cualquier orden y pasarlos al operador
siguiente en cuanto estén listos. Para más información, consulte cómo conservar el orden en PLINQ.
4. La forma de ejecución de consultas.
Si va a almacenar los resultados de una consulta llamando a ToArray o ToList, los resultados de todos los
subprocesos paralelos deben combinarse en la estructura de datos única. Esto implica un costo
computacional inevitable. Asimismo, si se recorren en iteración los resultados mediante el uso de un
bucle foreach (For Each en Visual Basic), los resultados de los subprocesos de trabajo deben serializarse
en el subproceso del enumerador. Pero si desea realizar alguna acción en función del resultado de cada
subproceso, puede utilizar el método ForAll para realizar este trabajo en varios subprocesos.
5. El tipo de opciones de combinación.
PLINQ puede configurarse para almacenar en búfer su salida y generarla en fragmentos o a la vez
después de que el conjunto de resultados completo se genere, o bien para transmitir secuencias de los
resultados individuales a medida que se generan. El primero da como resultado una reducción del
tiempo de ejecución total y el último da como resultado una reducción de la latencia entre los elementos
producidos. Aunque las opciones de combinación no siempre tienen una repercusión importante en el
rendimiento general de las consultas, pueden afectar al rendimiento percibido porque controlan el
tiempo que un usuario debe esperar para ver los resultados. Para más información, consulte las opciones
de combinación en PLINQ.
6. El tipo de partición.
En algunos casos, una consulta PLINQ sobre una colección de origen indexable puede producir una carga
de trabajo desequilibrada. Cuando esto ocurre, es posible que pueda aumentar el rendimiento de las
consultas con la creación de un particionador personalizado. Para más información, consulte Custom
Partitioners for PLINQ and TPL (Particionadores personalizados para PLINQ y TPL).

Cuando PLINQ elige el modo secuencial


PLINQ siempre intentará ejecutar una consulta al menos tan rápido como se ejecutaría de forma secuencial.
Aunque PLINQ no se fija en lo caros que son los delegados de usuario desde el punto de vista computacional o
en lo grande que es el origen de entrada, sí busca determinadas "formas" de consulta. En concreto, busca
operadores de consulta o combinaciones de operadores que normalmente provocan que una consulta se
ejecute más lentamente en modo paralelo. Cuando encuentra esas formas, PLINQ vuelve al modo secuencial de
forma predeterminada.
Sin embargo, después de medir el rendimiento de una consulta concreta, puede determinar que realmente se
ejecute más rápido en modo paralelo. En tales casos puede usar la marca
ParallelExecutionMode.ForceParallelism con el método WithExecutionMode para indicar a PLINQ que paralelice
la consulta. Para obtener más información, vea Cómo: Especificar el modo de ejecución en PLINQ.
En la lista siguiente se describen las formas de consulta PLINQ que de forma predeterminada se ejecutarán en
modo secuencial:
Las consultas que contienen una instrucción Select, Where indexada, SelectMany indexada o una cláusula
ElementAt después de un operador de ordenación o filtrado que ha quitado o reorganizado los índices
originales.
Las consultas que contienen un operador Take, TakeWhile, Skip o SkipWhile y donde los índices de la
secuencia de origen no están en el orden original.
Las consultas que contienen Zip o SequenceEquals, a menos que uno de los orígenes de datos tenga un
índice ordenado inicialmente y el otro origen de datos sea indexable, es decir, una matriz o IList(T).
Las consultas que contienen Concat, a menos que se aplique a los orígenes de datos indexables.
Las consultas que contienen Reverse, a menos que se aplique a un origen de datos indexable.

Vea también
Parallel LINQ (PLINQ)
Conservar el orden en PLINQ
16/09/2020 • 10 minutes to read • Edit Online

En PLINQ, el objetivo es maximizar el rendimiento manteniendo la exactitud. Una consulta se debería ejecutar lo
más rápido que fuese posible pero con resultados correctos. La exactitud exige que se conserve el orden de la
secuencia de origen en algunos casos; sin embargo, la ordenación puede suponer la utilización de muchos
recursos de computación. Por consiguiente, de forma predeterminada, PLINQ no conserva el orden de la
secuencia de origen. En este sentido, PLINQ se parece a LINQ to SQL, pero es diferente de LINQ to Objects, que
conserva el orden.
Para reemplazar el comportamiento predeterminado, puede activar la capacidad de conservar el orden utilizando
el operador AsOrdered en la secuencia de origen. Después, puede desactivarla en la consulta, utilizando el método
AsUnordered. Con ambos métodos, se procesa la consulta basándose en la heurística que determina si la consulta
se debe ejecutar de forma paralela o secuencial. Para más información, consulte Introducción a la velocidad en
PLINQ.
En el siguiente ejemplo se muestra una consulta paralela no ordenada que filtra todos los elementos que
coinciden con una condición, sin intentar ordenar los resultados de forma alguna.

var cityQuery = (from city in cities.AsParallel()


where city.Population > 10000
select city)
.Take(1000);

Dim cityQuery = From city In cities.AsParallel()


Where City.Population > 10000
Take (1000)

Esta consulta no obtiene necesariamente las 1000 primeras ciudades de la secuencia de origen que cumplen la
condición, sino que algún conjunto de las 1000 ciudades que la cumplen. Los operadores de consulta PLINQ
particionan la secuencia de origen en varias secuencias secundarias que se procesan como tareas simultáneas. Si
no se especifica que se conserve el orden, los resultados de cada partición se presentan a la siguiente etapa de la
consulta con un orden arbitrario. Por otra parte, una partición puede producir un subconjunto de los resultados
antes de continuar procesando los elementos restantes. El orden resultante puede ser diferente cada vez. Una
aplicación no puede controlar este hecho, porque depende de cómo programe los subprocesos el sistema
operativo.
En el siguiente ejemplo se reemplaza el comportamiento predeterminado utilizando al operador AsOrdered en la
secuencia de origen. De esta forma se garantiza que el método Take devuelve las 1000 primeras ciudades de la
secuencia de origen que cumplen la condición.

var orderedCities = (from city in cities.AsParallel().AsOrdered()


where city.Population > 10000
select city)
.Take(1000);
Dim orderedCities = From city In cities.AsParallel().AsOrdered()
Where City.Population > 10000
Take (1000)

Sin embargo, esta consulta probablemente no se ejecute tan rápido como la versión no ordenada, porque debe
realizar el seguimiento del orden original en todas las particiones y, en el momento de la fusión mediante
combinación, garantizar que el orden es coherente. Por consiguiente, recomendamos usar AsOrdered solo cuando
sea estrictamente necesario y únicamente para las partes de la consulta que lo requieran. Cuando ya no sea
necesario conservar el orden, use AsUnordered para desactivarlo. En el siguiente ejemplo se consigue mediante la
creación de dos consultas.

var orderedCities2 = (from city in cities.AsParallel().AsOrdered()


where city.Population > 10000
select city)
.Take(1000);

var finalResult = from city in orderedCities2.AsUnordered()


join p in people.AsParallel() on city.Name equals p.CityName into details
from c in details
select new { Name = city.Name, Pop = city.Population, Mayor = c.Mayor };

foreach (var city in finalResult) { /*...*/ }

Dim orderedCities2 = From city In cities.AsParallel().AsOrdered()


Where city.Population > 10000
Select city
Take (1000)

Dim finalResult = From city In orderedCities2.AsUnordered()


Join p In people.AsParallel() On city.Name Equals p.CityName
Select New With {.Name = city.Name, .Pop = city.Population, .Mayor = city.Mayor}

For Each city In finalResult


Console.WriteLine(city.Name & ":" & city.Pop & ":" & city.Mayor)
Next

Observe que PLINQ conserva el orden de una secuencia generada por operadores que imponen el orden para el
resto de la consulta. En otras palabras, los operadores de tipo OrderBy y ThenBy se tratan como si fuesen
seguidos de una llamada a AsOrdered.

Operadores de consulta y ordenación


Los siguientes operadores de consulta introducen la conservación del orden en todas las operaciones posteriores
de una consulta o hasta que se llame a AsUnordered:
OrderBy
OrderByDescending
ThenBy
ThenByDescending
En algunos casos, los siguientes operadores de consulta PLINQ pueden requerir secuencias de origen ordenadas
para generar resultados correctos:
Reverse
SequenceEqual
TakeWhile
SkipWhile
Zip
Algunos operadores de consulta PLINQ se comportan de manera diferente, dependiendo de si su secuencia de
origen está ordenada o no. En la siguiente tabla se enumeran estos operadores.

RESULTA DO C UA N DO L A SEC UEN C IA RESULTA DO C UA N DO L A SEC UEN C IA


" ??" DE O RIGEN ESTÁ O RDEN A DA DE O RIGEN N O ESTÁ O RDEN A DA

Aggregate Salida no determinista para operaciones Salida no determinista para operaciones


no asociativas o no conmutativas. no asociativas o no conmutativas.

All No aplicable No aplicable

Any No aplicable No aplicable

AsEnumerable No aplicable No aplicable

Average Salida no determinista para operaciones Salida no determinista para operaciones


no asociativas o no conmutativas. no asociativas o no conmutativas.

Cast Resultados ordenados Resultados no ordenados

Concat Resultados ordenados Resultados no ordenados

Count No aplicable No aplicable

DefaultIfEmpty No aplicable No aplicable

Distinct Resultados ordenados Resultados no ordenados

ElementAt Se devuelve el elemento especificado Elemento arbitrario

ElementAtOrDefault Se devuelve el elemento especificado Elemento arbitrario

Except Resultados no ordenados Resultados no ordenados

First Se devuelve el elemento especificado Elemento arbitrario

FirstOrDefault Se devuelve el elemento especificado Elemento arbitrario

ForAll Ejecución no determinista en paralelo Ejecución no determinista en paralelo

GroupBy Resultados ordenados Resultados no ordenados

GroupJoin Resultados ordenados Resultados no ordenados

Intersect Resultados ordenados Resultados no ordenados

Join Resultados ordenados Resultados no ordenados


RESULTA DO C UA N DO L A SEC UEN C IA RESULTA DO C UA N DO L A SEC UEN C IA
" ??" DE O RIGEN ESTÁ O RDEN A DA DE O RIGEN N O ESTÁ O RDEN A DA

Last Se devuelve el elemento especificado Elemento arbitrario

LastOrDefault Se devuelve el elemento especificado Elemento arbitrario

LongCount No aplicable No aplicable

Min No aplicable No aplicable

OrderBy Reordena la secuencia Inicia una nueva sección ordenada

OrderByDescending Reordena la secuencia Inicia una nueva sección ordenada

Range No aplicable (el mismo valor No aplicable


predeterminado que AsParallel)

Repeat No aplicable (el mismo valor No aplicable


predeterminado que AsParallel)

Reverse Invierte el orden No hace nada

Select Resultados ordenados Resultados no ordenados

Select (indexada) Resultados ordenados Resultados no ordenados.

SelectMany Resultados ordenados. Resultados no ordenados

SelectMany (indexada) Resultados ordenados. Resultados no ordenados.

SequenceEqual Comparación ordenada Comparación no ordenada

Single No aplicable No aplicable

SingleOrDefault No aplicable No aplicable

Skip Omite los primeros n elementos Omite todos los n elementos

SkipWhile Resultados ordenados. No determinista. Ejecuta SkipWhile en


el orden arbitrario actual

Sum Salida no determinista para operaciones Salida no determinista para operaciones


no asociativas o no conmutativas. no asociativas o no conmutativas.

Take Toma los n primeros elementos Toma cualquier elemento n

TakeWhile Resultados ordenados No determinista. Ejecuta TakeWhile en


el orden arbitrario actual

ThenBy Complementa OrderBy Complementa OrderBy

ThenByDescending Complementa OrderBy Complementa OrderBy


RESULTA DO C UA N DO L A SEC UEN C IA RESULTA DO C UA N DO L A SEC UEN C IA
" ??" DE O RIGEN ESTÁ O RDEN A DA DE O RIGEN N O ESTÁ O RDEN A DA

ToArray Resultados ordenados Resultados no ordenados

ToDictionary No aplicable No aplicable

ToList Resultados ordenados Resultados no ordenados

ToLookup Resultados ordenados Resultados no ordenados

Union Resultados ordenados Resultados no ordenados

Where Resultados ordenados Resultados no ordenados

Where (indexada) Resultados ordenados Resultados no ordenados

Zip Resultados ordenados Resultados no ordenados

Los resultados desordenados no se ordenan aleatoriamente de forma activa; simplemente no se les aplica
ninguna lógica de ordenación especial. En algunos casos, una consulta desordenada puede conservar el orden de
la secuencia de origen. En las consultas que usan el operador Select indizado, PLINQ garantiza que los elementos
de salida aparecerán en el orden de los índices ascendentes, pero no ofrece ninguna garantía sobre qué índices se
asignarán a qué elementos.

Vea también
Parallel LINQ (PLINQ)
Programación en paralelo
Opciones de fusión mediante combinación en PLINQ
16/09/2020 • 7 minutes to read • Edit Online

Cuando una consulta se ejecuta en paralelo, PLINQ crea particiones de la secuencia de origen para que varios
subprocesos puedan funcionar en diferentes partes al mismo tiempo, por lo general en subprocesos
independientes. Si los resultados se van a usar en un subproceso, por ejemplo, en un bucle foreach ( For Each en
Visual Basic), los resultados de cada subproceso deben volver a combinarse en una secuencia. El tipo de
combinación que PLINQ realiza depende de los operadores que están presentes en la consulta. Por ejemplo, los
operadores que imponen un nuevo orden de los resultados deben almacenar en búfer todos los elementos de
todos los subprocesos. Desde la perspectiva del subproceso utilizado (que también es el del usuario de la
aplicación), una consulta totalmente almacenada en búfer podría ejecutarse durante un período de tiempo
considerable antes de generar su primer resultado. Otros operadores, de forma predeterminada, están
parcialmente almacenados en búfer; producen sus resultados en lotes. Un operador, ForAll, no se almacena en
búfer de forma predeterminada. Genera inmediatamente todos los elementos de todos los subprocesos.
Mediante el uso del método WithMergeOptions, como se muestra en el ejemplo siguiente, puede proporcionar
una sugerencia a PLINQ que indica qué tipo de combinación se debe llevar a cabo.

var scanLines = from n in nums.AsParallel()


.WithMergeOptions(ParallelMergeOptions.NotBuffered)
where n % 2 == 0
select ExpensiveFunc(n);

Dim scanlines = From n In nums.AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered)


Where n Mod 2 = 0
Select ExpensiveFunc(n)

Para obtener el ejemplo completo, vea Cómo: Especificar opciones de fusión mediante combinación en PLINQ.
Si la consulta determinada no puede admitir la opción solicitada, simplemente se omitirá la opción. En la mayoría
de los casos, no es necesario especificar una opción de combinación para una consulta PLINQ. Sin embargo, en
algunos casos puede observar mediante pruebas y mediciones que una consulta se ejecuta mejor en un modo no
predeterminado. Un uso común de esta opción es forzar a un operador de combinación de fragmentos a
transmitir por secuencias sus resultados con el fin de proporcionar una interfaz de usuario más dinámica.

ParallelMergeOptions
La enumeración ParallelMergeOptions incluye las siguientes opciones que especifican, para las formas de consulta
compatibles, cómo se produjo el resultado final de la consulta cuando se usan los resultados en un subproceso:
Not Buffered

La opción NotBuffered hace que cada elemento procesado devuelva cada subproceso en cuanto se
produzca. Este comportamiento es análogo a la salida "streaming". Si el operador AsOrdered está presente
en la consulta, NotBuffered conserva el orden de los elementos de origen. Aunque NotBuffered empieza a
producir resultados en cuanto están disponibles, el tiempo total para generar todos los resultados puede
ser más largo con respecto al uso de alguna de las demás opciones de combinación.
Auto Buffered
La opción AutoBuffered hace que la consulta recopile los elementos en un búfer y, a continuación,
proporcionará periódicamente el contenido del búfer a la vez para el subproceso utilizado. Esto es análogo
a producir los datos de origen en "fragmentos" en lugar de usar el comportamiento de "streaming" de
NotBuffered . AutoBuffered puede tardar más que NotBuffered en habilitar el primer elemento en el
subproceso utilizado. El tamaño del búfer y el comportamiento productivo exacto no se pueden configurar
y pueden variar en función de varios factores relacionados con la consulta.
FullyBuffered

La opción FullyBuffered hace que el resultado de la consulta completa se almacene en búfer antes de que
se produzca cualquiera de los elementos. Al usar esta opción, puede tardar más tiempo antes de que el
primer elemento esté disponible en el subproceso utilizado, pero los resultados completos pueden
producirse más rápido en comparación con el uso de otras opciones.

Operadores de consulta que admiten opciones de combinación


En la tabla siguiente se enumeran los operadores que admiten todos los modos de opción de combinación,
sujetos a las restricciones especificadas.

O P ERA DO R REST RIC C IO N ES

AsEnumerable None

Cast None

Concat Consultas no ordenadas que tienen solo un origen de matriz


o lista.

DefaultIfEmpty None

OfType None

Reverse Consultas no ordenadas que tienen solo un origen de matriz


o lista.

Select None

SelectMany None

Skip None

Take None

Where None

Todos los demás operadores de consulta PLINQ podrían omitir opciones de combinación proporcionadas por el
usuario. Algunos operadores de consulta, por ejemplo Reverse y OrderBy, no pueden proporcionar todos los
elementos hasta que no se hayan producido y reordenado. Por lo tanto, cuando se utiliza ParallelMergeOptions en
una consulta que también contiene un operador como Reverse, el comportamiento de combinación no se aplicará
en la consulta hasta después de que el operador genere sus resultados.
La capacidad de algunos operadores para controlar las opciones de combinación depende del tipo de la secuencia
de origen y de si el operador AsOrdered se usó anteriormente en la consulta. ForAll siempre es NotBuffered;
produce inmediatamente sus elementos. OrderBy siempre es FullyBuffered; debe ordenar toda la lista antes de
producirla.

Vea también
Parallel LINQ (PLINQ)
Cómo: Especificar opciones de fusión mediante combinación en PLINQ
Posibles problemas con PLINQ
16/09/2020 • 11 minutes to read • Edit Online

En muchos casos, PLINQ puede proporcionar importantes mejoras de rendimiento con respecto a las consultas
secuenciales LINQ to Objects. Sin embargo, el trabajo de paralelizar la ejecución de consultas aporta una
complejidad que puede conducir a problemas que, en código secuencial, no son tan comunes o no se producen en
ningún caso. En este tema se indican algunas prácticas que se deben evitar al escribir consultas PLINQ.

No suponer que la ejecución en paralelo siempre es más rápida


En ocasiones, la paralelización hace que una consulta PLINQ se ejecute más lentamente que su equivalente LINQ to
Objects. La regla de oro básica es que es poco probable que las consultas que tienen pocos elementos de origen y
delegados de usuario rápidos se agilicen demasiado. Sin embargo, dado que hay muchos factores que afectan al
rendimiento, se recomienda medir los resultados reales antes de decidir si usar PLINQ. Para más información,
consulte Introducción a la velocidad en PLINQ.

Evitar la escritura en ubicaciones de memoria compartida


En código secuencial, no es raro leer o escribir en variables estáticas o campos de clase. Sin embargo, cada vez que
varios subprocesos tienen acceso simultáneamente a estas variables, hay grandes posibilidades de que se
produzcan condiciones de carrera. Aunque se pueden usar bloqueos para sincronizar el acceso a la variable, el
costo de la sincronización puede afectar negativamente al rendimiento. Por tanto, se recomienda evitar, o al menos
limitar, el acceso al estado compartido en una consulta PLINQ en la medida de lo posible.

Evitar la paralelización excesiva


Si usa el método AsParallel , incurrirá en costos de sobrecarga al crear particiones de la colección de origen y
sincronizar los subprocesos de trabajo. El número de procesadores del equipo reduce también las ventajas de la
paralelización. Si se ejecutan varios subprocesos enlazados a cálculos en un único procesador, no se gana en
velocidad. Por tanto, debe tener cuidado para no paralelizar en exceso una consulta.
El escenario más común en el que se puede producir un exceso de paralelización son las consultas anidadas, como
se muestra en el siguiente fragmento de código.

var q = from cust in customers.AsParallel()


from order in cust.Orders.AsParallel()
where order.OrderDate > date
select new { cust, order };

Dim q = From cust In customers.AsParallel()


From order In cust.Orders.AsParallel()
Where order.OrderDate > aDate
Select New With {cust, order}

En este caso, es mejor paralelizar únicamente el origen de datos exterior (clientes), a menos que se cumplan una o
varias de las siguientes condiciones:
Se sabe que el origen de datos interno (cust.Orders) es muy largo.
Se realiza un cálculo costoso en cada pedido (la operación que se muestra en el ejemplo no es costosa).
Se sabe que el sistema de destino tiene suficientes procesadores como para controlar el número de
subprocesos que se producirán al paralelizar la consulta de cust.Orders .
En todos los casos, la mejor manera de determinar la forma óptima de la consulta es mediante la prueba y la
medición. Para obtener más información, vea Cómo: Medir el rendimiento de consultas PLINQ.

Evitar llamadas a métodos que no son seguros para subprocesos


La escritura en métodos de instancia que no son seguros para subprocesos de una consulta PLINQ puede producir
daños en los datos, que pueden pasar o no inadvertidos para el programa. También puede dar lugar a excepciones.
En el siguiente ejemplo, varios subprocesos estarían intentando llamar simultáneamente al método
FileStream.Write , lo que no se admite en la clase.

Dim fs As FileStream = File.OpenWrite(…)


a.AsParallel().Where(...).OrderBy(...).Select(...).ForAll(Sub(x) fs.Write(x))

FileStream fs = File.OpenWrite(...);
a.AsParallel().Where(...).OrderBy(...).Select(...).ForAll(x => fs.Write(x));

Limitar las llamadas a métodos seguros para subprocesos


La mayoría de los métodos estáticos de .NET Framework son seguros para subprocesos y se les puede llamar
simultáneamente desde varios subprocesos. Sin embargo, incluso en estos casos, la sincronización que esto
supone puede conducir a una ralentización importante en la consulta.

NOTE
Puede comprobarlo si inserta algunas llamadas a WriteLine en las consultas. Aunque este método se usa en los ejemplos de
la documentación para fines de demostración, no debe usarlo en consultas PLINQ.

Evitar operaciones de ordenación innecesarias


Cuando PLINQ ejecuta una consulta en paralelo, divide la secuencia de origen en particiones que pueden funcionar
simultáneamente en varios subprocesos. De forma predeterminada, el orden en el que se procesan las particiones
y se entregan los resultados no es predecible (excepto para operadores como OrderBy ). Puede indicar a PLINQ que
conserve el orden de cualquier secuencia de origen, pero esto tiene un impacto negativo en el rendimiento. El
procedimiento recomendado, siempre que sea posible, es estructurar las consultas para que no dependan de la
conservación del orden. Para más información, consulte cómo conservar el orden en PLINQ.

Preferir ForAll en lugar de ForEach cuando sea posible


Aunque PLINQ ejecuta una consulta en varios subprocesos, si utiliza los resultados en un bucle foreach ( For Each
en Visual Basic), los resultados de la consulta se deben volver a combinar en un subproceso y el enumerador debe
acceder a ellos en serie. En algunos casos, esto es inevitable; sin embargo, siempre que sea posible, utilice el
método ForAll para habilitar cada subproceso para generar sus propios resultados, por ejemplo, al escribir en
una colección segura para subprocesos como System.Collections.Concurrent.ConcurrentBag<T>.
Este mismo problema es aplicable a Parallel.ForEach. En otras palabras, source.AsParallel().Where().ForAll(...)
debe tener máxima prioridad con respecto a Parallel.ForEach(source.AsParallel().Where(), ...) .

Tener en cuenta los problemas de afinidad de los subprocesos


Algunas tecnologías, como la interoperabilidad COM para componentes de contenedor uniproceso (STA), Windows
Forms y Windows Presentation Foundation (WPF), imponen restricciones de afinidad de subprocesos que exigen
que el código se ejecute en un subproceso determinado. Por ejemplo, tanto en Windows Forms como en WPF, solo
se puede tener acceso a un control en el subproceso donde se creó. Si intenta tener acceso al estado compartido de
un control de formularios Windows Forms en una consulta PLINQ, se produce una excepción si se ejecuta en el
depurador. (Esta opción puede desactivarse). Sin embargo, si la consulta se usa en el subproceso de interfaz de
usuario, entonces puede acceder al control del bucle foreach que enumera los resultados de la consulta, ya que el
código se ejecuta en un solo subproceso.

No suponer que las iteraciones de ForEach, For y ForAll siempre se


ejecutan en paralelo
Es importante tener en cuenta que las iteraciones individuales de un bucle Parallel.For, Parallel.ForEach o ForAll
pueden ejecutarse en paralelo, pero no tiene que ser así necesariamente. Por consiguiente, se debe evitar escribir
código cuya exactitud dependa de la ejecución en paralelo de las iteraciones o de la ejecución de las iteraciones en
algún orden concreto.
Por ejemplo, es probable que este código lleve a un interbloqueo:

Dim mre = New ManualResetEventSlim()


Enumerable.Range(0, Environment.ProcessorCount * 100).AsParallel().ForAll(Sub(j)
If j = Environment.ProcessorCount Then
Console.WriteLine("Set on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j)
mre.Set()
Else
Console.WriteLine("Waiting on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j)
mre.Wait()
End If
End Sub) ' deadlocks

ManualResetEventSlim mre = new ManualResetEventSlim();


Enumerable.Range(0, Environment.ProcessorCount * 100).AsParallel().ForAll((j) =>
{
if (j == Environment.ProcessorCount)
{
Console.WriteLine("Set on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j);
mre.Set();
}
else
{
Console.WriteLine("Waiting on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j);
mre.Wait();
}
}); //deadlocks

En este ejemplo, una iteración establece un evento y el resto de las iteraciones esperan el evento. Ninguna de las
iteraciones que esperan puede completarse hasta que se haya completado la iteración del valor de evento. Sin
embargo, es posible que las iteraciones que esperan bloqueen todos los subprocesos que se utilizan para ejecutar
el bucle paralelo antes de que la iteración del valor de evento haya tenido oportunidad de ejecutarse. Esto produce
un interbloqueo: la iteración del valor de evento nunca se ejecutará y las iteraciones que esperan nunca se
activarán.
En concreto, una iteración de un bucle paralelo no debe esperar nunca otra iteración del bucle para progresar. Si el
bucle paralelo decide programar las iteraciones secuencialmente pero en el orden contrario, se producirá un
interbloqueo.
Vea también
Parallel LINQ (PLINQ)
Procedimiento para crear y ejecutar una consulta
PLINQ simple
16/09/2020 • 3 minutes to read • Edit Online

En el ejemplo de este artículo se muestra cómo crear una consulta sencilla de Parallel Language Integrated Query
(LINQ); para ello, se utiliza el método de extensión ParallelEnumerable.AsParallel en la secuencia de origen y se
ejecuta la consulta con el método ForAll.

NOTE
En esta documentación, se utilizan expresiones lambda para definir delegados en PLINQ. Si no está familiarizado con las
expresiones lambda de C# o Visual Basic, consulte Expresiones lambda en PLINQ y TPL.

Ejemplo
using System;
using System.Linq;

public class Example


{
public static void Main()
{
var source = Enumerable.Range(100, 20000);

// Result sequence might be out of order.


var parallelQuery = from num in source.AsParallel()
where num % 10 == 0
select num;

// Process result sequence in parallel


parallelQuery.ForAll((e) => DoSomething(e));

// Or use foreach to merge results first.


foreach (var n in parallelQuery) {
Console.WriteLine(n);
}

// You can also use ToArray, ToList, etc as with LINQ to Objects.
var parallelQuery2 = (from num in source.AsParallel()
where num % 10 == 0
select num).ToArray();

// Method syntax is also supported


var parallelQuery3 = source.AsParallel().Where(n => n % 10 == 0).Select(n => n);

Console.WriteLine("\nPress any key to exit...");


Console.ReadLine();
}

static void DoSomething(int i) { }


}
Imports System.Linq

Module Example
Public Sub Main()
Dim source = Enumerable.Range(100, 20000)

' Result sequence might be out of order.


Dim parallelQuery = From num In source.AsParallel()
Where num Mod 10 = 0
Select num

' Process result sequence in parallel


parallelQuery.ForAll(Sub(e)
DoSomething(e)
End Sub)

' Or use For Each to merge results first


' as in this example, Where results must
' be serialized sequentially through static Console method.
For Each n In parallelQuery
Console.Write("{0} ", n)
Next

' You can also use ToArray, ToList, etc, as with LINQ to Objects.
Dim parallelQuery2 = (From num In source.AsParallel()
Where num Mod 10 = 0
Select num).ToArray()

' Method syntax is also supported


Dim parallelQuery3 = source.AsParallel().Where(Function(n)
Return (n Mod 10) = 0
End Function).Select(Function(n)
Return n
End Function)

For Each i As Integer In parallelQuery3


Console.Write("{0} ", i)
Next

Console.WriteLine()
Console.WriteLine("Press any key to exit...")
Console.ReadLine()
End Sub

' A toy function to demonstrate syntax. Typically you need a more


' computationally expensive method to see speedup over sequential queries.
Sub DoSomething(ByVal i As Integer)
Console.Write("{0:###.## }", Math.Sqrt(i))
End Sub
End Module

En este ejemplo se muestra el patrón básico para la creación y ejecución de cualquier consulta Parallel LINQ cuando
el orden de la secuencia de resultados no importa. Por lo general, las consultas desordenadas son más rápidas que
las ordenadas. La consulta crea particiones del origen, dividiéndolo en tareas que se ejecutan de forma asincrónica
en varios subprocesos. El orden en el que se completa cada tarea no depende solo de la cantidad de trabajo
necesario para procesar los elementos de la partición, sino también de factores externos como, por ejemplo, el
modo en que el sistema operativo programa cada uno de los subprocesos. La finalidad de este ejemplo es mostrar
el uso, y puede que su ejecución no sea tan rápida como la de la consulta LINQ to Objects secuencial equivalente.
Para más información sobre la velocidad, vea Introducción a la velocidad en PLINQ. Para obtener más información
sobre cómo mantener el orden de los elementos de una consulta, vea Procedimiento para controlar la ordenación
en una consulta PLINQ.
Vea también
Parallel LINQ (PLINQ)
Cómo: Controlar la ordenación en una consulta
PLINQ
16/09/2020 • 4 minutes to read • Edit Online

En estos ejemplos se muestra cómo controlar la ordenación de una consulta PLINQ con el método de extensión
AsOrdered.

WARNING
La finalidad principal de estos ejemplos es mostrar el uso, y puede que su ejecución sea o no tan rápida como la de las
consultas LINQ to Objects secuenciales equivalentes.

Ejemplo
En el ejemplo siguiente se conserva el orden de la secuencia de origen. Esto a veces es necesario; por ejemplo,
algunos operadores de consulta requieren una secuencia de origen ordenada para generar resultados correctos.

var source = Enumerable.Range(9, 10000);

// Source is ordered; let's preserve it.


var parallelQuery = from num in source.AsParallel().AsOrdered()
where num % 3 == 0
select num;

// Use foreach to preserve order at execution time.


foreach (var v in parallelQuery)
Console.Write("{0} ", v);

// Some operators expect an ordered source sequence.


var lowValues = parallelQuery.Take(10);

Sub OrderedQuery()

Dim source = Enumerable.Range(9, 10000)

' Source is ordered let's preserve it.


Dim parallelQuery = From num In source.AsParallel().AsOrdered()
Where num Mod 3 = 0
Select num

' Use For Each to preserve order at execution time.


For Each item In parallelQuery
Console.Write("{0} ", item)
Next

' Some operators expect an ordered source sequence.


Dim lowValues = parallelQuery.Take(10)

End Sub

Ejemplo
En el ejemplo siguiente se muestran algunos operadores de consulta cuya secuencia de origen probablemente se
espera que esté ordenada. Estos operadores pueden funcionar en secuencias no ordenadas, pero podrían producir
resultados inesperados.

// Paste into PLINQDataSample class.


static void SimpleOrdering()
{

var customers = GetCustomers();

// Take the first 20, preserving the original order


var firstTwentyCustomers = customers
.AsParallel()
.AsOrdered()
.Take(20);

foreach (var c in firstTwentyCustomers)


Console.Write("{0} ", c.CustomerID);

// All elements in reverse order.


var reverseOrder = customers
.AsParallel()
.AsOrdered()
.Reverse();

foreach (var v in reverseOrder)


Console.Write("{0} ", v.CustomerID);

// Get the element at a specified index.


var cust = customers.AsParallel()
.AsOrdered()
.ElementAt(48);

Console.WriteLine("Element #48 is: {0}", cust.CustomerID);


}
' Paste into PLINQDataSample class
Shared Sub SimpleOrdering()
Dim customers As List(Of Customer) = GetCustomers().ToList()

' Take the first 20, preserving the original order

Dim firstTwentyCustomers = customers _


.AsParallel() _
.AsOrdered() _
.Take(20)

Console.WriteLine("Take the first 20 in original order")


For Each c As Customer In firstTwentyCustomers
Console.Write(c.CustomerID & " ")
Next

' All elements in reverse order.


Dim reverseOrder = customers _
.AsParallel() _
.AsOrdered() _
.Reverse()

Console.WriteLine(vbCrLf & "Take all elements in reverse order")


For Each c As Customer In reverseOrder
Console.Write("{0} ", c.CustomerID)
Next
' Get the element at a specified index.
Dim cust = customers.AsParallel() _
.AsOrdered() _
.ElementAt(48)

Console.WriteLine("Element #48 is: " & cust.CustomerID)

End Sub

Para ejecutar este método, péguelo en la clase PLINQDataSample en el ejemplo de datos de PLINQ del proyecto y
presione F5.

Ejemplo
En el ejemplo siguiente se muestra cómo se conserva el orden de la primera parte de una consulta, después se
quita el orden para aumentar el rendimiento de una cláusula de combinación y, por último, se vuelve a aplicar el
orden de la secuencia del resultado final.
// Paste into PLINQDataSample class.
static void OrderedThenUnordered()
{

var orders = GetOrders();


var orderDetails = GetOrderDetails();

var q2 = orders.AsParallel()
.Where(o => o.OrderDate < DateTime.Parse("07/04/1997"))
.Select(o => o)
.OrderBy(o => o.CustomerID) // Preserve original ordering for Take operation.
.Take(20)
.AsUnordered() // Remove ordering constraint to make join faster.
.Join(
orderDetails.AsParallel(),
ord => ord.OrderID,
od => od.OrderID,
(ord, od) =>
new
{
ID = ord.OrderID,
Customer = ord.CustomerID,
Product = od.ProductID
}
)
.OrderBy(i => i.Product); // Apply new ordering to final result sequence.

foreach (var v in q2)


Console.WriteLine("{0} {1} {2}", v.ID, v.Customer, v.Product);
}

' Paste into PLINQDataSample class


Sub OrderedThenUnordered()
Dim Orders As IEnumerable(Of Order) = GetOrders()
Dim orderDetails As IEnumerable(Of OrderDetail) = GetOrderDetails()

' Sometimes it's easier to create a query


' by composing two subqueries
Dim query1 = From ord In Orders.AsParallel()
Where ord.OrderDate < DateTime.Parse("07/04/1997")
Select ord
Order By ord.CustomerID
Take 20

Dim query2 = From ord In query1.AsUnordered()


Join od In orderDetails.AsParallel() On ord.OrderID Equals od.OrderID
Order By od.ProductID
Select New With {ord.OrderID, ord.CustomerID, od.ProductID}

For Each item In query2


Console.WriteLine("{0} {1} {2}", item.OrderID, item.CustomerID, item.ProductID)
Next
End Sub

Para ejecutar este método, péguelo en la clase PLINQDataSample en el ejemplo de datos de PLINQ del proyecto y
presione F5.

Vea también
ParallelEnumerable
Parallel LINQ (PLINQ)
Procedimiento para combinar consultas LINQ
paralelas y secuenciales
16/09/2020 • 2 minutes to read • Edit Online

Este ejemplo muestra cómo utilizar el método AsSequential para indicar a PLINQ que procese secuencialmente
todos los operadores subsiguientes en la consulta. Aunque el procesamiento secuencial suele ser más lento que el
paralelo, a veces es necesario para generar resultados correctos.

NOTE
La finalidad de este ejemplo es mostrar el uso, y puede que su ejecución no sea tan rápida como la de la consulta LINQ to
Objects secuencial equivalente. Para más información sobre la velocidad, vea Introducción a la velocidad en PLINQ.

Ejemplo
En el ejemplo siguiente se muestra un escenario en el que AsSequential es necesario, concretamente, para
conservar el orden que se estableció en una cláusula de la consulta anterior.

// Paste into PLINQDataSample class.


static void SequentialDemo()
{
var orders = GetOrders();
var query = (from ord in orders.AsParallel()
orderby ord.CustomerID
select new
{
Details = ord.OrderID,
Date = ord.OrderDate,
Shipped = ord.ShippedDate
}).
AsSequential().Take(5);
}

' Paste into PLINQDataSample class


Shared Sub SequentialDemo()

Dim orders = GetOrders()


Dim query = From ord In orders.AsParallel()
Order By ord.CustomerID
Select New With
{
ord.OrderID,
ord.OrderDate,
ord.ShippedDate
}

Dim query2 = query.AsSequential().Take(5)

For Each item In query2


Console.WriteLine("{0}, {1}, {2}", item.OrderDate, item.OrderID, item.ShippedDate)
Next
End Sub
Compilar el código
Para compilar y ejecutar este código, péguelo en el proyecto de ejemplo de datos PLINQ, agregue una línea para
llamar al método desde Main y presione F5 .

Vea también
Parallel LINQ (PLINQ)
Cómo: Controlar excepciones en una consulta PLINQ
16/09/2020 • 6 minutes to read • Edit Online

El primer ejemplo de este tema muestra cómo controlar la clase System.AggregateException que se puede iniciar
desde una consulta PLINQ cuando se ejecuta. El segundo ejemplo muestra cómo colocar bloques try-catch dentro
de los delegados, lo más cerca posible de donde se producirá la excepción. De este modo, se pueden detectar en
cuanto se producen y posiblemente continuar con la ejecución de la consulta. Cuando las excepciones pueden
propagarse de nuevo al subproceso de unión, es posible que una consulta continúe procesando algunos
elementos después de que se haya producido la excepción.
En algunos casos, cuando PLINQ vuelve a realizar la ejecución por secuencias y cuando se produce una excepción,
esta puede propagarse directamente y no ajustarse en una excepción AggregateException. Además, las
excepciones ThreadAbortException siempre se propagan directamente.

NOTE
Cuando está habilitada la opción "Solo mi código", Visual Studio se interrumpe en la línea que produce la excepción y
muestra el mensaje de error "Excepción no controlada por el código de usuario". Este error es benigno. Puede presionar F5
para continuar y ver el comportamiento de control de excepciones que se muestra en estos ejemplos. Para evitar que Visual
Studio se interrumpa con el primer error, desactive la casilla "Solo mi código" en Herramientas, Opciones, Depurar,
General.
La finalidad de este ejemplo es mostrar el uso, y puede que su ejecución no sea tan rápida como la de la consulta LINQ to
Objects secuencial equivalente. Para más información sobre la velocidad, vea Introducción a la velocidad en PLINQ.

Ejemplo
Este ejemplo muestra cómo colocar los bloques try-catch alrededor del código que ejecuta la consulta para
detectar cualquier excepción System.AggregateException que se produzca.
// Paste into PLINQDataSample class.
static void PLINQExceptions_1()
{
// Using the raw string array here. See PLINQ Data Sample.
string[] customers = GetCustomersAsStrings().ToArray();

// First, we must simulate some currupt input.


customers[54] = "###";

var parallelQuery = from cust in customers.AsParallel()


let fields = cust.Split(',')
where fields[3].StartsWith("C") //throw indexoutofrange
select new { city = fields[3], thread = Thread.CurrentThread.ManagedThreadId };
try
{
// We use ForAll although it doesn't really improve performance
// since all output is serialized through the Console.
parallelQuery.ForAll(e => Console.WriteLine("City: {0}, Thread:{1}", e.city, e.thread));
}

// In this design, we stop query processing when the exception occurs.


catch (AggregateException e)
{
foreach (var ex in e.InnerExceptions)
{
Console.WriteLine(ex.Message);
if (ex is IndexOutOfRangeException)
Console.WriteLine("The data source is corrupt. Query stopped.");
}
}
}

' Paste into PLINQDataSample class


Shared Sub PLINQExceptions_1()

' Using the raw string array here. See PLINQ Data Sample.
Dim customers As String() = GetCustomersAsStrings().ToArray()

' First, we must simulate some currupt input.


customers(20) = "###"

'throws indexoutofrange
Dim query = From cust In customers.AsParallel() _
Let fields = cust.Split(","c) _
Where fields(3).StartsWith("C") _
Select fields
Try
' We use ForAll although it doesn't really improve performance
' since all output is serialized through the Console.
query.ForAll(Sub(e)
Console.WriteLine("City: {0}, Thread:{1}")
End Sub)
Catch e As AggregateException

' In this design, we stop query processing when the exception occurs.
For Each ex In e.InnerExceptions
Console.WriteLine(ex.Message)
If TypeOf ex Is IndexOutOfRangeException Then
Console.WriteLine("The data source is corrupt. Query stopped.")
End If
Next
End Try
End Sub

En este ejemplo, la consulta no puede continuar después de que se produce la excepción. En el momento en que el
código de la aplicación detecta la excepción, PLINQ ya ha detenido la consulta en todos los subprocesos.

Ejemplo
En el ejemplo siguiente se muestra cómo colocar un bloque try-catch en un delegado para que sea posible
detectar una excepción y continuar con la ejecución de la consulta.

// Paste into PLINQDataSample class.


static void PLINQExceptions_2()
{

var customers = GetCustomersAsStrings().ToArray();


// Using the raw string array here.
// First, we must simulate some currupt input
customers[54] = "###";

// Create a delegate with a lambda expression.


// Assume that in this app, we expect malformed data
// occasionally and by design we just report it and continue.
Func<string[], string, bool> isTrue = (f, c) =>
{
try
{
string s = f[3];
return s.StartsWith(c);
}
catch (IndexOutOfRangeException e)
{
Console.WriteLine("Malformed cust: {0}", f);
return false;
}
};

// Using the raw string array here


var parallelQuery = from cust in customers.AsParallel()
let fields = cust.Split(',')
where isTrue(fields, "C") //use a named delegate with a try-catch
select new { city = fields[3] };
try
{
// We use ForAll although it doesn't really improve performance
// since all output must be serialized through the Console.
parallelQuery.ForAll(e => Console.WriteLine(e.city));
}

// IndexOutOfRangeException will not bubble up


// because we handle it where it is thrown.
catch (AggregateException e)
{
foreach (var ex in e.InnerExceptions)
Console.WriteLine(ex.Message);
}
}
' Paste into PLINQDataSample class
Shared Sub PLINQExceptions_2()

Dim customers() = GetCustomersAsStrings().ToArray()


' Using the raw string array here.
' First, we must simulate some currupt input
customers(20) = "###"

' Create a delegate with a lambda expression.


' Assume that in this app, we expect malformed data
' occasionally and by design we just report it and continue.
Dim isTrue As Func(Of String(), String, Boolean) = Function(f, c)

Try

Dim s As String = f(3)


Return s.StartsWith(c)

Catch e As IndexOutOfRangeException

Console.WriteLine("Malformed cust: {0}", f)


Return False
End Try
End Function

' Using the raw string array here


Dim query = From cust In customers.AsParallel()
Let fields = cust.Split(","c)
Where isTrue(fields, "C")
Select New With {.City = fields(3)}
Try
' We use ForAll although it doesn't really improve performance
' since all output must be serialized through the Console.
query.ForAll(Sub(e) Console.WriteLine(e.city))

' IndexOutOfRangeException will not bubble up


' because we handle it where it is thrown.
Catch e As AggregateException
For Each ex In e.InnerExceptions
Console.WriteLine(ex.Message)
Next
End Try
End Sub

Compilar el código
Para compilar y ejecutar estos ejemplos, cópielos en el ejemplo de datos de PLINQ y llame al método desde
Main.

Programación sólida
No se detecta ninguna excepción a menos que sepa cómo controlarla para que no se dañe el estado del programa.

Vea también
ParallelEnumerable
Parallel LINQ (PLINQ)
Procedimiento para cancelar una consulta PLINQ
16/09/2020 • 9 minutes to read • Edit Online

En los siguientes ejemplos se muestran dos formas de cancelar una consulta PLINQ. El primer ejemplo muestra
cómo cancelar una consulta que se compone principalmente de un cruce seguro de datos. El segundo ejemplo
muestra cómo cancelar una consulta que contiene una función de usuario que es cara desde el punto de vista
computacional.

NOTE
Cuando está habilitada la opción "Solo mi código", Visual Studio se interrumpe en la línea que produce la excepción y
muestra el mensaje de error "Excepción no controlada por el código de usuario". Este error es benigno. Puede presionar F5
para continuar y ver el comportamiento de control de excepciones que se muestra en estos ejemplos. Para evitar que Visual
Studio se interrumpa con el primer error, desactive la casilla "Solo mi código" en Herramientas, Opciones, Depurar,
General.
La finalidad de este ejemplo es mostrar el uso, y puede que su ejecución no sea tan rápida como la de la consulta LINQ to
Objects secuencial equivalente. Para más información sobre la velocidad, vea Introducción a la velocidad en PLINQ.

Ejemplo
namespace PLINQCancellation_1
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;

class Program
{
static void Main(string[] args)
{
int[] source = Enumerable.Range(1, 10000000).ToArray();
var cts = new CancellationTokenSource();

// Start a new asynchronous task that will cancel the


// operation from another thread. Typically you would call
// Cancel() in response to a button click or some other
// user interface event.
Task.Factory.StartNew(() =>
{
UserClicksTheCancelButton(cts);
});

int[] results = null;


try
{
results = (from num in source.AsParallel().WithCancellation(cts.Token)
where num % 3 == 0
orderby num descending
select num).ToArray();
}
catch (OperationCanceledException e)
{
WriteLine(e.Message);
}
catch (AggregateException ae)
catch (AggregateException ae)
{
if (ae.InnerExceptions != null)
{
foreach (Exception e in ae.InnerExceptions)
WriteLine(e.Message);
}
}
finally
{
cts.Dispose();
}

if (results != null)
{
foreach (var v in results)
WriteLine(v);
}
WriteLine();
ReadKey();
}

static void UserClicksTheCancelButton(CancellationTokenSource cts)


{
// Wait between 150 and 500 ms, then cancel.
// Adjust these values if necessary to make
// cancellation fire while query is still executing.
Random rand = new Random();
Thread.Sleep(rand.Next(150, 500));
cts.Cancel();
}
}
}
Class Program
Private Shared Sub Main(ByVal args As String())
Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()
Dim cs As New CancellationTokenSource()

' Start a new asynchronous task that will cancel the


' operation from another thread. Typically you would call
' Cancel() in response to a button click or some other
' user interface event.
Task.Factory.StartNew(Sub()
UserClicksTheCancelButton(cs)
End Sub)

Dim results As Integer() = Nothing


Try

results = (From num In source.AsParallel().WithCancellation(cs.Token) _


Where num Mod 3 = 0 _
Order By num Descending _
Select num).ToArray()
Catch e As OperationCanceledException

Console.WriteLine(e.Message)
Catch ae As AggregateException

If ae.InnerExceptions IsNot Nothing Then


For Each e As Exception In ae.InnerExceptions
Console.WriteLine(e.Message)
Next
End If
Finally
cs.Dispose()
End Try

If results IsNot Nothing Then


For Each item In results
Console.WriteLine(item)
Next
End If
Console.WriteLine()

Console.ReadKey()
End Sub

Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)


' Wait between 150 and 500 ms, then cancel.
' Adjust these values if necessary to make
' cancellation fire while query is still executing.
Dim rand As New Random()
Thread.Sleep(rand.[Next](150, 350))
cs.Cancel()
End Sub
End Class

El marco PLINQ no revierte una clase OperationCanceledException única en System.AggregateException;


OperationCanceledException debe controlarse en un bloque de filtrado independiente. Si uno o varios de los
delegados de usuario produce una clase OperationCanceledException(externalCT) (mediante el uso de una
referencia externa System.Threading.CancellationToken), pero ninguna otra excepción, y la consulta se ha definido
como AsParallel().WithCancellation(externalCT) , a continuación, PLINQ emitirá una única clase
OperationCanceledException (externalCT) en lugar de una clase System.AggregateException. Sin embargo, si un
usuario delegado inicia una clase OperationCanceledException y otro delegado inicia otro tipo de excepción,
ambas excepciones se propagarán a AggregateException.
Las instrucciones generales sobre la cancelación son las siguientes:
1. Si realiza la cancelación de un delegado de usuario, debe informar a PLINQ sobre la clase externa
CancellationToken e iniciar OperationCanceledException (externalCT).
2. Si se produce la cancelación y no se inicia ninguna otra excepción, controle un elemento
OperationCanceledException en lugar de uno AggregateException.

Ejemplo
En el ejemplo siguiente se muestra cómo controlar la cancelación cuando se tiene una función cara desde el punto
de vista computacional en el código de usuario.

namespace PLINQCancellation_2
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;

class Program
{
static void Main(string[] args)
{
int[] source = Enumerable.Range(1, 10000000).ToArray();
var cts = new CancellationTokenSource();

// Start a new asynchronous task that will cancel the


// operation from another thread. Typically you would call
// Cancel() in response to a button click or some other
// user interface event.
Task.Factory.StartNew(() =>
{
UserClicksTheCancelButton(cts);
});

double[] results = null;


try
{
results = (from num in source.AsParallel().WithCancellation(cts.Token)
where num % 3 == 0
select Function(num, cts.Token)).ToArray();
}
catch (OperationCanceledException e)
{
WriteLine(e.Message);
}
catch (AggregateException ae)
{
if (ae.InnerExceptions != null)
{
foreach (Exception e in ae.InnerExceptions)
WriteLine(e.Message);
}
}
finally
{
cts.Dispose();
}

if (results != null)
{
foreach (var v in results)
WriteLine(v);
}
WriteLine();
ReadKey();
ReadKey();
}

// A toy method to simulate work.


static double Function(int n, CancellationToken ct)
{
// If work is expected to take longer than 1 ms
// then try to check cancellation status more
// often within that work.
for (int i = 0; i < 5; i++)
{
// Work hard for approx 1 millisecond.
Thread.SpinWait(50000);

// Check for cancellation request.


ct.ThrowIfCancellationRequested();
}
// Anything will do for our purposes.
return Math.Sqrt(n);
}

static void UserClicksTheCancelButton(CancellationTokenSource cts)


{
// Wait between 150 and 500 ms, then cancel.
// Adjust these values if necessary to make
// cancellation fire while query is still executing.
Random rand = new Random();
Thread.Sleep(rand.Next(150, 500));
WriteLine("Press 'c' to cancel");
if (ReadKey().KeyChar == 'c')
cts.Cancel();
}
}
}

Class Program2
Private Shared Sub Main(ByVal args As String())

Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()


Dim cs As New CancellationTokenSource()

' Start a new asynchronous task that will cancel the


' operation from another thread. Typically you would call
' Cancel() in response to a button click or some other
' user interface event.
Task.Factory.StartNew(Sub()

UserClicksTheCancelButton(cs)
End Sub)

Dim results As Double() = Nothing


Try

results = (From num In source.AsParallel().WithCancellation(cs.Token) _


Where num Mod 3 = 0 _
Select [Function](num, cs.Token)).ToArray()
Catch e As OperationCanceledException

Console.WriteLine(e.Message)
Catch ae As AggregateException
If ae.InnerExceptions IsNot Nothing Then
For Each e As Exception In ae.InnerExceptions
Console.WriteLine(e.Message)
Next
End If
Finally
Finally
cs.Dispose()
End Try

If results IsNot Nothing Then


For Each item In results
Console.WriteLine(item)
Next
End If
Console.WriteLine()

Console.ReadKey()
End Sub

' A toy method to simulate work.


Private Shared Function [Function](ByVal n As Integer, ByVal ct As CancellationToken) As Double
' If work is expected to take longer than 1 ms
' then try to check cancellation status more
' often within that work.
For i As Integer = 0 To 4
' Work hard for approx 1 millisecond.
Thread.SpinWait(50000)

' Check for cancellation request.


If ct.IsCancellationRequested Then
Throw New OperationCanceledException(ct)
End If
Next
' Anything will do for our purposes.
Return Math.Sqrt(n)
End Function

Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)


' Wait between 150 and 500 ms, then cancel.
' Adjust these values if necessary to make
' cancellation fire while query is still executing.
Dim rand As New Random()
Thread.Sleep(rand.[Next](150, 350))
Console.WriteLine("Press 'c' to cancel")
If Console.ReadKey().KeyChar = "c"c Then
cs.Cancel()

End If
End Sub
End Class

Al controlar la cancelación en el código de usuario, no tiene que usar WithCancellation en la definición de consulta.
Sin embargo, se recomienda el uso de WithCancellation, porque WithCancellation no tiene ningún efecto en el
rendimiento de las consultas y permite que los operadores de consulta y el código de usuario controlen la
cancelación.
Con el fin de garantizar la capacidad de respuesta del sistema, se recomienda que compruebe la cancelación una
vez por cada milisegundo; sin embargo, cualquier período que transcurre hasta diez milisegundos se considera
aceptable. Esta frecuencia no debe tener un impacto negativo en el rendimiento del código.
Cuando se elimina un enumerador, por ejemplo cuando el código desencadena un bucle foreach (For Each en
Visual Basic) que está iterando en los resultados de la consulta, se cancela la consulta, pero no se inicia ninguna
excepción.

Vea también
ParallelEnumerable
Parallel LINQ (PLINQ)
Cancelación en subprocesos administrados
Cómo: Escribir una función de agregado
personalizada de PLINQ
16/09/2020 • 3 minutes to read • Edit Online

En este ejemplo se muestra cómo utilizar el método Aggregate para aplicar una función de agregación
personalizada a una secuencia de origen.

WARNING
La finalidad de este ejemplo es mostrar el uso, y puede que su ejecución no sea tan rápida como la de la consulta LINQ to
Objects secuencial equivalente. Para más información sobre la velocidad, vea Introducción a la velocidad en PLINQ.

Ejemplo
En el ejemplo siguiente se calcula la desviación estándar de una secuencia de enteros.
namespace PLINQAggregation
{
using System;
using System.Linq;

class aggregation
{
static void Main(string[] args)
{

// Create a data source for demonstration purposes.


int[] source = new int[100000];
Random rand = new Random();
for (int x = 0; x < source.Length; x++)
{
// Should result in a mean of approximately 15.0.
source[x] = rand.Next(10, 20);
}

// Standard deviation calculation requires that we first


// calculate the mean average. Average is a predefined
// aggregation operator, along with Max, Min and Count.
double mean = source.AsParallel().Average();

// We use the overload that is unique to ParallelEnumerable. The


// third Func parameter combines the results from each thread.
double standardDev = source.AsParallel().Aggregate(
// initialize subtotal. Use decimal point to tell
// the compiler this is a type double. Can also use: 0d.
0.0,

// do this on each thread


(subtotal, item) => subtotal + Math.Pow((item - mean), 2),

// aggregate results after all threads are done.


(total, thisThread) => total + thisThread,

// perform standard deviation calc on the aggregated result.


(finalSum) => Math.Sqrt((finalSum / (source.Length - 1)))
);
Console.WriteLine("Mean value is = {0}", mean);
Console.WriteLine("Standard deviation is {0}", standardDev);
Console.ReadLine();
}
}
}
Class aggregation
Private Shared Sub Main(ByVal args As String())

' Create a data source for demonstration purposes.


Dim source As Integer() = New Integer(99999) {}
Dim rand As New Random()
For x As Integer = 0 To source.Length - 1
' Should result in a mean of approximately 15.0.
source(x) = rand.[Next](10, 20)
Next

' Standard deviation calculation requires that we first


' calculate the mean average. Average is a predefined
' aggregation operator, along with Max, Min and Count.
Dim mean As Double = source.AsParallel().Average()

' We use the overload that is unique to ParallelEnumerable. The


' third Func parameter combines the results from each thread.
' initialize subtotal. Use decimal point to tell
' the compiler this is a type double. Can also use: 0d.

' do this on each thread

' aggregate results after all threads are done.

' perform standard deviation calc on the aggregated result.


Dim standardDev As Double = source.AsParallel().Aggregate(0.0R, Function(subtotal, item) subtotal +
Math.Pow((item - mean), 2), Function(total, thisThread) total + thisThread, Function(finalSum)
Math.Sqrt((finalSum / (source.Length - 1))))
Console.WriteLine("Mean value is = {0}", mean)
Console.WriteLine("Standard deviation is {0}", standardDev)

Console.ReadLine()
End Sub
End Class

Este ejemplo utiliza una sobrecarga del operador de consulta estándar agregado que sea único en PLINQ. Esta
sobrecarga adopta un delegado adicional System.Func<T1,T2,TResult> como tercer parámetro de entrada. Este
delegado combina los resultados de todos los subprocesos antes de realizar el cálculo final de los resultados
agregados. En este ejemplo se agregan las sumas de todos los subprocesos.
Tenga en cuenta que, cuando un cuerpo de expresiones lambda consta de una única expresión, el valor devuelto del
delegado System.Func<T,TResult> es el valor de la expresión.

Vea también
ParallelEnumerable
Parallel LINQ (PLINQ)
Procedimiento para especificar el modo de ejecución
en PLINQ
16/09/2020 • 2 minutes to read • Edit Online

En este ejemplo se muestra cómo forzar a PLINQ a omitir su heurística predeterminada y paralelizar una consulta,
independientemente de la forma de la consulta.

NOTE
La finalidad de este ejemplo es mostrar el uso y puede que su ejecución no sea tan rápida como la de la consulta LINQ to
Objects secuencial equivalente. Para más información sobre la velocidad, vea Introducción a la velocidad en PLINQ.

Ejemplo
// Paste into PLINQDataSample class.
static void ForceParallel()
{
var customers = GetCustomers();
var parallelQuery = (from cust in customers.AsParallel()
.WithExecutionMode(ParallelExecutionMode.ForceParallelism)
where cust.City == "Berlin"
select cust.CustomerName)
.ToList();
}

Private Shared Sub ForceParallel()


Dim customers = GetCustomers()
Dim parallelQuery = (From cust In
customers.AsParallel().WithExecutionMode(ParallelExecutionMode.ForceParallelism) _
Where cust.City = "Berlin" _
Select cust.CustomerName).ToList()
End Sub

PLINQ está diseñado para aprovechar las oportunidades para la paralelización. Sin embargo, no todas las
consultas se beneficiarán de la ejecución en paralelo. Por ejemplo, cuando una consulta contiene un delegado de
usuario único que realiza poco trabajo, la consulta normalmente se ejecutará con mayor rapidez de forma
secuencial. La ejecución secuencial es más rápida porque la sobrecarga necesaria para habilitar la ejecución en
paralelo es más cara que la velocidad que se obtiene. Por lo tanto, PLINQ no paraleliza automáticamente todas las
consultas. Primero examina la forma de la consulta y los diversos operadores que la componen. En función de este
análisis, PLINQ en el modo de ejecución predeterminado puede decidir ejecutar algunas o todas las consultas de
forma secuencial. Sin embargo, en algunos casos, puede saber más sobre su consulta de lo que PLINQ es capaz de
determinar a partir de su análisis. Por ejemplo, puede saber que un delegado es caro y que la consulta se
beneficiará definitivamente de la paralelización. En tales casos, puede usar el método WithExecutionMode y
especificar el valor ForceParallelism para indicar a PLINQ que ejecute siempre la consulta de forma paralela.

Compilar el código
Corte y pegue este código en el ejemplo de datos de PLINQ y llame al método desde Main .
Vea también
AsSequential
Parallel LINQ (PLINQ)
Cómo: Especificar opciones de combinación en
PLINQ
16/09/2020 • 2 minutes to read • Edit Online

En este ejemplo se muestra cómo especificar las opciones de combinación que se aplicarán a todos los operadores
subsiguientes en una consulta PLINQ. No es necesario establecer las opciones de combinación explícitamente,
pero, en caso de establecerlas, se puede mejorar el rendimiento. Para más información sobre las opciones de
combinación, consulte las opciones de combinación en PLINQ.

WARNING
La finalidad de este ejemplo es mostrar el uso, y puede que su ejecución no sea tan rápida como la de la consulta LINQ to
Objects secuencial equivalente. Para más información sobre la velocidad, vea Introducción a la velocidad en PLINQ.

Ejemplo
En el ejemplo siguiente se muestra el comportamiento de las opciones de combinación en un escenario básico
que tiene un origen no ordenado y aplica una función cara a cada elemento.
namespace MergeOptions
{
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;

class Program
{
static void Main(string[] args)
{

var nums = Enumerable.Range(1, 10000);

// Replace NotBuffered with AutoBuffered


// or FullyBuffered to compare behavior.
var scanLines = from n in nums.AsParallel()
.WithMergeOptions(ParallelMergeOptions.NotBuffered)
where n % 2 == 0
select ExpensiveFunc(n);

Stopwatch sw = Stopwatch.StartNew();
foreach (var line in scanLines)
{
Console.WriteLine(line);
}

Console.WriteLine("Elapsed time: {0} ms. Press any key to exit.",


sw.ElapsedMilliseconds);
Console.ReadKey();
}

// A function that demonstrates what a fly


// sees when it watches television :-)
static string ExpensiveFunc(int i)
{
Thread.SpinWait(2000000);
return String.Format("{0} *****************************************", i);
}
}
}
Class MergeOptions2

Sub DoMergeOptions()

Dim nums = Enumerable.Range(1, 10000)

' Replace NotBuffered with AutoBuffered


' or FullyBuffered to compare behavior.
Dim scanLines = From n In nums.AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered)
Where n Mod 2 = 0
Select ExpensiveFunc(n)

Dim sw = System.Diagnostics.Stopwatch.StartNew()
For Each line In scanLines
Console.WriteLine(line)
Next

Console.WriteLine("Elapsed time: {0} ms. Press any key to exit.")


Console.ReadKey()

End Sub
' A function that demonstrates what a fly
' sees when it watches television :-)
Function ExpensiveFunc(ByVal i As Integer) As String
System.Threading.Thread.SpinWait(2000000)
Return String.Format("{0} *****************************************", i)
End Function
End Class

En casos donde la opción AutoBuffered incurre en una latencia indeseable antes de que se produzca el primer
elemento, pruebe la opción NotBuffered para proporcionar elementos de resultados más rápido y sin problemas.

Vea también
ParallelMergeOptions
Parallel LINQ (PLINQ)
Procedimiento para iterar directorios con PLINQ
16/09/2020 • 5 minutes to read • Edit Online

En este artículo se muestran dos maneras de paralelizar operaciones en directorios de archivos. La primera
consulta utiliza el método GetFiles para rellenar una matriz de nombres de archivo en un directorio y en todos los
subdirectorios. Este método puede generar latencia al principio de la operación porque no realiza ninguna
devolución hasta que se rellena toda la matriz. Sin embargo, una vez que se rellena la matriz, PLINQ puede
procesarla en paralelo rápidamente.
La segunda consulta utiliza los métodos estáticos EnumerateDirectories y EnumerateFiles, que empiezan a
devolver resultados inmediatamente. Este enfoque puede ser más rápido cuando esté iterando los árboles de
directorio de gran tamaño, pero el tiempo de procesamiento en comparación con el primer ejemplo depende de
muchos factores.

NOTE
La finalidad de estos ejemplos es mostrar el uso, y puede que su ejecución no sea tan rápida como la de la consulta LINQ to
Objects secuencial equivalente. Para más información sobre la velocidad, vea Introducción a la velocidad en PLINQ.

Ejemplo de GetFiles
En el ejemplo siguiente se muestra cómo iterar directorios de archivos en escenarios sencillos cuando se tiene
acceso a todos los directorios del árbol, los tamaños de archivo no son grandes y los tiempos de acceso no son
significativos. Este enfoque implica un período de latencia al principio, mientras que la matriz de nombres de
archivo se está construyendo.
struct FileResult
{
public string Text;
public string FileName;
}
// Use Directory.GetFiles to get the source sequence of file names.
public static void FileIteration_1(string path)
{
var sw = Stopwatch.StartNew();
int count = 0;
string[] files = null;
try
{
files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine("You do not have permission to access one or more folders in this directory tree.");
return;
}

catch (FileNotFoundException)
{
Console.WriteLine("The specified directory {0} was not found.", path);
}

var fileContents = from file in files.AsParallel()


let extension = Path.GetExtension(file)
where extension == ".txt" || extension == ".htm"
let text = File.ReadAllText(file)
select new FileResult { Text = text , FileName = file }; //Or ReadAllBytes, ReadAllLines, etc.

try
{
foreach (var item in fileContents)
{
Console.WriteLine(Path.GetFileName(item.FileName) + ":" + item.Text.Length);
count++;
}
}
catch (AggregateException ae)
{
ae.Handle((ex) =>
{
if (ex is UnauthorizedAccessException)
{
Console.WriteLine(ex.Message);
return true;
}
return false;
});
}

Console.WriteLine("FileIteration_1 processed {0} files in {1} milliseconds", count,


sw.ElapsedMilliseconds);
}

Ejemplo de EnumerateFiles
En el ejemplo siguiente se muestra cómo iterar directorios de archivos en escenarios sencillos cuando se tiene
acceso a todos los directorios del árbol, los tamaños de archivo no son grandes y los tiempos de acceso no son
significativos. Este enfoque comienza con la generación de resultados con más rapidez que en el ejemplo anterior.
struct FileResult
{
public string Text;
public string FileName;
}

// Use Directory.EnumerateDirectories and EnumerateFiles to get the source sequence of file names.
public static void FileIteration_2(string path) //225512 ms
{
var count = 0;
var sw = Stopwatch.StartNew();
var fileNames = from dir in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
select dir;

var fileContents = from file in fileNames.AsParallel() // Use AsOrdered to preserve source ordering
let extension = Path.GetExtension(file)
where extension == ".txt" || extension == ".htm"
let Text = File.ReadAllText(file)
select new { Text, FileName = file }; //Or ReadAllBytes, ReadAllLines, etc.
try
{
foreach (var item in fileContents)
{
Console.WriteLine(Path.GetFileName(item.FileName) + ":" + item.Text.Length);
count++;
}
}
catch (AggregateException ae)
{
ae.Handle((ex) =>
{
if (ex is UnauthorizedAccessException)
{
Console.WriteLine(ex.Message);
return true;
}
return false;
});
}

Console.WriteLine("FileIteration_2 processed {0} files in {1} milliseconds", count,


sw.ElapsedMilliseconds);
}

Cuando se usa GetFiles, asegúrese de que tiene suficientes permisos en todos los directorios del árbol. De lo
contrario, se producirá una excepción y no se devolverá ningún resultado. Cuando se usa EnumerateDirectories en
una consulta PLINQ, resulta problemático controlar las excepciones de E/S de una forma correcta que le permita
continuar la iteración. Si el código debe administrar E/S o excepciones de acceso no autorizado, debe considerar el
enfoque descrito en Procedimiento Iteración de directorios de archivos con la clase Parallel.
Si la latencia de E/S es un problema, por ejemplo con E/S de archivos a través de una red, considere la posibilidad
de usar una de las técnicas de E/S asincrónicas descritas en TPL y la programación asincrónica tradicional de .NET
Framework y en esta entrada de blog .

Vea también
Parallel LINQ (PLINQ)
Procedimiento para medir el rendimiento de
consultas PLINQ
16/09/2020 • 2 minutes to read • Edit Online

En este ejemplo se muestra cómo utilizar la clase Stopwatch para medir el tiempo que tarda en ejecutarse una
consulta PLINQ.

Ejemplo
Este ejemplo utiliza un bucle foreach vacío ( For Each en Visual Basic) para medir el tiempo que tarda en
ejecutarse la consulta. En el código real, el bucle normalmente contiene pasos de procesamiento adicional que se
agregan al tiempo de ejecución de consulta total. Verá que el cronómetro no se inicia hasta justo antes del bucle,
ya que ese es el momento en el que comienza la ejecución de las consultas. Si necesita una medición más
específica, puede utilizar la propiedad ElapsedTicks en lugar de ElapsedMilliseconds .

using System;
using System.Diagnostics;
using System.Linq;

class Example
{
static void Main()
{
var source = Enumerable.Range(0, 3000000);

var queryToMeasure = from num in source.AsParallel()


where num % 3 == 0
select Math.Sqrt(num);

Console.WriteLine("Measuring...");

// The query does not run until it is enumerated.


// Therefore, start the timer here.
Stopwatch sw = Stopwatch.StartNew();

// For pure query cost, enumerate and do nothing else.


foreach (var n in queryToMeasure) { }

sw.Stop();
long elapsed = sw.ElapsedMilliseconds; // or sw.ElapsedTicks
Console.WriteLine("Total query time: {0} ms", elapsed);

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
}
}
Module Example
Sub Main()
Dim source = Enumerable.Range(0, 3000000)
' Define parallel and non-parallel queries.
Dim queryToMeasure = From num In source.AsParallel()
Where num Mod 3 = 0
Select Math.Sqrt(num)

Console.WriteLine("Measuring...")

' The query does not run until it is enumerated.


' Therefore, start the timer here.
Dim sw = System.Diagnostics.Stopwatch.StartNew()

' For pure query cost, enumerate and do nothing else.


For Each n As Double In queryToMeasure
Next

sw.Stop()
Dim elapsed As Long = sw.ElapsedMilliseconds ' or sw.ElapsedTicks
Console.WriteLine("Total query time: {0} ms.", elapsed)

Console.WriteLine("Press any key to exit.")


Console.ReadKey()
End Sub
End Module

El tiempo de ejecución total es una métrica útil cuando se experimenta con implementaciones de consulta, pero no
siempre muestra todo el panorama. Para obtener una vista más detallada y completa de la interacción de los
subprocesos de consulta entre sí y con otros procesos en ejecución, use el Visualizador de simultaneidad.

Vea también
Parallel LINQ (PLINQ)
Ejemplo de datos de PLINQ
16/09/2020 • 20 minutes to read • Edit Online

Este ejemplo contiene datos de ejemplo en formato .csv, junto con métodos que los transforman en colecciones
en memoria de clientes, productos, pedidos y detalles de pedidos. Si desea realizar más pruebas con PLINQ,
puede pegar los ejemplos de código de otros temas concretos en el código de este tema e invocarlo desde el
método Main . También puede usar estos datos con sus propias consultas PLINQ.
Los datos representan un subconjunto de la base de datos Northwind. Se incluyen cincuenta (50) registros de
clientes, pero no todos los campos. Se incluye un subconjunto de filas de los pedidos y los datos
correspondientes de los detalles de los pedidos para cada cliente. Se incluyen todos los productos.

NOTE
El conjunto de datos no es lo suficientemente grande para mostrar que PLINQ es más rápido que LINQ to Objects para
consultas que contienen solo las cláusulas básicas where y select . Para observar aumentos de velocidad de conjuntos
de datos pequeños como este, use consultas que contengan operaciones caras desde el punto de vista del cálculo en cada
elemento del conjunto de datos.

Para configurar este ejemplo


1. Cree un proyecto de aplicación de consola de Visual Basic o Visual C#.
2. Reemplace el contenido de Module1.vb o Program.cs con el código que sigue estos pasos.
3. En el menú Proyecto , haga clic en Agregar nuevo elemento . Seleccione Archivo de texto y después
haga clic en Aceptar . Copie los datos de este tema y, a continuación, péguelos en el nuevo archivo de
texto. En el menú Archivo , haga clic en Guardar , asigne al archivo el nombre Plinqdata.csv y, a
continuación, guárdelo en la carpeta que contiene los archivos de código fuente.
4. Presione F5 para comprobar que el proyecto se compila y ejecuta correctamente. En el resultado siguiente
debe mostrarse en la ventana de la consola.

Customer count: 50
Product count: 77
Order count: 190
Order Details count: 483
Press any key to exit.

// This class contains a subset of data from the Northwind database


// in the form of string arrays. The methods such as GetCustomers, GetOrders, and so on
// transform the strings into object arrays that you can query in an object-oriented way.
// Many of the code examples in the PLINQ How-to topics are designed to be pasted into
// the PLINQDataSample class and invoked from the Main method.
partial class PLINQDataSample
{

public static void Main()


{
////Call methods here.
TestDataSource();

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
}
}

static void TestDataSource()


{
Console.WriteLine("Customer count: {0}", GetCustomers().Count());
Console.WriteLine("Product count: {0}", GetProducts().Count());
Console.WriteLine("Order count: {0}", GetOrders().Count());
Console.WriteLine("Order Details count: {0}", GetOrderDetails().Count());
}

#region DataClasses
public class Order
{
private Lazy<OrderDetail[]> _orderDetails;
public Order()
{
_orderDetails = new Lazy<OrderDetail[]>(() => GetOrderDetailsForOrder(OrderID));
}
public int OrderID { get; set; }
public string CustomerID { get; set; }
public DateTime OrderDate { get; set; }
public DateTime ShippedDate { get; set; }
public OrderDetail[] OrderDetails { get { return _orderDetails.Value; } }
}

public class Customer


{
private Lazy<Order[]> _orders;
public Customer()
{
_orders = new Lazy<Order[]>(() => GetOrdersForCustomer(CustomerID));
}
public string CustomerID { get; set; }
public string CustomerName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public Order[] Orders
{
get
{
return _orders.Value;
}
}
}

public class Product


{
public string ProductName { get; set; }
public int ProductID { get; set; }
public double UnitPrice { get; set; }
}

public class OrderDetail


{
public int OrderID { get; set; }
public int ProductID { get; set; }
public double UnitPrice { get; set; }
public double Quantity { get; set; }
public double Discount { get; set; }
}
#endregion

public static IEnumerable<string> GetCustomersAsStrings()


{
return System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("CUSTOMERS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END CUSTOMERS") == false);
}
}

public static IEnumerable<Customer> GetCustomers()


{
var customers = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("CUSTOMERS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END CUSTOMERS") == false);
return (from line in customers
let fields = line.Split(',')
let custID = fields[0].Trim()
select new Customer()
{
CustomerID = custID,
CustomerName = fields[1].Trim(),
Address = fields[2].Trim(),
City = fields[3].Trim(),
PostalCode = fields[4].Trim()
});
}

public static Order[] GetOrdersForCustomer(string id)


{
// Assumes we copied the file correctly!
var orders = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("ORDERS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END ORDERS") == false);
var orderStrings = from line in orders
let fields = line.Split(',')
where fields[1].CompareTo(id) == 0
select new Order()
{
OrderID = Convert.ToInt32(fields[0]),
CustomerID = fields[1].Trim(),
OrderDate = DateTime.Parse(fields[2]),
ShippedDate = DateTime.Parse(fields[3])
};
return orderStrings.ToArray();
}

// "10248, VINET, 7/4/1996 12:00:00 AM, 7/16/1996 12:00:00 AM


public static IEnumerable<Order> GetOrders()
{
// Assumes we copied the file correctly!
var orders = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("ORDERS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END ORDERS") == false);
return from line in orders
let fields = line.Split(',')

select new Order()


{
OrderID = Convert.ToInt32(fields[0]),
CustomerID = fields[1].Trim(),
OrderDate = DateTime.Parse(fields[2]),
ShippedDate = DateTime.Parse(fields[3])
};
}

public static IEnumerable<Product> GetProducts()


{
// Assumes we copied the file correctly!
var products = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("PRODUCTS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END PRODUCTS") == false);
return from line in products
let fields = line.Split(',')
let fields = line.Split(',')
select new Product()
{
ProductID = Convert.ToInt32(fields[0]),
ProductName = fields[1].Trim(),
UnitPrice = Convert.ToDouble(fields[2])
};
}

public static IEnumerable<OrderDetail> GetOrderDetails()


{
// Assumes we copied the file correctly!
var orderDetails = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("ORDER DETAILS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END ORDER DETAILS") == false);

return from line in orderDetails


let fields = line.Split(',')
select new OrderDetail()
{
OrderID = Convert.ToInt32(fields[0]),
ProductID = Convert.ToInt32(fields[1]),
UnitPrice = Convert.ToDouble(fields[2]),
Quantity = Convert.ToDouble(fields[3]),
Discount = Convert.ToDouble(fields[4])
};
}

public static OrderDetail[] GetOrderDetailsForOrder(int id)


{
// Assumes we copied the file correctly!
var orderDetails = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("ORDER DETAILS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END ORDER DETAILS") == false);

var orderDetailStrings = from line in orderDetails


let fields = line.Split(',')
let ordID = Convert.ToInt32(fields[0])
where ordID == id
select new OrderDetail()
{
OrderID = ordID,
ProductID = Convert.ToInt32(fields[1]),
UnitPrice = Convert.ToDouble(fields[2]),
Quantity = Convert.ToDouble(fields[3]),
Discount = Convert.ToDouble(fields[4])
};

return orderDetailStrings.ToArray();
}
}

' This class contains a subset of data from the Northwind database
' in the form of string arrays. The methods such as GetCustomers, GetOrders, and so on
' transform the strings into object arrays that you can query in an object-oriented way.
' Many of the code examples in the PLINQ How-to topics are designed to be pasted into
' the PLINQDataSample class and invoked from the Main method.
' IMPORTANT: This data set is not large enough for meaningful comparisons of PLINQ vs. LINQ performance.
Class PLINQDataSample
Shared Sub Main(ByVal args As String())

'Call methods here.


TestDataSource()

Console.WriteLine("Press any key to exit.")


Console.ReadKey()
Console.ReadKey()
End Sub

Shared Sub TestDataSource()


Console.WriteLine("Customer count: {0}", GetCustomers().Count())
Console.WriteLine("Product count: {0}", GetProducts().Count())
Console.WriteLine("Order count: {0}", GetOrders().Count())
Console.WriteLine("Order Details count: {0}", GetOrderDetails().Count())
End Sub

#Region "DataClasses"
Class Order
Public Sub New()
_OrderDetails = New Lazy(Of OrderDetail())(Function() GetOrderDetailsForOrder(OrderID))
End Sub
Private _OrderID As Integer
Public Property OrderID() As Integer
Get
Return _OrderID
End Get
Set(ByVal value As Integer)
_OrderID = value
End Set
End Property
Private _CustomerID As String
Public Property CustomerID() As String
Get
Return _CustomerID
End Get
Set(ByVal value As String)
_CustomerID = value
End Set
End Property
Private _OrderDate As DateTime
Public Property OrderDate() As DateTime
Get
Return _OrderDate
End Get
Set(ByVal value As DateTime)
_OrderDate = value
End Set
End Property
Private _ShippedDate As DateTime
Public Property ShippedDate() As DateTime
Get
Return _ShippedDate
End Get
Set(ByVal value As DateTime)
_ShippedDate = value
End Set
End Property
Private _OrderDetails As Lazy(Of OrderDetail())
Public ReadOnly Property OrderDetails As OrderDetail()
Get
Return _OrderDetails.Value
End Get
End Property
End Class

Class Customer

Private _Orders As Lazy(Of Order())


Public Sub New()
_Orders = New Lazy(Of Order())(Function() GetOrdersForCustomer(_CustomerID))
End Sub

Private _CustomerID As String


Public Property CustomerID() As String
Get
Return _CustomerID
Return _CustomerID
End Get
Set(ByVal value As String)
_CustomerID = value
End Set
End Property
Private _CustomerName As String
Public Property CustomerName() As String
Get
Return _CustomerName
End Get
Set(ByVal value As String)
_CustomerName = value
End Set
End Property
Private _Address As String
Public Property Address() As String
Get
Return _Address
End Get
Set(ByVal value As String)
_Address = value
End Set
End Property
Private _City As String
Public Property City() As String
Get
Return _City
End Get
Set(ByVal value As String)
_City = value
End Set
End Property
Private _PostalCode As String
Public Property PostalCode() As String
Get
Return _PostalCode
End Get
Set(ByVal value As String)
_PostalCode = value
End Set
End Property
Public ReadOnly Property Orders() As Order()
Get
Return _Orders.Value
End Get
End Property
End Class

Class Product
Private _ProductName As String
Public Property ProductName() As String
Get
Return _ProductName
End Get
Set(ByVal value As String)
_ProductName = value
End Set
End Property
Private _ProductID As Integer
Public Property ProductID() As Integer
Get
Return _ProductID
End Get
Set(ByVal value As Integer)
_ProductID = value
End Set
End Property
Private _UnitPrice As Double
Public Property UnitPrice() As Double
Public Property UnitPrice() As Double
Get
Return _UnitPrice
End Get
Set(ByVal value As Double)
_UnitPrice = value
End Set
End Property
End Class

Class OrderDetail
Private _OrderID As Integer
Public Property OrderID() As Integer
Get
Return _OrderID
End Get
Set(ByVal value As Integer)
_OrderID = value
End Set
End Property
Private _ProductID As Integer
Public Property ProductID() As Integer
Get
Return _ProductID
End Get
Set(ByVal value As Integer)
_ProductID = value
End Set
End Property
Private _UnitPrice As Double
Public Property UnitPrice() As Double
Get
Return _UnitPrice
End Get
Set(ByVal value As Double)
_UnitPrice = value
End Set
End Property
Private _Quantity As Double
Public Property Quantity() As Double
Get
Return _Quantity
End Get
Set(ByVal value As Double)
_Quantity = value
End Set
End Property
Private _Discount As Double
Public Property Discount() As Double
Get
Return _Discount
End Get
Set(ByVal value As Double)
_Discount = value
End Set
End Property

End Class
#End Region

Shared Function GetCustomersAsStrings() As IEnumerable(Of String)

Return System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("CUSTOMERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END CUSTOMERS") = False)
End Function
Shared Function GetCustomers() As IEnumerable(Of Customer)
Dim customers = System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("CUSTOMERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END CUSTOMERS") = False)

Return From line In customers


Let fields = line.Split(","c)
Select New Customer With {
.CustomerID = fields(0).Trim(),
.CustomerName = fields(1).Trim(),
.Address = fields(2).Trim(),
.City = fields(3).Trim(),
.PostalCode = fields(4).Trim()}
End Function

Shared Function GetOrders() As IEnumerable(Of Order)

Dim orders = System.IO.File.ReadAllLines("..\..\plinqdata.csv").


SkipWhile(Function(line) line.StartsWith("ORDERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDERS") = False)

Return From line In orders


Let fields = line.Split(","c)
Select New Order With {
.OrderID = CType(fields(0).Trim(), Integer),
.CustomerID = fields(1).Trim(),
.OrderDate = DateTime.Parse(fields(2)),
.ShippedDate = DateTime.Parse(fields(3))}

End Function

Shared Function GetOrdersForCustomer(ByVal id As String) As Order()

Dim orders = System.IO.File.ReadAllLines("..\..\plinqdata.csv").


SkipWhile(Function(line) line.StartsWith("ORDERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDERS") = False)

Dim orderStrings = From line In orders


Let fields = line.Split(","c)
Let custID = fields(1).Trim()
Where custID = id
Select New Order With {
.OrderID = CType(fields(0).Trim(), Integer),
.CustomerID = custID,
.OrderDate = DateTime.Parse(fields(2)),
.ShippedDate = DateTime.Parse(fields(3))}

Return orderStrings.ToArray()

End Function
Shared Function GetProducts() As IEnumerable(Of Product)

Dim products = System.IO.File.ReadAllLines("..\..\plinqdata.csv").


SkipWhile(Function(line) line.StartsWith("PRODUCTS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END PRODUCTS") = False)

Return From line In products


Let fields = line.Split(","c)
Select New Product With {
.ProductID = CType(fields(0), Integer),
.ProductName = fields(1).Trim(),
.UnitPrice = CType(fields(2), Double)}
End Function

Shared Function GetOrderDetailsForOrder(ByVal orderID As Integer) As OrderDetail()


Dim orderDetails = System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("ORDER DETAILS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDER DETAILS") = False)

Dim ordDetailStrings = From line In orderDetails


Let fields = line.Split(","c)
Let ordID = Convert.ToInt32(fields(0))
Where ordID = orderID
Select New OrderDetail With {
.OrderID = ordID,
.ProductID = CType(fields(1), Integer),
.UnitPrice = CType(fields(2), Double),
.Quantity = CType(fields(3), Double),
.Discount = CType(fields(4), Double)}
Return ordDetailStrings.ToArray()

End Function

Shared Function GetOrderDetails() As IEnumerable(Of OrderDetail)


Dim orderDetails = System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("ORDER DETAILS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDER DETAILS") = False)

Return From line In orderDetails


Let fields = line.Split(","c)
Select New OrderDetail With {
.OrderID = CType(fields(0), Integer),
.ProductID = CType(fields(1), Integer),
.UnitPrice = CType(fields(2), Double),
.Quantity = CType(fields(3), Double),
.Discount = CType(fields(4), Double)}
End Function

End Class

data
CUSTOMERS
ALFKI,Alfreds Futterkiste,Obere Str. 57,Berlin,12209
ANATR,Ana Trujillo Emparedados y helados,Avda. de la Constitución 2222,México D.F.,05021
ANTON,Antonio Moreno Taquería,Mataderos 2312,México D.F.,05023
AROUT,Around the Horn,120 Hanover Sq.,London,WA1 1DP
BERGS,Berglunds snabbköp,Berguvsvägen 8,Luleå,S-958 22
BLAUS,Blauer See Delikatessen,Forsterstr. 57,Mannheim,68306
BLONP,Blondesddsl père et fils,24, place Kléber,Strasbourg,67000
BOLID,Bólido Comidas preparadas,C/ Araquil, 67,Madrid,28023
BONAP,Bon app',12, rue des Bouchers,Marseille,13008
BOTTM,Bottom-Dollar Markets,23 Tsawassen Blvd.,Tsawassen,T2F 8M4
BSBEV,B's Beverages,Fauntleroy Circus,London,EC2 5NT
CACTU,Cactus Comidas para llevar,Cerrito 333,Buenos Aires,1010
CENTC,Centro comercial Moctezuma,Sierras de Granada 9993,México D.F.,05022
CHOPS,Chop-suey Chinese,Hauptstr. 29,Bern,3012
COMMI,Comércio Mineiro,Av. dos Lusíadas, 23,Sao Paulo,05432-043
CONSH,Consolidated Holdings,Berkeley Gardens 12 Brewery,London,WX1 6LT
DRACD,Drachenblut Delikatessen,Walserweg 21,Aachen,52066
DUMON,Du monde entier,67, rue des Cinquante Otages,Nantes,44000
EASTC,Eastern Connection,35 King George,London,WX3 6FW
ERNSH,Ernst Handel,Kirchgasse 6,Graz,8010
FAMIA,Familia Arquibaldo,Rua Orós, 92,Sao Paulo,05442-030
FISSA,FISSA Fabrica Inter. Salchichas S.A.,C/ Moralzarzal, 86,Madrid,28034
FOLIG,Folies gourmandes,184, chaussée de Tournai,Lille,59000
FOLKO,Folk och fä HB,Åkergatan 24,Bräcke,S-844 67
FRANK,Frankenversand,Berliner Platz 43,München,80805
FRANR,France restauration,54, rue Royale,Nantes,44000
FRANS,Franchi S.p.A.,Via Monte Bianco 34,Torino,10100
FURIB,Furia Bacalhau e Frutos do Mar,Jardim das rosas n. 32,Lisboa,1675
GALED,Galería del gastrónomo,Rambla de Cataluña, 23,Barcelona,08022
GODOS,Godos Cocina Típica,C/ Romero, 33,Sevilla,41101
GOURL,Gourmet Lanchonetes,Av. Brasil, 442,Campinas,04876-786
GREAL,Great Lakes Food Market,2732 Baker Blvd.,Eugene,97403
GROSR,GROSELLA-Restaurante,5ª Ave. Los Palos Grandes,Caracas,1081
HANAR,Hanari Carnes,Rua do Paço, 67,Rio de Janeiro,05454-876
HILAA,HILARION-Abastos,Carrera 22 con Ave. Carlos Soublette #8-35,San Cristóbal,5022
HUNGC,Hungry Coyote Import Store,City Center Plaza 516 Main St.,Elgin,97827
HUNGO,Hungry Owl All-Night Grocers,8 Johnstown Road,Cork,
ISLAT,Island Trading,Garden House Crowther Way,Cowes,PO31 7PJ
KOENE,Königlich Essen,Maubelstr. 90,Brandenburg,14776
LACOR,La corne d'abondance,67, avenue de l'Europe,Versailles,78000
LAMAI,La maison d'Asie,1 rue Alsace-Lorraine,Toulouse,31000
LAUGB,Laughing Bacchus Wine Cellars,1900 Oak St.,Vancouver,V3F 2K1
LAZYK,Lazy K Kountry Store,12 Orchestra Terrace,Walla Walla,99362
LEHMS,Lehmanns Marktstand,Magazinweg 7,Frankfurt a.M.,60528
LETSS,Let's Stop N Shop,87 Polk St. Suite 5,San Francisco,94117
LILAS,LILA-Supermercado,Carrera 52 con Ave. Bolívar #65-98 Llano Largo,Barquisimeto,3508
LINOD,LINO-Delicateses,Ave. 5 de Mayo Porlamar,I. de Margarita,4980
LONEP,Lonesome Pine Restaurant,89 Chiaroscuro Rd.,Portland,97219
MAGAA,Magazzini Alimentari Riuniti,Via Ludovico il Moro 22,Bergamo,24100
MAISD,Maison Dewey,Rue Joseph-Bens 532,Bruxelles,B-1180
END CUSTOMERS

ORDERS
10250,HANAR,7/8/1996 12:00:00 AM,7/12/1996 12:00:00 AM
10253,HANAR,7/10/1996 12:00:00 AM,7/16/1996 12:00:00 AM
10254,CHOPS,7/11/1996 12:00:00 AM,7/23/1996 12:00:00 AM
10257,HILAA,7/16/1996 12:00:00 AM,7/22/1996 12:00:00 AM
10258,ERNSH,7/17/1996 12:00:00 AM,7/23/1996 12:00:00 AM
10263,ERNSH,7/23/1996 12:00:00 AM,7/31/1996 12:00:00 AM
10264,FOLKO,7/24/1996 12:00:00 AM,8/23/1996 12:00:00 AM
10265,BLONP,7/25/1996 12:00:00 AM,8/12/1996 12:00:00 AM
10267,FRANK,7/29/1996 12:00:00 AM,8/6/1996 12:00:00 AM
10275,MAGAA,8/7/1996 12:00:00 AM,8/9/1996 12:00:00 AM
10278,BERGS,8/12/1996 12:00:00 AM,8/16/1996 12:00:00 AM
10279,LEHMS,8/13/1996 12:00:00 AM,8/16/1996 12:00:00 AM
10280,BERGS,8/14/1996 12:00:00 AM,9/12/1996 12:00:00 AM
10283,LILAS,8/16/1996 12:00:00 AM,8/23/1996 12:00:00 AM
10284,LEHMS,8/19/1996 12:00:00 AM,8/27/1996 12:00:00 AM
10289,BSBEV,8/26/1996 12:00:00 AM,8/28/1996 12:00:00 AM
10290,COMMI,8/27/1996 12:00:00 AM,9/3/1996 12:00:00 AM
10296,LILAS,9/3/1996 12:00:00 AM,9/11/1996 12:00:00 AM
10297,BLONP,9/4/1996 12:00:00 AM,9/10/1996 12:00:00 AM
10298,HUNGO,9/5/1996 12:00:00 AM,9/11/1996 12:00:00 AM
10300,MAGAA,9/9/1996 12:00:00 AM,9/18/1996 12:00:00 AM
10303,GODOS,9/11/1996 12:00:00 AM,9/18/1996 12:00:00 AM
10307,LONEP,9/17/1996 12:00:00 AM,9/25/1996 12:00:00 AM
10309,HUNGO,9/19/1996 12:00:00 AM,10/23/1996 12:00:00 AM
10315,ISLAT,9/26/1996 12:00:00 AM,10/3/1996 12:00:00 AM
10317,LONEP,9/30/1996 12:00:00 AM,10/10/1996 12:00:00 AM
10318,ISLAT,10/1/1996 12:00:00 AM,10/4/1996 12:00:00 AM
10321,ISLAT,10/3/1996 12:00:00 AM,10/11/1996 12:00:00 AM
10323,KOENE,10/7/1996 12:00:00 AM,10/14/1996 12:00:00 AM
10325,KOENE,10/9/1996 12:00:00 AM,10/14/1996 12:00:00 AM
10327,FOLKO,10/11/1996 12:00:00 AM,10/14/1996 12:00:00 AM
10328,FURIB,10/14/1996 12:00:00 AM,10/17/1996 12:00:00 AM
10330,LILAS,10/16/1996 12:00:00 AM,10/28/1996 12:00:00 AM
10331,BONAP,10/16/1996 12:00:00 AM,10/21/1996 12:00:00 AM
10335,HUNGO,10/22/1996 12:00:00 AM,10/24/1996 12:00:00 AM
10337,FRANK,10/24/1996 12:00:00 AM,10/29/1996 12:00:00 AM
10340,BONAP,10/29/1996 12:00:00 AM,11/8/1996 12:00:00 AM
10342,FRANK,10/30/1996 12:00:00 AM,11/4/1996 12:00:00 AM
10343,LEHMS,10/31/1996 12:00:00 AM,11/6/1996 12:00:00 AM
10347,FAMIA,11/6/1996 12:00:00 AM,11/8/1996 12:00:00 AM
10350,LAMAI,11/11/1996 12:00:00 AM,12/3/1996 12:00:00 AM
10351,ERNSH,11/11/1996 12:00:00 AM,11/20/1996 12:00:00 AM
10352,FURIB,11/12/1996 12:00:00 AM,11/18/1996 12:00:00 AM
10355,AROUT,11/15/1996 12:00:00 AM,11/20/1996 12:00:00 AM
10357,LILAS,11/19/1996 12:00:00 AM,12/2/1996 12:00:00 AM
10358,LAMAI,11/20/1996 12:00:00 AM,11/27/1996 12:00:00 AM
10360,BLONP,11/22/1996 12:00:00 AM,12/2/1996 12:00:00 AM
10362,BONAP,11/25/1996 12:00:00 AM,11/28/1996 12:00:00 AM
10363,DRACD,11/26/1996 12:00:00 AM,12/4/1996 12:00:00 AM
10364,EASTC,11/26/1996 12:00:00 AM,12/4/1996 12:00:00 AM
10365,ANTON,11/27/1996 12:00:00 AM,12/2/1996 12:00:00 AM
10366,GALED,11/28/1996 12:00:00 AM,12/30/1996 12:00:00 AM
10368,ERNSH,11/29/1996 12:00:00 AM,12/2/1996 12:00:00 AM
10370,CHOPS,12/3/1996 12:00:00 AM,12/27/1996 12:00:00 AM
10371,LAMAI,12/3/1996 12:00:00 AM,12/24/1996 12:00:00 AM
10373,HUNGO,12/5/1996 12:00:00 AM,12/11/1996 12:00:00 AM
10375,HUNGC,12/6/1996 12:00:00 AM,12/9/1996 12:00:00 AM
10378,FOLKO,12/10/1996 12:00:00 AM,12/19/1996 12:00:00 AM
10380,HUNGO,12/12/1996 12:00:00 AM,1/16/1997 12:00:00 AM
10381,LILAS,12/12/1996 12:00:00 AM,12/13/1996 12:00:00 AM
10382,ERNSH,12/13/1996 12:00:00 AM,12/16/1996 12:00:00 AM
10383,AROUT,12/16/1996 12:00:00 AM,12/18/1996 12:00:00 AM
10384,BERGS,12/16/1996 12:00:00 AM,12/20/1996 12:00:00 AM
10386,FAMIA,12/18/1996 12:00:00 AM,12/25/1996 12:00:00 AM
10389,BOTTM,12/20/1996 12:00:00 AM,12/24/1996 12:00:00 AM
10391,DRACD,12/23/1996 12:00:00 AM,12/31/1996 12:00:00 AM
10394,HUNGC,12/25/1996 12:00:00 AM,1/3/1997 12:00:00 AM
10395,HILAA,12/26/1996 12:00:00 AM,1/3/1997 12:00:00 AM
10396,FRANK,12/27/1996 12:00:00 AM,1/6/1997 12:00:00 AM
10400,EASTC,1/1/1997 12:00:00 AM,1/16/1997 12:00:00 AM
10404,MAGAA,1/3/1997 12:00:00 AM,1/8/1997 12:00:00 AM
10405,LINOD,1/6/1997 12:00:00 AM,1/22/1997 12:00:00 AM
10408,FOLIG,1/8/1997 12:00:00 AM,1/14/1997 12:00:00 AM
10410,BOTTM,1/10/1997 12:00:00 AM,1/15/1997 12:00:00 AM
10411,BOTTM,1/10/1997 12:00:00 AM,1/21/1997 12:00:00 AM
10413,LAMAI,1/14/1997 12:00:00 AM,1/16/1997 12:00:00 AM
10414,FAMIA,1/14/1997 12:00:00 AM,1/17/1997 12:00:00 AM
10415,HUNGC,1/15/1997 12:00:00 AM,1/24/1997 12:00:00 AM
10422,FRANS,1/22/1997 12:00:00 AM,1/31/1997 12:00:00 AM
10423,GOURL,1/23/1997 12:00:00 AM,2/24/1997 12:00:00 AM
10425,LAMAI,1/24/1997 12:00:00 AM,2/14/1997 12:00:00 AM
10426,GALED,1/27/1997 12:00:00 AM,2/6/1997 12:00:00 AM
10431,BOTTM,1/30/1997 12:00:00 AM,2/7/1997 12:00:00 AM
10434,FOLKO,2/3/1997 12:00:00 AM,2/13/1997 12:00:00 AM
10436,BLONP,2/5/1997 12:00:00 AM,2/11/1997 12:00:00 AM
10444,BERGS,2/12/1997 12:00:00 AM,2/21/1997 12:00:00 AM
10445,BERGS,2/13/1997 12:00:00 AM,2/20/1997 12:00:00 AM
10449,BLONP,2/18/1997 12:00:00 AM,2/27/1997 12:00:00 AM
10453,AROUT,2/21/1997 12:00:00 AM,2/26/1997 12:00:00 AM
10456,KOENE,2/25/1997 12:00:00 AM,2/28/1997 12:00:00 AM
10457,KOENE,2/25/1997 12:00:00 AM,3/3/1997 12:00:00 AM
10460,FOLKO,2/28/1997 12:00:00 AM,3/3/1997 12:00:00 AM
10464,FURIB,3/4/1997 12:00:00 AM,3/14/1997 12:00:00 AM
10466,COMMI,3/6/1997 12:00:00 AM,3/13/1997 12:00:00 AM
10467,MAGAA,3/6/1997 12:00:00 AM,3/11/1997 12:00:00 AM
10468,KOENE,3/7/1997 12:00:00 AM,3/12/1997 12:00:00 AM
10470,BONAP,3/11/1997 12:00:00 AM,3/14/1997 12:00:00 AM
10471,BSBEV,3/11/1997 12:00:00 AM,3/18/1997 12:00:00 AM
10473,ISLAT,3/13/1997 12:00:00 AM,3/21/1997 12:00:00 AM
10476,HILAA,3/17/1997 12:00:00 AM,3/24/1997 12:00:00 AM
10480,FOLIG,3/20/1997 12:00:00 AM,3/24/1997 12:00:00 AM
10484,BSBEV,3/24/1997 12:00:00 AM,4/1/1997 12:00:00 AM
10485,LINOD,3/25/1997 12:00:00 AM,3/31/1997 12:00:00 AM
10486,HILAA,3/26/1997 12:00:00 AM,4/2/1997 12:00:00 AM
10488,FRANK,3/27/1997 12:00:00 AM,4/2/1997 12:00:00 AM
10490,HILAA,3/31/1997 12:00:00 AM,4/3/1997 12:00:00 AM
10491,FURIB,3/31/1997 12:00:00 AM,4/8/1997 12:00:00 AM
10492,BOTTM,4/1/1997 12:00:00 AM,4/11/1997 12:00:00 AM
10494,COMMI,4/2/1997 12:00:00 AM,4/9/1997 12:00:00 AM
10497,LEHMS,4/4/1997 12:00:00 AM,4/7/1997 12:00:00 AM
10497,LEHMS,4/4/1997 12:00:00 AM,4/7/1997 12:00:00 AM
10501,BLAUS,4/9/1997 12:00:00 AM,4/16/1997 12:00:00 AM
10507,ANTON,4/15/1997 12:00:00 AM,4/22/1997 12:00:00 AM
10509,BLAUS,4/17/1997 12:00:00 AM,4/29/1997 12:00:00 AM
10511,BONAP,4/18/1997 12:00:00 AM,4/21/1997 12:00:00 AM
10512,FAMIA,4/21/1997 12:00:00 AM,4/24/1997 12:00:00 AM
10519,CHOPS,4/28/1997 12:00:00 AM,5/1/1997 12:00:00 AM
10521,CACTU,4/29/1997 12:00:00 AM,5/2/1997 12:00:00 AM
10522,LEHMS,4/30/1997 12:00:00 AM,5/6/1997 12:00:00 AM
10528,GREAL,5/6/1997 12:00:00 AM,5/9/1997 12:00:00 AM
10529,MAISD,5/7/1997 12:00:00 AM,5/9/1997 12:00:00 AM
10532,EASTC,5/9/1997 12:00:00 AM,5/12/1997 12:00:00 AM
10535,ANTON,5/13/1997 12:00:00 AM,5/21/1997 12:00:00 AM
10538,BSBEV,5/15/1997 12:00:00 AM,5/16/1997 12:00:00 AM
10539,BSBEV,5/16/1997 12:00:00 AM,5/23/1997 12:00:00 AM
10541,HANAR,5/19/1997 12:00:00 AM,5/29/1997 12:00:00 AM
10544,LONEP,5/21/1997 12:00:00 AM,5/30/1997 12:00:00 AM
10550,GODOS,5/28/1997 12:00:00 AM,6/6/1997 12:00:00 AM
10551,FURIB,5/28/1997 12:00:00 AM,6/6/1997 12:00:00 AM
10558,AROUT,6/4/1997 12:00:00 AM,6/10/1997 12:00:00 AM
10568,GALED,6/13/1997 12:00:00 AM,7/9/1997 12:00:00 AM
10573,ANTON,6/19/1997 12:00:00 AM,6/20/1997 12:00:00 AM
10581,FAMIA,6/26/1997 12:00:00 AM,7/2/1997 12:00:00 AM
10582,BLAUS,6/27/1997 12:00:00 AM,7/14/1997 12:00:00 AM
10589,GREAL,7/4/1997 12:00:00 AM,7/14/1997 12:00:00 AM
10600,HUNGC,7/16/1997 12:00:00 AM,7/21/1997 12:00:00 AM
10614,BLAUS,7/29/1997 12:00:00 AM,8/1/1997 12:00:00 AM
10616,GREAL,7/31/1997 12:00:00 AM,8/5/1997 12:00:00 AM
10617,GREAL,7/31/1997 12:00:00 AM,8/4/1997 12:00:00 AM
10621,ISLAT,8/5/1997 12:00:00 AM,8/11/1997 12:00:00 AM
10629,GODOS,8/12/1997 12:00:00 AM,8/20/1997 12:00:00 AM
10634,FOLIG,8/15/1997 12:00:00 AM,8/21/1997 12:00:00 AM
10635,MAGAA,8/18/1997 12:00:00 AM,8/21/1997 12:00:00 AM
10638,LINOD,8/20/1997 12:00:00 AM,9/1/1997 12:00:00 AM
10643,ALFKI,8/25/1997 12:00:00 AM,9/2/1997 12:00:00 AM
10645,HANAR,8/26/1997 12:00:00 AM,9/2/1997 12:00:00 AM
10649,MAISD,8/28/1997 12:00:00 AM,8/29/1997 12:00:00 AM
10652,GOURL,9/1/1997 12:00:00 AM,9/8/1997 12:00:00 AM
10656,GREAL,9/4/1997 12:00:00 AM,9/10/1997 12:00:00 AM
10660,HUNGC,9/8/1997 12:00:00 AM,10/15/1997 12:00:00 AM
10662,LONEP,9/9/1997 12:00:00 AM,9/18/1997 12:00:00 AM
10665,LONEP,9/11/1997 12:00:00 AM,9/17/1997 12:00:00 AM
10677,ANTON,9/22/1997 12:00:00 AM,9/26/1997 12:00:00 AM
10685,GOURL,9/29/1997 12:00:00 AM,10/3/1997 12:00:00 AM
10690,HANAR,10/2/1997 12:00:00 AM,10/3/1997 12:00:00 AM
10692,ALFKI,10/3/1997 12:00:00 AM,10/13/1997 12:00:00 AM
10697,LINOD,10/8/1997 12:00:00 AM,10/14/1997 12:00:00 AM
10702,ALFKI,10/13/1997 12:00:00 AM,10/21/1997 12:00:00 AM
10707,AROUT,10/16/1997 12:00:00 AM,10/23/1997 12:00:00 AM
10709,GOURL,10/17/1997 12:00:00 AM,11/20/1997 12:00:00 AM
10710,FRANS,10/20/1997 12:00:00 AM,10/23/1997 12:00:00 AM
10726,EASTC,11/3/1997 12:00:00 AM,12/5/1997 12:00:00 AM
10729,LINOD,11/4/1997 12:00:00 AM,11/14/1997 12:00:00 AM
10731,CHOPS,11/6/1997 12:00:00 AM,11/14/1997 12:00:00 AM
10734,GOURL,11/7/1997 12:00:00 AM,11/12/1997 12:00:00 AM
10746,CHOPS,11/19/1997 12:00:00 AM,11/21/1997 12:00:00 AM
10753,FRANS,11/25/1997 12:00:00 AM,11/27/1997 12:00:00 AM
10760,MAISD,12/1/1997 12:00:00 AM,12/10/1997 12:00:00 AM
10763,FOLIG,12/3/1997 12:00:00 AM,12/8/1997 12:00:00 AM
10782,CACTU,12/17/1997 12:00:00 AM,12/22/1997 12:00:00 AM
10789,FOLIG,12/22/1997 12:00:00 AM,12/31/1997 12:00:00 AM
10797,DRACD,12/25/1997 12:00:00 AM,1/5/1998 12:00:00 AM
10807,FRANS,12/31/1997 12:00:00 AM,1/30/1998 12:00:00 AM
10819,CACTU,1/7/1998 12:00:00 AM,1/16/1998 12:00:00 AM
10825,DRACD,1/9/1998 12:00:00 AM,1/14/1998 12:00:00 AM
10835,ALFKI,1/15/1998 12:00:00 AM,1/21/1998 12:00:00 AM
10853,BLAUS,1/27/1998 12:00:00 AM,2/3/1998 12:00:00 AM
10872,GODOS,2/5/1998 12:00:00 AM,2/9/1998 12:00:00 AM
10874,GODOS,2/6/1998 12:00:00 AM,2/11/1998 12:00:00 AM
10881,CACTU,2/11/1998 12:00:00 AM,2/18/1998 12:00:00 AM
10881,CACTU,2/11/1998 12:00:00 AM,2/18/1998 12:00:00 AM
10887,GALED,2/13/1998 12:00:00 AM,2/16/1998 12:00:00 AM
10892,MAISD,2/17/1998 12:00:00 AM,2/19/1998 12:00:00 AM
10896,MAISD,2/19/1998 12:00:00 AM,2/27/1998 12:00:00 AM
10928,GALED,3/5/1998 12:00:00 AM,3/18/1998 12:00:00 AM
10937,CACTU,3/10/1998 12:00:00 AM,3/13/1998 12:00:00 AM
10952,ALFKI,3/16/1998 12:00:00 AM,3/24/1998 12:00:00 AM
10969,COMMI,3/23/1998 12:00:00 AM,3/30/1998 12:00:00 AM
10987,EASTC,3/31/1998 12:00:00 AM,4/6/1998 12:00:00 AM
11026,FRANS,4/15/1998 12:00:00 AM,4/28/1998 12:00:00 AM
11036,DRACD,4/20/1998 12:00:00 AM,4/22/1998 12:00:00 AM
11042,COMMI,4/22/1998 12:00:00 AM,5/1/1998 12:00:00 AM
END ORDERS

ORDER DETAILS
10250,41,7.7000,10,0
10250,51,42.4000,35,0.15
10250,65,16.8000,15,0.15
10253,31,10.0000,20,0
10253,39,14.4000,42,0
10253,49,16.0000,40,0
10254,24,3.6000,15,0.15
10254,55,19.2000,21,0.15
10254,74,8.0000,21,0
10257,27,35.1000,25,0
10257,39,14.4000,6,0
10257,77,10.4000,15,0
10258,2,15.2000,50,0.2
10258,5,17.0000,65,0.2
10258,32,25.6000,6,0.2
10263,16,13.9000,60,0.25
10263,24,3.6000,28,0
10263,30,20.7000,60,0.25
10263,74,8.0000,36,0.25
10264,2,15.2000,35,0
10264,41,7.7000,25,0.15
10265,17,31.2000,30,0
10265,70,12.0000,20,0
10267,40,14.7000,50,0
10267,59,44.0000,70,0.15
10267,76,14.4000,15,0.15
10275,24,3.6000,12,0.05
10275,59,44.0000,6,0.05
10278,44,15.5000,16,0
10278,59,44.0000,15,0
10278,63,35.1000,8,0
10278,73,12.0000,25,0
10279,17,31.2000,15,0.25
10280,24,3.6000,12,0
10280,55,19.2000,20,0
10280,75,6.2000,30,0
10283,15,12.4000,20,0
10283,19,7.3000,18,0
10283,60,27.2000,35,0
10283,72,27.8000,3,0
10284,27,35.1000,15,0.25
10284,44,15.5000,21,0
10284,60,27.2000,20,0.25
10284,67,11.2000,5,0.25
10289,3,8.0000,30,0
10289,64,26.6000,9,0
10290,5,17.0000,20,0
10290,29,99.0000,15,0
10290,49,16.0000,15,0
10290,77,10.4000,10,0
10296,11,16.8000,12,0
10296,16,13.9000,30,0
10296,69,28.8000,15,0
10297,39,14.4000,60,0
10297,72,27.8000,20,0
10297,72,27.8000,20,0
10298,2,15.2000,40,0
10298,36,15.2000,40,0.25
10298,59,44.0000,30,0.25
10298,62,39.4000,15,0
10300,66,13.6000,30,0
10300,68,10.0000,20,0
10303,40,14.7000,40,0.1
10303,65,16.8000,30,0.1
10303,68,10.0000,15,0.1
10307,62,39.4000,10,0
10307,68,10.0000,3,0
10309,4,17.6000,20,0
10309,6,20.0000,30,0
10309,42,11.2000,2,0
10309,43,36.8000,20,0
10309,71,17.2000,3,0
10315,34,11.2000,14,0
10315,70,12.0000,30,0
10317,1,14.4000,20,0
10318,41,7.7000,20,0
10318,76,14.4000,6,0
10321,35,14.4000,10,0
10323,15,12.4000,5,0
10323,25,11.2000,4,0
10323,39,14.4000,4,0
10325,6,20.0000,6,0
10325,13,4.8000,12,0
10325,14,18.6000,9,0
10325,31,10.0000,4,0
10325,72,27.8000,40,0
10327,2,15.2000,25,0.2
10327,11,16.8000,50,0.2
10327,30,20.7000,35,0.2
10327,58,10.6000,30,0.2
10328,59,44.0000,9,0
10328,65,16.8000,40,0
10328,68,10.0000,10,0
10330,26,24.9000,50,0.15
10330,72,27.8000,25,0.15
10331,54,5.9000,15,0
10335,2,15.2000,7,0.2
10335,31,10.0000,25,0.2
10335,32,25.6000,6,0.2
10335,51,42.4000,48,0.2
10337,23,7.2000,40,0
10337,26,24.9000,24,0
10337,36,15.2000,20,0
10337,37,20.8000,28,0
10337,72,27.8000,25,0
10340,18,50.0000,20,0.05
10340,41,7.7000,12,0.05
10340,43,36.8000,40,0.05
10342,2,15.2000,24,0.2
10342,31,10.0000,56,0.2
10342,36,15.2000,40,0.2
10342,55,19.2000,40,0.2
10343,64,26.6000,50,0
10343,68,10.0000,4,0.05
10343,76,14.4000,15,0
10347,25,11.2000,10,0
10347,39,14.4000,50,0.15
10347,40,14.7000,4,0
10347,75,6.2000,6,0.15
10350,50,13.0000,15,0.1
10350,69,28.8000,18,0.1
10351,38,210.8000,20,0.05
10351,41,7.7000,13,0
10351,44,15.5000,77,0.05
10351,65,16.8000,10,0.05
10351,65,16.8000,10,0.05
10352,24,3.6000,10,0
10352,54,5.9000,20,0.15
10355,24,3.6000,25,0
10355,57,15.6000,25,0
10357,10,24.8000,30,0.2
10357,26,24.9000,16,0
10357,60,27.2000,8,0.2
10358,24,3.6000,10,0.05
10358,34,11.2000,10,0.05
10358,36,15.2000,20,0.05
10360,28,36.4000,30,0
10360,29,99.0000,35,0
10360,38,210.8000,10,0
10360,49,16.0000,35,0
10360,54,5.9000,28,0
10362,25,11.2000,50,0
10362,51,42.4000,20,0
10362,54,5.9000,24,0
10363,31,10.0000,20,0
10363,75,6.2000,12,0
10363,76,14.4000,12,0
10364,69,28.8000,30,0
10364,71,17.2000,5,0
10365,11,16.8000,24,0
10366,65,16.8000,5,0
10366,77,10.4000,5,0
10368,21,8.0000,5,0.1
10368,28,36.4000,13,0.1
10368,57,15.6000,25,0
10368,64,26.6000,35,0.1
10370,1,14.4000,15,0.15
10370,64,26.6000,30,0
10370,74,8.0000,20,0.15
10371,36,15.2000,6,0.2
10373,58,10.6000,80,0.2
10373,71,17.2000,50,0.2
10375,14,18.6000,15,0
10375,54,5.9000,10,0
10378,71,17.2000,6,0
10380,30,20.7000,18,0.1
10380,53,26.2000,20,0.1
10380,60,27.2000,6,0.1
10380,70,12.0000,30,0
10381,74,8.0000,14,0
10382,5,17.0000,32,0
10382,18,50.0000,9,0
10382,29,99.0000,14,0
10382,33,2.0000,60,0
10382,74,8.0000,50,0
10383,13,4.8000,20,0
10383,50,13.0000,15,0
10383,56,30.4000,20,0
10384,20,64.8000,28,0
10384,60,27.2000,15,0
10386,24,3.6000,15,0
10386,34,11.2000,10,0
10389,10,24.8000,16,0
10389,55,19.2000,15,0
10389,62,39.4000,20,0
10389,70,12.0000,30,0
10391,13,4.8000,18,0
10394,13,4.8000,10,0
10394,62,39.4000,10,0
10395,46,9.6000,28,0.1
10395,53,26.2000,70,0.1
10395,69,28.8000,8,0
10396,23,7.2000,40,0
10396,71,17.2000,60,0
10396,72,27.8000,21,0
10400,29,99.0000,21,0
10400,35,14.4000,35,0
10400,49,16.0000,30,0
10404,26,24.9000,30,0.05
10404,42,11.2000,40,0.05
10404,49,16.0000,30,0.05
10405,3,8.0000,50,0
10408,37,20.8000,10,0
10408,54,5.9000,6,0
10408,62,39.4000,35,0
10410,33,2.0000,49,0
10410,59,44.0000,16,0
10411,41,7.7000,25,0.2
10411,44,15.5000,40,0.2
10411,59,44.0000,9,0.2
10413,1,14.4000,24,0
10413,62,39.4000,40,0
10413,76,14.4000,14,0
10414,19,7.3000,18,0.05
10414,33,2.0000,50,0
10415,17,31.2000,2,0
10415,33,2.0000,20,0
10422,26,24.9000,2,0
10423,31,10.0000,14,0
10423,59,44.0000,20,0
10425,55,19.2000,10,0.25
10425,76,14.4000,20,0.25
10426,56,30.4000,5,0
10426,64,26.6000,7,0
10431,17,31.2000,50,0.25
10431,40,14.7000,50,0.25
10431,47,7.6000,30,0.25
10434,11,16.8000,6,0
10434,76,14.4000,18,0.15
10436,46,9.6000,5,0
10436,56,30.4000,40,0.1
10436,64,26.6000,30,0.1
10436,75,6.2000,24,0.1
10444,17,31.2000,10,0
10444,26,24.9000,15,0
10444,35,14.4000,8,0
10444,41,7.7000,30,0
10445,39,14.4000,6,0
10445,54,5.9000,15,0
10449,10,24.8000,14,0
10449,52,5.6000,20,0
10449,62,39.4000,35,0
10453,48,10.2000,15,0.1
10453,70,12.0000,25,0.1
10456,21,8.0000,40,0.15
10456,49,16.0000,21,0.15
10457,59,44.0000,36,0
10460,68,10.0000,21,0.25
10460,75,6.2000,4,0.25
10464,4,17.6000,16,0.2
10464,43,36.8000,3,0
10464,56,30.4000,30,0.2
10464,60,27.2000,20,0
10466,11,16.8000,10,0
10466,46,9.6000,5,0
10467,24,3.6000,28,0
10467,25,11.2000,12,0
10468,30,20.7000,8,0
10468,43,36.8000,15,0
10470,18,50.0000,30,0
10470,23,7.2000,15,0
10470,64,26.6000,8,0
10471,7,24.0000,30,0
10471,56,30.4000,20,0
10473,33,2.0000,12,0
10473,71,17.2000,12,0
10476,55,19.2000,2,0.05
10476,70,12.0000,12,0
10480,47,7.6000,30,0
10480,59,44.0000,12,0
10484,21,8.0000,14,0
10484,40,14.7000,10,0
10484,51,42.4000,3,0
10485,2,15.2000,20,0.1
10485,3,8.0000,20,0.1
10485,55,19.2000,30,0.1
10485,70,12.0000,60,0.1
10486,11,16.8000,5,0
10486,51,42.4000,25,0
10486,74,8.0000,16,0
10488,59,44.0000,30,0
10488,73,12.0000,20,0.2
10490,59,44.0000,60,0
10490,68,10.0000,30,0
10490,75,6.2000,36,0
10491,44,15.5000,15,0.15
10491,77,10.4000,7,0.15
10492,25,11.2000,60,0.05
10492,42,11.2000,20,0.05
10494,56,30.4000,30,0
10497,56,30.4000,14,0
10497,72,27.8000,25,0
10497,77,10.4000,25,0
10501,54,7.4500,20,0
10507,43,46.0000,15,0.15
10507,48,12.7500,15,0.15
10509,28,45.6000,3,0
10511,4,22.0000,50,0.15
10511,7,30.0000,50,0.15
10511,8,40.0000,10,0.15
10512,24,4.5000,10,0.15
10512,46,12.0000,9,0.15
10512,47,9.5000,6,0.15
10512,60,34.0000,12,0.15
10519,10,31.0000,16,0.05
10519,56,38.0000,40,0
10519,60,34.0000,10,0.05
10521,35,18.0000,3,0
10521,41,9.6500,10,0
10521,68,12.5000,6,0
10522,1,18.0000,40,0.2
10522,8,40.0000,24,0
10522,30,25.8900,20,0.2
10522,40,18.4000,25,0.2
10528,11,21.0000,3,0
10528,33,2.5000,8,0.2
10528,72,34.8000,9,0
10529,55,24.0000,14,0
10529,68,12.5000,20,0
10529,69,36.0000,10,0
10532,30,25.8900,15,0
10532,66,17.0000,24,0
10535,11,21.0000,50,0.1
10535,40,18.4000,10,0.1
10535,57,19.5000,5,0.1
10535,59,55.0000,15,0.1
10538,70,15.0000,7,0
10538,72,34.8000,1,0
10539,13,6.0000,8,0
10539,21,10.0000,15,0
10539,33,2.5000,15,0
10539,49,20.0000,6,0
10539,49,20.0000,6,0
10541,24,4.5000,35,0.1
10541,38,263.5000,4,0.1
10541,65,21.0500,36,0.1
10541,71,21.5000,9,0.1
10544,28,45.6000,7,0
10544,67,14.0000,7,0
10550,17,39.0000,8,0.1
10550,19,9.2000,10,0
10550,21,10.0000,6,0.1
10550,61,28.5000,10,0.1
10551,16,17.4500,40,0.15
10551,35,18.0000,20,0.15
10551,44,19.4500,40,0
10558,47,9.5000,25,0
10558,51,53.0000,20,0
10558,52,7.0000,30,0
10558,53,32.8000,18,0
10558,73,15.0000,3,0
10568,10,31.0000,5,0
10573,17,39.0000,18,0
10573,34,14.0000,40,0
10573,53,32.8000,25,0
10581,75,7.7500,50,0.2
10582,57,19.5000,4,0
10582,76,18.0000,14,0
10589,35,18.0000,4,0
10600,54,7.4500,4,0
10600,73,15.0000,30,0
10614,11,21.0000,14,0
10614,21,10.0000,8,0
10614,39,18.0000,5,0
10616,38,263.5000,15,0.05
10616,56,38.0000,14,0
10616,70,15.0000,15,0.05
10616,71,21.5000,15,0.05
10617,59,55.0000,30,0.15
10621,19,9.2000,5,0
10621,23,9.0000,10,0
10621,70,15.0000,20,0
10621,71,21.5000,15,0
10629,29,123.7900,20,0
10629,64,33.2500,9,0
10634,7,30.0000,35,0
10634,18,62.5000,50,0
10634,51,53.0000,15,0
10634,75,7.7500,2,0
10635,4,22.0000,10,0.1
10635,5,21.3500,15,0.1
10635,22,21.0000,40,0
10638,45,9.5000,20,0
10638,65,21.0500,21,0
10638,72,34.8000,60,0
10643,28,45.6000,15,0.25
10643,39,18.0000,21,0.25
10643,46,12.0000,2,0.25
10645,18,62.5000,20,0
10645,36,19.0000,15,0
10649,28,45.6000,20,0
10649,72,34.8000,15,0
10652,30,25.8900,2,0.25
10652,42,14.0000,20,0
10656,14,23.2500,3,0.1
10656,44,19.4500,28,0.1
10656,47,9.5000,6,0.1
10660,20,81.0000,21,0
10662,68,12.5000,10,0
10665,51,53.0000,20,0
10665,59,55.0000,1,0
10665,76,18.0000,10,0
10665,76,18.0000,10,0
10677,26,31.2300,30,0.15
10677,33,2.5000,8,0.15
10685,10,31.0000,20,0
10685,41,9.6500,4,0
10685,47,9.5000,15,0
10690,56,38.0000,20,0.25
10690,77,13.0000,30,0.25
10692,63,43.9000,20,0
10697,19,9.2000,7,0.25
10697,35,18.0000,9,0.25
10697,58,13.2500,30,0.25
10697,70,15.0000,30,0.25
10702,3,10.0000,6,0
10702,76,18.0000,15,0
10707,55,24.0000,21,0
10707,57,19.5000,40,0
10707,70,15.0000,28,0.15
10709,8,40.0000,40,0
10709,51,53.0000,28,0
10709,60,34.0000,10,0
10710,19,9.2000,5,0
10710,47,9.5000,5,0
10726,4,22.0000,25,0
10726,11,21.0000,5,0
10729,1,18.0000,50,0
10729,21,10.0000,30,0
10729,50,16.2500,40,0
10731,21,10.0000,40,0.05
10731,51,53.0000,30,0.05
10734,6,25.0000,30,0
10734,30,25.8900,15,0
10734,76,18.0000,20,0
10746,13,6.0000,6,0
10746,42,14.0000,28,0
10746,62,49.3000,9,0
10746,69,36.0000,40,0
10753,45,9.5000,4,0
10753,74,10.0000,5,0
10760,25,14.0000,12,0.25
10760,27,43.9000,40,0
10760,43,46.0000,30,0.25
10763,21,10.0000,40,0
10763,22,21.0000,6,0
10763,24,4.5000,20,0
10782,31,12.5000,1,0
10789,18,62.5000,30,0
10789,35,18.0000,15,0
10789,63,43.9000,30,0
10789,68,12.5000,18,0
10797,11,21.0000,20,0
10807,40,18.4000,1,0
10819,43,46.0000,7,0
10819,75,7.7500,20,0
10825,26,31.2300,12,0
10825,53,32.8000,20,0
10835,59,55.0000,15,0
10835,77,13.0000,2,0.2
10853,18,62.5000,10,0
10872,55,24.0000,10,0.05
10872,62,49.3000,20,0.05
10872,64,33.2500,15,0.05
10872,65,21.0500,21,0.05
10874,10,31.0000,10,0
10881,73,15.0000,10,0
10887,25,14.0000,5,0
10892,59,55.0000,40,0.05
10896,45,9.5000,15,0
10896,56,38.0000,16,0
10928,47,9.5000,5,0
10928,47,9.5000,5,0
10928,76,18.0000,5,0
10937,28,45.6000,8,0
10937,34,14.0000,20,0
10952,6,25.0000,16,0.05
10952,28,45.6000,2,0
10969,46,12.0000,9,0
10987,7,30.0000,60,0
10987,43,46.0000,6,0
10987,72,34.8000,20,0
11026,18,62.5000,8,0
11026,51,53.0000,10,0
11036,13,6.0000,7,0
11036,59,55.0000,30,0
11042,44,19.4500,15,0
11042,61,28.5000,4,0
END ORDER DETAILS

PRODUCTS
1,Chai,18.0000
2,Chang,19.0000
3,Aniseed Syrup,10.0000
4,Chef Anton's Cajun Seasoning,22.0000
5,Chef Anton's Gumbo Mix,21.3500
6,Grandma's Boysenberry Spread,25.0000
7,Uncle Bob's Organic Dried Pears,30.0000
8,Northwoods Cranberry Sauce,40.0000
9,Mishi Kobe Niku,97.0000
10,Ikura,31.0000
11,Queso Cabrales,21.0000
12,Queso Manchego La Pastora,38.0000
13,Konbu,6.0000
14,Tofu,23.2500
15,Genen Shouyu,15.5000
16,Pavlova,17.4500
17,Alice Mutton,39.0000
18,Carnarvon Tigers,62.5000
19,Teatime Chocolate Biscuits,9.2000
20,Sir Rodney's Marmalade,81.0000
21,Sir Rodney's Scones,10.0000
22,Gustaf's Knäckebröd,21.0000
23,Tunnbröd,9.0000
24,Guaraná Fantástica,4.5000
25,NuNuCa Nuß-Nougat-Creme,14.0000
26,Gumbär Gummibärchen,31.2300
27,Schoggi Schokolade,43.9000
28,Rössle Sauerkraut,45.6000
29,Thüringer Rostbratwurst,123.7900
30,Nord-Ost Matjeshering,25.8900
31,Gorgonzola Telino,12.5000
32,Mascarpone Fabioli,32.0000
33,Geitost,2.5000
34,Sasquatch Ale,14.0000
35,Steeleye Stout,18.0000
36,Inlagd Sill,19.0000
37,Gravad lax,26.0000
38,Côte de Blaye,263.5000
39,Chartreuse verte,18.0000
40,Boston Crab Meat,18.4000
41,Jack's New England Clam Chowder,9.6500
42,Singaporean Hokkien Fried Mee,14.0000
43,Ipoh Coffee,46.0000
44,Gula Malacca,19.4500
45,Rogede sild,9.5000
46,Spegesild,12.0000
47,Zaanse koeken,9.5000
48,Chocolade,12.7500
49,Maxilaku,20.0000
50,Valkoinen suklaa,16.2500
51,Manjimup Dried Apples,53.0000
51,Manjimup Dried Apples,53.0000
52,Filo Mix,7.0000
53,Perth Pasties,32.8000
54,Tourtière,7.4500
55,Pâté chinois,24.0000
56,Gnocchi di nonna Alice,38.0000
57,Ravioli Angelo,19.5000
58,Escargots de Bourgogne,13.2500
59,Raclette Courdavault,55.0000
60,Camembert Pierrot,34.0000
61,Sirop d'érable,28.5000
62,Tarte au sucre,49.3000
63,Vegie-spread,43.9000
64,Wimmers gute Semmelknödel,33.2500
65,Louisiana Fiery Hot Pepper Sauce,21.0500
66,Louisiana Hot Spiced Okra,17.0000
67,Laughing Lumberjack Lager,14.0000
68,Scottish Longbreads,12.5000
69,Gudbrandsdalsost,36.0000
70,Outback Lager,15.0000
71,Flotemysost,21.5000
72,Mozzarella di Giovanni,34.8000
73,Röd Kaviar,15.0000
74,Longlife Tofu,10.0000
75,Rhönbräu Klosterbier,7.7500
76,Lakkalikööri,18.0000
77,Original Frankfurter grüne Soße,13.0000
END PRODUCTS

Vea también
Parallel LINQ (PLINQ)
Estructuras de datos para la programación paralela
16/09/2020 • 7 minutes to read • Edit Online

.NET Framework 4 incorpora varios tipos nuevos que son útiles en la programación paralela, incluido un conjunto
de clases de colecciones simultáneas, primitivos de sincronización ligera y tipos para la inicialización diferida.
Puede usar estos tipos con cualquier código de aplicación multiproceso, como la biblioteca TPL y PLINQ.

Clases de colecciones simultáneas


Las clases de colecciones del espacio de nombres System.Collections.Concurrent ofrece operaciones de agregar y
quitar seguras para subprocesos que evitan los bloqueos siempre que sea posible y usan el bloqueo específico
cuando los bloqueos son necesarios. A diferencia de las colecciones que se introdujeron en las versiones 1.0 y 2.0
de .NET Framework, una clase de colecciones simultáneas no requiere que el código de usuario adopte ningún
bloqueo cuando accede a los elementos. Las clases de colecciones simultáneas pueden mejorar significativamente
el rendimiento a través de tipos como System.Collections.ArrayList y System.Collections.Generic.List<T> (con
bloqueo implementado por el usuario) en escenarios donde varios subprocesos agregarán y quitarán elementos
de una colección.
En la tabla siguiente se enumeran las nuevas clases de colecciones simultáneas:

T IP O DESC RIP T IO N

System.Collections.Concurrent.BlockingCollection<T> Proporciona capacidades de bloqueo y establecimiento de


límites en colecciones seguras para subprocesos que
implementan
System.Collections.Concurrent.IProducerConsumerCollection
<T>. Los subprocesos de productor se bloquean si no hay
ranuras disponibles o si la colección está llena. Los
subprocesos de consumidor se bloquean si la colección está
vacía. Este tipo también admite el acceso sin bloqueo de
productores y consumidores. BlockingCollection<T> puede
utilizarse como una clase base o una memoria auxiliar para
proporcionar el bloqueo y el establecimiento de límite de
cualquier clase de colección que admite IEnumerable<T>.

System.Collections.Concurrent.ConcurrentBag<T> Una implementación de contenedor segura para subprocesos


que ofrece operaciones add y get escalables.

System.Collections.Concurrent.ConcurrentDictionary<TKey,TV Un tipo de diccionario simultáneo y escalable.


alue>

System.Collections.Concurrent.ConcurrentQueue<T> Un tipo de cola FIFO simultánea y escalable.

System.Collections.Concurrent.ConcurrentStack<T> Un tipo de pila LIFO simultánea y escalable.

Para obtener más información, consulte Colecciones seguras para subprocesos.

Primitivos de sincronización
Los nuevos primitivos de sincronización del espacio de nombres System.Threading permiten un rendimiento más
rápido y una simultaneidad específica al evitar mecanismos de bloqueo caros que se encuentran en el código
multithreading heredado. Algunos de los nuevos tipos, como System.Threading.Barrier y
System.Threading.CountdownEvent, no tienen ningún homólogo en versiones anteriores de .NET Framework.
En la tabla siguiente se enumeran los nuevos tipos de sincronización:

T IP O DESC RIP T IO N

System.Threading.Barrier Permite que varios subprocesos funcionen en un algoritmo en


paralelo proporcionando un punto en el que cada tarea
pueda señalizar su llegada y después bloquearse hasta que
llegan algunas tareas o todas. Para más información, consulte
Barrier.

System.Threading.CountdownEvent Simplifica los escenarios de bifurcación y combinación


proporcionando un mecanismo sencillo de encuentro. Para
más información, vea CountdownEvent.

System.Threading.ManualResetEventSlim Un primitivo de sincronización similar a


System.Threading.ManualResetEvent. ManualResetEventSlim
es ligero, pero solo puede utilizarse para la comunicación
dentro de un proceso.

System.Threading.SemaphoreSlim Un primitivo de sincronización que limita el número de


subprocesos que acceden simultáneamente a un recurso o a
un conjunto de recursos. Para más información, vea
Semaphore y SemaphoreSlim.

System.Threading.SpinLock Un primitivo de bloqueo de exclusión mutua genera el


subproceso que trata de obtener un bloqueo para esperar en
un bucle, o girar, durante un período de tiempo antes de
producir su cuanto. En escenarios en los que se prevé que la
espera del bloqueo sea corta, SpinLock ofrece mayor
rendimiento que otras formas de bloqueo. Para más
información, vea SpinLock.

System.Threading.SpinWait Un tipo pequeño y ligero que girará durante un tiempo


especificado y, finalmente, colocará el subproceso en un
estado de espera si se supera el número de giros. Para más
información, vea SpinWait.

Para obtener más información, consulte:


Utilizar SpinLock para la sincronización de bajo nivel
Sincronizar operaciones simultáneas con una clase Barrier

Clases de inicialización diferida


Con la inicialización diferida, no se asigna la memoria para un objeto hasta que se necesite. La inicialización
diferida puede mejorar el rendimiento al distribuir las asignaciones de objetos uniformemente a lo largo de la
duración de un programa. Puede habilitar la inicialización diferida para cualquier tipo personalizado ajustando el
tipo Lazy<T>.
En la tabla siguiente se enumeran los tipos de inicialización diferida:

T IP O DESC RIP T IO N

System.Lazy<T> Proporciona una inicialización diferida segura para


subprocesos y ligera.
T IP O DESC RIP T IO N

System.Threading.ThreadLocal<T> Proporciona un valor inicializado de forma diferida por cada


subproceso, donde cada subproceso invoca de forma diferida
la función de inicialización.

System.Threading.LazyInitializer Proporciona métodos estáticos que evitan la necesidad de


asignar una instancia de inicialización diferida y dedicada. En
su lugar, usan referencias para garantizar que los destinos se
han inicializado a medida que se accede a ellos.

Para obtener más información, vea Inicialización diferida.

Agregar excepciones
El tipo System.AggregateException puede usarse para capturar varias excepciones que se producen
simultáneamente en subprocesos independientes y devolverlas al subproceso combinado como una única
excepción. Los tipos System.Threading.Tasks.Task y System.Threading.Tasks.Parallel y PLINQ usan
AggregateException de forma amplia para este propósito. Para obtener más información, vea Control de
excepciones y Cómo: Controlar excepciones en una consulta PLINQ.

Vea también
System.Collections.Concurrent
System.Threading
Programación en paralelo
Herramientas de diagnóstico paralelo
16/09/2020 • 2 minutes to read • Edit Online

Visual Studio proporciona amplia compatibilidad para depurar aplicaciones de varios subprocesos y generar
perfiles de estas.

Depuración
El depurador de Visual Studio agrega ventanas nuevas para la depuración de aplicaciones paralelas. Para obtener
más información, consulta los temas siguientes:
Uso de la ventana Pilas paralelas
Usar la ventana Tareas
Tutorial: Depurar una aplicación paralela.

Generación de perfiles
Las vistas del informe Visualizador de simultaneidad permite visualizar cómo los subprocesos de un programa en
paralelo interactúan entre sí y con subprocesos de otros procesos del sistema. Para más información, consulte
Visualizador de simultaneidad.

Vea también
Programación en paralelo
Particionadores personalizados para PLINQ y TPL
16/09/2020 • 20 minutes to read • Edit Online

Para paralelizar una operación en un origen de datos, uno de los pasos esenciales es particionar el origen en
varias secciones a las que pueden acceder varios subprocesos al mismo tiempo. PLINQ y la biblioteca TPL
proporcionan particionadores predeterminados que funcionan de manera transparente al escribir un bucle
ForEach o una consulta en paralelo. Para escenarios más avanzados, puede conectar su propio particionador.

Tipos de particiones
Hay muchas maneras de particionar un origen de datos. En los enfoques más eficaces, varios subprocesos
cooperan para procesar la secuencia de origen original, en lugar de separar físicamente el origen en varias
secuencias secundarias. Para matrices y otros orígenes indexados como colecciones IList donde la longitud se
conoce de antemano, la creación de particiones por rangos es el tipo más sencillo de creación de particiones.
Cada subproceso recibe índices exclusivos de apertura y cierre, para poder procesar su rango del origen sin
sobrescribir subprocesos ni ser sobrescrito por algún subproceso. La única sobrecarga implicada en la creación
de particiones por rangos es el trabajo inicial de crear los rangos; después de eso, no se requiere ninguna
sincronización adicional. Por lo tanto, puede proporcionar un buen rendimiento siempre y cuando la carga de
trabajo se divida de manera uniforme. Una desventaja de la creación de particiones por rangos es que, si un
subproceso finaliza de forma anticipada, no puede ayudar a los otros subprocesos a finalizar su trabajo.
Para listas vinculadas u otras colecciones cuya longitud no se conoce, puede usar la creación de particiones por
fragmentos. En la creación de particiones por fragmentos, cada subproceso o tarea de una consulta o bucle
paralelos consumen algunos elementos de origen de un fragmento, los procesan y vuelven a activarse para
recuperar elementos adicionales. El particionador garantiza que todos los elementos se distribuyan y que no se
dupliquen. Un fragmento puede tener cualquier tamaño. Por ejemplo, el particionador que se muestra en Cómo:
Implementar las particiones dinámicas crea fragmentos que contienen un solo elemento. Siempre que los
fragmentos no sean demasiado grandes, este tipo de creación de particiones tiene un equilibrio de carga
inherente, porque la asignación de elementos a los subprocesos no es predeterminada. Sin embargo, el
particionador no incurre en sobrecarga de sincronización cada vez que el subproceso necesita obtener otro
fragmento. La cantidad de sincronización en que se incurre en estos casos es inversamente proporcional al
tamaño de los fragmentos.
En general, la creación de particiones por rangos solo es más rápida cuando el tiempo de ejecución del delegado
es de bajo a moderado y el origen tiene un gran número de elementos y el trabajo total de cada partición es
más o menos equivalente. Por tanto, la creación de particiones por fragmentos suele ser más rápida en la
mayoría de los casos. En los orígenes con un número reducido de elementos o con tiempos de ejecución más
largos para el delegado, el rendimiento de la creación de particiones por fragmentos y rangos es prácticamente
el mismo.
Los particionadores de TPL también admiten un número de particiones dinámico. Esto significa que se pueden
crear particiones sobre la marcha, por ejemplo, cuando el bucle ForEach genera una nueva tarea. Esta
característica permite al particionador escalarse junto con el propio bucle. Los particionadores dinámicos
también tienen un equilibrio de carga inherente. Cuando se crea un particionador personalizado, debe admitir la
creación de particiones dinámicas para poder usarlas desde un bucle ForEach.
Configuración de particionadores de equilibrio de carga para PLINQ
Algunas sobrecargas del método Partitioner.Create permiten crear un particionador para una matriz o un origen
IList y especificar si debe intentar equilibrar la carga de trabajo entre los subprocesos. Cuando se configura el
particionador para equilibrar la carga, se emplea la creación de particiones por fragmentos, y los elementos se
entregan a cada partición en pequeños fragmentos a medida que se solicitan. Este enfoque ayuda a garantizar
que todas las particiones tienen elementos para procesar hasta que se completa totalmente un bucle o una
consulta. Se puede usar una sobrecarga adicional para proporcionar particiones de equilibrio de carga de
cualquier origen IEnumerable.
En general, el equilibrio de carga requiere que las particiones soliciten elementos con relativa frecuencia desde
el particionador. Por el contrario, un particionador que crea particiones estáticas puede asignar todos los
elementos a cada particionador al mismo tiempo mediante el uso de la creación de particiones por rangos o por
fragmentos. Esto requiere menos sobrecarga que el equilibrio de carga, pero es posible que tarde más tiempo
en ejecutarse si un subproceso termina significativamente con más trabajo que los demás. De forma
predeterminada, cuando se pasa una interfaz IList o una matriz, PLINQ siempre utiliza la creación de particiones
por rangos sin equilibrio de carga. Para habilitar el equilibrio de carga para PLINQ, use el método
Partitioner.Create , tal como se muestra en el ejemplo siguiente.

// Static partitioning requires indexable source. Load balancing


// can use any IEnumerable.
var nums = Enumerable.Range(0, 100000000).ToArray();

// Create a load-balancing partitioner. Or specify false for static partitioning.


Partitioner<int> customPartitioner = Partitioner.Create(nums, true);

// The partitioner is the query's data source.


var q = from x in customPartitioner.AsParallel()
select x * Math.PI;

q.ForAll((x) =>
{
ProcessData(x);
});

' Static number of partitions requires indexable source.


Dim nums = Enumerable.Range(0, 100000000).ToArray()

' Create a load-balancing partitioner. Or specify false For Shared partitioning.


Dim customPartitioner = Partitioner.Create(nums, True)

' The partitioner is the query's data source.


Dim q = From x In customPartitioner.AsParallel()
Select x * Math.PI

q.ForAll(Sub(x) ProcessData(x))

La mejor forma de determinar si usar el equilibrio de carga en un escenario concreto es experimentar y medir el
tiempo que las operaciones tardan en completarse con cargas representativas y configuraciones de equipos. Por
ejemplo, el particionamiento estático podría proporcionar un aumento significativo de la velocidad en un equipo
de varios núcleos que tenga solo unos pocos núcleos, pero podría dar como resultado una ralentización de los
equipos que tienen relativamente muchos núcleos.
En la siguiente tabla se enumeran las sobrecargas disponibles del método Create. Estos particionadores no están
limitados para utilizarse solo con PLINQ o Task. También se pueden utilizar con cualquier construcción paralela
personalizada.

SO B REC A RGA USA EL EQ UIL IB RIO DE C A RGA

Create<TSource>(IEnumerable<TSource>) Siempre
SO B REC A RGA USA EL EQ UIL IB RIO DE C A RGA

Create<TSource>(TSource[], Boolean) Cuando el argumento Boolean se especifica como true

Create<TSource>(IList<TSource>, Boolean) Cuando el argumento Boolean se especifica como true

Create(Int32, Int32) Nunca

Create(Int32, Int32, Int32) Nunca

Create(Int64, Int64) Nunca

Create(Int64, Int64, Int64) Nunca

Configuración de particionadores por rangos estáticos para Parallel.ForEach


En un bucle For, el cuerpo del bucle se proporciona al método como un delegado. El costo de invocar ese
delegado es aproximadamente el mismo que una llamada al método virtual. En algunos escenarios, el cuerpo de
un bucle paralelo podría ser lo suficientemente pequeño como para que el costo de la invocación del delegado
en cada iteración del bucle sea significativo. En estas situaciones, puede usar una de las sobrecargas Create para
crear una interfaz IEnumerable<T> de particiones por rangos de los elementos de origen. A continuación, puede
pasar esta colección de intervalos a un método ForEach cuyo cuerpo se compone de un bucle for normal. La
ventaja de este enfoque es que se incurre en el costo de invocación del delegado solo una vez por rango, en
lugar de una vez por elemento. En el siguiente ejemplo se muestra el patrón básico.
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static void Main()
{

// Source must be array or IList.


var source = Enumerable.Range(0, 100000).ToArray();

// Partition the entire source array.


var rangePartitioner = Partitioner.Create(0, source.Length);

double[] results = new double[source.Length];

// Loop over the partitions in parallel.


Parallel.ForEach(rangePartitioner, (range, loopState) =>
{
// Loop over each range element without a delegate invocation.
for (int i = range.Item1; i < range.Item2; i++)
{
results[i] = source[i] * Math.PI;
}
});

Console.WriteLine("Operation complete. Print results? y/n");


char input = Console.ReadKey().KeyChar;
if (input == 'y' || input == 'Y')
{
foreach(double d in results)
{
Console.Write("{0} ", d);
}
}
}
}
Imports System.Threading.Tasks
Imports System.Collections.Concurrent

Module PartitionDemo

Sub Main()
' Source must be array or IList.
Dim source = Enumerable.Range(0, 100000).ToArray()

' Partition the entire source array.


' Let the partitioner size the ranges.
Dim rangePartitioner = Partitioner.Create(0, source.Length)

Dim results(source.Length - 1) As Double

' Loop over the partitions in parallel. The Sub is invoked


' once per partition.
Parallel.ForEach(rangePartitioner, Sub(range, loopState)

' Loop over each range element without a delegate invocation.


For i As Integer = range.Item1 To range.Item2 - 1
results(i) = source(i) * Math.PI
Next
End Sub)
Console.WriteLine("Operation complete. Print results? y/n")
Dim input As Char = Console.ReadKey().KeyChar
If input = "y"c Or input = "Y"c Then
For Each d As Double In results
Console.Write("{0} ", d)
Next
End If

End Sub
End Module

Cada subproceso del bucle recibe su propio Tuple<T1,T2> que contiene los valores de índice de inicio y fin en el
subrango especificado. El bucle for interno usa los valores fromInclusive y toExclusive para recorrer en
bucle la matriz o IList directamente.
Una de las sobrecargas Create le permite especificar el tamaño de las particiones y el número de particiones.
Esta sobrecarga puede usarse en escenarios en los que el trabajo por elemento es tan bajo que incluso una
llamada a un método virtual por elemento tiene un impacto considerable en el rendimiento.

Particionadores personalizados
En algunos casos, podría merecer la pena o incluso ser necesario implementar un particionador propio. Por
ejemplo, podría tener una clase de colección personalizada que puede crear particiones de forma más eficaz que
los particionadores predeterminados, según su conocimiento de la estructura interna de la clase. O bien, puede
que desee crear particiones por rangos de tamaños diferentes basándose en su conocimiento de cuánto tiempo
se tardará en procesar los elementos en ubicaciones distintas de la colección de origen.
Para crear un particionador personalizado básico, derive una clase de
System.Collections.Concurrent.Partitioner<TSource> e invalide los métodos virtuales, tal como se describe en la
tabla siguiente.
GetPartitions El subproceso principal llama una vez a este método y se
devuelve IList(IEnumerator(TSource)). Cada subproceso de
trabajo del bucle o la consulta puede llamar a
GetEnumerator en la lista para recuperar IEnumerator<T>
a través de una partición distinta.

SupportsDynamicPartitions Devuelva true si implementa GetDynamicPartitions; en


caso contrario, false .

GetDynamicPartitions Si SupportsDynamicPartitions es true , opcionalmente, se


puede llamar a este método en lugar de a GetPartitions.

Si los resultados se pueden ordenar o precisa de acceso indexado a los elementos, realice una derivación de
System.Collections.Concurrent.OrderablePartitioner<TSource> e invalide los métodos virtuales como se
describe en la tabla siguiente.

GetPartitions El subproceso principal llama una vez a este método y se


devuelve IList(IEnumerator(TSource)) . Cada subproceso
de trabajo del bucle o la consulta puede llamar a
GetEnumerator en la lista para recuperar IEnumerator<T>
a través de una partición distinta.

SupportsDynamicPartitions Devuelva true si implementa GetDynamicPartitions; de lo


contrario, false.

GetDynamicPartitions Normalmente, esto simplemente llama a


GetOrderableDynamicPartitions.

GetOrderableDynamicPartitions Si SupportsDynamicPartitions es true , opcionalmente, se


puede llamar a este método en lugar de a GetPartitions.

En la tabla siguiente se proporcionan detalles adicionales sobre cómo los tres tipos de particionadores de
equilibrio de carga implementan la clase OrderablePartitioner<TSource>.

IL IST / M AT RIZ SIN IL IST / M AT RIZ C O N


M ÉTO DO / P RO P IEDA D EQ UIL IB RIO DE C A RGA EQ UIL IB RIO DE C A RGA IEN UM ERA B L E

GetOrderablePartitions Usa la creación de Usa la creación de Usa la creación de


particiones por rangos particiones por fragmentos particiones por fragmentos
optimizada para listas de la mediante la creación de un
clase partitionCount número estático de
especificada particiones.

OrderablePartitioner<TSour Genera una excepción no Usa la creación de Usa la creación de


ce>.GetOrderableDynamicP admitida particiones por fragmentos particiones por fragmentos
artitions para listas y particiones mediante la creación de un
dinámicas número dinámico de
particiones.

KeysOrderedInEachPartition Devuelve true Devuelve true Devuelve true

KeysOrderedAcrossPartition Devuelve true Devuelve false Devuelve false


s
IL IST / M AT RIZ SIN IL IST / M AT RIZ C O N
M ÉTO DO / P RO P IEDA D EQ UIL IB RIO DE C A RGA EQ UIL IB RIO DE C A RGA IEN UM ERA B L E

KeysNormalized Devuelve true Devuelve true Devuelve true

SupportsDynamicPartitions Devuelve false Devuelve true Devuelve true

Particiones dinámicas
Si pretende que el particionador se use en un método ForEach, debe tener la capacidad de devolver un número
dinámico de particiones. Esto significa que el particionador puede proporcionar un enumerador para una nueva
partición a petición en cualquier momento durante la ejecución del bucle. Básicamente, siempre que el bucle
agrega una nueva tarea en paralelo, solicita una nueva partición de esa tarea. Si necesita que los datos se
puedan ordenar, realice la derivación de System.Collections.Concurrent.OrderablePartitioner<TSource>, para
que a cada elemento de cada partición se le asigne un índice único.
Para obtener más información y un ejemplo, vea Cómo: Implementar las particiones dinámicas.
Contrato para particionadores
Al implementar un particionador personalizado, siga estas instrucciones para ayudar a garantizar la interacción
correcta con PLINQ y ForEach en la biblioteca TPL:
Si se llama a GetPartitions con un argumento de cero o menos para partitionsCount , se produce
ArgumentOutOfRangeException. Aunque PLINQ y TPL nunca pasarán una clase partitionCount igual a 0,
no obstante, se recomienda adoptar medidas preventivas para evitar esta posibilidad.
GetPartitions y GetOrderablePartitions siempre deben devolver una serie de particiones partitionsCount .
Si el particionador agota los datos y no puede crear tantas particiones como se han solicitado, el método
debe devolver un enumerador vacío para cada una de las particiones restantes. De lo contrario, PLINQ y
TPL producirán una excepción InvalidOperationException.
GetPartitions, GetOrderablePartitions, GetDynamicPartitions y GetOrderableDynamicPartitions nunca
deberían devolver null ( Nothing en Visual Basic). Si lo hacen, PLINQ/TPL producirán una excepción
InvalidOperationException.
Los métodos que devuelven particiones siempre deberían devolver particiones que puedan enumerar
completamente y de forma única el origen de datos. No debería haber ninguna duplicación del origen de
datos ni elementos omitidos a menos que lo requiera específicamente el diseño del particionador. Si no
se respeta esta regla, se puede alterar el orden de salida.
Los siguientes captadores booleanos deben devolver siempre con precisión los siguientes valores para
que no se altere el orden de salida:
KeysOrderedInEachPartition : cada partición devuelve elementos con índices de claves crecientes.
KeysOrderedAcrossPartitions : para todas las particiones que se devuelven, los índices de clave de
la partición i son más altos que los índices de clave de la partición i-1.
: todos los índices de claves aumentan ininterrumpidamente sin espacios,
KeysNormalized
empezando desde cero.
Todos los índices deben ser únicos. No puede haber índices duplicados. Si no se respeta esta regla, se
puede alterar el orden de salida.
Todos los índices deben ser no negativos. Si no se respeta esta regla, PLINQ/TPL pueden producir
excepciones.
Vea también
Programación en paralelo
Implementar las particiones dinámicas
Implementar un particionador para particionamiento estático
Cómo: Implementar las particiones dinámicas
16/09/2020 • 4 minutes to read • Edit Online

En el ejemplo siguiente se muestra cómo implementar una clase


System.Collections.Concurrent.OrderablePartitioner<TSource> personalizada que implementa la partición
dinámica y que puede usarse desde determinadas sobrecargas ForEach y desde PLINQ.

Ejemplo
Cada vez que se llama a una partición MoveNext en el enumerador, este proporciona la partición con un
elemento de lista. En el caso de PLINQ y ForEach, la partición es una instancia Task. Dado que las solicitudes se
producen simultáneamente en varios subprocesos, se sincroniza el acceso al índice actual.

//
// An orderable dynamic partitioner for lists
//
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Numerics;

class OrderableListPartitioner<TSource> : OrderablePartitioner<TSource>


{
private readonly IList<TSource> m_input;

// Must override to return true.


public override bool SupportsDynamicPartitions => true;

public OrderableListPartitioner(IList<TSource> input) : base(true, false, true) =>


m_input = input;

public override IList<IEnumerator<KeyValuePair<long, TSource>>> GetOrderablePartitions(int


partitionCount)
{
var dynamicPartitions = GetOrderableDynamicPartitions();
var partitions =
new IEnumerator<KeyValuePair<long, TSource>>[partitionCount];

for (int i = 0; i < partitionCount; i++)


{
partitions[i] = dynamicPartitions.GetEnumerator();
}
return partitions;
}

public override IEnumerable<KeyValuePair<long, TSource>> GetOrderableDynamicPartitions() =>


new ListDynamicPartitions(m_input);

private class ListDynamicPartitions : IEnumerable<KeyValuePair<long, TSource>>


{
private IList<TSource> m_input;
private int m_pos = 0;
internal ListDynamicPartitions(IList<TSource> input) =>
m_input = input;

public IEnumerator<KeyValuePair<long, TSource>> GetEnumerator()


{
while (true)
{
// Each task gets the next item in the list. The index is
// incremented in a thread-safe manner to avoid races.
int elemIndex = Interlocked.Increment(ref m_pos) - 1;

if (elemIndex >= m_input.Count)


{
yield break;
}

yield return new KeyValuePair<long, TSource>(


elemIndex, m_input[elemIndex]);
}
}

IEnumerator IEnumerable.GetEnumerator() =>


((IEnumerable<KeyValuePair<long, TSource>>)this).GetEnumerator();
}
}

class ConsumerClass
{
static void Main()
{
var nums = Enumerable.Range(0, 10000).ToArray();
OrderableListPartitioner<int> partitioner = new OrderableListPartitioner<int>(nums);

// Use with Parallel.ForEach


Parallel.ForEach(partitioner, (i) => Console.WriteLine(i));

// Use with PLINQ


var query = from num in partitioner.AsParallel()
where num % 2 == 0
select num;

foreach (var v in query)


Console.WriteLine(v);
}
}

Imports System.Threading
Imports System.Threading.Tasks
Imports System.Collections.Concurrent

Module Module1
Public Class OrderableListPartitioner(Of TSource)
Inherits OrderablePartitioner(Of TSource)

Private ReadOnly m_input As IList(Of TSource)

Public Sub New(ByVal input As IList(Of TSource))


MyBase.New(True, False, True)
m_input = input
End Sub

' Must override to return true.


Public Overrides ReadOnly Property SupportsDynamicPartitions As Boolean
Get
Return True
End Get
End Property

Public Overrides Function GetOrderablePartitions(ByVal partitionCount As Integer) As IList(Of


IEnumerator(Of KeyValuePair(Of Long, TSource)))
Dim dynamicPartitions = GetOrderableDynamicPartitions()
Dim partitions(partitionCount - 1) As IEnumerator(Of KeyValuePair(Of Long, TSource))

For i = 0 To partitionCount - 1
partitions(i) = dynamicPartitions.GetEnumerator()
Next

Return partitions
End Function

Public Overrides Function GetOrderableDynamicPartitions() As IEnumerable(Of KeyValuePair(Of Long,


TSource))
Return New ListDynamicPartitions(m_input)
End Function

Private Class ListDynamicPartitions


Implements IEnumerable(Of KeyValuePair(Of Long, TSource))

Private m_input As IList(Of TSource)

Friend Sub New(ByVal input As IList(Of TSource))


m_input = input
End Sub

Public Function GetEnumerator() As IEnumerator(Of KeyValuePair(Of Long, TSource)) Implements


IEnumerable(Of KeyValuePair(Of Long, TSource)).GetEnumerator
Return New ListDynamicPartitionsEnumerator(m_input)
End Function

Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator


Return CType(Me, IEnumerable).GetEnumerator()
End Function
End Class

Private Class ListDynamicPartitionsEnumerator


Implements IEnumerator(Of KeyValuePair(Of Long, TSource))

Private m_input As IList(Of TSource)


Shared m_pos As Integer = 0
Private m_current As KeyValuePair(Of Long, TSource)

Public Sub New(ByVal input As IList(Of TSource))


m_input = input
m_pos = 0
Me.disposedValue = False
End Sub

Public ReadOnly Property Current As KeyValuePair(Of Long, TSource) Implements IEnumerator(Of


KeyValuePair(Of Long, TSource)).Current
Get
Return m_current
End Get
End Property

Public ReadOnly Property Current1 As Object Implements IEnumerator.Current


Get
Return Me.Current
End Get
End Property

Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext


Dim elemIndex = Interlocked.Increment(m_pos) - 1
If elemIndex >= m_input.Count Then
Return False
End If
End If

m_current = New KeyValuePair(Of Long, TSource)(elemIndex, m_input(elemIndex))


Return True
End Function

Public Sub Reset() Implements IEnumerator.Reset


m_pos = 0
End Sub

Private disposedValue As Boolean ' To detect redundant calls

Protected Overridable Sub Dispose(ByVal disposing As Boolean)


If Not Me.disposedValue Then
m_input = Nothing
m_current = Nothing
End If
Me.disposedValue = True
End Sub

Public Sub Dispose() Implements IDisposable.Dispose


Dispose(True)
GC.SuppressFinalize(Me)
End Sub

End Class

End Class

Class ConsumerClass

Shared Sub Main()

Console.BufferHeight = 20000
Dim nums = Enumerable.Range(0, 2000).ToArray()

Dim partitioner = New OrderableListPartitioner(Of Integer)(nums)

' Use with Parallel.ForEach


Parallel.ForEach(partitioner, Sub(i) Console.Write("{0}:{1} ", i,
Thread.CurrentThread.ManagedThreadId))

Console.WriteLine("PLINQ -----------------------------------")

' create a new partitioner, since Enumerators are not reusable.


Dim partitioner2 = New OrderableListPartitioner(Of Integer)(nums)
' Use with PLINQ
Dim query = From num In partitioner2.AsParallel()
Where num Mod 8 = 0
Select num

For Each v In query


Console.Write("{0} ", v)
Next

Console.WriteLine("press any key")


Console.ReadKey()
End Sub
End Class

End Module

Este es un ejemplo de creación de particiones por fragmentos, donde cada fragmento consta de un elemento.
Proporcionando más elementos a la vez, podría reducir la contención sobre el bloqueo y teóricamente lograr un
rendimiento más rápido. Sin embargo, en algún momento, los fragmentos más grandes podrían requerir lógica
de equilibrio de carga adicional con el fin de mantener todos los subprocesos ocupados hasta que se completa
todo el trabajo.

Vea también
Particionadores personalizados para PLINQ y TPL
Implementar un particionador para particionamiento estático
Cómo: Implementar un particionador para
particionamiento estático
16/09/2020 • 4 minutes to read • Edit Online

En el ejemplo siguiente se muestra una forma de implementar un particionador personalizado sencillo para PLINQ
que realiza una partición estática. Dado que el particionador no admite las particiones dinámicas, no se puede usar
desde Parallel.ForEach. Este particionador particular puede proporcionar velocidad con respecto al particionador
por rangos predeterminado para los orígenes de datos, para los que cada elemento requieren una cantidad
creciente de tiempo de procesamiento.

Ejemplo
// A static range partitioner for sources that require
// a linear increase in processing time for each succeeding element.
// The range sizes are calculated based on the rate of increase
// with the first partition getting the most elements and the
// last partition getting the least.
class MyPartitioner : Partitioner<int>
{
int[] source;
double rateOfIncrease = 0;

public MyPartitioner(int[] source, double rate)


{
this.source = source;
rateOfIncrease = rate;
}

public override IEnumerable<int> GetDynamicPartitions()


{
throw new NotImplementedException();
}

// Not consumable from Parallel.ForEach.


public override bool SupportsDynamicPartitions
{
get
{
return false;
}
}

public override IList<IEnumerator<int>> GetPartitions(int partitionCount)


{
List<IEnumerator<int>> _list = new List<IEnumerator<int>>();
int end = 0;
int start = 0;
int[] nums = CalculatePartitions(partitionCount, source.Length);

for (int i = 0; i < nums.Length; i++)


{
start = nums[i];
if (i < nums.Length - 1)
end = nums[i + 1];
else
end = source.Length;

_list.Add(GetItemsForPartition(start, end));
// For demonstratation.
Console.WriteLine("start = {0} b (end) = {1}", start, end);
}
return (IList<IEnumerator<int>>)_list;
}
/*
*
*
* B
// Model increasing workloads as a right triangle / |
divided into equal areas along vertical lines. / | |
Each partition is taller and skinnier / | |
than the last. / | | |
/ | | |
/ | | |
/ | | | |
/ | | | |
A /______|____|___|__| C
*/
private int[] CalculatePartitions(int partitionCount, int sourceLength)
{
// Corresponds to the opposite side of angle A, which corresponds
// to an index into the source array.
int[] partitionLimits = new int[partitionCount];
partitionLimits[0] = 0;

// Represent total work as rectangle of source length times "most expensive element"
// Note: RateOfIncrease can be factored out of equation.
double totalWork = sourceLength * (sourceLength * rateOfIncrease);
// Divide by two to get the triangle whose slope goes from zero on the left to "most"
// on the right. Then divide by number of partitions to get area of each partition.
totalWork /= 2;
double partitionArea = totalWork / partitionCount;

// Draw the next partitionLimit on the vertical coordinate that gives


// an area of partitionArea * currentPartition.
for (int i = 1; i < partitionLimits.Length; i++)
{
double area = partitionArea * i;

// Solve for base given the area and the slope of the hypotenuse.
partitionLimits[i] = (int)Math.Floor(Math.Sqrt((2 * area) / rateOfIncrease));
}
return partitionLimits;
}

IEnumerator<int> GetItemsForPartition(int start, int end)


{
// For demonstration purpsoes. Each thread receives its own enumerator.
Console.WriteLine("called on thread {0}", Thread.CurrentThread.ManagedThreadId);
for (int i = start; i < end; i++)
yield return source[i];
}
}

class Consumer
{
public static void Main2()
{
var source = Enumerable.Range(0, 10000).ToArray();

Stopwatch sw = Stopwatch.StartNew();
MyPartitioner partitioner = new MyPartitioner(source, .5);

var query = from n in partitioner.AsParallel()


select ProcessData(n);

foreach (var v in query) { }


Console.WriteLine("Processing time with custom partitioner {0}", sw.ElapsedMilliseconds);
Console.WriteLine("Processing time with custom partitioner {0}", sw.ElapsedMilliseconds);

var source2 = Enumerable.Range(0, 10000).ToArray();

sw = Stopwatch.StartNew();

var query2 = from n in source2.AsParallel()


select ProcessData(n);

foreach (var v in query2) { }


Console.WriteLine("Processing time with default partitioner {0}", sw.ElapsedMilliseconds);
}

// Consistent processing time for measurement purposes.


static int ProcessData(int i)
{
Thread.SpinWait(i * 1000);
return i;
}
}

Las particiones de este ejemplo se basan en el supuesto de un aumento lineal del tiempo de procesamiento de
cada elemento. En el mundo real, puede que sea difícil predecir los tiempos de procesamiento de esta manera. Si
usa un particionador estático con un origen de datos específico, puede optimizar la fórmula de partición del
origen, agregar la lógica de equilibrio de carga o usar un enfoque de partición por fragmentos, como se explica en
Cómo: Implementar las particiones dinámicas.

Vea también
Particionadores personalizados para PLINQ y TPL
Expresiones lambda en PLINQ y TPL
16/09/2020 • 4 minutes to read • Edit Online

La biblioteca TPL contiene muchos métodos que adoptan una de las familias System.Func<TResult> o
System.Action de delegados como parámetros de entrada. Puede usar estos delegados para pasar de la
lógica personalizada del programa al bucle, tarea o consulta en paralelo. Los ejemplos de código de TPL,
igual que igual que de PLINQ, usan expresiones lambda para crear instancias de esos delegados como
bloques de código insertados. En este tema se proporciona una breve introducción a Func y Action, y
muestra cómo usar las expresiones lambda en la biblioteca TPL y PLINQ.

NOTE
Para más información sobre los delegados en general, vea Delegados y Delegados. Para obtener más información
sobre las expresiones lambda en C# y Visual Basic, vea Expresiones lambda y Expresiones lambda.

Func (delegado)
Un delegado Func encapsula un método que devuelve un valor. En una signatura Func , el último
parámetro de tipo o el ubicado más a la derecha siempre especifica el tipo de devolución. Una causa común
de los errores de compilador es intentar pasar dos parámetros de entrada a un System.Func<T,TResult>; de
hecho, este tipo solo acepta un parámetro de entrada. .NET define 17 versiones de Func :
System.Func<TResult>, System.Func<T,TResult>, System.Func<T1,T2,TResult>, etc. hasta
System.Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>.

Action (delegado)
Un delegado System.Action encapsula un método (Sub en Visual Basic) que no devuelve ningún valor. En
una signatura de tipo Action , los parámetros de tipo representan solo parámetros de entrada. Al igual que
Func , .NET define 17 versiones de Action , desde una versión que no tiene parámetros de tipo hasta una
versión que tiene 16 de ellos.

Ejemplo
El siguiente ejemplo del método Parallel.ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>,
Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) muestra cómo expresar delegados Func y
Action mediante expresiones lambda.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

class ForEachWithThreadLocal
{
// Demonstrated features:
// Parallel.ForEach()
// Thread-local state
// Expected results:
// This example sums up the elements of an int[] in parallel.
// Each thread maintains a local sum. When a thread is initialized, that local sum is set to
0.
// On every iteration the current element is added to the local sum.
// When a thread is done, it safely adds its local sum to the global sum.
// After the loop is complete, the global sum is printed out.
// Documentation:
// http://msdn.microsoft.com/library/dd990270(VS.100).aspx
static void Main()
{
// The sum of these elements is 40.
int[] input = { 4, 1, 6, 2, 9, 5, 10, 3 };
int sum = 0;

try
{
Parallel.ForEach(
input, // source collection
() => 0, // thread local initializer
(n, loopState, localSum) => // body
{
localSum += n;
Console.WriteLine("Thread={0}, n={1}, localSum={2}",
Thread.CurrentThread.ManagedThreadId, n, localSum);
return localSum;
},
(localSum) => Interlocked.Add(ref sum, localSum) // thread local aggregator
);

Console.WriteLine("\nSum={0}", sum);
}
// No exception is expected in this example, but if one is still thrown from a task,
// it will be wrapped in AggregateException and propagated to the main thread.
catch (AggregateException e)
{
Console.WriteLine("Parallel.ForEach has thrown an exception. THIS WAS NOT EXPECTED.\n{0}",
e);
}
}
}
Imports System.Threading
Imports System.Threading.Tasks

Module ForEachDemo

' Demonstrated features:


' Parallel.ForEach()
' Thread-local state
' Expected results:
' This example sums up the elements of an int[] in parallel.
' Each thread maintains a local sum. When a thread is initialized, that local sum is set to 0.
' On every iteration the current element is added to the local sum.
' When a thread is done, it safely adds its local sum to the global sum.
' After the loop is complete, the global sum is printed out.
' Documentation:
' http://msdn.microsoft.com/library/dd990270(VS.100).aspx
Private Sub ForEachDemo()
' The sum of these elements is 40.
Dim input As Integer() = {4, 1, 6, 2, 9, 5, _
10, 3}
Dim sum As Integer = 0

Try
' source collection
Parallel.ForEach(input,
Function()
' thread local initializer
Return 0
End Function,
Function(n, loopState, localSum)
' body
localSum += n
Console.WriteLine("Thread={0}, n={1}, localSum={2}",
Thread.CurrentThread.ManagedThreadId, n, localSum)
Return localSum
End Function,
Sub(localSum)
' thread local aggregator
Interlocked.Add(sum, localSum)
End Sub)

Console.WriteLine(vbLf & "Sum={0}", sum)


Catch e As AggregateException
' No exception is expected in this example, but if one is still thrown from a task,
' it will be wrapped in AggregateException and propagated to the main thread.
Console.WriteLine("Parallel.ForEach has thrown an exception. THIS WAS NOT EXPECTED." & vbLf
& "{0}", e)
End Try
End Sub

End Module

Vea también
Programación en paralelo
Más información (Programación paralela)
16/09/2020 • 2 minutes to read • Edit Online

Los siguientes recursos contienen información adicional sobre programación paralela en .NET:
En el documento Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the
.NET Framework 4 (Patrones de programación paralela: descripción y aplicación de patrones paralelos con
.NET Framework 4) se describen patrones paralelos comunes y procedimientos recomendados para
desarrollar componentes paralelos con el uso de esos patrones.
En el libro Design Patterns for Decomposition and Coordination on Multicore Architectures (Diseño de
patrones para descomposición y coordinación en arquitecturas multinúcleo) se describen los patrones de
programación paralela que usan la compatibilidad con la programación paralela introducida en .NET
Framework 4.
El blog sobre programación paralela con .NET contiene muchos artículos detallados sobre la programación
paralela en .NET.
La página Ejemplos de programación paralela con .NET Core y .NET Standard contiene muchos ejemplos
que muestran las técnicas de programación paralela intermedias y avanzadas.

Vea también
Centro para desarrolladores de informática en paralelo
Programación en paralelo en Visual C++
Subprocesamiento administrado
16/09/2020 • 3 minutes to read • Edit Online

Tanto si va a desarrollar aplicaciones para equipos con uno o varios procesadores, desea que la aplicación
proporcione la interacción con mayor capacidad de respuesta con el usuario, incluso si la aplicación actualmente
hace otro trabajo. Usar varios subprocesos de ejecución es una de las formas más eficaces de mantener que la
aplicación siga respondiendo al usuario y, al mismo tiempo, usar el procesador entre eventos de usuario o durante
los mismos. Si bien esta sección presenta los conceptos básicos del subprocesamiento, se centra en los conceptos
del subprocesamiento administrado y su uso.

NOTE
A partir de .NET Framework 4, la programación multiproceso se ha simplificado significativamente con las clases
System.Threading.Tasks.Parallel y System.Threading.Tasks.Task, Parallel LINQ (PLINQ), clases de colecciones simultáneas
nuevas en el espacio de nombres System.Collections.Concurrent y un nuevo modelo de programación basado en el concepto
de tareas en lugar de subprocesos. Para más información, consulte Programación en paralelo.

En esta sección
Principios básicos del subprocesamiento administrado
Proporciona información general sobre el subprocesamiento administrado y describe cuándo usar varios
subprocesos.
Usar subprocesos y subprocesamiento
Explica cómo crear, iniciar, pausar, reanudar y anular subprocesos.
Procedimientos recomendados para el subprocesamiento administrado
Se describen los niveles de sincronización, cómo evitar interbloqueos y condiciones de carrera y otros problemas
de subprocesos.
Objetos y características de subprocesos
Describe las clases administradas que puede usar para sincronizar las actividades de subprocesamientos y los
datos de objetos accedidos en distintos subprocesos, y proporciona información general sobre los subprocesos de
grupos de subprocesos.

Referencia
System.Threading
Contiene clases para usar y sincronizar subprocesos administrados.
System.Collections.Concurrent
Contiene clases de colección seguras para usarlas con varios subprocesos.
System.Threading.Tasks
Contiene clases para crear y programar tareas de procesamiento simultáneo.

Secciones relacionadas
Dominios de aplicación
Proporciona información genera sobre los dominios de aplicación y de su uso en Common Language
Infrastructure.
E/S de archivos asincrónica
Describe las ventajas de rendimiento y el funcionamiento básico de la E/S asincrónica.
Modelo asincrónico basado en tareas (TAP)
Proporciona una introducción del patrón recomendado para programación asincrónica en. NET.
Llamada a métodos sincrónicos de forma asincrónica
Explica cómo llamar a métodos en los subprocesos de grupos de subprocesos con características integradas de
delegados.
Programación en paralelo
Describe las bibliotecas de programación en paralelo, las que simplifican el uso de varios subprocesos en las
aplicaciones.
Parallel LINQ (PLINQ)
Describe un sistema para ejecutar consultas en paralelo a fin de aprovechar los distintos procesadores.
Serialización en .NET
16/09/2020 • 2 minutes to read • Edit Online

La serialización es el proceso de convertir el estado de un objeto en un formato que se pueda almacenar o


transportar. El complemento de serialización es deserialización, que convierte una secuencia en un objeto. Juntos,
estos procesos permiten almacenar y transferir datos.
.NET incluye las siguientes tecnologías de serialización:
La serialización binaria preserva la fidelidad de tipo, lo que es útil para conservar el estado de un objeto
entre distintas invocaciones de una aplicación. Por ejemplo, puede compartir un objeto entre distintas
aplicaciones si lo serializa en el Portapapeles. Puede serializar un objeto en una secuencia, un disco, la
memoria, a través de la red, etc. La comunicación remota utiliza la serialización para pasar objetos "por
valor" de un equipo o dominio de aplicación a otro.
La serialización de SOAP y XML solo serializa propiedades y campos públicos y no preserva la fidelidad de
tipo. Esto es útil si se desea proporcionar o utilizar los datos sin restringir la aplicación que utiliza los datos.
Dado que XML es un estándar abierto, es una opción atractiva para compartir los datos por el web. SOAP es
igualmente un estándar abierto, que lo convierte en una opción atractiva.
La serialización de JSON solo serializa propiedades públicas y no preserva la fidelidad de tipo. JSON es un
estándar abierto que constituye una opción atractiva para compartir datos en Internet.

Referencia
System.Runtime.Serialization
Contiene clases que se pueden usar para serializar y deserializar objetos.
System.Xml.Serialization
Contiene clases que se pueden utilizar para serializar objetos en documentos o secuencias de formato XML.
System.Text.Json
Contiene clases que se pueden usar para serializar objetos en documentos o secuencias de formato JSON.
Serialización y deserialización de JSON en .NET:
información general
16/09/2020 • 2 minutes to read • Edit Online

El espacio de nombres System.Text.Json proporciona funcionalidad para serializar y deserializar desde JSON
(notaciones de objetos JavaScript).
El diseño de biblioteca resalta el rendimiento elevado y la asignación de memoria baja en un amplio conjunto de
características. La compatibilidad integrada con UTF-8 optimiza el proceso de lectura y escritura de texto JSON
codificado como UTF-8, que es la codificación más frecuente de los datos en Internet y los archivos en disco.
La biblioteca también proporciona clases para trabajar con un Document Object Model (DOM) en memoria. Esta
característica permite el acceso de solo lectura aleatorio de los elementos de una cadena o archivo JSON.

Cómo obtener la biblioteca


La biblioteca está integrada como parte del marco compartido de .NET Core 3.0.
En cuanto a otros marcos de destino, instale el paquete NuGet System.Text.Json, que admite lo siguiente:
.NET Standard 2.0 y versiones posteriores
.NET Framework 4.7.2 y versiones posteriores
.NET Core 2.0, 2.1 y 2.2

Recursos adicionales
Cómo usar la biblioteca
Procedimiento para migrar desde Newtonsoft.Json
Procedimiento para escribir convertidores
Código fuente System.Text.Json
Referencia de API System.Text.Json
Referencia de API de System.Text.Json.Serialization
Procedimiento para serializar y deserializar (calcular
referencias y resolver referencias) JSON en .NET
16/09/2020 • 49 minutes to read • Edit Online

En este artículo se muestra cómo usar el espacio de nombres System.Text.Json para serializar y deserializar a y
desde la notación de objetos JavaScript (JSON). Si va a portar el código existente de Newtonsoft.Json , consulte
Procedimiento para migrar a System.Text.Json .
Las instrucciones y el código de ejemplo usan la biblioteca directamente, no a través de un marco como
ASP.NET Core.
La mayor parte del código de ejemplo de la serialización establece JsonSerializerOptions.WriteIndented en true
para "imprimir correctamente" el JSON (con sangría y espacio en blanco para mayor legibilidad). Para su uso en
producción, normalmente aceptaría el valor predeterminado de false para este valor.
Los ejemplos de código hacen referencia a la siguiente clase y sus variantes:

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Espacios de nombres
El espacio de nombres System.Text.Json contiene todos los puntos de entrada y los tipos principales. El espacio de
nombres System.Text.Json.Serialization contiene atributos e interfaces API para escenarios avanzados y
personalización específicos de la serialización y deserialización. Los ejemplos de código que se muestran en este
artículo requieren directivas using para uno o ambos espacios de nombres:

using System.Text.Json;
using System.Text.Json.Serialization;

Los atributos del espacio de nombres System.Runtime.Serialization no se admiten actualmente en


System.Text.Json .

Procedimiento para escribir objetos .NET en JSON (serializar)


Para escribir JSON en una cadena o un archivo, llame al método JsonSerializer.Serialize.
En el ejemplo siguiente se crea un archivo JSON como cadena:

string jsonString;
jsonString = JsonSerializer.Serialize(weatherForecast);

En el ejemplo siguiente se usa código sincrónico para crear un archivo JSON:


jsonString = JsonSerializer.Serialize(weatherForecast);
File.WriteAllText(fileName, jsonString);

En el ejemplo siguiente se usa código asincrónico para crear un archivo JSON:

using (FileStream fs = File.Create(fileName))


{
await JsonSerializer.SerializeAsync(fs, weatherForecast);
}

En los ejemplos anteriores se usa la inferencia de tipos para el tipo que se está serializando. Una sobrecarga de
Serialize() toma un parámetro de tipo genérico:

jsonString = JsonSerializer.Serialize<WeatherForecastWithPOCOs>(weatherForecast);

Ejemplo de serialización
Aquí se muestra una clase de ejemplo que contiene colecciones y una clase anidada:

public class WeatherForecastWithPOCOs


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public string SummaryField;
public IList<DateTimeOffset> DatesAvailable { get; set; }
public Dictionary<string, HighLowTemps> TemperatureRanges { get; set; }
public string[] SummaryWords { get; set; }
}

public class HighLowTemps


{
public int High { get; set; }
public int Low { get; set; }
}

La salida JSON de la serialización de una instancia del tipo anterior es similar al ejemplo siguiente. La salida JSON
se minimiza de manera predeterminada:

{"Date":"2019-08-01T00:00:00-07:00","TemperatureCelsius":25,"Summary":"Hot","DatesAvailable":["2019-08-
01T00:00:00-07:00","2019-08-02T00:00:00-07:00"],"TemperatureRanges":{"Cold":{"High":20,"Low":-10},"Hot":
{"High":60,"Low":20}},"SummaryWords":["Cool","Windy","Humid"]}

En el ejemplo siguiente se muestra el mismo JSON, con formato (es decir, impreso correctamente con espacio en
blanco y sangría):
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"DatesAvailable": [
"2019-08-01T00:00:00-07:00",
"2019-08-02T00:00:00-07:00"
],
"TemperatureRanges": {
"Cold": {
"High": 20,
"Low": -10
},
"Hot": {
"High": 60,
"Low": 20
}
},
"SummaryWords": [
"Cool",
"Windy",
"Humid"
]
}

Serialización a UTF -8
Para serializar a UTF-8, llame al método JsonSerializer.SerializeToUtf8Bytes:

byte[] jsonUtf8Bytes;
var options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(weatherForecast, options);

También está disponible una sobrecarga Serialize que toma un valor Utf8JsonWriter.
La serialización a UTF-8 es aproximadamente un 5-10 % más rápida que el uso de métodos basados en cadenas.
La diferencia se debe a que no hay que convertir los bytes (como UTF-8) en cadenas (UTF-16).

Comportamiento de serialización
De manera predeterminada, se serializan todas las propiedades públicas. Puede especificar propiedades para
excluir.
El codificador predeterminado escapa a caracteres que no son ASCII, caracteres que distinguen HTML en el
intervalo ASCII y caracteres que deben escaparse según la especificación de JSON RFC 8259.
De forma predeterminada, JSON se minimiza. Puede imprimir correctamente el JSON.
De forma predeterminada, el uso de mayúsculas y minúsculas en los nombres JSON coincide con el de los
nombres de .NET. Puede personalizar el uso de mayúsculas y minúsculas e nombres JSON.
Se detectan las referencias circulares y se inician las excepciones.
Actualmente, se excluyen los campos.
Los tipos no admitidos incluyen:
Elementos primitivos de .NET que se asignan a elementos primitivos de JavaScript, tales como tipos numéricos,
cadenas y valores booleanos.
Objetos CLR antiguos sin formato (POCO) definidos por el usuario.
Matrices unidimensionales y escalonadas ( ArrayName[][] ).
Dictionary<string,TValue> donde TValue es object , JsonElement o un POCO.
Colecciones de los espacios de nombres siguientes.
System.Collections
System.Collections.Generic
System.Collections.Immutable
Puede implementar convertidores personalizados para controlar tipos adicionales o proporcionar funcionalidad
que no admiten los convertidores integrados.

Procedimiento para leer JSON en objetos .NET (deserializar)


Para deserializar a partir de una cadena o un archivo, llame al método JsonSerializer.Deserialize.
En el ejemplo siguiente se lee JSON desde una cadena y se crea una instancia de la clase WeatherForecast
mostrada anteriormente para el ejemplo de serialización:

weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithPOCOs>(jsonString);

Para deserializar a partir de un archivo mediante código sincrónico, lea el archivo en una cadena, tal y como se
muestra en el ejemplo siguiente:

jsonString = File.ReadAllText(fileName);
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString);

Para deserializar a partir de un archivo mediante código asincrónico, llame al método DeserializeAsync:

using (FileStream fs = File.OpenRead(fileName))


{
weatherForecast = await JsonSerializer.DeserializeAsync<WeatherForecast>(fs);
}

Deserialización desde UTF -8


Para deserializar desde UTF-8, llame a una sobrecarga JsonSerializer.Deserialize que tome un valor
Utf8JsonReader o ReadOnlySpan<byte> , tal y como se muestra en el ejemplo siguiente. En los ejemplos se
presupone que el JSON está en una matriz de bytes denominada jsonUtf8Bytes.

var readOnlySpan = new ReadOnlySpan<byte>(jsonUtf8Bytes);


weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(readOnlySpan);

var utf8Reader = new Utf8JsonReader(jsonUtf8Bytes);


weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(ref utf8Reader);

Comportamiento de la deserialización
De forma predeterminada, la coincidencia de nombres de la propiedad distingue mayúsculas de minúsculas.
Puede especificar la no distinción de mayúsculas y minúsculas.
Si el archivo JSON contiene un valor para una propiedad de solo lectura, el valor se omite y no se inicia
ninguna excepción.
No se admite la deserialización a tipos de referencia sin un constructor sin parámetros.
No se admite la deserialización a objetos inmutables o propiedades de solo lectura.
De forma predeterminada, las enumeraciones se admiten como números. Puede serializar nombres de
enumeración como cadenas.
No se admiten los campos.
De forma predeterminada, los comentarios o las comas finales en el JSON inician excepciones. Puede permitir
comentarios y comas finales.
La profundidad máxima predeterminada es 64.
Puede implementar convertidores personalizados para proporcionar funcionalidad que no admiten los
convertidores integrados.

Serialización a JSON con formato


Para imprimir correctamente la salida JSON, establezca JsonSerializerOptions.WriteIndented en true :

var options = new JsonSerializerOptions


{
WriteIndented = true,
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Aquí se muestra un tipo de ejemplo que se serializa y la salida de JSON impresa correctamente:

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}

Personalización de nombres y valores JSON


De forma predeterminada, los nombres de propiedad y las claves de diccionario no se modifican en la salida
JSON, ni siquiera el uso de mayúsculas y minúsculas. Los valores de enumeración se representan como números.
En esta sección se explica cómo:
Personalizar nombres de propiedades individuales
Convertir todos los nombres de propiedad en mayúsculas y minúsculas combinadas Camel
Implementar una directiva de nomenclatura de propiedades personalizada
Convertir claves de diccionario en mayúsculas y minúsculas combinadas Camel
Convertir enumeraciones en cadenas y palabras con mayúsculas y minúsculas combinadas Camel
En el caso de otros escenarios que requieran un tratamiento especial de los nombres y valores de propiedades
JSON, puede implementar convertidores personalizados.
Personalizar nombres de propiedades individuales
Para establecer el nombre de propiedades individuales, use el atributo [JsonPropertyName].
Aquí se muestra un tipo de ejemplo que se serializa y el JSON resultante:
public class WeatherForecastWithPropertyNameAttribute
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonPropertyName("Wind")]
public int WindSpeed { get; set; }
}

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"Wind": 35
}

Nombre de propiedad establecido por este atributo:


Se aplica en ambas direcciones, para la serialización y la deserialización.
Tiene prioridad sobre las directivas de nomenclatura de propiedades.
Uso de mayúsculas y minúsculas combinadas Camel en todos los nombres de propiedad JSON
Para usar palabras con mayúsculas y minúsculas combinadas Camel en todos los nombres de propiedad JSON,
establezca JsonSerializerOptions.PropertyNamingPolicy en JsonNamingPolicy.CamelCase , tal y como se muestra en
el ejemplo siguiente:

var serializeOptions = new JsonSerializerOptions


{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);

Aquí se muestra una clase de ejemplo que se serializa y la salida JSON:

public class WeatherForecastWithPropertyNameAttribute


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonPropertyName("Wind")]
public int WindSpeed { get; set; }
}

{
"date": "2019-08-01T00:00:00-07:00",
"temperatureCelsius": 25,
"summary": "Hot",
"Wind": 35
}

La directiva de nomenclatura de propiedades en mayúsculas y minúsculas combinadas Camel:


Se aplica a la serialización y deserialización.
Se invalida con atributos [JsonPropertyName] . Este es el motivo por el que el nombre de la propiedad JSON
Wind del ejemplo no usa mayúsculas y minúsculas combinadas Camel.
Uso de una directiva de nomenclatura de propiedades JSON personalizada
Para usar una directiva de nomenclatura de propiedades JSON personalizada, cree una clase que derive de
JsonNamingPolicy e invalide el método ConvertName, tal y como se muestra en el ejemplo siguiente:

using System.Text.Json;

namespace SystemTextJsonSamples
{
public class UpperCaseNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name) =>
name.ToUpper();
}
}

Establezca luego la propiedad JsonSerializerOptions.PropertyNamingPolicy en una instancia de la clase de


directiva de nomenclatura:

var options = new JsonSerializerOptions


{
PropertyNamingPolicy = new UpperCaseNamingPolicy(),
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Aquí se muestra una clase de ejemplo que se serializa y la salida JSON:

public class WeatherForecastWithPropertyNameAttribute


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonPropertyName("Wind")]
public int WindSpeed { get; set; }
}

{
"DATE": "2019-08-01T00:00:00-07:00",
"TEMPERATURECELSIUS": 25,
"SUMMARY": "Hot",
"Wind": 35
}

La directiva de nomenclatura de propiedades JSON personalizada:


Se aplica a la serialización y la deserialización.
Se invalida con atributos [JsonPropertyName] . Este es el motivo por el que el nombre de la propiedad JSON
Wind del ejemplo no usa mayúsculas.

Claves de diccionario en mayúsculas y minúsculas combinadas Camel


Si una propiedad de un objeto que se va a serializar es de tipo Dictionary<string,TValue> , se pueden convertir las
claves string en mayúsculas y minúsculas combinadas Camel. Para ello, establezca DictionaryKeyPolicy en
JsonNamingPolicy.CamelCase , tal y como se muestra en el ejemplo siguiente:
var options = new JsonSerializerOptions
{
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

La serialización de un objeto con un diccionario denominado TemperatureRanges que tenga pares clave-valor
"ColdMinTemp", 20 y "HotMinTemp", 40 se traduciría en una salida JSON similar a la del ejemplo siguiente:

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"TemperatureRanges": {
"coldMinTemp": 20,
"hotMinTemp": 40
}
}

La directiva de nomenclatura en mayúsculas y minúsculas combinadas Camel para las claves de diccionario se
aplica solo a la serialización. Si se deserializa un diccionario, las claves coincidirán con el archivo JSON aunque se
especifique JsonNamingPolicy.CamelCase para DictionaryKeyPolicy .
Enumeraciones como cadenas
De forma predeterminada, las enumeraciones se serializan como números. Para serializar nombres de
enumeración como cadenas, use JsonStringEnumConverter.
Por ejemplo, supongamos que hay que serializar la siguiente clase que tiene una enumeración:

public class WeatherForecastWithEnum


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public Summary Summary { get; set; }
}

public enum Summary


{
Cold, Cool, Warm, Hot
}

Si el resumen es Hot , el JSON serializado tiene el valor numérico 3 de forma predeterminada:

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": 3
}

En el código de ejemplo siguiente se serializan los nombres de enumeración en lugar de los valores numéricos y
los nombres se convierten en mayúsculas y minúsculas combinadas Camel:

options = new JsonSerializerOptions();


options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
options.WriteIndented = true;
jsonString = JsonSerializer.Serialize(weatherForecast, options);
El JSON resultante tendrá un aspecto similar al siguiente:

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "hot"
}

Los nombres de cadena de enumeración también se pueden deserializar, tal y como se muestra en el ejemplo
siguiente:

options = new JsonSerializerOptions();


options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithEnum>(jsonString, options);

Exclusión de propiedades de la serialización


De manera predeterminada, se serializan todas las propiedades públicas. Si no quiere que algunas de ellas
aparezcan en la salida JSON, tiene varias opciones. En esta sección se explica cómo excluir:
Propiedades individuales
Todas las propiedades de solo lectura
Todas las propiedades de valores NULL
Exclusión de propiedades individuales
Para omitir propiedades individuales, use el atributo [JsonIgnore].
Aquí se muestra un tipo de ejemplo que se va a serializar y la salida JSON:

public class WeatherForecastWithIgnoreAttribute


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
[JsonIgnore]
public string Summary { get; set; }
}

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
}

Exclusión de todas las propiedades de solo lectura


Una propiedad es de solo lectura si contiene un captador público pero no un establecedor público. Para excluir
todas las propiedades de solo lectura, establezca JsonSerializerOptions.IgnoreReadOnlyProperties en true , tal y
como se muestra en el ejemplo siguiente:

var options = new JsonSerializerOptions


{
IgnoreReadOnlyProperties = true,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Aquí se muestra un tipo de ejemplo que se va a serializar y la salida JSON:


public class WeatherForecastWithROProperty
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public int WindSpeedReadOnly { get; private set; } = 35;
}

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
}

Esta opción solo se aplica a la serialización. Durante la deserialización, las propiedades de solo lectura se omiten
de forma predeterminada.
Exclusión de todas las propiedades de valores NULL
Para excluir todas las propiedades de valores NULL, establezca la propiedad IgnoreNullValues en true , tal y como
se muestra en el ejemplo siguiente:

var options = new JsonSerializerOptions


{
IgnoreNullValues = true,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Aquí se muestra un objeto de ejemplo que se serializa y la salida JSON:

P RO P IEDA D. VA LO R

Fecha 1/8/2019 12:00 -07:00

TemperatureCelsius 25

Resumen null

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25
}

Este valor se aplica a la serialización y la deserialización. Para obtener información sobre su efecto en la
deserialización, consulte el artículo sobre cómo omitir valores NULL al deserializar.

Personalización de la codificación de caracteres


De forma predeterminada, el serializador escapa a todos los caracteres que no sean ASCII. Es decir, los reemplaza
por \uxxxx donde xxxx es el código Unicode del carácter. Por ejemplo, si la propiedad Summary está establecida
en cirílico жарко, el objeto WeatherForecast se serializa tal y como se muestra en este ejemplo:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "\u0436\u0430\u0440\u043A\u043E"
}

Serialización de juegos de caracteres de idioma


Para serializar los juegos de caracteres de uno o más idiomas sin escape, especifique intervalos Unicode al crear
una instancia de System.Text.Encodings.Web.JavaScriptEncoder, tal y como se muestra en el ejemplo siguiente:

using System;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;

options = new JsonSerializerOptions


{
Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.Cyrillic),
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Este código no escapa a los caracteres cirílicos o griegos. Si la propiedad Summary está establecida en cirílico
жарко, el objeto WeatherForecast se serializa tal y como se muestra en este ejemplo:

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "жарко"
}

Para serializar todos los conjuntos de lenguaje sin escape, use UnicodeRanges.All.
Serialización de caracteres específicos
Una alternativa consiste en especificar caracteres individuales que se quieren dejar pasar sin secuencia de escape.
En el ejemplo siguiente se serializan solo los dos primeros caracteres de жарко:

using System;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;

var encoderSettings = new TextEncoderSettings();


encoderSettings.AllowCharacters('\u0436', '\u0430');
encoderSettings.AllowRange(UnicodeRanges.BasicLatin);
options = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.Create(encoderSettings),
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Aquí se muestra un ejemplo de JSON producido por el código anterior:


{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "жа\u0440\u043A\u043E"
}

Serialización de todos los caracteres


Para minimizar el escape, puede usar JavaScriptEncoder.UnsafeRelaxedJsonEscaping tal y como se muestra en el
ejemplo siguiente:

using System;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;

options = new JsonSerializerOptions


{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Cau t i on

En comparación con el codificador predeterminado, el codificador UnsafeRelaxedJsonEscaping es más permisivo


sobre dejar que los caracteres pasen sin secuencia de escape:
No escapa a caracteres que distinguen HTML, tales como < , > , & y ' .
No ofrece ninguna protección adicional de defensa en profundidad contra ataques de divulgación de
información y XSS, tales como los que podrían traducirse en un desacuerdo entre el cliente y el servidor sobre
el juego de caracteres.
Use el codificador no seguro solo cuando sepa que el cliente va a interpretar la carga resultante como JSON con
codificación UTF-8. Por ejemplo, puede utilizarlo si el servidor envía el encabezado de respuesta
Content-Type: application/json; charset=utf-8 . No permita nunca que la salida de UnsafeRelaxedJsonEscaping sin
formato se emita en una página HTML o en un elemento <script> .

Serialización de propiedades de clases derivadas


No se admite la serialización de una jerarquía de tipos polimórficos. Por ejemplo, si una propiedad se define como
interfaz o como clase abstracta, solo se serializan las propiedades definidas en la interfaz o la clase abstracta,
aunque el tipo de entorno de ejecución tenga propiedades adicionales. Las excepciones a este comportamiento se
explican en esta sección.
Por ejemplo, supongamos que tiene una clase WeatherForecast y una clase derivada WeatherForecastDerived :

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}
public class WeatherForecastDerived : WeatherForecast
{
public int WindSpeed { get; set; }
}

Supongamos también que el argumento de tipo del método Serialize en tiempo de compilación es
WeatherForecast :

var options = new JsonSerializerOptions


{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecast>(weatherForecast, options);

En este escenario, la propiedad WindSpeed no se serializa aunque el objeto weatherForecast sea en realidad un
objeto WeatherForecastDerived . Solo se serializan las propiedades de la clase base:

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}

Este comportamiento está diseñado para ayudar a evitar la exposición accidental de datos en un tipo derivado
creado en tiempo de ejecución.
Para serializar las propiedades del tipo derivado del ejemplo anterior, use uno de los enfoques siguientes:
Llame a una sobrecarga de Serialize que le permita especificar el tipo en tiempo de ejecución:

options = new JsonSerializerOptions


{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, weatherForecast.GetType(), options);

Declare el objeto que se va a serializar como object .

options = new JsonSerializerOptions


{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize<object>(weatherForecast, options);

En el escenario de ejemplo anterior, ambos enfoques hacen que la propiedad WindSpeed se incluya en la salida
JSON:

{
"WindSpeed": 35,
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
IMPORTANT
Estos enfoques proporcionan serialización polimórfica solo para el objeto raíz que se va a serializar, no para las propiedades
de dicho objeto raíz.

Puede obtener una serialización polimórfica para objetos de nivel inferior si se definen como tipo object . Por
ejemplo, supongamos que la clase WeatherForecast tiene una propiedad denominada PreviousForecast que se
puede definir como tipo WeatherForecast o object :

public class WeatherForecastWithPrevious


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public WeatherForecast PreviousForecast { get; set; }
}

public class WeatherForecastWithPreviousAsObject


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public object PreviousForecast { get; set; }
}

Si la propiedad PreviousForecast contiene una instancia de WeatherForecastDerived :


La salida JSON de la serialización WeatherForecastWithPrevious no incluye WindSpeed .
La salida JSON de la serialización WeatherForecastWithPreviousAsObject incluye WindSpeed .

Para serializar WeatherForecastWithPreviousAsObject , no es necesario llamar a Serialize<object> o GetType


porque el objeto raíz no es el que puede ser de un tipo derivado. En el ejemplo de código siguiente no se llama a
Serialize<object> ni GetType :

options = new JsonSerializerOptions


{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecastWithPreviousAsObject, options);

El código anterior serializa correctamente WeatherForecastWithPreviousAsObject :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"PreviousForecast": {
"WindSpeed": 35,
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
}

El mismo enfoque para definir propiedades como object funciona con interfaces. Supongamos que tiene la
interfaz y la implementación siguientes, y quiere serializar una clase con propiedades que contienen instancias de
implementación:

using System;

namespace SystemTextJsonSamples
{
public interface IForecast
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

public class Forecast : IForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public int WindSpeed { get; set; }
}

public class Forecasts


{
public IForecast Monday { get; set; }
public object Tuesday { get; set; }
}
}

Cuando se serializa una instancia de Forecasts , solo Tuesday muestra la propiedad WindSpeed , porque Tuesday
se define como object :

var forecasts = new Forecasts


{
Monday = new Forecast
{
Date = DateTime.Parse("2020-01-06"),
TemperatureCelsius = 10,
Summary = "Cool",
WindSpeed = 8
},
Tuesday = new Forecast
{
Date = DateTime.Parse("2020-01-07"),
TemperatureCelsius = 11,
Summary = "Rainy",
WindSpeed = 10
}
};

options = new JsonSerializerOptions


{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(forecasts, options);

En el ejemplo de código siguiente se muestra el JSON resultante del código anterior:


{
"Monday": {
"Date": "2020-01-06T00:00:00-08:00",
"TemperatureCelsius": 10,
"Summary": "Cool"
},
"Tuesday": {
"Date": "2020-01-07T00:00:00-08:00",
"TemperatureCelsius": 11,
"Summary": "Rainy",
"WindSpeed": 10
}
}

Para obtener más información sobre la serialización polimórfica e información sobre la deserialización ,
consulte Migración desde Newtonsoft.Json a System.Text.Json.

Autorización de comentarios y comas finales


De forma predeterminada, los comentarios y las comas finales no se permiten en JSON. Para permitir
comentarios en JSON, establezca la propiedad JsonSerializerOptions.ReadCommentHandling en
JsonCommentHandling.Skip . Y para permitir las comas finales, establezca la propiedad
JsonSerializerOptions.AllowTrailingCommas en true . En el siguiente ejemplo se muestra cómo permitirlos
ambos:

var options = new JsonSerializerOptions


{
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
};
var weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, options);

Aquí se muestra un ejemplo de JSON con comentarios y una coma final:

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25, // Fahrenheit 77
"Summary": "Hot", /* Zharko */
}

Coincidencia de propiedades sin distinción entre mayúsculas y


minúsculas
De forma predeterminada, la deserialización busca coincidencias con el nombre de propiedad que distingan
mayúsculas y minúsculas entre JSON y las propiedades del objeto de destino. Para cambiar ese comportamiento,
establezca JsonSerializerOptions.PropertyNameCaseInsensitive en true :

var options = new JsonSerializerOptions


{
PropertyNameCaseInsensitive = true,
};
var weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, options);

Aquí se muestra un ejemplo de JSON con nombres de propiedades en mayúsculas y minúsculas combinadas
Camel. Se puede deserializar en el tipo siguiente que tenga nombres de propiedades en mayúsculas y minúsculas
Pascal.

{
"date": "2019-08-01T00:00:00-07:00",
"temperatureCelsius": 25,
"summary": "Hot",
}

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

JSON de desbordamiento de control


Durante la deserialización, es posible que reciba datos en el archivo JSON que no están representados por las
propiedades del tipo de destino. Por ejemplo, supongamos que el tipo de destino es el siguiente:

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Y el JSON que se va a deserializar es el siguiente:

{
"Date": "2019-08-01T00:00:00-07:00",
"temperatureCelsius": 25,
"Summary": "Hot",
"DatesAvailable": [
"2019-08-01T00:00:00-07:00",
"2019-08-02T00:00:00-07:00"
],
"SummaryWords": [
"Cool",
"Windy",
"Humid"
]
}

Si deserializa el JSON que se muestra en el tipo mostrado, las propiedades DatesAvailable y SummaryWords no
tienen a donde ir y se pierden. Para capturar datos adicionales tales como estas propiedades, aplique el atributo
JsonExtensionData a una propiedad de tipo Dictionary<string,object> o Dictionary<string,JsonElement> :

public class WeatherForecastWithExtensionData


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonExtensionData]
public Dictionary<string, object> ExtensionData { get; set; }
}
Al deserializar el JSON que se muestra anteriormente en este tipo de ejemplo, los datos adicionales se convierten
en pares clave-valor de la propiedad ExtensionData :

P RO P IEDA D. VA LO R N OTA S

Fecha 1/8/2019 12:00 -07:00

TemperatureCelsius 0 Error de coincidencia con distinción de


mayúsculas y minúsculas (
temperatureCelsius en el JSON), por
lo que no se establece la propiedad.

Resumen Acceso frecuente

ExtensionData temperatureCelsius: 25 Dado que el caso no coincidía, esta


propiedad JSON es un extra y se
convierte en un par clave-valor en el
diccionario.

DatesAvailable: La propiedad adicional del JSON se


1/8/2019 12:00 -07:00 convierte en un par clave-valor, con
2/8/2019 12:00 -07:00 una matriz como objeto de valor.

SummaryWords: La propiedad adicional del JSON se


Geniales convierte en un par clave-valor, con
Viento una matriz como objeto de valor.
Húmedo

Cuando se serializa el objeto de destino, los pares clave-valor de los datos de la extensión se convierten en
propiedades JSON tal y como estaban en el JSON entrante:

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 0,
"Summary": "Hot",
"temperatureCelsius": 25,
"DatesAvailable": [
"2019-08-01T00:00:00-07:00",
"2019-08-02T00:00:00-07:00"
],
"SummaryWords": [
"Cool",
"Windy",
"Humid"
]
}

Observe que el nombre de la propiedad ExtensionData no aparece en el archivo JSON. Este comportamiento
permite que JSON realice un viaje de ida y vuelta sin perder ningún dato adicional que de otro modo no se
deserializaría.

Omisión de NULL al deserializar


De forma predeterminada, si una propiedad en JSON es NULL, la propiedad correspondiente en el objeto de
destino se establece en NULL. En algunos escenarios, la propiedad de destino podría tener un valor
predeterminado y no se quiere que un valor NULL invalide el valor predeterminado.
Por ejemplo, supongamos que el siguiente código representa su objeto de destino:
public class WeatherForecastWithDefault
{
public WeatherForecastWithDefault()
{
Date = DateTimeOffset.Parse("2001-01-01");
Summary = "No summary";
}
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Supongamos también que se deserializa el siguiente JSON:

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": null
}

Después de la deserialización, la propiedad Summary del objeto WeatherForecastWithDefault es NULL.


Para cambiar este comportamiento, establezca JsonSerializerOptions.IgnoreNullValues en true , tal y como se
muestra en el ejemplo siguiente:

var options = new JsonSerializerOptions


{
IgnoreNullValues = true
};
weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithDefault>(jsonString, options);

Con esta opción, la propiedad Summary del objeto WeatherForecastWithDefault es el valor predeterminado "No
summary" después de la deserialización.
Los valores NULL en JSON solo se omiten si son válidos. Los valores NULL para los tipos de valor que no aceptan
valores NULL provocan excepciones.

Utf8JsonReader, Utf8JsonWriter y JsonDocument


System.Text.Json.Utf8JsonReader es un lector de solo avance, de baja asignación y de alto rendimiento para texto
JSON con codificación UTF-8 que se lee desde ReadOnlySpan<byte> o ReadOnlySequence<byte> . Utf8JsonReader es
un tipo de bajo nivel que se puede usar para compilar analizadores y deserializadores personalizados. El método
JsonSerializer.Deserialize usa Utf8JsonReader en segundo plano.
System.Text.Json.Utf8JsonWriter ofrece una forma de escribir texto JSON con codificación UTF-8 de alto
rendimiento a partir de tipos de .NET comunes como String , Int32 y DateTime . El escritor es un tipo de bajo
nivel que se puede usar para compilar serializadores personalizados. El método JsonSerializer.Serialize usa
Utf8JsonWriter en segundo plano.

System.Text.Json.JsonDocument ofrece la posibilidad de crear una especificación Document Object Model de solo
lectura (DOM) mediante Utf8JsonReader . La especificación DOM proporciona acceso aleatorio a los datos en una
carga JSON. A los elementos JSON que componen la carga se puede acceder mediante el tipo JsonElement. El tipo
JsonElement contiene los enumeradores de matriz y objeto junto con las API para convertir texto JSON en tipos
de .NET comunes. JsonDocument expone una propiedad RootElement.
En las secciones siguientes se muestra cómo usar estas herramientas para leer y escribir JSON.
Uso de JsonDocument para acceder a datos
En el ejemplo siguiente se muestra cómo usar la clase JsonDocument para el acceso aleatorio a los datos de una
cadena JSON:

double sum = 0;
int count = 0;

using (JsonDocument document = JsonDocument.Parse(jsonString))


{
JsonElement root = document.RootElement;
JsonElement studentsElement = root.GetProperty("Students");
foreach (JsonElement student in studentsElement.EnumerateArray())
{
if (student.TryGetProperty("Grade", out JsonElement gradeElement))
{
sum += gradeElement.GetDouble();
}
else
{
sum += 70;
}
count++;
}
}

double average = sum / count;


Console.WriteLine($"Average grade : {average}");

El código anterior:
Supone que el JSON que se va a analizar está en una cadena denominada jsonString .
Calcula la calificación media de los objetos de una matriz de Students que tienen una propiedad Grade .
Asigna una calificación predeterminada de 70 a los alumnos que no tienen ninguna calificación.
Cuenta los alumnos incrementando una variable count con cada iteración. Una alternativa es llamar a
GetArrayLength, tal y como se muestra en el ejemplo siguiente:
double sum = 0;
int count = 0;

using (JsonDocument document = JsonDocument.Parse(jsonString))


{
JsonElement root = document.RootElement;
JsonElement studentsElement = root.GetProperty("Students");

count = studentsElement.GetArrayLength();

foreach (JsonElement student in studentsElement.EnumerateArray())


{
if (student.TryGetProperty("Grade", out JsonElement gradeElement))
{
sum += gradeElement.GetDouble();
}
else
{
sum += 70;
}
}
}

double average = sum / count;


Console.WriteLine($"Average grade : {average}");

Aquí se muestra un ejemplo del código JSON que este código procesa:

{
"Class Name": "Science",
"Teacher\u0027s Name": "Jane",
"Semester": "2019-01-01",
"Students": [
{
"Name": "John",
"Grade": 94.3
},
{
"Name": "James",
"Grade": 81.0
},
{
"Name": "Julia",
"Grade": 91.9
},
{
"Name": "Jessica",
"Grade": 72.4
},
{
"Name": "Johnathan"
}
],
"Final": true
}

Uso de JsonDocument para escribir JSON


En el siguiente ejemplo se muestra cómo escribir JSON desde una JsonDocument:
string jsonString = File.ReadAllText(inputFileName);

var writerOptions = new JsonWriterOptions


{
Indented = true
};

var documentOptions = new JsonDocumentOptions


{
CommentHandling = JsonCommentHandling.Skip
};

using FileStream fs = File.Create(outputFileName);


using var writer = new Utf8JsonWriter(fs, options: writerOptions);
using JsonDocument document = JsonDocument.Parse(jsonString, documentOptions);

JsonElement root = document.RootElement;

if (root.ValueKind == JsonValueKind.Object)
{
writer.WriteStartObject();
}
else
{
return;
}

foreach (JsonProperty property in root.EnumerateObject())


{
property.WriteTo(writer);
}

writer.WriteEndObject();

writer.Flush();

El código anterior:
Lee un archivo JSON, carga los datos en un JsonDocument y escribe JSON con formato (impreso
correctamente) en un archivo.
Utiliza JsonDocumentOptions para especificar que se permiten los comentarios en el JSON de entrada, pero se
omiten.
Cuando termina, llama a Flush en el escritor. Una alternativa consiste en permitir el vaciado automático del
escritor cuando se elimina.
Aquí se muestra un ejemplo de entrada JSON que el código de ejemplo va a procesar:

{"Class Name": "Science","Teacher's Name": "Jane","Semester": "2019-01-01","Students": [{"Name":


"John","Grade": 94.3},{"Name": "James","Grade": 81.0},{"Name": "Julia","Grade": 91.9},{"Name":
"Jessica","Grade": 72.4},{"Name": "Johnathan"}],"Final": true}

El resultado es la siguiente salida JSON impresa correctamente:


{
"Class Name": "Science",
"Teacher\u0027s Name": "Jane",
"Semester": "2019-01-01",
"Students": [
{
"Name": "John",
"Grade": 94.3
},
{
"Name": "James",
"Grade": 81.0
},
{
"Name": "Julia",
"Grade": 91.9
},
{
"Name": "Jessica",
"Grade": 72.4
},
{
"Name": "Johnathan"
}
],
"Final": true
}

Uso de Utf8JsonWriter
En el siguiente ejemplo, se muestra cómo utilizar la clase Utf8JsonWriter:

var options = new JsonWriterOptions


{
Indented = true
};

using (var stream = new MemoryStream())


{
using (var writer = new Utf8JsonWriter(stream, options))
{
writer.WriteStartObject();
writer.WriteString("date", DateTimeOffset.UtcNow);
writer.WriteNumber("temp", 42);
writer.WriteEndObject();
}

string json = Encoding.UTF8.GetString(stream.ToArray());


Console.WriteLine(json);
}

Uso de Utf8JsonReader
En el siguiente ejemplo, se muestra cómo utilizar la clase Utf8JsonReader:
var options = new JsonReaderOptions
{
AllowTrailingCommas = true,
CommentHandling = JsonCommentHandling.Skip
};
Utf8JsonReader reader = new Utf8JsonReader(jsonUtf8Bytes, options);

while (reader.Read())
{
Console.Write(reader.TokenType);

switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
case JsonTokenType.String:
{
string text = reader.GetString();
Console.Write(" ");
Console.Write(text);
break;
}

case JsonTokenType.Number:
{
int intValue = reader.GetInt32();
Console.Write(" ");
Console.Write(intValue);
break;
}

// Other token types elided for brevity


}
Console.WriteLine();
}

En el código anterior se supone que la variable jsonUtf8 es una matriz de bytes que contiene JSON válido, con
codificación UTF-8.
Filtrado de datos con Utf8JsonReader
En el ejemplo siguiente se muestra cómo leer un archivo de forma sincrónica y buscar un valor:
using System;
using System.IO;
using System.Text;
using System.Text.Json;

namespace SystemTextJsonSamples
{
public class Utf8ReaderFromFile
{
private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name");
private static ReadOnlySpan<byte> Utf8Bom => new byte[] { 0xEF, 0xBB, 0xBF };
public static void Run()
{
// ReadAllBytes if the file encoding is UTF-8:
string fileName = "UniversitiesUtf8.json";
ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);

// Read past the UTF-8 BOM bytes if a BOM exists.


if (jsonReadOnlySpan.StartsWith(Utf8Bom))
{
jsonReadOnlySpan = jsonReadOnlySpan.Slice(Utf8Bom.Length);
}

// Or read as UTF-16 and transcode to UTF-8 to convert to a ReadOnlySpan<byte>


//string fileName = "Universities.json";
//string jsonString = File.ReadAllText(fileName);
//ReadOnlySpan<byte> jsonReadOnlySpan = Encoding.UTF8.GetBytes(jsonString);

int count = 0;
int total = 0;

var reader = new Utf8JsonReader(jsonReadOnlySpan);

while (reader.Read())
{
JsonTokenType tokenType = reader.TokenType;

switch (tokenType)
{
case JsonTokenType.StartObject:
total++;
break;
case JsonTokenType.PropertyName:
if (reader.ValueTextEquals(s_nameUtf8))
{
// Assume valid JSON, known schema
reader.Read();
if (reader.GetString().EndsWith("University"))
{
count++;
}
}
break;
}
}
Console.WriteLine($"{count} out of {total} have names that end with 'University'");
}
}
}

El código anterior:
Supone que el JSON contiene una matriz de objetos y cada objeto puede contener una propiedad "name"
de tipo cadena.
Cuenta los objetos y los valores de propiedad "name" que terminan en "University".
Supone que el archivo tiene codificación UTF-16 y lo transcodifica a UTF-8. Un archivo con codificación
UTF-8 puede leerse directamente en ReadOnlySpan<byte> mediante el código siguiente:

ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);

Si el archivo contiene una marca BOM UTF-8, quítela antes de pasar los bytes a Utf8JsonReader , ya que el
lector espera texto. De lo contrario, la marca BOM se considera JSON no válido y el lector inicia una
excepción.
Aquí se muestra un ejemplo de JSON que el código anterior puede leer. El mensaje de resumen resultante es "2 de
4 tienen nombres que terminan en 'University'":

[
{
"web_pages": [ "https://contoso.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "contoso.edu" ],
"name": "Contoso Community College"
},
{
"web_pages": [ "http://fabrikam.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "fabrikam.edu" ],
"name": "Fabrikam Community College"
},
{
"web_pages": [ "http://www.contosouniversity.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "contosouniversity.edu" ],
"name": "Contoso University"
},
{
"web_pages": [ "http://www.fabrikamuniversity.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "fabrikamuniversity.edu" ],
"name": "Fabrikam University"
}
]

Lectura de una secuencia mediante Utf8JsonReader


Al leer un archivo grande (un gigabyte o más de tamaño, por ejemplo), puede que desee evitar tener que cargar
todo el archivo en la memoria de una vez. En este escenario, puede usar FileStream.
Al usar Utf8JsonReader para leer de una secuencia, se aplican las siguientes reglas:
El búfer que contiene la carga parcial JSON debe ser al menos tan grande como el token JSON más grande
que contiene para que el lector pueda avanzar.
El búfer debe ser al menos tan grande como la secuencia más grande de espacio en blanco dentro del JSON.
El lector no realiza un seguimiento de los datos que ha leído hasta que lea completamente el TokenType
siguiente en la carga JSON. Por tanto, cuando haya bytes restantes en el búfer, tendrá que volver a pasarlos al
lector. Puede usar BytesConsumed para determinar el número de bytes que quedan.
El código siguiente muestra cómo leer desde una secuencia. Este ejemplo se muestra MemoryStream. Un código
similar funcionará con FileStream, excepto cuando FileStream contenga una marca BOM UTF-8 al principio. En
ese caso, debe quitar esos tres bytes del búfer antes de pasar los bytes restantes a Utf8JsonReader . En caso
contrario, el lector produciría una excepción, ya que la marca BOM no se considera una parte válida del JSON.
El código de ejemplo comienza con un búfer de 4 KB y duplica el tamaño del búfer cada vez que encuentra que el
tamaño no es lo suficientemente grande como para ajustarse a un token JSON completo, lo que es necesario para
que el lector realice el progreso de la carga de JSON. El ejemplo de JSON proporcionado en el fragmento de
código desencadena un aumento del tamaño del búfer solo si se establece un tamaño de búfer inicial muy
pequeño, por ejemplo, 10 bytes. Si establece el tamaño de búfer inicial en 10, las instrucciones Console.WriteLine
muestran la causa y el efecto de los aumentos del tamaño del búfer. En el tamaño de búfer inicial de 4 KB, se
muestra todo el JSON de ejemplo en cada Console.WriteLine y el tamaño del búfer nunca se debe aumentar.

using System;
using System.IO;
using System.Text;
using System.Text.Json;

namespace SystemTextJsonSamples
{
public class Utf8ReaderPartialRead
{
public static void Run()
{
var jsonString = @"{
""Date"": ""2019-08-01T00:00:00-07:00"",
""Temperature"": 25,
""TemperatureRanges"": {
""Cold"": { ""High"": 20, ""Low"": -10 },
""Hot"": { ""High"": 60, ""Low"": 20 }
},
""Summary"": ""Hot"",
}";

byte[] bytes = Encoding.UTF8.GetBytes(jsonString);


var stream = new MemoryStream(bytes);

var buffer = new byte[4096];

// Fill the buffer.


// For this snippet, we're assuming the stream is open and has data.
// If it might be closed or empty, check if the return value is 0.
stream.Read(buffer);

var reader = new Utf8JsonReader(buffer, isFinalBlock: false, state: default);


Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");

// Search for "Summary" property name


while (reader.TokenType != JsonTokenType.PropertyName || !reader.ValueTextEquals("Summary"))
{
if (!reader.Read())
{
// Not enough of the JSON is in the buffer to complete a read.
GetMoreBytesFromStream(stream, ref buffer, ref reader);
}
}

// Found the "Summary" property name.


Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
while (!reader.Read())
{
// Not enough of the JSON is in the buffer to complete a read.
GetMoreBytesFromStream(stream, ref buffer, ref reader);
GetMoreBytesFromStream(stream, ref buffer, ref reader);
}
// Display value of Summary property, that is, "Hot".
Console.WriteLine($"Got property value: {reader.GetString()}");
}

private static void GetMoreBytesFromStream(MemoryStream stream, ref byte[] buffer, ref Utf8JsonReader
reader)
{
int bytesRead;
if (reader.BytesConsumed < buffer.Length)
{
ReadOnlySpan<byte> leftover = buffer.AsSpan((int)reader.BytesConsumed);

if (leftover.Length == buffer.Length)
{
Array.Resize(ref buffer, buffer.Length * 2);
Console.WriteLine($"Increased buffer size to {buffer.Length}");
}

leftover.CopyTo(buffer);
bytesRead = stream.Read(buffer.AsSpan(leftover.Length));
}
else
{
bytesRead = stream.Read(buffer);
}
Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
reader = new Utf8JsonReader(buffer, isFinalBlock: bytesRead == 0, reader.CurrentState);
}
}
}

En el ejemplo anterior no se establece ningún límite para el tamaño del búfer. Si el tamaño del token es
demasiado grande, se podría producir un error en el código con una excepción OutOfMemoryException. Esto
puede ocurrir si el archivo JSON contiene un token de aproximadamente 1 GB o más de tamaño, ya que la
duplicación del tamaño de 1 GB da como resultado un tamaño demasiado grande para caber en un búfer de
int32 .

Recursos adicionales
Información general de System.Text.Json
Procedimientos para escribir convertidores personalizados
Procedimiento para migrar desde Newtonsoft.Json
Compatibilidad con DateTime y DateTimeOffset en System.Text.Json
Referencia de la API System.Text.Json
Procedimiento para escribir convertidores
personalizados para la serialización de JSON
(cálculo de referencias) en .NET
16/09/2020 • 28 minutes to read • Edit Online

En este artículo se muestra cómo crear convertidores personalizados para las clases de serialización de JSON
que se proporcionan en el espacio de nombres System.Text.Json. Para disponer de una introducción a
System.Text.Json , vea Cómo serializar y deserializar JSON en .NET.

Un convertidor es una clase que convierte un objeto o un valor en JSON; también admite conversiones a partir
de este formato. El espacio de nombres System.Text.Json tiene convertidores integrados para la mayoría de los
tipos primitivos que se asignan a primitivos de JavaScript. Puede escribir convertidores personalizados:
Para reemplazar el comportamiento predeterminado de un convertidor integrado. Por ejemplo, puede que
quiera que los valores DateTime se representen con el formato mm/dd/aaaa, en lugar del formato ISO 8601-
1:2019 predeterminado.
Para admitir un tipo de valor personalizado. Por ejemplo, una estructura PhoneNumber .
También puede escribir convertidores personalizados para personalizar o extender System.Text.Json con
funcionalidad no incluida en la versión actual. Los siguientes escenarios se describen más adelante en este
artículo:
Deserialización de tipos inferidos en las propiedades de objeto.
Compatibilidad para diccionarios con una clave que no sea de cadena.
Compatibilidad con la deserialización polimórfica.
Compatibilidad con el recorrido de ida y vuelta para Stack<T>.

Patrones de convertidores personalizados


Hay dos patrones para crear un convertidor personalizado: el patrón básico y el patrón de fábrica. El patrón de
fábrica es para los convertidores que controlan el tipo Enum o los valores genéricos abiertos. El patrón básico es
para los tipos no genéricos y los tipos genéricos y cerrados. Por ejemplo, los convertidores de los siguientes tipos
requieren el patrón de fábrica:
Dictionary<TKey, TValue>
Enum
List<T>

Entre los ejemplos de tipos que puede controlar el patrón básico se incluyen los siguientes:
Dictionary<int, string>
WeekdaysEnum
List<DateTimeOffset>
DateTime
Int32

El patrón básico crea una clase que puede controlar un tipo. El patrón de fábrica crea una clase que determina,
en tiempo de ejecución, qué tipo específico es necesario y crea dinámicamente el convertidor adecuado.
Convertidor básico de ejemplo
El ejemplo siguiente es un convertidor que reemplaza la serialización predeterminada para un tipo de datos
existente. El convertidor usa el formato mm/dd/aaaa para las propiedades DateTimeOffset .

using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
public override DateTimeOffset Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
DateTimeOffset.ParseExact(reader.GetString(),
"MM/dd/yyyy", CultureInfo.InvariantCulture);

public override void Write(


Utf8JsonWriter writer,
DateTimeOffset dateTimeValue,
JsonSerializerOptions options) =>
writer.WriteStringValue(dateTimeValue.ToString(
"MM/dd/yyyy", CultureInfo.InvariantCulture));
}
}

Convertidor de patrones de fábrica de ejemplo


En el código siguiente se muestra un convertidor personalizado que funciona con Dictionary<Enum,TValue> . El
código sigue el patrón de fábrica porque el primer parámetro de tipo genérico es Enum y el segundo es abierto.
El método CanConvert devuelve true solo para un elemento Dictionary con dos parámetros genéricos, el
primero de los cuales es un tipo Enum . El convertidor interno obtiene un convertidor existente para controlar el
tipo que se proporciona en tiempo de ejecución para TValue .

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class DictionaryTKeyEnumTValueConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
if (!typeToConvert.IsGenericType)
{
return false;
}

if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>))
{
return false;
}

return typeToConvert.GetGenericArguments()[0].IsEnum;
}
public override JsonConverter CreateConverter(
Type type,
JsonSerializerOptions options)
{
Type keyType = type.GetGenericArguments()[0];
Type valueType = type.GetGenericArguments()[1];

JsonConverter converter = (JsonConverter)Activator.CreateInstance(


typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
new Type[] { keyType, valueType }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { options },
culture: null);

return converter;
}

private class DictionaryEnumConverterInner<TKey, TValue> :


JsonConverter<Dictionary<TKey, TValue>> where TKey : struct, Enum
{
private readonly JsonConverter<TValue> _valueConverter;
private Type _keyType;
private Type _valueType;

public DictionaryEnumConverterInner(JsonSerializerOptions options)


{
// For performance, use the existing converter if available.
_valueConverter = (JsonConverter<TValue>)options
.GetConverter(typeof(TValue));

// Cache the key and value types.


_keyType = typeof(TKey);
_valueType = typeof(TValue);
}

public override Dictionary<TKey, TValue> Read(


ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return dictionary;
}

// Get the key.


if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

string propertyName = reader.GetString();

// For performance, parse with ignoreCase:false first.


if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) &&
!Enum.TryParse(propertyName, ignoreCase: true, out key))
{
throw new JsonException(
$"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\".");
$"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\".");
}

// Get the value.


TValue v;
if (_valueConverter != null)
{
reader.Read();
v = _valueConverter.Read(ref reader, _valueType, options);
}
else
{
v = JsonSerializer.Deserialize<TValue>(ref reader, options);
}

// Add to dictionary.
dictionary.Add(key, v);
}

throw new JsonException();


}

public override void Write(


Utf8JsonWriter writer,
Dictionary<TKey, TValue> dictionary,
JsonSerializerOptions options)
{
writer.WriteStartObject();

foreach (KeyValuePair<TKey, TValue> kvp in dictionary)


{
writer.WritePropertyName(kvp.Key.ToString());

if (_valueConverter != null)
{
_valueConverter.Write(writer, kvp.Value, options);
}
else
{
JsonSerializer.Serialize(writer, kvp.Value, options);
}
}

writer.WriteEndObject();
}
}
}
}

El código anterior es el mismo que el que se muestra en Compatibilidad para diccionarios con una clave que no
sea de cadena, más adelante en este artículo.

Pasos para seguir el patrón básico


En los pasos siguientes se explica cómo crear un convertidor siguiendo el patrón básico:
Cree una clase que derive de JsonConverter<T>, donde T es el tipo que se va a serializar y deserializar.
Reemplace el método Read para deserializar el JSON de entrada y convertirlo al tipo T . Use el elemento
Utf8JsonReader que se pasa al método para leer el JSON.
Invalide el método Write para serializar el objeto de entrada de tipo T . Use el elemento Utf8JsonWriter que
se pasa al método para escribir el JSON.
Reemplace el método CanConvert solo si es necesario. La implementación predeterminada devuelve true
cuando el tipo que se va a convertir es de tipo T . Por lo tanto, los convertidores que solo admiten el tipo T
no necesitan reemplazar este método. Para obtener un ejemplo de un convertidor que necesita reemplazar
este método, consulte la sección sobre la deserialización polimórfica, más adelante en este artículo.
Puede hacer referencia al código fuente de convertidores integrados como implementaciones de referencia para
escribir convertidores personalizados.

Pasos para seguir el patrón de fábrica


En los pasos siguientes se explica cómo crear un convertidor siguiendo el patrón de fábrica:
Cree una clase que derive de JsonConverterFactory.
Reemplace el método CanConvert para devolver "true" cuando el tipo que se va a convertir sea uno que el
convertidor puede controlar. Por ejemplo, si el convertidor es para List<T> , solo puede controlar List<int> ,
List<string> y List<DateTime> .
Invalide el método CreateConverter para devolver una instancia de una clase de convertidor que controlará
el tipo de conversión que se proporciona en tiempo de ejecución.
Cree la clase de convertidor de la que el método CreateConverter crea instancias.
El patrón de fábrica es necesario para los genéricos abiertos porque el código para convertir un objeto en una
cadena (y también para realizar una conversión a partir de esta) no es el mismo para todos los tipos. Un
convertidor para un tipo genérico abierto ( List<T> , por ejemplo) tiene que crear un convertidor para un tipo
genérico cerrado ( List<DateTime> , por ejemplo) en segundo plano. Es necesario escribir código para controlar
cada tipo genérico cerrado que el convertidor puede controlar.
El tipo Enum es similar a un tipo genérico abierto: un convertidor para Enum tiene que crear un convertidor para
un elemento Enum específico ( WeekdaysEnum , por ejemplo) en segundo plano.

Control de errores
Si necesita producir una excepción en el código de control de errores, considere la posibilidad de iniciar una
excepción JsonException sin un mensaje. Este tipo de excepción crea automáticamente un mensaje que incluye la
ruta de acceso a la parte del JSON que causó el error. Por ejemplo, la instrucción throw new JsonException();
genera un mensaje de error como el ejemplo siguiente:

Unhandled exception. System.Text.Json.JsonException:


The JSON value could not be converted to System.Object.
Path: $.Date | LineNumber: 1 | BytePositionInLine: 37.

Si proporciona un mensaje (por ejemplo, throw new JsonException("Error occurred") ), la excepción sigue
proporcionando la ruta de acceso en la propiedad Path.

Registro de un convertidor personalizado


Registre un convertidor personalizado para que los métodos Serialize y Deserialize lo utilicen. Elija uno de
los enfoques siguientes:
Agregue una instancia de la clase de convertidor a la colección JsonSerializerOptions.Converters.
Aplique el atributo [JsonConverter] a las propiedades que requieren el convertidor personalizado.
Aplique el atributo [JsonConverter] a una clase o una estructura que represente un tipo de valor
personalizado.

Ejemplo de registro: colección de convertidores


Este es un ejemplo que hace que DateTimeOffsetConverter sea el valor predeterminado para propiedades de
tipo DateTimeOffset:
var serializeOptions = new JsonSerializerOptions();
serializeOptions.Converters.Add(new DateTimeOffsetConverter());
serializeOptions.WriteIndented = true;
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);

Suponga que serializa una instancia del tipo siguiente:

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Este es un ejemplo de salida JSON que muestra que se ha usado el convertidor personalizado:

{
"Date": "08/01/2019",
"TemperatureCelsius": 25,
"Summary": "Hot"
}

En el código siguiente se usa el mismo enfoque para realizar una deserialización mediante el convertidor
DateTimeOffset personalizado:

var deserializeOptions = new JsonSerializerOptions();


deserializeOptions.Converters.Add(new DateTimeOffsetConverter());
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, deserializeOptions);

Ejemplo de registro: [JsonConverter] en una propiedad


El código siguiente selecciona un convertidor personalizado para la propiedad Date :

public class WeatherForecastWithConverterAttribute


{
[JsonConverter(typeof(DateTimeOffsetConverter))]
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

El código para serializar WeatherForecastWithConverterAttribute no requiere el uso de


JsonSerializeOptions.Converters :

var serializeOptions = new JsonSerializerOptions();


serializeOptions.WriteIndented = true;
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);

El código que se va a deserializar tampoco requiere el uso de Converters :

weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithConverterAttribute>(jsonString);
Ejemplo de registro: [JsonConverter] en un tipo
Este es el código que crea una estructura y le aplica el atributo [JsonConverter] :

using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
[JsonConverter(typeof(TemperatureConverter))]
public struct Temperature
{
public Temperature(int degrees, bool celsius)
{
_degrees = degrees;
_isCelsius = celsius;
}
private bool _isCelsius;
private int _degrees;
public int Degrees => _degrees;
public bool IsCelsius => _isCelsius;
public bool IsFahrenheit => !_isCelsius;
public override string ToString() =>
$"{_degrees.ToString()}{(_isCelsius ? "C" : "F")}";
public static Temperature Parse(string input)
{
int degrees = int.Parse(input.Substring(0, input.Length - 1));
bool celsius = (input.Substring(input.Length - 1) == "C");
return new Temperature(degrees, celsius);
}
}
}

Este es el convertidor personalizado para la estructura anterior:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class TemperatureConverter : JsonConverter<Temperature>
{
public override Temperature Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
Temperature.Parse(reader.GetString());

public override void Write(


Utf8JsonWriter writer,
Temperature temperature,
JsonSerializerOptions options) =>
writer.WriteStringValue(temperature.ToString());
}
}

El atributo [JsonConvert] de la estructura registra el convertidor personalizado como valor predeterminado


para las propiedades de tipo Temperature . El convertidor se usa automáticamente en la propiedad
TemperatureCelsius del tipo siguiente al serializarla o deserializarla:
public class WeatherForecastWithTemperatureStruct
{
public DateTimeOffset Date { get; set; }
public Temperature TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Prioridad de registro del convertidor


Durante la serialización o la deserialización, se elige un convertidor para cada elemento JSON en el orden
siguiente, que se muestra de la prioridad más alta a la más baja:
Elemento [JsonConverter] aplicado a una propiedad.
Convertidor agregado a la colección Converters .
Elemento [JsonConverter] aplicado a un tipo de valor personalizado o POCO.

Si se registran varios convertidores personalizados para un tipo en la colección Converters , se usa el primer
convertidor que devuelve "true" para CanConvert .
Solo se elige un convertidor integrado si no se registra ningún convertidor personalizado aplicable.

Ejemplos de convertidor para escenarios comunes


En las secciones siguientes se proporcionan ejemplos de convertidor que abordan algunos escenarios comunes
que la funcionalidad integrada no controla.
Deserialización de los tipos inferidos en propiedades de objeto
Compatibilidad para diccionarios con una clave que no sea de cadena
Compatibilidad con la deserialización polimórfica
Compatibilidad con el recorrido de ida y vuelta para Stack<T>.
Deserialización de los tipos inferidos en propiedades de objeto
Al realizar una deserialización en una propiedad de tipo object , se crea un objeto JsonElement . La razón es que
el deserializador no sabe qué tipo de CLR debe crear, y tampoco intenta averiguarlo. Por ejemplo, si una
propiedad JSON tiene "true", el deserializador no infiere que el valor es de tipo Boolean , y si un elemento tiene
"01/01/2019", el deserializador no infiere que es de tipo DateTime .
La inferencia de tipos puede ser incorrecta. Si el deserializador analiza un número JSON que no tiene un
separador decimal como long , pueden producirse problemas de salida del intervalo si el valor se serializó
originalmente como ulong o BigInteger . El análisis de un número que tiene un separador decimal como
double puede perder precisión si el número se ha serializado originalmente como decimal .

En escenarios que requieren inferencia de tipos, el código siguiente muestra un convertidor personalizado para
las propiedades object . El código convierte:
true y false , en Boolean
Números sin decimales, en long
Números con un decimal, en double
Fechas, en DateTime
Cadenas, en string
Todo lo demás, en JsonElement
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class ObjectToInferredTypesConverter
: JsonConverter<object>
{
public override object Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.True)
{
return true;
}

if (reader.TokenType == JsonTokenType.False)
{
return false;
}

if (reader.TokenType == JsonTokenType.Number)
{
if (reader.TryGetInt64(out long l))
{
return l;
}

return reader.GetDouble();
}

if (reader.TokenType == JsonTokenType.String)
{
if (reader.TryGetDateTime(out DateTime datetime))
{
return datetime;
}

return reader.GetString();
}

// Use JsonElement as fallback.


// Newtonsoft uses JArray or JObject.
using JsonDocument document = JsonDocument.ParseValue(ref reader);
return document.RootElement.Clone();
}

public override void Write(


Utf8JsonWriter writer,
object objectToWrite,
JsonSerializerOptions options) =>
throw new InvalidOperationException("Should not get here.");
}
}

El código siguiente registra el convertidor:

var deserializeOptions = new JsonSerializerOptions();


deserializeOptions.Converters.Add(new ObjectToInferredTypesConverter());

Este es un ejemplo de tipo con propiedades object :


public class WeatherForecastWithObjectProperties
{
public object Date { get; set; }
public object TemperatureCelsius { get; set; }
public object Summary { get; set; }
}

El siguiente ejemplo de JSON para deserializar contiene valores que se deserializarán como DateTime , long y
string :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
}

Sin el convertidor personalizado, la deserialización coloca un elemento JsonElement en cada propiedad.


La carpeta de pruebas unitarias en el espacio de nombres System.Text.Json.Serialization tiene más ejemplos
de convertidores personalizados que controlan la deserialización en propiedades object .
Compatibilidad para diccionarios con una clave que no sea de cadena
La compatibilidad integrada con colecciones de diccionarios es para Dictionary<string, TValue> . Por lo tanto, la
clave debe ser una cadena. Para admitir un diccionario con un entero o algún otro tipo como clave, se requiere
un convertidor personalizado.
En el código siguiente se muestra un convertidor personalizado que funciona con Dictionary<Enum,TValue> :

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class DictionaryTKeyEnumTValueConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
if (!typeToConvert.IsGenericType)
{
return false;
}

if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>))
{
return false;
}

return typeToConvert.GetGenericArguments()[0].IsEnum;
}

public override JsonConverter CreateConverter(


Type type,
JsonSerializerOptions options)
{
Type keyType = type.GetGenericArguments()[0];
Type valueType = type.GetGenericArguments()[1];

JsonConverter converter = (JsonConverter)Activator.CreateInstance(


typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
new Type[] { keyType, valueType }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { options },
culture: null);

return converter;
}

private class DictionaryEnumConverterInner<TKey, TValue> :


JsonConverter<Dictionary<TKey, TValue>> where TKey : struct, Enum
{
private readonly JsonConverter<TValue> _valueConverter;
private Type _keyType;
private Type _valueType;

public DictionaryEnumConverterInner(JsonSerializerOptions options)


{
// For performance, use the existing converter if available.
_valueConverter = (JsonConverter<TValue>)options
.GetConverter(typeof(TValue));

// Cache the key and value types.


_keyType = typeof(TKey);
_valueType = typeof(TValue);
}

public override Dictionary<TKey, TValue> Read(


ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return dictionary;
}

// Get the key.


if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

string propertyName = reader.GetString();

// For performance, parse with ignoreCase:false first.


if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) &&
!Enum.TryParse(propertyName, ignoreCase: true, out key))
{
throw new JsonException(
$"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\".");
}

// Get the value.


TValue v;
if (_valueConverter != null)
{
reader.Read();
v = _valueConverter.Read(ref reader, _valueType, options);
}
}
else
{
v = JsonSerializer.Deserialize<TValue>(ref reader, options);
}

// Add to dictionary.
dictionary.Add(key, v);
}

throw new JsonException();


}

public override void Write(


Utf8JsonWriter writer,
Dictionary<TKey, TValue> dictionary,
JsonSerializerOptions options)
{
writer.WriteStartObject();

foreach (KeyValuePair<TKey, TValue> kvp in dictionary)


{
writer.WritePropertyName(kvp.Key.ToString());

if (_valueConverter != null)
{
_valueConverter.Write(writer, kvp.Value, options);
}
else
{
JsonSerializer.Serialize(writer, kvp.Value, options);
}
}

writer.WriteEndObject();
}
}
}
}

El código siguiente registra el convertidor:

var serializeOptions = new JsonSerializerOptions();


serializeOptions.Converters.Add(new DictionaryTKeyEnumTValueConverter());

El convertidor puede serializar y deserializar la propiedad TemperatureRanges de la siguiente clase que usa el
elemento Enum siguiente:

public class WeatherForecastWithEnumDictionary


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public Dictionary<SummaryWordsEnum, int> TemperatureRanges { get; set; }
}

public enum SummaryWordsEnum


{
Cold, Hot
}

La salida JSON de la serialización es similar al ejemplo siguiente:


{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"TemperatureRanges": {
"Cold": 20,
"Hot": 40
}
}

La carpeta de pruebas unitarias en el espacio de nombres System.Text.Json.Serialization tiene más ejemplos


de convertidores personalizados que controlan los diccionarios con una clave que no sea de cadena.
Compatibilidad con la deserialización polimórfica
Las características integradas proporcionan una gama limitada de serialización polimórfica, pero no admiten
ningún tipo de deserialización. La deserialización requiere un convertidor personalizado.
Suponga, por ejemplo, que tiene una clase base abstracta Person , con clases derivadas Employee y Customer .
La deserialización polimórfica significa que puede especificar Person como destino de la deserialización en
tiempo de diseño, y los objetos Customer y Employee en el JSON se deserializan correctamente en tiempo de
ejecución. Durante la deserialización, debe buscar pistas que identifiquen el tipo requerido en JSON. Los tipos de
pistas disponibles varían según cada escenario. Por ejemplo, una propiedad de discriminador puede estar
disponible o puede tener que confiar en la presencia o ausencia de una propiedad determinada. La versión
actual de System.Text.Json no proporciona atributos para especificar cómo controlar los escenarios de
deserialización polimórficos, por lo que se requieren convertidores personalizados.
En el código siguiente se muestra una clase base, dos clases derivadas y un convertidor personalizado para ellas.
El convertidor usa una propiedad de discriminador para realizar la deserialización polimórfica. El discriminador
de tipo no se encuentra en las definiciones de clase, pero se crea durante la serialización y se lee durante la
deserialización.

public class Person


{
public string Name { get; set; }
}

public class Customer : Person


{
public decimal CreditLimit { get; set; }
}

public class Employee : Person


{
public string OfficeNumber { get; set; }
}

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class PersonConverterWithTypeDiscriminator : JsonConverter<Person>
{
enum TypeDiscriminator
{
Customer = 1,
Employee = 2
}
public override bool CanConvert(Type typeToConvert) =>
typeof(Person).IsAssignableFrom(typeToConvert);

public override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions


options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

string propertyName = reader.GetString();


if (propertyName != "TypeDiscriminator")
{
throw new JsonException();
}

reader.Read();
if (reader.TokenType != JsonTokenType.Number)
{
throw new JsonException();
}

TypeDiscriminator typeDiscriminator = (TypeDiscriminator)reader.GetInt32();


Person person = typeDiscriminator switch
{
TypeDiscriminator.Customer => new Customer(),
TypeDiscriminator.Employee => new Employee(),
_ => throw new JsonException()
};

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return person;
}

if (reader.TokenType == JsonTokenType.PropertyName)
{
propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "CreditLimit":
decimal creditLimit = reader.GetDecimal();
((Customer)person).CreditLimit = creditLimit;
break;
case "OfficeNumber":
string officeNumber = reader.GetString();
((Employee)person).OfficeNumber = officeNumber;
break;
case "Name":
string name = reader.GetString();
person.Name = name;
break;
}
}
}

throw new JsonException();


}
public override void Write(Utf8JsonWriter writer, Person person, JsonSerializerOptions options)
{
writer.WriteStartObject();

if (person is Customer customer)


{
writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.Customer);
writer.WriteNumber("CreditLimit", customer.CreditLimit);
}
else if (person is Employee employee)
{
writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.Employee);
writer.WriteString("OfficeNumber", employee.OfficeNumber);
}

writer.WriteString("Name", person.Name);

writer.WriteEndObject();
}
}
}

El código siguiente registra el convertidor:

var serializeOptions = new JsonSerializerOptions();


serializeOptions.Converters.Add(new PersonConverterWithTypeDiscriminator());

El convertidor puede deserializar el JSON que se creó con el mismo convertidor para serializar, por ejemplo:

[
{
"TypeDiscriminator": 1,
"CreditLimit": 10000,
"Name": "John"
},
{
"TypeDiscriminator": 2,
"OfficeNumber": "555-1234",
"Name": "Nancy"
}
]

El código del convertidor en el ejemplo anterior lee y escribe cada propiedad manualmente. Una alternativa es
llamar a Deserialize o Serialize para realizar parte del trabajo. Para obtener un ejemplo, vea esta publicación
de StackOverflow.
Compatibilidad con el recorrido de ida y vuelta para Stack<T>
Si deserializa una cadena JSON en un objeto Stack<T> y después serializa ese objeto, el contenido de la pila está
en orden inverso. Este comportamiento se aplica a los siguientes tipos e interfaz, así como a los tipos definidos
por el usuario que derivan de ellos:
Stack
Stack<T>
ConcurrentStack<T>
ImmutableStack<T>
IImmutableStack<T>
Para admitir la serialización y deserialización que conserva el orden original en la pila, se requiere un
convertidor personalizado.
En el código siguiente se muestra un convertidor personalizado que permite el recorrido de ida y vuelta hacia y
desde objetos Stack<T> :

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class JsonConverterFactoryForStackOfT : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert.IsGenericType &&
typeToConvert.GetGenericTypeDefinition() == typeof(Stack<>);
}

public override JsonConverter CreateConverter(


Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(typeToConvert.IsGenericType &&
typeToConvert.GetGenericTypeDefinition() == typeof(Stack<>));

Type elementType = typeToConvert.GetGenericArguments()[0];

JsonConverter converter = (JsonConverter)Activator.CreateInstance(


typeof(JsonConverterForStackOfT<>)
.MakeGenericType(new Type[] { elementType }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: null,
culture: null)!;

return converter;
}
}

public class JsonConverterForStackOfT<T> : JsonConverter<Stack<T>>


{
public override Stack<T> Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartArray || !reader.Read())
{
throw new JsonException();
}

var elements = new Stack<T>();

while (reader.TokenType != JsonTokenType.EndArray)


{
elements.Push(JsonSerializer.Deserialize<T>(ref reader, options));

if (!reader.Read())
{
throw new JsonException();
}
}

return elements;
}

public override void Write(


Utf8JsonWriter writer, Stack<T> value, JsonSerializerOptions options)
{
{
writer.WriteStartArray();

var reversed = new Stack<T>(value);

foreach (T item in reversed)


{
JsonSerializer.Serialize(writer, item, options);
}

writer.WriteEndArray();
}
}
}

El código siguiente registra el convertidor:

var options = new JsonSerializerOptions


{
Converters = { new JsonConverterFactoryForStackOfT() },
};

Otros ejemplos de convertidor personalizado


El artículo Migración de Newtonsoft.Json a System.Text.Json contiene ejemplos adicionales de convertidores
personalizados.
La carpeta de pruebas unitarias en el código fuente de System.Text.Json.Serialization incluye otros ejemplos
de convertidor personalizado, como:
Convertidor Int32 que convierte valores nulos en 0 al realizar la deserialización
Convertidor Int32 que permite valores de cadena y número al realizar la deserialización
Convertidor Enum
Convertidor List<T> que acepta datos externos
Convertidor Long[] que funciona con una lista de números delimitados por comas
Si necesita crear un convertidor que modifique el comportamiento de un convertidor integrado existente, puede
obtener el código fuente del convertidor existente para que sirva como punto de partida para la personalización.

Recursos adicionales
Código fuente para convertidores integrados
Compatibilidad con DateTime y DateTimeOffset en System.Text.Json
Información general sobre System.Text.Json
Procedimiento para usar System.Text.Json
Procedimiento para migrar de Newtonsoft.Json
Referencia de API de System.Text.Json
Referencia de API de System.Text.Json.Serialization
Procedimiento para realizar la migración de
Newtonsoft.Json a System.Text.Json
16/09/2020 • 65 minutes to read • Edit Online

En este artículo se muestra cómo realizar la migración de Newtonsoft.Json a System.Text.Json.


El espacio de nombres System.Text.Json proporciona funcionalidad para serializar y deserializar desde JSON
(notaciones de objetos JavaScript). La biblioteca de System.Text.Json se incluye en el marco compartido de .NET
Core 3.0. En cuanto a otros marcos de destino, instale el paquete NuGet System.Text.Json, que admite lo
siguiente:
.NET Standard 2.0 y versiones posteriores
.NET Framework 4.7.2 y versiones posteriores
.NET Core 2.0, 2.1 y 2.2
System.Text.Json se centra principalmente en el rendimiento, la seguridad y el cumplimiento de estándares.
Tiene algunas diferencias clave en el comportamiento predeterminado y no pretende tener paridad de
características con Newtonsoft.Json . En algunos escenarios, System.Text.Json no tiene ninguna funcionalidad
integrada, pero se recomiendan algunas soluciones alternativas. En otros escenarios, las soluciones alternativas
no son prácticas. Si su aplicación depende de una característica que falta, puede presentar una incidencia para
averiguar si se puede agregar compatibilidad para su escenario.
La mayor parte de este artículo trata sobre cómo usar la API de JsonSerializer, pero también incluye
instrucciones sobre cómo usar JsonDocument (que representa los Document Object Model o DOM),
Utf8JsonReader y Utf8JsonWriter.

Tabla de diferencias entre Newtonsoft.Json y System.Text.Json


En la siguiente tabla se enumeran las características de Newtonsoft.Json y las equivalentes de System.Text.Json .
Existen varias categorías de equivalentes:
Compatible con la funcionalidad integrada. Para obtener un comportamiento similar al de System.Text.Json
es posible que se requiera el uso de un atributo o una opción global.
No compatible; hay disponible una solución alternativa. Las soluciones alternativas son convertidores
personalizados, que puede que no proporcionen una paridad completa con la funcionalidad Newtonsoft.Json
. En esos casos, se proporciona un código de ejemplo como muestra. Si confía en estas características de
Newtonsoft.Json , la migración requerirá modificaciones en los modelos de objetos .NET u otros cambios de
código.
No compatible; la solución alternativa no es práctica o no es posible. Si confía en estas características de
Newtonsoft.Json , no será posible realizar la migración sin cambios importantes.

C A RA C T ERÍST IC A : N EW TO N SO F T. JSO N EQ UIVA L EN T E: SY ST EM . T EXT. JSO N

Deserialización sin distinción entre mayúsculas y minúsculas ️ Valor global PropertyNameCaseInsensitive



de forma predeterminada

Nombres de propiedad en mayúsculas y minúsculas (Camel) ️ Valor global PropertyNamingPolicy


Mínimo escape de caracteres ️ Escape de caracteres estricto, configurable



C A RA C T ERÍST IC A : N EW TO N SO F T. JSO N EQ UIVA L EN T E: SY ST EM . T EXT. JSO N

Valor global NullValueHandling.Ignore ️ Opción global IgnoreNullValues


Permitir comentarios ️ Valor global ReadCommentHandling


Permitir comas finales ️ Valor global AllowTrailingCommas


Registro del convertidor personalizado ️ El orden de precedencia es diferente


De forma predeterminada, no hay ninguna profundidad ️ Profundidad máxima predeterminada de 64, configurable

máxima

Compatibilidad con una gran variedad de tipos ️ Algunos tipos requieren convertidores personalizados

Deserializar cadenas como números ️ No compatible, solución alternativa, ejemplo


Deserializar Dictionary con clave que no sea de cadena ️ No compatible, solución alternativa, ejemplo

Serialización polimórfica ️ No compatible, solución alternativa, ejemplo


Deserialización polimórfica ️ No compatible, solución alternativa, ejemplo


Deserializar los tipos inferidos en propiedades de object ️ No compatible, solución alternativa, ejemplo

Deserializar el literal null de JSON a tipos de valor que no ️ No compatible, solución alternativa, ejemplo

aceptan valores NULL

Deserialización en clases y estructuras inmutables ️ No compatible, solución alternativa, ejemplo


Atributo [JsonConstructor] ️ No compatible, solución alternativa, ejemplo


Valor Required en el atributo [JsonProperty] ️ No compatible, solución alternativa, ejemplo


Valor NullValueHandling en el atributo [JsonProperty] ️ No compatible, solución alternativa, ejemplo


Valor DefaultValueHandling en el atributo ️ No compatible, solución alternativa, ejemplo



[JsonProperty]

Valor global DefaultValueHandling ️ No compatible, solución alternativa, ejemplo


DefaultContractResolver para excluir propiedades ️ No compatible, solución alternativa, ejemplo


Valores DateTimeZoneHandling y DateFormatString ️ No compatibles, solución alternativa, ejemplo


Devoluciones de llamada ️ No compatibles, solución alternativa, ejemplo


Compatibilidad con campos públicos y no públicos ️ No compatible, solución alternativa


Compatibilidad con captadores y establecedores de ️ No compatible, solución alternativa



propiedades internos y privados
C A RA C T ERÍST IC A : N EW TO N SO F T. JSO N EQ UIVA L EN T E: SY ST EM . T EXT. JSO N

Método JsonConvert.PopulateObject ️ No compatible, solución alternativa


Valor global ObjectCreationHandling ️ No compatible, solución alternativa


Agregar a colecciones sin establecedores ️ No compatible, solución alternativa


Valor global PreserveReferencesHandling ❌ No compatible

Valor global ReferenceLoopHandling ❌ No compatible

Compatibilidad con atributos ❌ No compatible


System.Runtime.Serialization

Valor global MissingMemberHandling ❌ No compatible

Permitir nombres de propiedad sin comillas ❌ No compatible

Permitir comillas simples alrededor de los valores de cadena ❌ No compatible

Permitir valores JSON que no son de cadena para las ❌ No compatible


propiedades de cadena

Esta no es una lista exhaustiva de características de Newtonsoft.Json . La lista incluye muchos de los escenarios
que se han solicitado en publicaciones de problemas de GitHub o StackOverflow. Si implementa una solución
alternativa para uno de los escenarios que aquí se enumeran que no tenga actualmente un código de ejemplo, y
si quiere compartir la solución, haga clic en Esta página en la sección Comentarios de la parte inferior de esta
página. De esta forma se abre una incidencia en el repositorio de GitHub de esta documentación y también se
muestra en la sección Comentarios de esta página.

Diferencias en el comportamiento predeterminado de JsonSerializer


en comparación con Newtonsoft.Json
System.Text.Json es estricto de forma predeterminada y evita cualquier conjetura o interpretación en nombre del
llamador, con lo que resalta el comportamiento determinista. La biblioteca se ha diseñado intencionadamente de
esta manera por motivos de rendimiento y seguridad. De manera predeterminada, Newtonsoft.Json es flexible.
Esta diferencia fundamental en el diseño es la responsable de muchas de las siguientes diferencias específicas en
el comportamiento predeterminado.
Deserialización sin distinción entre mayúsculas y minúsculas
Durante la deserialización, Newtonsoft.Json realiza de forma predeterminada la coincidencia de nombres de
propiedad sin distinción entre mayúsculas y minúsculas. El valor predeterminado de System.Text.Json distingue
entre mayúsculas y minúsculas, lo que proporciona un mejor rendimiento, ya que realiza una coincidencia
exacta. Para obtener información sobre cómo realizar la coincidencia sin distinción entre mayúsculas y
minúsculas, vea Coincidencia de propiedades sin distinción entre mayúsculas y minúsculas.
Si usa System.Text.Json indirectamente mediante ASP.NET Core, no es necesario hacer nada para obtener un
comportamiento como Newtonsoft.Json . ASP.NET Core especifica los valores para los nombres de propiedad
con grafía Camel y la coincidencia sin distinción entre mayúsculas y minúsculas cuando usa System.Text.Json .
Los valores predeterminados se establecen en la clase JsonOptions.
Mínimo escape de caracteres
Durante la serialización, Newtonsoft.Json es relativamente permisivo con respecto a si se permite que los
caracteres queden sin escapar. Es decir, no los reemplaza por \uxxxx donde xxxx es el punto de código del
carácter. En aquellos casos donde los escapa, lo hace emitiendo una \ antes del carácter (por ejemplo, " se
convierte en \" ). De forma predeterminada, System.Text.Json escapa más caracteres para proporcionar
protecciones de defensa en profundidad contra los ataques de scripting entre sitios (XSS) o de divulgación de
información, y lo hace mediante el uso de la secuencia de seis caracteres. System.Text.Json escapa todos los
caracteres que no sean ASCII de forma predeterminada, por lo que no es necesario hacer nada si usa
StringEscapeHandling.EscapeNonAscii en Newtonsoft.Json . System.Text.Json de forma predeterminada también
escapa los caracteres que distinguen HTML. Para obtener información sobre cómo invalidar el comportamiento
predeterminado de System.Text.Json , vea Personalización de la codificación de caracteres.
Comentarios
Durante la deserialización, Newtonsoft.Json omite de forma predeterminada los comentarios en JSON. El valor
predeterminado de System.Text.Json es producir excepciones para los comentarios porque la especificación
RFC 8259 no los incluye. Para obtener información sobre cómo permitir comentarios, vea Permitir comentarios
y comas finales.
Comas finales
Durante la deserialización, Newtonsoft.Json omite de forma predeterminada las comas finales. También omite
varias comas finales (por ejemplo, [{"Color":"Red"},{"Color":"Green"},,] ). El valor predeterminado de
System.Text.Json es producir excepciones para las comas finales porque la especificación RFC 8259 no las
permite. Para obtener información sobre cómo hacer que System.Text.Json las acepte, vea Permitir comentarios
y comas finales. No hay ninguna manera de permitir varias comas finales.
Precedencia de registro del convertidor
La precedencia de registro de Newtonsoft.Json para los convertidores personalizados es la siguiente:
Atributo en la propiedad
Atributo en el tipo
Colección de convertidores
Este orden implica que un convertidor que se registre aplicando un atributo en el nivel de tipo invalidará a un
convertidor personalizado de la colección Converters , y un atributo en el nivel de propiedad invalidará a ambos
registros.
La precedencia de registro de System.Text.Json para los convertidores personalizados es diferente:
Atributo en la propiedad
Colección Converters
Atributo en el tipo
En este caso, la diferencia es que un convertidor personalizado de la colección Converters invalida a un atributo
en el nivel de tipo. La intención de este orden de precedencia es que los cambios de tiempo de ejecución
invaliden las opciones de tiempo de diseño. No hay ninguna manera de cambiar la precedencia.
Para más información sobre el registro de convertidores personalizados, vea Registro de un convertidor
personalizado.
Profundidad máxima
De forma predeterminada, Newtonsoft.Json no tiene un límite de profundidad máxima. Par System.Text.Json hay
un límite predeterminado de 64, y se puede configurar mediante el valor JsonSerializerOptions.MaxDepth.
Si usa System.Text.Json indirectamente mediante ASP.NET Core, el límite predeterminado de profundidad
máxima es de 32. El valor predeterminado es el mismo que para el enlace de modelos y se establece en la clase
JsonOptions.
Cadenas JSON (nombres de propiedad y valores de cadena)
Durante la deserialización, Newtonsoft.Json acepta nombres de propiedad entre comillas dobles, comillas
simples o sin comillas. Acepta valores de cadena entre comillas dobles o comillas simples. Por ejemplo,
Newtonsoft.Json acepta el siguiente código JSON:

{
"name1": "value",
'name2': "value",
name3: 'value'
}

System.Text.Json solo acepta nombres de propiedad y valores de cadena entre comillas dobles, ya que ese es el
formato requerido por la especificación RFC 8259 y es el único formato que se considera JSON válido.
Un valor entre comillas simples da como resultado una JsonException con el siguiente mensaje:

''' is an invalid start of a value.

Valores que no son de cadena para propiedades de cadena


Newtonsoft.Json acepta valores que no son de cadena, como un número o los literales true y false para la
deserialización de las propiedades de tipo cadena. A continuación se muestra un ejemplo de JSON que
deserializa correctamente Newtonsoft.Json en la clase siguiente:

{
"String1": 1,
"String2": true,
"String3": false
}

public class ExampleClass


{
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
}

System.Text.Json no deserializa valores que no son de cadena en propiedades de cadena. Un valor que no sea
de cadena recibido para un campo de cadena da como resultado una JsonException con el siguiente mensaje:

The JSON value could not be converted to System.String.

Escenarios con JsonSerializer que requieren soluciones alternativas


Los escenarios siguientes no son compatibles con la funcionalidad integrada, pero hay disponibles soluciones
alternativas. Las soluciones alternativas son convertidores personalizados, que puede que no proporcionen una
paridad completa con la funcionalidad Newtonsoft.Json . En esos casos, se proporciona un código de ejemplo
como muestra. Si confía en estas características de Newtonsoft.Json , la migración requerirá modificaciones en
los modelos de objetos .NET u otros cambios de código.
Tipos sin compatibilidad integrada
System.Text.Json no proporciona compatibilidad integrada con los siguientes tipos:
DataTable y tipos relacionados
Tipos en F#, como las uniones discriminadas, los tipos de registro y los tipos de registros anónimos.
ExpandoObject
TimeZoneInfo
BigInteger
TimeSpan
DBNull
Type
ValueTuple y sus tipos genéricos asociados
Se pueden implementar convertidores personalizados para tipos que no tienen compatibilidad integrada.
Números entrecomillados
Newtonsoft.Json puede serializar o deserializar los números representados por cadenas JSON (entre comillas).
Por ejemplo, puede aceptar: {"DegreesCelsius":"23"} en lugar de {"DegreesCelsius":23} . Para habilitar ese
comportamiento en System.Text.Json, implemente un convertidor personalizado como el ejemplo siguiente. El
convertidor controla las propiedades definidas como long :
Las serializa como cadenas JSON.
Acepta números de JSON y números entre comillas durante la deserialización.

using System;
using System.Buffers;
using System.Buffers.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class LongToStringConverter : JsonConverter<long>
{
public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() :
reader.ValueSpan;
if (Utf8Parser.TryParse(span, out long number, out int bytesConsumed) && span.Length ==
bytesConsumed)
return number;

if (Int64.TryParse(reader.GetString(), out number))


return number;
}

return reader.GetInt64();
}

public override void Write(Utf8JsonWriter writer, long longValue, JsonSerializerOptions options)


{
writer.WriteStringValue(longValue.ToString());
}
}
}

Registre este convertidor personalizado usando un atributo en propiedades individuales de long o agregando
el convertidor a la colección Converters.
Diccionario con clave que no es de cadena
Newtonsoft.Json admite colecciones de tipo Dictionary<TKey, TValue> . La compatibilidad integrada con
colecciones de diccionarios en System.Text.Json se limita a Dictionary<string, TValue> . Por lo tanto, la clave
debe ser una cadena.
Para admitir un diccionario con un entero o algún otro tipo como clave, cree un convertidor como el ejemplo de
Cómo escribir convertidores personalizados.
Serialización polimórfica
Newtonsoft.Json realiza automáticamente la serialización polimórfica. Para obtener información sobre las
capacidades limitadas de serialización polimórfica de System.Text.Json, vea Serialización de propiedades de
clases derivadas.
La solución alternativa que se describe aquí es para definir propiedades que pueden contener clases derivadas
como el tipo object . Si eso no es posible, otra opción es crear un convertidor con un método Write para toda
la jerarquía de tipo de herencia, como en el ejemplo de Cómo escribir convertidores personalizados.
Deserialización polimórfica
Newtonsoft.Json tiene un valor TypeNameHandling que agrega metadatos de nombre de tipo al JSON durante la
serialización. Usa los metadatos durante la deserialización para realizar la deserialización polimórfica.
System.Text.Json puede realizar un intervalo limitado de serialización polimórfica, pero no deserialización
polimórfica.
Para admitir la deserialización polimórfica, cree un convertidor como el ejemplo de Cómo escribir convertidores
personalizados.
Deserialización de propiedades de objeto
Cuando Newtonsoft.Json deserializa en Object:
infiere el tipo de valores primitivos en la carga JSON (excepto null ) y devuelve los valores string , long ,
double , boolean o DateTime almacenados como un objeto al que se ha aplicado la conversión boxing. Los
valores primitivos son valores JSON únicos, como un número JSON, una cadena, un valor true , false o
null .
Devuelve JObject o JArray para valores complejos en la carga de JSON. Los valores complejos son
colecciones de pares clave-valor JSON entre llaves ( {} ) o listas de valores entre corchetes ( [] ). Las
propiedades y los valores entre llaves o corchetes pueden tener propiedades o valores adicionales.
Devuelve una referencia nula cuando la carga útil tiene el literal JSON null .

System.Text.Json almacena un objeto JsonElement al que se ha aplicado la conversión boxing para valores
primitivos y los complejos, siempre que se deserialice en Object; por ejemplo:
Propiedad object .
Un valor de diccionario object .
Un valor de matriz object .
Una raíz object .

Pero System.Text.Json trata null igual que Newtonsoft.Json , y devuelve una referencia nula cuando la carga
útil tiene el literal JSON null en ella.
Para implementar la inferencia de tipos para las propiedades object , cree un convertidor como el ejemplo de
Cómo escribir convertidores personalizados.
Deserialización de null en un tipo que no acepta valores NULL
Newtonsoft.Json no provoca una excepción en el escenario siguiente:

NullValueHandling se establece en Ignore y,


durante la deserialización, el archivo JSON contiene un valor NULL para un tipo de valor que no acepta
valores NULL.
En el mismo escenario, System.Text.Json produce una excepción. (La configuración de control de valores NULL
correspondiente es JsonSerializerOptions.IgnoreNullValues).
Si es el propietario del tipo de destino, la mejor solución alternativa posible es hacer que la propiedad en
cuestión acepte valores NULL (por ejemplo, cambiar int a int? ).
Otra solución alternativa consiste en crear un convertidor para el tipo, como en el ejemplo siguiente, en el que
se tratan los valores NULL de los tipos DateTimeOffset :

using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class DateTimeOffsetNullHandlingConverter : JsonConverter<DateTimeOffset>

{
public override DateTimeOffset Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return default;
}
return reader.GetDateTimeOffset();
}

public override void Write(


Utf8JsonWriter writer,
DateTimeOffset dateTimeValue,
JsonSerializerOptions options)
{
writer.WriteStringValue(dateTimeValue);
}
}
}

Registre este convertidor personalizado usando un atributo en la propiedad o agregando el convertidor a la


colección Converters.
Nota: El convertidor anterior trata los valores NULL de manera diferente de como lo hace
Newtonsoft.Json para los POCO que especifican valores predeterminados. Por ejemplo, suponga que el
siguiente código representa su objeto de destino:

public class WeatherForecastWithDefault


{
public WeatherForecastWithDefault()
{
Date = DateTimeOffset.Parse("2001-01-01");
Summary = "No summary";
}
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}
Y suponga que el siguiente código JSON se deserializa con el convertidor anterior:

{
"Date": null,
"TemperatureCelsius": 25,
"Summary": null
}

Después de la deserialización, la propiedad Date tiene 1/1/0001 ( default(DateTimeOffset) ), es decir, se


sobrescribe el valor establecido en el constructor. Dados los mismos objetos POCO y JSON, la deserialización de
Newtonsoft.Json dejaría 1/1/2001 en la propiedad Date .

Deserialización en clases y estructuras inmutables


Newtonsoft.Json se puede deserializar en clases y estructuras inmutables, ya que puede usar constructores que
tengan parámetros. System.Text.Json solo admite constructores públicos carentes de parámetros. Como solución
alternativa, puede llamar a un constructor con parámetros en un convertidor personalizado.
A continuación se muestra una estructura inmutable con varios parámetros de constructor:

public readonly struct ImmutablePoint


{
public ImmutablePoint(int x, int y)
{
X = x;
Y = y;
}

public int X { get; }


public int Y { get; }
}

Y este es un convertidor que serializa y deserializa esta estructura:

using System;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class ImmutablePointConverter : JsonConverter<ImmutablePoint>
{
private readonly JsonEncodedText XName = JsonEncodedText.Encode("X");
private readonly JsonEncodedText YName = JsonEncodedText.Encode("Y");

private readonly JsonConverter<int> _intConverter;

public ImmutablePointConverter(JsonSerializerOptions options)


{
if (options?.GetConverter(typeof(int)) is JsonConverter<int> intConverter)
{
_intConverter = intConverter;
}
else
{
throw new InvalidOperationException();
}
}

public override ImmutablePoint Read(


ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
};

int x = default;
bool xSet = false;

int y = default;
bool ySet = false;

// Get the first property.


reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

if (reader.ValueTextEquals(XName.EncodedUtf8Bytes))
{
x = ReadProperty(ref reader, options);
xSet = true;
}
else if (reader.ValueTextEquals(YName.EncodedUtf8Bytes))
{
y = ReadProperty(ref reader, options);
ySet = true;
}
else
{
throw new JsonException();
}

// Get the second property.


reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

if (xSet && reader.ValueTextEquals(YName.EncodedUtf8Bytes))


{
y = ReadProperty(ref reader, options);
}
else if (ySet && reader.ValueTextEquals(XName.EncodedUtf8Bytes))
{
x = ReadProperty(ref reader, options);
}
else
{
throw new JsonException();
}

reader.Read();

if (reader.TokenType != JsonTokenType.EndObject)
{
throw new JsonException();
}

return new ImmutablePoint(x, y);


}

private int ReadProperty(ref Utf8JsonReader reader, JsonSerializerOptions options)


{
Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
reader.Read();
return _intConverter.Read(ref reader, typeof(int), options);
}

private void WriteProperty(Utf8JsonWriter writer, JsonEncodedText name, int intValue,


JsonSerializerOptions options)
{
writer.WritePropertyName(name);
_intConverter.Write(writer, intValue, options);
}

public override void Write(


Utf8JsonWriter writer,
ImmutablePoint point,
JsonSerializerOptions options)
{
writer.WriteStartObject();
WriteProperty(writer, XName, point.X, options);
WriteProperty(writer, YName, point.Y, options);
writer.WriteEndObject();
}
}
}

Registre este convertidor personalizado agregando el convertidor a la colección Converters.


Para obtener un ejemplo de un convertidor similar que controla las propiedades genéricas abiertas, vea el
convertidor integrado para pares clave-valor.
Especificación del constructor que se va a usar
El atributo [JsonConstructor] de Newtonsoft.Json le permite especificar el constructor al que se llamará al
deserializar en un objeto POCO. System.Text.Json solo admite constructores carentes de parámetros. Como
solución alternativa, puede llamar al constructor que necesite en un convertidor personalizado. Vea el ejemplo
para deserialización en clases y estructuras inmutables.
Propiedades obligatorias
En Newtonsoft.Json , especifica que se requiere una propiedad estableciendo Required en el atributo
[JsonProperty] . Newtonsoft.Json produce una excepción si no se recibe ningún valor en el objeto JSON para
una propiedad marcada como requerida.
System.Text.Json no produce una excepción si no se recibe ningún valor para una de las propiedades del tipo de
destino. Por ejemplo, si tiene una clase WeatherForecast :

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

El siguiente JSON se deserializa sin errores:

{
"TemperatureCelsius": 25,
"Summary": "Hot"
}

Para que se produzca un error en la deserialización si no hay una propiedad Date en el objeto JSON,
implemente un convertidor personalizado. El siguiente código de convertidor de ejemplo produce una
excepción si no se establece la propiedad Date cuando se completa la deserialización:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class WeatherForecastRequiredPropertyConverter : JsonConverter<WeatherForecast>
{
public override WeatherForecast Read(
ref Utf8JsonReader reader,
Type type,
JsonSerializerOptions options)
{
// Don't pass in options when recursively calling Deserialize.
WeatherForecast forecast = JsonSerializer.Deserialize<WeatherForecast>(ref reader);

// Check for required fields set by values in JSON


if (forecast.Date == default)
{
throw new JsonException("Required property not received in the JSON");
}
return forecast;
}

public override void Write(


Utf8JsonWriter writer,
WeatherForecast forecast, JsonSerializerOptions options)
{
// Don't pass in options when recursively calling Serialize.
JsonSerializer.Serialize(writer, forecast);
}
}
}

Registre este convertidor personalizado agregando el convertidor a la colección


JsonSerializerOptions.Converters.
Este patrón de llamada recursiva al convertidor exige que se registre el convertidor mediante
JsonSerializerOptions, no mediante un atributo. Si registra el convertidor mediante un atributo, el convertidor
personalizado se llama a sí mismo de forma recursiva. El resultado es un bucle infinito que finaliza en una
excepción de desbordamiento de pila.
Al registrar el convertidor mediante el objeto de opciones, evite un bucle infinito; para ello, no pase el objeto de
opciones cuando llame a Serialize o Deserialize de forma recursiva. El objeto de opciones contiene la colección
Converters. Si lo pasa a Serialize o Deserialize , el convertidor personalizado se llama a sí mismo, con lo que
se crea un bucle infinito que produce una excepción de desbordamiento de pila. Si las opciones predeterminadas
no son factibles, cree una nueva instancia de las opciones con la configuración que necesite. Este enfoque será
lento, ya que cada instancia nueva se almacena en caché de forma independiente.
Existe un patrón alternativo que puede usar el registro de JsonConverterAttribute en la clase que se va a
convertir. En este enfoque, el código del convertidor llama a Serialize o Deserialize en una clase que deriva
de la clase que se va a convertir. La clase derivada no tiene ningún elemento JsonConverterAttribute aplicado.
En el siguiente ejemplo de esta alternativa:
WeatherForecastWithRequiredPropertyConverterAttribute es la clase que se va a deserializar y a la que se le ha
aplicado JsonConverterAttribute .
WeatherForecastWithoutRequiredPropertyConverterAttribute es la clase derivada que no tiene el atributo del
convertidor.
El código del convertidor llama a Serialize y en
Deserialize
WeatherForecastWithoutRequiredPropertyConverterAttribute para evitar un bucle infinito. Hay un costo de
rendimiento en este enfoque aplicado a la serialización debido a una creación de instancias de objeto
adicional y la copia de valores de propiedad.
Estos son los tipos WeatherForecast* :

[JsonConverter(typeof(WeatherForecastRequiredPropertyConverterForAttributeRegistration))]
public class WeatherForecastWithRequiredPropertyConverterAttribute
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

public class WeatherForecastWithoutRequiredPropertyConverterAttribute :


WeatherForecastWithRequiredPropertyConverterAttribute
{
}

Y este es el convertidor:
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class WeatherForecastRequiredPropertyConverterForAttributeRegistration :
JsonConverter<WeatherForecastWithRequiredPropertyConverterAttribute>
{
public override WeatherForecastWithRequiredPropertyConverterAttribute Read(
ref Utf8JsonReader reader,
Type type,
JsonSerializerOptions options)
{
// OK to pass in options when recursively calling Deserialize.
WeatherForecastWithRequiredPropertyConverterAttribute forecast =
JsonSerializer.Deserialize<WeatherForecastWithoutRequiredPropertyConverterAttribute>(
ref reader,
options);

// Check for required fields set by values in JSON.


if (forecast.Date == default)
{
throw new JsonException("Required property not received in the JSON");
}

return forecast;
}

public override void Write(


Utf8JsonWriter writer,
WeatherForecastWithRequiredPropertyConverterAttribute forecast,
JsonSerializerOptions options)
{
var weatherForecastWithoutConverterAttributeOnClass =
new WeatherForecastWithoutRequiredPropertyConverterAttribute
{
Date = forecast.Date,
TemperatureCelsius = forecast.TemperatureCelsius,
Summary = forecast.Summary
};

// OK to pass in options when recursively calling Serialize.


JsonSerializer.Serialize(
writer,
weatherForecastWithoutConverterAttributeOnClass,
options);
}
}
}

El convertidor de propiedades necesario requeriría lógica adicional en el caso de necesitar administrar atributos
como [JsonIgnore] u otras opciones, como codificadores personalizados. Además, el código de ejemplo no
controla las propiedades para las que se establece un valor predeterminado en el constructor, y este enfoque no
distingue entre los siguientes escenarios:
Falta una propiedad en el objeto JSON.
Hay una propiedad para un tipo que no acepta valores NULL en el objeto JSON, pero el valor es el
predeterminado para el tipo, como cero para int .
Hay una propiedad para un tipo de valor que acepta valores NULL en el objeto JSON, pero el valor es NULL.
Omitir condicionalmente una propiedad
Newtonsoft.Json ofrece varias formas de omitir condicionalmente una propiedad en la serialización o
deserialización:
DefaultContractResolver le permite seleccionar las propiedades que se van a incluir o excluir, en función de
criterios arbitrarios.
Los valores NullValueHandling y DefaultValueHandling de JsonSerializerSettings le permiten especificar
que se deben omitir todas las propiedades de valores NULL o valores predeterminados.
Los valores NullValueHandling y DefaultValueHandling del atributo [JsonProperty] le permiten especificar
propiedades individuales que se deben omitir cuando se establecen en null o en el valor predeterminado.
System.Text.Json proporciona las siguientes formas de omitir las propiedades durante la serialización:
El atributo [JsonIgnore] de una propiedad hace que la propiedad se omita en el objeto JSON durante la
serialización.
La opción global IgnoreNullValues le permite excluir todas las propiedades de valores NULL.
La opción global IgnoreReadOnlyProperties le permite excluir todas las propiedades de solo lectura.
Estas opciones no le permiten:
Omitir todas las propiedades que tienen el valor predeterminado para el tipo.
Omitir todas las propiedades seleccionadas que tienen el valor predeterminado para el tipo.
Omitir las propiedades seleccionadas si su valor es NULL.
Omitir las propiedades seleccionadas en función de criterios arbitrarios evaluados en tiempo de ejecución.
Para esa funcionalidad, puede escribir un convertidor personalizado. Este es un POCO de ejemplo y un
convertidor personalizado para él que ilustra este enfoque:

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class WeatherForecastRuntimeIgnoreConverter : JsonConverter<WeatherForecast>
{
public override WeatherForecast Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

var wf = new WeatherForecast();

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return wf;
}

if (reader.TokenType == JsonTokenType.PropertyName)
if (reader.TokenType == JsonTokenType.PropertyName)
{
string propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "Date":
DateTimeOffset date = reader.GetDateTimeOffset();
wf.Date = date;
break;
case "TemperatureCelsius":
int temperatureCelsius = reader.GetInt32();
wf.TemperatureCelsius = temperatureCelsius;
break;
case "Summary":
string summary = reader.GetString();
wf.Summary = string.IsNullOrWhiteSpace(summary) ? "N/A" : summary;
break;
}
}
}

throw new JsonException();


}

public override void Write(Utf8JsonWriter writer, WeatherForecast wf, JsonSerializerOptions options)


{
writer.WriteStartObject();

writer.WriteString("Date", wf.Date);
writer.WriteNumber("TemperatureCelsius", wf.TemperatureCelsius);
if (!string.IsNullOrWhiteSpace(wf.Summary) && wf.Summary != "N/A")
{
writer.WriteString("Summary", wf.Summary);
}

writer.WriteEndObject();
}
}
}

El convertidor hace que se omita la propiedad Summary de la serialización si su valor es NULL, una cadena vacía
o "N/A".
Registre este convertidor personalizado usando un atributo en la clase o agregando el convertidor a la colección
Converters.
Este enfoque requiere lógica adicional en los siguientes casos:
El objeto POCO incluye propiedades complejas.
Debe controlar los atributos como [JsonIgnore] , o las opciones como los codificadores personalizados.
Especificación del formato de fecha
Newtonsoft.Json proporciona varias maneras de controlar cómo se serializan y deserializan las propiedades de
los tipos DateTime y DateTimeOffset :
El valor DateTimeZoneHandling se puede usar para serializar todos los valores DateTime como fechas UTC.
El valor DateFormatString y los convertidores de DateTime se pueden usar para personalizar el formato de
las cadenas de fecha.
En System.Text.Json, el único formato que tiene compatibilidad integrada es ISO 8601-1:2019, ya que se ha
adoptado ampliamente, no es ambiguo, y realiza de forma precisa los recorridos de ida y vuelta. Para usar
cualquier otro formato, cree un convertidor personalizado. Para obtener más información, consulte
Compatibilidad con DateTime y DateTimeOffset en System.Text.Json.
Devoluciones de llamada
Newtonsoft.Json le permite ejecutar código personalizado en varios puntos en el proceso de serialización o
deserialización:
OnDeserializing: al empezar a deserializar un objeto
OnDeserialized: al finalizar la deserialización de un objeto
OnSerializing: al empezar a serializar un objeto
OnSerialized: al finalizar la serialización de un objeto
En System.Text.Json, puede simular devoluciones de llamada escribiendo un convertidor personalizado. En el
ejemplo siguiente se muestra un convertidor personalizado para un objeto POCO. El convertidor incluye código
que muestra un mensaje en cada punto que corresponde a una devolución de llamada de Newtonsoft.Json .

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class WeatherForecastCallbacksConverter : JsonConverter<WeatherForecast>
{
public override WeatherForecast Read(
ref Utf8JsonReader reader,
Type type,
JsonSerializerOptions options)
{
// Place "before" code here (OnDeserializing),
// but note that there is no access here to the POCO instance.
Console.WriteLine("OnDeserializing");

// Don't pass in options when recursively calling Deserialize.


WeatherForecast forecast = JsonSerializer.Deserialize<WeatherForecast>(ref reader);

// Place "after" code here (OnDeserialized)


Console.WriteLine("OnDeserialized");

return forecast;
}

public override void Write(


Utf8JsonWriter writer,
WeatherForecast forecast, JsonSerializerOptions options)
{
// Place "before" code here (OnSerializing)
Console.WriteLine("OnSerializing");

// Don't pass in options when recursively calling Serialize.


JsonSerializer.Serialize(writer, forecast);

// Place "after" code here (OnSerialized)


Console.WriteLine("OnSerialized");
}
}
}

Registre este convertidor personalizado agregando el convertidor a la colección Converters.


Si usa un convertidor personalizado que sigue el ejemplo anterior:
El código OnDeserializing no tiene acceso a la nueva instancia POCO. Para manipular la nueva instancia
POCO al inicio de la deserialización, coloque ese código en el constructor POCO.
Evite un bucle infinito; para ello, registre el convertidor en el objeto de opciones y no pase el objeto de
opciones cuando llame a Serialize o Deserialize de forma recursiva.

Para más información sobre los convertidores personalizados que llaman a Serialize o Deserialize de forma
recursiva, consulte la sección Propiedades obligatorias anteriormente en este artículo.
Campos públicos y no públicos
Newtonsoft.Json puede serializar y deserializar los campos, así como las propiedades. System.Text.Json solo
funciona con propiedades públicas. Los convertidores personalizados no proporcionan esta funcionalidad.
Captadores y establecedores de propiedades internos y privados
Newtonsoft.Json puede usar captadores y establecedores de propiedades internos y privados a través del
atributo JsonProperty . System.Text.Json solo admite establecedores públicos. Los convertidores personalizados
no proporcionan esta funcionalidad.
Rellenar objetos existentes
El método JsonConvert.PopulateObject de Newtonsoft.Json deserializa un documento JSON en una instancia
existente de una clase, en lugar de crear una nueva instancia. System.Text.Json siempre crea una nueva instancia
del tipo de destino mediante el constructor sin parámetros público predeterminado. Los convertidores
personalizados se pueden deserializar en una instancia existente.
Reutilización en lugar de reemplazo de propiedades
El valor ObjectCreationHandling de Newtonsoft.Json le permite especificar que los objetos de las propiedades
deben reutilizarse en lugar de reemplazarse durante la deserialización. System.Text.Json siempre reemplaza los
objetos en las propiedades. Los convertidores personalizados no proporcionan esta funcionalidad.
Agregar a colecciones sin establecedores
Durante la deserialización, Newtonsoft.Json agrega objetos a una colección, incluso si la propiedad no tiene
ningún establecedor. System.Text.Json omite las propiedades que no tienen establecedores. Los convertidores
personalizados no proporcionan esta funcionalidad.

Escenarios que JsonSerializer no admite actualmente


En los escenarios siguientes, no hay ninguna solución alternativa que sea práctica o posible. Si confía en estas
características de Newtonsoft.Json , no será posible realizar la migración sin cambios importantes.
Conservación de referencias a objetos y procesado de bucles
De forma predeterminada, Newtonsoft.Json realiza la serialización por valor. Por ejemplo, si un objeto contiene
dos propiedades que contienen una referencia al mismo objeto Person , los valores de las propiedades de dicho
objeto Person se duplican en JSON.
Newtonsoft.Json tiene un valor PreserveReferencesHandling en JsonSerializerSettings que le permite realizar
serializaciones por referencia:
Los metadatos de un identificador se agregan al JSON creado para el primer objeto Person .
El JSON que se crea para el segundo objeto Person contiene una referencia a ese identificador en lugar de
los valores de propiedad.
Newtonsoft.Json también tiene un valor ReferenceLoopHandling que le permite omitir las referencias circulares
en lugar de producir una excepción.
System.Text.Json solo admite la serialización por valor y produce una excepción para las referencias circulares.
Atributos de System.Runtime.Serialization
System.Text.Json no admite atributos del espacio de nombres System.Runtime.Serialization , como
DataMemberAttribute y IgnoreDataMemberAttribute .
Números octales
Newtonsoft.Json trata los números con un cero a la izquierda como números octales. System.Text.Json no
permite ceros a la izquierda porque la especificación RFC 8259 no los permite.
MissingMemberHandling
Newtonsoft.Json se puede configurar para producir excepciones durante la deserialización si el archivo JSON
incluye propiedades que faltan en el tipo de destino. System.Text.Json omite las propiedades adicionales en el
archivo JSON, excepto cuando se usa el atributo [JsonExtensionData]. No hay ninguna solución alternativa para
la característica de miembro que falta.
TraceWriter
Newtonsoft.Json le permite realizar la depuración mediante el uso de un TraceWriter para ver los registros
generados por la serialización o deserialización. System.Text.Json no realiza el registro.

JsonDocument y JsonElement en comparación con JToken (como


JObject, JArray)
System.Text.Json.JsonDocument proporciona la capacidad de analizar y compilar un Document Object Model
(DOM) de solo lectura a partir de cargas JSON existentes. DOM proporciona acceso aleatorio a los datos en
una carga JSON. A los elementos JSON que componen los datos se puede acceder mediante el tipo
JsonElement. El tipo JsonElement proporciona las API para convertir texto JSON en tipos comunes de .NET.
JsonDocument expone una propiedad RootElement.

JsonDocument es IDisposable
JsonDocument compila una vista en memoria de los datos en un búfer agrupado. Por lo tanto, a diferencia de
JObject o JArray de Newtonsoft.Json , el tipo JsonDocument implementa IDisposable y debe usarse dentro de
un bloque Using.
Devuelva un JsonDocument desde la API solo si quiere transferir la propiedad de la duración y derivar la
responsabilidad al autor de la llamada. En la mayoría de los escenarios, eso no es necesario. Si el autor de la
llamada necesita trabajar con todo el documento JSON, devuelva el Clone del RootElement, que es un
JsonElement. Si el autor de la llamada necesita trabajar con un elemento determinado dentro del documento
JSON, devuelva el Clone de dicho JsonElement. Si devuelve el RootElement o un subelemento directamente sin
realizar un Clone , el autor de la llamada no podrá acceder al JsonElement devuelto después de que se elimine
el JsonDocument que lo posee.
Este es un ejemplo en el que se le requiere que realice un Clone :

public JsonElement LookAndLoad(JsonElement source)


{
string json = File.ReadAllText(source.GetProperty("fileName").GetString());

using (JsonDocument doc = JsonDocument.Parse(json))


{
return doc.RootElement.Clone();
}
}

El código anterior espera un JsonElement que contiene una propiedad fileName . Abre el archivo JSON y crea
un JsonDocument . El método supone que el autor de la llamada quiere trabajar con todo el documento, por lo
que devuelve el Clone del RootElement .
Si recibe un JsonElement y está devolviendo un subelemento, no es necesario devolver un Clone del
subelemento. El autor de la llamada es responsable de mantener activo el JsonDocument al que pertenece el
JsonElement pasado. Por ejemplo:

public JsonElement ReturnFileName(JsonElement source)


{
return source.GetProperty("fileName");
}

JSonDocument es de solo lectura


El DOM System.Text.Json no puede agregar, quitar o modificar elementos JSON. Está diseñado de esta manera
para favorecer el rendimiento y para reducir las asignaciones para el análisis de los tamaños comunes de carga
de JSON (es decir, < 1 MB). Si el escenario usa actualmente un DOM modificable, una de las siguientes
soluciones alternativas podría ser factible:
Para compilar un JsonDocument desde cero (es decir, sin pasar una carga de JSON existente al método Parse
), escriba el texto JSON mediante Utf8JsonWriter y analice la salida del mismo para crear un nuevo
JsonDocument .
Para modificar un JsonDocument existente, úselo para escribir texto JSON, realizar cambios mientras escribe y
analizar la salida del mismo para crear un nuevo JsonDocument .
Para combinar documentos JSON existentes, equivalentes a las API de JObject.Merge o JContainer.Merge
desde Newtonsoft.Json , vea este problema de GitHub.
JsonElement es una estructura de unión
JsonDocument expone el RootElement como una propiedad de tipo JsonElement (que es una unión), un tipo de
estructura que abarca cualquier elemento JSON. Newtonsoft.Json utiliza tipos jerárquicos dedicados como
JObject , JArray , JToken , etc. JsonElement es lo que puede buscar y enumerar, y puede usar JsonElement para
materializar los elementos JSON en tipos de .NET.
Cómo buscar subelementos en JsonDocument y JsonElement
Las búsquedas de tokens JSON mediante JObject o JArray desde Newtonsoft.Json suelen ser relativamente
rápidas porque son búsquedas en algunos diccionarios. Por comparación, las búsquedas en JsonElement
requieren una búsqueda secuencial de las propiedades y, por lo tanto, son relativamente lentas (por ejemplo, al
usar TryGetProperty ). System.Text.Json está diseñado para minimizar el tiempo de análisis inicial en lugar del
tiempo de búsqueda. Por lo tanto, use los enfoques siguientes para optimizar el rendimiento al buscar en un
objeto JsonDocument :
Use los enumeradores integrados (EnumerateArray y EnumerateObject) en lugar de crear sus propios bucles
o índices.
No realice una búsqueda secuencial en todo el JsonDocument a través de todas las propiedades mediante
RootElement . En su lugar, busque objetos JSON anidados en función de la estructura conocida de los datos
JSON. Por ejemplo, si busca una propiedad Grade en objetos Student , recorra los objetos Student y
obtenga el valor de Grade para cada uno, en lugar de buscar en todos los objetos JsonElement en busca de
propiedades Grade . Si lo hace, se pasará innecesariamente sobre los mismos datos.
Para obtener un ejemplo de código, vea Uso de JsonDocument para acceder a los datos.

Utf8JsonReader en comparación con JsonTextReader


System.Text.Json.Utf8JsonReader es un lector de solo avance, de baja asignación y de alto rendimiento para texto
JSON con codificación UTF-8 que se lee desde ReadOnlySpan<byte> o ReadOnlySequence<byte>.
Utf8JsonReader es un tipo de bajo nivel que se puede usar para compilar analizadores y deserializadores
personalizados.
En las siguientes secciones se explican los patrones de programación recomendados para el uso de
Utf8JsonReader .

Utf8JsonReader es una estructura de referencia


Dado que el tipo Utf8JsonReader es una estructura de referencia, tiene ciertas limitaciones. Por ejemplo, no se
puede almacenar como un campo en una clase o estructura que no sea una estructura de referencia. Para lograr
un alto rendimiento, este tipo debe ser ref struct porque necesita almacenar en caché la entrada
ReadOnlySpan<byte> que, a su vez, es una estructura de referencia. Además, este tipo es mutable ya que
contiene el estado; por tanto, páselo por referencia en lugar de por valor. Si se pasa por valor, se producirá
una copia de la estructura y los cambios de estado no serán visibles para el autor de la llamada. Esto difiere de
Newtonsoft.Json debido a que el JsonTextReader de Newtonsoft.Json es una clase. Para más información sobre
el uso de las estructuras de referencia, vea Escritura de código C# seguro y eficaz.
Lectura de texto UTF -8
Para lograr el mejor rendimiento posible mientras usa Utf8JsonReader , lea cargas de JSON ya codificadas como
texto UTF-8 en lugar de como cadenas UTF-16. Para obtener un ejemplo de código, vea Filtrado de datos
mediante Utf8JsonReader.
Lectura con Stream o PipeReader
Utf8JsonReader admite la lectura desde ReadOnlySpan<byte> o ReadOnlySequence<byte> con codificación
UTF-8 (que es el resultado de la lectura desde PipeReader).
Para la lectura sincrónica, puede leer la carga de JSON hasta el final de la secuencia en una matriz de bytes y
pasarla al lector. Para leer de una cadena (que tiene codificación UTF-16), llame a UTF8.GetBytes para
transcodificar primero la cadena en una matriz de bytes con codificación UTF-8. Después, páselo a
Utf8JsonReader .

Dado que Utf8JsonReader considera que la entrada es texto JSON, se considera que una marca de orden de
bytes (BOM) de UTF-8 no es válida. El autor de la llamada debe filtrarla antes de pasar los datos al lector.
Para obtener códigos de ejemplo, vea Uso de Utf8JsonReader.
Lectura con ReadOnlySequence de varios segmentos
Si la entrada JSON es ReadOnlySpan<byte>, se puede acceder a cada elemento JSON desde la propiedad
ValueSpan en el lector a medida que avance por el bucle de lectura. Pero si la entrada es
ReadOnlySequence<byte> (que es el resultado de la lectura de PipeReader), algunos elementos JSON podrían
ocupar varios segmentos del objeto ReadOnlySequence<byte> . No se puede acceder a estos elementos desde
ValueSpan en un bloque de memoria contiguo. En su lugar, siempre que tenga un ReadOnlySequence<byte> de
varios segmentos como entrada, sondee la propiedad HasValueSequence en el lector para averiguar cómo
acceder al elemento JSON actual. Este es un patrón recomendado:

while (reader.Read())
{
switch (reader.TokenType)
{
// ...
ReadOnlySpan<byte> jsonElement = reader.HasValueSequence ?
reader.ValueSequence.ToArray() :
reader.ValueSpan;
// ...
}
}

Uso de ValueTextEquals para las búsquedas de nombres de propiedad


No use ValueSpan para realizar comparaciones byte a byte mediante una llamada a SequenceEqual para las
búsquedas de nombres de propiedad. En su lugar, llame a ValueTextEquals, ya que ese método anula el escape
de caracteres que se van a escapar en JSON. Este es un ejemplo en el que se muestra cómo buscar una
propiedad denominada "name":

private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name");

while (reader.Read())
{
JsonTokenType tokenType = reader.TokenType;

switch (tokenType)
{
case JsonTokenType.StartObject:
total++;
break;
case JsonTokenType.PropertyName:
if (reader.ValueTextEquals(s_nameUtf8))
{
count++;
}
break;
}

Lectura de valores NULL en tipos de valor que aceptan valores NULL


Newtonsoft.Json proporciona las API que devuelven Nullable<T>, como ReadAsBoolean , que controla un
TokenType Null por usted devolviendo un valor bool? . Las API integradas de System.Text.Json solo
devuelven tipos de valor que no aceptan valores NULL. Por ejemplo, Utf8JsonReader.GetBoolean devuelve bool .
Si encuentra Null en el elemento JSON, inicia una excepción. En los siguientes ejemplos se muestran dos
formas de controlar valores NULL: una devolviendo un tipo de valor que acepta valores NULL y otra
devolviendo el valor predeterminado:

public bool? ReadAsNullableBoolean()


{
_reader.Read();
if (_reader.TokenType == JsonTokenType.Null)
{
return null;
}
if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False)
{
throw new JsonException();
}
return _reader.GetBoolean();
}

public bool ReadAsBoolean(bool defaultValue)


{
_reader.Read();
if (_reader.TokenType == JsonTokenType.Null)
{
return defaultValue;
}
if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False)
{
throw new JsonException();
}
return _reader.GetBoolean();
}
Compatibilidad con múltiples versiones
Si necesita seguir usando Newtonsoft.Json para determinadas plataformas de destino, puede tener varias
versiones y dos implementaciones. Pero esto no es algo trivial y requeriría algunos #ifdefs y la duplicación de
orígenes. Una manera de compartir todo el código posible es crear un contenedor de ref struct alrededor de
Utf8JsonReader y Newtonsoft.Json JsonTextReader . Dicho contenedor unificaría el área expuesta pública
mientras aísla las diferencias de comportamiento. Esto le permite aislar los cambios principalmente en la
construcción del tipo, junto con pasar el nuevo tipo por referencia. Este es el patrón que sigue la biblioteca de
Microsoft.Extensions.DependencyModel:
UnifiedJsonReader.JsonTextReader.cs
UnifiedJsonReader.Utf8JsonReader.cs

Utf8JsonWriter en comparación con JsonTextWriter


System.Text.Json.Utf8JsonWriter ofrece una forma de escribir texto JSON con codificación UTF-8 de alto
rendimiento a partir de tipos de .NET comunes como String , Int32 y DateTime . El escritor es un tipo de bajo
nivel que se puede usar para compilar serializadores personalizados.
En las siguientes secciones se explican los patrones de programación recomendados para el uso de
Utf8JsonWriter .

Escritura con texto UTF -8


Para lograr el mejor rendimiento posible mientras usa Utf8JsonWriter , escriba cargas de JSON ya codificadas
como texto UTF-8 en lugar de como cadenas UTF-16. Utilice JsonEncodedText para almacenar en caché y
codificar previamente los nombres y valores de las propiedades de cadena conocidas como estáticos y pasarlos
al escritor, en lugar de usar literales de cadena UTF-16. Esto es más rápido que el almacenamiento en caché y el
uso de matrices de bytes UTF-8.
Este enfoque también funciona si necesita realizar un escape personalizado. System.Text.Json no permite
deshabilitar el escape mientras se escribe una cadena, pero podría pasar su propio JavaScriptEncoder
personalizado como una opción al escritor, o crear su propio JsonEncodedText que use su JavascriptEncoder
para realizar el escape y, después, escribir el JsonEncodedText en lugar de la cadena. Para más información, vea
Personalización de la codificación de caracteres.
Escritura de valores sin formato
El método WriteRawValue de Newtonsoft.Json escribe el JSON sin formato donde se espera un valor.
System.Text.Json no tiene equivalente directo, pero esta es una solución alternativa que garantiza que solo se
escribe un JSON válido:

using JsonDocument doc = JsonDocument.Parse(string);


doc.WriteTo(writer);

Personalización del escape de caracteres


El valor StringEscapeHandling de JsonTextWriter ofrece opciones para escapar todos los caracteres que no sean
ASCII o caracteres HTML. De forma predeterminada, Utf8JsonWriter convierte todos los caracteres que no son
ASCII y HTML. Este escape se hace por motivos de seguridad de defensa en profundidad. Para especificar una
directiva de escape diferente, cree un JavaScriptEncoder y configure JsonWriterOptions.Encoder. Para más
información, vea Personalización de la codificación de caracteres.
Personalización del formato JSON
JsonTextWriter incluye la configuración siguiente, para la cual Utf8JsonWriter no tiene ningún equivalente:
Indentation: especifica el número de caracteres a los que se va a aplicar sangría. Utf8JsonWriter siempre
realiza una sangría de dos caracteres.
IndentChar: especifica el carácter que se va a utilizar para la sangría. Utf8JsonWriter siempre usa el espacio
en blanco.
QuoteChar: especifica el carácter que se va a usar para rodear los valores de cadena. Utf8JsonWriter siempre
usa comillas dobles.
QuoteName: especifica si los nombres de propiedad deben encerrarse entre comillas. Utf8JsonWriter
siempre los coloca entrecomillados.
No hay ninguna solución alternativa que permita personalizar el JSON generado por Utf8JsonWriter de estas
maneras.
Escritura de valores NULL
Para escribir valores NULL mediante Utf8JsonWriter , llame a:
WriteNull para escribir un par clave-valor con NULL como valor.
WriteNullValue para escribir NULL como un elemento de una matriz JSON.
En el caso de una propiedad de cadena, si la cadena es NULL, WriteString y WriteStringValue son equivalentes a
WriteNull y WriteNullValue .

Escritura de valores TimeSpan, URI o char


JsonTextWriter proporciona métodos WriteValue para los valores TimeSpan, URIy char. Utf8JsonWriter no
tiene métodos equivalentes. En su lugar, dé formato a estos valores como cadenas (por ejemplo, llamando a
ToString() ) y llame a WriteStringValue.

Compatibilidad con múltiples versiones


Si necesita seguir usando Newtonsoft.Json para determinadas plataformas de destino, puede tener varias
versiones y dos implementaciones. Pero esto no es algo trivial y requeriría algunos #ifdefs y la duplicación de
orígenes. Una manera de compartir todo el código posible es crear un contenedor alrededor de Utf8JsonWriter
y Newtonsoft JsonTextWriter . Dicho contenedor unificaría el área expuesta pública mientras aísla las diferencias
de comportamiento. Esto le permite aislar los cambios principalmente en la construcción del tipo. La biblioteca
de Microsoft.Extensions.DependencyModel sigue:
UnifiedJsonWriter.JsonTextWriter.cs
UnifiedJsonWriter.Utf8JsonWriter.cs

Recursos adicionales
Información general de System.Text.Json
Modo de uso de System.Text.Json
Procedimientos para escribir convertidores personalizados
Compatibilidad con DateTime y DateTimeOffset en System.Text.Json
Referencia de API de System.Text.Json
Referencia de API de System.Text.Json.Serialization
Serialización binaria
16/09/2020 • 15 minutes to read • Edit Online

La serialización se puede definir como el proceso de almacenar el estado de un objeto a los medios de
almacenamiento. Durante este proceso, los campos públicos y privados del objeto y el nombre de la clase,
incluso el ensamblado que contiene la clase, se convierten en una secuencia de bytes, que se escribe a
continuación en un flujo de datos. Cuando se deserializa el objeto como consecuencia, se crea un clon exacto del
objeto original.
Al implementar un mecanismo de la serialización en un entorno orientado a objetos, tiene que realizar varios
intercambios entre la facilidad de uso y la flexibilidad. El proceso se puede automatizar en gran medida, con tal
de que sea proporcionado el control suficiente sobre el proceso. Por ejemplo, las situaciones se pueden presentar
donde la serialización binaria simple no es suficiente, o podría haber una razón concreta para decidir qué campos
en una clase necesitan ser serializados. Las secciones siguientes examinan el sólido mecanismo de serialización
proporcionado con .NET y resaltan varias características importantes que permiten personalizar el proceso para
satisfacer las necesidades.

NOTE
El estado de un objeto UTF-8 o UTF-7 codificado no se conserva si el objeto se serializa y se deserializa utilizando distintas
versiones de .NET Framework.

WARNING
La serialización binaria puede ser peligrosa. Para obtener más información, consulte la Guía de seguridad BinaryFormatter.

La serialización binaria permite modificar miembros privados dentro de un objeto y, por tanto, cambiar su
estado. Por este motivo, se recomiendan otros marcos de serialización, como System.Text.Json, que operan en la
superficie de la API pública.

.NET Core
.NET Core admite la serialización binaria para un subconjunto de tipos. Puede ver la lista de tipos compatibles en
la sección Tipos serializables siguiente. Se garantiza que los tipos indicados son serializables entre
.NET Framework 4.5.1 y versiones posteriores, y entre .NET Core 2.0 y versiones posteriores. Otras
implementaciones de .NET, como Mono, no son oficialmente compatibles, pero también deberían funcionar.
Tipos serializables
T IP O N OTA S

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException A partir de .NET Core 2.0.4.

Microsoft.CSharp.RuntimeBinder.RuntimeBinderInternalComp A partir de .NET Core 2.0.4.


ilerException

System.AccessViolationException A partir de .NET Core 2.0.4.

System.AggregateException A partir de .NET Core 2.0.4.


T IP O N OTA S

System.AppDomainUnloadedException A partir de .NET Core 2.0.4.

System.ApplicationException A partir de .NET Core 2.0.4.

System.ArgumentException A partir de .NET Core 2.0.4.

System.ArgumentNullException A partir de .NET Core 2.0.4.

System.ArgumentOutOfRangeException A partir de .NET Core 2.0.4.

System.ArithmeticException A partir de .NET Core 2.0.4.

System.Array

System.ArraySegment<T>

System.ArrayTypeMismatchException A partir de .NET Core 2.0.4.

System.Attribute

System.BadImageFormatException A partir de .NET Core 2.0.4.

System.Boolean

System.Byte

System.CannotUnloadAppDomainException A partir de .NET Core 2.0.4.

System.Char

System.Collections.ArrayList

System.Collections.BitArray

System.Collections.Comparer

System.Collections.DictionaryEntry

System.Collections.Generic.Comparer<T>

System.Collections.Generic.Dictionary<TKey,TValue>

System.Collections.Generic.EqualityComparer<T>

System.Collections.Generic.HashSet<T>

System.Collections.Generic.KeyNotFoundException A partir de .NET Core 2.0.4.

System.Collections.Generic.KeyValuePair<TKey,TValue>
T IP O N OTA S

System.Collections.Generic.LinkedList<T>

System.Collections.Generic.List<T>

System.Collections.Generic.Queue<T>

System.Collections.Generic.SortedDictionary<TKey,TValue>

System.Collections.Generic.SortedList<TKey,TValue>

System.Collections.Generic.SortedSet<T>

System.Collections.Generic.Stack<T>

System.Collections.Hashtable

System.Collections.ObjectModel.Collection<T>

System.Collections.ObjectModel.KeyedCollection<TKey,TItem
>

System.Collections.ObjectModel.ObservableCollection<T>

System.Collections.ObjectModel.ReadOnlyCollection<T>

System.Collections.ObjectModel.ReadOnlyDictionary<TKey,T
Value>

System.Collections.ObjectModel.ReadOnlyObservableCollecti
on<T>

System.Collections.Queue

System.Collections.SortedList

System.Collections.Specialized.HybridDictionary

System.Collections.Specialized.ListDictionary

System.Collections.Specialized.OrderedDictionary

System.Collections.Specialized.StringCollection

System.Collections.Specialized.StringDictionary

System.Collections.Stack

A partir de .NET Core 2.0.4.


System.Collections.Generic.NonRandomizedStringEqualityComparer

System.ComponentModel.BindingList<T>
T IP O N OTA S

System.ComponentModel.DataAnnotations.ValidationExcepti A partir de .NET Core 2.0.4.


on

System.ComponentModel.Design.CheckoutException A partir de .NET Core 2.0.4.

System.ComponentModel.InvalidAsynchronousStateExceptio A partir de .NET Core 2.0.4.


n

System.ComponentModel.InvalidEnumArgumentException A partir de .NET Core 2.0.4.

System.ComponentModel.LicenseException A partir de .NET Core 2.0.4.


No se admite la serialización desde .NET Framework a
.NET Core.

System.ComponentModel.WarningException A partir de .NET Core 2.0.4.

System.ComponentModel.Win32Exception A partir de .NET Core 2.0.4.

System.Configuration.ConfigurationErrorsException A partir de .NET Core 2.0.4.

System.Configuration.ConfigurationException A partir de .NET Core 2.0.4.

System.Configuration.Provider.ProviderException A partir de .NET Core 2.0.4.

System.Configuration.SettingsPropertyIsReadOnlyException A partir de .NET Core 2.0.4.

System.Configuration.SettingsPropertyNotFoundException A partir de .NET Core 2.0.4.

System.Configuration.SettingsPropertyWrongTypeException A partir de .NET Core 2.0.4.

System.ContextMarshalException A partir de .NET Core 2.0.4.

System.DBNull A partir de .NET Core 2.0.2 y versiones posteriores.

System.Data.Common.DbException A partir de .NET Core 2.0.4.

System.Data.ConstraintException A partir de .NET Core 2.0.4.

System.Data.DBConcurrencyException A partir de .NET Core 2.0.4.

System.Data.DataException A partir de .NET Core 2.0.4.

System.Data.DataSet

System.Data.DataTable Si establece RemotingFormat en


SerializationFormat.Binary , solo se puede intercambiar
con .NET Core 2.1 y versiones posteriores.

System.Data.DeletedRowInaccessibleException A partir de .NET Core 2.0.4.

System.Data.DuplicateNameException A partir de .NET Core 2.0.4.


T IP O N OTA S

System.Data.EvaluateException A partir de .NET Core 2.0.4.

System.Data.InRowChangingEventException A partir de .NET Core 2.0.4.

System.Data.InvalidConstraintException A partir de .NET Core 2.0.4.

System.Data.InvalidExpressionException A partir de .NET Core 2.0.4.

System.Data.MissingPrimaryKeyException A partir de .NET Core 2.0.4.

System.Data.NoNullAllowedException A partir de .NET Core 2.0.4.

System.Data.Odbc.OdbcException A partir de .NET Core 2.0.4.

System.Data.OperationAbortedException A partir de .NET Core 2.0.4.

System.Data.PropertyCollection

System.Data.ReadOnlyException A partir de .NET Core 2.0.4.

System.Data.RowNotInTableException A partir de .NET Core 2.0.4.

System.Data.SqlClient.SqlException A partir de .NET Core 2.0.4.


No se admite la serialización desde .NET Framework a
.NET Core

System.Data.SqlTypes.SqlAlreadyFilledException A partir de .NET Core 2.0.4.

System.Data.SqlTypes.SqlBoolean

System.Data.SqlTypes.SqlByte

System.Data.SqlTypes.SqlDateTime

System.Data.SqlTypes.SqlDouble

System.Data.SqlTypes.SqlGuid

System.Data.SqlTypes.SqlInt16

System.Data.SqlTypes.SqlInt32

System.Data.SqlTypes.SqlInt64

System.Data.SqlTypes.SqlNotFilledException A partir de .NET Core 2.0.4.

System.Data.SqlTypes.SqlNullValueException A partir de .NET Core 2.0.4.

System.Data.SqlTypes.SqlString
T IP O N OTA S

System.Data.SqlTypes.SqlTruncateException A partir de .NET Core 2.0.4.

System.Data.SqlTypes.SqlTypeException A partir de .NET Core 2.0.4.

System.Data.StrongTypingException A partir de .NET Core 2.0.4.

System.Data.SyntaxErrorException A partir de .NET Core 2.0.4.

System.Data.VersionNotFoundException A partir de .NET Core 2.0.4.

System.DataMisalignedException A partir de .NET Core 2.0.4.

System.DateTime

System.DateTimeOffset

System.Decimal

System.Diagnostics.Contracts.ContractException A partir de .NET Core 2.0.4.

System.Diagnostics.Tracing.EventSourceException A partir de .NET Core 2.0.4.

System.IO.DirectoryNotFoundException A partir de .NET Core 2.0.4.

System.DirectoryServices.AccountManagement.MultipleMatc A partir de .NET Core 2.0.4.


hesException

System.DirectoryServices.AccountManagement.NoMatchingP A partir de .NET Core 2.0.4.


rincipalException

System.DirectoryServices.AccountManagement.PasswordExce A partir de .NET Core 2.0.4.


ption

System.DirectoryServices.AccountManagement.PrincipalExcep A partir de .NET Core 2.0.4.


tion

System.DirectoryServices.AccountManagement.PrincipalExists A partir de .NET Core 2.0.4.


Exception

System.DirectoryServices.AccountManagement.PrincipalOper A partir de .NET Core 2.0.4.


ationException

System.DirectoryServices.AccountManagement.PrincipalServe A partir de .NET Core 2.0.4.


rDownException

System.DirectoryServices.ActiveDirectory.ActiveDirectoryObje A partir de .NET Core 2.0.4.


ctExistsException

System.DirectoryServices.ActiveDirectory.ActiveDirectoryObje A partir de .NET Core 2.0.4.


ctNotFoundException
T IP O N OTA S

System.DirectoryServices.ActiveDirectory.ActiveDirectoryOper A partir de .NET Core 2.0.4.


ationException

System.DirectoryServices.ActiveDirectory.ActiveDirectoryServ A partir de .NET Core 2.0.4.


erDownException

System.DirectoryServices.ActiveDirectory.ForestTrustCollision A partir de .NET Core 2.0.4.


Exception

System.DirectoryServices.ActiveDirectory.SyncFromAllServers A partir de .NET Core 2.0.4.


OperationException

System.DirectoryServices.DirectoryServicesCOMException A partir de .NET Core 2.0.4.

System.DirectoryServices.Protocols.BerConversionException A partir de .NET Core 2.0.4.

System.DirectoryServices.Protocols.DirectoryException A partir de .NET Core 2.0.4.

System.DirectoryServices.Protocols.DirectoryOperationExcepti A partir de .NET Core 2.0.4.


on

System.DirectoryServices.Protocols.LdapException A partir de .NET Core 2.0.4.

System.DirectoryServices.Protocols.TlsOperationException A partir de .NET Core 2.0.4.

System.DivideByZeroException A partir de .NET Core 2.0.4.

System.DllNotFoundException A partir de .NET Core 2.0.4.

System.Double

System.Drawing.Color

System.Drawing.Point

System.Drawing.PointF

System.Drawing.Rectangle

System.Drawing.RectangleF

System.Drawing.Size

System.Drawing.SizeF

System.DuplicateWaitObjectException A partir de .NET Core 2.0.4.

System.EntryPointNotFoundException A partir de .NET Core 2.0.4.

System.Enum
T IP O N OTA S

System.EventArgs A partir de .NET Core 2.0.6.

System.Exception

System.ExecutionEngineException A partir de .NET Core 2.0.4.

System.FieldAccessException A partir de .NET Core 2.0.4.

System.FormatException A partir de .NET Core 2.0.4.

System.Globalization.CompareInfo

System.Globalization.CultureNotFoundException A partir de .NET Core 2.0.4.

System.Globalization.SortVersion

System.Guid

System.IO.Compression.ZLibException A partir de .NET Core 2.0.4.

System.IO.DriveNotFoundException A partir de .NET Core 2.0.4.

System.IO.EndOfStreamException A partir de .NET Core 2.0.4.

System.IO.FileFormatException A partir de .NET Core 2.0.4.

System.IO.FileLoadException A partir de .NET Core 2.0.4.

System.IO.FileNotFoundException A partir de .NET Core 2.0.4.

System.IO.IOException A partir de .NET Core 2.0.4.

System.IO.InternalBufferOverflowException A partir de .NET Core 2.0.4.

System.IO.InvalidDataException A partir de .NET Core 2.0.4.

System.IO.IsolatedStorage.IsolatedStorageException A partir de .NET Core 2.0.4.

System.IO.PathTooLongException A partir de .NET Core 2.0.4.

System.IndexOutOfRangeException A partir de .NET Core 2.0.4.

System.InsufficientExecutionStackException A partir de .NET Core 2.0.4.

System.InsufficientMemoryException A partir de .NET Core 2.0.4.

System.Int16

System.Int32
T IP O N OTA S

System.Int64

System.IntPtr

System.InvalidCastException A partir de .NET Core 2.0.4.

System.InvalidOperationException A partir de .NET Core 2.0.4.

System.InvalidProgramException A partir de .NET Core 2.0.4.

System.InvalidTimeZoneException A partir de .NET Core 2.0.4.

System.MemberAccessException A partir de .NET Core 2.0.4.

System.MethodAccessException A partir de .NET Core 2.0.4.

System.MissingFieldException A partir de .NET Core 2.0.4.

System.MissingMemberException A partir de .NET Core 2.0.4.

System.MissingMethodException A partir de .NET Core 2.0.4.

System.MulticastNotSupportedException A partir de .NET Core 2.0.4.

System.Net.Cookie

System.Net.CookieCollection

System.Net.CookieContainer

System.Net.CookieException A partir de .NET Core 2.0.4.

System.Net.HttpListenerException A partir de .NET Core 2.0.4.

System.Net.Mail.SmtpException A partir de .NET Core 2.0.4.

System.Net.Mail.SmtpFailedRecipientException A partir de .NET Core 2.0.4.

System.Net.Mail.SmtpFailedRecipientsException A partir de .NET Core 2.0.4.

System.Net.NetworkInformation.NetworkInformationExceptio A partir de .NET Core 2.0.4.


n

System.Net.NetworkInformation.PingException A partir de .NET Core 2.0.4.

System.Net.ProtocolViolationException A partir de .NET Core 2.0.4.

System.Net.Sockets.SocketException A partir de .NET Core 2.0.4.

System.Net.WebException A partir de .NET Core 2.0.4.


T IP O N OTA S

System.Net.WebSockets.WebSocketException A partir de .NET Core 2.0.4.

System.NotFiniteNumberException A partir de .NET Core 2.0.4.

System.NotImplementedException A partir de .NET Core 2.0.4.

System.NotSupportedException A partir de .NET Core 2.0.4.

System.NullReferenceException A partir de .NET Core 2.0.4.

System.Nullable<T>

System.Numerics.BigInteger

System.Numerics.Complex

System.Object

System.ObjectDisposedException A partir de .NET Core 2.0.4.

System.OperationCanceledException A partir de .NET Core 2.0.4.

System.OutOfMemoryException A partir de .NET Core 2.0.4.

System.OverflowException A partir de .NET Core 2.0.4.

System.PlatformNotSupportedException A partir de .NET Core 2.0.4.

System.RankException A partir de .NET Core 2.0.4.

System.Reflection.AmbiguousMatchException A partir de .NET Core 2.0.4.

System.Reflection.CustomAttributeFormatException A partir de .NET Core 2.0.4.

System.Reflection.InvalidFilterCriteriaException A partir de .NET Core 2.0.4.

System.Reflection.ReflectionTypeLoadException A partir de .NET Core 2.0.4.


No se admite la serialización desde .NET Framework a
.NET Core.

System.Reflection.TargetException A partir de .NET Core 2.0.4.

System.Reflection.TargetInvocationException A partir de .NET Core 2.0.4.

System.Reflection.TargetParameterCountException A partir de .NET Core 2.0.4.

System.Resources.MissingManifestResourceException A partir de .NET Core 2.0.4.

System.Resources.MissingSatelliteAssemblyException A partir de .NET Core 2.0.4.


T IP O N OTA S

System.Runtime.CompilerServices.RuntimeWrappedException A partir de .NET Core 2.0.4.

System.Runtime.InteropServices.COMException A partir de .NET Core 2.0.4.

System.Runtime.InteropServices.ExternalException A partir de .NET Core 2.0.4.

System.Runtime.InteropServices.InvalidComObjectException A partir de .NET Core 2.0.4.

System.Runtime.InteropServices.InvalidOleVariantTypeExcepti A partir de .NET Core 2.0.4.


on

System.Runtime.InteropServices.MarshalDirectiveException A partir de .NET Core 2.0.4.

System.Runtime.InteropServices.SEHException A partir de .NET Core 2.0.4.

System.Runtime.InteropServices.SafeArrayRankMismatchExce A partir de .NET Core 2.0.4.


ption

System.Runtime.InteropServices.SafeArrayTypeMismatchExce A partir de .NET Core 2.0.4.


ption

System.Runtime.Serialization.InvalidDataContractException A partir de .NET Core 2.0.4.

System.Runtime.Serialization.SerializationException A partir de .NET Core 2.0.4.

System.SByte

System.Security.AccessControl.PrivilegeNotHeldException A partir de .NET Core 2.0.4.

System.Security.Authentication.AuthenticationException A partir de .NET Core 2.0.4.

System.Security.Authentication.InvalidCredentialException A partir de .NET Core 2.0.4.

System.Security.Cryptography.CryptographicException A partir de .NET Core 2.0.4.

System.Security.Cryptography.CryptographicUnexpectedOpe A partir de .NET Core 2.0.4.


rationException

A partir de .NET Core 2.0.4.


System.Security.Cryptography.Xml.CryptoSignedXmlRecursionException

System.Security.HostProtectionException A partir de .NET Core 2.0.4.

System.Security.Policy.PolicyException A partir de .NET Core 2.0.4.

System.Security.Principal.IdentityNotMappedException A partir de .NET Core 2.0.4.

System.Security.SecurityException A partir de .NET Core 2.0.4.


Datos de serialización limitados.

System.Security.VerificationException A partir de .NET Core 2.0.4.


T IP O N OTA S

System.Security.XmlSyntaxException A partir de .NET Core 2.0.4.

System.ServiceProcess.TimeoutException A partir de .NET Core 2.0.4.

System.Single

System.StackOverflowException A partir de .NET Core 2.0.4.

System.String

System.StringComparer

System.SystemException A partir de .NET Core 2.0.4.

System.Text.DecoderFallbackException A partir de .NET Core 2.0.4.

System.Text.EncoderFallbackException A partir de .NET Core 2.0.4.

System.Text.RegularExpressions.RegexMatchTimeoutException A partir de .NET Core 2.0.4.

System.Text.StringBuilder

System.Threading.AbandonedMutexException A partir de .NET Core 2.0.4.

System.Threading.BarrierPostPhaseException A partir de .NET Core 2.0.4.

System.Threading.LockRecursionException A partir de .NET Core 2.0.4.

System.Threading.SemaphoreFullException A partir de .NET Core 2.0.4.

System.Threading.SynchronizationLockException A partir de .NET Core 2.0.4.

System.Threading.Tasks.TaskCanceledException A partir de .NET Core 2.0.4.

System.Threading.Tasks.TaskSchedulerException A partir de .NET Core 2.0.4.

System.Threading.ThreadAbortException A partir de .NET Core 2.0.4.

System.Threading.ThreadInterruptedException A partir de .NET Core 2.0.4.

System.Threading.ThreadStartException A partir de .NET Core 2.0.4.

System.Threading.ThreadStateException A partir de .NET Core 2.0.4.

System.Threading.WaitHandleCannotBeOpenedException A partir de .NET Core 2.0.4.

System.TimeSpan

System.TimeZoneInfo.AdjustmentRule
T IP O N OTA S

System.TimeZoneInfo

System.TimeZoneNotFoundException A partir de .NET Core 2.0.4.

System.TimeoutException A partir de .NET Core 2.0.4.

System.Transactions.TransactionAbortedException A partir de .NET Core 2.0.4.

System.Transactions.TransactionException A partir de .NET Core 2.0.4.

System.Transactions.TransactionInDoubtException A partir de .NET Core 2.0.4.

System.Transactions.TransactionManagerCommunicationExce A partir de .NET Core 2.0.4.


ption

System.Transactions.TransactionPromotionException A partir de .NET Core 2.0.4.

System.Tuple

System.TypeAccessException A partir de .NET Core 2.0.4.

System.TypeInitializationException A partir de .NET Core 2.0.4.

System.TypeLoadException A partir de .NET Core 2.0.4.

System.TypeUnloadedException A partir de .NET Core 2.0.4.

System.UInt16

System.UInt32

System.UInt64

System.UIntPtr

System.UnauthorizedAccessException A partir de .NET Core 2.0.4.

System.Uri

System.UriFormatException A partir de .NET Core 2.0.4.

System.ValueTuple No es serializable en .NET Framework 4.7 y versiones


anteriores.

System.ValueType

System.Version

System.WeakReference<T>
T IP O N OTA S

System.WeakReference

System.Xml.Schema.XmlSchemaException A partir de .NET Core 2.0.4.

System.Xml.Schema.XmlSchemaInferenceException A partir de .NET Core 2.0.4.

System.Xml.Schema.XmlSchemaValidationException A partir de .NET Core 2.0.4.

System.Xml.XPath.XPathException A partir de .NET Core 2.0.4.

System.Xml.XmlException A partir de .NET Core 2.0.4.

System.Xml.Xsl.XsltCompileException A partir de .NET Core 2.0.4.

System.Xml.Xsl.XsltException A partir de .NET Core 2.0.4.

Vea también
System.Runtime.Serialization
Contiene clases que se pueden usar para serializar y deserializar objetos.
Serialización SOAP y XML
Describe el mecanismo de la serialización XML que está incluido con Common Language Runtime.
Seguridad y serialización
Describe las instrucciones de la codificación seguras que hay que seguir al escribir el código que realiza la
serialización.
Comunicación remota de .NET
Describe los diversos métodos a partir de .NET Framework para las comunicaciones remotas.
Servicios web XML creados con ASP.NET y clientes de servicio web XML
Artículos en los que se describe y explica cómo programar servicios web XML creados con ASP.NET.
Guía de seguridad de BinaryFormatter
16/09/2020 • 11 minutes to read • Edit Online

Este artículo se aplica a la implementaciones de .NET siguientes:


.NET Framework (todas las versiones)
.NET Core 2.1 a 3.1
.NET 5.0 y versiones posteriores

Fondo
WARNING
El tipo BinaryFormatter es peligroso y no se recomienda para el procesamiento de datos. Las aplicaciones deben dejar de
usar BinaryFormatter lo antes posible, aunque crean que los datos que procesan son de confianza. BinaryFormatter
no es seguro y no se puede convertir en seguro.

Este artículo también se aplica a los tipos siguientes:


SoapFormatter
NetDataContractSerializer
LosFormatter
ObjectStateFormatter
Las vulnerabilidades de deserialización son una categoría de amenazas donde las cargas de solicitud se procesan
sin seguridad. Un atacante que aprovecha correctamente estas vulnerabilidades en una aplicación puede
provocar ataques por denegación de servicio (DoS), la divulgación de información o la ejecución remota de
código dentro de la aplicación de destino. Esta categoría de riesgo se engloba dentro de OWASP Top 10. Los
destinos incluyen aplicaciones escritas en diversos lenguajes, incluidos C/C++, Java y C#.
En .NET, el mayor destino de riesgo son las aplicaciones que usan el tipo BinaryFormatter para deserializar datos.
BinaryFormatter se utiliza ampliamente en todo el ecosistema de .NET debido a su eficacia y facilidad de uso.
Pero esta misma eficacia ofrece a los atacantes la posibilidad de influir en el flujo de control dentro de la
aplicación de destino. Los ataques correctos pueden dar lugar a que el atacante pueda ejecutar código en el
contexto del proceso de destino.
Como analogía más simple, imagine que la llamada a BinaryFormatter.Deserialize sobre una carga es
equivalente a interpretar esa carga como un archivo ejecutable independiente e iniciarlo.

Vulnerabilidades de seguridad de BinaryFormatter


WARNING
El método BinaryFormatter.Deserialize nunca es seguro cuando se usa con una entrada que no es de confianza. Se
recomienda encarecidamente que los consumidores consideren el uso de una de las alternativas que se describen más
adelante en este artículo.

BinaryFormatter se implementó antes de que las vulnerabilidades de deserialización fueran una categoría de
amenaza bien entendida. Como resultado, el código no sigue los procedimientos recomendados modernos. El
método Deserialize se puede usar como un vector para que los atacantes realicen ataques DoS contra
aplicaciones de consumo. Es posible que estos ataques hagan que la aplicación deje de responder o que se
produzca una terminación inesperada del proceso. Esta categoría de ataque no se puede mitigar con
SerializationBinder o con cualquier otro modificador de configuración BinaryFormatter . .NET considera que
este comportamiento es por definición y no emitirá una actualización de código para modificarlo.
BinaryFormatter.Deserialize puede ser vulnerable a otras categorías de ataque, como la divulgación de
información o la ejecución remota de código. El uso de características como un elemento SerializationBinder
personalizado puede ser insuficiente para mitigar estos riesgos de forma correcta. Existe la posibilidad de que se
detecte una vulnerabilidad nueva para la que .NET no pueda publicar de forma práctica una actualización de
seguridad. Los consumidores deben evaluar sus escenarios individuales y considerar su posible exposición a
estos riesgos.
Se recomienda que los consumidores de BinaryFormatter realicen valoraciones de riesgo individuales en sus
aplicaciones. Es responsabilidad exclusiva del consumidor determinar si se usa BinaryFormatter . Los
consumidores deben evaluar el riesgo de los requisitos de seguridad, técnicos, de reputación, legales y
normativos del uso de BinaryFormatter .

Alternativas preferidas
.NET ofrece varios serializadores integrados que pueden administrar de forma segura los datos que no son de
confianza:
XmlSerializer y DataContractSerializer para serializar gráficos de objetos en y desde XML. No se debe
confundir DataContractSerializer con NetDataContractSerializer.
BinaryReader y BinaryWriter para XML y JSON.
Las API de System.Text.Json para serializar gráficos de objetos en JSON.

Alternativas peligrosas
Evite los siguientes serializadores:
SoapFormatter
LosFormatter
NetDataContractSerializer
ObjectStateFormatter
Los serializadores anteriores realizan una deserialización polimórfica sin restricciones y son peligrosos, como
BinaryFormatter .

Riesgos de asumir que los datos son de confianza


Con frecuencia, un desarrollador de aplicaciones podría creer que solo procesa entradas de confianza. El caso de
la entrada segura es cierto en algunas circunstancias poco frecuentes. Pero es mucho más común que una carga
cruce un límite de confianza sin que el desarrollador sea consciente de ello.
Le recomendamos que use un ser vidor local en el que los empleados usan un cliente de escritorio desde
sus estaciones de trabajo para interactuar con el servicio. Este escenario se podría considerar ingenuamente
como una configuración "segura" en la que es aceptable usar BinaryFormatter . Pero este escenario presenta un
vector de malware que obtiene acceso al equipo de un único empleado para poder propagarse por toda la
empresa. Ese malware puede aprovechar el uso de BinaryFormatter de la empresa para después moverse
lateralmente desde la estación de trabajo del empleado hasta el servidor de back-end. Luego, puede extraer
datos confidenciales de la empresa, como secretos comerciales o datos de clientes.
Considere también una aplicación que use BinaryFormatter para conser var el estado de guardado.
Esto podría parecer inicialmente un escenario seguro, ya que la lectura y escritura de datos en una unidad de
disco duro propia representa una amenaza menor. Pero es habitual compartir documentos por correo
electrónico o Internet, y la mayoría de los usuarios finales no perciben que abrir estos archivos descargados sea
un comportamiento arriesgado.
Este escenario se puede aprovechar con un efecto malintencionado. Si la aplicación es un juego, los usuarios que
comparten archivos de guardado se ponen en riesgo sin saberlo. Los propios desarrolladores también pueden
ser el objetivo. El atacante podría enviar un correo electrónico al soporte técnico de los desarrolladores, adjuntar
un archivo de datos malintencionado y pedir al personal de soporte técnico que lo abra. Este tipo de ataque
podría servir al atacante para adentrarse en la empresa.
Otro escenario es cuando el archivo de datos se encuentra en el almacenamiento en la nube y se sincroniza de
forma automática entre los equipos del usuario. Un atacante que consiga obtener acceso a la cuenta de
almacenamiento en la nube puede envenenar el archivo de datos. Este archivo de datos se sincronizará
automáticamente con los equipos del usuario. La próxima vez que el usuario abra el archivo de datos, se
ejecutará la carga del atacante. Por tanto, el atacante puede aprovechar un riesgo de la cuenta de
almacenamiento en la nube para obtener permisos de ejecución de código completos.
Considere una aplicación que pase de un modelo de instalación de escritorio a un modelo de
primero en la nube. Este escenario incluye aplicaciones que se mueven desde una aplicación de escritorio o un
modelo de cliente enriquecido a un modelo basado en web. Los modelos de amenazas descritos para la
aplicación de escritorio no se aplican necesariamente al servicio basado en la nube. Es posible que en el modelo
de amenazas de la aplicación de escritorio se descarte una amenaza concreta como "no interesante para que el
cliente se ataque a sí mismo". Pero esa misma amenaza podría resultar interesante cuando considera que un
usuario remoto (el cliente) ataca al propio servicio en la nube.

NOTE
En términos generales, la intención de la serialización es transmitir un objeto dentro o fuera de una aplicación. Un ejercicio
de modelado de amenazas casi siempre marca este tipo de transferencia de datos como el cruce de un límite de confianza.

Recursos adicionales
YSoSerial.Net para investigar cómo los adversarios atacan las aplicaciones que usan BinaryFormatter .
Información general sobre vulnerabilidades de deserialización:
OWASP Top 10: A8:2017 - Deserialización no segura
CWE-502: Deserialización de datos que no son de confianza
Conceptos de serialización
16/09/2020 • 6 minutes to read • Edit Online

¿Por qué desearía utilizar la serialización? Las dos razones más importantes son conservar así el estado de un
objeto a los medios de almacenamiento, de manera que una copia exacta se puede recrear en una copia intermedia
posterior y para enviar por valor el objeto de un dominio de aplicación a otro. Por ejemplo, la serialización se utiliza
para guardar el estado de sesión en ASP.NET y copiar los objetos en el Portapapeles en Windows Forms. La
comunicación remota se utiliza también para pasar por valor los objetos de un dominio de aplicación a otro.

WARNING
La serialización binaria puede ser peligrosa. Para obtener más información, consulte la Guía de seguridad BinaryFormatter.

Almacenamiento persistente
Es a menudo necesario almacenar el valor de los campos de un objeto en el disco y a continuación, más tarde,
recuperar estos datos. Aunque esto es fácil de lograr sin confiar en la serialización, este enfoque es a menudo
embarazoso y propenso a errores y se vuelve progresivamente más complejo al necesitar realizar el seguimiento
de una jerarquía de objetos. Imagine escribir una aplicación empresarial grande, que contiene miles de objetos, y
tener que escribir el código para guardar y restaurar los campos y propiedades a y desde el disco para cada objeto.
La serialización proporciona un mecanismo conveniente para lograr este objetivo.
Common Language Runtime administra cómo se almacenan los objetos en memoria y proporciona un mecanismo
de serialización automatizado mediante reflexión. Cuando se serializa un objeto, el nombre de la clase, el
ensamblado y todos los miembros de datos de la instancia de clase se escribe en el almacenamiento. Los objetos
almacenan a menudo referencias a otras instancias en variables miembro. Cuando se serializa la clase, las pistas de
motor de serialización hacen referencia a los objetos, ya serializados, para asegurarse de que no se serializa el
mismo objeto más de una vez. La arquitectura de serialización proporcionada correctamente con .NET Framework
controla de forma automática gráficos de objetos y referencias circulares. El único requisito de los gráficos de
objetos es que todos los objetos, a los que hace referencia el objeto serializado, también se deben marcar como
Serializable (para más información, vea Serialización básica). Si no se hace esto, se producirá una excepción
cuando el serializador intente serializar el objeto sin marca.
Cuando se deserializa la clase serializada, se vuelve a crear la clase y se restauran automáticamente los valores de
todos los miembros de datos.

Serializar por valor


Los objetos sólo son válidos en el dominio de aplicación donde se crean. Se produce un error ante cualquier intento
de pasar el objeto como un parámetro o de devolverlo como un resultado, a menos que el objeto derive de
MarshalByRefObject o esté marcado como Serializable . Si el objeto está marcado como Serializable , se serializa
automáticamente, se transporta de un dominio de aplicación al otro y luego se deserializa para generar una copia
exacta del objeto en el segundo dominio de aplicación. Este proceso se conoce normalmente como valor por
cálculo de referencias.
Cuando un objeto deriva de MarshalByRefObject , se pasa una referencia al objeto de un dominio de aplicación a
otro, en lugar de pasar el propio objeto. También puede marcar un objeto que derive de MarshalByRefObject como
Serializable . Cuando se usa este objeto con comunicación remota, el formateador responsable de la serialización,
que se ha preconfigurado con un selector suplente ( SurrogateSelector ), toma el control del proceso de
serialización y reemplaza todos los objetos derivados de MarshalByRefObject por un proxy. Sin SurrogateSelector ,
la arquitectura de serialización sigue las reglas de serialización estándar descritas en Pasos del proceso de
serialización.

Secciones relacionadas
Serialización binaria
Describe el mecanismo de la serialización binaria que está incluido con Common Language Runtime.
Comunicación remota de .NET
Describe los diversos métodos de comunicaciones disponibles en .NET Framework para las comunicaciones
remotas.
Serialización SOAP y XML
Describe el mecanismo de la serialización XML y SOAP que está incluido con Common Language Runtime.
Serialización básica
16/09/2020 • 4 minutes to read • Edit Online

WARNING
La serialización binaria puede ser peligrosa. Para obtener más información, consulte la Guía de seguridad BinaryFormatter.

La manera más sencilla de convertir una clase en serializable es marcarla con SerializableAttribute como se
muestra a continuación.

[Serializable]
public class MyObject {
public int n1 = 0;
public int n2 = 0;
public String str = null;
}

El ejemplo de código siguiente muestra cómo serializar una instancia de esta clase en un archivo.

MyObject obj = new MyObject();


obj.n1 = 1;
obj.n2 = 24;
obj.str = "Some String";
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("MyFile.bin", FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, obj);
stream.Close();

Este ejemplo utiliza un formateador binario para hacer la serialización. Todo lo que necesita hacer es crear una
instancia del flujo y el formateador que piensa usar y luego llamar al método Serialize en el formateador. La
secuencia y el objeto para serializar se proporcionan como parámetros a esta llamada. Aunque no se muestra
explícitamente en este ejemplo, todas las variables miembro de una clase serán serializadas, incluso las variables
marcadas como privadas. En este aspecto, la serialización binaria difiere de la clase XmlSerializer, que solo serializa
campos públicos. Para más información sobre cómo excluir variables miembro de la serialización binaria, vea
Serialización selectiva.
Restaurar el objeto a su estado anterior es así de fácil. Primero, cree un flujo para leer y un Formatter y luego
indique al formateador que deserialice el objeto. El ejemplo de código siguiente muestra cómo se realiza la acción.

IFormatter formatter = new BinaryFormatter();


Stream stream = new FileStream("MyFile.bin", FileMode.Open, FileAccess.Read, FileShare.Read);
MyObject obj = (MyObject) formatter.Deserialize(stream);
stream.Close();

// Here's the proof.


Console.WriteLine("n1: {0}", obj.n1);
Console.WriteLine("n2: {0}", obj.n2);
Console.WriteLine("str: {0}", obj.str);

El BinaryFormatter usado arriba es muy eficaz y genera un flujo de bytes compacto. Todos los objetos serializados
con este formateador también se pueden deserializar con él, que lo convierte en una herramienta ideal para
serializar objetos que se deserializarán en .NET Framework. Es importante tener en cuenta que no se llama a los
constructores cuando se deserializa un objeto. Esta restricción se coloca en deserialización por las razones de
rendimiento. Sin embargo, esto infringe algunos de los contratos usuales hechos en tiempo de ejecución con la
escritura de objeto y los programadores debería asegurarse que entienden las ramificaciones al marcar un objeto
como serializable.
Si la portabilidad es un requisito, use SoapFormatter en su lugar. Simplemente reemplace Binar yFormatter en el
código anterior por SoapFormatter y llame a Serialize y Deserialize como antes. Este formateador genera el
resultado siguiente para obtener el ejemplo utilizado anteriormente.

<SOAP-ENV:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:a1="http://schemas.microsoft.com/clr/assem/ToFile">

<SOAP-ENV:Body>
<a1:MyObject id="ref-1">
<n1>1</n1>
<n2>24</n2>
<str id="ref-3">Some String</str>
</a1:MyObject>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Es importante tener en cuenta que el atributo Serializable no se puede heredar. Si deriva una nueva clase de
MyObject , la nueva clase también se debe marcar con el atributo o no se puede serializar. Por ejemplo, al intentar
serializar una instancia de la clase siguiente, se obtiene una SerializationException que informa de que el tipo
MyStuff no está marcado como serializable.

public class MyStuff : MyObject


{
public int n3;
}

Es conveniente usar el atributo Serializable, aunque tiene limitaciones, como se ha mostrado anteriormente. Vea
Directrices de serialización para más información sobre cuándo se debe marcar una clase para la serialización. La
serialización no se puede agregar a una clase después de que se haya compilado.

Vea también
Serialización binaria
Serialización SOAP y XML
Serialización selectiva
16/09/2020 • 2 minutes to read • Edit Online

Una clase a menudo contiene campos que no se deberían serializar. Por ejemplo, suponga que una clase almacena
un Identificador de subproceso en una variable miembro. Al deserializar la clase, es posible que el subproceso que
ha almacenado el identificador al serializar la clase ya no se esté ejecutando; así, serializar este valor no tiene
sentido. Puede evitar que las variables miembro se serialicen si las marca con el atributo NonSerialized como se
indica a continuación.

[Serializable]
public class MyObject
{
public int n1;
[NonSerialized] public int n2;
public String str;
}

Si es posible, haz que un objeto pueda contener datos seguros no serializables. Si se debe serializar el objeto,
aplique el atributo NonSerialized a campos concretos que almacenen datos confidenciales. Si no excluye estos
campos de la serialización, sea consciente de que los datos que almacenan se exponen a cualquier código que
tenga permiso para serializar. Para más información sobre cómo escribir código de serialización seguro, vea
Seguridad y serialización.

WARNING
La serialización binaria puede ser peligrosa. Para obtener más información, consulte la Guía de seguridad BinaryFormatter.

Vea también
Serialización binaria
Serialización SOAP y XML
Seguridad y serialización
Serialización personalizada
16/09/2020 • 14 minutes to read • Edit Online

La serialización personalizada es el proceso de controlar la serialización y deserialización de un tipo. Controlando la


serialización, es posible asegurarse compatibilidad de la serialización, que es la capacidad para serializar y
deserializar entre las versiones de un tipo sin interrumpir la función básica del tipo. En la primera versión de un
tipo, puede haber por ejemplo, solo dos campos. En la versión siguiente de un tipo, se agregan varios campos más.
Todavía la segunda versión de una aplicación debe poder serializar y deserializar ambos tipos. En las secciones
siguientes se describe cómo controlar la serialización.

WARNING
La serialización binaria puede ser peligrosa. Para obtener más información, consulte la Guía de seguridad BinaryFormatter.

IMPORTANT
En versiones previas a .NET Framework 4.0, la serialización de datos de usuario personalizados de un ensamblado de
confianza parcial se realizaba mediante el método GetObjectData. A partir de la versión 4.0, ese método se marca con el
atributo de la clase SecurityCriticalAttribute que impide la ejecución en ensamblados de confianza parcial. Para solucionarlo,
implemente la interfaz ISafeSerializationData.

Ejecutar los métodos personalizados durante y después de la


serialización
El procedimiento recomendado y la manera más fácil (se introduce en la versión 2.0 de .NET Framework) es aplicar
los atributos siguientes a los métodos que se utilizan para corregir los datos durante y después de la serialización:
OnDeserializedAttribute
OnDeserializingAttribute
OnSerializedAttribute
OnSerializingAttribute
Estos atributos permiten al tipo participar en cualquiera de las cuatro fases de los procesos de deserialización y
serialización. Los atributos especifican los métodos del tipo que se debe invocar durante cada fase. Los métodos no
tienen acceso a la secuencia de la serialización pero en su lugar le permiten modificar el objeto antes de y después
de la serialización o antes de y después de la deserialización. Los atributos se pueden aplicar en todos los niveles de
la jerarquía de herencia de tipo y se llama a cada método en la jerarquía desde la base a los más derivados. Este
mecanismo evita la complejidad y cualquier problema resultante de implementar la interfaz ISerializable
proporcionando la responsabilidad por la serialización y deserialización a la implementación más derivada.
Además, este mecanismo permite a los formateadores omitir la población de campos y recuperación de la
secuencia de la serialización. Para obtener los detalles y ejemplos de controlar serialización y deserialización, haga
clic en cualquiera de los vínculos anteriores.
Además, al agregar un nuevo campo a un tipo serializable existente, aplique el atributo OptionalFieldAttribute al
campo. BinaryFormatter y SoapFormatter omite la ausencia del campo cuando se procesa una secuencia que falta
en el nuevo campo.
Implementar la interfaz ISerializable
La otra manera de controlar la serialización se logra implementando la interfaz ISerializable en un objeto. Tenga en
cuenta, sin embargo, que el método en la sección anterior reemplaza este método para controlar la serialización.
Además, no debe usar la serialización predeterminada en una clase que se marca con el atributo Serializable y tiene
declaración o seguridad imperativa en el nivel de clase o en sus constructores. En su lugar, estas clases siempre
deben implementar la interfaz ISerializable.
Implementar ISerializable, implica implementar el método GetObjectData y un constructor especial que se usa
cuando se deserializa el objeto. El código de ejemplo siguiente muestra cómo implementar ISerializable en la clase
MyObject de una sección anterior.

[Serializable]
public class MyObject : ISerializable
{
public int n1;
public int n2;
public String str;

public MyObject()
{
}

protected MyObject(SerializationInfo info, StreamingContext context)


{
n1 = info.GetInt32("i");
n2 = info.GetInt32("j");
str = info.GetString("k");
}

[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]


public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("i", n1);
info.AddValue("j", n2);
info.AddValue("k", str);
}
}
<Serializable()> _
Public Class MyObject
Implements ISerializable
Public n1 As Integer
Public n2 As Integer
Public str As String

Public Sub New()


End Sub

Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)


n1 = info.GetInt32("i")
n2 = info.GetInt32("j")
str = info.GetString("k")
End Sub 'New

<SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter := True)> _


Public Overridable Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
info.AddValue("i", n1)
info.AddValue("j", n2)
info.AddValue("k", str)
End Sub
End Class

Cuando se llama a GetObjectData durante la serialización, es responsable de rellenar el elemento


SerializationInfo proporcionado por la llamada al método. Agregue las variables que se van a serializar como
nombre y pares de valor. Cualquier texto se puede utilizar como nombre. Tiene la libertad para decidir qué
variables miembro se agregan a SerializationInfo, con tal de que los datos suficientes se serialicen para restaurar el
objeto durante la deserialización. Las clases derivadas deben llamar al método GetObjectData en el objeto base si
el último implementa ISerializable.
Observe que la serialización puede permitir a otro código ver o modificar datos de instancia de objeto que de lo
contrario son inaccesibles. Por consiguiente, el código que realiza la serialización requiere SecurityPermission con
la marca SerializationFormatter especificada. De acuerdo con la directiva predeterminada, no se concede este
permiso al código descargado de Internet o de la intranet; únicamente el código del equipo local tiene garantizado
este permiso. El método GetObjectData se debe proteger explícitamente o exigiendo el elemento
SecurityPermission con la marca SerializationFormatter especificada o exigiendo otros permisos que
específicamente ayudan a proteger los datos privados.
Si un campo privado almacena información confidencial, debe exigir los permisos adecuados en GetObjectData
para proteger los datos. Recuerde que ese código al que se le ha concedido SecurityPermission con la marca
SerializationFormatter especificada puede ver y modificar los datos almacenados en campos privados. Un autor
de llamada malintencionado al que se le haya concedido este SecurityPermission puede ver datos como
ubicaciones del directorio ocultas o permisos concedidos y usar los datos para aprovecharse de una vulnerabilidad
de seguridad en el equipo. Para obtener una lista completa de marcas de permiso de seguridad que puede
especificar, vea SecurityPermissionFlag Enumeration.
Es importante enfatizar que cuando ISerializable se agrega a una clase, se debe implementar GetObjectData y el
constructor especial. El compilador proporciona una advertencia si falta GetObjectData . Sin embargo, porque es
imposible de exigir la implementación de un constructor, no se proporciona ninguna advertencia si el constructor
está ausente, y se produce una excepción cuando se intenta deserializar una clase sin el constructor.
El diseño actual se favoreció sobre un método SetObjectData para ir alrededor de la seguridad potencial y los
problemas controlando las versiones. Por ejemplo, un método SetObjectData debe ser público si se define como
parte de una interfaz; así los usuarios deben escribir el código para defenderse de llamar varias veces al método
SetObjectData . De lo contrario, una aplicación malintencionada que llama al método SetObjectData en un
objeto en el proceso de ejecutar una operación puede producir los posibles problemas.
Durante la deserialización, SerializationInfo se pasa a la clase utilizando el constructor proporcionado para este
propósito. Se omite cualquier restricción de visibilidad colocada en el constructor cuando se deserializa el objeto;
así que puede marcar la clase como pública, protegida, interna o privada. Sin embargo, es un procedimiento
recomendado para proteger al constructor a menos que se selle la clase, en cuyo caso el constructor se debe
marcar privado. El constructor también debe realizar la validación de entrada en profundidad. Para evitar el mal uso
por código dañino, el constructor debe exigir las mismas comprobaciones de seguridad y permisos exigidos para
obtener una instancia de la clase utilizando cualquier otro constructor. Si no sigue esta recomendación, el código
malintencionado puede preserializar un objeto, obtener el control con el SecurityPermission con el marcador
SerializationFormatter especificado y deserializar el objeto en un equipo cliente que omite cualquier seguridad que
se habría aplicado durante la construcción de la instancia estándar usando un constructor público.
Para restaurar el estado del objeto, simplemente recupere los valores de las variables de SerializationInfo utilizando
los nombres utilizó durante la serialización. Si la clase base implementa ISerializable, se debe llamar al constructor
base para permitir al objeto base restaurar sus variables.
Al derivar una nueva clase de uno que implementa ISerializable, la clase derivada debe implementar ambos, el
constructor y el método GetObjectData si tiene variables que necesitan serializarse. El ejemplo de código
siguiente muestra cómo esto se hace utilizando la clase MyObject mostrada previamente.

[Serializable]
public class ObjectTwo : MyObject
{
public int num;

public ObjectTwo()
: base()
{
}

protected ObjectTwo(SerializationInfo si, StreamingContext context)


: base(si, context)
{
num = si.GetInt32("num");
}

[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]


public override void GetObjectData(SerializationInfo si, StreamingContext context)
{
base.GetObjectData(si,context);
si.AddValue("num", num);
}
}
<Serializable()> _
Public Class ObjectTwo
Inherits MyObject
Public num As Integer

Public Sub New()

End Sub

Protected Sub New(ByVal si As SerializationInfo, _


ByVal context As StreamingContext)
MyBase.New(si, context)
num = si.GetInt32("num")
End Sub

<SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter := True)> _


Public Overrides Sub GetObjectData(ByVal si As SerializationInfo, ByVal context As StreamingContext)
MyBase.GetObjectData(si, context)
si.AddValue("num", num)
End Sub
End Class

No se olvide de llamar a la clase base en el constructor de deserialización. Si no se hace esto, nunca se llama al
constructor en la clase base y el objeto no se construye totalmente después de la deserialización.
Los objetos se reconstruyen al revés; y llamar a los métodos durante la deserialización puede tener efectos
secundarios indeseables, porque los métodos llamados podrían hacer referencia a las referencias que no se han
deserializado cuando se realiza la llamada. Si la clase deserializada implementa IDeserializationCallback, se llama al
método OnDeserialization automáticamente cuando se ha deserializado el gráfico de objetos completo. Se han
restaurado todos los objetos secundarios hechos referencia totalmente en este punto. Una tabla hash es un
ejemplo típico de una clase que es difícil de deserializar sin utilizar el agente de escucha de evento. Es fácil de
recuperar los pares de valor y clave durante la deserialización, pero volver a agregar estos objetos a la tabla hash
puede producir los problemas, porque no hay ninguna garantía de que se hayan deserializado las clases que
derivaron de la tabla hash. Llamar a los métodos en una tabla hash en esta copia intermedia no es, por
consiguiente, aconsejable.

Vea también
Serialización binaria
Serialización SOAP y XML
Seguridad y serialización
Pasos del proceso de serialización
16/09/2020 • 2 minutes to read • Edit Online

Cuando se llama al método Serialize en un formateador, la serialización de objetos procede según la secuencia
siguiente de reglas:
Una comprobación se realiza para determinar si el formateador tiene un selector de suplente. Si el
formateador lo tiene, compruebe si el selector de suplente administra objetos del tipo determinado. Si el
seleccionador administra el tipo de objeto, se llama a ISerializable.GetObjectData en el selector de suplente.
Si no hay ningún selector de suplente o si no administra el tipo de objeto, se realiza una comprobación para
determinar si el objeto está marcado con el atributo Serializable. Si el objeto no está marcado, se genera
SerializationException.
Si el objeto está marcado correctamente, compruebe si el objeto implementa la interfaz ISerializable. Si es
así, se llama a GetObjectData en el objeto.
Si el objeto no implementa ISerializable, se usa la directiva de la serialización predeterminada, serializando
todos los campos no marcados como NonSerialized.

WARNING
La serialización binaria puede ser peligrosa. Para obtener más información, consulte la Guía de seguridad BinaryFormatter.

Vea también
Serialización binaria
Serialización SOAP y XML
Serialización tolerante a versiones
16/09/2020 • 12 minutes to read • Edit Online

En la versión 1.0 y 1.1 de .NET Framework, la creación de tipos serializables que serían reutilizables a partir de una
versión de una aplicación a lo siguiente, ha resultado problemática. Si un tipo se modificara agregando los campos
adicionales, se producirían los problemas siguientes:
Las versiones anteriores de una aplicación produciría excepciones en caso de solicitar la deserialización de las
nuevas versiones del tipo anterior.
Las versiones más recientes de una aplicación producirían las excepciones al deserializar versiones anteriores de
un tipo con datos que faltan.
La Serialización Tolerante a versiones (VTS) es un conjunto de características introducido en .NET Framework 2.0
que facilita, con el tiempo, modificar los tipos serializables. Específicamente, las características VTS están habilitadas
para las clases a las que se ha aplicado el atributo SerializableAttribute, incluidos los tipos genéricos. VTS posibilita
el agregar los nuevos campos a esas clases sin interrumpir la compatibilidad con otras versiones del tipo. Para
obtener una aplicación de ejemplo en funcionamiento, vea Ejemplo de tecnología de serialización tolerante a
versiones.
Las características VTS están habilitadas al utilizar BinaryFormatter. Además, todas las características, excepto la
tolerancia de datos extraños, están habilitadas también al utilizar SoapFormatter. Para más información sobre el
uso de estas clases para la serialización, vea Serialización binaria.

WARNING
La serialización binaria puede ser peligrosa. Para obtener más información, consulte la Guía de seguridad BinaryFormatter.

Lista de características
En la lista de características se incluyen las siguientes:
Tolerancia de datos extraños o inesperados. Esto permite a las versiones más recientes del tipo enviar los datos a
las versiones anteriores.
Tolerancia de datos opcionales que faltan. Esto permite a las versiones anteriores enviar los datos a las versiones
más recientes.
Devoluciones de llamada de la serialización. Esto habilita el valor predeterminado inteligente en casos donde
faltan datos.
Hay además, una característica para declarar cuando se ha agregado un nuevo campo opcional. Ésta es la
propiedad VersionAdded del atributo OptionalFieldAttribute.
Estas opciones se tratan con más detalle en las secciones siguientes.
Tolerancia de datos extraños o inesperados
En el pasado, durante la deserialización, cualquier dato extraño o inesperado produjeron excepciones. Con VTS, en
la misma situación, cualquier dato extraño o inesperado se omite en lugar de producir excepciones. Esto habilita
aplicaciones que utilizan versiones más recientes de un tipo (es decir, una versión que incluye más campos) para
enviar información a las aplicaciones que esperan versiones anteriores del mismo tipo.
En el ejemplo siguiente, los datos adicionales contenidos en CountryField de la versión 2.0 de la clase Address se
omiten cuando una aplicación anterior deserializa la versión más reciente.
// Version 1 of the Address class.
[Serializable]
public class Address
{
public string Street;
public string City;
}
// Version 2.0 of the Address class.
[Serializable]
public class Address
{
public string Street;
public string City;
// The older application ignores this data.
public string CountryField;
}

' Version 1 of the Address class.


<Serializable> _
Public Class Address
Public Street As String
Public City As String
End Class

' Version 2.0 of the Address class.


<Serializable> _
Public Class Address
Public Street As String
Public City As String
' The older application ignores this data.
Public CountryField As String
End Class

Tolerancia de datos que faltan


Los campos se pueden marcar como opcionales aplicando el atributo OptionalFieldAttribute a ellos. Durante la
deserialización, si faltan datos opcionales, el motor de la serialización omite la ausencia y no inicia una excepción.
Así, las aplicaciones que esperan versiones anteriores de un tipo pueden enviar los datos a las aplicaciones que
esperan versiones más recientes del mismo tipo.
El ejemplo siguiente muestra la versión 2.0 de la clase Address con el campo CountryField marcado como
opcional. Si una aplicación más anterior envía la versión 1 a una aplicación más reciente que espera la versión 2.0,
se omite la ausencia de los datos.

[Serializable]
public class Address
{
public string Street;
public string City;

[OptionalField]
public string CountryField;
}
<Serializable> _
Public Class Address
Public Street As String
Public City As String

<OptionalField> _
Public CountryField As String
End Class

Devoluciones de llamada de la serialización


Las devoluciones de llamada de la serialización son un mecanismo que proporciona los enlaces en el proceso de
serialización/deserialización en cuatro puntos.

C UA N DO SE L L A M A A L M ÉTO DO
AT RIB UTO A SO C IA DO. USO T ÍP IC O

OnDeserializingAttribute Antes de la deserialización.* Inicialice los valores predeterminados


para los campos opcionales.

OnDeserializedAttribute Después de la deserialización. Corrija valores de campo opcionales


basados en el contenido de otros
campos.

OnSerializingAttribute Antes de la serialización. Prepare para la serialización. Por


ejemplo, cree las estructuras de datos
opcionales.

OnSerializedAttribute Después de la serialización. Registre los eventos de serialización.

* Esta devolución de llamada se invoca antes del constructor de deserialización, si hay alguno.
Empleo de devoluciones de llamada
Para utilizar las devoluciones de llamada, aplique el atributo adecuado a un método que acepta un parámetro
StreamingContext. Solo se puede marcar un método por clase con cada uno de estos atributos. Por ejemplo:

[OnDeserializing]
private void SetCountryRegionDefault(StreamingContext sc)
{
CountryField = "Japan";
}

<OnDeserializing>
Private Sub SetCountryRegionDefault(sc As StreamingContext)
CountryField = "Japan"
End Sub

El uso previsto de estos métodos es para las versiones. Durante la deserialización, un campo opcional no se puede
inicializar correctamente si faltan los datos para el campo. Esto se puede corregir si se crea el método que asigna el
valor correcto y luego se aplica el atributo OnDeserializingAttribute u OnDeserializedAttribute al método.
El ejemplo siguiente muestra el método en el contexto de un tipo. Si una versión anterior de una aplicación envía
una instancia de la clase Address a una versión posterior de la aplicación, faltarán los datos de campo
CountryField . Pero después de la deserialización, el campo se establecerá en el valor predeterminado "Japan".
[Serializable]
public class Address
{
public string Street;
public string City;
[OptionalField]
public string CountryField;

[OnDeserializing]
private void SetCountryRegionDefault(StreamingContext sc)
{
CountryField = "Japan";
}
}

<Serializable> _
Public Class Address
Public Street As String
Public City As String
<OptionalField> _
Public CountryField As String

<OnDeserializing> _
Private Sub SetCountryRegionDefault(sc As StreamingContext)
CountryField = "Japan"
End Sub
End Class

Propiedad VersionAdded
OptionalFieldAttribute tiene la propiedad VersionAdded . En la versión 2.0 de .NET Framework no se usa. Pero
es importante establecer correctamente esta propiedad para asegurarse de que el tipo sea compatible con los
motores de serialización futuros.
La propiedad indica qué versión de un tipo de un campo determinado se ha agregado. Se debería incrementar en
uno exactamente (comenzando en 2) cada vez que se modifica el tipo, como se muestra en el ejemplo siguiente:
// Version 1.0
[Serializable]
public class Person
{
public string FullName;
}

// Version 2.0
[Serializable]
public class Person
{
public string FullName;

[OptionalField(VersionAdded = 2)]
public string NickName;
[OptionalField(VersionAdded = 2)]
public DateTime BirthDate;
}

// Version 3.0
[Serializable]
public class Person
{
public string FullName;

[OptionalField(VersionAdded=2)]
public string NickName;
[OptionalField(VersionAdded=2)]
public DateTime BirthDate;

[OptionalField(VersionAdded=3)]
public int Weight;
}

' Version 1.0


<Serializable> _
Public Class Person
Public FullName
End Class

' Version 2.0


<Serializable> _
Public Class Person
Public FullName As String

<OptionalField(VersionAdded := 2)> _
Public NickName As String
<OptionalField(VersionAdded := 2)> _
Public BirthDate As DateTime
End Class

' Version 3.0


<Serializable> _
Public Class Person
Public FullName As String

<OptionalField(VersionAdded := 2)> _
Public NickName As String
<OptionalField(VersionAdded := 2)> _
Public BirthDate As DateTime

<OptionalField(VersionAdded := 3)> _
Public Weight As Integer
End Class
SerializationBinder
Algunos usuarios pueden necesitar controlar qué clase serializar y deserializar porque se necesita una versión
diferente de la clase en el servidor y el cliente. SerializationBinder es una clase abstracta usada para controlar los
tipos reales empleados durante la serialización y la deserialización. Para usar esta clase, obtenga una clase de
SerializationBinder y omita los métodos BindToName y BindToType. Para obtener más información, vea Control de
la serialización y la deserialización con SerializationBinder.

Procedimientos recomendados
Para asegurar el comportamiento apropiado de la versión, siga estas reglas al modificar un tipo de la versión para
la versión:
Nunca quite un campo serializado.
Nunca aplique el atributo NonSerializedAttribute a un campo si el atributo no se aplicó al campo en la versión
anterior.
Nunca cambie el nombre o el tipo de un campo serializado.
Al agregar un nuevo campo serializado, aplique el atributo OptionalFieldAttribute .
Al quitar un atributo NonSerializedAttribute de un campo (que no era serializable en una versión anterior),
aplique el atributo OptionalFieldAttribute .
Para todos los campos opcionales, establezca valores predeterminados significativos mediante las devoluciones
de llamada de serialización, a menos que se acepten 0 o null como valores predeterminados.
Para asegurarse de que un tipo será compatible con motores de serialización futuros, siga estas instrucciones:
Establezca siempre correctamente la propiedad VersionAdded en el atributo OptionalFieldAttribute .
Evite la versión bifurcada.

Vea también
SerializableAttribute
BinaryFormatter
SoapFormatter
VersionAdded
OptionalFieldAttribute
OnDeserializingAttribute
OnDeserializedAttribute
OnSerializingAttribute
OnSerializedAttribute
StreamingContext
NonSerializedAttribute
Serialización binaria
Directrices de serialización
16/09/2020 • 19 minutes to read • Edit Online

Este documento enumera las instrucciones que se deben tener en cuenta al diseñar una API para su serialización.

WARNING
La serialización binaria puede ser peligrosa. Para obtener más información, consulte la Guía de seguridad BinaryFormatter.

.NET proporciona tres tecnologías de serialización principales que están optimizadas para varios escenarios de
serialización. En la siguiente tabla se enumeran estas tecnologías y los principales tipos de .NET relacionados con
ellas.

T EC N O LO GÍA C L A SES P ERT IN EN T ES N OTA S

Serialización de contratos de datos DataContractAttribute Persistencia general

DataMemberAttribute Servicios Web

DataContractSerializer JSON

NetDataContractSerializer

DataContractJsonSerializer

ISerializable

Serialización XML XmlSerializer Formato XML


con control completo

Serialización en tiempo de ejecución SerializableAttribute .NET Remoting


(Binary y SOAP)
ISerializable

BinaryFormatter

SoapFormatter

Al diseñar tipos nuevos, debería decidir cuales de estas tecnologías deben admitir esos tipos (en caso de que haya
alguna). Las siguientes instrucciones describen cómo tomar una decisión y cómo proporcionar dicha
compatibilidad. Estas instrucciones no están pensadas para ayudarle a elegir qué tecnología de serialización
debería utilizar en la implementación de su aplicación o biblioteca. Tales instrucciones no están relacionadas
directamente con el diseño de la API y, por lo tanto, no forman parte de este tema.

Instrucciones
Piense en la serialización al diseñar los nuevos tipos.
La serialización es un factor importante en el diseño de cualquier tipo, es posible que los programas
necesiten conservar o transmitir instancias del tipo.
Elegir la tecnología de serialización correcta que se va a admitir
Un tipo dado puede admitir una o varias tecnologías de serialización, o ninguna.
CONSIDERE admitir la serialización de contrato de datos si puede que las instancias de su tipo deban
conservarse o usarse en Servicios Web.
CONSIDERE la posibilidad de admitir la serialización XML en lugar de —o además de— la serialización de
contrato de datos si necesita más control sobre el formato XML que se genera cuando se serializa el tipo.
Esto puede ser necesario en algunos escenarios de interoperabilidad donde es necesario utilizar una
construcción XML no admitida por la serialización de contrato de datos; por ejemplo, para generar atributos
XML.
CONSIDERE la posibilidad de admitir la serialización en tiempo de ejecución si las instancias de su tipo
necesitan traspasar los límites de .NET Remoting.
EVITE admitir la serialización en tiempo de ejecución o la serialización XML solo por motivos generales de
persistencia. Preferencia por la serialización de contrato de datos
Admitir la serialización de contrato de datos
Los tipos pueden admitir la serialización de contrato de datos mediante la aplicación de DataContractAttribute al
tipo y DataMemberAttribute a los miembros (campos y propiedades) del tipo.

[DataContract]
class Person
{

[DataMember]
string LastName { get; set; }
[DataMember]
string FirstName { get; set; }

public Person(string firstNameValue, string lastNameValue)


{
FirstName = firstNameValue;
LastName = lastNameValue;
}
}

<DataContract()> Public Class Person


<DataMember()> Public Property LastName As String
<DataMember()> Public Property FirstName As String

Public Sub New(ByVal firstNameValue As String, ByVal lastNameValue As String)


FirstName = firstNameValue
LastName = lastNameValue
End Sub

End Class

1. CONSIDERE la posibilidad de marcar como públicos los miembros de datos del tipo si el tipo se puede
utilizar con confianza parcial. Con plena confianza, los serializadores de contrato de datos pueden serializar
y deserializar tipos y miembros no públicos, pero solo los miembros públicos se pueden serializar y
deserializar con confianza parcial.
2. Implemente getter y setter en todas las propiedades que tienen Data-MemberAttribute. Los serializadores
de contrato de datos requieren getter y setter para que el tipo se considere serializable. Si el tipo no se va a
usar con confianza parcial, uno o los dos descriptores de acceso de propiedad pueden ser no públicos.
[DataContract]
class Person2
{

string lastName;
string firstName;

public Person2(string firstName, string lastName)


{
this.lastName = lastName;
this.firstName = firstName;
}

[DataMember]
public string LastName
{
// Implement get and set.
get { return lastName; }
private set { lastName = value; }
}

[DataMember]
public string FirstName
{
// Implement get and set.
get { return firstName; }
private set { firstName = value; }
}
}

<DataContract()> Class Person2


Private lastNameValue As String
Private firstNameValue As String

Public Sub New(ByVal firstName As String, ByVal lastName As String)


Me.lastNameValue = lastName
Me.firstNameValue = firstName
End Sub

<DataMember()> Property LastName As String


Get
Return lastNameValue
End Get

Set(ByVal value As String)


lastNameValue = value
End Set

End Property

<DataMember()> Property FirstName As String


Get
Return firstNameValue

End Get
Set(ByVal value As String)
firstNameValue = value
End Set
End Property

End Class

3. CONSIDERE la posibilidad de usar devoluciones de llamada de serialización para la inicialización de


instancias deserializadas.
No se llama a los constructores cuando se deserializan los objetos. Por consiguiente, cualquier lógica que se
ejecute durante la construcción normal debe implementarse como una de las devoluciones de llamada de
serialización.

[DataContract]
class Person3
{
[DataMember]
string lastName;
[DataMember]
string firstName;
string fullName;

public Person3(string firstName, string lastName)


{
// This constructor is not called during deserialization.
this.lastName = lastName;
this.firstName = firstName;
fullName = firstName + " " + lastName;
}

public string FullName


{
get { return fullName; }
}

// This method is called after the object


// is completely deserialized. Use it instead of the
// constructror.
[OnDeserialized]
void OnDeserialized(StreamingContext context)
{
fullName = firstName + " " + lastName;
}
}

<DataContract()> _
Class Person3
<DataMember()> Private lastNameValue As String
<DataMember()> Private firstNameValue As String
Dim fullNameValue As String

Public Sub New(ByVal firstName As String, ByVal lastName As String)


lastNameValue = lastName
firstNameValue = firstName
fullNameValue = firstName & " " & lastName
End Sub

Public ReadOnly Property FullName As String


Get
Return fullNameValue
End Get
End Property

<OnDeserialized()> Sub OnDeserialized(ByVal context As StreamingContext)


fullNameValue = firstNameValue & " " & lastNameValue
End Sub
End Class

El atributo OnDeserializedAttribute es el atributo de devolución de llamada más utilizado. Los demás


atributos de la familia son OnDeserializingAttribute, OnSerializingAttribute y OnSerializedAttribute. Se
pueden utilizar para marcar devoluciones de llamada que se ejecutan antes de la deserialización, antes de la
serialización y, por último, después de la serialización, respectivamente.
4. CONSIDERE la posibilidad de usar KnownTypeAttribute para indicar tipos concretos que se deberían utilizar
al deserializar un gráfico de objetos complejo.
Por ejemplo, si una clase abstracta representa un tipo de un miembro de datos deserializado, el serializador
necesitará la información del tipo conocido para decidir de qué tipo concreto se crearán instancias para
asignarlas al miembro. Si el tipo conocido no se especifica utilizando el atributo, deberá pasarse al
serializador explícitamente (puede hacerlo pasando tipos conocidos al constructor del serializador) o deberá
especificarse en el archivo de configuración.

// The KnownTypeAttribute specifies types to be


// used during serialization.
[KnownType(typeof(USAddress))]
[DataContract]
class Person4
{

[DataMember]
string fullNameValue;
[DataMember]
Address address; // Address is abstract

public Person4(string fullName, Address address)


{
this.fullNameValue = fullName;
this.address = address;
}

public string FullName


{
get { return fullNameValue; }
}
}

[DataContract]
public abstract class Address
{
public abstract string FullAddress { get; }
}

[DataContract]
public class USAddress : Address
{

[DataMember]
public string Street { get; set; }
[DataMember]
public string City { get; set; }
[DataMember]
public string State { get; set; }
[DataMember]
public string ZipCode { get; set; }

public override string FullAddress


{
get
{
return Street + "\n" + City + ", " + State + " " + ZipCode;
}
}
}
<KnownType(GetType(USAddress)), _
DataContract()> Class Person4
<DataMember()> Property fullNameValue As String
<DataMember()> Property addressValue As USAddress ' Address is abstract

Public Sub New(ByVal fullName As String, ByVal address As Address)


fullNameValue = fullName
addressValue = address
End Sub

Public ReadOnly Property FullName() As String


Get
Return fullNameValue
End Get

End Property
End Class

<DataContract()> Public MustInherit Class Address


Public MustOverride Function FullAddress() As String
End Class

<DataContract()> Public Class USAddress


Inherits Address
<DataMember()> Public Property Street As String
<DataMember()> Public City As String
<DataMember()> Public State As String
<DataMember()> Public ZipCode As String

Public Overrides Function FullAddress() As String


Return Street & "\n" & City & ", " & State & " " & ZipCode
End Function
End Class

Cuando no se conoce la lista de tipos conocidos estáticamente (cuando se compila la clase Person ),
KnownTypeAttribute también puede apuntar a un método que devuelve una lista de tipos conocidos en
tiempo de ejecución.
5. Tenga en cuenta la compatibilidad con versiones anteriores y posteriores al crear o cambiar tipos
serializables.
Tenga presente que las secuencias serializadas de futuras versiones del tipo se puedan deserializar en la
versión actual del tipo y viceversa. Asegúrese de que entiende que los miembros de datos, incluso los
privados e internos, no pueden sufrir cambios en sus nombres, tipos, o incluso su orden, en versiones
futuras del tipo a menos que se tomen precauciones para conservar el contrato mediante el uso de
parámetros explícitos para los atributos del contrato de datos. Pruebe la compatibilidad de la serialización
cuando realice cambios en los tipos serializables. Pruebe a deserializar la nueva versión en una versión
anterior, y viceversa.
6. CONSIDERE la posibilidad de implementar la interfaz IExtensibleDataObject para permitir los viajes de ida y
vuelta entre distintas versiones del tipo.
La interfaz permite al serializador asegurarse de que no se pierden datos durante los viajes de ida y vuelta.
La propiedad ExtensionData almacena cualquier dato de la versión futura del tipo que es desconocido para
la versión actual. Cuando la versión actual se serialice y deserialice en una versión futura, los datos
adicionales estarán disponibles en la secuencia serializada a través del valor de propiedad ExtensionData .
// Implement the IExtensibleDataObject interface.
[DataContract]
class Person5 : IExtensibleDataObject
{
ExtensionDataObject serializationData;
[DataMember]
string fullNameValue;

public Person5(string fullName)


{
this.fullNameValue = fullName;
}

public string FullName


{
get { return fullNameValue; }
}

ExtensionDataObject IExtensibleDataObject.ExtensionData
{
get
{
return serializationData;
}
set { serializationData = value; }
}
}

<DataContract()> Class Person5


Implements IExtensibleDataObject
<DataMember()> Dim fullNameValue As String

Public Sub New(ByVal fullName As String)


fullName = fullName
End Sub

Public ReadOnly Property FullName


Get
Return fullNameValue
End Get
End Property
Private serializationData As ExtensionDataObject
Public Property ExtensionData As ExtensionDataObject Implements IExtensibleDataObject.ExtensionData
Get
Return serializationData
End Get
Set(ByVal value As ExtensionDataObject)
serializationData = value
End Set
End Property
End Class

Para obtener más información, vea Forward-Compatible Data Contracts (Contratos de datos compatibles
con el reenvío).
Admitir la serialización XML
La serialización de contrato de datos es la tecnología de serialización principal (predeterminada) en .NET
Framework, pero hay escenarios de serialización que la serialización de contrato de datos no admite. Por ejemplo,
no proporciona control total sobre la forma del XML generado o utilizado por el serializador. Si se requiere un
control tan detallado, debe usarse la serialización XML y los tipos se deben diseñar para admitir esta tecnología de
serialización.
1. EVITE diseñar sus tipos concretamente para la serialización XML, a menos que tenga una razón de peso para
controlar la forma del XML generado. Esta tecnología de serialización ha sido reemplazada por la
serialización de contrato de datos que se describió en la sección anterior.
En otras palabras, no aplique atributos del espacio de nombres System.Xml.Serialization a los nuevos tipos,
a menos que sepa que el tipo se utilizará con la serialización XML. El siguiente ejemplo muestra cómo se
puede usar System.Xml.Serialization para controlar la forma del XML generado.

public class Address2


{
[System.Xml.Serialization.XmlAttribute] // Serialize as XML attribute, instead of an element.
public string Name { get { return "Poe, Toni"; } set { } }
[System.Xml.Serialization.XmlElement(ElementName = "StreetLine")] // Explicitly name the element.
public string Street = "1 Main Street";
}

Public Class Address2


' Supports XML Serialization.
<System.Xml.Serialization.XmlAttribute()> _
Public ReadOnly Property Name As String ' Serialize as XML attribute, instead of an element.
Get
Return "Poe, Toni"
End Get
End Property
<System.Xml.Serialization.XmlElement(ElementName:="StreetLine")> _
Public Street As String = "1 Main Street" ' Explicitly names the element 'StreetLine'.
End Class

2. CONSIDERE la posibilidad de implementar la interfaz IXmlSerializable si desea un mayor control sobre la


forma del XML serializado del que se proporciona aplicando los atributos de serialización XML. Dos
métodos de la interfaz, ReadXml y WriteXml, permiten controlar totalmente la secuencia XML serializada.
También puede controlar el esquema XML que se genera para el tipo aplicando el atributo
XmlSchemaProviderAttribute.
Admitir la serialización en tiempo de ejecución
La serialización en tiempo de ejecución es una tecnología usada por .NET Remoting. Si piensa que sus tipos se
transportarán mediante .NET Remoting, debe asegurarse de que admitan la serialización en tiempo de ejecución.
La compatibilidad básica para la serialización en tiempo de ejecución se puede proporcionar aplicando el atributo
SerializableAttribute y, en escenarios más avanzados, se implementa un patrón serializable en tiempo de ejecución
simple (implemente -ISerializable y proporcione un constructor de serialización).
1. CONSIDERE la posibilidad de admitir la serialización en tiempo de ejecución si sus tipos se van a usar con
.NET Remoting. Por ejemplo, el espacio de nombres System.AddIn usa .NET Remoting. Así, todos los tipos
intercambiados entre los complementos System.AddIn deben admitir la serialización en tiempo de
ejecución.

// Apply SerializableAttribute to support runtime serialization.


[Serializable]
public class Person6
{
// Code not shown.
}
<Serializable()> Public Class Person6 ' Support runtime serialization with the SerializableAttribute.

' Code not shown.


End Class

2. CONSIDERE la implementación del patrón serializable en tiempo de ejecución si desea un control completo
sobre el proceso de serialización. Por ejemplo, si desea transformar los datos cuando se serializan o
deserializan.
El patrón es muy simple. Todo lo que necesita hacer es implementar la interfaz ISerializable y proporcionar
un constructor especial que se utiliza cuando se deserializa el objeto.

// Implement the ISerializable interface for more control.


[Serializable]
public class Person_Runtime_Serializable : ISerializable
{
string fullName;

public Person_Runtime_Serializable() { }
protected Person_Runtime_Serializable(SerializationInfo info, StreamingContext context){
if (info == null) throw new System.ArgumentNullException("info");
fullName = (string)info.GetValue("name", typeof(string));
}
[SecurityPermission(SecurityAction.LinkDemand,
Flags = SecurityPermissionFlag.SerializationFormatter)]
void ISerializable.GetObjectData(SerializationInfo info,
StreamingContext context) {
if (info == null) throw new System.ArgumentNullException("info");
info.AddValue("name", fullName);
}

public string FullName


{
get { return fullName; }
set { fullName = value; }
}
}
' Implement the ISerializable interface for more control.
<Serializable()> Public Class Person_Runtime_Serializable
Implements ISerializable

Private fullNameValue As String

Public Sub New()


' empty constructor.
End Sub
Protected Sub New(ByVal info As SerializationInfo, _
ByVal context As StreamingContext)
If info Is Nothing Then
Throw New System.ArgumentNullException("info")
FullName = CType(info.GetValue("name", GetType(String)), String)
End If
End Sub

Private Sub GetObjectData(ByVal info As SerializationInfo, _


ByVal context As StreamingContext) _
Implements ISerializable.GetObjectData
If info Is Nothing Then
Throw New System.ArgumentNullException("info")
info.AddValue("name", FullName)
End If
End Sub

Public Property FullName As String

Get
Return fullNameValue
End Get
Set(ByVal value As String)
fullNameValue = value

End Set
End Property

End Class

3. Proteja el constructor de serialización y proporcione dos parámetros con el tipo y el nombre que se indican
en el ejemplo que se muestra aquí.

protected Person_Runtime_Serializable(SerializationInfo info, StreamingContext context){

Protected Sub New(ByVal info As SerializationInfo, _


ByVal context As StreamingContext)

4. Implemente los miembros ISerializable explícitamente.

void ISerializable.GetObjectData(SerializationInfo info,


StreamingContext context) {

Private Sub GetObjectData(ByVal info As SerializationInfo, _


ByVal context As StreamingContext) _
Implements ISerializable.GetObjectData

5. Aplique una petición de vínculo a la implementación ISerializable.GetObjectData . De este modo se


garantiza que solo el núcleo de plena confianza y el serializador en tiempo de ejecución tienen acceso al
miembro.

[SecurityPermission(SecurityAction.LinkDemand,
Flags = SecurityPermissionFlag.SerializationFormatter)]

<Serializable()> Public Class Person_Runtime_Serializable2


Implements ISerializable
<SecurityPermission(SecurityAction.LinkDemand,
Flags:=SecurityPermissionFlag.SerializationFormatter)> _
Private Sub GetObjectData(ByVal info As System.Runtime.Serialization.SerializationInfo, _
ByVal context As System.Runtime.Serialization.StreamingContext) _
Implements System.Runtime.Serialization.ISerializable.GetObjectData
' Code not shown.
End Sub
End Class

Vea también
Utilización de contratos de datos
Serializador de contratos de datos
Tipos admitidos por el serializador de contratos de datos
Serialización binaria
.NET Remoting
Serialización SOAP y XML
Seguridad y serialización
Cómo: Fragmentar datos serializados
16/09/2020 • 6 minutes to read • Edit Online

WARNING
La serialización binaria puede ser peligrosa. Para obtener más información, consulte la Guía de seguridad BinaryFormatter.

Dos problemas que se producen al enviar los conjuntos de datos grandes en mensajes del Servicio Web son:
1. Un espacio de trabajo grande (memoria) debido al almacenamiento en búfer por el motor de la serialización.
2. Consumo de ancho de banda inmoderado debido a 33 por ciento inflación después de la codificación de
Base64.
Para resolver estos problemas, implemente la interfaz IXmlSerializable para controlar la serialización y
deserialización. Específicamente, implemente WriteXml y los métodos ReadXml al fragmento los datos.
Para implementar la fragmentación del lado del servidor
1. En el equipo del servidor, el método web se debe apagar del almacenado en búfer de ASP.NET y devolver un
tipo que implementa IXmlSerializable.
2. El tipo que implementa IXmlSerializable fragmenta los datos en el método WriteXml.
Para implementar el procesamiento del lado cliente
1. Modifique el método Web en el proxy de cliente para devolver el tipo que implementa IXmlSerializable.
Puede usar una SchemaImporterExtension para realizar esta acción automáticamente, pero no se muestra
aquí.
2. Implemente el método ReadXml para leer el flujo de datos fragmentado y escribir los bytes en el disco. Esta
implementación también genera eventos de progreso que pueden ser utilizados por un control gráfico,
como una barra de progreso.

Ejemplo
El ejemplo de código siguiente muestra el método Web en el cliente que desactiva el almacenado en búfer de
ASP.NET. También muestra la implementación del lado cliente de la interfaz IXmlSerializable que fragmenta los
datos en el método WriteXml.

[WebMethod]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute
(ParameterStyle= SoapParameterStyle.Bare)]
public SongStream DownloadSong(DownloadAuthorization Authorization, string filePath)
{

// Turn off response buffering.


System.Web.HttpContext.Current.Response.Buffer = false;
// Return a song.
SongStream song = new SongStream(filePath);
return song;
}
<WebMethod(),
System.Web.Services.Protocols.SoapDocumentMethodAttribute(ParameterStyle:=SoapParameterStyle.Bare)> _
Public Function DownloadSong(ByVal Authorization As DownloadAuthorization, ByVal filePath As String) As
SongStream

' Turn off response buffering.


System.Web.HttpContext.Current.Response.Buffer = False
' Return a song.
Dim song As New SongStream(filePath)
Return song

End Function
End Class

[XmlSchemaProvider("MySchema")]
public class SongStream : IXmlSerializable
{

private const string ns = "http://demos.Contoso.com/webservices";


private string filePath;

public SongStream(){ }

public SongStream(string filePath)


{
this.filePath = filePath;
}

// This is the method named by the XmlSchemaProviderAttribute applied to the type.


public static XmlQualifiedName MySchema(XmlSchemaSet xs)
{
// This method is called by the framework to get the schema for this type.
// We return an existing schema from disk.

XmlSerializer schemaSerializer = new XmlSerializer(typeof(XmlSchema));


string xsdPath = null;
// NOTE: replace the string with your own path.
xsdPath = System.Web.HttpContext.Current.Server.MapPath("SongStream.xsd");
XmlSchema s = (XmlSchema)schemaSerializer.Deserialize(
new XmlTextReader(xsdPath), null);
xs.XmlResolver = new XmlUrlResolver();
xs.Add(s);

return new XmlQualifiedName("songStream", ns);


}

void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)


{
// This is the chunking code.
// ASP.NET buffering must be turned off for this to work.

int bufferSize = 4096;


char[] songBytes = new char[bufferSize];
FileStream inFile = File.Open(this.filePath, FileMode.Open, FileAccess.Read);

long length = inFile.Length;

// Write the file name.


writer.WriteElementString("fileName", ns, Path.GetFileNameWithoutExtension(this.filePath));

// Write the size.


writer.WriteElementString("size", ns, length.ToString());

// Write the song bytes.


writer.WriteStartElement("song", ns);
StreamReader sr = new StreamReader(inFile, true);
int readLen = sr.Read(songBytes, 0, bufferSize);

while (readLen > 0)


{
writer.WriteStartElement("chunk", ns);
writer.WriteChars(songBytes, 0, readLen);
writer.WriteEndElement();

writer.Flush();
readLen = sr.Read(songBytes, 0, bufferSize);
}

writer.WriteEndElement();
inFile.Close();
}

System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
throw new System.NotImplementedException();
}

void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)


{
throw new System.NotImplementedException();
}
}

<XmlSchemaProvider("MySchema")> _
Public Class SongStream
Implements IXmlSerializable

Private Const ns As String = "http://demos.Contoso.com/webservices"


Private filePath As String

Public Sub New()

End Sub

Public Sub New(ByVal filePath As String)


Me.filePath = filePath
End Sub

' This is the method named by the XmlSchemaProviderAttribute applied to the type.
Public Shared Function MySchema(ByVal xs As XmlSchemaSet) As XmlQualifiedName
' This method is called by the framework to get the schema for this type.
' We return an existing schema from disk.
Dim schemaSerializer As New XmlSerializer(GetType(XmlSchema))
Dim xsdPath As String = Nothing
' NOTE: replace SongStream.xsd with your own schema file.
xsdPath = System.Web.HttpContext.Current.Server.MapPath("SongStream.xsd")
Dim s As XmlSchema = CType(schemaSerializer.Deserialize(New XmlTextReader(xsdPath)), XmlSchema)
xs.XmlResolver = New XmlUrlResolver()
xs.Add(s)

Return New XmlQualifiedName("songStream", ns)

End Function

Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements IXmlSerializable.WriteXml


' This is the chunking code.
' ASP.NET buffering must be turned off for this to work.

Dim bufferSize As Integer = 4096


Dim songBytes(bufferSize) As Char
Dim songBytes(bufferSize) As Char
Dim inFile As FileStream = File.Open(Me.filePath, FileMode.Open, FileAccess.Read)

Dim length As Long = inFile.Length

' Write the file name.


writer.WriteElementString("fileName", ns, Path.GetFileNameWithoutExtension(Me.filePath))

' Write the size.


writer.WriteElementString("size", ns, length.ToString())

' Write the song bytes.


writer.WriteStartElement("song", ns)

Dim sr As New StreamReader(inFile, True)


Dim readLen As Integer = sr.Read(songBytes, 0, bufferSize)

While readLen > 0


writer.WriteStartElement("chunk", ns)
writer.WriteChars(songBytes, 0, readLen)
writer.WriteEndElement()

writer.Flush()
readLen = sr.Read(songBytes, 0, bufferSize)
End While

writer.WriteEndElement()
inFile.Close()
End Sub

Function GetSchema() As System.Xml.Schema.XmlSchema Implements IXmlSerializable.GetSchema


Throw New System.NotImplementedException()
End Function

Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements IXmlSerializable.ReadXml


Throw New System.NotImplementedException()
End Sub
End Class

public class SongFile : IXmlSerializable


{
public static event ProgressMade OnProgress;

public SongFile()
{ }

private const string ns = "http://demos.teched2004.com/webservices";


public static string MusicPath;
private string filePath;
private double size;

void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)


{
reader.ReadStartElement("DownloadSongResult", ns);
ReadFileName(reader);
ReadSongSize(reader);
ReadAndSaveSong(reader);
reader.ReadEndElement();
}

void ReadFileName(XmlReader reader)


{
string fileName = reader.ReadElementString("fileName", ns);
this.filePath =
Path.Combine(MusicPath, Path.ChangeExtension(fileName, ".mp3"));
}
void ReadSongSize(XmlReader reader)
{
this.size = Convert.ToDouble(reader.ReadElementString("size", ns));
}

void ReadAndSaveSong(XmlReader reader)


{
FileStream outFile = File.Open(
this.filePath, FileMode.Create, FileAccess.Write);

string songBase64;
byte[] songBytes;
reader.ReadStartElement("song", ns);
double totalRead=0;
while(true)
{
if (reader.IsStartElement("chunk", ns))
{
songBase64 = reader.ReadElementString();
totalRead += songBase64.Length;
songBytes = Convert.FromBase64String(songBase64);
outFile.Write(songBytes, 0, songBytes.Length);
outFile.Flush();

if (OnProgress != null)
{
OnProgress(100 * (totalRead / size));
}
}

else
{
break;
}
}

outFile.Close();
reader.ReadEndElement();
}

[PermissionSet(SecurityAction.Demand, Name="FullTrust")]
public void Play()
{
System.Diagnostics.Process.Start(this.filePath);
}

System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
throw new System.NotImplementedException();
}

public void WriteXml(XmlWriter writer)


{
throw new System.NotImplementedException();
}
}

Public Class SongFile


Implements IXmlSerializable
Public Shared Event OnProgress As ProgressMade

Public Sub New()

End Sub

Private Const ns As String = "http://demos.teched2004.com/webservices"


Private Const ns As String = "http://demos.teched2004.com/webservices"
Public Shared MusicPath As String
Private filePath As String
Private size As Double

Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements IXmlSerializable.ReadXml


reader.ReadStartElement("DownloadSongResult", ns)
ReadFileName(reader)
ReadSongSize(reader)
ReadAndSaveSong(reader)
reader.ReadEndElement()
End Sub

Sub ReadFileName(ByVal reader As XmlReader)


Dim fileName As String = reader.ReadElementString("fileName", ns)
Me.filePath = Path.Combine(MusicPath, Path.ChangeExtension(fileName, ".mp3"))

End Sub

Sub ReadSongSize(ByVal reader As XmlReader)


Me.size = Convert.ToDouble(reader.ReadElementString("size", ns))

End Sub

Sub ReadAndSaveSong(ByVal reader As XmlReader)


Dim outFile As FileStream = File.Open(Me.filePath, FileMode.Create, FileAccess.Write)

Dim songBase64 As String


Dim songBytes() As Byte
reader.ReadStartElement("song", ns)
Dim totalRead As Double = 0
While True
If reader.IsStartElement("chunk", ns) Then
songBase64 = reader.ReadElementString()
totalRead += songBase64.Length
songBytes = Convert.FromBase64String(songBase64)
outFile.Write(songBytes, 0, songBytes.Length)
outFile.Flush()
RaiseEvent OnProgress((100 * (totalRead / size)))
Else
Exit While
End If
End While

outFile.Close()
reader.ReadEndElement()
End Sub

<PermissionSet(SecurityAction.Demand, Name:="FullTrust")> _
Public Sub Play()
System.Diagnostics.Process.Start(Me.filePath)
End Sub

Function GetSchema() As System.Xml.Schema.XmlSchema Implements IXmlSerializable.GetSchema


Throw New System.NotImplementedException()
End Function

Public Sub WriteXml(ByVal writer As XmlWriter) Implements IXmlSerializable.WriteXml


Throw New System.NotImplementedException()
End Sub
End Class

Compilación del código


El código utiliza los siguientes espacios de nombres: System, System.Runtime.Serialization,
System.Web.Services, System.Web.Services.Protocols, System.Xml, System.Xml.Serialization, y
System.Xml.Schema.

Vea también
Serialización personalizada
Procedimiento para determinar si un objeto de
.NET Standard es serializable
16/09/2020 • 3 minutes to read • Edit Online

.NET Standard es una especificación que define los tipos y miembros que debe haber en las implementaciones de
.NET específicas que se ajusten a esa versión del estándar. Pero .NET Standard no define si un tipo es serializable.
Los tipos definidos en la biblioteca de .NET Standard no están marcados con el atributo SerializableAttribute. En su
lugar, son las implementaciones de .NET específicas, como .NET Framework y .NET Core, las que deciden libremente
si un tipo determinado es serializable.
Si ha desarrollado una biblioteca que tiene como destino .NET Standard, cualquier implementación de .NET que
admita .NET Standard podrá usar esa biblioteca. Esto significa que no se puede saber con antelación si un tipo
determinado es serializable; solo se sabrá en tiempo de ejecución.
Para determinar si un objeto es serializable en tiempo de ejecución, hay que recuperar el valor de la propiedad
IsSerializable de un objeto Type que representa el tipo de ese objeto. En el siguiente ejemplo se proporciona una
implementación. En él se define un método de extensión IsSerializable(Object) que indica si cualquier instancia
de Object se puede serializar.

namespace Libraries
{
using System;

public static class UtilityLibrary


{
public static bool IsSerializable(this object obj)
{
if (obj == null)
return false;

Type t = obj.GetType();
return t.IsSerializable;
}
}
}

Imports System.Runtime.CompilerServices

Namespace Global.Libraries

Public Module UtilityLibrary


<Extension>
Public Function IsSerializable(obj As Object) As Boolean
If obj Is Nothing Then Return False

Dim t As Type = obj.GetType()


Return t.IsSerializable
End Function
End Module
End Namespace

Después, se puede pasar cualquier objeto al método para determinar si se puede serializar y deserializar en la
implementación actual de .NET, como se muestra en el ejemplo siguiente:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Libraries;

namespace test_serialization
{
class Program
{
static void Main()
{
var value = ValueTuple.Create("03244562", DateTime.Now, 13452.50m);
if (value.IsSerializable())
{
// Serialize the value tuple.
var formatter = new BinaryFormatter();
using (var stream = new FileStream("data.bin", FileMode.Create,
FileAccess.Write, FileShare.None))
{
formatter.Serialize(stream, value);
}
// Deserialize the value tuple.
using (var readStream = new FileStream("data.bin", FileMode.Open))
{
object restoredValue = formatter.Deserialize(readStream);
Console.WriteLine($"{restoredValue.GetType().Name}: {restoredValue}");
}
}
else
{
Console.WriteLine($"{nameof(value)} is not serializable");
}
}
}
}
// The example displays output like the following:
// ValueTuple`3: (03244562, 10/18/2017 5:25:22 PM, 13452.50)

Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
Imports Libraries

Module Program
Sub Main()
Dim value = ValueTuple.Create("03244562", DateTime.Now, 13452.50d)
If value.IsSerializable() Then
Dim formatter As New BinaryFormatter()
' Serialize the value tuple.
Using stream As New FileStream("data.bin", FileMode.Create,
FileAccess.Write, FileShare.None)
formatter.Serialize(stream, value)
End Using
' Deserialize the value tuple.
Using readStream As New FileStream("data.bin", FileMode.Open)
Dim restoredValue = formatter.Deserialize(readStream)
Console.WriteLine($"{restoredValue.GetType().Name}: {restoredValue}")
End Using
Else
Console.WriteLine($"{nameof(value)} is not serializable")
End If
End Sub
End Module
' The example displays output like the following:
' ValueTuple`3: (03244562, 10/18/2017 5:25:22 PM, 13452.50)
Vea también
Serialización binaria
System.SerializableAttribute
Type.IsSerializable
Ejemplo de tecnología de serialización básica
16/09/2020 • 5 minutes to read • Edit Online

Descargar ejemplo
En este ejemplo se demuestra la capacidad de Common Language Runtime para serializar un gráfico de objetos de
la memoria en una secuencia. En este ejemplo se puede utilizar SoapFormatter o BinaryFormatter para la
serialización. Se serializa o deserializa una lista vinculada, llena de datos, desde o hacia una secuencia del archivo.
En cualquier caso, se muestra la lista para que pueda ver los resultados. La lista vinculada es de tipo LinkedList ,
un tipo definido por este ejemplo.
Para obtener más información sobre la serialización, revise los comentarios de los archivos de código fuente y de
build.proj.
Para generar el ejemplo desde el símbolo del sistema
1. Navegue hasta uno de los subdirectorios específicos de lenguaje bajo el directorio
Technologies\Serialization\Runtime Serialization\Basic, utilizando el símbolo del sistema.
2. Escriba msbuild SerializationCS.sln , msbuild SerializationJSL.sln o msbuild SerializationVB.sln ,
según el lenguaje de programación que prefiera, en la línea de comandos.
Para compilar el ejemplo con Visual Studio
1. Abra el Explorador de archivos y navegue hasta uno de los subdirectorios específicos del lenguaje del
ejemplo.
2. Haga doble clic en el icono del archivo SerializationCS.sln, SerializationJSL.sln o SerializationVB.sln,
dependiendo del lenguaje de programación elegido, para abrir el archivo en Visual Studio.
3. En el menú Compilar , seleccione Compilar solución .
La aplicación de ejemplo se generará en el subdirectorio predeterminado \bin o \bin\Debug.
Para ejecutar el ejemplo
1. Navegue hasta el directorio que contiene el ejecutable generado.
2. Escriba Serialization.exe , junto con los valores de parámetro que quiera, en la línea de comandos.

NOTE
En este ejemplo se genera una aplicación de consola. Para poder ver el resultado, debe iniciarla desde la línea de comandos.

Comentarios
La aplicación de ejemplo acepta parámetros de línea de comandos que indican qué comprobación desea ejecutar.
Para serializar una lista de 10 nodos en un archivo denominado Test.xml mediante el formateador SOAP, use los
parámetros sx Test.xml 10 .
Por ejemplo:

Serialize.exe -sx Test.xml 10

Para deserializar el archivo Test.xml del ejemplo anterior, use los parámetros dx Test.xml .
Por ejemplo:

Serialize.exe -dx Test.xml

En los dos ejemplos anteriores, la "x" en el modificador de la línea de comandos indica que desea realizar una
serialización de XML SOAP. Puede utilizar "b" en su lugar para utilizar la serialización binaria. Si desea realizar la
serialización con un gran número de nodos, puede que prefiera redirigir el resultado de la consola a un archivo.
Por ejemplo:

Serialize.exe -sb Test.bin 10000 >somefile.txt

Las viñetas siguientes describen brevemente las clases y las tecnologías que se utilizan en este ejemplo.
Serialización en tiempo de ejecución
IFormatter Se usa para hacer referencia a un objeto BinaryFormatter o SoapFormatter.
BinaryFormatter Se usa para serializar una lista vinculada a una secuencia en formato binario. El
formateador binario utiliza un formato que solo entiende el tipo BinaryFormatter. Sin embargo, los
datos son concisos.
SoapFormatter Se usa para serializar una lista vinculada a una secuencia en formato SOAP. SOAP es
un formato estándar.
E/S de secuencia
Stream Se utiliza para serializar y deserializar. El tipo de secuencia concreto utilizado en este ejemplo
es el tipo FileStream. Sin embargo, la serialización se puede utilizar con cualquier tipo derivado de
Stream.
File Se utiliza para crear objetos FileStream para leer y crear archivos en disco.
FileStream Se utiliza para serializar y deserializar las listas vinculadas.

Vea también
System.IO
File
FileStream
Stream
Random
System.Runtime.Serialization
BinaryFormatter
SoapFormatter
IFormatter
SerializableAttribute
System.Xml.Serialization
Serialización básica
Serialización binaria
Controlar la serialización XML mediante atributos
Introducción a la serialización XML
Serialización
Serialización SOAP y XML
Serialización de SOAP y XML
16/09/2020 • 2 minutes to read • Edit Online

La serialización XML convierte (serializa) las propiedades y campos públicos de un objeto y los parámetros y
valores devueltos de los métodos en una secuencia XML que se ajusta a un documento específico del lenguaje
de definición de esquemas XML (XSD). La serialización XML produce clases fuertemente tipadas cuyas
propiedades y campos se convierten en un formato en serie (en este caso, a XML) para almacenaje y
transporte.
Dado que XML es un estándar abierto, cualquier aplicación, según sea necesario, puede procesar la secuencia
XML sin tener en cuenta la plataforma. Por ejemplo, los servicios Web XML creados utilizando el ASP.NET
utilizan la clase XmlSerializer para crear secuencias XML que pasan los datos entre las aplicaciones de servicio
Web XML a lo largo de Internet o en intranets. A la inversa, la deserialización toma este tipo de secuencia XML y
reconstruye el objeto.
La serialización XML también se puede usar para serializar objetos en secuencias XML que se ajustan a la
especificación SOAP. SOAP es un protocolo basado en XML, diseñado específicamente para transportar
llamadas a procedimiento utilizando XML.
Para serializar o deserializar objetos utilice la clase XmlSerializer. Para crear las clases que se van a serializar,
utilice la herramienta XML Schema Definition.

Vea también
Serialización binaria
Servicios Web XML creados con ASP.NET y clientes de servicio Web XML
serialización XML
16/09/2020 • 21 minutes to read • Edit Online

La serialización es el proceso de convertir un objeto a un formulario que pueda transportarse fácilmente. Por
ejemplo, puede serializar un objeto y transportarlo a través de Internet utilizando http entre un cliente y un
servidor. El otro fin para el cual sirve es que la deserialización reconstruye el objeto desde la secuencia.
La serialización XML serializa solo los campos públicos y los valores de propiedad de un objeto en una secuencia
XML. La serialización XML no incluye información de tipo. Por ejemplo, si tiene un objeto Book que existe en el
espacio de nombres Librar y , no hay garantía de que se deserialice en un objeto del mismo tipo.

NOTE
La serialización XML no convierte los métodos, indizadores, campos privados ni propiedades (excepto las colecciones de
solo lectura). Para serializar todos los campos y propiedades, tanto públicos como privados, de un objeto, utilice el
DataContractSerializer en vez de la serialización XML.

La clase central en serialización XML es la clase XmlSerializer, y los métodos más importantes de esta clase son
Serialize y Deserialize . XmlSerializer crea los archivos C# y los compila en archivos .dll para realizar esta
serialización. En .NET Framework 2.0, la herramienta Generador de serializador XML (Sgen.exe) está diseñada
para generar de antemano estos ensamblados de serialización para implementarlos con la aplicación y mejorar el
rendimiento de inicio. La secuencia XML generada por XmlSerializer es conforme con la recomendación del
lenguaje de definición de esquema XML (XSD) 1.0 de World Wide Web Consortium (W3C). Además, los tipos de
datos generados son conformes al documento titulado "Esquema XML parte 2: Tipos de datos."
Los datos de los objetos se describen mediante construcciones de lenguaje de programación, como clases,
campos, propiedades, tipos primitivos, matrices e incluso XML insertado en forma de objetos XmlElement o
XmlAttribute . Existe la opción de crear clases propias, anotadas con atributos, o de utilizar la herramienta de
definición de esquema XML para generar las clases de acuerdo con un documento de esquema XML existente.
Si se dispone de un esquema XML, es posible ejecutar la herramienta de definición de esquema XML con el fin de
generar un conjunto de clases fuertemente tipadas para el esquema y que se anoten con atributos. Cuando se
serializa una instancia de este tipo de clase, el XML generado se adhiere al Esquema XML. Contando con este tipo
de clase, se puede programar con un modelo de objetos manipulados con facilidad estando asegurado que el
XML generado cumple el esquema XML. Esta es una alternativa al uso de otras clases en .NET Framework, como
las clases XmlReader y XmlWriter , para analizar y escribir una secuencia XML. Para obtener más información,
vea XML Documents and Data (Documentos y datos XML). Estas clases le permiten analizar cualquier secuencia
XML. En contraste, use XmlSerializer cuando se espera que la secuencia XML cumpla con un esquema XML
conocido.
Los atributos controlan la secuencia XML generada por la clase XmlSerializer , lo que le permite establecer el
espacio de nombres XML, el nombre de elemento, el nombre de atributo, etc., de la secuencia XML. Para obtener
más información sobre estos atributos y la forma en que controlan la serialización XML, vea Controlling XML
Serialization Using Attributes (Controlar la serialización XML mediante atributos). Para consultar una tabla de los
atributos que se usan para controlar el XML generado, vea Attributes That Control XML Serialization (Atributos
que controlan la serialización XML).
La clase XmlSerializer puede serializar adicionalmente un objeto y generar una secuencia XML SOAP codificada.
El XML generado cumple las normas de la sección 5 del documento de World Wide Web Consortium con título
"Protocolo simple de acceso a objetos (SOAP) 1.1" (SOAP). Para más información sobre este proceso, vea
Procedimiento para serializar un objeto como secuencia XML con codificación SOAP. Para consultar una tabla de
los atributos que controlan el XML generado, vea Attributes That Control Encoded SOAP Serialization (Atributos
que controlan la serialización SOAP codificada).
La clase XmlSerializer genera los mensajes SOAP creados por servicios Web XML y pasados a estos. Para
controlar los mensajes SOAP, puede aplicar los atributos a las clases, los valores devueltos, parámetros y campos
situados en un archivo de servicio Web XML (.asmx). Puede utilizar ambos, los atributos mostrados en "atributos
que controlan la serialización XML" y los “atributos que controlan la serialización de SOAP codificada” porque un
servicio Web XML puede utilizar o el estilo SOAP literal o codificado. Para obtener más información sobre cómo
usar atributos para controlar el XML generado por un servicio Web XML, vea XML Serialization with XML Web
Services (Serialización XML con servicios Web XML). Para más información sobre SOAP y servicios Web XML, vea
Personalizar el formato de mensajes SOAP.

Consideraciones de seguridad para aplicaciones XmlSerializer


Si crea una aplicación que usa la clase XmlSerializer , conviene estar al tanto de los siguientes puntos y sus
implicaciones:
XmlSerializer crea archivos de C# (.cs) y los compila en archivos .dll en el directorio denominado por la
variable de entorno TEMP; la serialización se produce con esas DLL.

NOTE
Estos ensamblados de serialización se pueden generar de antemano y firmar utilizando la herramienta SGen.exe.
Esta no es la causa del funcionamiento de ningún servidor de servicio Web. En otras palabras, solo está para el uso
del cliente y para la serialización manual.

El código y las DLL son vulnerables a un proceso malintencionado en el momento de creación y


compilación. Al utilizar un equipo que ejecuta Microsoft Windows NT 4.0 o posterior, podría ser posible
que dos o más usuarios compartan el directorio TEMP. Es peligroso compartir un directorio TEMP si las
dos cuentas tienen privilegios de seguridad diferentes y la cuenta con privilegios más altos ejecuta una
aplicación mediante XmlSerializer . En este caso, un usuario puede incumplir la seguridad del equipo
reemplazando o el archivo .cs o .dll que está compilado. Para eliminar esta preocupación, siempre
asegurarse de que cada cuenta en el equipo tiene su propio perfil. De forma predeterminada, la variable de
entorno TEMP señala a un directorio diferente para cada cuenta.
Si un usuario malintencionado envía una secuencia continua de datos XML a un servidor web (un ataque
por denegación de servicio), XmlSerializer sigue procesando los datos hasta que el equipo se quede sin
recursos.
Este tipo de ataque se elimina si se está utilizando un equipo que ejecuta Internet Information Services
(IIS), y su aplicación se está ejecutando dentro de IIS. IIS representa una puerta que no procesa secuencias
que sean más largas que la cantidad fija (el valor predeterminado es 4 KB). Si crea una aplicación que no
usa IIS y deserializa con XmlSerializer , debería implementar una puerta similar que evite un ataque por
denegación de servicio.
XmlSerializer serializa los datos y ejecuta código mediante el uso de cualquier tipo que se le dé.
Hay dos maneras en las que un objeto malintencionado representa una amenaza. Podría ejecutar software
malintencionado o insertarlo en el archivo de C# creado por XmlSerializer . En el primer caso, si un objeto
malintencionado intenta ejecutar un procedimiento destructivo, la seguridad de acceso del código ayuda a
evitar que se haga cualquier daño. En el segundo caso, existe la posibilidad de que un objeto
malintencionado inserte de algún modo código en el archivo de C# creado por XmlSerializer . Aunque se
ha examinado este problema completamente, y este tipo de ataque está considerado improbable, debería
tomar la precaución de nunca serializar los datos con un tipo desconocido y de poca confianza.
Los datos confidenciales serializados podrían ser vulnerables.
Después de que XmlSerializer haya serializado los datos, estos pueden almacenarse como un archivo
XML u otro almacén de datos. Si su almacén de datos está disponible para otros procesos, o está visible en
una intranet o Internet, los datos se pueden robar y utilizar malévolamente. Por ejemplo, si se crea una
aplicación que serializa órdenes, que incluyen la tarjeta de crédito, los datos son muy sensibles. Para
ayudar a evitar esto, siempre proteja el almacén para que sus datos y los pasos de la toma lo mantenga
privado.

Serialización de una clase simple


El ejemplo de código siguiente muestra una clase básica con un campo público.

Public Class OrderForm


Public OrderDate As DateTime
End Class

public class OrderForm


{
public DateTime OrderDate;
}

Cuando se serializa una instancia de esta clase, se podría parecer a lo siguiente.

<OrderForm>
<OrderDate>12/12/01</OrderDate>
</OrderForm>

Para obtener más ejemplos de serialización, vea Examples of XML Serialization (Ejemplos de serialización XML).

Elementos que se pueden serializar


Los elementos siguientes se pueden serializar mediante la clase XmlSerializer :
Las propiedades de lectura y escritura públicas y campos de clases públicas.
Las clases que implementan ICollection o IEnumerable .

NOTE
Solo se serializan las colecciones, no las propiedades públicas.

Los objetos XmlElement .


Los objetos XmlNode .
Los objetos DataSet .
Para más información sobre cómo serializar o deserializar objetos, vea Procedimiento para serializar un objeto y
Procedimiento para deserializar un objeto.

Ventajas de utilizar serialización XML


La clase XmlSerializer proporciona un control completo y flexible al serializar un objeto como XML. Si está
creando un servicio Web XML, puede aplicar atributos que controlan la serialización a las clases y miembros para
asegurarse que el resultado de XML cumple con un esquema concreto.
Por ejemplo, XmlSerializer permite hacer lo siguiente:
Especifique si un campo o propiedad debería estar codificada como un atributo o un elemento.
Especifique un espacio de nombres XML para utilizar.
Especifique el nombre de un elemento o atributo si un campo o el nombre de propiedad es impropio.
Otra ventaja de serialización XML es que no tiene ninguna restricción en las aplicaciones que desarrolla, con tal
de que la secuencia XML que se genera cumpla con un esquema determinado. Imagine un esquema que se utiliza
para describir los libros. Representa un título, autor, publicador y un elemento de número ISBN. Puede desarrollar
una aplicación que procesa los datos XML como usted desee, por ejemplo, como una orden de libro o como un
inventario de libros. En cualquier caso, el único requisito es que la secuencia XML cumple con el esquema del
lenguaje de definición de esquemas XML (XSD) especificado.

Consideraciones de la serialización XML


Al usar la clase XmlSerializer , debe tener en cuenta lo siguiente:
La herramienta Sgen.exe está diseñada expresamente para generar los ensamblados de serialización para
rendimiento óptimo.
Los datos serializados contienen solo los datos mismos y la estructura de sus clases. La identidad del tipo y
la información del ensamble no están incluidas.
Solo se pueden serializar los campos y las propiedades públicas. Las propiedades deben ser de acceso
público (métodos get y set). Si debe serializar datos no públicos, use la clase DataContractSerializer en
lugar de la serialización XML.
Una clase debe tener un constructor sin parámetros que se vaya a serializar por medio de XmlSerializer .
Los métodos no pueden serializarse.
XmlSerializer puede procesar clases que implementan de manera diferente IEnumerable o ICollection
si cumplen ciertos requisitos, como se indica a continuación.
Una clase que implementa IEnumerable debe implementar un método Add público que requiere un solo
parámetro. El parámetro del método Add debe ser coherente (polimórfico) con el tipo devuelto por la
propiedad IEnumerator.Current devuelta por el método GetEnumerator .
Una clase que implementa ICollection además de IEnumerable (como CollectionBase ) debe tener una
propiedad indexada pública Item (un indexador en C#) que toma un número entero y debe tener una
propiedad pública Count de tipo integer . El parámetro pasado al método Add debe ser del mismo tipo
que aquel que devuelve la propiedad Item , o una de las bases de dicho tipo.
Para las clases que implementan ICollection , los valores que se van a serializar se recuperan de la
propiedad Item indexada, en lugar de mediante una llamada a GetEnumerator . Además, no se serializan
los campos públicos y propiedades, con la excepción de los campos públicos que devuelven otra clase de
colección (uno que implemente ICollection ). Para ver un ejemplo, vea Examples of XML Serialization
(Ejemplos de serialización XML).

Asignación de tipo de datos XSD


En el documento de W3C titulado Esquema XML parte 2: Tipos de datos se especifican los tipos de datos simples
que se pueden usar en un esquema de lenguaje de definición de esquema XML (XSD). Para muchos de estos (por
ejemplo, int y decimal ), hay un tipo de datos correspondiente en .NET Framework. Pero algunos tipos de datos
XML no tienen un tipo de datos correspondiente en .NET Framework (por ejemplo, el tipo de datos NMTOKEN ).
En casos como este, si usa la herramienta de definición de esquema XML (Xsd.exe) para generar clases a partir de
un esquema, se aplica un atributo adecuado a un miembro de cadena de tipo y su propiedad DataType se
establee en el nombre del tipo de datos XML. Por ejemplo, si un esquema contiene un elemento denominado
"MyToken" con el tipo de datos XML NMTOKEN , la clase generada podría contener un miembro, como se
muestra en el ejemplo siguiente.

<XmlElement(DataType:="NMTOKEN")> _
Public MyToken As String

[XmlElement(DataType = "NMTOKEN")]
public string MyToken;

De igual forma, si está creando una clase que debe cumplir un esquema XML (XSD) concreto, debe aplicar el
atributo adecuado y establecer su propiedad DataType en el nombre del tipo de datos XML deseado.
Para obtener una lista completa de asignaciones de tipo, vea la propiedad DataType para cualquiera de las clases
de atributos siguientes:
SoapAttributeAttribute
SoapElementAttribute
XmlArrayItemAttribute
XmlAttributeAttribute
XmlElementAttribute
XmlRootAttribute

Vea también
XmlSerializer
DataContractSerializer
FileStream
Serialización SOAP y XML
Serialización binaria
Serialización
XmlSerializer
Ejemplos de serialización XML
Cómo: Serialización de un objeto
Cómo: Deserialización de un objeto
Ejemplos de serialización XML
16/09/2020 • 18 minutes to read • Edit Online

La serialización XML puede tomar más de un formulario, del simple al complejo. Por ejemplo, puede serializar una
clase que simplemente está compuesta de campos públicos y propiedades, como se muestra en Introducción a la
serialización XML. Los ejemplos de código siguientes resuelven varios escenarios avanzados, incluso cómo utilizar
la serialización XML para generar una secuencia XML que cumple con un documento de esquema XML concreto
(XSD).

Serializar un conjunto de datos


Además de serializar una instancia de una clase pública, una instancia de DataSet se puede serializar también,
como se muestra en el ejemplo de código siguiente.

Private Sub SerializeDataSet(filename As String)


Dim ser As XmlSerializer = new XmlSerializer(GetType(DataSet))
' Creates a DataSet; adds a table, column, and ten rows.
Dim ds As DataSet = new DataSet("myDataSet")
Dim t As DataTable = new DataTable("table1")
Dim c As DataColumn = new DataColumn("thing")
t.Columns.Add(c)
ds.Tables.Add(t)
Dim r As DataRow
Dim i As Integer
for i = 0 to 10
r = t.NewRow()
r(0) = "Thing " & i
t.Rows.Add(r)
Next
Dim writer As TextWriter = new StreamWriter(filename)
ser.Serialize(writer, ds)
writer.Close()
End Sub

private void SerializeDataSet(string filename){


XmlSerializer ser = new XmlSerializer(typeof(DataSet));

// Creates a DataSet; adds a table, column, and ten rows.


DataSet ds = new DataSet("myDataSet");
DataTable t = new DataTable("table1");
DataColumn c = new DataColumn("thing");
t.Columns.Add(c);
ds.Tables.Add(t);
DataRow r;
for(int i = 0; i<10;i++){
r = t.NewRow();
r[0] = "Thing " + i;
t.Rows.Add(r);
}
TextWriter writer = new StreamWriter(filename);
ser.Serialize(writer, ds);
writer.Close();
}

Serializar un XmlElement y XMLNode


También puede serializar instancias de XmlElement o XmlNode, como se muestra en el ejemplo de código
siguiente.

private Sub SerializeElement(filename As String)


Dim ser As XmlSerializer = new XmlSerializer(GetType(XmlElement))
Dim myElement As XmlElement = _
new XmlDocument().CreateElement("MyElement", "ns")
myElement.InnerText = "Hello World"
Dim writer As TextWriter = new StreamWriter(filename)
ser.Serialize(writer, myElement)
writer.Close()
End Sub

Private Sub SerializeNode(filename As String)


Dim ser As XmlSerializer = _
new XmlSerializer(GetType(XmlNode))
Dim myNode As XmlNode = new XmlDocument(). _
CreateNode(XmlNodeType.Element, "MyNode", "ns")
myNode.InnerText = "Hello Node"
Dim writer As TextWriter = new StreamWriter(filename)
ser.Serialize(writer, myNode)
writer.Close()
End Sub

private void SerializeElement(string filename){


XmlSerializer ser = new XmlSerializer(typeof(XmlElement));
XmlElement myElement=
new XmlDocument().CreateElement("MyElement", "ns");
myElement.InnerText = "Hello World";
TextWriter writer = new StreamWriter(filename);
ser.Serialize(writer, myElement);
writer.Close();
}

private void SerializeNode(string filename){


XmlSerializer ser = new XmlSerializer(typeof(XmlNode));
XmlNode myNode= new XmlDocument().
CreateNode(XmlNodeType.Element, "MyNode", "ns");
myNode.InnerText = "Hello Node";
TextWriter writer = new StreamWriter(filename);
ser.Serialize(writer, myNode);
writer.Close();
}

Serializar una clase que contiene un campo que devuelve un objeto


complejo
Si una propiedad o un campo devuelve un objeto complejo como, por ejemplo, una matriz o una instancia de
clase, XmlSerializer lo convierte a un elemento anidado en el documento XML principal. Por ejemplo, la primera
clase del ejemplo del código siguiente devuelve una instancia de la segunda clase.

Public Class PurchaseOrder


Public MyAddress As Address
End Class

Public Class Address


Public FirstName As String
End Class
public class PurchaseOrder
{
public Address MyAddress;
}
public class Address
{
public string FirstName;
}

El resultado XML serializado podría parecerse a lo siguiente.

<PurchaseOrder>
<MyAddress>
<FirstName>George</FirstName>
</MyAddress>
</PurchaseOrder>

Serializando una matriz de objetos


También puede serializar un campo que devuelve una matriz de objetos, como se muestra en el ejemplo de
código siguiente.

Public Class PurchaseOrder


public ItemsOrders () As Item
End Class

Public Class Item


Public ItemID As String
Public ItemPrice As decimal
End Class

public class PurchaseOrder


{
public Item [] ItemsOrders;
}

public class Item


{
public string ItemID;
public decimal ItemPrice;
}

La instancia de clase serializada se podría parecerse a lo siguiente, si se ordenan dos elementos.

<PurchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ItemsOrders>
<Item>
<ItemID>aaa111</ItemID>
<ItemPrice>34.22</ItemPrice>
</Item>
<Item>
<ItemID>bbb222</ItemID>
<ItemPrice>2.89</ItemPrice>
</Item>
</ItemsOrders>
</PurchaseOrder>
Serializar una clase que implementa la interfaz ICollection
Puede crear sus propias clases de colección implementando la interfaz ICollection y utilizar XmlSerializer para
serializar instancias de estas clases. Tenga en cuenta que cuando una clase implementa la interfaz ICollection, se
serializa solo la colección contenida por la clase. No se serializará cualquier propiedad pública o campo
agregados a la clase. La clase debe incluir un método Add y una propiedad Item (indexador de C#) que se va a
serializar.

Imports System.Collections
Imports System.IO
Imports System.Xml.Serialization

Public Class Test


Shared Sub Main()
Dim t As Test= new Test()
t.SerializeCollection("coll.xml")
End Sub

Private Sub SerializeCollection(filename As String)


Dim Emps As Employees = new Employees()
' Note that only the collection is serialized -- not the
' CollectionName or any other public property of the class.
Emps.CollectionName = "Employees"
Dim John100 As Employee = new Employee("John", "100xxx")
Emps.Add(John100)
Dim x As XmlSerializer = new XmlSerializer(GetType(Employees))
Dim writer As TextWriter = new StreamWriter(filename)
x.Serialize(writer, Emps)
writer.Close()
End Sub
End Class

Public Class Employees


Implements ICollection
Public CollectionName As String
Private empArray As ArrayList = new ArrayList()

Public ReadOnly Default Overloads _


Property Item(index As Integer) As Employee
get
return CType (empArray(index), Employee)
End Get
End Property

Public Sub CopyTo(a As Array, index As Integer) _


Implements ICollection.CopyTo
empArray.CopyTo(a, index)
End Sub

Public ReadOnly Property Count () As integer Implements _


ICollection.Count
get
Count = empArray.Count
End Get

End Property

Public ReadOnly Property SyncRoot ()As Object _


Implements ICollection.SyncRoot
get
return me
End Get
End Property

Public ReadOnly Property IsSynchronized () As Boolean _


Implements ICollection.IsSynchronized
get
return false
End Get
End Property

Public Function GetEnumerator() As IEnumerator _


Implements IEnumerable.GetEnumerator

return empArray.GetEnumerator()
End Function

Public Function Add(newEmployee As Employee) As Integer


empArray.Add(newEmployee)
return empArray.Count
End Function
End Class

Public Class Employee


Public EmpName As String
Public EmpID As String

Public Sub New ()


End Sub

Public Sub New (newName As String , newID As String )


EmpName = newName
EmpID = newID
End Sub
End Class
using System;
using System.Collections;
using System.IO;
using System.Xml.Serialization;

public class Test {


static void Main(){
Test t = new Test();
t.SerializeCollection("coll.xml");
}

private void SerializeCollection(string filename){


Employees Emps = new Employees();
// Note that only the collection is serialized -- not the
// CollectionName or any other public property of the class.
Emps.CollectionName = "Employees";
Employee John100 = new Employee("John", "100xxx");
Emps.Add(John100);
XmlSerializer x = new XmlSerializer(typeof(Employees));
TextWriter writer = new StreamWriter(filename);
x.Serialize(writer, Emps);
}
}
public class Employees:ICollection {
public string CollectionName;
private ArrayList empArray = new ArrayList();

public Employee this[int index]{


get{return (Employee) empArray[index];}
}

public void CopyTo(Array a, int index){


empArray.CopyTo(a, index);
}
public int Count{
get{return empArray.Count;}
}
public object SyncRoot{
get{return this;}
}
public bool IsSynchronized{
get{return false;}
}
public IEnumerator GetEnumerator(){
return empArray.GetEnumerator();
}

public void Add(Employee newEmployee){


empArray.Add(newEmployee);
}
}

public class Employee {


public string EmpName;
public string EmpID;
public Employee(){}
public Employee(string empName, string empID){
EmpName = empName;
EmpID = empID;
}
}

Ejemplo del pedido de compra


Puede cortar y pegar el código de ejemplo siguiente en un archivo de texto cambiado el nombre con una
extensión de nombre de archivo del .cs o .vb. Utilice el C# o compilador de Visual Basic para compilar el archivo. A
continuación, ejecútelo utilizando el nombre de la aplicación ejecutable.
Este ejemplo utiliza un escenario simple para mostrar cómo una instancia de un objeto se crea y serializa en una
secuencia del archivo utilizando el método Serialize. La secuencia XML está guardada en un archivo y el mismo
archivo se lee a continuación atrás y se reconstruye en una copia del objeto original utilizando el método
Deserialize.
Una clase en este ejemplo, denominada PurchaseOrder se serializa y, a continuación, se deserializa. Una segunda
clase denominada Address también está incluido porque el campo público denominado ShipTo debe estar
establecido en Address . De igual forma, una clase OrderedItem está incluida porque una matriz de los objetos
OrderedItem debe estar establecida en el campo OrderedItems . Finalmente, una clase denominada Test
contiene el código que serializa y deserializa las clases.
El método CreatePO crea los objetos de clase PurchaseOrder , Address y OrderedItem y establece los valores de
campo públicos. El método también construye una instancia de la clase XmlSerializer que se utiliza para serializar
y deserializar PurchaseOrder . Observe que el código pasa el tipo de la clase que se serializará para el constructor.
El código también crea FileStream que se usa para escribir la secuencia XML en un documento XML.
El método ReadPo es un poco más fácil. Apenas crea los objetos para deserializar y hace una lectura de salida de
sus valores. Como sucede con el método CreatePo , primero debe crear un objeto XmlSerializer y pasar al
constructor el tipo de la clase que se va a deserializar. Asimismo, se exige FileStream que lea el documento XML.
Para deserializar los objetos, llame al método Deserialize con FileStream como un argumento. El objeto
deserializado se debe convertir a una variable de objeto de tipo PurchaseOrder . El código lee a continuación los
valores del PurchaseOrder deserializado. Observe que también puede leer el archivo PO.xml que se crea para ver
el XML real generado.

Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports Microsoft.VisualBasic

' The XmlRoot attribute allows you to set an alternate name


' (PurchaseOrder) for the XML element and its namespace. By
' default, the XmlSerializer uses the class name. The attribute
' also allows you to set the XML namespace for the element. Lastly,
' the attribute sets the IsNullable property, which specifies whether
' the xsi:null attribute appears if the class instance is set to
' a null reference.
<XmlRoot("PurchaseOrder", _
Namespace := "http://www.cpandl.com", IsNullable := False)> _
Public Class PurchaseOrder
Public ShipTo As Address
Public OrderDate As String
' The XmlArrayAttribute changes the XML element name
' from the default of "OrderedItems" to "Items".
<XmlArray("Items")> _
Public OrderedItems() As OrderedItem
Public SubTotal As Decimal
Public ShipCost As Decimal
Public TotalCost As Decimal
End Class

Public Class Address


' The XmlAttribute attribute instructs the XmlSerializer to serialize the
' Name field as an XML attribute instead of an XML element (XML element is
' the default behavior).
<XmlAttribute()> _
Public Name As String
Public Line1 As String

' Setting the IsNullable property to false instructs the


' Setting the IsNullable property to false instructs the
' XmlSerializer that the XML attribute will not appear if
' the City field is set to a null reference.
<XmlElement(IsNullable := False)> _
Public City As String
Public State As String
Public Zip As String
End Class

Public Class OrderedItem


Public ItemName As String
Public Description As String
Public UnitPrice As Decimal
Public Quantity As Integer
Public LineTotal As Decimal

' Calculate is a custom method that calculates the price per item
' and stores the value in a field.
Public Sub Calculate()
LineTotal = UnitPrice * Quantity
End Sub
End Class

Public Class Test


Public Shared Sub Main()
' Read and write purchase orders.
Dim t As New Test()
t.CreatePO("po.xml")
t.ReadPO("po.xml")
End Sub

Private Sub CreatePO(filename As String)


' Creates an instance of the XmlSerializer class;
' specifies the type of object to serialize.
Dim serializer As New XmlSerializer(GetType(PurchaseOrder))
Dim writer As New StreamWriter(filename)
Dim po As New PurchaseOrder()

' Creates an address to ship and bill to.


Dim billAddress As New Address()
billAddress.Name = "Teresa Atkinson"
billAddress.Line1 = "1 Main St."
billAddress.City = "AnyTown"
billAddress.State = "WA"
billAddress.Zip = "00000"
' Set ShipTo and BillTo to the same addressee.
po.ShipTo = billAddress
po.OrderDate = System.DateTime.Now.ToLongDateString()

' Creates an OrderedItem.


Dim i1 As New OrderedItem()
i1.ItemName = "Widget S"
i1.Description = "Small widget"
i1.UnitPrice = CDec(5.23)
i1.Quantity = 3
i1.Calculate()

' Inserts the item into the array.


Dim items(0) As OrderedItem
items(0) = i1
po.OrderedItems = items
' Calculates the total cost.
Dim subTotal As New Decimal()
Dim oi As OrderedItem
For Each oi In items
subTotal += oi.LineTotal
Next oi
po.SubTotal = subTotal
po.ShipCost = CDec(12.51)
po.TotalCost = po.SubTotal + po.ShipCost
po.TotalCost = po.SubTotal + po.ShipCost
' Serializes the purchase order, and close the TextWriter.
serializer.Serialize(writer, po)
writer.Close()
End Sub

Protected Sub ReadPO(filename As String)


' Creates an instance of the XmlSerializer class;
' specifies the type of object to be deserialized.
Dim serializer As New XmlSerializer(GetType(PurchaseOrder))
' If the XML document has been altered with unknown
' nodes or attributes, handles them with the
' UnknownNode and UnknownAttribute events.
AddHandler serializer.UnknownNode, AddressOf serializer_UnknownNode
AddHandler serializer.UnknownAttribute, AddressOf _
serializer_UnknownAttribute

' A FileStream is needed to read the XML document.


Dim fs As New FileStream(filename, FileMode.Open)
' Declare an object variable of the type to be deserialized.
Dim po As PurchaseOrder
' Uses the Deserialize method to restore the object's state
' with data from the XML document.
po = CType(serializer.Deserialize(fs), PurchaseOrder)
' Reads the order date.
Console.WriteLine(("OrderDate: " & po.OrderDate))

' Reads the shipping address.


Dim shipTo As Address = po.ShipTo
ReadAddress(shipTo, "Ship To:")
' Reads the list of ordered items.
Dim items As OrderedItem() = po.OrderedItems
Console.WriteLine("Items to be shipped:")
Dim oi As OrderedItem
For Each oi In items
Console.WriteLine((ControlChars.Tab & oi.ItemName & _
ControlChars.Tab & _
oi.Description & ControlChars.Tab & oi.UnitPrice & _
ControlChars.Tab & _
oi.Quantity & ControlChars.Tab & oi.LineTotal))
Next oi
' Reads the subtotal, shipping cost, and total cost.
Console.WriteLine((ControlChars.Cr & New String _
(ControlChars.Tab, 5) & _
" Subtotal" & ControlChars.Tab & po.SubTotal & ControlChars.Cr & _
New String(ControlChars.Tab, 5) & " Shipping" & ControlChars.Tab & _
po.ShipCost & ControlChars.Cr & New String(ControlChars.Tab, 5) & _
" Total" & New String(ControlChars.Tab, 2) & po.TotalCost))
End Sub

Protected Sub ReadAddress(a As Address, label As String)


' Reads the fields of the Address.
Console.WriteLine(label)
Console.Write((ControlChars.Tab & a.Name & ControlChars.Cr & _
ControlChars.Tab & a.Line1 & ControlChars.Cr & ControlChars.Tab & _
a.City & ControlChars.Tab & a.State & ControlChars.Cr & _
ControlChars.Tab & a.Zip & ControlChars.Cr))
End Sub

Protected Sub serializer_UnknownNode(sender As Object, e As _


XmlNodeEventArgs)
Console.WriteLine(("Unknown Node:" & e.Name & _
ControlChars.Tab & e.Text))
End Sub

Protected Sub serializer_UnknownAttribute(sender As Object, _


e As XmlAttributeEventArgs)
Dim attr As System.Xml.XmlAttribute = e.Attr
Console.WriteLine(("Unknown attribute " & attr.Name & "='" & _
attr.Value & "'"))
attr.Value & "'"))
End Sub 'serializer_UnknownAttribute
End Class 'Test

using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

// The XmlRoot attribute allows you to set an alternate name


// (PurchaseOrder) for the XML element and its namespace. By
// default, the XmlSerializer uses the class name. The attribute
// also allows you to set the XML namespace for the element. Lastly,
// the attribute sets the IsNullable property, which specifies whether
// the xsi:null attribute appears if the class instance is set to
// a null reference.
[XmlRoot("PurchaseOrder", Namespace="http://www.cpandl.com",
IsNullable = false)]
public class PurchaseOrder
{
public Address ShipTo;
public string OrderDate;
// The XmlArray attribute changes the XML element name
// from the default of "OrderedItems" to "Items".
[XmlArray("Items")]
public OrderedItem[] OrderedItems;
public decimal SubTotal;
public decimal ShipCost;
public decimal TotalCost;
}

public class Address


{
// The XmlAttribute attribute instructs the XmlSerializer to serialize the
// Name field as an XML attribute instead of an XML element (XML element is
// the default behavior).
[XmlAttribute]
public string Name;
public string Line1;

// Setting the IsNullable property to false instructs the


// XmlSerializer that the XML attribute will not appear if
// the City field is set to a null reference.
[XmlElement(IsNullable = false)]
public string City;
public string State;
public string Zip;
}

public class OrderedItem


{
public string ItemName;
public string Description;
public decimal UnitPrice;
public int Quantity;
public decimal LineTotal;

// Calculate is a custom method that calculates the price per item


// and stores the value in a field.
public void Calculate()
{
LineTotal = UnitPrice * Quantity;
}
}

public class Test


{
public static void Main()
public static void Main()
{
// Read and write purchase orders.
Test t = new Test();
t.CreatePO("po.xml");
t.ReadPO("po.xml");
}

private void CreatePO(string filename)


{
// Creates an instance of the XmlSerializer class;
// specifies the type of object to serialize.
XmlSerializer serializer =
new XmlSerializer(typeof(PurchaseOrder));
TextWriter writer = new StreamWriter(filename);
PurchaseOrder po=new PurchaseOrder();

// Creates an address to ship and bill to.


Address billAddress = new Address();
billAddress.Name = "Teresa Atkinson";
billAddress.Line1 = "1 Main St.";
billAddress.City = "AnyTown";
billAddress.State = "WA";
billAddress.Zip = "00000";
// Sets ShipTo and BillTo to the same addressee.
po.ShipTo = billAddress;
po.OrderDate = System.DateTime.Now.ToLongDateString();

// Creates an OrderedItem.
OrderedItem i1 = new OrderedItem();
i1.ItemName = "Widget S";
i1.Description = "Small widget";
i1.UnitPrice = (decimal) 5.23;
i1.Quantity = 3;
i1.Calculate();

// Inserts the item into the array.


OrderedItem [] items = {i1};
po.OrderedItems = items;
// Calculate the total cost.
decimal subTotal = new decimal();
foreach(OrderedItem oi in items)
{
subTotal += oi.LineTotal;
}
po.SubTotal = subTotal;
po.ShipCost = (decimal) 12.51;
po.TotalCost = po.SubTotal + po.ShipCost;
// Serializes the purchase order, and closes the TextWriter.
serializer.Serialize(writer, po);
writer.Close();
}

protected void ReadPO(string filename)


{
// Creates an instance of the XmlSerializer class;
// specifies the type of object to be deserialized.
XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder));
// If the XML document has been altered with unknown
// nodes or attributes, handles them with the
// UnknownNode and UnknownAttribute events.
serializer.UnknownNode+= new
XmlNodeEventHandler(serializer_UnknownNode);
serializer.UnknownAttribute+= new
XmlAttributeEventHandler(serializer_UnknownAttribute);

// A FileStream is needed to read the XML document.


FileStream fs = new FileStream(filename, FileMode.Open);
// Declares an object variable of the type to be deserialized.
PurchaseOrder po;
PurchaseOrder po;
// Uses the Deserialize method to restore the object's state
// with data from the XML document. */
po = (PurchaseOrder) serializer.Deserialize(fs);
// Reads the order date.
Console.WriteLine ("OrderDate: " + po.OrderDate);

// Reads the shipping address.


Address shipTo = po.ShipTo;
ReadAddress(shipTo, "Ship To:");
// Reads the list of ordered items.
OrderedItem [] items = po.OrderedItems;
Console.WriteLine("Items to be shipped:");
foreach(OrderedItem oi in items)
{
Console.WriteLine("\t"+
oi.ItemName + "\t" +
oi.Description + "\t" +
oi.UnitPrice + "\t" +
oi.Quantity + "\t" +
oi.LineTotal);
}
// Reads the subtotal, shipping cost, and total cost.
Console.WriteLine(
"\n\t\t\t\t\t Subtotal\t" + po.SubTotal +
"\n\t\t\t\t\t Shipping\t" + po.ShipCost +
"\n\t\t\t\t\t Total\t\t" + po.TotalCost
);
}

protected void ReadAddress(Address a, string label)


{
// Reads the fields of the Address.
Console.WriteLine(label);
Console.Write("\t"+
a.Name +"\n\t" +
a.Line1 +"\n\t" +
a.City +"\t" +
a.State +"\n\t" +
a.Zip +"\n");
}

protected void serializer_UnknownNode


(object sender, XmlNodeEventArgs e)
{
Console.WriteLine("Unknown Node:" + e.Name + "\t" + e.Text);
}

protected void serializer_UnknownAttribute


(object sender, XmlAttributeEventArgs e)
{
System.Xml.XmlAttribute attr = e.Attr;
Console.WriteLine("Unknown attribute " +
attr.Name + "='" + attr.Value + "'");
}
}

El XML generado puede parecerse a lo siguiente.


<?xml version="1.0" encoding="utf-8"?>
<PurchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.cpandl.com">
<ShipTo Name="Teresa Atkinson">
<Line1>1 Main St.</Line1>
<City>AnyTown</City>
<State>WA</State>
<Zip>00000</Zip>
</ShipTo>
<OrderDate>Wednesday, June 27, 2001</OrderDate>
<Items>
<OrderedItem>
<ItemName>Widget S</ItemName>
<Description>Small widget</Description>
<UnitPrice>5.23</UnitPrice>
<Quantity>3</Quantity>
<LineTotal>15.69</LineTotal>
</OrderedItem>
</Items>
<SubTotal>15.69</SubTotal>
<ShipCost>12.51</ShipCost>
<TotalCost>28.2</TotalCost>
</PurchaseOrder>

Vea también
Introducción a la serialización XML
Controlar la serialización XML mediante atributos
Atributos que controlan la serialización XML
XmlSerializer (clase)
Cómo: Serialización de un objeto
Cómo: Deserialización de un objeto
Herramienta de definición de esquema XML y
serialización XML
16/09/2020 • 2 minutes to read • Edit Online

La herramienta de definición de esquema XML (Herramienta de definición de esquema XML (Xsd.exe)) se instala
junto con las herramientas de .NET Framework como parte del kit de desarrollo de software (SDK) de Windows®.
La herramienta está diseñada para cumplir principalmente dos propósitos:
Para crear archivos de clase C# o para crearlos de clase Visual Basic y que así cumplan con un lenguaje de
definición de esquema XML (XSD) concreto. La herramienta toma un Esquema XML como un argumento y
genera un archivo que contiene varias clases que cuando se serializa con XmlSerializer, cumpla al esquema.
Para más información sobre cómo usar la herramienta para generar clases que cumplan con un esquema
en concreto, vea Procedimiento para usar la herramienta de definición de esquema XML para generar clases
y documentos de esquema XML.
Generar un documento de esquema XML de un archivo .dll o .exe. Para ver el esquema de un conjunto de
archivos que ha creado, o uno que se ha modificado con atributos, pase la DLL o el EXE como un argumento
de la herramienta para generar el esquema XML. Para más información sobre cómo usar la herramienta
para generar un documento de esquema XML a partir de un conjunto de clases, vea Procedimiento para
usar la herramienta de definición de esquema XML para generar clases y documentos de esquema XML.
Para más información sobre cómo usar la herramienta, vea Herramienta de definición de esquema XML (Xsd.exe).

Vea también
DataSet
Introducción a la serialización XML
Herramienta de definición de esquema XML (Xsd.exe)
XmlSerializer
Cómo: Serialización de un objeto
Cómo: Deserializar un objeto
Cómo: Usar la herramienta de definición de esquema XML para generar clases y documentos de esquema XML
Compatibilidad con enlaces del esquema XML
Controlar la serialización XML mediante atributos
16/09/2020 • 11 minutes to read • Edit Online

Los atributos se pueden utilizar para controlar la serialización XML de un objeto o crear una secuencia XML
alternativa a partir del mismo conjunto de clases. Para obtener más información sobre la creación de una
secuencia XML alternativa vea Procedimiento para especificar un nombre de elemento alternativo para una
secuencia XML.

NOTE
Si el código XML generado se ajusta a la sección 5 del documento Protocolo simple de acceso a objetos (SOAP) 1.1, del
Consorcio WWC (W3C), use los atributos enumerados en Atributos que controlan la serialización SOAP codificada.

La clase o nombre de miembro determina, de forma predeterminada, un nombre del elemento XML. En una clase
simple denominada Book , un campo denominado ISBN generará una etiqueta de elemento XML <ISBN> como
se muestra en el ejemplo siguiente.

Public Class Book


Public ISBN As String
End Class
' When an instance of the Book class is serialized, it might
' produce this XML:
' <ISBN>1234567890</ISBN>.

public class Book


{
public string ISBN;
}
// When an instance of the Book class is serialized, it might
// produce this XML:
// <ISBN>1234567890</ISBN>.

Se puede cambiar este comportamiento predeterminado si desea dar un nuevo nombre al elemento. El código
siguiente muestra cómo un atributo habilita esto estableciendo la propiedad ElementName de
XmlElementAttribute.

Public Class TaxRates


< XmlElement(ElementName = "TaxRate")> _
Public ReturnTaxRate As Decimal
End Class

public class TaxRates {


[XmlElement(ElementName = "TaxRate")]
public decimal ReturnTaxRate;
}

Para obtener más información sobre atributos, vea Atributos. Para obtener una lista de atributos que controlan la
serialización XML, vea Atributos que controlan la serialización XML.
Controlar la serialización de la matriz
Los atributos XmlArrayAttribute y XmlArrayItemAttribute están diseñados para controlar la serialización de las
matrices. Con estos atributos, puede controlar el nombre de elemento, el espacio de nombres y el tipo de datos
del esquema XML (XSD) (como se define en el documento de World Wide Web Consortium [www.w3.org]
titulado "Esquema XML parte 2: tipos de datos"). También puede especificar los tipos que pueden estar incluidos
en una matriz.
XmlArrayAttribute determinará las propiedades del elemento envolvente XML que resulta cuando se serializa una
matriz. Por ejemplo, de forma predeterminada, serializar la matriz siguiente producirá un elemento XML
denominado Employees . El elemento Employees contendrá una serie de elementos denominados según el tipo de
matriz Employee .

Public Class Group


Public Employees() As Employee
End Class
Public Class Employee
Public Name As String
End Class

public class Group {


public Employee[] Employees;
}
public class Employee {
public string Name;
}

Una instancia serializada se podría parecer a lo siguiente.

<Group>
<Employees>
<Employee>
<Name>Haley</Name>
</Employee>
</Employees>
</Group>

Aplicando XmlArrayAttribute, puede cambiar el nombre del elemento XML, como sigue.

Public Class Group


<XmlArray("TeamMembers")> _
Public Employees() As Employee
End Class

public class Group {


[XmlArray("TeamMembers")]
public Employee[] Employees;
}

El XML resultante puede parecerse a lo siguiente.


<Group>
<TeamMembers>
<Employee>
<Name>Haley</Name>
</Employee>
</TeamMembers>
</Group>

XmlArrayItemAttribute, por otro lado, controla cómo se serializan los elementos contenidos en la matriz. Observe
que el atributo se aplica al campo que devuelve la matriz.

Public Class Group


<XmlArrayItem("MemberName")> _
Public Employee() As Employees
End Class

public class Group {


[XmlArrayItem("MemberName")]
public Employee[] Employees;
}

El XML resultante puede parecerse a lo siguiente.

<Group>
<Employees>
<MemberName>Haley</MemberName>
</Employees>
</Group>

Serializar las clases derivadas


Otro uso de XmlArrayItemAttribute es permitir la serialización de clases derivadas. Por ejemplo, otra clase
denominada Manager que deriva de Employee se puede agregar al ejemplo anterior. Si no aplica
XmlArrayItemAttribute, se producirá un error en el código en tiempo de ejecución porque no se reconocerá el
tipo de clase derivada. Para solucionar esto, aplique dos veces el atributo, estableciendo la propiedad Type cada
vez, para cada tipo aceptable (base y derivado).

Public Class Group


<XmlArrayItem(Type:=GetType(Employee)), _
XmlArrayItem(Type:=GetType(Manager))> _
Public Employees() As Employee
End Class
Public Class Employee
Public Name As String
End Class
Public Class Manager
Inherits Employee
Public Level As Integer
End Class
public class Group {
[XmlArrayItem(Type = typeof(Employee)),
XmlArrayItem(Type = typeof(Manager))]
public Employee[] Employees;
}
public class Employee {
public string Name;
}
public class Manager:Employee {
public int Level;
}

Una instancia serializada se podría parecer a lo siguiente.

<Group>
<Employees>
<Employee>
<Name>Haley</Name>
</Employee>
<Employee xsi:type = "Manager">
<Name>Ann</Name>
<Level>3</Level>
</Employee>
</Employees>
</Group>

Serializar una matriz como una secuencia de elementos


También puede serializar una matriz como una secuencia plana de elementos XML aplicando
XmlElementAttribute al campo que devuelve la matriz como sigue.

Public Class Group


<XmlElement> _
Public Employees() As Employee
End Class

public class Group {


[XmlElement]
public Employee[] Employees;
}

Una instancia serializada se podría parecer a lo siguiente.

<Group>
<Employees>
<Name>Haley</Name>
</Employees>
<Employees>
<Name>Noriko</Name>
</Employees>
<Employees>
<Name>Marco</Name>
</Employees>
</Group>

Otra manera de diferenciar las dos secuencias XML es utilizar la herramienta de definición de esquemas XML para
generar los archivos de documento de esquema XML (XSD) a partir del código compilado. (Para obtener más
información sobre el uso de la herramienta, vea La herramienta de definición de esquema XML y serialización
XML). Cuando no se aplica ningún atributo al campo, el esquema describe el elemento de la manera siguiente.

<xs:element minOccurs="0" maxOccurs ="1" name="Employees" type="ArrayOfEmployee" />

Cuando XmlElementAttribute se aplica al campo, el esquema resultante describe el elemento como sigue.

<xs:element minOccurs="0" maxOccurs="unbounded" name="Employees" type="Employee" />

Serializar un ArrayList
La clase ArrayList puede contener una colección de objetos diversos. Además puede utilizar ArrayList tantas veces
como utiliza la matriz. En lugar de crear un campo que devuelve una matriz de objetos escritos, sin embargo,
puede crear un campo que devuelve un ArrayListúnico. Sin embargo, al igual que con las matrices, debe informar
aXmlSerializer de los tipos de objetos ArrayList que contiene. Para lograr esto, asigne varias instancias de
XmlElementAttribute al campo, como se muestra en el ejemplo siguiente.

Public Class Group


<XmlElement(Type:=GetType(Employee)), _
XmlElement(Type:=GetType(Manager))> _
Public Info As ArrayList
End Class

public class Group {


[XmlElement(Type = typeof(Employee)),
XmlElement(Type = typeof(Manager))]
public ArrayList Info;
}

Controle la serialización de clases utilizando XmlRootAttribute y


XmlTypeAttribute
Existen dos atributos que pueden aplicarse a la clase (y solamente una clase): XmlRootAttribute y
XmlTypeAttribute. Estos atributos son muy similares. XmlRootAttribute se puede aplicar a solo una clase: la clase
que, cuando se serializa, representa el elemento de apertura y cierre del documento XML, en otras palabras, el
elemento raíz. XmlTypeAttribute, por otro lado, se puede aplicar a cualquier clase, incluso a la clase raíz.
Por ejemplo, en los ejemplos anteriores, la clase Group es la clase raíz y todos sus campos públicos y
propiedades se vuelven elementos XML situados en el documento XML. Además, solo puede haber una clase de
raíz. Aplicando XmlRootAttribute, puede controlar la secuencia XML generada por XmlSerializer. Por ejemplo,
puede cambiar el nombre de elemento y espacio de nombres.
XmlTypeAttribute le permite controlar el esquema del XML generado. Esta función es útil si se necesita publicar el
esquema a través de un servicio Web XML. En el siguiente ejemplo, se aplica XmlTypeAttribute y
XmlRootAttribute a la misma clase.

<XmlRoot("NewGroupName"), _
XmlType("NewTypeName")> _
Public Class Group
Public Employees() As Employee
End Class
[XmlRoot("NewGroupName")]
[XmlType("NewTypeName")]
public class Group {
public Employee[] Employees;
}

Si esta clase está compilada, y la herramienta de definición de esquemas XML se utiliza para generar su esquema,
encontraría el XML siguiente que describe Group .

<xs:element name="NewGroupName" type="NewTypeName" />

En contraste, si fuera serializar una instancia de la clase, solo NewGroupName se buscarían en el documento XML.

<NewGroupName>
. . .
</NewGroupName>

Evitar la serialización con XmlIgnoreAttribute


Podrían darse situaciones en las que una propiedad pública o el campo no necesite ser serializado. Por ejemplo,
un campo o propiedad se puede utilizar para contener los metadatos. En casos como éste, aplique
XmlIgnoreAttribute al campo o la propiedad y XmlSerializer omitirán sobre él.

Vea también
Atributos que controlan la serialización XML
Atributos que controlan la serialización SOAP codificada
Introducción a la serialización XML
Ejemplos de serialización XML
Cómo: Especificar un nombre de elemento alternativo para una secuencia XML
Cómo: Serialización de un objeto
Cómo: Deserialización de un objeto
Atributos que controlan la serialización XML
16/09/2020 • 4 minutes to read • Edit Online

Se pueden aplicar atributos a clases y a miembros de clase en la siguiente tabla para controlar la manera en que
XmlSerializer serializa o deserializa una instancia de la clase. Para entender cómo controlan estos atributos la
serialización XML, vea Controlar la serialización XML mediante atributos.
Estos atributos también se pueden utilizar para controlar los mensajes SOAP de estilo literales generados por un
servicio Web XML. Para más información sobre la aplicación de estos atributos a un método de servicios web
XML, vea Serialización XML con servicios web XML.
Para obtener más información sobre atributos, vea Atributos.

AT RIB UTO SE A P L IC A A ESP EC IF IC A

XmlAnyAttributeAttribute El campo público, propiedad, Al deserializar, la matriz estará llena de


parámetro o valor devuelto que objetos XmlAttribute que representan
devuelve una matriz de objetos todos los atributos XML desconocidos
XmlAttribute objects. para el esquema.

XmlAnyElementAttribute El campo público, propiedad, Al deserializar, la matriz estará llena de


parámetro o valor devuelto que objetos XmlElement que representan
devuelve una matriz de objetos todos los atributos XML desconocidos
XmlElement objects. para el esquema

XmlArrayAttribute El campo público, propiedad, Los miembros de la matriz se


parámetro o valor devuelto que generarán como miembros de una
devuelve una matriz de objetos matriz de XML.
complejos.

XmlArrayItemAttribute El campo público, propiedad, Los tipos derivados que se pueden


parámetro o valor devuelto que insertar en una matriz. Normalmente
devuelve una matriz de objetos aplicado junto con un
complejos. XmlArrayAttribute.

XmlAttributeAttribute Campo público, propiedad, parámetro El miembro se serializará como un


o valor devuelto. atributo XML.

XmlChoiceIdentifierAttribute Campo público, propiedad, parámetro El miembro se puede desambiguar


o valor devuelto. adicionalmente utilizando una
enumeración.

XmlElementAttribute Campo público, propiedad, parámetro El campo o propiedad se serializará


o valor devuelto. como un elemento XML.

XmlEnumAttribute Campo público que es un identificador Nombre de elemento del miembro de


de enumeración. una enumeración.

XmlIgnoreAttribute Propiedades públicas y campos. Se debería omitir la propiedad o campo


cuando se serializa la clase
contenedora.
AT RIB UTO SE A P L IC A A ESP EC IF IC A

XmlIncludeAttribute Declaraciones de clase derivada La clase debería estar incluida al


públicas y valores devueltos de generar los esquemas (para ser
métodos públicos para los documentos reconocido cuando se serializa).
de lenguaje de descripción de servicios
Web (WSDL).

XmlRootAttribute Declaraciones de clase públicas. Controla la serialización XML del


destino de atributo como elemento raíz
XML. Utilice el atributo para especificar
el espacio de nombres y nombre de
elemento.

XmlTextAttribute Propiedades públicas y campos. La propiedad o campo se debería


serializar como texto XML.

XmlTypeAttribute Declaraciones de clase públicas. El nombre y espacio de nombres del


tipo XML.

Además de estos atributos, que se encuentran todos en el espacio de nombres System.Xml.Serialization también
se puede aplicar el atributo DefaultValueAttribute a un campo. DefaultValueAttribute establece el valor que se
asignará automáticamente al miembro si no se especifica ningún valor.
Para controlar la serialización SOAP y XML codificada, vea Atributos que controlan la serialización SOAP
codificada.

Vea también
Serialización SOAP y XML
XmlSerializer
Controlar la serialización XML mediante atributos
Cómo: Especificar un nombre de elemento alternativo para una secuencia XML
Cómo: Serialización de un objeto
Cómo: Deserialización de un objeto
Serialización XML con servicios Web XML
16/09/2020 • 8 minutes to read • Edit Online

La serialización XML es el mecanismo de transporte subyacente utilizado en la arquitectura de los servicios Web
XML, realizada por la clase XmlSerializer. Para controlar el XML generado por un servicio web XML, puede aplicar
los atributos de Atributos que controlan la serialización XML y Atributos que controlan la serialización SOAP
codificada a las clases, los valores devueltos, los parámetros y los campos de un archivo usados para crear un
servicio web XML (.asmx). Para más información sobre cómo crear un servicio web XML, vea Servicios web XML
con ASP.NET.

Estilos literales y codificados


Se puede aplicar formato al XML generado por un servicio web XML de dos maneras distintas, estilo literal o
codificado, como se explica en Personalizar el formato de mensajes SOAP. Hay, por consiguiente, dos conjuntos de
atributos que controlan la serialización XML. Los atributos que se mencionan en Atributos que controlan la
serialización XML están diseñados para controlar el XML de estilo literal. Los atributos mencionados en Atributos
que controlan la serialización SOAP codificada controlan el estilo codificado. Aplicando selectivamente estos
atributos, puede entallar una aplicación para devolver uno o ambos estilos. Además, estos atributos se pueden
aplicar (según corresponda) a los valores devueltos y parámetros.
Ejemplo de utilizar ambos estilos
Al estar creando un servicio Web XML, puede utilizar ambos conjuntos de atributos en los métodos. En el ejemplo
de código siguiente, la clase denominada MyService contiene dos métodos de servicios Web XML,
MyLiteralMethod y MyEncodedMethod . Ambos métodos realizan la misma función: devolviendo una instancia de la
clase Order . En la clase Order , los atributos XmlTypeAttribute y SoapTypeAttribute se aplican al campo OrderID
y ambos tienen la propiedad ElementName establecida en valores diferentes.
Para ejecutar el ejemplo, pegue el código en un archivo con una extensión .asmx y coloque el archivo en un
directorio virtual administrado por Internet Information Services (IIS). De un examinador de HTML, como Internet
Explorer, escriba el nombre del equipo, directorio virtual y archivo.
<%@ WebService Language="VB" Class="MyService" %>
Imports System
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Xml.Serialization
Public Class Order
' Both types of attributes can be applied. Depending on which type
' the method used, either one will affect the call.
<SoapElement(ElementName:= "EncodedOrderID"), _
XmlElement(ElementName:= "LiteralOrderID")> _
public OrderID As String
End Class

Public Class MyService


<WebMethod, SoapDocumentMethod> _
public Function MyLiteralMethod() As Order
Dim myOrder As Order = New Order()
return myOrder
End Function
<WebMethod, SoapRpcMethod> _
public Function MyEncodedMethod() As Order
Dim myOrder As Order = New Order()
return myOrder
End Function
End Class

<%@ WebService Language="C#" Class="MyService" %>


using System;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
public class Order {
// Both types of attributes can be applied. Depending on which type
// the method used, either one will affect the call.
[SoapElement(ElementName = "EncodedOrderID")]
[XmlElement(ElementName = "LiteralOrderID")]
public String OrderID;
}
public class MyService {
[WebMethod][SoapDocumentMethod]
public Order MyLiteralMethod(){
Order myOrder = new Order();
return myOrder;
}
[WebMethod][SoapRpcMethod]
public Order MyEncodedMethod(){
Order myOrder = new Order();
return myOrder;
}
}

En el ejemplo de código siguiente se llama a MyLiteralMethod . El nombre de elemento se cambia a


"LiteralOrderID."
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<MyLiteralMethodResponse xmlns="http://tempuri.org/">
<MyLiteralMethodResult>
<LiteralOrderID>string</LiteralOrderID>
</MyLiteralMethodResult>
</MyLiteralMethodResponse>
</soap:Body>
</soap:Envelope>

En el ejemplo de código siguiente se llama a MyEncodedMethod . El nombre de elemento es "EncodedOrderID."

<?xml version="1.0" encoding="utf-8"?>


<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://tempuri.org/" xmlns:types="http://tempuri.org/encodedTypes"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<tns:MyEncodedMethodResponse>
<MyEncodedMethodResult href="#id1" />
</tns:MyEncodedMethodResponse>
<types:Order id="id1" xsi:type="types:Order">
<EncodedOrderID xsi:type="xsd:string">string</EncodedOrderID>
</types:Order>
</soap:Body>
</soap:Envelope>

Aplicar los atributos a los valores de devolución


También puede aplicar los atributos a los valores devueltos para controlar el espacio de nombres, nombre de
elemento, etc. En el ejemplo de código siguiente se aplica el atributo XmlElementAttribute para devolver valores
del método MyLiteralMethod . Hacer esto le permite controlar el espacio de nombres y nombre de elemento.

<WebMethod, SoapDocumentMethod> _
public Function MyLiteralMethod() As _
<XmlElement(Namespace:="http://www.cohowinery.com", _
ElementName:= "BookOrder")> _
Order
Dim myOrder As Order = New Order()
return myOrder
End Function

[return: XmlElement(Namespace = "http://www.cohowinery.com",


ElementName = "BookOrder")]
[WebMethod][SoapDocumentMethod]
public Order MyLiteralMethod(){
Order myOrder = new Order();
return myOrder;
}

Cuando se invoca, el código devuelve XML que se parece a lo siguiente.


<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<MyLiteralMethodResponse xmlns="http://tempuri.org/">
<BookOrder xmlns="http://www.cohowinery.com">
<LiteralOrderID>string</LiteralOrderID>
</BookOrder>
</MyLiteralMethodResponse>
</soap:Body>
</soap:Envelope>

Atributos aplicados a parámetros


También puede aplicar los atributos a los parámetros para especificar el espacio de nombres, nombre de elemento
etc. El ejemplo de código siguiente agrega un parámetro al método MyLiteralMethodResponse y aplica el atributo
XmlAttributeAttribute al parámetro. El nombre de elemento y espacio de nombres están establecidos para el
parámetro.

<WebMethod, SoapDocumentMethod> _
public Function MyLiteralMethod(<XmlElement _
("MyOrderID", Namespace:="http://www.microsoft.com")>ID As String) As _
<XmlElement(Namespace:="http://www.cohowinery.com", _
ElementName:= "BookOrder")> _
Order
Dim myOrder As Order = New Order()
myOrder.OrderID = ID
return myOrder
End Function

[return: XmlElement(Namespace = "http://www.cohowinery.com",


ElementName = "BookOrder")]
[WebMethod][SoapDocumentMethod]
public Order MyLiteralMethod([XmlElement("MyOrderID",
Namespace="http://www.microsoft.com")] string ID){
Order myOrder = new Order();
myOrder.OrderID = ID;
return myOrder;
}

La solicitud SOAP debería tener un aspecto similar al siguiente.

<?xml version="1.0" encoding="utf-8"?>


<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<MyLiteralMethod xmlns="http://tempuri.org/">
<MyOrderID xmlns="http://www.microsoft.com">string</MyOrderID>
</MyLiteralMethod>
</soap:Body>
</soap:Envelope>

Aplicar los atributos a las clases


Si necesita controlar el espacio de nombres de elementos que ponen en correlación a las clases, puede aplicar
XmlTypeAttribute , XmlRootAttribute y SoapTypeAttribute , según corresponda. En el siguiente ejemplo de código
se aplican los tres a la clase Order .
<XmlType("BigBookService"), _
SoapType("SoapBookService"), _
XmlRoot("BookOrderForm")> _
Public Class Order
' Both types of attributes can be applied. Depending on which
' the method used, either one will affect the call.
<SoapElement(ElementName:= "EncodedOrderID"), _
XmlElement(ElementName:= "LiteralOrderID")> _
public OrderID As String
End Class

[XmlType("BigBooksService", Namespace = "http://www.cpandl.com")]


[SoapType("SoapBookService")]
[XmlRoot("BookOrderForm")]
public class Order {
// Both types of attributes can be applied. Depending on which
// the method used, either one will affect the call.
[SoapElement(ElementName = "EncodedOrderID")]
[XmlElement(ElementName = "LiteralOrderID")]
public String OrderID;
}

Se pueden ver los resultados de aplicar XmlTypeAttribute y SoapTypeAttribute al examinar la descripción del
servicio, como se muestra en el ejemplo de código siguiente.

<s:element name="BookOrderForm" type="s0:BigBookService" />


<s:complexType name="BigBookService">
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="LiteralOrderID" type="s:string" />
</s:sequence>

<s:schema targetNamespace="http://tempuri.org/encodedTypes">
<s:complexType name="SoapBookService">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="EncodedOrderID" type="s:string" />
</s:sequence>
</s:complexType>
</s:schema>
</s:complexType>

El efecto de XmlRootAttribute también se puede ver en el Http GET y resultados de Http POST, como sigue.

<?xml version="1.0" encoding="utf-8"?>


<BookOrderForm xmlns="http://tempuri.org/">
<LiteralOrderID>string</LiteralOrderID>
</BookOrderForm>

Vea también
Serialización SOAP y XML
Atributos que controlan la serialización SOAP codificada
Cómo: Serializar un objeto como secuencia XML con codificación SOAP
Cómo: invalidar la serialización XML SOAP codificada
Introducción a la serialización XML
Cómo: Serialización de un objeto
Cómo: Deserialización de un objeto
Atributos que controlan la serialización SOAP
codificada
16/09/2020 • 2 minutes to read • Edit Online

El documento de World Wide Web Consortium (W3C) denominado Simple Object Access Protocol (SOAP) 1.1
contiene una sección opcional (sección 5) que describe cómo se pueden codificar los parámetros SOAP. Para
ajustarse a la sección 5 de la especificación, debe usar un conjunto especial de atributos que se encuentra en el
espacio de nombres System.Xml.Serialization. Aplique según corresponda esos atributos a las clases y
miembros de clases y, a continuación, utilice XmlSerializer para serializar instancias de la clase o clases.
La tabla siguiente muestra los atributos, donde se pueden aplicar, y lo que hacen. Para más información sobre
el uso de estos atributos para controlar la serialización de XML, consulte Cómo: Serializar un objeto como
secuencia XML con codificación SOAP y Cómo: Invalidar la serialización XML SOAP codificada.
Para obtener más información sobre atributos, vea Atributos.

AT RIB UTO SE A P L IC A A ESP EC IF IC A

SoapAttributeAttribute Campo público, propiedad, parámetro El miembro de clase se serializará


o valor devuelto. como un atributo XML.

SoapElementAttribute Campo público, propiedad, parámetro La clase se serializará como un


o valor devuelto. elemento XML.

SoapEnumAttribute Campo público que es un identificador Nombre de elemento del miembro de


de enumeración. una enumeración.

SoapIgnoreAttribute Propiedades públicas y campos. Se debería omitir la propiedad o


campo cuando se serializa la clase
contenedora.

SoapIncludeAttribute Declaraciones de clase derivada El tipo debería estar incluido al generar


públicas y métodos públicos para los los esquemas (para ser reconocido
documentos de lenguaje de cuando se serializa).
descripción de servicios Web (WSDL).

SoapTypeAttribute Declaraciones de clase públicas. La clase se debería serializar como un


tipo de XML.

Vea también
Serialización SOAP y XML
Cómo: Serializar un objeto como secuencia XML con codificación SOAP
Cómo: Invalidar la serialización XML SOAP codificada
Atributos
XmlSerializer
Cómo: Serialización de un objeto
Cómo: Deserialización de un objeto
Procedimiento para serializar un objeto
16/09/2020 • 2 minutes to read • Edit Online

Para serializar un objeto, primero cree el objeto que será serializado y establezca Debe determinar el formato de
transporte en el que la secuencia XML estará almacenada, o como una secuencia o como un archivo, para ello.
Por ejemplo, si la secuencia XML debe estar guardada en un formulario permanente, cree un objeto FileStream.

NOTE
Para obtener más ejemplos de serialización XML, vea Ejemplos de serialización XML.

Serializar un objeto
1. Cree el objeto y establezca sus campos públicos y propiedades.
2. Construya un XmlSerializer utilizando el tipo de objeto. Para obtener más información, vea los
constructores de clase XmlSerializer .
3. Llame al método Serialize para generar o una secuencia XML o una representación del archivo de las
propiedades públicas del objeto y campos. En el ejemplo siguiente se crea un archivo.

Dim myObject As MySerializableClass = New MySerializableClass()


' Insert code to set properties and fields of the object.
Dim mySerializer As XmlSerializer = New XmlSerializer(GetType(MySerializableClass))
' To write to a file, create a StreamWriter object.
Dim myWriter As StreamWriter = New StreamWriter("myFileName.xml")
mySerializer.Serialize(myWriter, myObject)
myWriter.Close()

MySerializableClass myObject = new MySerializableClass();


// Insert code to set properties and fields of the object.
XmlSerializer mySerializer = new
XmlSerializer(typeof(MySerializableClass));
// To write to a file, create a StreamWriter object.
StreamWriter myWriter = new StreamWriter("myFileName.xml");
mySerializer.Serialize(myWriter, myObject);
myWriter.Close();

Vea también
Introducción a la serialización XML
Cómo: Deserialización de un objeto
Deserialización de un objeto con XmlSerializer
16/09/2020 • 2 minutes to read • Edit Online

Al deserializar un objeto, el formato de transporte determina si creará una secuencia u objeto de archivo. Una
vez determinado el formato de transporte, puede llamar Serialize o los métodos Deserialize, como se requiera.

Para deserializar un objeto


1. Construya unXmlSerializer utilizando el tipo del objeto para deserializar.
2. Llame al método Deserialize para generar una réplica del objeto. Al deserializar, debe convertir el objeto
devuelto al tipo del original, tal y como se muestra en el ejemplo siguiente, que deserializa el objeto en
un archivo (aunque también se podía deserializar en una secuencia).

' Construct an instance of the XmlSerializer with the type


' of object that is being deserialized.
Dim mySerializer As New XmlSerializer(GetType(MySerializableClass))
' To read the file, create a FileStream.
Dim myFileStream As New FileStream("myFileName.xml", FileMode.Open)
' Call the Deserialize method and cast to the object type.
Dim myObject = CType( _
mySerializer.Deserialize(myFileStream), MySerializableClass)

// Construct an instance of the XmlSerializer with the type


// of object that is being deserialized.
var mySerializer = new XmlSerializer(typeof(MySerializableClass));
// To read the file, create a FileStream.
var myFileStream = new FileStream("myFileName.xml", FileMode.Open);
// Call the Deserialize method and cast to the object type.
var myObject = (MySerializableClass) mySerializer.Deserialize(myFileStream)

Vea también
Introducción a la serialización XML
Cómo: Serialización de un objeto
Procedimiento para usar la herramienta de definición
de esquema XML para generar clases y documentos
de esquema XML
16/09/2020 • 3 minutes to read • Edit Online

La herramienta XML Schema Definition (Xsd.exe) le permite generar un esquema XML que describe una clase o
generar la clase definida por un esquema XML. Los procedimientos siguientes muestran cómo realizar estas
operaciones.
La herramienta de definición de esquema XML (Xsd.exe) suele estar en la siguiente ruta de acceso:
C:\Archivos de programa (x86)\Microsoft SDKs\Windows\{versión}\bin\NETFX {versión} Tools\
Para generar clases que cumplen con un esquema concreto
1. Abra un símbolo del sistema.
2. Pasar el esquema XML como un argumento a la herramienta XML Schema Definition, que crea un conjunto
de clases con las que precisamente coinciden el Esquema XML, por ejemplo:

xsd mySchema.xsd

La herramienta solo puede procesar esquemas que hagan referencia a la especificación de World Wide
Web Consortium XML del 16 de marzo de 2001. En otras palabras, el espacio de nombres del esquema
XML debe ser "http://www.w3.org/2001/XMLSchema" como se muestra en el ejemplo siguiente.

<?xml version="1.0" encoding="utf-8"?>


<xs:schema attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace=""
xmlns:xs="http://www.w3.org/2001/XMLSchema" />

3. Modifique las clases con métodos, propiedades o campos, como sea necesario. Para más información sobre
cómo modificar una clase con atributos, vea Controlar la serialización XML mediante atributos y Atributos
que controlan la serialización SOAP codificada.
Es a menudo útil para examinar el esquema de la secuencia XML que se genera cuando se serializan las instancias
de una clase (o clases). Por ejemplo, podría publicar su esquema para que otros lo utilicen o podría compararlo
con un esquema con el que está intentando lograr la conformidad.
Para generar un documento de esquema XML de un conjunto de clases
1. Compile la clase o clases en un archivo DLL.
2. Abra un símbolo del sistema.
3. Pasar el archivo DLL como un argumento a Xsd.exe, por ejemplo:

xsd MyFile.dll

Se escribirá el esquema (o esquemas), comenzando con "schema0.xsd" del nombre.

Vea también
DataSet
Herramienta de definición de esquema XML y serialización XML
Introducción a la serialización XML
Herramienta de definición de esquema XML (Xsd.exe)
XmlSerializer
Cómo: Serialización de un objeto
Cómo: Deserialización de un objeto
Procedimiento para controlar la serialización de
clases derivadas
16/09/2020 • 5 minutes to read • Edit Online

El uso del atributo XmlElementAttribute para cambiar el nombre de un elemento XML no es la única manera de
personalizar la serialización de objeto. También puede personalizar la secuencia XML derivando de una clase
existente e indicando a la instancia XmlSerializer cómo serializar la nueva clase.
Por ejemplo, dada una clase Book , puede derivar de él y crear una clase ExpandedBook que tiene más propiedades.
Pero debe indicarle a XmlSerializer que acepte el tipo derivado al serializar o deserializar. Para ello, cree una
instancia XmlElementAttribute y establezca su propiedad Type en el tipo de clase derivada. Agregue
XmlElementAttribute a una instancia XmlAttributes. Después, agregue XmlAttributes a una instancia
XmlAttributeOverrides. Para ello, especifique el tipo que se invalida y el nombre del miembro que acepta la clase
derivada. Esta implementación se muestra en el ejemplo siguiente.

Ejemplo
Public Class Orders
public Books() As Book
End Class

Public Class Book


public ISBN As String
End Class

Public Class ExpandedBook


Inherits Book
public NewEdition As Boolean
End Class

Public Class Run


Shared Sub Main()
Dim t As Run = New Run()
t.SerializeObject("Book.xml")
t.DeserializeObject("Book.xml")
End Sub

Public Sub SerializeObject(filename As String)


' Each overridden field, property, or type requires
' an XmlAttributes instance.
Dim attrs As XmlAttributes = New XmlAttributes()

' Creates an XmlElementAttribute instance to override the


' field that returns Book objects. The overridden field
' returns Expanded objects instead.
Dim attr As XmlElementAttribute = _
New XmlElementAttribute()
attr.ElementName = "NewBook"
attr.Type = GetType(ExpandedBook)

' Adds the element to the collection of elements.


attrs.XmlElements.Add(attr)

' Creates the XmlAttributeOverrides.


Dim attrOverrides As XmlAttributeOverrides = _
New XmlAttributeOverrides()

' Adds the type of the class that contains the overridden
' Adds the type of the class that contains the overridden
' member, as well as the XmlAttributes instance to override it
' with, to the XmlAttributeOverrides instance.
attrOverrides.Add(GetType(Orders), "Books", attrs)

' Creates the XmlSerializer using the XmlAttributeOverrides.


Dim s As XmlSerializer = _
New XmlSerializer(GetType(Orders), attrOverrides)

' Writing the file requires a TextWriter instance.


Dim writer As TextWriter = New StreamWriter(filename)

' Creates the object to be serialized.


Dim myOrders As Orders = New Orders()

' Creates an object of the derived type.


Dim b As ExpandedBook = New ExpandedBook()
b.ISBN= "123456789"
b.NewEdition = True
myOrders.Books = New ExpandedBook(){b}

' Serializes the object.


s.Serialize(writer,myOrders)
writer.Close()
End Sub

Public Sub DeserializeObject(filename As String)


Dim attrOverrides As XmlAttributeOverrides = _
New XmlAttributeOverrides()
Dim attrs As XmlAttributes = New XmlAttributes()

' Creates an XmlElementAttribute to override the


' field that returns Book objects. The overridden field
' returns Expanded objects instead.
Dim attr As XmlElementAttribute = _
New XmlElementAttribute()
attr.ElementName = "NewBook"
attr.Type = GetType(ExpandedBook)

' Adds the XmlElementAttribute to the collection of objects.


attrs.XmlElements.Add(attr)

attrOverrides.Add(GetType(Orders), "Books", attrs)

' Creates the XmlSerializer using the XmlAttributeOverrides.


Dim s As XmlSerializer = _
New XmlSerializer(GetType(Orders), attrOverrides)

Dim fs As FileStream = New FileStream(filename, FileMode.Open)


Dim myOrders As Orders = CType( s.Deserialize(fs), Orders)
Console.WriteLine("ExpandedBook:")

' The difference between deserializing the overridden


' XML document and serializing it is this: To read the derived
' object values, you must declare an object of the derived type
' and cast the returned object to it.
Dim expanded As ExpandedBook
Dim b As Book
for each b in myOrders.Books
expanded = CType(b, ExpandedBook)
Console.WriteLine(expanded.ISBN)
Console.WriteLine(expanded.NewEdition)
Next
End Sub
End Class

public class Orders


{
{
public Book[] Books;
}

public class Book


{
public string ISBN;
}

public class ExpandedBook:Book


{
public bool NewEdition;
}

public class Run


{
public void SerializeObject(string filename)
{
// Each overridden field, property, or type requires
// an XmlAttributes instance.
XmlAttributes attrs = new XmlAttributes();

// Creates an XmlElementAttribute instance to override the


// field that returns Book objects. The overridden field
// returns Expanded objects instead.
XmlElementAttribute attr = new XmlElementAttribute();
attr.ElementName = "NewBook";
attr.Type = typeof(ExpandedBook);

// Adds the element to the collection of elements.


attrs.XmlElements.Add(attr);

// Creates the XmlAttributeOverrides instance.


XmlAttributeOverrides attrOverrides = new XmlAttributeOverrides();

// Adds the type of the class that contains the overridden


// member, as well as the XmlAttributes instance to override it
// with, to the XmlAttributeOverrides.
attrOverrides.Add(typeof(Orders), "Books", attrs);

// Creates the XmlSerializer using the XmlAttributeOverrides.


XmlSerializer s =
new XmlSerializer(typeof(Orders), attrOverrides);

// Writing the file requires a TextWriter instance.


TextWriter writer = new StreamWriter(filename);

// Creates the object to be serialized.


Orders myOrders = new Orders();

// Creates an object of the derived type.


ExpandedBook b = new ExpandedBook();
b.ISBN= "123456789";
b.NewEdition = true;
myOrders.Books = new ExpandedBook[]{b};

// Serializes the object.


s.Serialize(writer,myOrders);
writer.Close();
}

public void DeserializeObject(string filename)


{
XmlAttributeOverrides attrOverrides =
new XmlAttributeOverrides();
XmlAttributes attrs = new XmlAttributes();

// Creates an XmlElementAttribute to override the


// field that returns Book objects. The overridden field
// returns Expanded objects instead.
// returns Expanded objects instead.
XmlElementAttribute attr = new XmlElementAttribute();
attr.ElementName = "NewBook";
attr.Type = typeof(ExpandedBook);

// Adds the XmlElementAttribute to the collection of objects.


attrs.XmlElements.Add(attr);

attrOverrides.Add(typeof(Orders), "Books", attrs);

// Creates the XmlSerializer using the XmlAttributeOverrides.


XmlSerializer s =
new XmlSerializer(typeof(Orders), attrOverrides);

FileStream fs = new FileStream(filename, FileMode.Open);


Orders myOrders = (Orders) s.Deserialize(fs);
Console.WriteLine("ExpandedBook:");

// The difference between deserializing the overridden


// XML document and serializing it is this: To read the derived
// object values, you must declare an object of the derived type
// and cast the returned object to it.
ExpandedBook expanded;
foreach(Book b in myOrders.Books)
{
expanded = (ExpandedBook)b;
Console.WriteLine(
expanded.ISBN + "\n" +
expanded.NewEdition);
}
}
}

Vea también
XmlSerializer
XmlElementAttribute
XmlAttributes
XmlAttributeOverrides
Serialización SOAP y XML
Cómo: Serialización de un objeto
Cómo: para especificar un nombre de elemento alternativo para una secuencia XML
Procedimiento para especificar un nombre de
elemento alternativo para una secuencia XML
16/09/2020 • 3 minutes to read • Edit Online

UtilizandoXmlSerializer, se puede generar más de una secuencia XML con el mismo conjunto de clases. Puede
que desee proceder de esta forma ya que dos servicios Web XML diferentes requieren la misma información
básica, con solo ligeras diferencias. Por ejemplo, imagine dos servicios Web XML que procesan órdenes para los
libros y así ambos requieren los números de ISBN. Un servicio usa la etiqueta <ISBN> mientras el segundo usa la
etiqueta <BookID>. Tiene una clase denominada Book que contiene un campo denominado ISBN . Cuando se
serializa una instancia de la clase Book , utilizará, de forma predeterminada, el nombre de miembro (ISBN) como
el nombre de elemento de etiqueta. Para el primer servicio Web XML, esto es como esperado. Pero para enviar la
secuencia XML al segundo servicio Web XML, debe invalidar la serialización para que el nombre de elemento de
la etiqueta sea BookID .

Para crear una secuencia XML con un nombre de elemento alternativo


1. Cree una instancia de la clase XmlElementAttribute.
2. Establece el ElementName de XmlElementAttribute a "BookID".
3. Cree una instancia de la clase XmlAttributes.
4. Agregue el objeto XmlElementAttribute a la colección a la que ha accedido mediante la propiedad
XmlElements de XmlAttributes .
5. Cree una instancia de la clase XmlAttributeOverrides.
6. Agregue XmlAttributes a XmlAttributeOverrides, pasando el tipo del objeto para invalidarlo y el nombre
de miembro invalidado.
7. Cree una instancia de la clase XmlSerializer con XmlAttributeOverrides .
8. Cree una instancia de la clase Book y serialice o deserialícela.

Ejemplo
Public Function SerializeOverride()
' Creates an XmlElementAttribute with the alternate name.
Dim myElementAttribute As XmlElementAttribute = _
New XmlElementAttribute()
myElementAttribute.ElementName = "BookID"
Dim myAttributes As XmlAttributes = New XmlAttributes()
myAttributes.XmlElements.Add(myElementAttribute)
Dim myOverrides As XmlAttributeOverrides = New XmlAttributeOverrides()
myOverrides.Add(typeof(Book), "ISBN", myAttributes)
Dim mySerializer As XmlSerializer = _
New XmlSerializer(GetType(Book), myOverrides)
Dim b As Book = New Book()
b.ISBN = "123456789"
' Creates a StreamWriter to write the XML stream to.
Dim writer As StreamWriter = New StreamWriter("Book.xml")
mySerializer.Serialize(writer, b);
End Class
public void SerializeOverride()
{
// Creates an XmlElementAttribute with the alternate name.
XmlElementAttribute myElementAttribute = new XmlElementAttribute();
myElementAttribute.ElementName = "BookID";
XmlAttributes myAttributes = new XmlAttributes();
myAttributes.XmlElements.Add(myElementAttribute);
XmlAttributeOverrides myOverrides = new XmlAttributeOverrides();
myOverrides.Add(typeof(Book), "ISBN", myAttributes);
XmlSerializer mySerializer =
new XmlSerializer(typeof(Book), myOverrides)
Book b = new Book();
b.ISBN = "123456789"
// Creates a StreamWriter to write the XML stream to.
StreamWriter writer = new StreamWriter("Book.xml");
mySerializer.Serialize(writer, b);
}

La secuencia XML puede parecerse a lo siguiente.

<Book>
<BookID>123456789</BookID>
</Book>

Vea también
XmlElementAttribute
XmlAttributes
XmlAttributeOverrides
Serialización SOAP y XML
XmlSerializer
Cómo: Serialización de un objeto
Cómo: Deserialización de un objeto
Procedimiento para calificar elementos XML y
nombres de atributo de XML
16/09/2020 • 4 minutes to read • Edit Online

Los espacios de nombres XML contenidos por instancias de la clase XmlSerializerNamespaces deben cumplir con la
especificación de World Wide Web Consortium (W3C) llamada Espacios de nombres en XML.
Los espacios de nombres XML proporcionan una método para calificar los nombres de elementos y atributos XML
en documentos XML. Un nombre calificado se compone de un prefijo y un nombre local, separados por dos puntos.
El prefijo funciona únicamente como marcador de posición y está asignado a un identificador URI que especifica un
espacio de nombres. La combinación del espacio de nombres del URI, universalmente administrado, y el nombre
local genera un nombre del que se garantiza que es universalmente único.
Creando una instancia de XmlSerializerNamespaces y agregando los pares de espacio de nombres al objeto, puede
especificar los prefijos utilizados en un documento XML.

Para crear nombres calificados en un documento XML


1. Cree una instancia de la clase XmlSerializerNamespaces .
2. Agregue todos los prefijos y pares de espacio de nombres a XmlSerializerNamespaces .
3. Aplique el atributo apropiado System.Xml.Serialization a cada método o clase que XmlSerializer vaya a
serializar en un documento XML.
Los atributos disponibles son: XmlAnyElementAttribute, XmlArrayAttribute, XmlArrayItemAttribute,
XmlAttributeAttribute, XmlElementAttribute, XmlRootAttribute, y XmlTypeAttribute.
4. Establezca la propiedad Namespace de cada atributo en uno de los valores de espacio de nombres del
XmlSerializerNamespaces .
5. Pase XmlSerializerNamespaces al método Serialize de XmlSerializer .

Ejemplo
En el siguiente ejemplo, se crea un XmlSerializerNamespaces al que se agregan dos prefijos y pares de espacio de
nombres al objeto. El código crea XmlSerializer que se utiliza para serializar una instancia de la clase Books . El
código llama al método Serialize con XmlSerializerNamespaces , permitiéndole al XML contener los espacios de
nombres prefijados.
Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization

Public Module Program

Public Sub Main()


SerializeObject("XmlNamespaces.xml")
End Sub

Public Sub SerializeObject(filename As String)


Dim mySerializer As New XmlSerializer(GetType(Books))
' Writing a file requires a TextWriter.
Dim myWriter As New StreamWriter(filename)

' Creates an XmlSerializerNamespaces and adds two


' prefix-namespace pairs.
Dim myNamespaces As New XmlSerializerNamespaces()
myNamespaces.Add("books", "http://www.cpandl.com")
myNamespaces.Add("money", "http://www.cohowinery.com")

' Creates a Book.


Dim myBook As New Book()
myBook.TITLE = "A Book Title"
Dim myPrice As New Price()
myPrice.price = CDec(9.95)
myPrice.currency = "US Dollar"
myBook.PRICE = myPrice
Dim myBooks As New Books()
myBooks.Book = myBook
mySerializer.Serialize(myWriter, myBooks, myNamespaces)
myWriter.Close()
End Sub
End Module

Public Class Books


<XmlElement([Namespace] := "http://www.cohowinery.com")> _
Public Book As Book
End Class

<XmlType([Namespace] := "http://www.cpandl.com")> _
Public Class Book
<XmlElement([Namespace] := "http://www.cpandl.com")> _
Public TITLE As String
<XmlElement([Namespace] := "http://www.cohowinery.com")> _
Public PRICE As Price
End Class

Public Class Price


<XmlAttribute([Namespace] := "http://www.cpandl.com")> _
Public currency As String
<XmlElement([Namespace] := "http://www.cohowinery.com")> _
Public price As Decimal
End Class
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

public class Program


{
public static void Main()
{
SerializeObject("XmlNamespaces.xml");
}

public static void SerializeObject(string filename)


{
var mySerializer = new XmlSerializer(typeof(Books));
// Writing a file requires a TextWriter.
TextWriter myWriter = new StreamWriter(filename);

// Creates an XmlSerializerNamespaces and adds two


// prefix-namespace pairs.
var myNamespaces = new XmlSerializerNamespaces();
myNamespaces.Add("books", "http://www.cpandl.com");
myNamespaces.Add("money", "http://www.cohowinery.com");

// Creates a Book.
var myBook = new Book();
myBook.TITLE = "A Book Title";
var myPrice = new Price();
myPrice.price = (decimal) 9.95;
myPrice.currency = "US Dollar";
myBook.PRICE = myPrice;
var myBooks = new Books();
myBooks.Book = myBook;
mySerializer.Serialize(myWriter, myBooks, myNamespaces);
myWriter.Close();
}
}

public class Books


{
[XmlElement(Namespace = "http://www.cohowinery.com")]
public Book Book;
}

[XmlType(Namespace ="http://www.cpandl.com")]
public class Book
{
[XmlElement(Namespace = "http://www.cpandl.com")]
public string TITLE;
[XmlElement(Namespace ="http://www.cohowinery.com")]
public Price PRICE;
}

public class Price


{
[XmlAttribute(Namespace = "http://www.cpandl.com")]
public string currency;
[XmlElement(Namespace = "http://www.cohowinery.com")]
public decimal price;
}

Vea también
XmlSerializer
Herramienta de definición de esquema XML y serialización XML
Introducción a la serialización XML
XmlSerializer (clase)
Atributos que controlan la serialización XML
Cómo: Especificar un nombre de elemento alternativo para una secuencia XML
Cómo: Serialización de un objeto
Cómo: Deserialización de un objeto
Procedimiento para serializar un objeto como
secuencia XML con codificación SOAP
16/09/2020 • 2 minutes to read • Edit Online

Dado que un mensaje SOAP se genera mediante XML, se puede usar la clase XmlSerializer para serializar las
clases y generar mensajes SOAP codificados. El XML resultante se ajusta a la sección 5 del documento de World
Wide Web Consortium, "Protocolo simple de acceso a objetos (SOAP) 1.1". Si está creando un servicio Web XML
que se comunica a través de mensajes SOAP, puede personalizar la secuencia XML aplicando un conjunto de
atributos SOAP especiales a las clases y miembros de clases. Para obtener más información, vea Atributos que
controlan la serialización SOAP codificada.
Para serializar un objeto como secuencia XML con codificación SOAP
1. Cree la clase mediante la herramienta de definición de esquema XML (Xsd.exe).
2. Aplique uno o más de los atributos especiales situados en System.Xml.Serialization . Vea la lista en
"Atributos que controlan la serialización SOAP codificada".
3. Cree XmlTypeMapping creando un nuevo SoapReflectionImporter e invocando el método ImportTypeMapping
con el tipo de la clase serializada.
En el ejemplo de código siguiente se llama al método ImportTypeMapping de la clase
SoapReflectionImporter para crear XmlTypeMapping .

' Serializes a class named Group as a SOAP message.


Dim myTypeMapping As XmlTypeMapping =
New SoapReflectionImporter().ImportTypeMapping(GetType(Group))

// Serializes a class named Group as a SOAP message.


XmlTypeMapping myTypeMapping =
new SoapReflectionImporter().ImportTypeMapping(typeof(Group));

4. Cree una instancia de la clase XmlSerializer pasando XmlTypeMapping al constructor


XmlSerializer(XmlTypeMapping).

Dim mySerializer As XmlSerializer = New XmlSerializer(myTypeMapping)

XmlSerializer mySerializer = new XmlSerializer(myTypeMapping);

5. Llame al método Serialize o Deserialize .

Ejemplo
' Serializes a class named Group as a SOAP message.
Dim myTypeMapping As XmlTypeMapping =
New SoapReflectionImporter().ImportTypeMapping(GetType(Group))
Dim mySerializer As XmlSerializer = New XmlSerializer(myTypeMapping)
// Serializes a class named Group as a SOAP message.
XmlTypeMapping myTypeMapping =
new SoapReflectionImporter().ImportTypeMapping(typeof(Group));
XmlSerializer mySerializer = new XmlSerializer(myTypeMapping);

Vea también
Serialización SOAP y XML
Atributos que controlan la serialización SOAP codificada
Serialización XML con servicios web XML
Cómo: Serialización de un objeto
Cómo: Deserializar un objeto
Cómo: Invalidar la serialización XML SOAP codificada
Procedimiento para invalidar la serialización XML
SOAP codificada
16/09/2020 • 5 minutes to read • Edit Online

El proceso para invalidar serialización XML de objetos como los mensajes SOAP es similar al proceso para
invalidar la serialización XML estándar. Para obtener más información sobre la invalidación de la serialización XML
estándar, vea Cómo: Especificar un nombre de elemento alternativo para una secuencia XML.

Para invalidar serialización de objetos como mensajes SOAP


1. Cree una instancia de la clase SoapAttributeOverrides.
2. Cree un SoapAttributes para cada miembro de clase que se serializa.
3. Cree una instancia de uno o más de los atributos que afectan a la serialización XML, según corresponda, al
miembro a serializándose. Para obtener más información, vea "Atributos que controlan la serialización de
SOAP codificada".
4. Establezca la propiedad adecuada de SoapAttributes en el atributo creado en el paso 3.
5. Agregue SoapAttributes a SoapAttributeOverrides .
6. Cree XmlTypeMapping mediante SoapAttributeOverrides . Utilice el método
SoapReflectionImporter.ImportTypeMapping .
7. Cree XmlSerializer mediante XmlTypeMapping .
8. Serialice o deserialice el objeto.

Ejemplo
El ejemplo de código siguiente serializa un archivo de dos maneras: primero, sin invalidar el comportamiento de
clase XmlSerializer y segundo, invalidando el comportamiento. El ejemplo contiene una clase denominada
Group con varios miembros. Varios atributos, como SoapElementAttribute , se han aplicado a los miembros de
clase. Cuando la clase se serializa con el método SerializeOriginal , los atributos controlan el contenido del
mensaje SOAP. Cuando se llama al método SerializeOverride , el comportamiento de XmlSerializer se invalida
creando varios atributos y estableciendo las propiedades de SoapAttributes en esos atributos (según
corresponda).

using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Schema;

public class Group


{
[SoapAttribute (Namespace = "http://www.cpandl.com")]
public string GroupName;

[SoapAttribute(DataType = "base64Binary")]
public Byte [] GroupNumber;

[SoapAttribute(DataType = "date", AttributeName = "CreationDate")]


public DateTime Today;
public DateTime Today;
[SoapElement(DataType = "nonNegativeInteger", ElementName = "PosInt")]
public string PositiveInt;
// This is ignored when serialized unless it is overridden.
[SoapIgnore]
public bool IgnoreThis;

public GroupType Grouptype;

[SoapInclude(typeof(Car))]
public Vehicle myCar(string licNumber)
{
Vehicle v;
if(licNumber == "")
{
v = new Car();
v.licenseNumber = "!!!!!!";
}
else
{
v = new Car();
v.licenseNumber = licNumber;
}
return v;
}
}

public abstract class Vehicle


{
public string licenseNumber;
public DateTime makeDate;
}

public class Car: Vehicle


{
}

public enum GroupType


{
// These enums can be overridden.
small,
large
}

public class Run


{
public static void Main()
{
Run test = new Run();
test.SerializeOriginal("SoapOriginal.xml");
test.SerializeOverride("SoapOverrides.xml");
test.DeserializeOriginal("SoapOriginal.xml");
test.DeserializeOverride("SoapOverrides.xml");

}
public void SerializeOriginal(string filename)
{
// Creates an instance of the XmlSerializer class.
XmlTypeMapping myMapping =
(new SoapReflectionImporter().ImportTypeMapping(
typeof(Group)));
XmlSerializer mySerializer =
new XmlSerializer(myMapping);

// Writing the file requires a TextWriter.


TextWriter writer = new StreamWriter(filename);

// Creates an instance of the class that will be serialized.


Group myGroup = new Group();
// Sets the object properties.
myGroup.GroupName = ".NET";

Byte [] hexByte = new Byte[2]{Convert.ToByte(100),


Convert.ToByte(50)};
myGroup.GroupNumber = hexByte;

DateTime myDate = new DateTime(2002,5,2);


myGroup.Today = myDate;

myGroup.PositiveInt= "10000";
myGroup.IgnoreThis=true;
myGroup.Grouptype= GroupType.small;
Car thisCar =(Car) myGroup.myCar("1234566");

// Prints the license number just to prove the car was created.
Console.WriteLine("License#: " + thisCar.licenseNumber + "\n");

// Serializes the class and closes the TextWriter.


mySerializer.Serialize(writer, myGroup);
writer.Close();
}

public void SerializeOverride(string filename)


{
// Creates an instance of the XmlSerializer class
// that overrides the serialization.
XmlSerializer overRideSerializer = CreateOverrideSerializer();

// Writing the file requires a TextWriter.


TextWriter writer = new StreamWriter(filename);

// Creates an instance of the class that will be serialized.


Group myGroup = new Group();

// Sets the object properties.


myGroup.GroupName = ".NET";

Byte [] hexByte = new Byte[2]{Convert.ToByte(100),


Convert.ToByte(50)};
myGroup.GroupNumber = hexByte;

DateTime myDate = new DateTime(2002,5,2);


myGroup.Today = myDate;

myGroup.PositiveInt= "10000";
myGroup.IgnoreThis=true;
myGroup.Grouptype= GroupType.small;
Car thisCar =(Car) myGroup.myCar("1234566");

// Serializes the class and closes the TextWriter.


overRideSerializer.Serialize(writer, myGroup);
writer.Close();
}

public void DeserializeOriginal(string filename)


{
// Creates an instance of the XmlSerializer class.
XmlTypeMapping myMapping =
(new SoapReflectionImporter().ImportTypeMapping(
typeof(Group)));
XmlSerializer mySerializer =
new XmlSerializer(myMapping);

TextReader reader = new StreamReader(filename);

// Deserializes and casts the object.


Group myGroup;
myGroup = (Group) mySerializer.Deserialize(reader);

Console.WriteLine(myGroup.GroupName);
Console.WriteLine(myGroup.GroupNumber[0]);
Console.WriteLine(myGroup.GroupNumber[1]);
Console.WriteLine(myGroup.Today);
Console.WriteLine(myGroup.PositiveInt);
Console.WriteLine(myGroup.IgnoreThis);
Console.WriteLine();
}

public void DeserializeOverride(string filename)


{
// Creates an instance of the XmlSerializer class.
XmlSerializer overRideSerializer = CreateOverrideSerializer();
// Reading the file requires a TextReader.
TextReader reader = new StreamReader(filename);

// Deserializes and casts the object.


Group myGroup;
myGroup = (Group) overRideSerializer.Deserialize(reader);

Console.WriteLine(myGroup.GroupName);
Console.WriteLine(myGroup.GroupNumber[0]);
Console.WriteLine(myGroup.GroupNumber[1]);
Console.WriteLine(myGroup.Today);
Console.WriteLine(myGroup.PositiveInt);
Console.WriteLine(myGroup.IgnoreThis);
}

private XmlSerializer CreateOverrideSerializer()


{
SoapAttributeOverrides mySoapAttributeOverrides =
new SoapAttributeOverrides();
SoapAttributes soapAtts = new SoapAttributes();

SoapElementAttribute mySoapElement = new SoapElementAttribute();


mySoapElement.ElementName = "xxxx";
soapAtts.SoapElement = mySoapElement;
mySoapAttributeOverrides.Add(typeof(Group), "PositiveInt",
soapAtts);

// Overrides the IgnoreThis property.


SoapIgnoreAttribute myIgnore = new SoapIgnoreAttribute();
soapAtts = new SoapAttributes();
soapAtts.SoapIgnore = false;
mySoapAttributeOverrides.Add(typeof(Group), "IgnoreThis",
soapAtts);

// Overrides the GroupType enumeration.


soapAtts = new SoapAttributes();
SoapEnumAttribute xSoapEnum = new SoapEnumAttribute();
xSoapEnum.Name = "Over1000";
soapAtts.SoapEnum = xSoapEnum;

// Adds the SoapAttributes to the


// mySoapAttributeOverrides.
mySoapAttributeOverrides.Add(typeof(GroupType), "large",
soapAtts);

// Creates a second enumeration and adds it.


soapAtts = new SoapAttributes();
xSoapEnum = new SoapEnumAttribute();
xSoapEnum.Name = "ZeroTo1000";
soapAtts.SoapEnum = xSoapEnum;
mySoapAttributeOverrides.Add(typeof(GroupType), "small",
soapAtts);

// Overrides the Group type.


soapAtts = new SoapAttributes();
SoapTypeAttribute soapType = new SoapTypeAttribute();
soapType.TypeName = "Team";
soapAtts.SoapType = soapType;
mySoapAttributeOverrides.Add(typeof(Group),soapAtts);

// Creates an XmlTypeMapping that is used to create an instance


// of the XmlSerializer class. Then returns the XmlSerializer.
XmlTypeMapping myMapping = (new SoapReflectionImporter(
mySoapAttributeOverrides)).ImportTypeMapping(typeof(Group));

XmlSerializer ser = new XmlSerializer(myMapping);


return ser;
}
}

Vea también
Serialización SOAP y XML
Atributos que controlan la serialización SOAP codificada
Serialización XML con servicios web XML
Cómo: Serialización de un objeto
Cómo: Deserializar un objeto
Cómo: Serializar un objeto como secuencia XML con codificación SOAP
<system.xml.serialization> (Elemento)
16/09/2020 • 2 minutes to read • Edit Online

El elemento de nivel superior para controlar la serialización XML. Para más información sobre los archivos de
configuración, vea Configuration File Schema (Esquema de archivos de configuración).
<configuration>
<system.xml.serialization>

Sintaxis
<system.xml.serialization>
</system.xml.serialization>

Atributos y elementos
En las siguientes secciones se describen los atributos, los elementos secundarios y los elementos primarios.
Atributos
Ninguno.
Elementos secundarios
EL EM EN TO DESC RIP C IÓ N

Elemento <dateTimeSerialization> Determina el modo de serialización XML de los objetos


DateTime.

Elemento <schemaImporterExtensions> Contiene tipos que son utilizados por XmlSchemaImporter


para asignar de los tipos XSD a los tipos de .NET Framework.

Elementos primarios
EL EM EN TO DESC RIP C IÓ N

Elemento <configuration> Elemento raíz necesario en cada archivo de configuración


utilizado por Common Language Runtime y las aplicaciones
de .NET Framework.

Ejemplo
El ejemplo de código siguiente muestra cómo especificar el modo de la serialización de un objeto DateTime y la
suma de tipos utilizada por XmlSchemaImporter al asignar los tipos XSD a los tipos de .NET Framework.
<system.xml.serialization>
<xmlSerializer checkDeserializeAdvances="false" />
<dateTimeSerialization mode = "Local" />
<schemaImporterExtensions>
<add
name = "MobileCapabilities"
type = "System.Web.Mobile.MobileCapabilities,
System.Web.Mobile, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f6f11d40a3a" />
</schemaImporterExtensions>
</system.xml.serialization>

Vea también
XmlSchemaImporter
DateTimeSerializationSection.DateTimeSerializationMode
Esquema de los archivos de configuración
Elemento <dateTimeSerialization>
Elemento <schemaImporterExtensions>
<add> Elemento para <schemaImporterExtensions>
<dateTimeSerialization> (Elemento)
16/09/2020 • 2 minutes to read • Edit Online

Determina el modo de serialización XML de los objetos DateTime.


<configuration>
<dateTimeSerialization>

Sintaxis
<dateTimeSerialization
mode = "Roundtrip|Local"
/>

Atributos y elementos
En las siguientes secciones se describen los atributos, los elementos secundarios y los elementos primarios.
Atributos
AT RIB UTO S DESC RIP C IÓ N

mode Opcional. Especifica el modo de serialización. Establece uno de


los valores de
DateTimeSerializationSection.DateTimeSerializationMode . El
valor predeterminado es RoundTrip .

Elementos secundarios
Ninguno.
Elementos primarios
EL EM EN TO DESC RIP C IÓ N

system.xml.serialization El elemento de nivel superior para controlar la serialización


XML.

Comentarios
En las versiones 1.0, 1.1, 2.0 y posteriores de .NET Framework, cuando esta propiedad se establece en Local , los
objetos DateTime siempre reciben formato según la hora local. Es decir, la información de zona horaria local
siempre se incluye con los datos serializados. Establezca esta propiedad en Local para garantizar la compatibilidad
con las versiones anteriores de .NET Framework.
En la versión 2.0 y posteriores de .NET Framework que tienen esta propiedad establecida en Roundtrip , los
objetos DateTime se examinan para determinar si están en la zona horaria local, UTC o una no especificada. Los
objetos DateTime se serializan a continuación de este tipo de manera que esta información se conserva. Éste es el
comportamiento predeterminado y el que se recomienda para todas las aplicaciones nuevas que no funcionan con
versiones anteriores de .Net Framework.
Vea también
DateTime
XmlSchemaImporter
DateTimeSerializationSection.DateTimeSerializationMode
Esquema de los archivos de configuración
Elemento <schemaImporterExtensions>
<add> Elemento para <schemaImporterExtensions>
Elemento <system.xml.serialization>
<schemaImporterExtensions> (Elemento)
16/09/2020 • 2 minutes to read • Edit Online

Contiene tipos que son utilizados por XmlSchemaImporter para asignar de los tipos XSD a los tipos de .NET
Framework. Para más información sobre los archivos de configuración, vea Configuration File Schema (Esquema
de archivos de configuración).

Sintaxis
<schemaImporterExtensions>
<!-- Add types -->
</schemaImporterExtensions>

Elementos secundarios
EL EM EN TO DESC RIP C IÓ N

<add> Elemento para <schemaImporterExtensions> Agrega tipos que son usados por XmlSchemaImporter para
crear las asignaciones.

Elementos primarios
EL EM EN TO DESC RIP C IÓ N

Elemento <system.xml.serialization> El elemento de nivel superior para controlar la serialización


XML.

Ejemplo
El ejemplo de código siguiente muestra cómo agregar tipos que son utilizados por XmlSchemaImporter al asignar
los tipos XSD a los tipos de .NET Framework.

<system.xml.serialization>
<schemaImporterExtensions>
<add name = "MobileCapabilities" type =
"System.Web.Mobile.MobileCapabilities,
System.Web.Mobile, Version - 2.0.0.0, Culture = neutral,
PublicKeyToken = b03f5f6f11d40a3a" />
</schemaImporterExtensions>
</system.xml.serialization>

Vea también
XmlSchemaImporter
DateTimeSerializationSection.DateTimeSerializationMode
Esquema de los archivos de configuración
Elemento <dateTimeSerialization>
<add> Elemento para <schemaImporterExtensions>
Elemento <system.xml.serialization>
Elemento <add> para <schemaImporterExtensions>
16/09/2020 • 2 minutes to read • Edit Online

Agrega tipos utilizados por XmlSchemaImporter para asignar los tipos XSD a los tipos de .NET Framework. Para
más información sobre los archivos de configuración, vea Configuration File Schema (Esquema de archivos de
configuración).
<configuration>
<system.xml.serialization>
<schemaImporterExtensions>
<add>

Sintaxis
<add name = "typeName" type="fully qualified type [,Version=version number] [,Culture=culture]
[,PublicKeyToken= token]"/>

Atributos y elementos
En las siguientes secciones se describen los atributos, los elementos secundarios y los elementos primarios.
Atributos
AT RIB UTO DESC RIP C IÓ N

name Un nombre sencillo que se utiliza para buscar la instancia.

type Obligatorio. Especifica la clase de extensión de esquema que


se agrega. El valor del atributo type debe estar en una línea e
incluye el nombre de tipo completo. Cuando el ensamblado se
encuentra en la caché global de ensamblados (GAC), también
debe incluir la versión, la referencia cultural y el token de clave
pública del ensamblado firmado.

Elementos secundarios
Ninguno.
Elementos primarios
EL EM EN TO DESC RIP C IÓ N

<schemaImporterExtensions> Contiene los tipos que utiliza XmlSchemaImporter .

Ejemplo
El ejemplo de código siguiente agrega un tipo de extensión que XmlSchemaImporter puede utilizar al asignar los
tipos.
<configuration>
<system.xml.serialization>
<schemaImporterExtensions>
<add name="contoso" type="System.Web.Mobile.MobileCapabilities,
System.Web.Mobile, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" />
</schemaImporterExtensions>
</system.xml.serialization>
</configuration>

Vea también
XmlSchemaImporter
Elemento <system.xml.serialization>
Elemento <schemaImporterExtensions>
<xmlSerializer> (Elemento)
16/09/2020 • 2 minutes to read • Edit Online

Especifica si se hace una comprobación adicional de progreso de XmlSerializer.


<configuration>
<system.xml.serialization>

Sintaxis
<xmlSerializer checkDeserializerAdvance = "true|false" />

Atributos y elementos
En las siguientes secciones se describen los atributos, los elementos secundarios y los elementos primarios.
Atributos
AT RIB UTO DESC RIP C IÓ N

checkDeserializeAdvances Especifica si se comprueba el progreso de XmlSerializer.


Establezca el atributo a "verdadero" o "falso." El valor
predeterminado es "true".

useLegacySerializationGeneration Especifica si XmlSerializer usa generación de serialización


heredada, que genera ensamblados escribiendo código de C#
en un archivo y después compilándolo en un ensamblado. El
valor predeterminado es false .

Elementos secundarios
Ninguno.
Elementos primarios
EL EM EN TO DESC RIP C IÓ N

Elemento <system.xml.serialization> Contiene la configuración para XmlSerializer y las clases


XmlSchemaImporter.

Comentarios
De forma predeterminada, XmlSerializer proporciona una capa adicional de seguridad contra los ataques por
denegación de servicio potenciales al deserializar datos que no son de confianza. Actúa de esta modo intentando
detectar los bucles sin fin durante la deserialización. Si se detecta este tipo de condición, se inicia una excepción con
el siguiente mensaje: "Error interno: la deserialización no ha podido avanzar sobre la secuencia subyacente".
Recibir este mensaje necesariamente no indica que un ataque por denegación de servicio está en curso. En algunas
circunstancias raras, el mecanismo de detección de bucle sin fin genera un positivo falso y la excepción se
producirá para un mensaje entrante legítimo. Si detecta que en la aplicación concreta esta capa adicional de
protección está rechazando los mensajes legítimos, establezca el atributo checkDeserializeAdvances en "false".
Ejemplo
En el ejemplo de código siguiente se establece el atributo checkDeserializeAdvances en "false".

<configuration>
<system.xml.serialization>
<xmlSerializer checkDeserializeAdvances="false" />
</system.xml.serialization>
</configuration>

Vea también
XmlSerializer
Elemento <system.xml.serialization>
Serialización SOAP y XML
Herramienta Generador de serializador XML
(Sgen.exe)
16/09/2020 • 5 minutes to read • Edit Online

El Generador de serializador XML crea un ensamblado de serialización XML para los tipos de un ensamblado
especificado. El ensamblado de serialización mejora el rendimiento de inicio de un elemento XmlSerializer al
serializar o deserializar objetos de los tipos especificados.

Sintaxis
Ejecute la herramienta desde la línea de comandos.

sgen [options]

TIP
Para que las herramientas de .NET Framework funcionen como deben, hay que configurar correctamente las variables de
entorno Path , Include y Lib . Establezca estas variables de entorno ejecutando SDKVars.bat, que se encuentra en el
directorio <SDK>\v2.0\Bin. SDKVars.bat debe ejecutarse en cada shell de comando.

Parámetros
O P C IÓ N DESC RIP C IÓ N

/a[ssembly]: nombre_de_archivo Genera código de serialización para todos los tipos incluidos
en el ensamblado o la aplicación ejecutable especificados por
filename. Solo se puede proporcionar un nombre de archivo.
Si se repite este argumento, se utilizará el último nombre.

/c[ompiler]: opciones Especifica las opciones que se deben pasar al compilador de


C#. Todas las opciones de csc.exe se admiten tal como se
pasan al compilador. Esto puede servir para especificar que se
debería firmar el ensamblado, así como para especificar el
archivo de clave.

/d[ebug] Genera un imagen que se puede utilizar con un depurador.

/f[orce] Exige que se sobrescriba un ensamblado existente que tenga


el mismo nombre. El valor predeterminado es false .

/help o /? Muestra las opciones y la sintaxis de los comandos para la


herramienta.

/k[eep] Suprime la eliminación de los archivos de código fuente


generados y otros archivos temporales después de que se
han compilado en el ensamblado de serialización. Puede
servir para determinar si la herramienta genera código de
serialización para un tipo determinado.
O P C IÓ N DESC RIP C IÓ N

/n[ologo] Suprime la presentación de la portada de inicio de Microsoft.

/o[ut]: ruta_de_acceso Especifica el directorio en el que se debe guardar el


ensamblado generado. Nota: El nombre del ensamblado
generado está compuesto por el nombre del ensamblado de
entrada y "xmlSerializers.dll".

/p[roxytypes] Solo se genera código de serialización para los tipos de proxy


de servicio Web XML.

/r[eference]: archivos_de_ensamblado Especifica los ensamblados a los que hacen referencia los
tipos que requieren serialización XML. Acepta varios archivos
de ensamblado separados por comas.

/s[ilent] Suprime la presentación de mensajes de aprobación.

/t[ype]: tipo Solo genera código de serialización para el tipo especificado.

/v[erbose] Muestra resultados detallados para la depuración. Enumera


tipos del ensamblado de destino que no se pueden serializar
con XmlSerializer.

/? Muestra las opciones y la sintaxis de los comandos para la


herramienta.

Comentarios
Cuando no se utiliza el Generador de serializador XML, un objeto XmlSerializer genera código de serialización y
un ensamblado de serialización para cada tipo siempre que se ejecuta una aplicación. Para mejorar el rendimiento
del inicio de la serialización XML, use la herramienta Sgen.exe a fin de generar esos ensamblados con antelación.
Estos ensamblados se podrán implementar después con la aplicación.
El Generador de serializador XML también puede mejorar el rendimiento de los clientes que utilizan proxy de
servicio Web XML para comunicarse con los servidores, dado que no se verá afectado el rendimiento del proceso
de serialización la primera vez que se carga el tipo.
Estos ensamblados generados no se pueden utilizar en el lado del servidor de un servicio Web. Esta herramienta
solo es para los clientes de servicios Web y escenarios de serialización manual.
Si el ensamblado que contiene el tipo que se debe serializar se denomina MyType.dll, el ensamblado de
serialización asociado se denominará MyType.XmlSerializers.dll.

Ejemplos
Mediante el siguiente comando se crea un ensamblado denominado Data.XmlSerializers.dll para serializar todos
los tipos que contiene el ensamblado denominado Data.dll.

sgen Data.dll

Se puede hacer referencia al ensamblado Data.XmlSerializers.dll desde código cuando se necesite serializar y
deserializar los tipos en Data.dll.
Vea también
Herramientas
Símbolos del sistema
XML Schema Definition Tool (Xsd.exe)
16/09/2020 • 18 minutes to read • Edit Online

La herramienta Definición de esquemas XML (Xsd.exe) genera clases de esquemas XML o de Common Language
Runtime a partir de archivos XDR, XML y XSD, o a partir de clases de un ensamblado de motor en tiempo de
ejecución.
La herramienta de definición de esquema XML (Xsd.exe) suele estar en la siguiente ruta de acceso:
C:\Archivos de programa (x86)\Microsoft SDKs\Windows\{versión}\bin\NETFX {versión} Tools\

Sintaxis
Ejecute la herramienta desde la línea de comandos.

xsd file.xdr [-outputdir:directory][/parameters:file.xml]


xsd file.xml [-outputdir:directory] [/parameters:file.xml]
xsd file.xsd {/classes | /dataset} [/element:element]
[/enableLinqDataSet] [/language:language]
[/namespace:namespace] [-outputdir:directory] [URI:uri]
[/parameters:file.xml]
xsd {file.dll | file.exe} [-outputdir:directory] [/type:typename [...]][/parameters:file.xml]

TIP
Para que las herramientas de .NET Framework funcionen como deben, hay que configurar correctamente las variables de
entorno Path , Include y Lib . Establezca estas variables de entorno ejecutando SDKVars.bat, que se encuentra en el
directorio <SDK>\v2.0\Bin. SDKVars.bat debe ejecutarse en cada shell de comando.

Argumento
A RGUM EN TO DESC RIP C IÓ N
A RGUM EN TO DESC RIP C IÓ N

file.extension Especifica el archivo de entrada que se desea convertir. Debe


especificar alguna de las siguientes extensiones: .xdr, .xml,
.xsd, .dll o .exe.

Si se especifica un archivo de esquema XDR (extensión .xdr),


Xsd.exe convierte el esquema XDR en un esquema XSD. El
archivo de salida tiene el mismo nombre que el del esquema
XDR, pero con la extensión .xsd.

Si se especifica un archivo XML (extensión .xml), Xsd.exe


deduce, por los datos del archivo, que se trata de un
esquema y genera un esquema XSD. El archivo de salida
tiene el mismo nombre que el archivo XML, pero con la
extensión .xsd.

Si se especifica un archivo de esquema XML (extensión .xsd),


Xsd.exe genera código fuente para objetos de motor en
tiempo de ejecución que corresponden al esquema XML.

Si se especifica un archivo de ensamblado de motor en


tiempo de ejecución (extensión .exe o .dll), Xsd.exe genera
esquemas para uno o más tipos de ese ensamblado. Se
puede utilizar la opción /type para especificar los tipos
para los que se generan esquemas. Los esquemas de salida
se denominan schema0.xsd, schema1.xsd, etc. Xsd.exe genera
varios esquemas solo si los tipos dados especifican un
espacio de nombres mediante el atributo personalizado
XMLRoot .

Opciones generales
O P C IÓ N DESC RIP C IÓ N

/h[elp] Muestra las opciones y la sintaxis de los comandos para la


herramienta.

/o[utputdir]: directory Especifica el directorio de los archivos de salida. Este


argumento sólo puede aparecer una vez. El valor
predeterminado es el directorio actual.

/? Muestra las opciones y la sintaxis de los comandos para la


herramienta.

/p[arameters]: file.xml Lee las opciones de los distintos modos de operación desde
el archivo .xml especificado. La forma abreviada es /p: . Para
más información, vea la sección Comentarios.

Opciones de archivos XSD


Se debe especificar sólo una de las opciones siguientes de archivos .xsd.

O P C IÓ N DESC RIP C IÓ N
O P C IÓ N DESC RIP C IÓ N

/c[lasses] Genera clases que corresponden al esquema especificado.


Para leer datos XML del objeto, use el método
XmlSerializer.Deserialize.

/d[ataset] Genera una clase derivada de DataSet que corresponde al


esquema especificado. Para leer datos XML de la clase
derivada, use el método DataSet.ReadXml.

También se puede especificar cualquiera de las opciones siguientes de archivos .xsd.

O P C IÓ N DESC RIP C IÓ N

/e[lement]: element Especifica el elemento del esquema para el que se genera


código. De forma predeterminada se escriben todos los
elementos. Este argumento se puede especificar varias veces.

/enableDataBinding Implementa la interfaz INotifyPropertyChanged en todos los


tipos generados para habilitar el enlace de datos. La forma
abreviada es /edb .

/enableLinqDataSet (Forma abreviada: /eld ). Especifica que el Conjunto de


datos generado se puede consultar utilizando LINQ a
Conjunto de datos. Esta opción se utiliza cuando también se
especifica la opción /dataset. Para más información, vea LINQ
to DataSet Overview (Información general sobre LINQ to
DataSet) y Querying Typed DataSets (Consultar objetos
DataSet con tipo). Para obtener información general sobre
cómo usar LINQ, vea Language Integrated Query (LINQ)
(C#) o Language Integrated Query (LINQ) (Visual Basic).

/f[ields] Genera campos en lugar de propiedades. De manera


predeterminada, se generan propiedades.

/l[anguage]: language Especifica el lenguaje de programación que se utiliza. Se


puede elegir entre CS (C#, que es el valor predeterminado),
VB (Visual Basic), JS (JScript) o VJS (Visual J#). También
se puede especificar un nombre completo para una clase que
implemente System.CodeDom.Compiler.CodeDomProvider.

/n[amespace]: namespace Especifica el espacio de nombres del motor en tiempo de


ejecución para los tipos generados. El espacio de nombres
predeterminado es Schemas .

/nologo Suprime la pancarta.

/order Genera identificadores de orden explícitos en todos los


miembros de partícula.

/o[ut]: directoryName Especifica el directorio de salida en el que se colocan los


archivos. El valor predeterminado es el directorio actual.

/u[ri]: uri Especifica el identificador URI de los elementos del esquema


para el que se genera código. Este identificador URI, si existe,
se aplica a todos los elementos especificados con la opción
/element .
Opciones de archivos DLL y EXE
O P C IÓ N DESC RIP C IÓ N

/t[ype]: typename Especifica el nombre del tipo para el que se crea un esquema.
Se pueden especificar varios argumentos de tipo. Si
typename no especifica un espacio de nombres, Xsd.exe
busca todos los tipos del ensamblado con el tipo
especificado. Si typename especifica un espacio de nombres,
solo se busca ese tipo. Si typename termina con carácter de
asterisco (*), la herramienta busca todos los tipos que
empiezan con la cadena anterior a *. Si se omite la opción
/type , Xsd.exe genera esquemas para todos los tipos del
ensamblado.

Comentarios
En la siguiente tabla se muestran las operaciones que realiza Xsd.exe.

XDR a XSD Genera un esquema XML a partir de un archivo de esquema


reducido de datos XML. XDR es un formato de esquemas
anterior basado en XML.

XML a XSD Genera un esquema XML a partir de un archivo XML.

XSD a DataSet Genera clases DataSet de Common Language Runtime a


partir de un archivo de esquema XSD. Las clases generadas
proporcionan un modelo de objetos completo para datos
XML regulares.

XSD a clases Genera clases de motor en tiempo de ejecución a partir de


un archivo de esquema XSD. Las clases generadas se pueden
usar conjuntamente con
System.Xml.Serialization.XmlSerializer para leer y escribir
código XML que sigue al esquema.

Clases a XSD Genera un esquema XML a partir de un tipo o tipos de un


archivo de ensamblado de motor en tiempo de ejecución. El
esquema generado define el formato XML usado por
XmlSerializer.

Xsd.exe solo permite manipular esquemas XML que siguen al lenguaje de definición de esquemas XML (XSD)
propuesto por el consorcio World Wide Web (W3C). Para más información sobre la propuesta XSD (lenguaje de
definición de esquemas XML) o la norma XML, vea https://w3.org.

Establecer opciones con un archivo XML


El uso del modificador /parameters permite especificar un único archivo XML que establece distintas opciones.
Las opciones que pueden establecerse dependen de la forma en que se esté utilizando la herramienta XSD.exe.
Entre estas opciones se incluyen la generación de esquemas, archivos de código o archivos de código que
incluyen características DataSet . Por ejemplo, el elemento <assembly> puede establecerse en el nombre de un
archivo ejecutable (.exe) o de biblioteca de tipos (.dll) al generar un esquema, pero no al generar un archivo de
código. En el siguiente XML se muestra la forma de utilizar el elemento <generateSchemas> con un archivo
ejecutable especificado:
<!-- This is in a file named GenerateSchemas.xml. -->
<xsd xmlns='http://microsoft.com/dotnet/tools/xsd/'>
<generateSchemas>
<assembly>ConsoleApplication1.exe</assembly>
</generateSchemas>
</xsd>

Si el XML anterior está incluido en un archivo denominado GenerateSchemas.xml, use el modificador


/parameters escribiendo lo siguiente en una línea de comandos y presionando Entrar :

xsd /p:GenerateSchemas.xml

Por otro lado, si se estuviese generando un esquema para un tipo único situado en el ensamblado, se podría
utilizar el siguiente código XML:

<!-- This is in a file named GenerateSchemaFromType.xml. -->


<xsd xmlns='http://microsoft.com/dotnet/tools/xsd/'>
<generateSchemas>
<type>IDItems</type>
</generateSchemas>
</xsd>

Pero para poder utilizar el código anterior, habría que especificar también el nombre del ensamblado en el
símbolo del sistema. Escriba lo siguiente en el símbolo del sistema (se da por hecho que el nombre del
archivo XML es GenerateSchemaFromType.xml):

xsd /p:GenerateSchemaFromType.xml ConsoleApplication1.exe

Solo se debe especificar una de las siguientes opciones para el elemento <generateSchemas> .

EL EM EN TO DESC RIP C IÓ N

<assembly> Especifica el ensamblado a partir del cual generar el


esquema.

<type> Especifica un tipo situado en un ensamblado para el que


generar un esquema.

<xml> Especifica un archivo XML para el que generar un esquema.

<xdr> Especifica un archivo XDR para el que generar un esquema.

Para generar un archivo de código, utilice el elemento <generateClasses> . En el siguiente ejemplo se genera un
archivo de código. Observe que se muestran también dos atributos que permiten establecer el lenguaje de
programación y el espacio de nombres del archivo generado.

<xsd xmlns='http://microsoft.com/dotnet/tools/xsd/'>
<generateClasses language='VB' namespace='Microsoft.Serialization.Examples'/>
</xsd>
<!-- You must supply an .xsd file when typing in the command line.-->
<!-- For example: xsd /p:genClasses mySchema.xsd -->

Entre las opciones que se pueden establecer para el elemento <generateClasses> se incluyen las siguientes.
EL EM EN TO DESC RIP C IÓ N

<element> Especifica un elemento del archivo .xsd para el que generar


código.

<schemaImporterExtensions> Especifica un tipo derivado de la clase


SchemaImporterExtension.

<schema> Especifica un archivo de esquema XML para el que generar


un código. Se pueden especificar varios archivos de esquema
XML con varios elementos <schema>.

En la siguiente tabla se muestran los atributos que también pueden utilizarse con el elemento <generateClasses>
.

AT RIB UTO DESC RIP C IÓ N

lenguaje Especifica el lenguaje de programación que se utiliza. Se


puede elegir entre CS (C#, que es el valor predeterminado),
VB (Visual Basic), JS (JScript) o VJS (Visual J#). También
se puede especificar un nombre completo para una clase que
implemente CodeDomProvider.

namespace Especifica el espacio de nombres del código generado. El


espacio de nombres debe ajustarse a los estándares CLR (por
ejemplo, no debe incluir espacios ni caracteres de barra
diagonal inversa).

opciones Uno de los siguientes valores: none , properties (genera


propiedades en lugar de campos públicos), order o
enableDataBinding (vea los modificadores /order y
/enableDataBinding en la sección Opciones de archivos
XSD anterior).

También se puede controlar la forma en que se genera el código DataSet mediante el uso del elemento
<generateDataSet> . El siguiente XML especifica que el código generado usa estructuras DataSet (como la clase
DataTable) para crear código de Visual Basic para un elemento especificado. Las estructuras de DataSet
generadas admitirán consultas LINQ.

<xsd xmlns='http://microsoft.com/dotnet/tools/xsd/'>
<generateDataSet language='VB' namespace='Microsoft.Serialization.Examples' enableLinqDataSet='true'>
</generateDataSet>
</xsd>

Entre las opciones que se pueden establecer para el elemento <generateDataSet> se incluyen las siguientes.

EL EM EN TO DESC RIP C IÓ N

<schema> Especifica un archivo de esquema XML para el que generar


un código. Se pueden especificar varios archivos de esquema
XML con varios elementos <schema>.

En la siguiente tabla se muestran los atributos que también pueden utilizarse con el elemento <generateDataSet>
.
AT RIB UTO DESC RIP C IÓ N

enableLinqDataSet Especifica que el Conjunto de datos generado se puede


consultar utilizando LINQ a Conjunto de datos. El valor
predeterminado es false.

lenguaje Especifica el lenguaje de programación que se utiliza. Se


puede elegir entre CS (C#, que es el valor predeterminado),
VB (Visual Basic), JS (JScript) o VJS (Visual J#). También
se puede especificar un nombre completo para una clase que
implemente CodeDomProvider.

namespace Especifica el espacio de nombres del código generado. El


espacio de nombres debe ajustarse a los estándares CLR (por
ejemplo, no debe incluir espacios ni caracteres de barra
diagonal inversa).

Hay atributos que pueden establecerse en el elemento <xsd> de nivel superior. Estas opciones pueden utilizarse
con cualquiera de los elementos secundarios ( <generateSchemas> , <generateClasses> o <generateDataSet> ). El
siguiente código XML genera código para un elemento denominado "IDItems" del directorio de resultados
denominado "MyOutputDirectory".

<xsd xmlns='http://microsoft.com/dotnet/tools/xsd/' output='MyOutputDirectory'>


<generateClasses>
<element>IDItems</element>
</generateClasses>
</xsd>

En la siguiente tabla se muestran los atributos que también pueden utilizarse con el elemento <xsd> .

AT RIB UTO DESC RIP C IÓ N

salida Nombre del directorio donde se colocará el esquema o


archivo de código generado.

nologo Suprime la pancarta. Se establece en true o false .

ayuda Muestra las opciones y la sintaxis de los comandos para la


herramienta. Se establece en true o false .

Ejemplos
El comando siguiente genera un esquema XML a partir de myFile.xdr y lo guarda en el directorio actual.

xsd myFile.xdr

El comando siguiente genera un esquema XML a partir de myFile.xml y lo guarda en el directorio especificado.

xsd myFile.xml /outputdir:myOutputDir

El siguiente comando genera un conjunto de datos correspondiente al esquema especificado en el lenguaje C# y


guarda estos datos como XSDSchemaFile.cs en el directorio actual.
xsd /dataset /language:CS XSDSchemaFile.xsd

El comando siguiente genera esquemas XML para todos los tipos del ensamblado myAssembly.dll , y los guarda
como schema0.xsd en el directorio actual.

xsd myAssembly.dll

Vea también
DataSet
System.Xml.Serialization.XmlSerializer
Herramientas
Símbolos del sistema
Información general de LINQ to DataSet
Consultar objetos DataSet con tipo
LINQ (Language-Integrated Query) (C#)
LINQ (Language-Integrated Query) (Visual Basic)
E/S de archivos y secuencias
16/09/2020 • 17 minutes to read • Edit Online

La E/S (entrada/salida) de archivos y secuencias hace referencia a la transferencia de datos con destino u origen
en un medio de almacenamiento. En .NET Framework, los espacios de nombres System.IO contienen tipos que
permiten la lectura y escritura, tanto sincrónica como asincrónica, en archivos y flujos de datos. Estos espacios de
nombres también contienen tipos que realizan la compresión y la descompresión de archivos, así como tipos que
permiten la comunicación a través de canalizaciones y puertos de serie.
Un archivo es una colección de bytes ordenada y con nombre que tiene un almacenamiento persistente. Cuando
se trabaja con archivos, se opera con las rutas de acceso de directorios, almacenamiento en disco y nombres de
archivo y de directorio. En cambio, una secuencia es una sucesión de bytes que se puede utilizar para leer y
escribir en una memoria auxiliar, que puede ser uno de los distintos tipos de medios de almacenamiento (por
ejemplo, discos o memoria). Al igual que hay varios tipos de memorias auxiliares distintas de los discos, existen
varios tipos de secuencias distintas de las secuencias de archivo, como las secuencias de red, de memoria y de
canalización.

Archivos y directorios
Se pueden utilizar los tipos del espacio de nombres System.IO para interactuar con archivos y directorios. Por
ejemplo, se pueden obtener y establecer las propiedades de los archivos y directorios, y recuperar colecciones de
archivos y de directorios basándose en criterios de búsqueda.
Para obtener las convenciones de nomenclatura de las rutas de acceso y las maneras de expresar una ruta de
acceso de archivo para los sistemas Windows, incluidos con la sintaxis de dispositivo DOS compatible en .NET
Core 1.1 y versiones posteriores, y .NET Framework 4.6.2 y versiones posteriores, vea Formatos de ruta de acceso
de archivo en los sistemas Windows.
Estas son algunas clases de archivo y directorio de uso general:
File: proporciona métodos estáticos para crear, copiar, eliminar, mover y abrir archivos, y ayuda a crear un
objeto FileStream.
FileInfo: proporciona métodos de instancia para crear, copiar, eliminar, mover y abrir archivos, y ayuda a
crear un objeto FileStream.
Directory: proporciona métodos estáticos para crear, mover y enumerar directorios y subdirectorios.
DirectoryInfo: proporciona métodos de instancia para crear, mover y enumerar directorios y
subdirectorios.
Path: proporciona métodos y propiedades para procesar cadenas de directorio entre plataformas.
Siempre debe proporcionar un control de excepciones sólido al llamar a métodos del sistema de archivos. Para
más información, vea Control de errores de E/S.
Además de usar estas clases, los usuarios de Visual Basic pueden usar los métodos y propiedades
proporcionados por la clase Microsoft.VisualBasic.FileIO.FileSystem para la E/S de archivos.
Vea Cómo: Copiar directorios, Cómo: Crear una lista de directorios y Cómo: Enumerar directorios y archivos.

Secuencias
La clase base abstracta Stream es compatible con bytes de lectura y escritura. Todas las clases que representan
secuencias heredan de la clase Stream. La clase Stream y sus clases derivadas proporcionan una visión genérica
de los repositorios y los orígenes de datos, y evitan que el programador tenga que ocuparse de los detalles
específicos del sistema operativo y los dispositivos subyacentes.
Las secuencias comprenden tres operaciones fundamentales:
Lectura: transferencia de datos desde una secuencia a una estructura de datos como, por ejemplo, una
matriz de bytes.
Escritura: transferencia de datos a una secuencia desde un origen de datos.
Búsqueda: consulta y modificación de la posición actual en una secuencia.
Dependiendo del repositorio o el origen de datos subyacente, una secuencia puede admitir solo algunas de estas
características. Por ejemplo, la clase PipeStream no admite operaciones de búsqueda. Las propiedades CanRead,
CanWrite y CanSeek de una secuencia especifican las operaciones que admite.
Estas son algunas de las clases de secuencias de uso general:
FileStream: para leer y escribir en un archivo.
IsolatedStorageFileStream: para leer y escribir en un archivo en almacenamiento aislado.
MemoryStream: para leer y escribir en la memoria como una memoria auxiliar.
BufferedStream: para mejorar el rendimiento de las operaciones de lectura y escritura.
NetworkStream: para leer y escribir sobre los sockets de red.
PipeStream: para leer y escribir sobre canalizaciones anónimas y con nombre.
CryptoStream: para vincular secuencias de datos con transformaciones criptográficas.
Para obtener un ejemplo de cómo trabajar con flujos de forma asincrónica, vea E/S de archivos asincrónica.

Lectores y escritores
El espacio de nombres System.IO también proporciona tipos para leer los caracteres codificados de las
secuencias y escribirlos en ellas. Normalmente, las secuencias están diseñadas para la entrada y salida de bytes.
Los tipos de lectura y escritura controlan la conversión de caracteres codificados en bytes y a la inversa, para que
la secuencia pueda completar la operación. Cada clase de lectura y escritura se asocia a una secuencia, que se
puede recuperar mediante la propiedad BaseStream de la clase.
Estas son algunas clases de lectura y escritura de uso general:
BinaryReader y BinaryWriter: para leer y escribir tipos de datos primitivos como valores binarios.
StreamReader y StreamWriter: para leer y escribir caracteres utilizando un valor de codificación para
convertir los caracteres en bytes y a la inversa.
StringReader y StringWriter: para leer y escribir caracteres en cadenas.
TextReader y TextWriter: sirven como clases base abstractas para otros lectores y escritores que leen y
escriben caracteres y cadenas, pero no datos binarios.
Vea Cómo: Leer texto de un archivo, Cómo: Escribir texto en un archivo, Cómo: Leer caracteres de una cadena y
Cómo: Escribir caracteres en una cadena.

Operaciones de E/S asincrónicas


Leer o escribir una gran cantidad de datos puede requerir muchos recursos. Estas tareas se deben realizar de
forma asincrónica si la aplicación necesita seguir respondiendo al usuario. Con las operaciones de E/S
sincrónicas, el subproceso de la interfaz de usuario está bloqueado hasta que se completa la operación que usa
gran cantidad de recursos. Use operaciones de E/S asincrónicas al desarrollar aplicaciones para la Tienda
Windows 8.x y, de este modo, evitar crear la impresión de que la aplicación ya no funciona.
Los miembros asincrónicos contienen Async en sus nombres, como los métodos CopyToAsync, FlushAsync,
ReadAsync y WriteAsync. Estos métodos se usan con las palabras clave async y await .
Para más información, consulte E/S de archivos asincrónica.

Compresión
La compresión se refiere al proceso de reducir el tamaño de un archivo para su almacenamiento. La
descompresión es el proceso de extraer el contenido de un archivo comprimido para que esté en un formato
utilizable. El espacio de nombres System.IO.Compression contiene tipos para comprimir y descomprimir archivos
y secuencias.
Las clases siguientes se utilizan con frecuencia al comprimir y descomprimir archivos y secuencias:
ZipArchive: para crear y recuperar entradas en el archivo zip.
ZipArchiveEntry: para representar un archivo comprimido.
ZipFile: para crear, extraer y abrir un paquete comprimido.
ZipFileExtensions: para crear y extraer entradas en un paquete comprimido.
DeflateStream: para comprimir y descomprimir secuencias utilizando el algoritmo de deflación (Deflate).
GZipStream: para comprimir y descomprimir secuencias con formato de datos gzip.
Vea Cómo: Comprimir y extraer archivos.

Almacenamiento aislado
El almacenamiento aislado es un mecanismo de almacenamiento de datos que proporciona aislamiento y
seguridad mediante la definición de modos estándar de asociar código a los datos guardados. El
almacenamiento proporciona un sistema de archivos virtual que está aislado para cada usuario, ensamblado y
(opcionalmente) dominio. El almacenamiento aislado es especialmente útil cuando la aplicación no tiene permiso
para obtener acceso a los archivos del usuario. Se pueden guardar los valores o los archivos de la aplicación de
una forma controlada por la directiva de seguridad del equipo.
El almacenamiento aislado no está disponible para aplicaciones de la Tienda Windows 8.x, sino que deben usarse
las clases de datos de aplicaciones del espacio de nombres Windows.Storage. Para más información, vea Datos
de la aplicación.
Las clases siguientes se utilizan con frecuencia al implementar el almacenamiento aislado:
IsolatedStorage: proporciona la clase base para las implementaciones de almacenamiento aislado.
IsolatedStorageFile: proporciona un área de almacenamiento aislado que contiene archivos y directorios.
IsolatedStorageFileStream: expone un archivo dentro del almacenamiento aislado.
Vea Almacenamiento aislado.

Operaciones de E/S en aplicaciones de Microsoft Store


El conjunto de .NET para las aplicaciones de la Tienda Windows 8.x contiene muchos de los tipos necesarios para
leer y escribir en secuencias. Sin embargo, dicho conjunto no incluye todos los tipos de E/S de .NET Framework.
Estas son algunas diferencias importantes que se deben tener en cuenta al utilizar operaciones de E/S en las
aplicaciones de la Tienda Windows 8.x:
Los tipos relacionados específicamente con las operaciones de archivo, como File, FileInfo, Directory y
DirectoryInfo, no se incluyen en el conjunto de .NET para las aplicaciones de la Tienda Windows 8.x. En su
lugar, use los tipos en el espacio de nombres Windows.Storage de Windows Runtime, como StorageFile y
StorageFolder.
El almacenamiento aislado no está disponible; use en su lugar datos de aplicaciones.
Use métodos asincrónicos, como ReadAsync y WriteAsync, para evitar bloquear el subproceso de interfaz
de usuario.
Los tipos de compresión que se basan en rutas de acceso ZipFile y ZipFileExtensions no están disponibles.
En su lugar, use los tipos del espacio de nombres Windows.Storage.Compression.
Si es necesario, puede convertir entre las secuencias de .NET Framework y las secuencias de Windows Runtime.
Para obtener más información, vea Cómo: Convertir flujos de .NET Framework en flujos de Windows Runtime y
viceversa o WindowsRuntimeStreamExtensions.
Para obtener más información sobre las operaciones de E/S en una aplicación de la Tienda Windows 8.x, vea
Inicio rápido: lectura y escritura de un archivo.

E/S y seguridad
Cuando se utilizan las clases del espacio de nombres System.IO, se deben seguir los requisitos de seguridad del
sistema operativo como las listas de control de acceso (ACL) para controlar el acceso a los archivos y directorios.
Este requisito es adicional a cualquier requisito FileIOPermission. Se pueden administrar las listas de control de
acceso mediante programación. Para obtener más información, vea Cómo: Agregar o quitar entradas de la lista
de control de acceso.
Las políticas de seguridad predeterminadas impiden que las aplicaciones de intranet o Internet obtengan acceso
a los archivos del equipo del usuario. Por lo tanto, no use las clases de E/S que requieren una ruta a un archivo
físico al escribir el código que se descarga a través de Internet o una intranet. En su lugar, use el almacenamiento
aislado para las aplicaciones tradicionales de .NET Framework, o bien datos de aplicaciones para las aplicaciones
de la Tienda Windows 8.x.
Solo se realiza una comprobación de seguridad cuando se construye la secuencia. Por consiguiente, no abra una
secuencia y se la pase al código o a los dominios de aplicación de menos confianza.

Temas relacionados
Tareas de E/S comunes
Proporciona una lista de tareas de E/S asociadas a los archivos, directorios y secuencias, y vínculos al
contenido y los ejemplos pertinentes para cada tarea.
E/S de archivos asincrónica
Describe las ventajas de rendimiento y el funcionamiento básico de la E/S asincrónica.
Almacenamiento aislado
Describe un mecanismo de almacenamiento de datos que proporciona aislamiento y seguridad mediante
la definición de las formas estándar de asociar código a los datos guardados.
Canalizaciones
Describe las operaciones anónimas y de canalización con nombre de .NET Framework.
Archivos asignados a memoria
Describe los archivos asignados a memoria, que incluyen el contenido de archivos en disco en memoria
virtual. Puede usar archivos asignados a memoria para editar archivos muy grandes y crear memoria
compartida para la comunicación entre procesos.
Formatos de ruta de acceso de archivo en los
sistemas Windows
16/09/2020 • 29 minutes to read • Edit Online

Los miembros de muchos de los tipos del espacio de nombres System.IO incluyen un parámetro path que
permite especificar una ruta de acceso absoluta o relativa a un recurso de sistema de archivos. Después, esta ruta
de acceso se pasa a las API del sistema de archivos de Windows. En este tema se describen los formatos de las
rutas de acceso de archivo que se pueden usar en los sistemas Windows.

Rutas de acceso DOS tradicionales


Una ruta de acceso DOS estándar puede constar de tres componentes:
Una letra de volumen o unidad seguida por el separador de volumen ( : ).
Un nombre de directorio. El carácter separador de directorio separa los subdirectorios dentro de la jerarquía de
directorios anidados.
Un nombre de archivo opcional. El carácter separador de directorio separa la ruta de acceso y el nombre del
archivo.
Si los tres componentes están presentes, la ruta de acceso es absoluta. Si no se especifica la letra de volumen o
unidad y el nombre de directorio comienza por el carácter separador de directorio, la ruta de acceso es relativa con
respecto a la raíz de la unidad actual. En caso contrario, la ruta de acceso es relativa al directorio actual. En la tabla
siguiente se muestran algunas rutas de acceso de directorio y archivo posibles.

RUTA DE A C C ESO DESC RIP C IÓ N

C:\Documents\Newsletters\Summer2018.pdf Ruta de acceso de archivo absoluta desde la raíz de la unidad


C: .

\Program Files\Custom Utilities\StringFinder.exe Ruta de acceso absoluta desde la raíz de la unidad actual.

2018\January.xlsx Ruta de acceso relativa a un archivo en un subdirectorio del


directorio actual.

..\Publications\TravelBrochure.pdf Ruta de acceso relativa a un archivo en un directorio del


mismo nivel del directorio actual.

C:\Projects\apilibrary\apilibrary.sln Ruta de acceso absoluta a un archivo desde la raíz de la


unidad C: .

C:Projects\apilibrary\apilibrary.sln Ruta de acceso relativa desde el directorio actual de la unidad


C: .
IMPORTANT
Observe la diferencia entre las dos últimas rutas de acceso. En ambas figura el especificador de volumen opcional ( C: en
ambos casos), pero la primera comienza por la raíz del volumen especificado, mientras que la segunda no. Como resultado, la
primera es una ruta de acceso absoluta desde el directorio raíz de la unidad C: , mientras que la segunda es una ruta de
acceso relativa desde el directorio actual de la unidad C: . El uso de la segunda forma cuando está previsto el de la primera
suele ser motivo de errores que implican rutas de acceso de archivo de Windows.

Puede determinar si una ruta de acceso de archivo es un nombre completo (es decir, si la ruta de acceso es
independiente del directorio actual y no cambia cuando cambia el directorio actual) mediante una llamada al
método IsPathFullyQualified. Tenga en cuenta que una ruta de acceso de este tipo puede incluir segmentos de
directorio relativos ( . y .. ) y seguir siendo completa si la ruta de acceso resuelta siempre apunta a la misma
ubicación.
En el ejemplo siguiente se muestra la diferencia entre las rutas de acceso absolutas y relativas. Se supone que
existe el directorio D:\FY2018\ y que aún no ha establecido ningún directorio actual para D:\ desde el símbolo
del sistema antes de ejecutar el ejemplo.

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;

public class Example


{
public static void Main(string[] args)
{
Console.WriteLine($"Current directory is '{Environment.CurrentDirectory}'");
Console.WriteLine("Setting current directory to 'C:\\'");

Directory.SetCurrentDirectory(@"C:\");
string path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:FY2018");
Console.WriteLine($"'D:FY2018' resolves to {path}");

Console.WriteLine("Setting current directory to 'D:\\Docs'");


Directory.SetCurrentDirectory(@"D:\Docs");

path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:FY2018");

// This will be "D:\Docs\FY2018" as it happens to match the drive of the current directory
Console.WriteLine($"'D:FY2018' resolves to {path}");

Console.WriteLine("Setting current directory to 'C:\\'");


Directory.SetCurrentDirectory(@"C:\");

path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");

// This will be either "D:\FY2018" or "D:\FY2018\FY2018" in the subprocess. In the sub process,
// the command prompt set the current directory before launch of our application, which
// sets a hidden environment variable that is considered.
path = Path.GetFullPath(@"D:FY2018");
Console.WriteLine($"'D:FY2018' resolves to {path}");

if (args.Length < 1)
{
Console.WriteLine(@"Launching again, after setting current directory to D:\FY2018");
Uri currentExe = new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase, UriKind.Absolute);
string commandLine = $"/C cd D:\\FY2018 & \"{currentExe.LocalPath}\" stop";
string commandLine = $"/C cd D:\\FY2018 & \"{currentExe.LocalPath}\" stop";
ProcessStartInfo psi = new ProcessStartInfo("cmd", commandLine); ;
Process.Start(psi).WaitForExit();

Console.WriteLine("Sub process returned:");


path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:FY2018");
Console.WriteLine($"'D:FY2018' resolves to {path}");
}
Console.WriteLine("Press any key to continue... ");
Console.ReadKey();
}
}
// The example displays the following output:
// Current directory is 'C:\Programs\file-paths'
// Setting current directory to 'C:\'
// 'D:\FY20

También podría gustarte